Merge branch 'master' into tudor_merge_os_master

This commit is contained in:
tudor.malene@gmail.com
2018-10-02 15:50:20 +01:00
510 changed files with 10259 additions and 5920 deletions

View File

@ -13,8 +13,8 @@ import net.corda.node.internal.DataSourceFactory
import net.corda.node.internal.NodeWithInfo
import net.corda.node.services.Permissions
import net.corda.node.services.config.PasswordEncryption
import net.corda.testing.node.internal.NodeBasedTest
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.node.internal.NodeBasedTest
import org.apache.activemq.artemis.api.core.ActiveMQSecurityException
import org.apache.shiro.authc.credential.DefaultPasswordService
import org.junit.After
@ -33,7 +33,6 @@ import kotlin.test.assertFailsWith
*/
@RunWith(Parameterized::class)
class AuthDBTests : NodeBasedTest() {
private lateinit var node: NodeWithInfo
private lateinit var client: CordaRPCClient
private lateinit var db: UsersDB

View File

@ -12,10 +12,15 @@ import net.corda.core.messaging.startFlow
import net.corda.core.utilities.getOrThrow
import net.corda.node.internal.NodeStartup
import net.corda.node.services.Permissions.Companion.startFlow
import net.corda.nodeapi.exceptions.InternalNodeException
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.NodeHandle
import net.corda.testing.driver.NodeParameters
import net.corda.testing.driver.driver
import net.corda.testing.node.User
import net.corda.testing.node.internal.startNode
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Test
import java.io.*
@ -24,12 +29,22 @@ import kotlin.test.assertEquals
class BootTests {
@Test
fun `java deserialization is disabled`() {
driver(DriverParameters(notarySpecs = emptyList())) {
val user = User("u", "p", setOf(startFlow<ObjectInputStreamFlow>()))
val future = CordaRPCClient(startNode(rpcUsers = listOf(user)).getOrThrow().rpcAddress).
start(user.username, user.password).proxy.startFlow(::ObjectInputStreamFlow).returnValue
assertThatThrownBy { future.getOrThrow() }
.isInstanceOf(CordaRuntimeException::class.java)
val user = User("u", "p", setOf(startFlow<ObjectInputStreamFlow>()))
val devParams = NodeParameters(providedName = BOB_NAME, rpcUsers = listOf(user))
val params = NodeParameters(rpcUsers = listOf(user))
fun NodeHandle.attemptJavaDeserialization() {
CordaRPCClient(rpcAddress).use(user.username, user.password) { connection ->
connection.proxy
rpc.startFlow(::ObjectInputStreamFlow).returnValue.getOrThrow()
}
}
driver {
val devModeNode = startNode(devParams).getOrThrow()
val node = startNode(ALICE_NAME, devMode = false, parameters = params).getOrThrow()
assertThatThrownBy { devModeNode.attemptJavaDeserialization() }.isInstanceOf(CordaRuntimeException::class.java)
assertThatThrownBy { node.attemptJavaDeserialization() }.isInstanceOf(InternalNodeException::class.java)
}
}

View File

@ -4,15 +4,14 @@ import net.corda.core.crypto.Crypto
import net.corda.core.internal.div
import net.corda.core.utilities.getOrThrow
import net.corda.node.services.config.configureDevKeyAndTrustStores
import net.corda.nodeapi.internal.config.SSLConfiguration
import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.driver
import net.corda.testing.internal.stubs.CertificateStoreStubs
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Test
import java.nio.file.Path
import javax.security.auth.x500.X500Principal
class NodeKeystoreCheckTest {
@ -21,22 +20,20 @@ class NodeKeystoreCheckTest {
driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) {
assertThatThrownBy {
startNode(customOverrides = mapOf("devMode" to false)).getOrThrow()
}.hasMessageContaining("Identity certificate not found")
}.hasMessageContaining("One or more keyStores (identity or TLS) or trustStore not found.")
}
}
@Test
fun `node should throw exception if cert path doesn't chain to the trust root`() {
driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) {
// Create keystores
// Create keystores.
val keystorePassword = "password"
val config = object : SSLConfiguration {
override val keyStorePassword: String = keystorePassword
override val trustStorePassword: String = keystorePassword
override val certificatesDirectory: Path = baseDirectory(ALICE_NAME) / "certificates"
override val crlCheckSoftFail: Boolean = true
}
config.configureDevKeyAndTrustStores(ALICE_NAME)
val certificatesDirectory = baseDirectory(ALICE_NAME) / "certificates"
val signingCertStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory, keystorePassword)
val p2pSslConfig = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory, keyStorePassword = keystorePassword, trustStorePassword = keystorePassword)
p2pSslConfig.configureDevKeyAndTrustStores(ALICE_NAME, signingCertStore, certificatesDirectory)
// This should pass with correct keystore.
val node = startNode(
@ -48,8 +45,8 @@ class NodeKeystoreCheckTest {
node.stop()
// Fiddle with node keystore.
config.loadNodeKeyStore().update {
// Self signed root
signingCertStore.get().update {
// 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)

View File

@ -19,6 +19,7 @@ import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.MAX_MESSAGE_SIZE
import net.corda.testing.core.TestIdentity
import net.corda.testing.driver.PortAllocation
import net.corda.testing.internal.stubs.CertificateStoreStubs
import net.corda.testing.internal.rigorousMock
import org.apache.activemq.artemis.api.core.Message.HDR_DUPLICATE_DETECTION_ID
import org.apache.activemq.artemis.api.core.RoutingType
@ -27,7 +28,6 @@ import org.junit.Assert.assertArrayEquals
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.security.KeyStore
import java.util.*
import kotlin.test.assertEquals
@ -168,44 +168,54 @@ class AMQPBridgeTest {
}
private fun createArtemis(sourceQueueName: String?): Triple<ArtemisMessagingServer, ArtemisMessagingClient, BridgeManager> {
val baseDir = temporaryFolder.root.toPath() / "artemis"
val certificatesDirectory = baseDir / "certificates"
val p2pSslConfiguration = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory)
val signingCertificateStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory)
val artemisConfig = rigorousMock<AbstractNodeConfiguration>().also {
doReturn(temporaryFolder.root.toPath() / "artemis").whenever(it).baseDirectory
doReturn(baseDir).whenever(it).baseDirectory
doReturn(ALICE_NAME).whenever(it).myLegalName
doReturn("trustpass").whenever(it).trustStorePassword
doReturn(certificatesDirectory).whenever(it).certificatesDirectory
doReturn(signingCertificateStore).whenever(it).signingCertificateStore
doReturn(p2pSslConfiguration).whenever(it).p2pSslOptions
doReturn(true).whenever(it).crlCheckSoftFail
doReturn("cordacadevpass").whenever(it).keyStorePassword
doReturn(artemisAddress).whenever(it).p2pAddress
doReturn(null).whenever(it).jmxMonitoringHttpPort
}
artemisConfig.configureWithDevSSLCertificate()
val artemisServer = ArtemisMessagingServer(artemisConfig, artemisAddress.copy(host = "0.0.0.0"), MAX_MESSAGE_SIZE)
val artemisClient = ArtemisMessagingClient(artemisConfig, artemisAddress, MAX_MESSAGE_SIZE)
val artemisClient = ArtemisMessagingClient(artemisConfig.p2pSslOptions, artemisAddress, MAX_MESSAGE_SIZE)
artemisServer.start()
artemisClient.start()
val bridgeManager = AMQPBridgeManager(artemisConfig, artemisAddress, MAX_MESSAGE_SIZE)
val bridgeManager = AMQPBridgeManager(artemisConfig.p2pSslOptions, artemisAddress, MAX_MESSAGE_SIZE)
bridgeManager.start()
val artemis = artemisClient.started!!
if (sourceQueueName != null) {
// Local queue for outgoing messages
artemis.session.createQueue(sourceQueueName, RoutingType.ANYCAST, sourceQueueName, true)
bridgeManager.deployBridge(sourceQueueName, amqpAddress, setOf(BOB.name))
bridgeManager.deployBridge(sourceQueueName, listOf(amqpAddress), setOf(BOB.name))
}
return Triple(artemisServer, artemisClient, bridgeManager)
}
private fun createAMQPServer(maxMessageSize: Int = MAX_MESSAGE_SIZE): AMQPServer {
val baseDir = temporaryFolder.root.toPath() / "server"
val certificatesDirectory = baseDir / "certificates"
val p2pSslConfiguration = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory)
val signingCertificateStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory)
val serverConfig = rigorousMock<AbstractNodeConfiguration>().also {
doReturn(temporaryFolder.root.toPath() / "server").whenever(it).baseDirectory
doReturn(BOB_NAME).whenever(it).myLegalName
doReturn("trustpass").whenever(it).trustStorePassword
doReturn("cordacadevpass").whenever(it).keyStorePassword
doReturn(certificatesDirectory).whenever(it).certificatesDirectory
doReturn(signingCertificateStore).whenever(it).signingCertificateStore
doReturn(p2pSslConfiguration).whenever(it).p2pSslOptions
}
serverConfig.configureWithDevSSLCertificate()
val keyStore = serverConfig.p2pSslOptions.keyStore.get()
val amqpConfig = object : AMQPConfiguration {
override val keyStore: KeyStore = serverConfig.loadSslKeyStore().internal
override val keyStorePrivateKeyPassword: CharArray = serverConfig.keyStorePassword.toCharArray()
override val trustStore: KeyStore = serverConfig.loadTrustStore().internal
override val keyStore = keyStore
override val trustStore = serverConfig.p2pSslOptions.trustStore.get()
override val trace: Boolean = true
override val maxMessageSize: Int = maxMessageSize
}

View File

@ -13,7 +13,8 @@ import net.corda.core.utilities.seconds
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.configureWithDevSSLCertificate
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_PREFIX
import net.corda.nodeapi.internal.config.SSLConfiguration
import net.corda.nodeapi.internal.config.CertificateStoreSupplier
import net.corda.nodeapi.internal.config.MutualSslConfiguration
import net.corda.nodeapi.internal.crypto.*
import net.corda.nodeapi.internal.protonwrapper.messages.MessageStatus
import net.corda.nodeapi.internal.protonwrapper.netty.AMQPClient
@ -27,6 +28,7 @@ import net.corda.testing.driver.PortAllocation
import net.corda.testing.internal.DEV_INTERMEDIATE_CA
import net.corda.testing.internal.DEV_ROOT_CA
import net.corda.testing.internal.rigorousMock
import net.corda.testing.internal.stubs.CertificateStoreStubs
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x509.*
import org.bouncycastle.cert.jcajce.JcaX509CRLConverter
@ -50,7 +52,6 @@ import java.io.Closeable
import java.math.BigInteger
import java.net.InetSocketAddress
import java.security.KeyPair
import java.security.KeyStore
import java.security.PrivateKey
import java.security.Security
import java.security.cert.X509CRL
@ -61,6 +62,7 @@ import javax.ws.rs.Path
import javax.ws.rs.Produces
import javax.ws.rs.core.Response
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class CertificateRevocationListNodeTests {
@Rule
@ -80,6 +82,32 @@ class CertificateRevocationListNodeTests {
private abstract class AbstractNodeConfiguration : NodeConfiguration
companion object {
fun createRevocationList(clrServer: CrlServer, signatureAlgorithm: String, caCertificate: X509Certificate,
caPrivateKey: PrivateKey,
endpoint: String,
indirect: Boolean,
vararg serialNumbers: BigInteger): X509CRL {
println("Generating CRL for $endpoint")
val builder = JcaX509v2CRLBuilder(caCertificate.subjectX500Principal, Date(System.currentTimeMillis() - 1.minutes.toMillis()))
val extensionUtils = JcaX509ExtensionUtils()
builder.addExtension(Extension.authorityKeyIdentifier,
false, extensionUtils.createAuthorityKeyIdentifier(caCertificate))
val issuingDistPointName = GeneralName(
GeneralName.uniformResourceIdentifier,
"http://${clrServer.hostAndPort.host}:${clrServer.hostAndPort.port}/crl/$endpoint")
// This is required and needs to match the certificate settings with respect to being indirect
val issuingDistPoint = IssuingDistributionPoint(DistributionPointName(GeneralNames(issuingDistPointName)), indirect, false)
builder.addExtension(Extension.issuingDistributionPoint, true, issuingDistPoint)
builder.setNextUpdate(Date(System.currentTimeMillis() + 1.seconds.toMillis()))
serialNumbers.forEach {
builder.addCRLEntry(it, Date(System.currentTimeMillis() - 10.minutes.toMillis()), ReasonFlags.certificateHold)
}
val signer = JcaContentSignerBuilder(signatureAlgorithm).setProvider(Crypto.findProvider("BC")).build(caPrivateKey)
return JcaX509CRLConverter().setProvider(Crypto.findProvider("BC")).getCRL(builder.build(signer))
}
}
@Before
fun setUp() {
Security.addProvider(BouncyCastleProvider())
@ -330,22 +358,25 @@ class CertificateRevocationListNodeTests {
nodeCrlDistPoint: String = "http://${server.hostAndPort}/crl/node.crl",
tlsCrlDistPoint: String? = "http://${server.hostAndPort}/crl/empty.crl",
maxMessageSize: Int = MAX_MESSAGE_SIZE): Pair<AMQPClient, X509Certificate> {
val baseDirectory = temporaryFolder.root.toPath() / "client"
val certificatesDirectory = baseDirectory / "certificates"
val p2pSslConfiguration = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory)
val signingCertificateStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory)
val clientConfig = rigorousMock<AbstractNodeConfiguration>().also {
doReturn(temporaryFolder.root.toPath() / "client").whenever(it).baseDirectory
doReturn(baseDirectory).whenever(it).baseDirectory
doReturn(certificatesDirectory).whenever(it).certificatesDirectory
doReturn(BOB_NAME).whenever(it).myLegalName
doReturn("trustpass").whenever(it).trustStorePassword
doReturn("cordacadevpass").whenever(it).keyStorePassword
doReturn(p2pSslConfiguration).whenever(it).p2pSslOptions
doReturn(signingCertificateStore).whenever(it).signingCertificateStore
doReturn(crlCheckSoftFail).whenever(it).crlCheckSoftFail
}
clientConfig.configureWithDevSSLCertificate()
val nodeCert = clientConfig.recreateNodeCaAndTlsCertificates(nodeCrlDistPoint, tlsCrlDistPoint)
val clientTruststore = clientConfig.loadTrustStore().internal
val clientKeystore = clientConfig.loadSslKeyStore().internal
val nodeCert = (signingCertificateStore to p2pSslConfiguration).recreateNodeCaAndTlsCertificates(nodeCrlDistPoint, tlsCrlDistPoint)
val keyStore = clientConfig.p2pSslOptions.keyStore.get()
val amqpConfig = object : AMQPConfiguration {
override val keyStore: KeyStore = clientKeystore
override val keyStorePrivateKeyPassword: CharArray = clientConfig.keyStorePassword.toCharArray()
override val trustStore: KeyStore = clientTruststore
override val keyStore = keyStore
override val trustStore = clientConfig.p2pSslOptions.trustStore.get()
override val crlCheckSoftFail: Boolean = crlCheckSoftFail
override val maxMessageSize: Int = maxMessageSize
}
@ -360,21 +391,24 @@ class CertificateRevocationListNodeTests {
nodeCrlDistPoint: String = "http://${server.hostAndPort}/crl/node.crl",
tlsCrlDistPoint: String? = "http://${server.hostAndPort}/crl/empty.crl",
maxMessageSize: Int = MAX_MESSAGE_SIZE): Pair<AMQPServer, X509Certificate> {
val baseDirectory = temporaryFolder.root.toPath() / "server"
val certificatesDirectory = baseDirectory / "certificates"
val p2pSslConfiguration = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory)
val signingCertificateStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory)
val serverConfig = rigorousMock<AbstractNodeConfiguration>().also {
doReturn(temporaryFolder.root.toPath() / "server").whenever(it).baseDirectory
doReturn(baseDirectory).whenever(it).baseDirectory
doReturn(certificatesDirectory).whenever(it).certificatesDirectory
doReturn(name).whenever(it).myLegalName
doReturn("trustpass").whenever(it).trustStorePassword
doReturn("cordacadevpass").whenever(it).keyStorePassword
doReturn(p2pSslConfiguration).whenever(it).p2pSslOptions
doReturn(signingCertificateStore).whenever(it).signingCertificateStore
doReturn(crlCheckSoftFail).whenever(it).crlCheckSoftFail
}
serverConfig.configureWithDevSSLCertificate()
val nodeCert = serverConfig.recreateNodeCaAndTlsCertificates(nodeCrlDistPoint, tlsCrlDistPoint)
val serverTruststore = serverConfig.loadTrustStore().internal
val serverKeystore = serverConfig.loadSslKeyStore().internal
val nodeCert = (signingCertificateStore to p2pSslConfiguration).recreateNodeCaAndTlsCertificates(nodeCrlDistPoint, tlsCrlDistPoint)
val keyStore = serverConfig.p2pSslOptions.keyStore.get()
val amqpConfig = object : AMQPConfiguration {
override val keyStore: KeyStore = serverKeystore
override val keyStorePrivateKeyPassword: CharArray = serverConfig.keyStorePassword.toCharArray()
override val trustStore: KeyStore = serverTruststore
override val keyStore = keyStore
override val trustStore = serverConfig.p2pSslOptions.trustStore.get()
override val crlCheckSoftFail: Boolean = crlCheckSoftFail
override val maxMessageSize: Int = maxMessageSize
}
@ -384,22 +418,28 @@ class CertificateRevocationListNodeTests {
amqpConfig), nodeCert)
}
private fun SSLConfiguration.recreateNodeCaAndTlsCertificates(nodeCaCrlDistPoint: String, tlsCrlDistPoint: String?): X509Certificate {
val nodeKeyStore = loadNodeKeyStore()
val (nodeCert, nodeKeys) = nodeKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA)
private fun Pair<CertificateStoreSupplier, MutualSslConfiguration>.recreateNodeCaAndTlsCertificates(nodeCaCrlDistPoint: String, tlsCrlDistPoint: String?): X509Certificate {
val signingCertificateStore = first
val p2pSslConfiguration = second
val nodeKeyStore = signingCertificateStore.get()
val (nodeCert, nodeKeys) = nodeKeyStore.query { getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA) }
val newNodeCert = replaceCrlDistPointCaCertificate(nodeCert, CertificateType.NODE_CA, INTERMEDIATE_CA.keyPair, nodeCaCrlDistPoint)
val nodeCertChain = listOf(newNodeCert, INTERMEDIATE_CA.certificate, *nodeKeyStore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA).drop(2).toTypedArray())
nodeKeyStore.internal.deleteEntry(X509Utilities.CORDA_CLIENT_CA)
nodeKeyStore.save()
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)
}
val sslKeyStore = loadSslKeyStore()
val (tlsCert, tlsKeys) = sslKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_TLS)
val sslKeyStore = p2pSslConfiguration.keyStore.get()
val (tlsCert, tlsKeys) = sslKeyStore.query { getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_TLS) }
val newTlsCert = replaceCrlDistPointCaCertificate(tlsCert, CertificateType.TLS, nodeKeys, tlsCrlDistPoint, X500Name.getInstance(ROOT_CA.certificate.subjectX500Principal.encoded))
val sslCertChain = listOf(newTlsCert, newNodeCert, INTERMEDIATE_CA.certificate, *sslKeyStore.getCertificateChain(X509Utilities.CORDA_CLIENT_TLS).drop(3).toTypedArray())
sslKeyStore.internal.deleteEntry(X509Utilities.CORDA_CLIENT_TLS)
sslKeyStore.save()
val sslCertChain = listOf(newTlsCert, newNodeCert, INTERMEDIATE_CA.certificate, *sslKeyStore.query { getCertificateChain(X509Utilities.CORDA_CLIENT_TLS) }.drop(3).toTypedArray())
sslKeyStore.update {
internal.deleteEntry(X509Utilities.CORDA_CLIENT_TLS)
}
sslKeyStore.update {
setPrivateKey(X509Utilities.CORDA_CLIENT_TLS, tlsKeys.private, sslCertChain)
}
@ -442,12 +482,15 @@ class CertificateRevocationListNodeTests {
@Path("node.crl")
@Produces("application/pkcs7-crl")
fun getNodeCRL(): Response {
return Response.ok(createRevocationList(
return Response.ok(CertificateRevocationListNodeTests.createRevocationList(
server,
SIGNATURE_ALGORITHM,
INTERMEDIATE_CA.certificate,
INTERMEDIATE_CA.keyPair.private,
NODE_CRL,
false,
*revokedNodeCerts.toTypedArray()).encoded).build()
*revokedNodeCerts.toTypedArray()).encoded)
.build()
}
@GET
@ -455,11 +498,14 @@ class CertificateRevocationListNodeTests {
@Produces("application/pkcs7-crl")
fun getIntermediateCRL(): Response {
return Response.ok(createRevocationList(
server,
SIGNATURE_ALGORITHM,
ROOT_CA.certificate,
ROOT_CA.keyPair.private,
INTEMEDIATE_CRL,
false,
*revokedIntermediateCerts.toTypedArray()).encoded).build()
*revokedIntermediateCerts.toTypedArray()).encoded)
.build()
}
@GET
@ -467,33 +513,13 @@ class CertificateRevocationListNodeTests {
@Produces("application/pkcs7-crl")
fun getEmptyCRL(): Response {
return Response.ok(createRevocationList(
server,
SIGNATURE_ALGORITHM,
ROOT_CA.certificate,
ROOT_CA.keyPair.private,
EMPTY_CRL, true).encoded).build()
}
private fun createRevocationList(caCertificate: X509Certificate,
caPrivateKey: PrivateKey,
endpoint: String,
indirect: Boolean,
vararg serialNumbers: BigInteger): X509CRL {
println("Generating CRL for $endpoint")
val builder = JcaX509v2CRLBuilder(caCertificate.subjectX500Principal, Date(System.currentTimeMillis() - 1.minutes.toMillis()))
val extensionUtils = JcaX509ExtensionUtils()
builder.addExtension(Extension.authorityKeyIdentifier,
false, extensionUtils.createAuthorityKeyIdentifier(caCertificate))
val issuingDistPointName = GeneralName(
GeneralName.uniformResourceIdentifier,
"http://${server.hostAndPort.host}:${server.hostAndPort.port}/crl/$endpoint")
// This is required and needs to match the certificate settings with respect to being indirect
val issuingDistPoint = IssuingDistributionPoint(DistributionPointName(GeneralNames(issuingDistPointName)), indirect, false)
builder.addExtension(Extension.issuingDistributionPoint, true, issuingDistPoint)
builder.setNextUpdate(Date(System.currentTimeMillis() + 1.seconds.toMillis()))
serialNumbers.forEach {
builder.addCRLEntry(it, Date(System.currentTimeMillis() - 10.minutes.toMillis()), ReasonFlags.certificateHold)
}
val signer = JcaContentSignerBuilder(SIGNATURE_ALGORITHM).setProvider(BouncyCastleProvider.PROVIDER_NAME).build(caPrivateKey)
return JcaX509CRLConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME).getCRL(builder.build(signer))
EMPTY_CRL,
true).encoded)
.build()
}
}
@ -533,4 +559,32 @@ class CertificateRevocationListNodeTests {
}
}
}
@Test
fun `verify CRL algorithms`() {
val ECDSA_ALGORITHM = "SHA256withECDSA"
val EC_ALGORITHM = "EC"
val EMPTY_CRL = "empty.crl"
val crl = createRevocationList(
server,
ECDSA_ALGORITHM,
ROOT_CA.certificate,
ROOT_CA.keyPair.private,
EMPTY_CRL,
true)
// This should pass.
crl.verify(ROOT_CA.keyPair.public)
// Try changing the algorithm to EC will fail.
assertFailsWith<IllegalArgumentException>("Unknown signature type requested: EC") {
createRevocationList(
server,
EC_ALGORITHM,
ROOT_CA.certificate,
ROOT_CA.keyPair.private,
EMPTY_CRL,
true)
}
}
}

View File

@ -12,16 +12,18 @@ import net.corda.core.utilities.NetworkHostAndPort
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.configureWithDevSSLCertificate
import net.corda.node.services.messaging.ArtemisMessagingServer
import net.corda.nodeapi.ArtemisTcpTransport.Companion.CIPHER_SUITES
import net.corda.nodeapi.internal.ArtemisMessagingClient
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_PREFIX
import net.corda.nodeapi.internal.config.SSLConfiguration
import net.corda.nodeapi.internal.createDevKeyStores
import net.corda.nodeapi.internal.crypto.*
import net.corda.nodeapi.internal.ArtemisTcpTransport
import net.corda.nodeapi.internal.config.MutualSslConfiguration
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.protonwrapper.messages.MessageStatus
import net.corda.nodeapi.internal.protonwrapper.netty.AMQPClient
import net.corda.nodeapi.internal.protonwrapper.netty.AMQPConfiguration
import net.corda.nodeapi.internal.protonwrapper.netty.AMQPServer
import net.corda.nodeapi.internal.protonwrapper.netty.init
import net.corda.nodeapi.internal.registerDevP2pCertificates
import net.corda.nodeapi.internal.registerDevSigningCertificates
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.CHARLIE_NAME
@ -29,13 +31,13 @@ import net.corda.testing.core.MAX_MESSAGE_SIZE
import net.corda.testing.driver.PortAllocation
import net.corda.testing.internal.createDevIntermediateCaCertPath
import net.corda.testing.internal.rigorousMock
import net.corda.testing.internal.stubs.CertificateStoreStubs
import org.apache.activemq.artemis.api.core.RoutingType
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Assert.assertArrayEquals
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.security.KeyStore
import java.security.cert.X509Certificate
import javax.net.ssl.*
import kotlin.concurrent.thread
@ -87,6 +89,22 @@ class ProtonWrapperTests {
}
}
@Test
fun `AMPQ Client fails to connect when crl soft fail check is disabled`() {
val amqpServer = createServer(serverPort, CordaX500Name("Rogue 1", "London", "GB"),
maxMessageSize = MAX_MESSAGE_SIZE, crlCheckSoftFail = false)
amqpServer.use {
amqpServer.start()
val amqpClient = createClient()
amqpClient.use {
val clientConnected = amqpClient.onConnection.toFuture()
amqpClient.start()
val clientConnect = clientConnected.get()
assertEquals(false, clientConnect.connected)
}
}
}
@Test
fun `AMPQ Client refuses to connect to unexpected server`() {
val amqpServer = createServer(serverPort, CordaX500Name("Rogue 1", "London", "GB"))
@ -102,34 +120,30 @@ class ProtonWrapperTests {
}
}
private fun SSLConfiguration.createTrustStore(rootCert: X509Certificate) {
val trustStore = loadOrCreateKeyStore(trustStoreFile, trustStorePassword)
trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCert)
trustStore.save(trustStoreFile, trustStorePassword)
}
private fun MutualSslConfiguration.createTrustStore(rootCert: X509Certificate) {
trustStore.get(true)[X509Utilities.CORDA_ROOT_CA] = rootCert
}
@Test
fun `Test AMQP Client with invalid root certificate`() {
val sslConfig = object : SSLConfiguration {
override val certificatesDirectory = temporaryFolder.root.toPath()
override val keyStorePassword = "serverstorepass"
override val trustStorePassword = "trustpass"
override val crlCheckSoftFail: Boolean = true
}
val certificatesDirectory = temporaryFolder.root.toPath()
val signingCertificateStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory, "serverstorepass")
val sslConfig = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory, keyStorePassword = "serverstorepass")
val (rootCa, intermediateCa) = createDevIntermediateCaCertPath()
// Generate server cert and private key and populate another keystore suitable for SSL
sslConfig.createDevKeyStores(ALICE_NAME, rootCa.certificate, intermediateCa)
signingCertificateStore.get(true).also { it.registerDevSigningCertificates(ALICE_NAME, rootCa.certificate, intermediateCa) }
sslConfig.keyStore.get(true).also { it.registerDevP2pCertificates(ALICE_NAME, rootCa.certificate, intermediateCa) }
sslConfig.createTrustStore(rootCa.certificate)
val keyStore = loadKeyStore(sslConfig.sslKeystore, sslConfig.keyStorePassword)
val trustStore = loadKeyStore(sslConfig.trustStoreFile, sslConfig.trustStorePassword)
val keyStore = sslConfig.keyStore.get()
val trustStore = sslConfig.trustStore.get()
val context = SSLContext.getInstance("TLS")
val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
keyManagerFactory.init(keyStore, sslConfig.keyStorePassword.toCharArray())
keyManagerFactory.init(keyStore)
val keyManagers = keyManagerFactory.keyManagers
val trustMgrFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
trustMgrFactory.init(trustStore)
@ -139,7 +153,7 @@ class ProtonWrapperTests {
val serverSocketFactory = context.serverSocketFactory
val serverSocket = serverSocketFactory.createServerSocket(serverPort) as SSLServerSocket
val serverParams = SSLParameters(CIPHER_SUITES.toTypedArray(),
val serverParams = SSLParameters(ArtemisTcpTransport.CIPHER_SUITES.toTypedArray(),
arrayOf("TLSv1.2"))
serverParams.wantClientAuth = true
serverParams.needClientAuth = true
@ -388,11 +402,16 @@ class ProtonWrapperTests {
}
private fun createArtemisServerAndClient(maxMessageSize: Int = MAX_MESSAGE_SIZE): Pair<ArtemisMessagingServer, ArtemisMessagingClient> {
val baseDirectory = temporaryFolder.root.toPath() / "artemis"
val certificatesDirectory = baseDirectory / "certificates"
val signingCertificateStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory)
val p2pSslConfiguration = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory)
val artemisConfig = rigorousMock<AbstractNodeConfiguration>().also {
doReturn(temporaryFolder.root.toPath() / "artemis").whenever(it).baseDirectory
doReturn(baseDirectory).whenever(it).baseDirectory
doReturn(certificatesDirectory).whenever(it).certificatesDirectory
doReturn(CHARLIE_NAME).whenever(it).myLegalName
doReturn("trustpass").whenever(it).trustStorePassword
doReturn("cordacadevpass").whenever(it).keyStorePassword
doReturn(signingCertificateStore).whenever(it).signingCertificateStore
doReturn(p2pSslConfiguration).whenever(it).p2pSslOptions
doReturn(NetworkHostAndPort("0.0.0.0", artemisPort)).whenever(it).p2pAddress
doReturn(null).whenever(it).jmxMonitoringHttpPort
doReturn(true).whenever(it).crlCheckSoftFail
@ -400,28 +419,32 @@ class ProtonWrapperTests {
artemisConfig.configureWithDevSSLCertificate()
val server = ArtemisMessagingServer(artemisConfig, NetworkHostAndPort("0.0.0.0", artemisPort), maxMessageSize)
val client = ArtemisMessagingClient(artemisConfig, NetworkHostAndPort("localhost", artemisPort), maxMessageSize)
val client = ArtemisMessagingClient(artemisConfig.p2pSslOptions, NetworkHostAndPort("localhost", artemisPort), maxMessageSize)
server.start()
client.start()
return Pair(server, client)
}
private fun createClient(maxMessageSize: Int = MAX_MESSAGE_SIZE): AMQPClient {
val baseDirectory = temporaryFolder.root.toPath() / "client"
val certificatesDirectory = baseDirectory / "certificates"
val signingCertificateStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory)
val p2pSslConfiguration = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory)
val clientConfig = rigorousMock<AbstractNodeConfiguration>().also {
doReturn(temporaryFolder.root.toPath() / "client").whenever(it).baseDirectory
doReturn(baseDirectory).whenever(it).baseDirectory
doReturn(certificatesDirectory).whenever(it).certificatesDirectory
doReturn(BOB_NAME).whenever(it).myLegalName
doReturn("trustpass").whenever(it).trustStorePassword
doReturn("cordacadevpass").whenever(it).keyStorePassword
doReturn(signingCertificateStore).whenever(it).signingCertificateStore
doReturn(p2pSslConfiguration).whenever(it).p2pSslOptions
doReturn(true).whenever(it).crlCheckSoftFail
}
clientConfig.configureWithDevSSLCertificate()
val clientTruststore = clientConfig.loadTrustStore().internal
val clientKeystore = clientConfig.loadSslKeyStore().internal
val clientTruststore = clientConfig.p2pSslOptions.trustStore.get()
val clientKeystore = clientConfig.p2pSslOptions.keyStore.get()
val amqpConfig = object : AMQPConfiguration {
override val keyStore: KeyStore = clientKeystore
override val keyStorePrivateKeyPassword: CharArray = clientConfig.keyStorePassword.toCharArray()
override val trustStore: KeyStore = clientTruststore
override val keyStore = clientKeystore
override val trustStore = clientTruststore
override val trace: Boolean = true
override val maxMessageSize: Int = maxMessageSize
}
@ -434,21 +457,25 @@ class ProtonWrapperTests {
}
private fun createSharedThreadsClient(sharedEventGroup: EventLoopGroup, id: Int, maxMessageSize: Int = MAX_MESSAGE_SIZE): AMQPClient {
val baseDirectory = temporaryFolder.root.toPath() / "client_%$id"
val certificatesDirectory = baseDirectory / "certificates"
val signingCertificateStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory)
val p2pSslConfiguration = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory)
val clientConfig = rigorousMock<AbstractNodeConfiguration>().also {
doReturn(temporaryFolder.root.toPath() / "client_%$id").whenever(it).baseDirectory
doReturn(baseDirectory).whenever(it).baseDirectory
doReturn(certificatesDirectory).whenever(it).certificatesDirectory
doReturn(CordaX500Name(null, "client $id", "Corda", "London", null, "GB")).whenever(it).myLegalName
doReturn("trustpass").whenever(it).trustStorePassword
doReturn("cordacadevpass").whenever(it).keyStorePassword
doReturn(signingCertificateStore).whenever(it).signingCertificateStore
doReturn(p2pSslConfiguration).whenever(it).p2pSslOptions
doReturn(true).whenever(it).crlCheckSoftFail
}
clientConfig.configureWithDevSSLCertificate()
val clientTruststore = clientConfig.loadTrustStore().internal
val clientKeystore = clientConfig.loadSslKeyStore().internal
val clientTruststore = clientConfig.p2pSslOptions.trustStore.get()
val clientKeystore = clientConfig.p2pSslOptions.keyStore.get()
val amqpConfig = object : AMQPConfiguration {
override val keyStore: KeyStore = clientKeystore
override val keyStorePrivateKeyPassword: CharArray = clientConfig.keyStorePassword.toCharArray()
override val trustStore: KeyStore = clientTruststore
override val keyStore = clientKeystore
override val trustStore = clientTruststore
override val trace: Boolean = true
override val maxMessageSize: Int = maxMessageSize
}
@ -459,22 +486,29 @@ class ProtonWrapperTests {
sharedThreadPool = sharedEventGroup)
}
private fun createServer(port: Int, name: CordaX500Name = ALICE_NAME, maxMessageSize: Int = MAX_MESSAGE_SIZE): AMQPServer {
private fun createServer(port: Int,
name: CordaX500Name = ALICE_NAME,
maxMessageSize: Int = MAX_MESSAGE_SIZE,
crlCheckSoftFail: Boolean = true): AMQPServer {
val baseDirectory = temporaryFolder.root.toPath() / "server"
val certificatesDirectory = baseDirectory / "certificates"
val signingCertificateStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory)
val p2pSslConfiguration = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory)
val serverConfig = rigorousMock<AbstractNodeConfiguration>().also {
doReturn(temporaryFolder.root.toPath() / "server").whenever(it).baseDirectory
doReturn(baseDirectory).whenever(it).baseDirectory
doReturn(certificatesDirectory).whenever(it).certificatesDirectory
doReturn(name).whenever(it).myLegalName
doReturn("trustpass").whenever(it).trustStorePassword
doReturn("cordacadevpass").whenever(it).keyStorePassword
doReturn(true).whenever(it).crlCheckSoftFail
doReturn(signingCertificateStore).whenever(it).signingCertificateStore
doReturn(p2pSslConfiguration).whenever(it).p2pSslOptions
doReturn(crlCheckSoftFail).whenever(it).crlCheckSoftFail
}
serverConfig.configureWithDevSSLCertificate()
val serverTruststore = serverConfig.loadTrustStore().internal
val serverKeystore = serverConfig.loadSslKeyStore().internal
val serverTruststore = serverConfig.p2pSslOptions.trustStore.get()
val serverKeystore = serverConfig.p2pSslOptions.keyStore.get()
val amqpConfig = object : AMQPConfiguration {
override val keyStore: KeyStore = serverKeystore
override val keyStorePrivateKeyPassword: CharArray = serverConfig.keyStorePassword.toCharArray()
override val trustStore: KeyStore = serverTruststore
override val keyStore = serverKeystore
override val trustStore = serverTruststore
override val trace: Boolean = true
override val maxMessageSize: Int = maxMessageSize
}

View File

@ -8,6 +8,8 @@ import net.corda.core.internal.packageName
import net.corda.core.messaging.startFlow
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.unwrap
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
@ -48,8 +50,8 @@ class AsymmetricCorDappsTests {
driver(DriverParameters(startNodesInProcess = false, cordappsForAllNodes = emptySet())) {
val nodeA = startNode(additionalCordapps = setOf(TestCorDapp.Factory.create("Szymon CorDapp", "1.0", classes = setOf(Ping::class.java)))).getOrThrow()
val nodeB = startNode(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(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()
nodeA.rpc.startFlow(::Ping, nodeB.nodeInfo.singleIdentity(), 1).returnValue.getOrThrow()
}
}
@ -63,7 +65,7 @@ class AsymmetricCorDappsTests {
val cordappForNodeB = TestCorDapp.Factory.create("nodeB_only", "1.0", classes = setOf(Pong::class.java))
driver(DriverParameters(startNodesInProcess = false, cordappsForAllNodes = setOf(sharedCordapp))) {
val (nodeA, nodeB) = listOf(startNode(), startNode(additionalCordapps = setOf(cordappForNodeB))).transpose().getOrThrow()
val (nodeA, nodeB) = listOf(startNode(providedName = ALICE_NAME), startNode(providedName = BOB_NAME, additionalCordapps = setOf(cordappForNodeB))).transpose().getOrThrow()
nodeA.rpc.startFlow(::Ping, nodeB.nodeInfo.singleIdentity(), 1).returnValue.getOrThrow()
}
}
@ -77,7 +79,7 @@ class AsymmetricCorDappsTests {
val cordappForNodeB = TestCorDapp.Factory.create("nodeB_only", "1.0", classes = setOf(Pong::class.java))
driver(DriverParameters(startNodesInProcess = true, cordappsForAllNodes = setOf(sharedCordapp))) {
val (nodeA, nodeB) = listOf(startNode(), startNode(additionalCordapps = setOf(cordappForNodeB))).transpose().getOrThrow()
val (nodeA, nodeB) = listOf(startNode(providedName = ALICE_NAME), startNode(providedName = BOB_NAME, additionalCordapps = setOf(cordappForNodeB))).transpose().getOrThrow()
nodeA.rpc.startFlow(::Ping, nodeB.nodeInfo.singleIdentity(), 1).returnValue.getOrThrow()
}
}

View File

@ -10,6 +10,8 @@ import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.unwrap
import net.corda.node.services.Permissions
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.driver
@ -38,8 +40,8 @@ class FlowRetryTest {
startNodesInProcess = isQuasarAgentSpecified(),
notarySpecs = emptyList()
)) {
val nodeAHandle = startNode(rpcUsers = listOf(user)).getOrThrow()
val nodeBHandle = startNode(rpcUsers = listOf(user)).getOrThrow()
val nodeAHandle = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow()
val nodeBHandle = startNode(providedName = BOB_NAME, rpcUsers = listOf(user)).getOrThrow()
val result = CordaRPCClient(nodeAHandle.rpcAddress).start(user.username, user.password).use {
it.proxy.startFlow(::InitiatorFlow, numSessions, numIterations, nodeBHandle.nodeInfo.singleIdentity()).returnValue.getOrThrow()
@ -66,7 +68,7 @@ class InitiatorFlow(private val sessionsCount: Int, private val iterationsCount:
fun tracker() = ProgressTracker(FIRST_STEP)
val seen = Collections.synchronizedSet(HashSet<Visited>())
val seen: MutableSet<Visited> = Collections.synchronizedSet(HashSet<Visited>())
fun visit(sessionNum: Int, iterationNum: Int, step: Step) {
val visited = Visited(sessionNum, iterationNum, step)
@ -117,7 +119,7 @@ class InitiatedFlow(val session: FlowSession) : FlowLogic<Any>() {
fun tracker() = ProgressTracker(FIRST_STEP)
val seen = Collections.synchronizedSet(HashSet<Visited>())
val seen: MutableSet<Visited> = Collections.synchronizedSet(HashSet<Visited>())
fun visit(sessionNum: Int, iterationNum: Int, step: Step) {
val visited = Visited(sessionNum, iterationNum, step)
@ -154,4 +156,4 @@ 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)
data class Visited(val sessionNum: Int, val iterationNum: Int, val step: Step)

View File

@ -1,15 +1,19 @@
package net.corda.node.modes.draining
import co.paralleluniverse.fibers.Suspendable
import net.corda.client.rpc.internal.drainAndShutdown
import net.corda.core.flows.*
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowSession
import net.corda.core.flows.InitiatedBy
import net.corda.core.flows.InitiatingFlow
import net.corda.core.flows.StartableByRPC
import net.corda.core.identity.Party
import net.corda.core.internal.concurrent.map
import net.corda.core.messaging.startFlow
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.unwrap
import net.corda.node.services.Permissions
import net.corda.nodeapi.internal.hasCancelledDrainingShutdown
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.singleIdentity
@ -18,6 +22,7 @@ import net.corda.testing.driver.PortAllocation
import net.corda.testing.driver.driver
import net.corda.testing.internal.chooseIdentity
import net.corda.testing.node.User
import net.corda.testing.node.internal.waitForShutdown
import org.assertj.core.api.AssertionsForInterfaceTypes.assertThat
import org.junit.After
import org.junit.Before
@ -29,6 +34,9 @@ import java.util.concurrent.TimeUnit
import kotlin.test.fail
class P2PFlowsDrainingModeTest {
companion object {
private val logger = contextLogger()
}
private val portAllocation = PortAllocation.Incremental(10000)
private val user = User("mark", "dadada", setOf(Permissions.all()))
@ -36,10 +44,6 @@ class P2PFlowsDrainingModeTest {
private var executor: ScheduledExecutorService? = null
companion object {
private val logger = loggerFor<P2PFlowsDrainingModeTest>()
}
@Before
fun setup() {
executor = Executors.newSingleThreadScheduledExecutor()
@ -81,24 +85,74 @@ class P2PFlowsDrainingModeTest {
}
@Test
fun `clean shutdown by draining`() {
driver(DriverParameters(startNodesInProcess = true, portAllocation = portAllocation, notarySpecs = emptyList())) {
fun `terminate node waiting for pending flows`() {
driver(DriverParameters(portAllocation = portAllocation, notarySpecs = emptyList())) {
val nodeA = startNode(providedName = ALICE_NAME, rpcUsers = users).getOrThrow()
val nodeB = startNode(providedName = BOB_NAME, rpcUsers = users).getOrThrow()
var successful = false
val latch = CountDownLatch(1)
nodeB.rpc.setFlowsDrainingModeEnabled(true)
IntRange(1, 10).forEach { nodeA.rpc.startFlow(::InitiateSessionFlow, nodeB.nodeInfo.chooseIdentity()) }
nodeA.rpc.drainAndShutdown()
.doOnError { error ->
error.printStackTrace()
successful = false
}
.doOnCompleted { successful = true }
.doAfterTerminate { latch.countDown() }
.subscribe()
nodeA.waitForShutdown().doOnError(Throwable::printStackTrace).doOnError { successful = false }.doOnCompleted { successful = true }.doAfterTerminate(latch::countDown).subscribe()
nodeA.rpc.terminate(true)
nodeB.rpc.setFlowsDrainingModeEnabled(false)
latch.await()
assertThat(successful).isTrue()
}
}
@Test
fun `terminate resets persistent draining mode property when waiting for pending flows`() {
driver(DriverParameters(portAllocation = portAllocation, notarySpecs = emptyList())) {
val nodeA = startNode(providedName = ALICE_NAME, rpcUsers = users).getOrThrow()
var successful = false
val latch = CountDownLatch(1)
// This would not be needed, as `terminate(true)` sets draining mode anyway, but it's here to ensure that it removes the persistent value anyway.
nodeA.rpc.setFlowsDrainingModeEnabled(true)
nodeA.rpc.waitForShutdown().doOnError(Throwable::printStackTrace).doOnError { successful = false }.doOnCompleted(nodeA::stop).doOnCompleted {
val nodeARestarted = startNode(providedName = ALICE_NAME, rpcUsers = users).getOrThrow()
successful = !nodeARestarted.rpc.isFlowsDrainingModeEnabled()
}.doAfterTerminate(latch::countDown).subscribe()
nodeA.rpc.terminate(true)
latch.await()
assertThat(successful).isTrue()
}
}
@Test
fun `disabling draining mode cancels draining shutdown`() {
driver(DriverParameters(portAllocation = portAllocation, notarySpecs = emptyList())) {
val nodeA = startNode(providedName = ALICE_NAME, rpcUsers = users).getOrThrow()
val nodeB = startNode(providedName = BOB_NAME, rpcUsers = users).getOrThrow()
var successful = false
val latch = CountDownLatch(1)
nodeB.rpc.setFlowsDrainingModeEnabled(true)
IntRange(1, 10).forEach { nodeA.rpc.startFlow(::InitiateSessionFlow, nodeB.nodeInfo.chooseIdentity()) }
nodeA.waitForShutdown().doOnError(Throwable::printStackTrace).doAfterTerminate { successful = false }.doAfterTerminate(latch::countDown).subscribe()
nodeA.rpc.terminate(true)
nodeA.rpc.hasCancelledDrainingShutdown().doOnError(Throwable::printStackTrace).doOnError { successful = false }.doOnCompleted { successful = true }.doAfterTerminate(latch::countDown).subscribe()
nodeA.rpc.setFlowsDrainingModeEnabled(false)
nodeB.rpc.setFlowsDrainingModeEnabled(false)
latch.await()
assertThat(successful).isTrue()

View File

@ -23,8 +23,10 @@ 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.testing.common.internal.testNetworkParameters
import net.corda.testing.core.DUMMY_BANK_A_NAME
import net.corda.testing.core.DUMMY_NOTARY_NAME
@ -50,7 +52,7 @@ class AttachmentLoadingTests {
@JvmField
val testSerialization = SerializationEnvironmentRule()
private val attachments = MockAttachmentStorage()
private val provider = CordappProviderImpl(JarScanningCordappLoader.fromJarUrls(listOf(isolatedJAR)), MockCordappConfigProvider(), attachments).apply {
private val provider = CordappProviderImpl(JarScanningCordappLoader.fromJarUrls(listOf(isolatedJAR), VersionInfo.UNKNOWN), MockCordappConfigProvider(), attachments).apply {
start(testNetworkParameters().whitelistedContractImplementations)
}
private val cordapp get() = provider.cordapps.first()

View File

@ -116,4 +116,4 @@ class ScheduledFlowIntegrationTests {
assertEquals(aliceSpentStates.count(), bobSpentStates.count())
}
}
}
}

View File

@ -1,18 +1,20 @@
package net.corda.node.services.messaging
import com.codahale.metrics.MetricRegistry
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever
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.NetworkMapCacheImpl
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
@ -21,6 +23,7 @@ import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.driver.PortAllocation
import net.corda.testing.internal.LogHelper
import net.corda.testing.internal.rigorousMock
import net.corda.testing.internal.stubs.CertificateStoreStubs
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
import net.corda.testing.node.internal.MOCK_VERSION_INFO
import org.apache.activemq.artemis.api.core.ActiveMQConnectionTimedOutException
@ -64,24 +67,30 @@ class ArtemisMessagingTest {
private var messagingClient: P2PMessagingClient? = null
private var messagingServer: ArtemisMessagingServer? = null
private lateinit var networkMapCache: NetworkMapCacheImpl
private lateinit var networkMapCache: PersistentNetworkMapCache
@Before
fun setUp() {
abstract class AbstractNodeConfiguration : NodeConfiguration
val baseDirectory = temporaryFolder.root.toPath()
val certificatesDirectory = baseDirectory / "certificates"
val signingCertificateStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory)
val p2pSslConfiguration = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory)
config = rigorousMock<AbstractNodeConfiguration>().also {
doReturn(temporaryFolder.root.toPath()).whenever(it).baseDirectory
doReturn(ALICE_NAME).whenever(it).myLegalName
doReturn("trustpass").whenever(it).trustStorePassword
doReturn("cordacadevpass").whenever(it).keyStorePassword
doReturn(certificatesDirectory).whenever(it).certificatesDirectory
doReturn(signingCertificateStore).whenever(it).signingCertificateStore
doReturn(p2pSslConfiguration).whenever(it).p2pSslOptions
doReturn(NetworkHostAndPort("0.0.0.0", serverPort)).whenever(it).p2pAddress
doReturn(null).whenever(it).jmxMonitoringHttpPort
doReturn(FlowTimeoutConfiguration(5.seconds, 3, backoffBase = 1.0)).whenever(it).flowTimeout
}
LogHelper.setLevel(PersistentUniquenessProvider::class)
database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), { null }, { null })
val persistentNetworkMapCache = PersistentNetworkMapCache(database, ALICE_NAME).apply { start(emptyList()) }
networkMapCache = NetworkMapCacheImpl(persistentNetworkMapCache, rigorousMock(), database).apply { start() }
networkMapCache = PersistentNetworkMapCache(TestingNamedCacheFactory(), database, rigorousMock()).apply { start(emptyList()) }
}
@After
@ -216,6 +225,8 @@ class ArtemisMessagingTest {
ServiceAffinityExecutor("ArtemisMessagingTests", 1),
database,
networkMapCache,
MetricRegistry(),
TestingNamedCacheFactory(),
isDrainingModeOn = { false },
drainingModeWasChangedEvents = PublishSubject.create<Pair<Boolean, Boolean>>()).apply {
config.configureWithDevSSLCertificate()

View File

@ -1,8 +1,6 @@
package net.corda.node.services.network
import net.corda.core.concurrent.CordaFuture
import net.corda.core.crypto.random63BitValue
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.*
import net.corda.core.internal.concurrent.transpose
import net.corda.core.messaging.ParametersUpdateInfo
@ -10,9 +8,7 @@ import net.corda.core.node.NodeInfo
import net.corda.core.serialization.serialize
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.seconds
import net.corda.node.services.config.configureDevKeyAndTrustStores
import net.corda.nodeapi.internal.NODE_INFO_DIRECTORY
import net.corda.nodeapi.internal.config.NodeSSLConfiguration
import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME
import net.corda.nodeapi.internal.network.NETWORK_PARAMS_UPDATE_FILE_NAME
import net.corda.nodeapi.internal.network.SignedNetworkParameters
@ -242,19 +238,3 @@ class NetworkMapTest(var initFunc: (URL, NetworkMapServer) -> CompatibilityZoneP
assertThat(rpc.networkMapSnapshot()).containsOnly(*nodes)
}
}
private fun DriverDSLImpl.startNode(providedName: CordaX500Name, devMode: Boolean): CordaFuture<NodeHandle> {
var customOverrides = emptyMap<String, String>()
if (!devMode) {
val nodeDir = baseDirectory(providedName)
val nodeSslConfig = object : NodeSSLConfiguration {
override val baseDirectory = nodeDir
override val keyStorePassword = "cordacadevpass"
override val trustStorePassword = "trustpass"
override val crlCheckSoftFail = true
}
nodeSslConfig.configureDevKeyAndTrustStores(providedName)
customOverrides = mapOf("devMode" to "false")
}
return startNode(providedName = providedName, customOverrides = customOverrides)
}

View File

@ -1,10 +1,12 @@
package net.corda.node.services.network
import net.corda.core.node.NodeInfo
import net.corda.core.serialization.serialize
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.node.MockServices.Companion.makeTestDataSourceProperties
@ -18,7 +20,6 @@ class PersistentNetworkMapCacheTest {
private companion object {
val ALICE = TestIdentity(ALICE_NAME, 70)
val BOB = TestIdentity(BOB_NAME, 80)
val CHARLIE = TestIdentity(CHARLIE_NAME, 90)
}
@Rule
@ -27,7 +28,7 @@ class PersistentNetworkMapCacheTest {
private var portCounter = 1000
private val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), { null }, { null })
private val charlieNetMapCache = PersistentNetworkMapCache(database, CHARLIE.name)
private val charlieNetMapCache = PersistentNetworkMapCache(TestingNamedCacheFactory(), database, InMemoryIdentityService(trustRoot = DEV_ROOT_CA.certificate))
@After
fun cleanUp() {
@ -38,7 +39,6 @@ class PersistentNetworkMapCacheTest {
fun addNode() {
val alice = createNodeInfo(listOf(ALICE))
charlieNetMapCache.addNode(alice)
assertThat(charlieNetMapCache.nodeReady).isDone()
val fromDb = database.transaction {
session.createQuery(
"from ${NodeInfoSchemaV1.PersistentNodeInfo::class.java.name}",
@ -48,32 +48,6 @@ class PersistentNetworkMapCacheTest {
assertThat(fromDb).containsOnly(alice)
}
@Test
fun `adding the node's own node-info doesn't complete the nodeReady future`() {
val charlie = createNodeInfo(listOf(CHARLIE))
charlieNetMapCache.addNode(charlie)
assertThat(charlieNetMapCache.nodeReady).isNotDone()
assertThat(charlieNetMapCache.getNodeByLegalName(CHARLIE.name)).isEqualTo(charlie)
}
@Test
fun `starting with just the node's own node-info in the db`() {
val charlie = createNodeInfo(listOf(CHARLIE))
saveNodeInfoIntoDb(charlie)
assertThat(charlieNetMapCache.allNodes).containsOnly(charlie)
charlieNetMapCache.start(emptyList())
assertThat(charlieNetMapCache.nodeReady).isNotDone()
}
@Test
fun `starting with another node-info in the db`() {
val alice = createNodeInfo(listOf(ALICE))
saveNodeInfoIntoDb(alice)
assertThat(charlieNetMapCache.allNodes).containsOnly(alice)
charlieNetMapCache.start(emptyList())
assertThat(charlieNetMapCache.nodeReady).isDone()
}
@Test
fun `unknown legal name`() {
charlieNetMapCache.addNode(createNodeInfo(listOf(ALICE)))
@ -135,19 +109,4 @@ class PersistentNetworkMapCacheTest {
serial = 1
)
}
private fun saveNodeInfoIntoDb(nodeInfo: NodeInfo) {
database.transaction {
session.save(NodeInfoSchemaV1.PersistentNodeInfo(
id = 0,
hash = nodeInfo.serialize().hash.toString(),
addresses = nodeInfo.addresses.map { NodeInfoSchemaV1.DBHostAndPort.fromHostAndPort(it) },
legalIdentitiesAndCerts = nodeInfo.legalIdentitiesAndCerts.mapIndexed { idx, elem ->
NodeInfoSchemaV1.DBPartyAndCertificate(elem, isMain = idx == 0)
},
platformVersion = nodeInfo.platformVersion,
serial = nodeInfo.serial
))
}
}
}

View File

@ -17,13 +17,13 @@ import net.corda.node.services.messaging.RPCServerConfiguration
import net.corda.node.utilities.createKeyPairAndSelfSignedTLSCertificate
import net.corda.node.utilities.saveToKeyStore
import net.corda.node.utilities.saveToTrustStore
import net.corda.nodeapi.ArtemisTcpTransport.Companion.rpcConnectorTcpTransport
import net.corda.nodeapi.BrokerRpcSslOptions
import net.corda.nodeapi.internal.config.SSLConfiguration
import net.corda.nodeapi.internal.ArtemisTcpTransport.Companion.rpcConnectorTcpTransport
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.createNodeSslConfig
import net.corda.testing.internal.p2pSslOptions
import org.apache.activemq.artemis.api.core.ActiveMQConnectionTimedOutException
import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl
import org.assertj.core.api.Assertions.assertThat
@ -58,12 +58,12 @@ class ArtemisRpcTests {
val brokerSslOptions = BrokerRpcSslOptions(keyStorePath, "password")
val trustStorePath = saveToTrustStore(tempFile("rpcTruststore.jks"), selfSignCert)
val clientSslOptions = ClientRpcSslOptions(trustStorePath, "password")
testSslCommunication(createNodeSslConfig(tempFolder.root.toPath()), brokerSslOptions, true, clientSslOptions)
testSslCommunication(p2pSslOptions(tempFolder.root.toPath()), brokerSslOptions, true, clientSslOptions)
}
@Test
fun rpc_with_ssl_disabled() {
testSslCommunication(createNodeSslConfig(tempFolder.root.toPath()), null, false, null)
testSslCommunication(p2pSslOptions(tempFolder.root.toPath()), null, false, null)
}
@Test
@ -73,7 +73,7 @@ class ArtemisRpcTests {
val brokerSslOptions = BrokerRpcSslOptions(keyStorePath, "password")
// here client sslOptions are passed null (as in, do not use SSL)
assertThatThrownBy {
testSslCommunication(createNodeSslConfig(tempFolder.root.toPath()), brokerSslOptions, true, null)
testSslCommunication(p2pSslOptions(tempFolder.root.toPath()), brokerSslOptions, true, null)
}.isInstanceOf(ActiveMQConnectionTimedOutException::class.java)
}
@ -91,11 +91,11 @@ class ArtemisRpcTests {
val clientSslOptions = ClientRpcSslOptions(trustStorePath, "password")
assertThatThrownBy {
testSslCommunication(createNodeSslConfig(tempFolder.root.toPath()), brokerSslOptions, true, clientSslOptions)
testSslCommunication(p2pSslOptions(tempFolder.root.toPath()), brokerSslOptions, true, clientSslOptions)
}.isInstanceOf(RPCException::class.java)
}
private fun testSslCommunication(nodeSSlconfig: SSLConfiguration,
private fun testSslCommunication(nodeSSlconfig: MutualSslConfiguration,
brokerSslOptions: BrokerRpcSslOptions?,
useSslForBroker: Boolean,
clientSslOptions: ClientRpcSslOptions?,
@ -140,7 +140,7 @@ class ArtemisRpcTests {
class TestRpcOpsImpl : TestRpcOps {
override fun greet(name: String): String = "Oh, hello $name!"
override val protocolVersion: Int = 1
override val protocolVersion: Int = 1000
}
private fun tempFile(name: String): Path = tempFolder.root.toPath() / name

View File

@ -9,13 +9,13 @@ import net.corda.core.messaging.startFlow
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.unwrap
import net.corda.node.services.Permissions
import net.corda.nodeapi.exceptions.InternalNodeException
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.NodeParameters
import net.corda.testing.driver.driver
import net.corda.testing.driver.*
import net.corda.testing.node.User
import net.corda.testing.node.internal.startNode
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.assertj.core.api.AssertionsForInterfaceTypes.assertThat
import org.hibernate.exception.GenericJDBCException
@ -23,75 +23,95 @@ import org.junit.Test
import java.sql.SQLException
class RpcExceptionHandlingTest {
private val user = User("mark", "dadada", setOf(Permissions.all()))
private val users = listOf(user)
@Test
fun `rpc client handles exceptions thrown on node side`() {
driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) {
fun `rpc client receive client-relevant exceptions regardless of devMode`() {
val params = NodeParameters(rpcUsers = users)
val clientRelevantMessage = "This is for the players!"
val node = startNode(NodeParameters(rpcUsers = users)).getOrThrow()
assertThatThrownBy { node.rpc.startFlow(::Flow).returnValue.getOrThrow() }.isInstanceOfSatisfying(CordaRuntimeException::class.java) { exception ->
assertThat(exception).hasNoCause()
assertThat(exception.stackTrace).isEmpty()
}
fun NodeHandle.throwExceptionFromFlow() {
rpc.startFlow(::ClientRelevantErrorFlow, clientRelevantMessage).returnValue.getOrThrow()
}
}
@Test
fun `rpc client handles client-relevant exceptions thrown on node side`() {
driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) {
val node = startNode(NodeParameters(rpcUsers = users)).getOrThrow()
val clientRelevantMessage = "This is for the players!"
assertThatThrownBy { node.rpc.startFlow(::ClientRelevantErrorFlow, clientRelevantMessage).returnValue.getOrThrow() }.isInstanceOfSatisfying(CordaRuntimeException::class.java) { exception ->
fun assertThatThrownExceptionIsReceivedUnwrapped(node: NodeHandle) {
assertThatThrownBy { node.throwExceptionFromFlow() }.isInstanceOfSatisfying(ClientRelevantException::class.java) { exception ->
assertThat(exception).hasNoCause()
assertThat(exception.stackTrace).isEmpty()
assertThat(exception.message).isEqualTo(clientRelevantMessage)
}
}
driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) {
val devModeNode = startNode(params, BOB_NAME).getOrThrow()
val node = startNode(ALICE_NAME, devMode = false, parameters = params).getOrThrow()
assertThatThrownExceptionIsReceivedUnwrapped(devModeNode)
assertThatThrownExceptionIsReceivedUnwrapped(node)
}
}
@Test
fun `FlowException is received by the RPC client`() {
fun `FlowException is received by the RPC client only if in devMode`() {
val params = NodeParameters(rpcUsers = users)
val expectedMessage = "Flow error!"
val expectedErrorId = 123L
fun NodeHandle.throwExceptionFromFlow() {
rpc.startFlow(::FlowExceptionFlow, expectedMessage, expectedErrorId).returnValue.getOrThrow()
}
driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) {
val node = startNode(NodeParameters(rpcUsers = users)).getOrThrow()
val exceptionMessage = "Flow error!"
assertThatThrownBy { node.rpc.startFlow(::FlowExceptionFlow, exceptionMessage).returnValue.getOrThrow() }
.isInstanceOfSatisfying(FlowException::class.java) { exception ->
assertThat(exception).hasNoCause()
assertThat(exception.stackTrace).isEmpty()
assertThat(exception.message).isEqualTo(exceptionMessage)
}
val devModeNode = startNode(params, BOB_NAME).getOrThrow()
val node = startNode(ALICE_NAME, devMode = false, parameters = params).getOrThrow()
assertThatThrownBy { devModeNode.throwExceptionFromFlow() }.isInstanceOfSatisfying(FlowException::class.java) { exception ->
assertThat(exception).hasNoCause()
assertThat(exception.stackTrace).isEmpty()
assertThat(exception.message).isEqualTo(expectedMessage)
assertThat(exception.errorId).isEqualTo(expectedErrorId)
}
assertThatThrownBy { node.throwExceptionFromFlow() }.isInstanceOfSatisfying(InternalNodeException::class.java) { exception ->
assertThat(exception).hasNoCause()
assertThat(exception.stackTrace).isEmpty()
assertThat(exception.message).isEqualTo(InternalNodeException.message)
assertThat(exception.errorId).isEqualTo(expectedErrorId)
}
}
}
@Test
fun `rpc client handles exceptions thrown on counter-party side`() {
val params = NodeParameters(rpcUsers = users)
fun DriverDSL.scenario(devMode: Boolean) {
val nodeA = startNode(ALICE_NAME, devMode, params).getOrThrow()
val nodeB = startNode(BOB_NAME, devMode, params).getOrThrow()
nodeA.rpc.startFlow(::InitFlow, nodeB.nodeInfo.singleIdentity()).returnValue.getOrThrow()
}
driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) {
val nodeA = startNode(NodeParameters(providedName = ALICE_NAME, rpcUsers = users)).getOrThrow()
val nodeB = startNode(NodeParameters(providedName = BOB_NAME, rpcUsers = users)).getOrThrow()
assertThatThrownBy { nodeA.rpc.startFlow(::InitFlow, nodeB.nodeInfo.singleIdentity()).returnValue.getOrThrow() }.isInstanceOfSatisfying(CordaRuntimeException::class.java) { exception ->
assertThatThrownBy { scenario(true) }.isInstanceOfSatisfying(CordaRuntimeException::class.java) { exception ->
assertThat(exception).hasNoCause()
assertThat(exception.stackTrace).isEmpty()
}
}
}
}
driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) {
assertThatThrownBy { scenario(false) }.isInstanceOfSatisfying(InternalNodeException::class.java) { exception ->
@StartableByRPC
class Flow : FlowLogic<String>() {
@Suspendable
override fun call(): String {
throw GenericJDBCException("Something went wrong!", SQLException("Oops!"))
assertThat(exception).hasNoCause()
assertThat(exception.stackTrace).isEmpty()
assertThat(exception.message).isEqualTo(InternalNodeException.message)
}
}
}
}
@ -121,7 +141,11 @@ class ClientRelevantErrorFlow(private val message: String) : FlowLogic<String>()
}
@StartableByRPC
class FlowExceptionFlow(private val message: String) : FlowLogic<String>() {
class FlowExceptionFlow(private val message: String, private val errorId: Long? = null) : FlowLogic<String>() {
@Suspendable
override fun call(): String = throw FlowException(message)
override fun call(): String {
val exception = FlowException(message)
errorId?.let { exception.originalErrorId = it }
throw exception
}
}

View File

@ -28,6 +28,10 @@ import kotlin.concurrent.thread
import kotlin.test.assertEquals
class HardRestartTest {
companion object {
val logConfigFile = ProjectStructure.projectRootDir / "config" / "dev" / "log4j2.xml"
}
@StartableByRPC
@InitiatingFlow
class Ping(private val pongParty: Party, val times: Int) : FlowLogic<Unit>() {
@ -54,10 +58,6 @@ class HardRestartTest {
}
}
companion object {
val logConfigFile = ProjectStructure.projectRootDir / "config" / "dev" / "log4j2.xml"
}
@Test
fun restartShortPingPongFlowRandomly() {
val demoUser = User("demo", "demo", setOf(Permissions.startFlow<Ping>(), Permissions.all()))
@ -257,4 +257,4 @@ class HardRestartTest {
}
}
}
}
}

View File

@ -16,6 +16,7 @@ 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
@ -155,7 +156,7 @@ class RaftTransactionCommitLogTests {
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.Companion::createMap) }
val stateMachineFactory = { RaftTransactionCommitLog(database, Clock.systemUTC(), { RaftUniquenessProvider.createMap(TestingNamedCacheFactory()) }) }
val server = CopycatServer.builder(address)
.withStateMachine(stateMachineFactory)

View File

@ -0,0 +1,70 @@
package net.corda.services.messaging
import com.typesafe.config.ConfigValueFactory
import junit.framework.TestCase.assertEquals
import net.corda.client.rpc.CordaRPCClient
import net.corda.core.contracts.Amount
import net.corda.core.contracts.Issued
import net.corda.core.contracts.withoutIssuer
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
import net.corda.finance.DOLLARS
import net.corda.finance.contracts.asset.Cash
import net.corda.finance.flows.CashIssueAndPaymentFlow
import net.corda.node.services.Permissions.Companion.all
import net.corda.testing.core.DUMMY_BANK_A_NAME
import net.corda.testing.core.DUMMY_BANK_B_NAME
import net.corda.testing.core.expect
import net.corda.testing.core.expectEvents
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.PortAllocation
import net.corda.testing.driver.driver
import net.corda.testing.node.User
import org.junit.Test
import java.util.*
class AdditionP2PAddressModeTest {
private val portAllocation = PortAllocation.Incremental(27182)
@Test
fun `runs nodes with one configured to use additionalP2PAddresses`() {
val testUser = User("test", "test", setOf(all()))
driver(DriverParameters(startNodesInProcess = true, inMemoryDB = true, extraCordappPackagesToScan = listOf("net.corda.finance"))) {
val mainAddress = portAllocation.nextHostAndPort().toString()
val altAddress = portAllocation.nextHostAndPort().toString()
val haConfig = mutableMapOf<String, Any?>()
haConfig["detectPublicIp"] = false
haConfig["p2pAddress"] = mainAddress //advertise this as primary
haConfig["messagingServerAddress"] = altAddress // but actually host on the alternate address
haConfig["messagingServerExternal"] = false
haConfig["additionalP2PAddresses"] = ConfigValueFactory.fromIterable(listOf(altAddress)) // advertise this secondary address
val (nodeA, nodeB) = listOf(
startNode(providedName = DUMMY_BANK_A_NAME, rpcUsers = listOf(testUser), customOverrides = haConfig),
startNode(providedName = DUMMY_BANK_B_NAME, rpcUsers = listOf(testUser), customOverrides = mapOf("p2pAddress" to portAllocation.nextHostAndPort().toString()))
).map { it.getOrThrow() }
val (nodeARpc, nodeBRpc) = listOf(nodeA, nodeB).map {
val client = CordaRPCClient(it.rpcAddress)
client.start(testUser.username, testUser.password).proxy
}
val nodeBVaultUpdates = nodeBRpc.vaultTrack(Cash.State::class.java).updates
val issueRef = OpaqueBytes.of(0.toByte())
nodeARpc.startFlowDynamic(
CashIssueAndPaymentFlow::class.java,
DOLLARS(1234),
issueRef,
nodeB.nodeInfo.legalIdentities.get(0),
true,
defaultNotaryIdentity
).returnValue.getOrThrow()
nodeBVaultUpdates.expectEvents {
expect { update ->
println("Bob got vault update of $update")
val amount: Amount<Issued<Currency>> = update.produced.first().state.data.amount
assertEquals(1234.DOLLARS, amount.withoutIssuer())
}
}
}
}
}

View File

@ -2,7 +2,6 @@ package net.corda.services.messaging
import net.corda.core.crypto.Crypto
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.copyTo
import net.corda.core.internal.createDirectories
import net.corda.core.internal.exists
import net.corda.core.internal.toX500Name
@ -11,9 +10,10 @@ import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_P2P_U
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER
import net.corda.nodeapi.internal.DEV_INTERMEDIATE_CA
import net.corda.nodeapi.internal.DEV_ROOT_CA
import net.corda.nodeapi.internal.config.SSLConfiguration
import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.loadDevCaTrustStore
import net.corda.testing.internal.stubs.CertificateStoreStubs
import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration
import org.apache.activemq.artemis.api.core.ActiveMQClusterSecurityException
import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException
@ -84,57 +84,35 @@ class MQSecurityAsNodeTest : P2PMQSecurityTest() {
@Test
fun `login with invalid certificate chain`() {
val sslConfig = object : SSLConfiguration {
override val certificatesDirectory = Files.createTempDirectory("certs")
override val keyStorePassword: String get() = "cordacadevpass"
override val trustStorePassword: String get() = "trustpass"
override val crlCheckSoftFail: Boolean = true
val certsDir = Files.createTempDirectory("certs")
certsDir.createDirectories()
val signingCertStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certsDir)
val p2pSslConfig = CertificateStoreStubs.P2P.withCertificatesDirectory(certsDir)
init {
val legalName = CordaX500Name("MegaCorp", "London", "GB")
certificatesDirectory.createDirectories()
if (!trustStoreFile.exists()) {
javaClass.classLoader.getResourceAsStream("certificates/cordatruststore.jks").use { it.copyTo(trustStoreFile) }
}
val clientKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
// Set name constrain to the legal name.
val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, legalName.toX500Name()))), arrayOf())
val clientCACert = X509Utilities.createCertificate(
CertificateType.INTERMEDIATE_CA,
DEV_INTERMEDIATE_CA.certificate,
DEV_INTERMEDIATE_CA.keyPair,
legalName.x500Principal,
clientKeyPair.public,
nameConstraints = nameConstraints)
val tlsKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
// Using different x500 name in the TLS cert which is not allowed in the name constraints.
val clientTLSCert = X509Utilities.createCertificate(
CertificateType.TLS,
clientCACert,
clientKeyPair,
CordaX500Name("MiniCorp", "London", "GB").x500Principal,
tlsKeyPair.public)
loadNodeKeyStore(createNew = true).update {
setPrivateKey(
X509Utilities.CORDA_CLIENT_CA,
clientKeyPair.private,
listOf(clientCACert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate))
}
loadSslKeyStore(createNew = true).update {
setPrivateKey(
X509Utilities.CORDA_CLIENT_TLS,
tlsKeyPair.private,
listOf(clientTLSCert, clientCACert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate))
}
}
val legalName = CordaX500Name("MegaCorp", "London", "GB")
if (!p2pSslConfig.trustStore.path.exists()) {
val trustStore = p2pSslConfig.trustStore.get(true)
loadDevCaTrustStore().copyTo(trustStore)
}
val attacker = clientTo(alice.node.configuration.p2pAddress, sslConfig)
val clientKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
// Set name constrain to the legal name.
val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, legalName.toX500Name()))), arrayOf())
val clientCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, DEV_INTERMEDIATE_CA.certificate, DEV_INTERMEDIATE_CA.keyPair, legalName.x500Principal, clientKeyPair.public, nameConstraints = nameConstraints)
val tlsKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
// Using different x500 name in the TLS cert which is not allowed in the name constraints.
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))
}
p2pSslConfig.keyStore.get(createNew = true).update {
setPrivateKey(X509Utilities.CORDA_CLIENT_TLS, tlsKeyPair.private, listOf(clientTLSCert, clientCACert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate))
}
val attacker = clientTo(alice.node.configuration.p2pAddress, p2pSslConfig)
assertThatExceptionOfType(ActiveMQNotConnectedException::class.java).isThrownBy {
attacker.start(PEER_USER, PEER_USER)
}

View File

@ -18,12 +18,12 @@ import net.corda.node.internal.NodeWithInfo
import net.corda.nodeapi.RPCApi
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.INTERNAL_PREFIX
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NOTIFICATIONS_ADDRESS
import net.corda.nodeapi.internal.config.SSLConfiguration
import net.corda.nodeapi.internal.config.MutualSslConfiguration
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.node.User
import net.corda.testing.core.singleIdentity
import net.corda.testing.internal.configureTestSSL
import net.corda.testing.node.User
import net.corda.testing.node.internal.NodeBasedTest
import net.corda.testing.node.internal.startFlow
import org.apache.activemq.artemis.api.core.ActiveMQNonExistentQueueException
@ -94,7 +94,7 @@ abstract class MQSecurityTest : NodeBasedTest() {
assertAllQueueCreationAttacksFail(randomQueue)
}
fun clientTo(target: NetworkHostAndPort, sslConfiguration: SSLConfiguration? = configureTestSSL(CordaX500Name("MegaCorp", "London", "GB"))): SimpleMQClient {
fun clientTo(target: NetworkHostAndPort, sslConfiguration: MutualSslConfiguration? = configureTestSSL(CordaX500Name("MegaCorp", "London", "GB"))): SimpleMQClient {
val client = SimpleMQClient(target, sslConfiguration)
clients += client
return client

View File

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

View File

@ -3,8 +3,8 @@ package net.corda.services.messaging
import net.corda.core.identity.CordaX500Name
import net.corda.core.serialization.internal.nodeSerializationEnv
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.nodeapi.ArtemisTcpTransport
import net.corda.nodeapi.internal.config.SSLConfiguration
import net.corda.nodeapi.internal.ArtemisTcpTransport.Companion.p2pConnectorTcpTransport
import net.corda.nodeapi.internal.config.MutualSslConfiguration
import net.corda.testing.internal.configureTestSSL
import org.apache.activemq.artemis.api.core.client.*
@ -12,7 +12,7 @@ import org.apache.activemq.artemis.api.core.client.*
* As the name suggests this is a simple client for connecting to MQ brokers.
*/
class SimpleMQClient(val target: NetworkHostAndPort,
private val config: SSLConfiguration? = configureTestSSL(DEFAULT_MQ_LEGAL_NAME)) {
private val config: MutualSslConfiguration? = configureTestSSL(DEFAULT_MQ_LEGAL_NAME)) {
companion object {
val DEFAULT_MQ_LEGAL_NAME = CordaX500Name(organisation = "SimpleMQClient", locality = "London", country = "GB")
}
@ -22,7 +22,7 @@ class SimpleMQClient(val target: NetworkHostAndPort,
lateinit var producer: ClientProducer
fun start(username: String? = null, password: String? = null, enableSSL: Boolean = true) {
val tcpTransport = ArtemisTcpTransport.p2pConnectorTcpTransport(target, config, enableSSL = enableSSL)
val tcpTransport = p2pConnectorTcpTransport(target, config, enableSSL = enableSSL)
val locator = ActiveMQClient.createServerLocatorWithoutHA(tcpTransport).apply {
isBlockOnNonDurableSend = true
threadPoolMaxSize = 1

View File

@ -21,16 +21,8 @@ public class CordaCaplet extends Capsule {
private Config parseConfigFile(List<String> args) {
String baseDirOption = getOption(args, "--base-directory");
// Ensure consistent behaviour with NodeArgsParser.kt, see CORDA-1598.
if (null == baseDirOption || baseDirOption.isEmpty()) {
baseDirOption = getOption(args, "-base-directory");
}
this.baseDir = Paths.get((baseDirOption == null) ? "." : baseDirOption).toAbsolutePath().normalize().toString();
String config = getOption(args, "--config-file");
// Same as for baseDirOption.
if (null == config || config.isEmpty()) {
config = getOption(args, "-config-file");
}
File configFile = (config == null) ? new File(baseDir, "node.conf") : new File(config);
try {
ConfigParseOptions parseOptions = ConfigParseOptions.defaults().setAllowMissing(false);

View File

@ -3,12 +3,12 @@
package net.corda.node
import net.corda.cliutils.start
import net.corda.node.internal.NodeStartup
import kotlin.system.exitProcess
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.
exitProcess(if (NodeStartup(args).run()) 0 else 1)
}
NodeStartup().start(args)
}

View File

@ -1,142 +0,0 @@
package net.corda.node
import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import joptsimple.OptionSet
import joptsimple.util.EnumConverter
import joptsimple.util.PathConverter
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.parseAsNodeConfiguration
import net.corda.node.utilities.AbstractArgsParser
import net.corda.nodeapi.internal.config.UnknownConfigKeysPolicy
import org.slf4j.event.Level
import java.nio.file.Path
import java.nio.file.Paths
// NOTE: Do not use any logger in this class as args parsing is done before the logger is setup.
class NodeArgsParser : AbstractArgsParser<CmdLineOptions>() {
// The intent of allowing a command line configurable directory and config path is to allow deployment flexibility.
// Other general configuration should live inside the config file unless we regularly need temporary overrides on the command line
private val baseDirectoryArg = optionParser
.accepts("base-directory", "The node working directory where all the files are kept")
.withRequiredArg()
.withValuesConvertedBy(PathConverter())
.defaultsTo(Paths.get("."))
private val configFileArg = optionParser
.accepts("config-file", "The path to the config file")
.withRequiredArg()
.defaultsTo("node.conf")
private val loggerLevel = optionParser
.accepts("logging-level", "Enable logging at this level and higher")
.withRequiredArg()
.withValuesConvertedBy(object : EnumConverter<Level>(Level::class.java) {})
.defaultsTo(Level.INFO)
private val logToConsoleArg = optionParser.accepts("log-to-console", "If set, prints logging to the console as well as to a file.")
private val sshdServerArg = optionParser.accepts("sshd", "Enables SSHD server for node administration.")
private val noLocalShellArg = optionParser.accepts("no-local-shell", "Do not start the embedded shell locally.")
private val isRegistrationArg = optionParser.accepts("initial-registration", "Start initial node registration with Corda network to obtain certificate from the permissioning server.")
private val networkRootTrustStorePathArg = optionParser.accepts("network-root-truststore", "Network root trust store obtained from network operator.")
.withRequiredArg()
.withValuesConvertedBy(PathConverter())
.defaultsTo((Paths.get("certificates") / "network-root-truststore.jks"))
private val networkRootTrustStorePasswordArg = optionParser.accepts("network-root-truststore-password", "Network root trust store password obtained from network operator.")
.withRequiredArg()
private val unknownConfigKeysPolicy = optionParser.accepts("on-unknown-config-keys", "How to behave on unknown node configuration.")
.withRequiredArg()
.withValuesConvertedBy(object : EnumConverter<UnknownConfigKeysPolicy>(UnknownConfigKeysPolicy::class.java) {})
.defaultsTo(UnknownConfigKeysPolicy.FAIL)
private val devModeArg = optionParser.accepts("dev-mode", "Run the node in developer mode. Unsafe for production.")
private val isVersionArg = optionParser.accepts("version", "Print the version and exit")
private val justGenerateNodeInfoArg = optionParser.accepts("just-generate-node-info",
"Perform the node start-up task necessary to generate its nodeInfo, save it to disk, then quit")
private val justGenerateRpcSslCertsArg = optionParser.accepts("just-generate-rpc-ssl-settings",
"Generate the ssl keystore and truststore for a secure RPC connection.")
private val bootstrapRaftClusterArg = optionParser.accepts("bootstrap-raft-cluster", "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.")
private val clearNetworkMapCache = optionParser.accepts("clear-network-map-cache", "Clears local copy of network map, on node startup it will be restored from server or file system.")
override fun doParse(optionSet: OptionSet): CmdLineOptions {
require(optionSet.nonOptionArguments().isEmpty()) { "Unrecognized argument(s): ${optionSet.nonOptionArguments().joinToString(separator = ", ")}"}
val baseDirectory = optionSet.valueOf(baseDirectoryArg).normalize().toAbsolutePath()
val configFilePath = Paths.get(optionSet.valueOf(configFileArg))
val configFile = if (configFilePath.isAbsolute) configFilePath else baseDirectory / configFilePath.toString()
val loggingLevel = optionSet.valueOf(loggerLevel)
val logToConsole = optionSet.has(logToConsoleArg)
val isRegistration = optionSet.has(isRegistrationArg)
val isVersion = optionSet.has(isVersionArg)
val noLocalShell = optionSet.has(noLocalShellArg)
val sshdServer = optionSet.has(sshdServerArg)
val justGenerateNodeInfo = optionSet.has(justGenerateNodeInfoArg)
val justGenerateRpcSslCerts = optionSet.has(justGenerateRpcSslCertsArg)
val bootstrapRaftCluster = optionSet.has(bootstrapRaftClusterArg)
val networkRootTrustStorePath = optionSet.valueOf(networkRootTrustStorePathArg)
val networkRootTrustStorePassword = optionSet.valueOf(networkRootTrustStorePasswordArg)
val unknownConfigKeysPolicy = optionSet.valueOf(unknownConfigKeysPolicy)
val devMode = optionSet.has(devModeArg)
val clearNetworkMapCache = optionSet.has(clearNetworkMapCache)
val registrationConfig = 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
}
return CmdLineOptions(baseDirectory,
configFile,
loggingLevel,
logToConsole,
registrationConfig,
isVersion,
noLocalShell,
sshdServer,
justGenerateNodeInfo,
justGenerateRpcSslCerts,
bootstrapRaftCluster,
unknownConfigKeysPolicy,
devMode,
clearNetworkMapCache)
}
}
data class NodeRegistrationOption(val networkRootTrustStorePath: Path, val networkRootTrustStorePassword: String)
data class CmdLineOptions(val baseDirectory: Path,
val configFile: Path,
val loggingLevel: Level,
val logToConsole: Boolean,
val nodeRegistrationOption: NodeRegistrationOption?,
val isVersion: Boolean,
val noLocalShell: Boolean,
val sshdServer: Boolean,
val justGenerateNodeInfo: Boolean,
val justGenerateRpcSslCerts: Boolean,
val bootstrapRaftCluster: Boolean,
val unknownConfigKeysPolicy: UnknownConfigKeysPolicy,
val devMode: Boolean,
val clearNetworkMapCache: Boolean) {
fun loadConfig(): Pair<Config, Try<NodeConfiguration>> {
val rawConfig = ConfigHelper.loadConfig(
baseDirectory,
configFile,
configOverrides = ConfigFactory.parseMap(mapOf("noLocalShell" to this.noLocalShell) +
if (devMode) mapOf("devMode" to this.devMode) else emptyMap<String, Any>())
)
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."
}
}
}
}
}
}

View File

@ -0,0 +1,134 @@
package net.corda.node
import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
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.parseAsNodeConfiguration
import net.corda.nodeapi.internal.config.UnknownConfigKeysPolicy
import picocli.CommandLine.Option
import java.nio.file.Path
import java.nio.file.Paths
class NodeCmdLineOptions {
@Option(
names = ["-b", "--base-directory"],
description = ["The node working directory where all the files are kept."]
)
var baseDirectory: Path = Paths.get(".").toAbsolutePath().normalize()
@Option(
names = ["-f", "--config-file"],
description = ["The path to the config file. By default this is node.conf in the base directory."]
)
private var _configFile: Path? = null
val configFile: Path get() = _configFile ?: (baseDirectory / "node.conf")
@Option(
names = ["--sshd"],
description = ["If set, enables SSH server for node administration."]
)
var sshdServer: Boolean = false
@Option(
names = ["--sshd-port"],
description = ["The port to start the SSH server on, if enabled."]
)
var sshdServerPort: Int = 2222
@Option(
names = ["-n", "--no-local-shell"],
description = ["Do not start the embedded shell locally."]
)
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"]
)
var justGenerateNodeInfo: Boolean = false
@Option(
names = ["--just-generate-rpc-ssl-settings"],
description = ["Generate the SSL key and trust stores for a secure RPC connection."]
)
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."]
)
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
}
}
fun loadConfig(): Pair<Config, Try<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())
)
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."
}
}
}
}
}
}
data class NodeRegistrationOption(val networkRootTrustStorePath: Path, val networkRootTrustStorePassword: String)

View File

@ -1,5 +1,7 @@
package net.corda.node
import net.corda.nodeapi.internal.PLATFORM_VERSION
/**
* Encapsulates various pieces of version information of the node.
*/
@ -14,4 +16,9 @@ data class VersionInfo(
/** The exact version control commit ID of the node build. */
val revision: String,
/** The node vendor */
val vendor: String)
val vendor: String) {
companion object {
val UNKNOWN = VersionInfo(PLATFORM_VERSION, "Unknown", "Unknown", "Unknown")
}
}

View File

@ -2,6 +2,7 @@ package net.corda.node.cordapp
import net.corda.core.cordapp.Cordapp
import net.corda.core.flows.FlowLogic
import net.corda.core.internal.cordapp.CordappImpl
import net.corda.core.schemas.MappedSchema
/**
@ -12,7 +13,7 @@ interface CordappLoader {
/**
* Returns all [Cordapp]s found.
*/
val cordapps: List<Cordapp>
val cordapps: List<CordappImpl>
/**
* Returns a [ClassLoader] containing all types from all [Cordapp]s.

View File

@ -38,12 +38,14 @@ 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.rpc.proxies.AuthenticatedRpcOpsProxy
import net.corda.node.internal.rpc.proxies.ExceptionMaskingRpcOpsProxy
import net.corda.node.internal.rpc.proxies.ExceptionSerialisingRpcOpsProxy
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.rpc.NodeRpcOptions
import net.corda.node.services.config.shell.toShellConfig
import net.corda.node.services.events.NodeSchedulerService
import net.corda.node.services.events.ScheduledActivityObserver
@ -52,21 +54,26 @@ import net.corda.node.services.keys.KeyManagementServiceInternal
import net.corda.node.services.keys.PersistentKeyManagementService
import net.corda.node.services.messaging.DeduplicationHandler
import net.corda.node.services.messaging.MessagingService
import net.corda.node.services.network.*
import net.corda.node.services.network.NetworkMapClient
import net.corda.node.services.network.NetworkMapUpdater
import net.corda.node.services.network.NodeInfoWatcher
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.upgrade.ContractUpgradeServiceImpl
import net.corda.node.services.vault.NodeVaultService
import net.corda.node.utilities.AffinityExecutor
import net.corda.node.utilities.JVMAgentRegistry
import net.corda.node.utilities.NamedThreadFactory
import net.corda.node.utilities.NodeBuildProperties
import net.corda.nodeapi.internal.DevIdentityGenerator
import net.corda.node.utilities.*
import net.corda.nodeapi.internal.NodeInfoAndSigned
import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.config.CertificateStore
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_CA
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_TLS
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
import net.corda.nodeapi.internal.crypto.X509Utilities.DISTRIBUTED_NOTARY_ALIAS_PREFIX
import net.corda.nodeapi.internal.crypto.X509Utilities.NODE_IDENTITY_ALIAS_PREFIX
import net.corda.nodeapi.internal.persistence.*
import net.corda.nodeapi.internal.storeLegalIdentity
import net.corda.tools.shell.InteractiveShell
@ -106,6 +113,7 @@ 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,
protected val versionInfo: VersionInfo,
protected val cordappLoader: CordappLoader,
protected val serverThread: AffinityExecutor.ServiceAffinityExecutor,
@ -115,6 +123,11 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
@Suppress("LeakingThis")
private var tokenizableServices: MutableList<Any>? = mutableListOf(platformClock, this)
protected val metricRegistry = MetricRegistry()
protected val cacheFactory = cacheFactoryPrototype.bindWithConfig(configuration).bindWithMetrics(metricRegistry).tokenize()
val monitoringService = MonitoringService(metricRegistry).tokenize()
protected val runOnStop = ArrayList<() -> Any?>()
init {
@ -129,7 +142,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
}
val schemaService = NodeSchemaService(cordappLoader.cordappSchemas, configuration.notary != null).tokenize()
val identityService = PersistentIdentityService().tokenize()
val identityService = PersistentIdentityService(cacheFactory).tokenize()
val database: CordaPersistence = createCordaPersistence(
configuration.database,
identityService::wellKnownPartyFromX500Name,
@ -141,14 +154,13 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
// TODO Break cyclic dependency
identityService.database = database
}
private val persistentNetworkMapCache = PersistentNetworkMapCache(database, configuration.myLegalName)
val networkMapCache = NetworkMapCacheImpl(persistentNetworkMapCache, identityService, database).tokenize()
val networkMapCache = PersistentNetworkMapCache(cacheFactory, database, identityService).tokenize()
val checkpointStorage = DBCheckpointStorage()
@Suppress("LeakingThis")
val transactionStorage = makeTransactionStorage(configuration.transactionCacheSizeBytes).tokenize()
val networkMapClient: NetworkMapClient? = configuration.networkServices?.let { NetworkMapClient(it.networkMapURL) }
private val metricRegistry = MetricRegistry()
val attachments = NodeAttachmentService(metricRegistry, database, configuration.attachmentContentCacheSizeBytes, configuration.attachmentCacheBound).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()
@Suppress("LeakingThis")
val keyManagementService = makeKeyManagementService(identityService).tokenize()
@ -157,7 +169,6 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
val vaultService = makeVaultService(keyManagementService, servicesForResolution, database).tokenize()
val nodeProperties = NodePropertiesPersistentStore(StubbedNodeUniqueIdProvider::value, database)
val flowLogicRefFactory = FlowLogicRefFactoryImpl(cordappLoader.appClassLoader)
val monitoringService = MonitoringService(metricRegistry).tokenize()
val networkMapUpdater = NetworkMapUpdater(
networkMapCache,
NodeInfoWatcher(
@ -204,7 +215,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
/**
* Completes once the node has successfully registered with the network map service
* or has loaded network map data from local database
* or has loaded network map data from local database.
*/
val nodeReadyFuture: CordaFuture<Unit> get() = networkMapCache.nodeReady.map { Unit }
@ -213,7 +224,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
}
/** Set to non-null once [start] has been successfully called. */
open val started get() = _started
open val started: S? get() = _started
@Volatile
private var _started: S? = null
@ -229,30 +240,34 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
/** The implementation of the [CordaRPCOps] interface used by this node. */
open fun makeRPCOps(): CordaRPCOps {
val ops: CordaRPCOps = CordaRPCOpsImpl(services, smm, flowStarter) { shutdownExecutor.submit { stop() } }
val ops: CordaRPCOps = CordaRPCOpsImpl(services, smm, flowStarter) { shutdownExecutor.submit { stop() } }.also { it.closeOnStop() }
val proxies = mutableListOf<(CordaRPCOps) -> CordaRPCOps>()
// Mind that order is relevant here.
val proxies = listOf<(CordaRPCOps) -> CordaRPCOps>(::AuthenticatedRpcOpsProxy, { ExceptionSerialisingRpcOpsProxy(it, true) })
proxies += ::AuthenticatedRpcOpsProxy
if (!configuration.devMode) {
proxies += { it -> ExceptionMaskingRpcOpsProxy(it, true) }
}
proxies += { it -> ExceptionSerialisingRpcOpsProxy(it, configuration.devMode) }
return proxies.fold(ops) { delegate, decorate -> decorate(delegate) }
}
private fun initKeyStore(): X509Certificate {
private fun initKeyStores(): X509Certificate {
if (configuration.devMode) {
configuration.configureWithDevSSLCertificate()
}
return validateKeyStore()
return validateKeyStores()
}
open fun generateAndSaveNodeInfo(): NodeInfo {
check(started == null) { "Node has already been started" }
log.info("Generating nodeInfo ...")
val trustRoot = initKeyStore()
val trustRoot = initKeyStores()
val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null)
startDatabase()
val nodeCa = configuration.loadNodeKeyStore().getCertificate(X509Utilities.CORDA_CLIENT_CA)
val nodeCa = configuration.signingCertificateStore.get()[CORDA_CLIENT_CA]
identityService.start(trustRoot, listOf(identity.certificate, nodeCa))
return database.use {
it.transaction {
persistentNetworkMapCache.start(notaries = emptyList())
val (_, nodeInfoAndSigned) = updateNodeInfo(identity, identityKeyPair, publish = false)
nodeInfoAndSigned.nodeInfo
}
@ -264,8 +279,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
log.info("Starting clearing of network map cache entries...")
startDatabase()
database.use {
persistentNetworkMapCache.start(notaries = emptyList())
persistentNetworkMapCache.clearNetworkMapCache()
networkMapCache.clearNetworkMapCache()
}
}
@ -277,8 +291,8 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
}
log.info("Node starting up ...")
val trustRoot = initKeyStore()
val nodeCa = configuration.loadNodeKeyStore().getCertificate(X509Utilities.CORDA_CLIENT_CA)
val trustRoot = initKeyStores()
val nodeCa = configuration.signingCertificateStore.get()[CORDA_CLIENT_CA]
initialiseJVMAgents()
schemaService.mappedSchemasWarnings().forEach {
@ -300,14 +314,13 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
"Node's platform version is lower than network's required minimumPlatformVersion"
}
servicesForResolution.start(netParams)
networkMapCache.start(netParams.notaries)
startDatabase()
val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null)
identityService.start(trustRoot, listOf(identity.certificate, nodeCa))
val (keyPairs, nodeInfoAndSigned, myNotaryIdentity) = database.transaction {
persistentNetworkMapCache.start(netParams.notaries)
networkMapCache.start()
updateNodeInfo(identity, identityKeyPair, publish = true)
}
@ -350,7 +363,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
}
}
/** Subclasses must override this to create a [StartedNode] of the desired type, using the provided machinery. */
/** Subclasses must override this to create a "started" node of the desired type, using the provided machinery. */
abstract fun createStartedNode(nodeInfo: NodeInfo, rpcOps: CordaRPCOps, notaryService: NotaryService?): S
private fun verifyCheckpointsCompatible(tokenizableServices: List<Any>) {
@ -368,10 +381,10 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
open fun startShell() {
if (configuration.shouldInitCrashShell()) {
val shellConfiguration = configuration.toShellConfig()
shellConfiguration.sshHostKeyDirectory?.let {
shellConfiguration.sshdPort?.let {
log.info("Binding Shell SSHD server on port $it.")
}
InteractiveShell.startShellInternal(shellConfiguration, cordappLoader.appClassLoader)
InteractiveShell.startShell(shellConfiguration, cordappLoader.appClassLoader)
}
}
@ -455,7 +468,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
} else {
1.days
}
val executor = Executors.newSingleThreadScheduledExecutor(NamedThreadFactory("Network Map Updater", Executors.defaultThreadFactory()))
val executor = Executors.newSingleThreadScheduledExecutor(NamedThreadFactory("Network Map Updater"))
executor.submit(object : Runnable {
override fun run() {
val republishInterval = try {
@ -516,13 +529,13 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
}
/**
* If the [serviceClass] is a notary service, it will only be enable if the "custom" flag is set in
* 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
* This customizes the ServiceHub for each CordaService that is initiating flows.
*/
// TODO Move this into its own file
private class AppServiceHubImpl<T : SerializeAsToken>(private val serviceHub: ServiceHub, private val flowStarter: FlowStarter) : AppServiceHub, ServiceHub by serviceHub {
@ -563,28 +576,38 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
private fun <T : SerializeAsToken> installCordaService(flowStarter: FlowStarter, serviceClass: Class<T>, myNotaryIdentity: PartyAndCertificate?) {
serviceClass.requireAnnotation<CordaService>()
val service = try {
val serviceContext = AppServiceHubImpl<T>(services, flowStarter)
if (isNotaryService(serviceClass)) {
myNotaryIdentity ?: throw IllegalStateException("Trying to install a notary service but no notary identity specified")
val constructor = serviceClass.getDeclaredConstructor(AppServiceHub::class.java, PublicKey::class.java).apply { isAccessible = true }
serviceContext.serviceInstance = constructor.newInstance(serviceContext, myNotaryIdentity.owningKey)
serviceContext.serviceInstance
} else {
try {
val extendedServiceConstructor = serviceClass.getDeclaredConstructor(AppServiceHub::class.java).apply { isAccessible = true }
serviceContext.serviceInstance = extendedServiceConstructor.newInstance(serviceContext)
serviceContext.serviceInstance
} 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)
}
}
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)
}
}
} catch (e: InvocationTargetException) {
throw ServiceInstantiationException(e.cause)
}
cordappServices.putInstance(serviceClass, service)
if (service is NotaryService) handleCustomNotaryService(service)
@ -687,36 +710,56 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
}
protected open fun makeTransactionStorage(transactionCacheSizeBytes: Long): WritableTransactionStorage {
return DBTransactionStorage(transactionCacheSizeBytes, database)
return DBTransactionStorage(database, cacheFactory)
}
@VisibleForTesting
protected open fun acceptableLiveFiberCountOnStop(): Int = 0
private fun validateKeyStore(): X509Certificate {
val containCorrectKeys = try {
// This will throw IOException if key file not found or KeyStoreException if keystore password is incorrect.
val sslKeystore = configuration.loadSslKeyStore()
val identitiesKeystore = configuration.loadNodeKeyStore()
X509Utilities.CORDA_CLIENT_TLS in sslKeystore && X509Utilities.CORDA_CLIENT_CA in identitiesKeystore
private fun getCertificateStores(): AllCertificateStores? {
return try {
// The following will throw IOException if key file not found or KeyStoreException if keystore password is incorrect.
val sslKeyStore = configuration.p2pSslOptions.keyStore.get()
val identitiesKeyStore = configuration.signingCertificateStore.get()
val trustStore = configuration.p2pSslOptions.trustStore.get()
AllCertificateStores(trustStore, sslKeyStore, identitiesKeyStore)
} catch (e: KeyStoreException) {
log.warn("Certificate key store found but key store password does not match configuration.")
false
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 keystore", e)
false
log.error("IO exception while trying to validate keystores and truststore", e)
null
}
require(containCorrectKeys) {
"Identity certificate not found. " +
"Please either copy your existing identity key and certificate from another node, " +
}
private data class AllCertificateStores(val trustStore: CertificateStore, val sslKeyStore: CertificateStore, val identitiesKeyStore: CertificateStore)
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"
}
// Step 2. Check that trustStore contains the correct key-alias entry.
require(CORDA_ROOT_CA in certStores.trustStore) {
"Alias for trustRoot key not found. Please ensure you have an updated trustStore file."
}
// Step 3. Check that tls keyStore contains the correct key-alias entry.
require(CORDA_CLIENT_TLS in certStores.sslKeyStore) {
"Alias for TLS key not found. Please ensure you have an updated TLS keyStore file."
}
// Check all cert path chain to the trusted root
val sslCertChainRoot = configuration.loadSslKeyStore().getCertificateChain(X509Utilities.CORDA_CLIENT_TLS).last()
val nodeCaCertChainRoot = configuration.loadNodeKeyStore().getCertificateChain(X509Utilities.CORDA_CLIENT_CA).last()
val trustRoot = configuration.loadTrustStore().getCertificate(X509Utilities.CORDA_ROOT_CA)
// Step 4. Check that identity keyStores contain the correct key-alias entry for Node CA.
require(CORDA_CLIENT_CA in certStores.identitiesKeyStore) {
"Alias for Node CA key not found. Please ensure you have an updated identity keyStore file."
}
// Step 5. Check all cert paths chain to the trusted root.
val trustRoot = certStores.trustStore[CORDA_ROOT_CA]
val sslCertChainRoot = certStores.sslKeyStore.query { getCertificateChain(CORDA_CLIENT_TLS) }.last()
val nodeCaCertChainRoot = certStores.identitiesKeyStore.query { getCertificateChain(CORDA_CLIENT_CA) }.last()
require(sslCertChainRoot == trustRoot) { "TLS certificate must chain to the trusted root." }
require(nodeCaCertChainRoot == trustRoot) { "Client CA certificate must chain to the trusted root." }
@ -725,12 +768,12 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
}
// Specific class so that MockNode can catch it.
class DatabaseConfigurationException(msg: String) : CordaException(msg)
class DatabaseConfigurationException(message: String) : CordaException(message)
protected open fun startDatabase() {
val props = configuration.dataSourceProperties
if (props.isEmpty) throw DatabaseConfigurationException("There must be a database configured.")
database.startHikariPool(props, configuration.database, schemaService.internalSchemas())
database.startHikariPool(props, configuration.database, schemaService.internalSchemas(), metricRegistry)
// Now log the vendor string as this will also cause a connection to be tested eagerly.
logVendorString(database, log)
}
@ -751,7 +794,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
// 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
// the identity key. But the infrastructure to make that easy isn't here yet.
return PersistentKeyManagementService(identityService, database)
return PersistentKeyManagementService(cacheFactory, identityService, database)
}
private fun makeCoreNotaryService(notaryConfig: NotaryConfig, myNotaryIdentity: PartyAndCertificate?): NotaryService {
@ -760,7 +803,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
return notaryConfig.run {
when {
raft != null -> {
val uniquenessProvider = RaftUniquenessProvider(configuration, database, platformClock, monitoringService.metrics, raft)
val uniquenessProvider = RaftUniquenessProvider(configuration.baseDirectory, configuration.p2pSslOptions, database, platformClock, monitoringService.metrics, cacheFactory, raft)
(if (validating) ::RaftValidatingNotaryService else ::RaftNonValidatingNotaryService)(services, notaryKey, uniquenessProvider)
}
bftSMaRt != null -> {
@ -787,7 +830,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
// Meanwhile, we let the remote service send us updates until the acknowledgment buffer overflows and it
// unsubscribes us forcibly, rather than blocking the shutdown process.
// Run shutdown hooks in opposite order to starting
// Run shutdown hooks in opposite order to starting.
for (toRun in runOnStop.reversed()) {
toRun()
}
@ -804,40 +847,40 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
networkParameters: NetworkParameters)
private fun obtainIdentity(notaryConfig: NotaryConfig?): Pair<PartyAndCertificate, KeyPair> {
val keyStore = configuration.loadNodeKeyStore()
val keyStore = configuration.signingCertificateStore.get()
val (id, singleName) = if (notaryConfig == null || !notaryConfig.isClusterConfig) {
// Node's main identity or if it's a single node notary
Pair(DevIdentityGenerator.NODE_IDENTITY_ALIAS_PREFIX, configuration.myLegalName)
// 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(DevIdentityGenerator.DISTRIBUTED_NOTARY_ALIAS_PREFIX, null)
Pair(DISTRIBUTED_NOTARY_ALIAS_PREFIX, null)
}
// TODO: Integrate with Key management service?
val privateKeyAlias = "$id-private-key"
if (privateKeyAlias !in keyStore) {
singleName ?: throw IllegalArgumentException(
"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 ${configuration.nodeKeystore}, generating fresh key!")
// TODO This check shouldn't be needed
check(singleName == configuration.myLegalName)
// 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.getCertificateAndKeyPair(privateKeyAlias)
val (x509Cert, keyPair) = keyStore.query { getCertificateAndKeyPair(privateKeyAlias) }
// TODO: Use configuration to indicate composite key should be used instead of public key for the identity.
val compositeKeyAlias = "$id-composite-key"
val certificates = if (compositeKeyAlias in keyStore) {
// Use composite key instead if it exists
val certificate = keyStore.getCertificate(compositeKeyAlias)
// 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.getCertificateChain(privateKeyAlias).drop(1)
listOf(certificate) + keyStore.query { getCertificateChain(privateKeyAlias) }.drop(1)
} else {
keyStore.getCertificateChain(privateKeyAlias).let {
keyStore.query { getCertificateChain(privateKeyAlias) }.let {
check(it[0] == x509Cert) { "Certificates from key store do not line up!" }
it
}
@ -901,6 +944,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
override val clock: Clock get() = platformClock
override val configuration: NodeConfiguration get() = this@AbstractNode.configuration
override val networkMapUpdater: NetworkMapUpdater get() = this@AbstractNode.networkMapUpdater
override val cacheFactory: NamedCacheFactory get() = this@AbstractNode.cacheFactory
private lateinit var _myInfo: NodeInfo
override val myInfo: NodeInfo get() = _myInfo
@ -1013,9 +1057,9 @@ fun createCordaPersistence(databaseConfig: DatabaseConfig,
return CordaPersistence(databaseConfig, schemaService.schemaOptions.keys, jdbcUrl, attributeConverters)
}
fun CordaPersistence.startHikariPool(hikariProperties: Properties, databaseConfig: DatabaseConfig, schemas: Set<MappedSchema>) {
fun CordaPersistence.startHikariPool(hikariProperties: Properties, databaseConfig: DatabaseConfig, schemas: Set<MappedSchema>, metricRegistry: MetricRegistry? = null) {
try {
val dataSource = DataSourceFactory.createDataSource(hikariProperties)
val dataSource = DataSourceFactory.createDataSource(hikariProperties, metricRegistry = metricRegistry)
val schemaMigration = SchemaMigration(schemas, dataSource, databaseConfig)
schemaMigration.nodeStartup(dataSource.connection.use { DBCheckpointStorage().getCheckpointCount(it) != 0L })
start(dataSource)
@ -1027,4 +1071,13 @@ fun CordaPersistence.startHikariPool(hikariProperties: Properties, databaseConfi
else -> throw CouldNotCreateDataSourceException("Could not create the DataSource: ${ex.message}", ex)
}
}
}
}
fun clientSslOptionsCompatibleWith(nodeRpcOptions: NodeRpcOptions): ClientRpcSslOptions? {
if (!nodeRpcOptions.useSsl || nodeRpcOptions.sslConfig == null) {
return null
}
// 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

@ -5,11 +5,12 @@ 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.deserialize
import net.corda.core.serialization.internal.CheckpointSerializationDefaults
import net.corda.core.serialization.internal.checkpointDeserialize
import net.corda.node.services.api.CheckpointStorage
import net.corda.node.services.statemachine.SubFlow
import net.corda.node.services.statemachine.SubFlowVersion
import net.corda.serialization.internal.SerializeAsTokenContextImpl
import net.corda.serialization.internal.CheckpointSerializeAsTokenContextImpl
import net.corda.serialization.internal.withTokenContext
object CheckpointVerifier {
@ -19,13 +20,13 @@ object CheckpointVerifier {
* @throws CheckpointIncompatibleException if any offending checkpoint is found.
*/
fun verifyCheckpointsCompatible(checkpointStorage: CheckpointStorage, currentCordapps: List<Cordapp>, platformVersion: Int, serviceHub: ServiceHub, tokenizableServices: List<Any>) {
val checkpointSerializationContext = SerializationDefaults.CHECKPOINT_CONTEXT.withTokenContext(
SerializeAsTokenContextImpl(tokenizableServices, SerializationDefaults.SERIALIZATION_FACTORY, SerializationDefaults.CHECKPOINT_CONTEXT, serviceHub)
val checkpointSerializationContext = CheckpointSerializationDefaults.CHECKPOINT_CONTEXT.withTokenContext(
CheckpointSerializeAsTokenContextImpl(tokenizableServices, CheckpointSerializationDefaults.CHECKPOINT_SERIALIZATION_FACTORY, CheckpointSerializationDefaults.CHECKPOINT_CONTEXT, serviceHub)
)
checkpointStorage.getAllCheckpoints().forEach { (_, serializedCheckpoint) ->
val checkpoint = try {
serializedCheckpoint.deserialize(context = checkpointSerializationContext)
serializedCheckpoint.checkpointDeserialize(context = checkpointSerializationContext)
} catch (e: Exception) {
throw CheckpointIncompatibleException.CannotBeDeserialisedException(e)
}

View File

@ -19,6 +19,7 @@ import net.corda.core.internal.RPC_UPLOADER
import net.corda.core.internal.STRUCTURAL_STEP_PREFIX
import net.corda.core.internal.sign
import net.corda.core.messaging.*
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.AttachmentId
import net.corda.core.node.services.NetworkMapCache
@ -27,17 +28,21 @@ import net.corda.core.node.services.vault.*
import net.corda.core.serialization.serialize
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.loggerFor
import net.corda.node.services.api.FlowStarter
import net.corda.node.services.api.ServiceHubInternal
import net.corda.node.services.messaging.context
import net.corda.node.services.statemachine.StateMachineManager
import net.corda.nodeapi.exceptions.NonRpcFlowException
import net.corda.nodeapi.exceptions.RejectedCommandException
import net.corda.nodeapi.internal.pendingFlowsCount
import rx.Observable
import rx.Subscription
import java.io.InputStream
import java.net.ConnectException
import java.security.PublicKey
import java.time.Instant
import java.util.concurrent.atomic.AtomicReference
/**
* Server side implementations of RPCs available to MQ based client tools. Execution takes place on the server
@ -48,13 +53,38 @@ internal class CordaRPCOpsImpl(
private val smm: StateMachineManager,
private val flowStarter: FlowStarter,
private val shutdownNode: () -> Unit
) : CordaRPCOps {
) : CordaRPCOps, AutoCloseable {
private companion object {
private val logger = loggerFor<CordaRPCOpsImpl>()
}
private val drainingShutdownHook = AtomicReference<Subscription?>()
init {
services.nodeProperties.flowsDrainingMode.values.filter { it.isDisabled() }.subscribe({
cancelDrainingShutdownHook()
}, {
// Nothing to do in case of errors here.
})
}
private fun Pair<Boolean, Boolean>.isDisabled(): Boolean = first && !second
/**
* Returns the RPC protocol version, which is the same the node's platform Version. Exists since version 1 so guaranteed
* to be present.
*/
override val protocolVersion: Int get() = nodeInfo().platformVersion
override fun networkMapSnapshot(): List<NodeInfo> {
val (snapshot, updates) = networkMapFeed()
updates.notUsed()
return snapshot
}
override val networkParameters: NetworkParameters get() = services.networkParameters
override fun networkParametersFeed(): DataFeed<ParametersUpdateInfo?, ParametersUpdateInfo> {
return services.networkMapUpdater.trackParametersUpdate()
}
@ -213,7 +243,7 @@ internal class CordaRPCOpsImpl(
return services.networkMapCache.getNodeByLegalIdentity(party)
}
override fun registeredFlows(): List<String> = services.rpcFlows.map { it.name }.sorted()
override fun registeredFlows(): List<String> = services.rpcFlows.asSequence().map(Class<*>::getName).sorted().toList()
override fun clearNetworkMapCache() {
services.networkMapCache.clearNetworkMapCache()
@ -262,18 +292,46 @@ internal class CordaRPCOpsImpl(
return vaultTrackBy(criteria, PageSpecification(), sorting, contractStateType)
}
override fun setFlowsDrainingModeEnabled(enabled: Boolean) {
services.nodeProperties.flowsDrainingMode.setEnabled(enabled)
override fun setFlowsDrainingModeEnabled(enabled: Boolean) = setPersistentDrainingModeProperty(enabled, propagateChange = true)
override fun isFlowsDrainingModeEnabled() = services.nodeProperties.flowsDrainingMode.isEnabled()
override fun shutdown() = terminate(false)
override fun terminate(drainPendingFlows: Boolean) {
if (drainPendingFlows) {
logger.info("Waiting for pending flows to complete before shutting down.")
setFlowsDrainingModeEnabled(true)
drainingShutdownHook.set(pendingFlowsCount().updates.doOnNext {(completed, total) ->
logger.info("Pending flows progress before shutdown: $completed / $total.")
}.doOnCompleted { setPersistentDrainingModeProperty(false, false) }.doOnCompleted(::cancelDrainingShutdownHook).doOnCompleted { logger.info("No more pending flows to drain. Shutting down.") }.doOnCompleted(shutdownNode::invoke).subscribe({
// Nothing to do on each update here, only completion matters.
}, { error ->
logger.error("Error while waiting for pending flows to drain in preparation for shutdown. Cause was: ${error.message}", error)
}))
} else {
shutdownNode.invoke()
}
}
override fun isFlowsDrainingModeEnabled(): Boolean {
return services.nodeProperties.flowsDrainingMode.isEnabled()
override fun isWaitingForShutdown() = drainingShutdownHook.get() != null
override fun close() {
cancelDrainingShutdownHook()
}
override fun shutdown() {
shutdownNode.invoke()
private fun cancelDrainingShutdownHook() {
drainingShutdownHook.getAndSet(null)?.let {
it.unsubscribe()
logger.info("Cancelled draining shutdown hook.")
}
}
private fun setPersistentDrainingModeProperty(enabled: Boolean, propagateChange: Boolean) = services.nodeProperties.flowsDrainingMode.setEnabled(enabled, propagateChange)
private fun stateMachineInfoFromFlowLogic(flowLogic: FlowLogic<*>): StateMachineInfo {
return StateMachineInfo(flowLogic.runId, flowLogic.javaClass.name, flowLogic.stateMachine.context.toFlowInitiator(), flowLogic.track(), flowLogic.stateMachine.context)
}

View File

@ -1,5 +1,6 @@
package net.corda.node.internal
import com.codahale.metrics.MetricRegistry
import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import com.zaxxer.hikari.util.PropertyElf
@ -32,10 +33,14 @@ object DataSourceFactory {
}.set(null, SynchronizedGetPutRemove<String, Database>())
}
fun createDataSource(hikariProperties: Properties, pool: Boolean = true): DataSource {
fun createDataSource(hikariProperties: Properties, pool: Boolean = true, metricRegistry: MetricRegistry? = null): DataSource {
val config = HikariConfig(hikariProperties)
return if (pool) {
HikariDataSource(config)
val dataSource = HikariDataSource(config)
if (metricRegistry != null) {
dataSource.metricRegistry = metricRegistry
}
dataSource
} else {
// Basic init for the one test that wants to go via this API but without starting a HikariPool:
(Class.forName(hikariProperties.getProperty("dataSourceClassName")).newInstance() as DataSource).also {

View File

@ -12,4 +12,3 @@ sealed class InitiatedFlowFactory<out F : FlowLogic<*>> {
val appName: String,
override val factory: (FlowSession) -> F) : InitiatedFlowFactory<F>()
}

View File

@ -21,6 +21,7 @@ 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.nodeSerializationEnv
import net.corda.core.utilities.NetworkHostAndPort
@ -37,29 +38,24 @@ 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.KryoServerSerializationScheme
import net.corda.node.serialization.kryo.KryoSerializationScheme
import net.corda.node.services.Permissions
import net.corda.node.services.api.FlowStarter
import net.corda.node.services.api.ServiceHubInternal
import net.corda.node.services.api.StartedNodeServices
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.SecurityConfiguration
import net.corda.node.services.config.shouldInitCrashShell
import net.corda.node.services.config.shouldStartLocalShell
import net.corda.node.services.config.JmxReporterType
import net.corda.node.services.config.*
import net.corda.node.services.messaging.*
import net.corda.node.services.rpc.ArtemisRpcBroker
import net.corda.node.utilities.AddressUtils
import net.corda.node.utilities.AffinityExecutor
import net.corda.node.utilities.DemoClock
import net.corda.node.utilities.*
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.INTERNAL_SHELL_USER
import net.corda.nodeapi.internal.ShutdownHook
import net.corda.nodeapi.internal.addShutdownHook
import net.corda.nodeapi.internal.bridging.BridgeControlListener
import net.corda.nodeapi.internal.config.User
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.serialization.internal.*
import net.corda.nodeapi.internal.persistence.CouldNotCreateDataSourceException
import net.corda.serialization.internal.*
import org.apache.commons.lang.SystemUtils
import org.h2.jdbc.JdbcSQLException
import org.slf4j.Logger
import org.slf4j.LoggerFactory
@ -71,10 +67,10 @@ import java.net.InetAddress
import java.nio.file.Path
import java.nio.file.Paths
import java.time.Clock
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import javax.management.ObjectName
import kotlin.system.exitProcess
import java.util.concurrent.TimeUnit
class NodeWithInfo(val node: Node, val info: NodeInfo) {
val services: StartedNodeServices = object : StartedNodeServices, ServiceHubInternal by node.services, FlowStarter by node.flowStarter {}
@ -92,10 +88,12 @@ class NodeWithInfo(val node: Node, val info: NodeInfo) {
open class Node(configuration: NodeConfiguration,
versionInfo: VersionInfo,
private val initialiseSerialization: Boolean = true,
cordappLoader: CordappLoader = makeCordappLoader(configuration)
cordappLoader: CordappLoader = makeCordappLoader(configuration, versionInfo),
cacheFactoryPrototype: NamedCacheFactory = DefaultNamedCacheFactory()
) : AbstractNode<NodeInfo>(
configuration,
createClock(configuration),
cacheFactoryPrototype,
versionInfo,
cordappLoader,
// Under normal (non-test execution) it will always be "1"
@ -134,11 +132,33 @@ open class Node(configuration: NodeConfiguration,
}
private val sameVmNodeCounter = AtomicInteger()
private fun makeCordappLoader(configuration: NodeConfiguration): CordappLoader {
return JarScanningCordappLoader.fromDirectories(configuration.cordappDirectories)
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 {
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
}
private fun hasMinimumJavaVersion(): Boolean {
// when the ext.java8_minUpdateVersion gradle constant changes, so must this check
val major = SystemUtils.JAVA_VERSION_FLOAT
return try {
val update = SystemUtils.JAVA_VERSION.substringAfter("_").toLong()
major == 1.8F && update >= 171
} catch (e: NumberFormatException) { // custom JDKs may not have the update version (e.g. 1.8.0-adoptopenjdk)
false
}
}
}
override val log: Logger get() = staticLog
@ -196,7 +216,9 @@ open class Node(configuration: NodeConfiguration,
database = database,
networkMap = networkMapCache,
isDrainingModeOn = nodeProperties.flowsDrainingMode::isEnabled,
drainingModeWasChangedEvents = nodeProperties.flowsDrainingMode.values
drainingModeWasChangedEvents = nodeProperties.flowsDrainingMode.values,
metricRegistry = metricRegistry,
cacheFactory = cacheFactory
)
}
@ -227,12 +249,12 @@ open class Node(configuration: NodeConfiguration,
startLocalRpcBroker(securityManager)
}
val bridgeControlListener = BridgeControlListener(configuration, network.serverAddress, networkParameters.maxMessageSize)
val bridgeControlListener = BridgeControlListener(configuration.p2pSslOptions, network.serverAddress, networkParameters.maxMessageSize)
printBasicNodeInfo("Advertised P2P messaging addresses", nodeInfo.addresses.joinToString())
val rpcServerConfiguration = RPCServerConfiguration.DEFAULT
rpcServerAddresses?.let {
internalRpcMessagingClient = InternalRPCMessagingClient(configuration, it.admin, MAX_RPC_MESSAGE_SIZE, CordaX500Name.build(configuration.loadSslKeyStore().getCertificate(X509Utilities.CORDA_CLIENT_TLS).subjectX500Principal), rpcServerConfiguration)
internalRpcMessagingClient = InternalRPCMessagingClient(configuration.p2pSslOptions, it.admin, MAX_RPC_MESSAGE_SIZE, CordaX500Name.build(configuration.p2pSslOptions.keyStore.get()[X509Utilities.CORDA_CLIENT_TLS].subjectX500Principal), rpcServerConfiguration)
printBasicNodeInfo("RPC connection address", it.primary.toString())
printBasicNodeInfo("RPC admin connection address", it.admin.toString())
}
@ -271,18 +293,17 @@ open class Node(configuration: NodeConfiguration,
val rpcBrokerDirectory: Path = baseDirectory / "brokers" / "rpc"
with(rpcOptions) {
rpcBroker = if (useSsl) {
ArtemisRpcBroker.withSsl(configuration, this.address, adminAddress, sslConfig!!, securityManager, MAX_RPC_MESSAGE_SIZE, jmxMonitoringHttpPort != null, rpcBrokerDirectory, shouldStartLocalShell())
ArtemisRpcBroker.withSsl(configuration.p2pSslOptions, this.address, adminAddress, sslConfig!!, securityManager, MAX_RPC_MESSAGE_SIZE, jmxMonitoringHttpPort != null, rpcBrokerDirectory, shouldStartLocalShell())
} else {
ArtemisRpcBroker.withoutSsl(configuration, this.address, adminAddress, securityManager, MAX_RPC_MESSAGE_SIZE, jmxMonitoringHttpPort != null, rpcBrokerDirectory, shouldStartLocalShell())
ArtemisRpcBroker.withoutSsl(configuration.p2pSslOptions, this.address, adminAddress, securityManager, MAX_RPC_MESSAGE_SIZE, jmxMonitoringHttpPort != null, rpcBrokerDirectory, shouldStartLocalShell())
}
}
rpcBroker!!.closeOnStop()
rpcBroker!!.addresses
}
}
}
override fun myAddresses(): List<NetworkHostAndPort> = listOf(getAdvertisedAddress())
override fun myAddresses(): List<NetworkHostAndPort> = listOf(getAdvertisedAddress()) + configuration.additionalP2PAddresses
private fun getAdvertisedAddress(): NetworkHostAndPort {
return with(configuration) {
@ -420,12 +441,13 @@ open class Node(configuration: NodeConfiguration,
// https://jolokia.org/agent/jvm.html
JmxReporter.forRegistry(registry).inDomain("net.corda").createsObjectNamesWith { _, domain, name ->
// Make the JMX hierarchy a bit better organised.
val category = name.substringBefore('.')
val category = name.substringBefore('.').substringBeforeLast('/')
val component = name.substringBefore('.').substringAfterLast('/', "")
val subName = name.substringAfter('.', "")
if (subName == "")
ObjectName("$domain:name=$category")
(if (subName == "")
ObjectName("$domain:name=$category${if (component.isNotEmpty()) ",component=$component," else ""}")
else
ObjectName("$domain:type=$category,name=$subName")
ObjectName("$domain:type=$category,${if (component.isNotEmpty()) "component=$component," else ""}name=$subName"))
}.build().start()
}
@ -452,8 +474,8 @@ open class Node(configuration: NodeConfiguration,
SerializationFactoryImpl().apply {
registerScheme(AMQPServerSerializationScheme(cordappLoader.cordapps))
registerScheme(AMQPClientSerializationScheme(cordappLoader.cordapps))
registerScheme(KryoServerSerializationScheme())
},
checkpointSerializationFactory = CheckpointSerializationFactory(KryoSerializationScheme),
p2pContext = AMQP_P2P_CONTEXT.withClassLoader(classloader),
rpcServerContext = AMQP_RPC_SERVER_CONTEXT.withClassLoader(classloader),
storageContext = AMQP_STORAGE_CONTEXT.withClassLoader(classloader),
@ -489,4 +511,4 @@ open class Node(configuration: NodeConfiguration,
log.info("Shutdown complete")
}
}
}

View File

@ -1,19 +1,21 @@
package net.corda.node.internal
import com.jcabi.manifests.Manifests
import com.typesafe.config.Config
import com.typesafe.config.ConfigException
import com.typesafe.config.ConfigRenderOptions
import io.netty.channel.unix.Errors
import joptsimple.OptionParser
import joptsimple.util.PathConverter
import net.corda.cliutils.CordaCliWrapper
import net.corda.cliutils.CordaVersionProvider
import net.corda.cliutils.ExitCodes
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.loggerFor
import net.corda.node.*
import net.corda.node.internal.Node.Companion.isValidJavaVersion
import net.corda.node.internal.cordapp.MultipleCordappsForFlowException
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.NodeConfigurationImpl
@ -22,18 +24,19 @@ 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.NodeRegistrationHelper
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
import net.corda.nodeapi.internal.persistence.DatabaseIncompatibleException
import net.corda.tools.shell.InteractiveShell
import org.fusesource.jansi.Ansi
import org.fusesource.jansi.AnsiConsole
import org.slf4j.bridge.SLF4JBridgeHandler
import picocli.CommandLine.Mixin
import sun.misc.VMSupport
import java.io.Console
import java.io.File
@ -42,14 +45,13 @@ import java.io.RandomAccessFile
import java.lang.management.ManagementFactory
import java.net.InetAddress
import java.nio.file.Path
import java.nio.file.Paths
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(val args: Array<String>) {
open class NodeStartup : CordaCliWrapper("corda", "Runs a Corda Node") {
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"
@ -57,72 +59,84 @@ open class NodeStartup(val args: Array<String>) {
private const val INITIAL_REGISTRATION_MARKER = ".initialregistration"
}
@Mixin
val cmdLineOptions = NodeCmdLineOptions()
/**
* @return true if the node startup was successful. This value is intended to be the exit code of the process.
* @return exit code based on the success of the node startup. This value is intended to be the exit code of the process.
*/
open fun run(): Boolean {
override fun runProgram(): Int {
val startTime = System.currentTimeMillis()
if (!canNormalizeEmptyPath()) {
println("You are using a version of Java that is not supported (${System.getProperty("java.version")}). Please upgrade to the latest version.")
println("Corda will now exit...")
return false
}
// Step 1. Check for supported Java version.
if (!isValidJavaVersion()) return ExitCodes.FAILURE
val registrationMode = checkRegistrationMode()
val cmdlineOptions: CmdLineOptions = if (registrationMode && !args.contains("--initial-registration")) {
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.
NodeArgsParser().parseOrExit(*args.plus("--initial-registration"))
} else {
NodeArgsParser().parseOrExit(*args)
}
// We do the single node check before we initialise logging so that in case of a double-node start it
// 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)
enforceSingleNodeIsRunning(cmdLineOptions.baseDirectory)
initLogging(cmdlineOptions)
// Register all cryptography [Provider]s.
// Step 3. Initialise logging.
initLogging()
// Step 4. 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).
Crypto.registerProviders()
// Step 5. Print banner and basic node info.
val versionInfo = getVersionInfo()
if (cmdlineOptions.isVersion) {
println("${versionInfo.vendor} ${versionInfo.releaseVersion}")
println("Revision ${versionInfo.revision}")
println("Platform Version ${versionInfo.platformVersion}")
return true
}
drawBanner(versionInfo)
Node.printBasicNodeInfo(LOGS_CAN_BE_FOUND_IN_STRING, System.getProperty("log-path"))
val configuration = (attempt { loadConfiguration(cmdlineOptions) }.doOnException(handleConfigurationLoadingError(cmdlineOptions.configFile)) as? Try.Success)?.let(Try.Success<NodeConfiguration>::value) ?: return false
// 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
val errors = configuration.validate()
if (errors.isNotEmpty()) {
logger.error("Invalid node configuration. Errors were:${System.lineSeparator()}${errors.joinToString(System.lineSeparator())}")
return false
return ExitCodes.FAILURE
}
attempt { banJavaSerialisation(configuration) }.doOnException { error -> error.logAsUnexpected("Exception while configuring serialisation") } as? Try.Success ?: return false
// 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
attempt { preNetworkRegistration(configuration) }.doOnException(handleRegistrationError) as? Try.Success ?: return false
// Step 8. Any actions required before starting up the Corda network layer.
attempt { preNetworkRegistration(configuration) }.doOnException(handleRegistrationError) as? Try.Success ?: return ExitCodes.FAILURE
cmdlineOptions.nodeRegistrationOption?.let {
// Step 9. Check if in registration mode.
checkAndRunRegistrationMode(configuration, versionInfo)?.let {
return if (it) ExitCodes.SUCCESS
else ExitCodes.FAILURE
}
// Step 10. 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
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, cmdlineOptions.nodeRegistrationOption) }.doOnException(handleRegistrationError) as? Try.Success ?: return false
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)
deleteNodeRegistrationMarker(cmdLineOptions.baseDirectory)
return true
}
return null
}
logStartupInfo(versionInfo, cmdlineOptions, configuration)
return attempt { startNode(configuration, versionInfo, startTime, cmdlineOptions) }.doOnSuccess { logger.info("Node exiting successfully") }.doOnException(handleStartError).isSuccess
// 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)
@ -131,27 +145,12 @@ open class NodeStartup(val args: Array<String>) {
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 [errorCode=${errorCode()}]")
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" } ?: ""} [errorCode=${errorCode()}]", error)
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 fun Exception.errorCode(): String {
val hash = staticLocationBasedHash()
return Integer.toOctalString(hash)
}
private fun Throwable.staticLocationBasedHash(visited: Set<Throwable> = setOf(this)): Int {
val cause = this.cause
return when {
cause != null && !visited.contains(cause) -> Objects.hash(this::class.java.name, stackTrace, cause.staticLocationBasedHash(visited + cause))
else -> Objects.hash(this::class.java.name, stackTrace)
}
}
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.")
@ -186,14 +185,13 @@ open class NodeStartup(val args: Array<String>) {
""".trimIndent()
}
private fun loadConfiguration(cmdlineOptions: CmdLineOptions): NodeConfiguration {
val (rawConfig, configurationResult) = loadConfigFile(cmdlineOptions)
if (cmdlineOptions.devMode) {
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) {
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())))
@ -203,31 +201,14 @@ open class NodeStartup(val args: Array<String>) {
}
private fun checkRegistrationMode(): Boolean {
// Parse the command line args just to get the base directory. The base directory is needed to determine
// if the node registration marker file exists, _before_ we call NodeArgsParser.parse().
// If it does exist, we call NodeArgsParser with `--initial-registration` added to the argument list. This way
// we make sure that the initial registration is completed, even if the node was restarted before the first
// attempt to register succeeded and the node administrator forgets to specify `--initial-registration` upon
// restart.
val optionParser = OptionParser()
optionParser.allowsUnrecognizedOptions()
val baseDirectoryArg = optionParser
.accepts("base-directory", "The node working directory where all the files are kept")
.withRequiredArg()
.withValuesConvertedBy(PathConverter())
.defaultsTo(Paths.get("."))
val isRegistrationArg =
optionParser.accepts("initial-registration", "Start initial node registration with Corda network to obtain certificate from the permissioning server.")
val optionSet = optionParser.parse(*args)
val baseDirectory = optionSet.valueOf(baseDirectoryArg).normalize().toAbsolutePath()
// 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 = File((baseDirectory / INITIAL_REGISTRATION_MARKER).toUri())
if (!optionSet.has(isRegistrationArg) && !marker.exists()) {
val marker = cmdLineOptions.baseDirectory / INITIAL_REGISTRATION_MARKER
if (!cmdLineOptions.isRegistration && !marker.exists()) {
return false
}
try {
marker.createNewFile()
marker.createFile()
} catch (e: Exception) {
logger.warn("Could not create marker file for `--initial-registration`.", e)
}
@ -249,80 +230,20 @@ open class NodeStartup(val args: Array<String>) {
protected open fun createNode(conf: NodeConfiguration, versionInfo: VersionInfo): Node = Node(conf, versionInfo)
protected open fun startNode(conf: NodeConfiguration, versionInfo: VersionInfo, startTime: Long, cmdlineOptions: CmdLineOptions) {
cmdlineOptions.baseDirectory.createDirectories()
protected open fun startNode(conf: NodeConfiguration, versionInfo: VersionInfo, startTime: Long) {
cmdLineOptions.baseDirectory.createDirectories()
val node = createNode(conf, versionInfo)
if (cmdlineOptions.clearNetworkMapCache) {
if (cmdLineOptions.clearNetworkMapCache) {
node.clearNetworkMapCache()
return
}
if (cmdlineOptions.justGenerateNodeInfo) {
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) {
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 keystore password => ")
val keystorePassword2 = console.readPassword("Re-enter the keystore password => ")
if (!keystorePassword1.contentEquals(keystorePassword2)) {
println("The keystore passwords don't match.")
continue
}
saveToKeyStore(keyStorePath, keyPair, cert, String(keystorePassword1), "rpcssl")
println("The keystore was saved to: $keyStorePath .")
break
}
while (true) {
val trustStorePassword1 = console.readPassword("Enter the truststore password => ")
val trustStorePassword2 = console.readPassword("Re-enter the truststore password => ")
if (!trustStorePassword1.contentEquals(trustStorePassword2)) {
println("The truststore passwords don't match.")
continue
}
saveToTrustStore(trustStorePath, cert, String(trustStorePassword1), "rpcssl")
println("The 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 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())
}
}
if (cmdLineOptions.justGenerateRpcSslCerts) {
generateRpcSslCertificates(conf)
return
}
@ -335,8 +256,10 @@ open class NodeStartup(val args: Array<String>) {
}
val nodeInfo = node.start()
Node.printBasicNodeInfo("Loaded CorDapps", node.services.cordappProvider.cordapps.joinToString { it.name })
logLoadedCorDapps(node.services.cordappProvider.cordapps)
node.nodeReadyFuture.thenMatch({
// Elapsed time in seconds. We used 10 / 100.0 and not directly / 1000.0 to only keep two decimal digits.
val elapsed = (System.currentTimeMillis() - startTime) / 10 / 100.0
val name = nodeInfo.legalIdentitiesAndCerts.first().name.organisation
Node.printBasicNodeInfo("Node for \"$name\" started up and registered in $elapsed sec")
@ -361,7 +284,83 @@ open class NodeStartup(val args: Array<String>) {
node.run()
}
protected open fun logStartupInfo(versionInfo: VersionInfo, cmdlineOptions: CmdLineOptions, conf: NodeConfiguration) {
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}")
logger.info("Platform Version: ${versionInfo.platformVersion}")
@ -370,12 +369,11 @@ open class NodeStartup(val args: Array<String>) {
logger.info("PID: ${info.name.split("@").firstOrNull()}") // TODO Java 9 has better support for this
logger.info("Main class: ${NodeConfiguration::class.java.location.toURI().path}")
logger.info("CommandLine Args: ${info.inputArguments.joinToString(" ")}")
logger.info("Application Args: ${args.joinToString(" ")}")
logger.info("bootclasspath: ${info.bootClassPath}")
logger.info("classpath: ${info.classPath}")
logger.info("VM ${info.vmName} ${info.vmVendor} ${info.vmVersion}")
logger.info("Machine: ${lookupMachineNameAndMaybeWarn()}")
logger.info("Working Directory: ${cmdlineOptions.baseDirectory}")
logger.info("Working Directory: ${cmdLineOptions.baseDirectory}")
val agentProperties = VMSupport.getAgentProperties()
if (agentProperties.containsKey("sun.jdwp.listenerAddress")) {
logger.info("Debug port: ${agentProperties.getProperty("sun.jdwp.listenerAddress")}")
@ -403,24 +401,32 @@ open class NodeStartup(val args: Array<String>) {
println("Corda node will now terminate.")
}
protected open fun loadConfigFile(cmdlineOptions: CmdLineOptions): Pair<Config, Try<NodeConfiguration>> = cmdlineOptions.loadConfig()
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)
}
protected open fun getVersionInfo(): VersionInfo {
// Manifest properties are only available if running from the corda jar
fun manifestValue(name: String): String? = if (Manifests.exists(name)) Manifests.read(name) else null
return VersionInfo(
manifestValue("Corda-Platform-Version")?.toInt() ?: 1,
manifestValue("Corda-Release-Version") ?: "Unknown",
manifestValue("Corda-Revision") ?: "Unknown",
manifestValue("Corda-Vendor") ?: "Unknown"
PLATFORM_VERSION,
CordaVersionProvider.releaseVersion,
CordaVersionProvider.revision,
CordaVersionProvider.vendor
)
}
protected open fun logLoadedCorDapps(corDapps: List<CordappImpl>) {
fun CordappImpl.Info.description() = "$shortName version $version by $vendor"
Node.printBasicNodeInfo("Loaded ${corDapps.size} CorDapp(s)", corDapps.map { it.info }.joinToString(", ", transform = CordappImpl.Info::description))
corDapps.map { it.info }.filter { it.hasUnknownFields() }.let { malformed ->
if (malformed.isNotEmpty()) {
logger.warn("Found ${malformed.size} CorDapp(s) with unknown information. They will be unable to run on Corda in the future.")
}
}
}
private fun enforceSingleNodeIsRunning(baseDirectory: Path) {
// Write out our process ID (which may or may not resemble a UNIX process id - to us it's just a string) to a
// file that we'll do our best to delete on exit. But if we don't, it'll be overwritten next time. If it already
@ -454,14 +460,14 @@ open class NodeStartup(val args: Array<String>) {
}
}
protected open fun initLogging(cmdlineOptions: CmdLineOptions) {
val loggingLevel = cmdlineOptions.loggingLevel.name.toLowerCase(Locale.ENGLISH)
override fun initLogging() {
val loggingLevel = loggingLevel.name.toLowerCase(Locale.ENGLISH)
System.setProperty("defaultLogLevel", loggingLevel) // These properties are referenced from the XML config file.
if (cmdlineOptions.logToConsole) {
if (verbose) {
System.setProperty("consoleLogLevel", loggingLevel)
Node.renderBasicInfoToConsole = false
}
System.setProperty("log-path", (cmdlineOptions.baseDirectory / LOGS_DIRECTORY_NAME).toString())
System.setProperty("log-path", (cmdLineOptions.baseDirectory / LOGS_DIRECTORY_NAME).toString())
SLF4JBridgeHandler.removeHandlersForRootLogger() // The default j.u.l config adds a ConsoleHandler.
SLF4JBridgeHandler.install()
}
@ -491,20 +497,7 @@ open class NodeStartup(val args: Array<String>) {
return hostName
}
private fun canNormalizeEmptyPath(): Boolean {
// Check we're not running a version of Java with a known bug: https://github.com/corda/corda/issues/83
return try {
Paths.get("").normalize()
true
} catch (e: ArrayIndexOutOfBoundsException) {
false
}
}
open fun drawBanner(versionInfo: VersionInfo) {
// This line makes sure ANSI escapes work on Windows, where they aren't supported out of the box.
AnsiConsole.systemInstall()
Emoji.renderIfSupported {
val messages = arrayListOf(
"The only distributed ledger that pays\nhomage to Pac Man in its logo.",
@ -581,3 +574,4 @@ open class NodeStartup(val args: Array<String>) {
}
}
}

View File

@ -8,6 +8,7 @@ import net.corda.core.cordapp.CordappContext
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic
import net.corda.core.internal.DEPLOYED_CORDAPP_UPLOADER
import net.corda.core.internal.cordapp.CordappImpl
import net.corda.core.internal.createCordappContext
import net.corda.core.node.services.AttachmentId
import net.corda.core.node.services.AttachmentStorage
@ -34,7 +35,7 @@ open class CordappProviderImpl(private val cordappLoader: CordappLoader,
/**
* Current known CorDapps loaded on this node
*/
override val cordapps get() = cordappLoader.cordapps
override val cordapps: List<CordappImpl> get() = cordappLoader.cordapps
fun start(whitelistedContractImplementations: Map<String, List<AttachmentId>>) {
cordappAttachments.putAll(loadContractsIntoAttachmentStore())

View File

@ -3,8 +3,9 @@ package net.corda.node.internal.cordapp
import net.corda.core.cordapp.Cordapp
import net.corda.core.cordapp.CordappProvider
import net.corda.core.flows.FlowLogic
import net.corda.core.internal.cordapp.CordappImpl
interface CordappProviderInternal : CordappProvider {
val cordapps: List<Cordapp>
val cordapps: List<CordappImpl>
fun getCordappForFlow(flowLogic: FlowLogic<*>): Cordapp?
}

View File

@ -8,12 +8,14 @@ import net.corda.core.crypto.sha256
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.node.services.CordaService
import net.corda.core.schemas.MappedSchema
import net.corda.core.serialization.SerializationCustomSerializer
import net.corda.core.serialization.SerializationWhitelist
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
@ -24,6 +26,7 @@ import java.net.URL
import java.net.URLClassLoader
import java.nio.file.Path
import java.util.*
import java.util.jar.JarInputStream
import kotlin.reflect.KClass
import kotlin.streams.toList
@ -32,9 +35,10 @@ import kotlin.streams.toList
*
* @property cordappJarPaths The classpath of cordapp JARs
*/
class JarScanningCordappLoader private constructor(private val cordappJarPaths: List<RestrictedURL>) : CordappLoaderTemplate() {
class JarScanningCordappLoader private constructor(private val cordappJarPaths: List<RestrictedURL>,
private val versionInfo: VersionInfo = VersionInfo.UNKNOWN) : CordappLoaderTemplate() {
override val cordapps: List<Cordapp> by lazy { loadCordapps() + coreCordapp }
override val cordapps: List<CordappImpl> by lazy { loadCordapps() + coreCordapp }
override val appClassLoader: ClassLoader = URLClassLoader(cordappJarPaths.stream().map { it.url }.toTypedArray(), javaClass.classLoader)
@ -54,10 +58,9 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
*
* @param corDappDirectories Directories used to scan for CorDapp JARs.
*/
fun fromDirectories(corDappDirectories: Iterable<Path>): CordappLoader {
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() })
return JarScanningCordappLoader(corDappDirectories.distinct().flatMap(this::jarUrlsInDirectory).map { it.restricted() }, versionInfo)
}
/**
@ -65,7 +68,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>) = JarScanningCordappLoader(scanJars.map { it.restricted() })
fun fromJarUrls(scanJars: List<URL>, versionInfo: VersionInfo = VersionInfo.UNKNOWN): JarScanningCordappLoader {
return JarScanningCordappLoader(scanJars.map { it.restricted() }, versionInfo)
}
private fun URL.restricted(rootPackageName: String? = null) = RestrictedURL(this, rootPackageName)
@ -86,31 +91,42 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
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(),
allFlows = listOf(),
jarPath = ContractUpgradeFlow.javaClass.location, // Core JAR location
jarHash = SecureHash.allOnesHash
)
}
private fun loadCordapps(): List<Cordapp> {
return cordappJarPaths.map { scanCordapp(it).toCordapp(it) }
/** 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) }
.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}).")
false
} else {
true
}
}
cordapps.forEach { CordappInfoResolver.register(it.cordappClasses, it.info) }
return cordapps
}
private fun RestrictedScanResult.toCordapp(url: RestrictedURL): Cordapp {
private fun RestrictedScanResult.toCordapp(url: RestrictedURL): CordappImpl {
val info = url.url.openStream().let(::JarInputStream).use { it.manifest }.toCordappInfo(CordappImpl.jarName(url.url))
return CordappImpl(
findContractClassNames(this),
findInitiatedFlows(this),
@ -123,6 +139,7 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
findCustomSchemas(this),
findAllFlows(this),
url.url,
info,
getJarHash(url.url)
)
}
@ -268,24 +285,23 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
class MultipleCordappsForFlowException(message: String) : Exception(message)
abstract class CordappLoaderTemplate : CordappLoader {
override val flowCordappMap: Map<Class<out FlowLogic<*>>, Cordapp> by lazy {
cordapps.flatMap { corDapp -> corDapp.allFlows.map { flow -> flow to corDapp } }
.groupBy { it.first }
.mapValues {
if(it.value.size > 1) { throw MultipleCordappsForFlowException("There are multiple CorDapp JARs on the classpath for flow ${it.value.first().first.name}: [ ${it.value.joinToString { it.second.name }} ].") }
it.value.single().second
.mapValues { entry ->
if (entry.value.size > 1) {
throw MultipleCordappsForFlowException("There are multiple CorDapp JARs on the classpath for flow " +
"${entry.value.first().first.name}: [ ${entry.value.joinToString { it.second.name }} ].")
}
entry.value.single().second
}
}
override val cordappSchemas: Set<MappedSchema> by lazy {
cordapps.flatMap { it.customSchemas }.toSet()
}
override val appClassLoader: ClassLoader by lazy {
URLClassLoader(cordapps.stream().map { it.jarPath }.toTypedArray(), javaClass.classLoader)
}
}

View File

@ -1,13 +1,10 @@
package net.corda.node.internal.cordapp
import net.corda.core.cordapp.Cordapp
import net.corda.core.internal.cordapp.CordappImpl
import java.util.*
import java.util.jar.Attributes
import java.util.jar.Manifest
fun createTestManifest(name: String, title: String, version: String, vendor: String): Manifest {
val manifest = Manifest()
// Mandatory manifest attribute. If not present, all other entries are silently skipped.
@ -27,21 +24,22 @@ fun createTestManifest(name: String, title: String, version: String, vendor: Str
}
operator fun Manifest.set(key: String, value: String) {
mainAttributes.putValue(key, value)
}
internal fun Manifest?.toCordappInfo(defaultShortName: String): Cordapp.Info {
var unknown = CordappImpl.Info.UNKNOWN
fun Manifest?.toCordappInfo(defaultShortName: String): CordappImpl.Info {
var info = CordappImpl.Info.UNKNOWN
(this?.mainAttributes?.getValue("Name") ?: defaultShortName).let { shortName ->
unknown = unknown.copy(shortName = shortName)
info = info.copy(shortName = shortName)
}
this?.mainAttributes?.getValue("Implementation-Vendor")?.let { vendor ->
unknown = unknown.copy(vendor = vendor)
info = info.copy(vendor = vendor)
}
this?.mainAttributes?.getValue("Implementation-Version")?.let { version ->
unknown = unknown.copy(version = version)
info = info.copy(version = version)
}
return unknown
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
}

View File

@ -16,6 +16,8 @@ internal class AuthenticatedRpcOpsProxy(private val delegate: CordaRPCOps) : Cor
/**
* Returns the RPC protocol version, which is the same the node's Platform Version. Exists since version 1 so guaranteed
* to be present.
*
* TODO: Why is this logic duplicated vs the actual implementation?
*/
override val protocolVersion: Int get() = delegate.nodeInfo().platformVersion
@ -31,7 +33,6 @@ internal class AuthenticatedRpcOpsProxy(private val delegate: CordaRPCOps) : Cor
private companion object {
private fun proxy(delegate: CordaRPCOps, context: () -> RpcAuthContext): CordaRPCOps {
val handler = PermissionsEnforcingInvocationHandler(delegate, context)
return Proxy.newProxyInstance(delegate::class.java.classLoader, arrayOf(CordaRPCOps::class.java), handler) as CordaRPCOps
}

View File

@ -0,0 +1,125 @@
package net.corda.node.internal.rpc.proxies
import net.corda.core.ClientRelevantError
import net.corda.core.CordaException
import net.corda.core.CordaRuntimeException
import net.corda.core.concurrent.CordaFuture
import net.corda.core.contracts.TransactionVerificationException
import net.corda.core.doOnError
import net.corda.core.flows.IdentifiableException
import net.corda.core.internal.concurrent.doOnError
import net.corda.core.internal.concurrent.mapError
import net.corda.core.internal.declaredField
import net.corda.core.mapErrors
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.DataFeed
import net.corda.core.messaging.FlowHandle
import net.corda.core.messaging.FlowHandleImpl
import net.corda.core.messaging.FlowProgressHandle
import net.corda.core.messaging.FlowProgressHandleImpl
import net.corda.core.utilities.loggerFor
import net.corda.node.internal.InvocationHandlerTemplate
import net.corda.nodeapi.exceptions.InternalNodeException
import rx.Observable
import java.lang.reflect.Method
import java.lang.reflect.Proxy.newProxyInstance
import kotlin.reflect.KClass
internal class ExceptionMaskingRpcOpsProxy(private val delegate: CordaRPCOps, doLog: Boolean) : CordaRPCOps by proxy(delegate, doLog) {
private companion object {
private val logger = loggerFor<ExceptionMaskingRpcOpsProxy>()
private val whitelist = setOf(
ClientRelevantError::class,
TransactionVerificationException::class
)
private fun proxy(delegate: CordaRPCOps, doLog: Boolean): CordaRPCOps {
val handler = ErrorObfuscatingInvocationHandler(delegate, whitelist, doLog)
return newProxyInstance(delegate::class.java.classLoader, arrayOf(CordaRPCOps::class.java), handler) as CordaRPCOps
}
}
private class ErrorObfuscatingInvocationHandler(override val delegate: CordaRPCOps, private val whitelist: Set<KClass<*>>, private val doLog: Boolean) : InvocationHandlerTemplate {
override fun invoke(proxy: Any, method: Method, arguments: Array<out Any?>?): Any? {
try {
val result = super.invoke(proxy, method, arguments)
return result?.let { obfuscateResult(it) }
} catch (exception: Exception) {
// In this special case logging and re-throwing is the right approach.
log(exception)
throw obfuscate(exception)
}
}
private fun <RESULT : Any> obfuscateResult(result: RESULT): Any {
return when (result) {
is CordaFuture<*> -> wrapFuture(result)
is DataFeed<*, *> -> wrapFeed(result)
is FlowProgressHandle<*> -> wrapFlowProgressHandle(result)
is FlowHandle<*> -> wrapFlowHandle(result)
is Observable<*> -> wrapObservable(result)
else -> result
}
}
private fun wrapFlowProgressHandle(handle: FlowProgressHandle<*>): FlowProgressHandle<*> {
val returnValue = wrapFuture(handle.returnValue)
val progress = wrapObservable(handle.progress)
val stepsTreeIndexFeed = handle.stepsTreeIndexFeed?.let { wrapFeed(it) }
val stepsTreeFeed = handle.stepsTreeFeed?.let { wrapFeed(it) }
return FlowProgressHandleImpl(handle.id, returnValue, progress, stepsTreeIndexFeed, stepsTreeFeed)
}
private fun wrapFlowHandle(handle: FlowHandle<*>): FlowHandle<*> {
return FlowHandleImpl(handle.id, wrapFuture(handle.returnValue))
}
private fun <ELEMENT> wrapObservable(observable: Observable<ELEMENT>): Observable<ELEMENT> {
return observable.doOnError(::log).mapErrors(::obfuscate)
}
private fun <SNAPSHOT, ELEMENT> wrapFeed(feed: DataFeed<SNAPSHOT, ELEMENT>): DataFeed<SNAPSHOT, ELEMENT> {
return feed.doOnError(::log).mapErrors(::obfuscate)
}
private fun <RESULT> wrapFuture(future: CordaFuture<RESULT>): CordaFuture<RESULT> {
return future.doOnError(::log).mapError(::obfuscate)
}
private fun log(error: Throwable) {
if (doLog) {
logger.error("Error during RPC invocation", error)
}
}
private fun obfuscate(error: Throwable): Throwable {
val exposed = if (error.isWhitelisted()) error else InternalNodeException((error as? IdentifiableException)?.errorId)
removeDetails(exposed)
return exposed
}
private fun removeDetails(error: Throwable) {
error.stackTrace = arrayOf<StackTraceElement>()
error.declaredField<Any?>("cause").value = null
error.declaredField<Any?>("suppressedExceptions").value = null
when (error) {
is CordaException -> error.setCause(null)
is CordaRuntimeException -> error.setCause(null)
}
}
private fun Throwable.isWhitelisted(): Boolean {
return whitelist.any { it.isInstance(this) }
}
override fun toString(): String {
return "ErrorObfuscatingInvocationHandler(whitelist=$whitelist)"
}
}
override fun toString(): String {
return "ExceptionMaskingRpcOpsProxy"
}
}

View File

@ -2,6 +2,7 @@ package net.corda.node.serialization.amqp
import net.corda.core.context.Trace
import net.corda.core.serialization.SerializationContext
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.loggerFor
import net.corda.node.services.messaging.ObservableContextInterface
import net.corda.node.services.messaging.ObservableSubscription
@ -30,8 +31,9 @@ class RpcServerObservableSerializer : CustomSerializer.Implements<Observable<*>>
fun createContext(
serializationContext: SerializationContext,
observableContext: ObservableContextInterface
) = serializationContext.withProperty(
RpcServerObservableSerializer.RpcObservableContextKey, observableContext)
) = serializationContext.withProperty(RpcServerObservableSerializer.RpcObservableContextKey, observableContext)
val log = contextLogger()
}
override val schemaForDocumentation = Schema(
@ -136,5 +138,6 @@ class RpcServerObservableSerializer : CustomSerializer.Implements<Observable<*>>
}
}
observableContext.observableMap.put(observableId, observableWithSubscription)
log.trace("Serialized observable $observableId of type $obj")
}
}

View File

@ -8,8 +8,8 @@ import com.esotericsoftware.kryo.util.DefaultClassResolver
import com.esotericsoftware.kryo.util.Util
import net.corda.core.internal.kotlinObjectInstance
import net.corda.core.internal.writer
import net.corda.core.serialization.internal.CheckpointSerializationContext
import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.SerializationContext
import net.corda.core.utilities.contextLogger
import net.corda.serialization.internal.AttachmentsClassLoader
import net.corda.serialization.internal.MutableClassWhitelist
@ -25,7 +25,7 @@ import java.util.*
/**
* Corda specific class resolver which enables extra customisation for the purposes of serialization using Kryo
*/
class CordaClassResolver(serializationContext: SerializationContext) : DefaultClassResolver() {
class CordaClassResolver(serializationContext: CheckpointSerializationContext) : DefaultClassResolver() {
val whitelist: ClassWhitelist = TransientClassWhiteList(serializationContext.whitelist)
// These classes are assignment-compatible Java equivalents of Kotlin classes.

View File

@ -14,12 +14,11 @@ import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.TransactionSignature
import net.corda.core.internal.uncheckedCast
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationContext.UseCase.Checkpoint
import net.corda.core.serialization.SerializationContext.UseCase.Storage
import net.corda.core.serialization.SerializeAsTokenContext
import net.corda.core.serialization.SerializedBytes
import net.corda.core.transactions.*
import net.corda.core.utilities.OpaqueBytes
import net.corda.serialization.internal.checkUseCase
import net.corda.serialization.internal.serializationContextKey
import org.slf4j.Logger
import org.slf4j.LoggerFactory
@ -275,16 +274,9 @@ object SignedTransactionSerializer : Serializer<SignedTransaction>() {
}
}
sealed class UseCaseSerializer<T>(private val allowedUseCases: EnumSet<SerializationContext.UseCase>) : Serializer<T>() {
protected fun checkUseCase() {
net.corda.serialization.internal.checkUseCase(allowedUseCases)
}
}
@ThreadSafe
object PrivateKeySerializer : UseCaseSerializer<PrivateKey>(EnumSet.of(Storage, Checkpoint)) {
object PrivateKeySerializer : Serializer<PrivateKey>() {
override fun write(kryo: Kryo, output: Output, obj: PrivateKey) {
checkUseCase()
output.writeBytesWithLength(obj.encoded)
}

View File

@ -10,10 +10,9 @@ import com.esotericsoftware.kryo.io.Output
import com.esotericsoftware.kryo.pool.KryoPool
import com.esotericsoftware.kryo.serializers.ClosureSerializer
import net.corda.core.internal.uncheckedCast
import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationDefaults
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.*
import net.corda.core.serialization.internal.CheckpointSerializationContext
import net.corda.core.serialization.internal.CheckpointSerializationScheme
import net.corda.core.utilities.ByteSequence
import net.corda.serialization.internal.*
import java.security.PublicKey
@ -32,46 +31,30 @@ private object AutoCloseableSerialisationDetector : Serializer<AutoCloseable>()
override fun read(kryo: Kryo, input: Input, type: Class<AutoCloseable>) = throw IllegalStateException("Should not reach here!")
}
abstract class AbstractKryoSerializationScheme : SerializationScheme {
object KryoSerializationScheme : CheckpointSerializationScheme {
private val kryoPoolsForContexts = ConcurrentHashMap<Pair<ClassWhitelist, ClassLoader>, KryoPool>()
protected abstract fun rpcClientKryoPool(context: SerializationContext): KryoPool
protected abstract fun rpcServerKryoPool(context: SerializationContext): KryoPool
// this can be overridden in derived serialization schemes
protected open val publicKeySerializer: Serializer<PublicKey> = PublicKeySerializer
private fun getPool(context: SerializationContext): KryoPool {
private fun getPool(context: CheckpointSerializationContext): KryoPool {
return kryoPoolsForContexts.computeIfAbsent(Pair(context.whitelist, context.deserializationClassLoader)) {
when (context.useCase) {
SerializationContext.UseCase.Checkpoint ->
KryoPool.Builder {
val serializer = Fiber.getFiberSerializer(false) as KryoSerializer
val classResolver = CordaClassResolver(context).apply { setKryo(serializer.kryo) }
// TODO The ClassResolver can only be set in the Kryo constructor and Quasar doesn't provide us with a way of doing that
val field = Kryo::class.java.getDeclaredField("classResolver").apply { isAccessible = true }
serializer.kryo.apply {
field.set(this, classResolver)
// don't allow overriding the public key serializer for checkpointing
DefaultKryoCustomizer.customize(this)
addDefaultSerializer(AutoCloseable::class.java, AutoCloseableSerialisationDetector)
register(ClosureSerializer.Closure::class.java, CordaClosureSerializer)
classLoader = it.second
}
}.build()
SerializationContext.UseCase.RPCClient ->
rpcClientKryoPool(context)
SerializationContext.UseCase.RPCServer ->
rpcServerKryoPool(context)
else ->
KryoPool.Builder {
DefaultKryoCustomizer.customize(CordaKryo(CordaClassResolver(context)), publicKeySerializer).apply { classLoader = it.second }
}.build()
}
KryoPool.Builder {
val serializer = Fiber.getFiberSerializer(false) as KryoSerializer
val classResolver = CordaClassResolver(context).apply { setKryo(serializer.kryo) }
// TODO The ClassResolver can only be set in the Kryo constructor and Quasar doesn't provide us with a way of doing that
val field = Kryo::class.java.getDeclaredField("classResolver").apply { isAccessible = true }
serializer.kryo.apply {
field.set(this, classResolver)
// don't allow overriding the public key serializer for checkpointing
DefaultKryoCustomizer.customize(this)
addDefaultSerializer(AutoCloseable::class.java, AutoCloseableSerialisationDetector)
register(ClosureSerializer.Closure::class.java, CordaClosureSerializer)
classLoader = it.second
}
}.build()
}
}
private fun <T : Any> SerializationContext.kryo(task: Kryo.() -> T): T {
private fun <T : Any> CheckpointSerializationContext.kryo(task: Kryo.() -> T): T {
return getPool(this).run { kryo ->
kryo.context.ensureCapacity(properties.size)
properties.forEach { kryo.context.put(it.key, it.value) }
@ -83,7 +66,7 @@ abstract class AbstractKryoSerializationScheme : SerializationScheme {
}
}
override fun <T : Any> deserialize(byteSequence: ByteSequence, clazz: Class<T>, context: SerializationContext): T {
override fun <T : Any> deserialize(byteSequence: ByteSequence, clazz: Class<T>, context: CheckpointSerializationContext): T {
val dataBytes = kryoMagic.consume(byteSequence)
?: throw KryoException("Serialized bytes header does not match expected format.")
return context.kryo {
@ -111,7 +94,7 @@ abstract class AbstractKryoSerializationScheme : SerializationScheme {
}
}
override fun <T : Any> serialize(obj: T, context: SerializationContext): SerializedBytes<T> {
override fun <T : Any> serialize(obj: T, context: CheckpointSerializationContext): SerializedBytes<T> {
return context.kryo {
SerializedBytes(kryoOutput {
kryoMagic.writeTo(this)
@ -131,13 +114,11 @@ abstract class AbstractKryoSerializationScheme : SerializationScheme {
}
}
val KRYO_CHECKPOINT_CONTEXT = SerializationContextImpl(
kryoMagic,
val KRYO_CHECKPOINT_CONTEXT = CheckpointSerializationContextImpl(
SerializationDefaults.javaClass.classLoader,
QuasarWhitelist,
emptyMap(),
true,
SerializationContext.UseCase.Checkpoint,
null,
AlwaysAcceptEncodingWhitelist
)

View File

@ -1,14 +0,0 @@
package net.corda.node.serialization.kryo
import com.esotericsoftware.kryo.pool.KryoPool
import net.corda.core.serialization.SerializationContext
import net.corda.serialization.internal.CordaSerializationMagic
class KryoServerSerializationScheme : AbstractKryoSerializationScheme() {
override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean {
return magic == kryoMagic && target == SerializationContext.UseCase.Checkpoint
}
override fun rpcClientKryoPool(context: SerializationContext): KryoPool = throw UnsupportedOperationException()
override fun rpcServerKryoPool(context: SerializationContext): KryoPool = throw UnsupportedOperationException()
}

View File

@ -8,7 +8,7 @@ interface NodePropertiesStore {
interface FlowsDrainingModeOperations {
fun setEnabled(enabled: Boolean)
fun setEnabled(enabled: Boolean, propagateChange: Boolean = true)
fun isEnabled(): Boolean

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.concurrent.OpenFuture
import net.corda.core.messaging.DataFeed
import net.corda.core.messaging.StateMachineTransactionMapping
import net.corda.core.node.NodeInfo
@ -24,10 +25,13 @@ 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
interface NetworkMapCacheInternal : NetworkMapCache, NetworkMapCacheBase {
override val nodeReady: OpenFuture<Void?>
interface NetworkMapCacheInternal : NetworkMapCache, NetworkMapCacheBaseInternal
interface NetworkMapCacheBaseInternal : NetworkMapCacheBase {
val allNodeHashes: List<SecureHash>
fun getNodeByHash(nodeHash: SecureHash): NodeInfo?
@ -129,6 +133,7 @@ interface ServiceHubInternal : ServiceHub {
}
fun getFlowFactory(initiatingFlowClass: Class<out FlowLogic<*>>): InitiatedFlowFactory<*>?
val cacheFactory: NamedCacheFactory
}
interface FlowStarter {

View File

@ -3,16 +3,16 @@ package net.corda.node.services.config
import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import net.corda.cliutils.CordaSystemUtils
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.createDirectories
import net.corda.core.internal.div
import net.corda.core.internal.exists
import net.corda.nodeapi.internal.*
import net.corda.nodeapi.internal.config.SSLConfiguration
import net.corda.nodeapi.internal.config.FileBasedCertificateStoreSupplier
import net.corda.nodeapi.internal.config.MutualSslConfiguration
import net.corda.nodeapi.internal.config.toProperties
import net.corda.nodeapi.internal.crypto.X509KeyStore
import net.corda.nodeapi.internal.crypto.loadKeyStore
import net.corda.nodeapi.internal.crypto.save
import org.slf4j.LoggerFactory
import java.nio.file.Path
@ -68,22 +68,33 @@ object ConfigHelper {
* the CA certs in Node resources. Then provision KeyStores into certificates folder under node path.
*/
// TODO Move this to KeyStoreConfigHelpers
fun NodeConfiguration.configureWithDevSSLCertificate() = configureDevKeyAndTrustStores(myLegalName)
fun NodeConfiguration.configureWithDevSSLCertificate() = p2pSslOptions.configureDevKeyAndTrustStores(myLegalName, signingCertificateStore, certificatesDirectory)
// TODO Move this to KeyStoreConfigHelpers
fun SSLConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500Name) {
fun MutualSslConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500Name, signingCertificateStore: FileBasedCertificateStoreSupplier, certificatesDirectory: Path) {
val specifiedTrustStore = trustStore.getOptional()
val specifiedKeyStore = keyStore.getOptional()
val specifiedSigningStore = signingCertificateStore.getOptional()
if (specifiedTrustStore != null && specifiedKeyStore != null && specifiedSigningStore != null) return
certificatesDirectory.createDirectories()
if (!trustStoreFile.exists()) {
loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/$DEV_CA_TRUST_STORE_FILE"), DEV_CA_TRUST_STORE_PASS).save(trustStoreFile, trustStorePassword)
if (specifiedTrustStore == null) {
loadDevCaTrustStore().copyTo(trustStore.get(true))
}
if (!sslKeystore.exists() || !nodeKeystore.exists()) {
val (nodeKeyStore) = createDevKeyStores(myLegalName)
if (keyStore.getOptional() == null || signingCertificateStore.getOptional() == null) {
val signingKeyStore = FileBasedCertificateStoreSupplier(signingCertificateStore.path, signingCertificateStore.password).get(true).also { it.registerDevSigningCertificates(myLegalName) }
FileBasedCertificateStoreSupplier(keyStore.path, keyStore.password).get(true).also { it.registerDevP2pCertificates(myLegalName) }
// Move distributed service composite key (generated by IdentityGenerator.generateToDisk) to keystore if exists.
val distributedServiceKeystore = certificatesDirectory / "distributedService.jks"
if (distributedServiceKeystore.exists()) {
val serviceKeystore = X509KeyStore.fromFile(distributedServiceKeystore, DEV_CA_KEY_STORE_PASS)
nodeKeyStore.update {
signingKeyStore.update {
serviceKeystore.aliases().forEach {
if (serviceKeystore.internal.isKeyEntry(it)) {
setPrivateKey(it, serviceKeystore.getPrivateKey(it, DEV_CA_PRIVATE_KEY_PASS), serviceKeystore.getCertificateChain(it))
@ -95,15 +106,3 @@ fun SSLConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500Name) {
}
}
}
/** This is generally covered by commons-lang. */
object CordaSystemUtils {
const val OS_NAME = "os.name"
const val MAC_PREFIX = "Mac"
const val WIN_PREFIX = "Windows"
fun isOsMac() = getOsName().startsWith(MAC_PREFIX)
fun isOsWindows() = getOsName().startsWith(WIN_PREFIX)
fun getOsName() = System.getProperty(OS_NAME)
}

View File

@ -11,7 +11,9 @@ 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.NodeSSLConfiguration
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
@ -30,7 +32,7 @@ private val DEFAULT_FLOW_MONITOR_PERIOD_MILLIS: Duration = Duration.ofMinutes(1)
private val DEFAULT_FLOW_MONITOR_SUSPENSION_LOGGING_THRESHOLD_MILLIS: Duration = Duration.ofMinutes(1)
private const val CORDAPPS_DIR_NAME_DEFAULT = "cordapps"
interface NodeConfiguration : NodeSSLConfiguration {
interface NodeConfiguration {
val myLegalName: CordaX500Name
val emailAddress: String
val jmxMonitoringHttpPort: Int?
@ -48,6 +50,7 @@ interface NodeConfiguration : NodeSSLConfiguration {
val notary: NotaryConfig?
val additionalNodeInfoPollingFrequencyMsec: Long
val p2pAddress: NetworkHostAndPort
val additionalP2PAddresses: List<NetworkHostAndPort>
val rpcOptions: NodeRpcOptions
val messagingServerAddress: NetworkHostAndPort?
val messagingServerExternal: Boolean
@ -69,9 +72,16 @@ interface NodeConfiguration : NodeSSLConfiguration {
val effectiveH2Settings: NodeH2Settings?
val flowMonitorPeriodMillis: Duration get() = DEFAULT_FLOW_MONITOR_PERIOD_MILLIS
val flowMonitorSuspensionLoggingThresholdMillis: Duration get() = DEFAULT_FLOW_MONITOR_SUSPENSION_LOGGING_THRESHOLD_MILLIS
val cordappDirectories: List<Path> get() = listOf(baseDirectory / CORDAPPS_DIR_NAME_DEFAULT)
val crlCheckSoftFail: Boolean
val jmxReporterType : JmxReporterType? get() = defaultJmxReporterType
val baseDirectory: Path
val certificatesDirectory: Path
val signingCertificateStore: FileBasedCertificateStoreSupplier
val p2pSslOptions: MutualSslConfiguration
val cordappDirectories: List<Path>
fun validate(): List<String>
companion object {
@ -176,8 +186,8 @@ data class NodeConfigurationImpl(
override val myLegalName: CordaX500Name,
override val jmxMonitoringHttpPort: Int? = null,
override val emailAddress: String,
override val keyStorePassword: String,
override val trustStorePassword: String,
private val keyStorePassword: String,
private val trustStorePassword: String,
override val crlCheckSoftFail: Boolean,
override val dataSourceProperties: Properties,
override val compatibilityZoneURL: URL? = null,
@ -189,6 +199,7 @@ data class NodeConfigurationImpl(
override val verifierType: VerifierType,
override val flowTimeout: FlowTimeoutConfiguration,
override val p2pAddress: NetworkHostAndPort,
override val additionalP2PAddresses: List<NetworkHostAndPort> = emptyList(),
private val rpcAddress: NetworkHostAndPort? = null,
private val rpcSettings: NodeRpcSettings,
override val messagingServerAddress: NetworkHostAndPort?,
@ -243,6 +254,17 @@ 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)
private val p2pTrustStoreFilePath: Path get() = certificatesDirectory / "truststore.jks"
private val p2pTrustStore = FileBasedCertificateStoreSupplier(p2pTrustStoreFilePath, trustStorePassword)
override val p2pSslOptions: MutualSslConfiguration = SslConfiguration.mutual(p2pKeyStore, p2pTrustStore)
override val rpcOptions: NodeRpcOptions
get() {
return actualRpcSettings.asOptions()
@ -356,8 +378,6 @@ data class NodeConfigurationImpl(
}
}
data class NodeRpcSettings(
val address: NetworkHostAndPort?,
val adminAddress: NetworkHostAndPort?,

View File

@ -1,6 +1,7 @@
package net.corda.node.services.config.shell
import net.corda.core.internal.div
import net.corda.node.internal.clientSslOptionsCompatibleWith
import net.corda.node.services.config.NodeConfiguration
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.INTERNAL_SHELL_USER
import net.corda.tools.shell.ShellConfiguration
@ -14,8 +15,8 @@ fun NodeConfiguration.toShellConfig() = ShellConfiguration(
cordappsDirectory = this.baseDirectory.toString() / CORDAPPS_DIR,
user = INTERNAL_SHELL_USER,
password = INTERNAL_SHELL_USER,
hostAndPort = this.rpcOptions.adminAddress,
nodeSslConfig = this,
hostAndPort = this.rpcOptions.address,
ssl = clientSslOptionsCompatibleWith(this.rpcOptions),
sshdPort = this.sshd?.port,
sshHostKeyDirectory = this.baseDirectory / SSHD_HOSTKEY_DIR,
noLocalShell = this.noLocalShell)

View File

@ -10,6 +10,7 @@ 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
@ -29,13 +30,14 @@ import javax.persistence.Lob
* cached for efficient lookup.
*/
@ThreadSafe
class PersistentIdentityService : SingletonSerializeAsToken(), IdentityServiceInternal {
class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSerializeAsToken(), IdentityServiceInternal {
companion object {
private val log = contextLogger()
fun createPKMap(): AppendOnlyPersistentMap<SecureHash, PartyAndCertificate, PersistentIdentity, String> {
fun createPKMap(cacheFactory: NamedCacheFactory): AppendOnlyPersistentMap<SecureHash, PartyAndCertificate, PersistentIdentity, String> {
return AppendOnlyPersistentMap(
"PersistentIdentityService_partyByKey",
cacheFactory = cacheFactory,
name = "PersistentIdentityService_partyByKey",
toPersistentEntityKey = { it.toString() },
fromPersistentEntity = {
Pair(
@ -50,9 +52,10 @@ class PersistentIdentityService : SingletonSerializeAsToken(), IdentityServiceIn
)
}
fun createX500Map(): AppendOnlyPersistentMap<CordaX500Name, SecureHash, PersistentIdentityNames, String> {
fun createX500Map(cacheFactory: NamedCacheFactory): AppendOnlyPersistentMap<CordaX500Name, SecureHash, PersistentIdentityNames, String> {
return AppendOnlyPersistentMap(
"PersistentIdentityService_partyByName",
cacheFactory = cacheFactory,
name = "PersistentIdentityService_partyByName",
toPersistentEntityKey = { it.toString() },
fromPersistentEntity = { Pair(CordaX500Name.parse(it.name), SecureHash.parse(it.publicKeyHash)) },
toPersistentEntity = { key: CordaX500Name, value: SecureHash ->
@ -101,8 +104,8 @@ class PersistentIdentityService : SingletonSerializeAsToken(), IdentityServiceIn
// CordaPersistence is not a c'tor parameter to work around the cyclic dependency
lateinit var database: CordaPersistence
private val keyToParties = createPKMap()
private val principalToParties = createX500Map()
private val keyToParties = createPKMap(cacheFactory)
private val principalToParties = createX500Map(cacheFactory)
fun start(trustRoot: X509Certificate, caCertificates: List<X509Certificate> = emptyList()) {
_trustRoot = trustRoot

View File

@ -6,6 +6,7 @@ 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
@ -25,12 +26,11 @@ import javax.persistence.Lob
*
* This class needs database transactions to be in-flight during method calls and init.
*/
class PersistentKeyManagementService(val identityService: PersistentIdentityService,
class PersistentKeyManagementService(cacheFactory: NamedCacheFactory, val identityService: PersistentIdentityService,
private val database: CordaPersistence) : SingletonSerializeAsToken(), KeyManagementServiceInternal {
@Entity
@javax.persistence.Table(name = "${NODE_DATABASE_PREFIX}our_key_pairs")
class PersistentKey(
@Id
@Column(name = "public_key_hash", length = MAX_HASH_HEX_SIZE, nullable = false)
var publicKeyHash: String,
@ -47,12 +47,15 @@ class PersistentKeyManagementService(val identityService: PersistentIdentityServ
}
private companion object {
fun createKeyMap(): AppendOnlyPersistentMap<PublicKey, PrivateKey, PersistentKey, String> {
fun createKeyMap(cacheFactory: NamedCacheFactory): AppendOnlyPersistentMap<PublicKey, PrivateKey, PersistentKey, String> {
return AppendOnlyPersistentMap(
"PersistentKeyManagementService_keys",
cacheFactory = cacheFactory,
name = "PersistentKeyManagementService_keys",
toPersistentEntityKey = { it.toStringShort() },
fromPersistentEntity = { Pair(Crypto.decodePublicKey(it.publicKey), Crypto.decodePrivateKey(
it.privateKey)) },
fromPersistentEntity = {
Pair(Crypto.decodePublicKey(it.publicKey),
Crypto.decodePrivateKey(it.privateKey))
},
toPersistentEntity = { key: PublicKey, value: PrivateKey ->
PersistentKey(key, value)
},
@ -61,7 +64,7 @@ class PersistentKeyManagementService(val identityService: PersistentIdentityServ
}
}
private val keysMap = createKeyMap()
private val keysMap = createKeyMap(cacheFactory)
override fun start(initialKeyPairs: Set<KeyPair>) {
initialKeyPairs.forEach { keysMap.addWithDuplicatesAllowed(it.public, it.private) }

View File

@ -1,6 +1,5 @@
package net.corda.node.services.messaging
import io.netty.channel.unix.Errors
import net.corda.core.internal.ThreadBox
import net.corda.core.internal.div
import net.corda.core.serialization.SingletonSerializeAsToken
@ -12,13 +11,13 @@ import net.corda.node.internal.artemis.BrokerJaasLoginModule.Companion.NODE_P2P_
import net.corda.node.internal.artemis.BrokerJaasLoginModule.Companion.PEER_ROLE
import net.corda.core.internal.errors.AddressBindingException
import net.corda.node.services.config.NodeConfiguration
import net.corda.nodeapi.ArtemisTcpTransport.Companion.p2pAcceptorTcpTransport
import net.corda.nodeapi.internal.AmqpMessageSizeChecksInterceptor
import net.corda.nodeapi.internal.ArtemisMessageSizeChecksInterceptor
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.INTERNAL_PREFIX
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.JOURNAL_HEADER_SIZE
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NOTIFICATIONS_ADDRESS
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_PREFIX
import net.corda.nodeapi.internal.ArtemisTcpTransport.Companion.p2pAcceptorTcpTransport
import net.corda.nodeapi.internal.requireOnDefaultFileSystem
import org.apache.activemq.artemis.api.core.SimpleString
import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl
@ -120,7 +119,7 @@ class ArtemisMessagingServer(private val config: NodeConfiguration,
bindingsDirectory = (artemisDir / "bindings").toString()
journalDirectory = (artemisDir / "journal").toString()
largeMessagesDirectory = (artemisDir / "large-messages").toString()
acceptorConfigurations = mutableSetOf(p2pAcceptorTcpTransport(NetworkHostAndPort(messagingServerAddress.host, messagingServerAddress.port), config))
acceptorConfigurations = mutableSetOf(p2pAcceptorTcpTransport(NetworkHostAndPort(messagingServerAddress.host, messagingServerAddress.port), config.p2pSslOptions))
// Enable built in message deduplication. Note we still have to do our own as the delayed commits
// and our own definition of commit mean that the built in deduplication cannot remove all duplicates.
idCacheSize = 2000 // Artemis Default duplicate cache size i.e. a guess
@ -162,8 +161,8 @@ class ArtemisMessagingServer(private val config: NodeConfiguration,
@Throws(IOException::class, KeyStoreException::class)
private fun createArtemisSecurityManager(): ActiveMQJAASSecurityManager {
val keyStore = config.loadSslKeyStore().internal
val trustStore = config.loadTrustStore().internal
val keyStore = config.p2pSslOptions.keyStore.get().value.internal
val trustStore = config.p2pSslOptions.trustStore.get().value.internal
val securityConfig = object : SecurityConfiguration() {
// Override to make it work with our login module

View File

@ -6,9 +6,9 @@ import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.serialization.internal.nodeSerializationEnv
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.node.internal.security.RPCSecurityManager
import net.corda.nodeapi.ArtemisTcpTransport
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_RPC_USER
import net.corda.nodeapi.internal.config.SSLConfiguration
import net.corda.nodeapi.internal.ArtemisTcpTransport
import net.corda.nodeapi.internal.config.MutualSslConfiguration
import org.apache.activemq.artemis.api.core.client.ActiveMQClient
import org.apache.activemq.artemis.api.core.client.ServerLocator
import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl
@ -16,7 +16,7 @@ import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl
/**
* Used by the Node to communicate with the RPC broker.
*/
class InternalRPCMessagingClient(val sslConfig: SSLConfiguration, val serverAddress: NetworkHostAndPort, val maxMessageSize: Int, val nodeName: CordaX500Name, val rpcServerConfiguration: RPCServerConfiguration) : SingletonSerializeAsToken(), AutoCloseable {
class InternalRPCMessagingClient(val sslConfig: MutualSslConfiguration, val serverAddress: NetworkHostAndPort, val maxMessageSize: Int, val nodeName: CordaX500Name, val rpcServerConfiguration: RPCServerConfiguration) : SingletonSerializeAsToken(), AutoCloseable {
private var locator: ServerLocator? = null
private var rpcServer: RPCServer? = null

View File

@ -4,6 +4,7 @@ import net.corda.core.crypto.SecureHash
import net.corda.core.identity.CordaX500Name
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
@ -15,17 +16,18 @@ import javax.persistence.Id
/**
* Encapsulate the de-duplication logic.
*/
class P2PMessageDeduplicator(private val database: CordaPersistence) {
class P2PMessageDeduplicator(cacheFactory: NamedCacheFactory, private val database: CordaPersistence) {
// A temporary in-memory set of deduplication IDs and associated high water mark details.
// When we receive a message we don't persist the ID immediately,
// so we store the ID here in the meantime (until the persisting db tx has committed). This is because Artemis may
// redeliver messages to the same consumer if they weren't ACKed.
private val beingProcessedMessages = ConcurrentHashMap<DeduplicationId, MessageMeta>()
private val processedMessages = createProcessedMessages()
private val processedMessages = createProcessedMessages(cacheFactory)
private fun createProcessedMessages(): AppendOnlyPersistentMap<DeduplicationId, MessageMeta, ProcessedMessage, String> {
private fun createProcessedMessages(cacheFactory: NamedCacheFactory): AppendOnlyPersistentMap<DeduplicationId, MessageMeta, ProcessedMessage, String> {
return AppendOnlyPersistentMap(
"P2PMessageDeduplicator_processedMessages",
cacheFactory = cacheFactory,
name = "P2PMessageDeduplicator_processedMessages",
toPersistentEntityKey = { it.toString },
fromPersistentEntity = { Pair(DeduplicationId(it.id), MessageMeta(it.insertionTime, it.hash, it.seqNo)) },
toPersistentEntity = { key: DeduplicationId, value: MessageMeta ->

View File

@ -1,6 +1,7 @@
package net.corda.node.services.messaging
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.ThreadBox
@ -26,7 +27,7 @@ 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.nodeapi.ArtemisTcpTransport.Companion.p2pConnectorTcpTransport
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
@ -34,6 +35,7 @@ import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.BRIDGE_NOT
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.JOURNAL_HEADER_SIZE
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2PMessagingHeaders
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEERS_PREFIX
import net.corda.nodeapi.internal.ArtemisTcpTransport.Companion.p2pConnectorTcpTransport
import net.corda.nodeapi.internal.bridging.BridgeControl
import net.corda.nodeapi.internal.bridging.BridgeEntry
import net.corda.nodeapi.internal.persistence.CordaPersistence
@ -81,6 +83,9 @@ class P2PMessagingClient(val config: NodeConfiguration,
private val nodeExecutor: AffinityExecutor.ServiceAffinityExecutor,
private val database: CordaPersistence,
private val networkMap: NetworkMapCacheInternal,
@Suppress("UNUSED")
private val metricRegistry: MetricRegistry,
cacheFactory: NamedCacheFactory,
private val isDrainingModeOn: () -> Boolean,
private val drainingModeWasChangedEvents: Observable<Pair<Boolean, Boolean>>
) : SingletonSerializeAsToken(), MessagingService, AddressToArtemisQueueResolver {
@ -120,7 +125,7 @@ class P2PMessagingClient(val config: NodeConfiguration,
private lateinit var advertisedAddress: NetworkHostAndPort
private var maxMessageSize: Int = -1
override val myAddress: SingleMessageRecipient get() = NodeAddress(myIdentity, advertisedAddress)
override val myAddress: SingleMessageRecipient get() = NodeAddress(myIdentity)
override val ourSenderUUID = UUID.randomUUID().toString()
private val state = ThreadBox(InnerState())
@ -129,7 +134,7 @@ class P2PMessagingClient(val config: NodeConfiguration,
private val handlers = ConcurrentHashMap<String, MessageHandler>()
private val deduplicator = P2PMessageDeduplicator(database)
private val deduplicator = P2PMessageDeduplicator(cacheFactory, database)
internal var messagingExecutor: MessagingExecutor? = null
/**
@ -149,7 +154,7 @@ class P2PMessagingClient(val config: NodeConfiguration,
started = true
log.info("Connecting to message broker: $serverAddress")
// TODO Add broker CN to config for host verification in case the embedded broker isn't used
val tcpTransport = p2pConnectorTcpTransport(serverAddress, config)
val tcpTransport = p2pConnectorTcpTransport(serverAddress, config.p2pSslOptions)
locator = ActiveMQClient.createServerLocatorWithoutHA(tcpTransport).apply {
// Never time out on our loopback Artemis connections. If we switch back to using the InVM transport this
// would be the default and the two lines below can be deleted.
@ -233,7 +238,7 @@ class P2PMessagingClient(val config: NodeConfiguration,
fun gatherAddresses(node: NodeInfo): Sequence<BridgeEntry> {
return state.locked {
node.legalIdentitiesAndCerts.map {
val messagingAddress = NodeAddress(it.party.owningKey, node.addresses.first())
val messagingAddress = NodeAddress(it.party.owningKey)
BridgeEntry(messagingAddress.queueName, node.addresses, node.legalIdentities.map { it.name })
}.filter { producerSession!!.queueQuery(SimpleString(it.queueName)).isExists }.asSequence()
}
@ -242,14 +247,14 @@ class P2PMessagingClient(val config: NodeConfiguration,
fun deployBridges(node: NodeInfo) {
gatherAddresses(node)
.forEach {
sendBridgeControl(BridgeControl.Create(myIdentity.toStringShort(), it))
sendBridgeControl(BridgeControl.Create(config.myLegalName.toString(), it))
}
}
fun destroyBridges(node: NodeInfo) {
gatherAddresses(node)
.forEach {
sendBridgeControl(BridgeControl.Delete(myIdentity.toStringShort(), it))
sendBridgeControl(BridgeControl.Delete(config.myLegalName.toString(), it))
}
}
@ -289,7 +294,7 @@ class P2PMessagingClient(val config: NodeConfiguration,
delayStartQueues += queue.toString()
}
}
val startupMessage = BridgeControl.NodeToBridgeSnapshot(myIdentity.toStringShort(), inboxes, requiredBridges)
val startupMessage = BridgeControl.NodeToBridgeSnapshot(config.myLegalName.toString(), inboxes, requiredBridges)
sendBridgeControl(startupMessage)
}
@ -310,12 +315,9 @@ class P2PMessagingClient(val config: NodeConfiguration,
return
}
eventsSubscription = p2pConsumer!!.messages
.doOnError { error -> throw error }
.doOnNext { message -> deliver(message) }
// this `run()` method is semantically meant to block until the message consumption runs, hence the latch here
.doOnCompleted(latch::countDown)
.doOnError { error -> throw error }
.subscribe()
.subscribe({ message -> deliver(message) }, { error -> throw error })
p2pConsumer!!
}
consumer.start()
@ -495,7 +497,7 @@ class P2PMessagingClient(val config: NodeConfiguration,
val peers = networkMap.getNodesByOwningKeyIndex(keyHash)
for (node in peers) {
val bridge = BridgeEntry(queueName, node.addresses, node.legalIdentities.map { it.name })
val createBridgeMessage = BridgeControl.Create(myIdentity.toStringShort(), bridge)
val createBridgeMessage = BridgeControl.Create(config.myLegalName.toString(), bridge)
sendBridgeControl(createBridgeMessage)
}
}
@ -540,7 +542,7 @@ class P2PMessagingClient(val config: NodeConfiguration,
override fun getAddressOfParty(partyInfo: PartyInfo): MessageRecipients {
return when (partyInfo) {
is PartyInfo.SingleNode -> NodeAddress(partyInfo.party.owningKey, partyInfo.addresses.single())
is PartyInfo.SingleNode -> NodeAddress(partyInfo.party.owningKey)
is PartyInfo.DistributedNode -> ServiceAddress(partyInfo.party.owningKey)
}
}

View File

@ -336,6 +336,7 @@ class RPCServer(
context.invocation.pushToLoggingContext()
when (arguments) {
is Try.Success -> {
log.debug { "Arguments: ${arguments.value.toTypedArray().contentDeepToString()}" }
rpcExecutor!!.submit {
val result = invokeRpc(context, clientToServer.methodName, arguments.value)
sendReply(clientToServer.replyId, clientToServer.clientAddress, result)

View File

@ -1,51 +0,0 @@
package net.corda.node.services.network
import net.corda.core.identity.AbstractParty
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.IdentityService
import net.corda.core.node.services.NetworkMapCache
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.utilities.contextLogger
import net.corda.node.services.api.NetworkMapCacheBaseInternal
import net.corda.node.services.api.NetworkMapCacheInternal
import net.corda.nodeapi.internal.persistence.CordaPersistence
class NetworkMapCacheImpl(
private val networkMapCacheBase: NetworkMapCacheBaseInternal,
private val identityService: IdentityService,
private val database: CordaPersistence
) : NetworkMapCacheBaseInternal by networkMapCacheBase, NetworkMapCacheInternal, SingletonSerializeAsToken() {
companion object {
private val logger = contextLogger()
}
fun start() {
for (nodeInfo in networkMapCacheBase.allNodes) {
for (identity in nodeInfo.legalIdentitiesAndCerts) {
identityService.verifyAndRegisterIdentity(identity)
}
}
networkMapCacheBase.changed.subscribe { mapChange ->
// TODO how should we handle network map removal
if (mapChange is NetworkMapCache.MapChange.Added) {
mapChange.node.legalIdentitiesAndCerts.forEach {
try {
identityService.verifyAndRegisterIdentity(it)
} catch (ignore: Exception) {
// Log a warning to indicate node info is not added to the network map cache.
logger.warn("Node info for :'${it.name}' is not added to the network map due to verification error.")
}
}
}
}
}
override fun getNodeByLegalIdentity(party: AbstractParty): NodeInfo? {
return database.transaction {
val wellKnownParty = identityService.wellKnownPartyFromAnonymous(party)
wellKnownParty?.let {
getNodesByLegalIdentityKey(it.owningKey).firstOrNull()
}
}
}
}

View File

@ -11,6 +11,7 @@ import net.corda.core.serialization.serialize
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.seconds
import net.corda.core.utilities.trace
import net.corda.node.VersionInfo
import net.corda.node.utilities.registration.cacheControl
import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.network.NetworkMap
@ -23,7 +24,7 @@ import java.security.cert.X509Certificate
import java.time.Duration
import java.util.*
class NetworkMapClient(compatibilityZoneURL: URL) {
class NetworkMapClient(compatibilityZoneURL: URL, private val versionInfo: VersionInfo) {
companion object {
private val logger = contextLogger()
}
@ -38,14 +39,18 @@ class NetworkMapClient(compatibilityZoneURL: URL) {
fun publish(signedNodeInfo: SignedNodeInfo) {
val publishURL = URL("$networkMapUrl/publish")
logger.trace { "Publishing NodeInfo to $publishURL." }
publishURL.post(signedNodeInfo.serialize())
publishURL.post(signedNodeInfo.serialize(),
"Platform-Version" to "${versionInfo.platformVersion}",
"Client-Version" to versionInfo.releaseVersion)
logger.trace { "Published NodeInfo to $publishURL successfully." }
}
fun ackNetworkParametersUpdate(signedParametersHash: SignedData<SecureHash>) {
val ackURL = URL("$networkMapUrl/ack-parameters")
logger.trace { "Sending network parameters with hash ${signedParametersHash.raw.deserialize()} approval to $ackURL." }
ackURL.post(signedParametersHash.serialize())
ackURL.post(signedParametersHash.serialize(),
"Platform-Version" to "${versionInfo.platformVersion}",
"Client-Version" to versionInfo.releaseVersion)
logger.trace { "Sent network parameters approval to $ackURL successfully." }
}

View File

@ -24,13 +24,12 @@ import java.nio.file.StandardCopyOption
import java.security.cert.X509Certificate
import java.time.Duration
import java.util.*
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledThreadPoolExecutor
import java.util.concurrent.TimeUnit
import kotlin.system.exitProcess
class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
private val fileWatcher: NodeInfoWatcher,
private val nodeInfoWatcher: NodeInfoWatcher,
private val networkMapClient: NetworkMapClient?,
private val baseDirectory: Path,
private val extraNetworkMapKeys: List<UUID>
@ -40,8 +39,10 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
private val defaultRetryInterval = 1.minutes
}
private val parametersUpdatesTrack: PublishSubject<ParametersUpdateInfo> = PublishSubject.create<ParametersUpdateInfo>()
private val executor = ScheduledThreadPoolExecutor(1, NamedThreadFactory("Network Map Updater Thread", Executors.defaultThreadFactory()))
private val parametersUpdatesTrack = PublishSubject.create<ParametersUpdateInfo>()
private val networkMapPoller = ScheduledThreadPoolExecutor(1, NamedThreadFactory("Network Map Updater Thread")).apply {
executeExistingDelayedTasksAfterShutdownPolicy = false
}
private var newNetworkParameters: Pair<ParametersUpdate, SignedNetworkParameters>? = null
private var fileWatcherSubscription: Subscription? = null
private lateinit var trustRoot: X509Certificate
@ -50,7 +51,7 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
override fun close() {
fileWatcherSubscription?.unsubscribe()
MoreExecutors.shutdownAndAwaitTermination(executor, 50, TimeUnit.SECONDS)
MoreExecutors.shutdownAndAwaitTermination(networkMapPoller, 50, TimeUnit.SECONDS)
}
fun start(trustRoot: X509Certificate, currentParametersHash: SecureHash, ourNodeInfoHash: SecureHash) {
@ -58,26 +59,38 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
this.trustRoot = trustRoot
this.currentParametersHash = currentParametersHash
this.ourNodeInfoHash = ourNodeInfoHash
// Subscribe to file based networkMap
fileWatcherSubscription = fileWatcher.nodeInfoUpdates().subscribe {
when (it) {
is NodeInfoUpdate.Add -> {
networkMapCache.addNode(it.nodeInfo)
}
is NodeInfoUpdate.Remove -> {
if (it.hash != ourNodeInfoHash) {
val nodeInfo = networkMapCache.getNodeByHash(it.hash)
nodeInfo?.let { networkMapCache.removeNode(it) }
watchForNodeInfoFiles()
if (networkMapClient != null) {
watchHttpNetworkMap()
}
}
private fun watchForNodeInfoFiles() {
nodeInfoWatcher
.nodeInfoUpdates()
.subscribe {
for (update in it) {
when (update) {
is NodeInfoUpdate.Add -> networkMapCache.addNode(update.nodeInfo)
is NodeInfoUpdate.Remove -> {
if (update.hash != ourNodeInfoHash) {
val nodeInfo = networkMapCache.getNodeByHash(update.hash)
nodeInfo?.let(networkMapCache::removeNode)
}
}
}
}
if (networkMapClient == null) {
// Mark the network map cache as ready on a successful poll of the node infos dir if not using
// the HTTP network map even if there aren't any node infos
networkMapCache.nodeReady.set(null)
}
}
}
}
}
if (networkMapClient == null) return
// Subscribe to remote network map if configured.
executor.executeExistingDelayedTasksAfterShutdownPolicy = false
executor.submit(object : Runnable {
private fun watchHttpNetworkMap() {
// The check may be expensive, so always run it in the background even the first time.
networkMapPoller.submit(object : Runnable {
override fun run() {
val nextScheduleDelay = try {
updateNetworkMapCache()
@ -86,9 +99,9 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
defaultRetryInterval
}
// Schedule the next update.
executor.schedule(this, nextScheduleDelay.toMillis(), TimeUnit.MILLISECONDS)
networkMapPoller.schedule(this, nextScheduleDelay.toMillis(), TimeUnit.MILLISECONDS)
}
}) // The check may be expensive, so always run it in the background even the first time.
})
}
fun trackParametersUpdate(): DataFeed<ParametersUpdateInfo?, ParametersUpdateInfo> {
@ -99,9 +112,13 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
}
fun updateNetworkMapCache(): Duration {
if (networkMapClient == null) throw CordaRuntimeException("Network map cache can be updated only if network map/compatibility zone URL is specified")
if (networkMapClient == null) {
throw CordaRuntimeException("Network map cache can be updated only if network map/compatibility zone URL is specified")
}
val (globalNetworkMap, cacheTimeout) = networkMapClient.getNetworkMap()
globalNetworkMap.parametersUpdate?.let { handleUpdateNetworkParameters(networkMapClient, it) }
val additionalHashes = extraNetworkMapKeys.flatMap {
try {
networkMapClient.getNetworkMap(it).payload.nodeInfoHashes
@ -111,6 +128,7 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
emptyList<SecureHash>()
}
}
val allHashesFromNetworkMap = (globalNetworkMap.nodeInfoHashes + additionalHashes).toSet()
if (currentParametersHash != globalNetworkMap.networkParameterHash) {
@ -120,12 +138,9 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
val currentNodeHashes = networkMapCache.allNodeHashes
// Remove node info from network map.
(currentNodeHashes - allHashesFromNetworkMap - fileWatcher.processedNodeInfoHashes)
.mapNotNull {
if (it != ourNodeInfoHash) {
networkMapCache.getNodeByHash(it)
} else null
}.forEach(networkMapCache::removeNode)
(currentNodeHashes - allHashesFromNetworkMap - nodeInfoWatcher.processedNodeInfoHashes)
.mapNotNull { if (it != ourNodeInfoHash) networkMapCache.getNodeByHash(it) else null }
.forEach(networkMapCache::removeNode)
(allHashesFromNetworkMap - currentNodeHashes).mapNotNull {
// Download new node info from network map
@ -141,6 +156,10 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
networkMapCache.addNode(it)
}
// Mark the network map cache as ready on a successful poll of the HTTP network map, even on the odd chance that
// it's empty
networkMapCache.nodeReady.set(null)
return cacheTimeout
}

View File

@ -3,23 +3,16 @@ package net.corda.node.services.network
import net.corda.core.crypto.SecureHash
import net.corda.core.internal.*
import net.corda.core.node.NodeInfo
import net.corda.core.serialization.internal.SerializationEnvironmentImpl
import net.corda.core.serialization.internal._contextSerializationEnv
import net.corda.core.serialization.serialize
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import net.corda.core.utilities.seconds
import net.corda.node.serialization.amqp.AMQPServerSerializationScheme
import net.corda.nodeapi.internal.NODE_INFO_DIRECTORY
import net.corda.nodeapi.internal.NodeInfoAndSigned
import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.network.NodeInfoFilesCopier
import net.corda.serialization.internal.AMQP_P2P_CONTEXT
import net.corda.serialization.internal.SerializationFactoryImpl
import rx.Observable
import rx.Scheduler
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.StandardCopyOption.REPLACE_EXISTING
import java.nio.file.attribute.FileTime
import java.time.Duration
@ -63,7 +56,7 @@ class NodeInfoWatcher(private val nodePath: Path,
}
}
internal data class NodeInfoFromFile(val nodeInfohash: SecureHash, val lastModified: FileTime)
private data class NodeInfoFromFile(val nodeInfohash: SecureHash, val lastModified: FileTime)
private val nodeInfosDir = nodePath / NODE_INFO_DIRECTORY
private val nodeInfoFilesMap = HashMap<Path, NodeInfoFromFile>()
@ -75,20 +68,16 @@ class NodeInfoWatcher(private val nodePath: Path,
}
/**
* Read all the files contained in [nodePath] / [NODE_INFO_DIRECTORY] and keep watching
* the folder for further updates.
* Read all the files contained in [nodePath] / [NODE_INFO_DIRECTORY] and keep watching the folder for further updates.
*
* We simply list the directory content every 5 seconds, the Java implementation of WatchService has been proven to
* be unreliable on MacOs and given the fairly simple use case we have, this simple implementation should do.
*
* @return an [Observable] returning [NodeInfoUpdate]s, at most one [NodeInfo] is returned for each processed file.
* @return an [Observable] that emits lists of [NodeInfoUpdate]s. Each emitted list is a poll event of the folder and
* may be empty if no changes were detected.
*/
fun nodeInfoUpdates(): Observable<NodeInfoUpdate> {
return Observable.interval(0, pollInterval.toMillis(), TimeUnit.MILLISECONDS, scheduler)
.flatMapIterable { loadFromDirectory() }
fun nodeInfoUpdates(): Observable<List<NodeInfoUpdate>> {
return Observable.interval(0, pollInterval.toMillis(), TimeUnit.MILLISECONDS, scheduler).map { pollDirectory() }
}
private fun loadFromDirectory(): List<NodeInfoUpdate> {
private fun pollDirectory(): List<NodeInfoUpdate> {
val processedPaths = HashSet<Path>()
val result = nodeInfosDir.list { paths ->
paths
@ -122,14 +111,3 @@ class NodeInfoWatcher(private val nodePath: Path,
return result.map { NodeInfoUpdate.Add(it.nodeInfo) } + removedHashes
}
}
// TODO Remove this once we have a tool that can read AMQP serialised files
fun main(args: Array<String>) {
_contextSerializationEnv.set(SerializationEnvironmentImpl(
SerializationFactoryImpl().apply {
registerScheme(AMQPServerSerializationScheme())
},
AMQP_P2P_CONTEXT)
)
println(Paths.get(args[0]).readObject<SignedNodeInfo>().verified())
}

View File

@ -1,25 +1,29 @@
package net.corda.node.services.network
import net.corda.core.concurrent.CordaFuture
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.toStringShort
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.bufferUntilSubscribed
import net.corda.core.internal.concurrent.OpenFuture
import net.corda.core.internal.concurrent.openFuture
import net.corda.core.messaging.DataFeed
import net.corda.core.node.NodeInfo
import net.corda.core.node.NotaryInfo
import net.corda.core.node.services.IdentityService
import net.corda.core.node.services.NetworkMapCache.MapChange
import net.corda.core.node.services.PartyInfo
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.serialization.serialize
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.Try
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.NetworkMapCacheBaseInternal
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
@ -33,8 +37,9 @@ import javax.annotation.concurrent.ThreadSafe
/** Database-based network map cache. */
@ThreadSafe
open class PersistentNetworkMapCache(private val database: CordaPersistence,
private val myLegalName: CordaX500Name) : SingletonSerializeAsToken(), NetworkMapCacheBaseInternal {
open class PersistentNetworkMapCache(cacheFactory: NamedCacheFactory,
private val database: CordaPersistence,
private val identityService: IdentityService) : NetworkMapCacheInternal, SingletonSerializeAsToken() {
companion object {
private val logger = contextLogger()
}
@ -44,10 +49,8 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence,
override val changed: Observable<MapChange> = _changed.wrapWithDatabaseTransaction()
private val changePublisher: rx.Observer<MapChange> get() = _changed.bufferUntilDatabaseCommit()
// TODO revisit the logic under which nodeReady and loadDBSuccess are set.
// with the NetworkMapService redesign their meaning is not too well defined.
private val _nodeReady = openFuture<Void?>()
override val nodeReady: CordaFuture<Void?> = _nodeReady
override val nodeReady: OpenFuture<Void?> = openFuture()
private lateinit var notaries: List<NotaryInfo>
override val notaryIdentities: List<Party> get() = notaries.map { it.identity }
@ -67,14 +70,14 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence,
fun start(notaries: List<NotaryInfo>) {
this.notaries = notaries
val otherNodeInfoCount = database.transaction {
session.createQuery(
"select count(*) from ${NodeInfoSchemaV1.PersistentNodeInfo::class.java.name} n join n.legalIdentitiesAndCerts i where i.name != :myLegalName")
.setParameter("myLegalName", myLegalName.toString())
.singleResult as Long
}
if (otherNodeInfoCount > 0) {
_nodeReady.set(null)
}
override fun getNodeByLegalIdentity(party: AbstractParty): NodeInfo? {
return database.transaction {
val wellKnownParty = identityService.wellKnownPartyFromAnonymous(party)
wellKnownParty?.let {
getNodesByLegalIdentityKey(it.owningKey).firstOrNull()
}
}
}
@ -123,8 +126,8 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence,
override fun getNodesByLegalIdentityKey(identityKey: PublicKey): List<NodeInfo> = nodesByKeyCache[identityKey]!!
private val nodesByKeyCache = NonInvalidatingCache<PublicKey, List<NodeInfo>>(
"PersistentNetworkMap_nodesByKey",
1024) { key ->
cacheFactory = cacheFactory,
name = "PersistentNetworkMap_nodesByKey") { key ->
database.transaction { queryByIdentityKey(session, key) }
}
@ -143,8 +146,8 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence,
}
private val identityByLegalNameCache = NonInvalidatingCache<CordaX500Name, Optional<PartyAndCertificate>>(
"PersistentNetworkMap_idByLegalName",
1024) { name ->
cacheFactory = cacheFactory,
name = "PersistentNetworkMap_idByLegalName") { name ->
Optional.ofNullable(database.transaction { queryIdentityByLegalName(session, name) })
}
@ -160,6 +163,7 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence,
val previousNode = getNodesByLegalIdentityKey(node.legalIdentities.first().owningKey).firstOrNull()
if (previousNode == null) {
logger.info("No previous node found")
if (!verifyAndRegisterIdentities(node)) return
database.transaction {
updateInfoDB(node, session)
changePublisher.onNext(MapChange.Added(node))
@ -169,6 +173,8 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence,
return
} else if (previousNode != node) {
logger.info("Previous node was found as: $previousNode")
// TODO We should be adding any new identities as well
if (!verifyIdentities(node)) return
database.transaction {
updateInfoDB(node, session)
changePublisher.onNext(MapChange.Modified(node, previousNode))
@ -177,12 +183,30 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence,
logger.info("Previous node was identical to incoming one - doing nothing")
}
}
if (node.legalIdentities[0].name != myLegalName) {
_nodeReady.set(null)
}
logger.debug { "Done adding node with info: $node" }
}
private fun verifyIdentities(node: NodeInfo): Boolean {
val failures = node.legalIdentitiesAndCerts.mapNotNull { Try.on { it.verify(identityService.trustAnchor) } as? Try.Failure }
if (failures.isNotEmpty()) {
logger.warn("$node has ${failures.size} invalid identities:")
failures.forEach { logger.warn("", it) }
}
return failures.isEmpty()
}
private fun verifyAndRegisterIdentities(node: NodeInfo): Boolean {
// First verify all the node's identities are valid before registering any of them
return if (verifyIdentities(node)) {
for (identity in node.legalIdentitiesAndCerts) {
identityService.verifyAndRegisterIdentity(identity)
}
true
} else {
false
}
}
override fun removeNode(node: NodeInfo) {
logger.info("Removing node with info: $node")
synchronized(_changed) {

View File

@ -65,7 +65,7 @@ class DBCheckpointStorage : CheckpointStorage {
override fun getCheckpoint(id: StateMachineRunId): SerializedBytes<Checkpoint>? {
val bytes = currentDBSession().get(DBCheckpoint::class.java, id.uuid.toString())?.checkpoint ?: return null
return SerializedBytes<Checkpoint>(bytes)
return SerializedBytes(bytes)
}
override fun getAllCheckpoints(): Stream<Pair<StateMachineRunId, SerializedBytes<Checkpoint>>> {
@ -78,8 +78,8 @@ class DBCheckpointStorage : CheckpointStorage {
}
}
override fun getCheckpointCount(connection: Connection): Long =
try {
override fun getCheckpointCount(connection: Connection): Long {
return try {
connection.prepareStatement("select count(*) from node_checkpoints").use { ps ->
ps.executeQuery().use { rs ->
rs.next()
@ -90,5 +90,5 @@ class DBCheckpointStorage : CheckpointStorage {
// Happens when the table was not created yet.
0L
}
}
}

View File

@ -15,6 +15,7 @@ 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
@ -31,7 +32,7 @@ typealias TxCacheValue = Pair<SerializedBytes<CoreTransaction>, List<Transaction
fun TxCacheValue.toSignedTx() = SignedTransaction(this.first, this.second)
fun SignedTransaction.toTxCacheValue() = TxCacheValue(this.txBits, this.sigs)
class DBTransactionStorage(cacheSizeBytes: Long, private val database: CordaPersistence) : WritableTransactionStorage, SingletonSerializeAsToken() {
class DBTransactionStorage(private val database: CordaPersistence, cacheFactory: NamedCacheFactory) : WritableTransactionStorage, SingletonSerializeAsToken() {
@Entity
@Table(name = "${NODE_DATABASE_PREFIX}transactions")
@ -49,9 +50,10 @@ class DBTransactionStorage(cacheSizeBytes: Long, private val database: CordaPers
)
private companion object {
fun createTransactionsMap(maxSizeInBytes: Long)
fun createTransactionsMap(cacheFactory: NamedCacheFactory)
: AppendOnlyPersistentMapBase<SecureHash, TxCacheValue, DBTransaction, String> {
return WeightBasedAppendOnlyPersistentMap<SecureHash, TxCacheValue, DBTransaction, String>(
cacheFactory = cacheFactory,
name = "DBTransactionStorage_transactions",
toPersistentEntityKey = { it.toString() },
fromPersistentEntity = {
@ -67,7 +69,6 @@ class DBTransactionStorage(cacheSizeBytes: Long, private val database: CordaPers
}
},
persistentEntityClass = DBTransaction::class.java,
maxWeight = maxSizeInBytes,
weighingFunc = { hash, tx -> hash.size + weighTx(tx) }
)
}
@ -86,7 +87,7 @@ class DBTransactionStorage(cacheSizeBytes: Long, private val database: CordaPers
}
}
private val txStorage = ThreadBox(createTransactionsMap(cacheSizeBytes))
private val txStorage = ThreadBox(createTransactionsMap(cacheFactory))
override fun addTransaction(transaction: SignedTransaction): Boolean = database.transaction {
txStorage.locked {

View File

@ -18,8 +18,8 @@ import net.corda.core.node.services.vault.AttachmentQueryCriteria
import net.corda.core.node.services.vault.AttachmentSort
import net.corda.core.serialization.*
import net.corda.core.utilities.contextLogger
import net.corda.node.services.config.NodeConfiguration
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
@ -43,9 +43,8 @@ import javax.persistence.*
@ThreadSafe
class NodeAttachmentService(
metrics: MetricRegistry,
private val database: CordaPersistence,
attachmentContentCacheSize: Long = NodeConfiguration.defaultAttachmentContentCacheSize,
attachmentCacheBound: Long = NodeConfiguration.defaultAttachmentCacheBound
cacheFactory: NamedCacheFactory,
private val database: CordaPersistence
) : AttachmentStorageInternal, SingletonSerializeAsToken() {
companion object {
private val log = contextLogger()
@ -206,8 +205,8 @@ class NodeAttachmentService(
// a problem somewhere else or this needs to be revisited.
private val attachmentContentCache = NonInvalidatingWeightBasedCache(
cacheFactory = cacheFactory,
name = "NodeAttachmentService_attachmentContent",
maxWeight = attachmentContentCacheSize,
weigher = Weigher<SecureHash, Optional<Pair<Attachment, ByteArray>>> { key, value -> key.size + if (value.isPresent) value.get().second.size else 0 },
loadFunction = { Optional.ofNullable(loadAttachmentContent(it)) }
)
@ -228,10 +227,9 @@ class NodeAttachmentService(
}
private val attachmentCache = NonInvalidatingCache<SecureHash, Optional<Attachment>>(
"NodeAttachmentService_attachemnt",
attachmentCacheBound) { key ->
Optional.ofNullable(createAttachment(key))
}
cacheFactory = cacheFactory,
name = "NodeAttachmentService_attachmentPresence",
loadFunction = { key -> Optional.ofNullable(createAttachment(key)) })
private fun createAttachment(key: SecureHash): Attachment? {
val content = attachmentContentCache.get(key)!!

View File

@ -57,12 +57,13 @@ class FlowsDrainingModeOperationsImpl(readPhysicalNodeId: () -> String, private
override val values = PublishSubject.create<Pair<Boolean, Boolean>>()!!
override fun setEnabled(enabled: Boolean) {
var oldValue: Boolean? = null
persistence.transaction {
oldValue = map.put(nodeSpecificFlowsExecutionModeKey, enabled.toString())?.toBoolean() ?: false
override fun setEnabled(enabled: Boolean, propagateChange: Boolean) {
val oldValue = persistence.transaction {
map.put(nodeSpecificFlowsExecutionModeKey, enabled.toString())?.toBoolean() ?: false
}
if (propagateChange) {
values.onNext(oldValue to enabled)
}
values.onNext(oldValue!! to enabled)
}
override fun isEnabled(): Boolean {

View File

@ -8,7 +8,7 @@ import net.corda.node.internal.artemis.BrokerJaasLoginModule.Companion.NODE_SECU
import net.corda.node.internal.artemis.BrokerJaasLoginModule.Companion.RPC_SECURITY_CONFIG
import net.corda.node.internal.security.RPCSecurityManager
import net.corda.nodeapi.BrokerRpcSslOptions
import net.corda.nodeapi.internal.config.SSLConfiguration
import net.corda.nodeapi.internal.config.MutualSslConfiguration
import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl
import org.apache.activemq.artemis.core.config.impl.SecurityConfiguration
import org.apache.activemq.artemis.core.server.ActiveMQServer
@ -28,17 +28,17 @@ class ArtemisRpcBroker internal constructor(
private val maxMessageSize: Int,
private val jmxEnabled: Boolean = false,
private val baseDirectory: Path,
private val nodeConfiguration: SSLConfiguration,
private val nodeConfiguration: MutualSslConfiguration,
private val shouldStartLocalShell: Boolean) : ArtemisBroker {
companion object {
private val logger = loggerFor<ArtemisRpcBroker>()
fun withSsl(configuration: SSLConfiguration, address: NetworkHostAndPort, adminAddress: NetworkHostAndPort, sslOptions: BrokerRpcSslOptions, securityManager: RPCSecurityManager, maxMessageSize: Int, jmxEnabled: Boolean, baseDirectory: Path, shouldStartLocalShell: Boolean): ArtemisBroker {
fun withSsl(configuration: MutualSslConfiguration, address: NetworkHostAndPort, adminAddress: NetworkHostAndPort, sslOptions: BrokerRpcSslOptions, securityManager: RPCSecurityManager, maxMessageSize: Int, jmxEnabled: Boolean, baseDirectory: Path, shouldStartLocalShell: Boolean): ArtemisBroker {
return ArtemisRpcBroker(address, adminAddress, sslOptions, true, securityManager, maxMessageSize, jmxEnabled, baseDirectory, configuration, shouldStartLocalShell)
}
fun withoutSsl(configuration: SSLConfiguration, address: NetworkHostAndPort, adminAddress: NetworkHostAndPort, securityManager: RPCSecurityManager, maxMessageSize: Int, jmxEnabled: Boolean, baseDirectory: Path, shouldStartLocalShell: Boolean): ArtemisBroker {
fun withoutSsl(configuration: MutualSslConfiguration, address: NetworkHostAndPort, adminAddress: NetworkHostAndPort, securityManager: RPCSecurityManager, maxMessageSize: Int, jmxEnabled: Boolean, baseDirectory: Path, shouldStartLocalShell: Boolean): ArtemisBroker {
return ArtemisRpcBroker(address, adminAddress, null, false, securityManager, maxMessageSize, jmxEnabled, baseDirectory, configuration, shouldStartLocalShell)
}
}
@ -83,14 +83,14 @@ class ArtemisRpcBroker internal constructor(
@Throws(IOException::class, KeyStoreException::class)
private fun createArtemisSecurityManager(loginListener: LoginListener): ActiveMQJAASSecurityManager {
val keyStore = nodeConfiguration.loadSslKeyStore().internal
val trustStore = nodeConfiguration.loadTrustStore().internal
val keyStore = nodeConfiguration.keyStore.get()
val trustStore = nodeConfiguration.trustStore.get()
val securityConfig = object : SecurityConfiguration() {
override fun getAppConfigurationEntry(name: String): Array<AppConfigurationEntry> {
val options = mapOf(
RPC_SECURITY_CONFIG to RPCJaasConfig(securityManager, loginListener, useSsl),
NODE_SECURITY_CONFIG to NodeJaasConfig(keyStore, trustStore)
NODE_SECURITY_CONFIG to NodeJaasConfig(keyStore.value.internal, trustStore.value.internal)
)
return arrayOf(AppConfigurationEntry(name, AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options))
}

View File

@ -4,12 +4,12 @@ import net.corda.core.internal.div
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.node.internal.artemis.BrokerJaasLoginModule
import net.corda.node.internal.artemis.SecureArtemisConfiguration
import net.corda.nodeapi.ArtemisTcpTransport.Companion.rpcAcceptorTcpTransport
import net.corda.nodeapi.ArtemisTcpTransport.Companion.rpcInternalAcceptorTcpTransport
import net.corda.nodeapi.BrokerRpcSslOptions
import net.corda.nodeapi.RPCApi
import net.corda.nodeapi.internal.ArtemisMessagingComponent
import net.corda.nodeapi.internal.config.SSLConfiguration
import net.corda.nodeapi.internal.ArtemisTcpTransport.Companion.rpcAcceptorTcpTransport
import net.corda.nodeapi.internal.ArtemisTcpTransport.Companion.rpcInternalAcceptorTcpTransport
import net.corda.nodeapi.internal.config.MutualSslConfiguration
import org.apache.activemq.artemis.api.core.SimpleString
import org.apache.activemq.artemis.core.config.CoreQueueConfiguration
import org.apache.activemq.artemis.core.security.Role
@ -17,7 +17,7 @@ import org.apache.activemq.artemis.core.settings.impl.AddressFullMessagePolicy
import org.apache.activemq.artemis.core.settings.impl.AddressSettings
import java.nio.file.Path
internal class RpcBrokerConfiguration(baseDirectory: Path, maxMessageSize: Int, jmxEnabled: Boolean, address: NetworkHostAndPort, adminAddress: NetworkHostAndPort?, sslOptions: BrokerRpcSslOptions?, useSsl: Boolean, nodeConfiguration: SSLConfiguration, shouldStartLocalShell: Boolean) : SecureArtemisConfiguration() {
internal class RpcBrokerConfiguration(baseDirectory: Path, maxMessageSize: Int, jmxEnabled: Boolean, address: NetworkHostAndPort, adminAddress: NetworkHostAndPort?, sslOptions: BrokerRpcSslOptions?, useSsl: Boolean, nodeConfiguration: MutualSslConfiguration, shouldStartLocalShell: Boolean) : SecureArtemisConfiguration() {
val loginListener: (String) -> Unit
init {

View File

@ -69,7 +69,7 @@ class NodeSchemaService(private val extraSchemas: Set<MappedSchema> = emptySet()
if (includeNotarySchemas) mapOf(Pair(NodeNotaryV1, SchemaOptions())) else emptyMap()
fun internalSchemas() = requiredSchemas.keys + extraSchemas.filter { schema -> // when mapped schemas from the finance module are present, they are considered as internal ones
schema::class.simpleName == "net.corda.finance.schemas.CashSchemaV1" || schema::class.simpleName == "net.corda.finance.schemas.CommercialPaperSchemaV1" }
schema::class.qualifiedName == "net.corda.finance.schemas.CashSchemaV1" || schema::class.qualifiedName == "net.corda.finance.schemas.CommercialPaperSchemaV1" }
override val schemaOptions: Map<MappedSchema, SchemaService.SchemaOptions> = requiredSchemas + extraSchemas.associateBy({ it }, { SchemaOptions() })

View File

@ -4,9 +4,9 @@ import co.paralleluniverse.fibers.Fiber
import co.paralleluniverse.fibers.Suspendable
import com.codahale.metrics.*
import net.corda.core.internal.concurrent.thenMatch
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.serialize
import net.corda.core.serialization.*
import net.corda.core.serialization.internal.CheckpointSerializationContext
import net.corda.core.serialization.internal.checkpointSerialize
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.trace
import net.corda.node.services.api.CheckpointStorage
@ -27,7 +27,7 @@ class ActionExecutorImpl(
private val checkpointStorage: CheckpointStorage,
private val flowMessaging: FlowMessaging,
private val stateMachineManager: StateMachineManagerInternal,
private val checkpointSerializationContext: SerializationContext,
private val checkpointSerializationContext: CheckpointSerializationContext,
metrics: MetricRegistry
) : ActionExecutor {
@ -237,7 +237,7 @@ class ActionExecutorImpl(
}
private fun serializeCheckpoint(checkpoint: Checkpoint): SerializedBytes<Checkpoint> {
return checkpoint.serialize(context = checkpointSerializationContext)
return checkpoint.checkpointSerialize(context = checkpointSerializationContext)
}
private fun cancelFlowTimeout(action: Action.CancelFlowTimeout) {

View File

@ -12,8 +12,8 @@ import net.corda.core.cordapp.Cordapp
import net.corda.core.flows.*
import net.corda.core.identity.Party
import net.corda.core.internal.*
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.serialize
import net.corda.core.serialization.internal.CheckpointSerializationContext
import net.corda.core.serialization.internal.checkpointSerialize
import net.corda.core.utilities.Try
import net.corda.core.utilities.debug
import net.corda.core.utilities.trace
@ -69,7 +69,7 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
val actionExecutor: ActionExecutor,
val stateMachine: StateMachine,
val serviceHub: ServiceHubInternal,
val checkpointSerializationContext: SerializationContext,
val checkpointSerializationContext: CheckpointSerializationContext,
val unfinishedFibers: ReusableLatch
)
@ -369,7 +369,7 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
Event.Suspend(
ioRequest = ioRequest,
maySkipCheckpoint = skipPersistingCheckpoint,
fiber = this.serialize(context = serializationContext.value)
fiber = this.checkpointSerialize(context = serializationContext.value)
)
} catch (throwable: Throwable) {
Event.Error(throwable)

View File

@ -5,4 +5,4 @@ import net.corda.core.CordaException
/**
* An exception propagated and thrown in case a session initiation fails.
*/
class SessionRejectException(reason: String) : CordaException(reason)
class SessionRejectException(message: String) : CordaException(message)

View File

@ -19,6 +19,10 @@ import net.corda.core.internal.concurrent.map
import net.corda.core.internal.concurrent.openFuture
import net.corda.core.messaging.DataFeed
import net.corda.core.serialization.*
import net.corda.core.serialization.internal.CheckpointSerializationContext
import net.corda.core.serialization.internal.CheckpointSerializationDefaults
import net.corda.core.serialization.internal.checkpointDeserialize
import net.corda.core.serialization.internal.checkpointSerialize
import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.Try
import net.corda.core.utilities.contextLogger
@ -36,7 +40,7 @@ import net.corda.node.utilities.AffinityExecutor
import net.corda.node.utilities.injectOldProgressTracker
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.wrapWithDatabaseTransaction
import net.corda.serialization.internal.SerializeAsTokenContextImpl
import net.corda.serialization.internal.CheckpointSerializeAsTokenContextImpl
import net.corda.serialization.internal.withTokenContext
import org.apache.activemq.artemis.utils.ReusableLatch
import rx.Observable
@ -103,7 +107,7 @@ class SingleThreadedStateMachineManager(
private val transitionExecutor = makeTransitionExecutor()
private val ourSenderUUID = serviceHub.networkService.ourSenderUUID
private var checkpointSerializationContext: SerializationContext? = null
private var checkpointSerializationContext: CheckpointSerializationContext? = null
private var actionExecutor: ActionExecutor? = null
override val allStateMachines: List<FlowLogic<*>>
@ -122,8 +126,8 @@ class SingleThreadedStateMachineManager(
override fun start(tokenizableServices: List<Any>) {
checkQuasarJavaAgentPresence()
val checkpointSerializationContext = SerializationDefaults.CHECKPOINT_CONTEXT.withTokenContext(
SerializeAsTokenContextImpl(tokenizableServices, SerializationDefaults.SERIALIZATION_FACTORY, SerializationDefaults.CHECKPOINT_CONTEXT, serviceHub)
val checkpointSerializationContext = CheckpointSerializationDefaults.CHECKPOINT_CONTEXT.withTokenContext(
CheckpointSerializeAsTokenContextImpl(tokenizableServices, CheckpointSerializationDefaults.CHECKPOINT_SERIALIZATION_FACTORY, CheckpointSerializationDefaults.CHECKPOINT_CONTEXT, serviceHub)
)
this.checkpointSerializationContext = checkpointSerializationContext
this.actionExecutor = makeActionExecutor(checkpointSerializationContext)
@ -134,6 +138,7 @@ class SingleThreadedStateMachineManager(
(fiber as FlowStateMachineImpl<*>).logger.warn("Caught exception from flow", throwable)
}
serviceHub.networkMapCache.nodeReady.then {
logger.info("Node ready, info: ${serviceHub.myInfo}")
resumeRestoredFlows(fibers)
flowMessaging.start { _, deduplicationHandler ->
executor.execute {
@ -530,7 +535,7 @@ class SingleThreadedStateMachineManager(
val resultFuture = openFuture<Any?>()
flowStateMachineImpl.transientValues = TransientReference(createTransientValues(flowId, resultFuture))
flowLogic.stateMachine = flowStateMachineImpl
val frozenFlowLogic = (flowLogic as FlowLogic<*>).serialize(context = checkpointSerializationContext!!)
val frozenFlowLogic = (flowLogic as FlowLogic<*>).checkpointSerialize(context = checkpointSerializationContext!!)
val flowCorDappVersion = createSubFlowVersion(serviceHub.cordappProvider.getCordappForFlow(flowLogic), serviceHub.myInfo.platformVersion)
@ -612,7 +617,7 @@ class SingleThreadedStateMachineManager(
private fun deserializeCheckpoint(serializedCheckpoint: SerializedBytes<Checkpoint>): Checkpoint? {
return try {
serializedCheckpoint.deserialize(context = checkpointSerializationContext!!)
serializedCheckpoint.checkpointDeserialize(context = checkpointSerializationContext!!)
} catch (exception: Throwable) {
logger.error("Encountered unrestorable checkpoint!", exception)
null
@ -657,7 +662,7 @@ class SingleThreadedStateMachineManager(
val resultFuture = openFuture<Any?>()
val fiber = when (flowState) {
is FlowState.Unstarted -> {
val logic = flowState.frozenFlowLogic.deserialize(context = checkpointSerializationContext!!)
val logic = flowState.frozenFlowLogic.checkpointDeserialize(context = checkpointSerializationContext!!)
val state = StateMachineState(
checkpoint = checkpoint,
pendingDeduplicationHandlers = initialDeduplicationHandler?.let { listOf(it) } ?: emptyList(),
@ -676,7 +681,7 @@ class SingleThreadedStateMachineManager(
fiber
}
is FlowState.Started -> {
val fiber = flowState.frozenFiber.deserialize(context = checkpointSerializationContext!!)
val fiber = flowState.frozenFiber.checkpointDeserialize(context = checkpointSerializationContext!!)
val state = StateMachineState(
checkpoint = checkpoint,
pendingDeduplicationHandlers = initialDeduplicationHandler?.let { listOf(it) } ?: emptyList(),
@ -741,7 +746,7 @@ class SingleThreadedStateMachineManager(
}
}
private fun makeActionExecutor(checkpointSerializationContext: SerializationContext): ActionExecutor {
private fun makeActionExecutor(checkpointSerializationContext: CheckpointSerializationContext): ActionExecutor {
return ActionExecutorImpl(
serviceHub,
checkpointStorage,

View File

@ -2,9 +2,9 @@ package net.corda.node.services.statemachine.interceptors
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.flows.StateMachineRunId
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.*
import net.corda.core.serialization.internal.CheckpointSerializationContext
import net.corda.core.serialization.internal.checkpointDeserialize
import net.corda.core.utilities.contextLogger
import net.corda.node.services.statemachine.ActionExecutor
import net.corda.node.services.statemachine.Event
@ -68,7 +68,7 @@ class FiberDeserializationChecker {
private val jobQueue = LinkedBlockingQueue<Job>()
private var foundUnrestorableFibers: Boolean = false
fun start(checkpointSerializationContext: SerializationContext) {
fun start(checkpointSerializationContext: CheckpointSerializationContext) {
require(checkerThread == null)
checkerThread = thread(name = "FiberDeserializationChecker") {
while (true) {
@ -76,7 +76,7 @@ class FiberDeserializationChecker {
when (job) {
is Job.Check -> {
try {
job.serializedFiber.deserialize(context = checkpointSerializationContext)
job.serializedFiber.checkpointDeserialize(context = checkpointSerializationContext)
} catch (throwable: Throwable) {
log.error("Encountered unrestorable checkpoint!", throwable)
foundUnrestorableFibers = true

View File

@ -102,7 +102,8 @@ class BFTNonValidatingNotaryService(
private fun createMap(): AppendOnlyPersistentMap<StateRef, SecureHash, CommittedState, PersistentStateRef> {
return AppendOnlyPersistentMap(
"BFTNonValidatingNotaryService_transactions",
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

View File

@ -1,5 +1,6 @@
package net.corda.node.services.transactions
import net.corda.core.concurrent.CordaFuture
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TimeWindow
import net.corda.core.crypto.SecureHash
@ -8,9 +9,10 @@ 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.ThreadBox
import net.corda.core.internal.concurrent.OpenFuture
import net.corda.core.internal.concurrent.openFuture
import net.corda.core.internal.notary.AsyncUniquenessProvider
import net.corda.core.internal.notary.NotaryInternalException
import net.corda.core.internal.notary.UniquenessProvider
import net.corda.core.internal.notary.isConsumedByTheSameTx
import net.corda.core.internal.notary.validateTimeWindow
import net.corda.core.schemas.PersistentStateRef
@ -20,17 +22,22 @@ 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
import java.time.Clock
import java.time.Instant
import java.util.*
import java.util.concurrent.LinkedBlockingQueue
import javax.annotation.concurrent.ThreadSafe
import javax.persistence.*
import kotlin.concurrent.thread
/** A RDBMS backed Uniqueness provider */
@ThreadSafe
class PersistentUniquenessProvider(val clock: Clock) : UniquenessProvider, SingletonSerializeAsToken() {
class PersistentUniquenessProvider(val clock: Clock, val database: CordaPersistence, cacheFactory: NamedCacheFactory) : AsyncUniquenessProvider, SingletonSerializeAsToken() {
@MappedSuperclass
class BaseComittedState(
@EmbeddedId
@ -63,21 +70,41 @@ class PersistentUniquenessProvider(val clock: Clock) : UniquenessProvider, Singl
var requestDate: Instant
)
private data class CommitRequest(
val states: List<StateRef>,
val txId: SecureHash,
val callerIdentity: Party,
val requestSignature: NotarisationRequestSignature,
val timeWindow: TimeWindow?,
val references: List<StateRef>,
val future: OpenFuture<AsyncUniquenessProvider.Result>)
@Entity
@javax.persistence.Table(name = "${NODE_DATABASE_PREFIX}notary_committed_states")
class CommittedState(id: PersistentStateRef, consumingTxHash: String) : BaseComittedState(id, consumingTxHash)
private class InnerState {
val commitLog = createMap()
private val commitLog = createMap(cacheFactory)
private val requestQueue = LinkedBlockingQueue<CommitRequest>(requestQueueSize)
/** A request processor thread. */
private val processorThread = thread(name = "Notary request queue processor", isDaemon = true) {
try {
while (!Thread.interrupted()) {
processRequest(requestQueue.take())
}
} catch (e: InterruptedException) {
}
log.debug { "Shutting down with ${requestQueue.size} in-flight requests unprocessed." }
}
private val mutex = ThreadBox(InnerState())
companion object {
private const val requestQueueSize = 100_000
private val log = contextLogger()
fun createMap(): AppendOnlyPersistentMap<StateRef, SecureHash, CommittedState, PersistentStateRef> =
fun createMap(cacheFactory: NamedCacheFactory): AppendOnlyPersistentMap<StateRef, SecureHash, CommittedState, PersistentStateRef> =
AppendOnlyPersistentMap(
"PersistentUniquenessProvider_transactions",
cacheFactory = cacheFactory,
name = "PersistentUniquenessProvider_transactions",
toPersistentEntityKey = { PersistentStateRef(it.txhash.toString(), it.index) },
fromPersistentEntity = {
//TODO null check will become obsolete after making DB/JPA columns not nullable
@ -99,23 +126,25 @@ class PersistentUniquenessProvider(val clock: Clock) : UniquenessProvider, Singl
)
}
override fun commit(
/**
* Generates and adds a [CommitRequest] to the request queue. If the request queue is full, this method will block
* until space is available.
*
* Returns a future that will complete once the request is processed, containing the commit [Result].
*/
override fun commitAsync(
states: List<StateRef>,
txId: SecureHash,
callerIdentity: Party,
requestSignature: NotarisationRequestSignature,
timeWindow: TimeWindow?,
references: List<StateRef>
) {
mutex.locked {
logRequest(txId, callerIdentity, requestSignature)
val conflictingStates = findAlreadyCommitted(states, references, commitLog)
if (conflictingStates.isNotEmpty()) {
handleConflicts(txId, conflictingStates)
} else {
handleNoConflicts(timeWindow, states, txId, commitLog)
}
}
): CordaFuture<AsyncUniquenessProvider.Result> {
val future = openFuture<AsyncUniquenessProvider.Result>()
val request = CommitRequest(states, txId, callerIdentity, requestSignature, timeWindow, references, future)
requestQueue.put(request)
return future
}
private fun logRequest(txId: SecureHash, callerIdentity: Party, requestSignature: NotarisationRequestSignature) {
@ -149,6 +178,25 @@ class PersistentUniquenessProvider(val clock: Clock) : UniquenessProvider, Singl
return conflictingStates
}
private fun commitOne(
states: List<StateRef>,
txId: SecureHash,
callerIdentity: Party,
requestSignature: NotarisationRequestSignature,
timeWindow: TimeWindow?,
references: List<StateRef>
) {
database.transaction {
logRequest(txId, callerIdentity, requestSignature)
val conflictingStates = findAlreadyCommitted(states, references, commitLog)
if (conflictingStates.isNotEmpty()) {
handleConflicts(txId, conflictingStates)
} else {
handleNoConflicts(timeWindow, states, txId, commitLog)
}
}
}
private fun handleConflicts(txId: SecureHash, conflictingStates: LinkedHashMap<StateRef, StateConsumptionDetails>) {
if (isConsumedByTheSameTx(txId.sha256(), conflictingStates)) {
log.debug { "Transaction $txId already notarised" }
@ -171,4 +219,26 @@ class PersistentUniquenessProvider(val clock: Clock) : UniquenessProvider, Singl
throw NotaryInternalException(outsideTimeWindowError)
}
}
private fun processRequest(request: CommitRequest) {
try {
commitOne(request.states, request.txId, request.callerIdentity, request.requestSignature, request.timeWindow, request.references)
respondWithSuccess(request)
} catch (e: Exception) {
log.warn("Error processing commit request", e)
respondWithError(request, e)
}
}
private fun respondWithError(request: CommitRequest, exception: Exception) {
if (exception is NotaryInternalException) {
request.future.set(AsyncUniquenessProvider.Result.Failure(exception.error))
} else {
request.future.setException(NotaryInternalException(NotaryError.General(Exception("Internal service error."))))
}
}
private fun respondWithSuccess(request: CommitRequest) {
request.future.set(AsyncUniquenessProvider.Result.Success)
}
}

View File

@ -20,10 +20,10 @@ 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.SerializationDefaults
import net.corda.core.serialization.SerializationFactory
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
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
@ -200,11 +200,11 @@ class RaftTransactionCommitLog<E, EK>(
}
class CordaKryoSerializer<T : Any> : TypeSerializer<T> {
private val context = SerializationDefaults.CHECKPOINT_CONTEXT.withEncoding(CordaSerializationEncoding.SNAPPY)
private val factory = SerializationFactory.defaultFactory
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.serialize(context = context)
val serialized = obj.checkpointSerialize(context = context)
buffer.writeInt(serialized.size)
buffer.write(serialized.bytes)
}

View File

@ -28,8 +28,8 @@ 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.nodeapi.internal.config.NodeSSLConfiguration
import net.corda.nodeapi.internal.config.SSLConfiguration
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
@ -51,17 +51,20 @@ import javax.persistence.Table
*/
@ThreadSafe
class RaftUniquenessProvider(
private val transportConfiguration: NodeSSLConfiguration,
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(): AppendOnlyPersistentMap<StateRef, Pair<Long, SecureHash>, CommittedState, PersistentStateRef> =
fun createMap(cacheFactory: NamedCacheFactory): AppendOnlyPersistentMap<StateRef, Pair<Long, SecureHash>, CommittedState, PersistentStateRef> =
AppendOnlyPersistentMap(
"RaftUniquenessProvider_transactions",
cacheFactory = cacheFactory,
name = "RaftUniquenessProvider_transactions",
toPersistentEntityKey = { PersistentStateRef(it) },
fromPersistentEntity = {
val txId = it.id.txId
@ -96,8 +99,6 @@ class RaftUniquenessProvider(
var index: Long = 0
)
/** Directory storing the Raft log and state machine snapshots */
private val storagePath: Path = transportConfiguration.baseDirectory
private lateinit var _clientFuture: CompletableFuture<CopycatClient>
private lateinit var server: CopycatServer
@ -111,7 +112,7 @@ class RaftUniquenessProvider(
fun start() {
log.info("Creating Copycat server, log stored in: ${storagePath.toAbsolutePath()}")
val stateMachineFactory = {
RaftTransactionCommitLog(db, clock, RaftUniquenessProvider.Companion::createMap)
RaftTransactionCommitLog(db, clock, { createMap(cacheFactory) })
}
val address = raftConfig.nodeAddress.let { Address(it.host, it.port) }
val storage = buildStorage(storagePath)
@ -155,14 +156,14 @@ class RaftUniquenessProvider(
.build()
}
private fun buildTransport(config: SSLConfiguration): Transport? {
private fun buildTransport(config: MutualSslConfiguration): Transport? {
return NettyTransport.builder()
.withSsl()
.withSslProtocol(SslProtocol.TLSv1_2)
.withKeyStorePath(config.sslKeystore.toString())
.withKeyStorePassword(config.keyStorePassword)
.withTrustStorePath(config.trustStoreFile.toString())
.withTrustStorePassword(config.trustStorePassword)
.withKeyStorePath(config.keyStore.path.toString())
.withKeyStorePassword(config.keyStore.password)
.withTrustStorePath(config.trustStore.path.toString())
.withTrustStorePassword(config.trustStore.password)
.build()
}

View File

@ -8,10 +8,10 @@ import java.security.PublicKey
/** A simple Notary service that does not perform transaction validation */
class SimpleNotaryService(override val services: ServiceHubInternal, override val notaryIdentityKey: PublicKey) : TrustedAuthorityNotaryService() {
override val uniquenessProvider = PersistentUniquenessProvider(services.clock)
override val uniquenessProvider = PersistentUniquenessProvider(services.clock, services.database, services.cacheFactory)
override fun createServiceFlow(otherPartySession: FlowSession): NotaryServiceFlow = NonValidatingNotaryFlow(otherPartySession, this)
override fun start() {}
override fun stop() {}
}
}

View File

@ -8,10 +8,10 @@ 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)
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

@ -7,6 +7,13 @@ import net.corda.core.internal.uncheckedCast
import net.corda.core.node.services.Vault
import net.corda.core.node.services.VaultQueryException
import net.corda.core.node.services.vault.*
import net.corda.core.node.services.vault.BinaryComparisonOperator.*
import net.corda.core.node.services.vault.CollectionOperator.*
import net.corda.core.node.services.vault.ColumnPredicate.*
import net.corda.core.node.services.vault.EqualityComparisonOperator.*
import net.corda.core.node.services.vault.LikenessOperator.*
import net.corda.core.node.services.vault.NullOperator.IS_NULL
import net.corda.core.node.services.vault.NullOperator.NOT_NULL
import net.corda.core.node.services.vault.QueryCriteria.CommonQueryCriteria
import net.corda.core.schemas.PersistentState
import net.corda.core.schemas.PersistentStateRef
@ -54,54 +61,93 @@ abstract class AbstractQueryCriteriaParser<Q : GenericQueryCriteria<Q,P>, in P:
protected fun columnPredicateToPredicate(column: Path<out Any?>, columnPredicate: ColumnPredicate<*>): Predicate {
return when (columnPredicate) {
is ColumnPredicate.EqualityComparison -> {
val literal = columnPredicate.rightLiteral
when (columnPredicate.operator) {
EqualityComparisonOperator.EQUAL -> criteriaBuilder.equal(column, literal)
EqualityComparisonOperator.NOT_EQUAL -> criteriaBuilder.notEqual(column, literal)
}
}
is ColumnPredicate.BinaryComparison -> {
val literal: Comparable<Any?>? = uncheckedCast(columnPredicate.rightLiteral)
@Suppress("UNCHECKED_CAST")
column as Path<Comparable<Any?>?>
when (columnPredicate.operator) {
BinaryComparisonOperator.GREATER_THAN -> criteriaBuilder.greaterThan(column, literal)
BinaryComparisonOperator.GREATER_THAN_OR_EQUAL -> criteriaBuilder.greaterThanOrEqualTo(column, literal)
BinaryComparisonOperator.LESS_THAN -> criteriaBuilder.lessThan(column, literal)
BinaryComparisonOperator.LESS_THAN_OR_EQUAL -> criteriaBuilder.lessThanOrEqualTo(column, literal)
}
}
is ColumnPredicate.Likeness -> {
@Suppress("UNCHECKED_CAST")
column as Path<String?>
when (columnPredicate.operator) {
LikenessOperator.LIKE -> criteriaBuilder.like(column, columnPredicate.rightLiteral)
LikenessOperator.NOT_LIKE -> criteriaBuilder.notLike(column, columnPredicate.rightLiteral)
}
}
is ColumnPredicate.CollectionExpression -> {
when (columnPredicate.operator) {
CollectionOperator.IN -> column.`in`(columnPredicate.rightLiteral)
CollectionOperator.NOT_IN -> criteriaBuilder.not(column.`in`(columnPredicate.rightLiteral))
}
}
is ColumnPredicate.Between -> {
@Suppress("UNCHECKED_CAST")
column as Path<Comparable<Any?>?>
val fromLiteral: Comparable<Any?>? = uncheckedCast(columnPredicate.rightFromLiteral)
val toLiteral: Comparable<Any?>? = uncheckedCast(columnPredicate.rightToLiteral)
criteriaBuilder.between(column, fromLiteral, toLiteral)
}
is ColumnPredicate.NullExpression -> {
when (columnPredicate.operator) {
NullOperator.IS_NULL -> criteriaBuilder.isNull(column)
NullOperator.NOT_NULL -> criteriaBuilder.isNotNull(column)
}
}
is EqualityComparison -> equalityComparisonToPredicate(column, columnPredicate)
is BinaryComparison -> binaryComparisonToPredicate(column, columnPredicate)
is Likeness -> likeComparisonToPredicate(column, columnPredicate)
is CollectionExpression -> collectionComparisonToPredicate(column, columnPredicate)
is Between -> betweenComparisonToPredicate(column, columnPredicate)
is NullExpression -> nullComparisonToPredicate(column, columnPredicate)
else -> throw VaultQueryException("Not expecting $columnPredicate")
}
}
private fun equalityComparisonToPredicate(column: Path<out Any?>, columnPredicate: EqualityComparison<*>): Predicate {
val literal = columnPredicate.rightLiteral
return if (literal is String) {
@Suppress("UNCHECKED_CAST")
column as Path<String?>
when (columnPredicate.operator) {
EQUAL -> criteriaBuilder.equal(column, literal)
EQUAL_IGNORE_CASE -> criteriaBuilder.equal(criteriaBuilder.upper(column), literal.toUpperCase())
NOT_EQUAL -> criteriaBuilder.notEqual(column, literal)
NOT_EQUAL_IGNORE_CASE -> criteriaBuilder.notEqual(criteriaBuilder.upper(column), literal.toUpperCase())
}
} else {
when (columnPredicate.operator) {
EQUAL, EQUAL_IGNORE_CASE -> criteriaBuilder.equal(column, literal)
NOT_EQUAL, NOT_EQUAL_IGNORE_CASE -> criteriaBuilder.notEqual(column, literal)
}
}
}
private fun binaryComparisonToPredicate(column: Path<out Any?>, columnPredicate: BinaryComparison<*>): Predicate {
val literal: Comparable<Any?>? = uncheckedCast(columnPredicate.rightLiteral)
@Suppress("UNCHECKED_CAST")
column as Path<Comparable<Any?>?>
return when (columnPredicate.operator) {
GREATER_THAN -> criteriaBuilder.greaterThan(column, literal)
GREATER_THAN_OR_EQUAL -> criteriaBuilder.greaterThanOrEqualTo(column, literal)
LESS_THAN -> criteriaBuilder.lessThan(column, literal)
LESS_THAN_OR_EQUAL -> criteriaBuilder.lessThanOrEqualTo(column, literal)
}
}
private fun likeComparisonToPredicate(column: Path<out Any?>, columnPredicate: Likeness): Predicate {
@Suppress("UNCHECKED_CAST")
column as Path<String?>
return when (columnPredicate.operator) {
LIKE -> criteriaBuilder.like(column, columnPredicate.rightLiteral)
LIKE_IGNORE_CASE -> criteriaBuilder.like(criteriaBuilder.upper(column), columnPredicate.rightLiteral.toUpperCase())
NOT_LIKE -> criteriaBuilder.notLike(column, columnPredicate.rightLiteral)
NOT_LIKE_IGNORE_CASE -> criteriaBuilder.notLike(criteriaBuilder.upper(column), columnPredicate.rightLiteral.toUpperCase())
}
}
private fun collectionComparisonToPredicate(column: Path<out Any?>, columnPredicate: CollectionExpression<*>): Predicate {
val literal = columnPredicate.rightLiteral
return if (literal.any { it is String }) {
@Suppress("UNCHECKED_CAST")
column as Path<String?>
@Suppress("UNCHECKED_CAST")
literal as Collection<String>
when (columnPredicate.operator) {
IN -> column.`in`(literal)
IN_IGNORE_CASE -> criteriaBuilder.upper(column).`in`(literal.map { it.toUpperCase() })
NOT_IN -> criteriaBuilder.not(column.`in`(literal))
NOT_IN_IGNORE_CASE -> criteriaBuilder.not(criteriaBuilder.upper(column).`in`(literal.map { it.toUpperCase() }))
}
} else {
when (columnPredicate.operator) {
IN, IN_IGNORE_CASE -> column.`in`(literal)
NOT_IN, NOT_IN_IGNORE_CASE -> criteriaBuilder.not(column.`in`(literal))
}
}
}
private fun betweenComparisonToPredicate(column: Path<out Any?>, columnPredicate: Between<*>): Predicate {
@Suppress("UNCHECKED_CAST")
column as Path<Comparable<Any?>?>
val fromLiteral: Comparable<Any?>? = uncheckedCast(columnPredicate.rightFromLiteral)
val toLiteral: Comparable<Any?>? = uncheckedCast(columnPredicate.rightToLiteral)
return criteriaBuilder.between(column, fromLiteral, toLiteral)
}
private fun nullComparisonToPredicate(column: Path<out Any?>, columnPredicate: NullExpression<*>): Predicate {
return when (columnPredicate.operator) {
IS_NULL -> criteriaBuilder.isNull(column)
NOT_NULL -> criteriaBuilder.isNotNull(column)
}
}
}
class HibernateAttachmentQueryCriteriaParser(override val criteriaBuilder: CriteriaBuilder,
@ -474,7 +520,7 @@ class HibernateQueryCriteriaParser(val contractStateType: Class<out ContractStat
// state status
stateTypes = criteria.status
if (criteria.status != Vault.StateStatus.ALL) {
val predicateID = Pair(VaultSchemaV1.VaultStates::stateStatus.name, EqualityComparisonOperator.EQUAL)
val predicateID = Pair(VaultSchemaV1.VaultStates::stateStatus.name, EQUAL)
if (commonPredicates.containsKey(predicateID)) {
val existingStatus = ((commonPredicates[predicateID] as ComparisonPredicate).rightHandOperand as LiteralExpression).literal
if (existingStatus != criteria.status) {
@ -487,23 +533,23 @@ class HibernateQueryCriteriaParser(val contractStateType: Class<out ContractStat
}
// state relevance.
if (criteria.isRelevant != Vault.RelevancyStatus.ALL) {
val predicateID = Pair(VaultSchemaV1.VaultStates::isRelevant.name, EqualityComparisonOperator.EQUAL)
if (criteria.relevancyStatus != Vault.RelevancyStatus.ALL) {
val predicateID = Pair(VaultSchemaV1.VaultStates::relevancyStatus.name, EQUAL)
if (commonPredicates.containsKey(predicateID)) {
val existingStatus = ((commonPredicates[predicateID] as ComparisonPredicate).rightHandOperand as LiteralExpression).literal
if (existingStatus != criteria.isRelevant) {
log.warn("Overriding previous attribute [${VaultSchemaV1.VaultStates::isRelevant.name}] value $existingStatus with ${criteria.status}")
commonPredicates.replace(predicateID, criteriaBuilder.equal(vaultStates.get<Vault.RelevancyStatus>(VaultSchemaV1.VaultStates::isRelevant.name), criteria.isRelevant))
if (existingStatus != criteria.relevancyStatus) {
log.warn("Overriding previous attribute [${VaultSchemaV1.VaultStates::relevancyStatus.name}] value $existingStatus with ${criteria.status}")
commonPredicates.replace(predicateID, criteriaBuilder.equal(vaultStates.get<Vault.RelevancyStatus>(VaultSchemaV1.VaultStates::relevancyStatus.name), criteria.relevancyStatus))
}
} else {
commonPredicates[predicateID] = criteriaBuilder.equal(vaultStates.get<Vault.RelevancyStatus>(VaultSchemaV1.VaultStates::isRelevant.name), criteria.isRelevant)
commonPredicates[predicateID] = criteriaBuilder.equal(vaultStates.get<Vault.RelevancyStatus>(VaultSchemaV1.VaultStates::relevancyStatus.name), criteria.relevancyStatus)
}
}
// contract state types
val contractStateTypes = deriveContractStateTypes(criteria.contractStateTypes)
if (contractStateTypes.isNotEmpty()) {
val predicateID = Pair(VaultSchemaV1.VaultStates::contractStateClassName.name, CollectionOperator.IN)
val predicateID = Pair(VaultSchemaV1.VaultStates::contractStateClassName.name, IN)
if (commonPredicates.containsKey(predicateID)) {
val existingTypes = (commonPredicates[predicateID]!!.expressions[0] as InPredicate<*>).values.map { (it as LiteralExpression).literal }.toSet()
if (existingTypes != contractStateTypes) {

View File

@ -4,6 +4,7 @@ import co.paralleluniverse.fibers.Suspendable
import co.paralleluniverse.strands.Strand
import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.containsAny
import net.corda.core.internal.*
import net.corda.core.messaging.DataFeed
import net.corda.core.node.ServicesForResolution
@ -113,10 +114,12 @@ class NodeVaultService(
// For EVERY state to be committed to the vault, this checks whether it is spendable by the recording
// node. The behaviour is as follows:
//
// 1) All vault updates marked as RELEVANT will, of, course all have isRelevant = true.
// 2) For ALL_VISIBLE updates, those which are not relevant according to the relevancy rules will have isRelevant = false.
// 1) All vault updates marked as RELEVANT will, of course, all have relevancy_status = 1 in the
// "vault_states" table.
// 2) For ALL_VISIBLE updates, those which are not relevant according to the relevancy rules will have
// relevancy_status = 0 in the "vault_states" table.
//
// This is useful when it comes to querying for fungible states, when we do not want non-relevant states
// This is useful when it comes to querying for fungible states, when we do not want irrelevant states
// included in the result.
//
// The same functionality could be obtained by passing in a list of participants to the vault query,
@ -134,7 +137,7 @@ class NodeVaultService(
contractStateClassName = stateAndRef.value.state.data.javaClass.name,
stateStatus = Vault.StateStatus.UNCONSUMED,
recordedTime = clock.instant(),
isRelevant = if (isRelevant) Vault.RelevancyStatus.RELEVANT else Vault.RelevancyStatus.NOT_RELEVANT
relevancyStatus = if (isRelevant) Vault.RelevancyStatus.RELEVANT else Vault.RelevancyStatus.NOT_RELEVANT
)
stateToAdd.stateRef = PersistentStateRef(stateAndRef.key)
session.save(stateToAdd)
@ -402,7 +405,7 @@ class NodeVaultService(
val enrichedCriteria = QueryCriteria.VaultQueryCriteria(
contractStateTypes = setOf(contractStateType),
softLockingCondition = QueryCriteria.SoftLockingCondition(QueryCriteria.SoftLockingType.UNLOCKED_AND_SPECIFIED, listOf(lockId)),
isRelevant = Vault.RelevancyStatus.RELEVANT
relevancyStatus = Vault.RelevancyStatus.RELEVANT
)
val results = queryBy(contractStateType, enrichedCriteria.and(eligibleStatesQuery), sorter)
@ -433,7 +436,7 @@ class NodeVaultService(
is OwnableState -> (state.participants.map { it.owningKey } + state.owner.owningKey).toSet()
else -> state.participants.map { it.owningKey }
}
return keysToCheck.any { it in myKeys }
return keysToCheck.any { it.containsAny(myKeys) }
}
@Throws(VaultQueryException::class)
@ -511,7 +514,7 @@ class NodeVaultService(
vaultState.notary,
vaultState.lockId,
vaultState.lockUpdateTime,
vaultState.isRelevant))
vaultState.relevancyStatus))
} else {
// TODO: improve typing of returned other results
log.debug { "OtherResults: ${Arrays.toString(result.toArray())}" }

View File

@ -61,8 +61,8 @@ object VaultSchemaV1 : MappedSchema(schemaFamily = VaultSchema.javaClass, versio
var lockId: String? = null,
/** Used to determine whether a state abides by the relevancy rules of the recording node */
@Column(name = "is_relevant", nullable = false)
var isRelevant: Vault.RelevancyStatus,
@Column(name = "relevancy_status", nullable = false)
var relevancyStatus: Vault.RelevancyStatus,
/** refers to the last time a lock was taken (reserved) or updated (released, re-reserved) */
@Column(name = "lock_timestamp", nullable = true)

View File

@ -309,21 +309,20 @@ abstract class AppendOnlyPersistentMapBase<K, V, E, out EK>(
// Open for tests to override
open class AppendOnlyPersistentMap<K, V, E, out EK>(
cacheFactory: NamedCacheFactory,
name: String,
toPersistentEntityKey: (K) -> EK,
fromPersistentEntity: (E) -> Pair<K, V>,
toPersistentEntity: (key: K, value: V) -> E,
persistentEntityClass: Class<E>,
cacheBound: Long = 1024
persistentEntityClass: Class<E>
) : AppendOnlyPersistentMapBase<K, V, E, EK>(
toPersistentEntityKey,
fromPersistentEntity,
toPersistentEntity,
persistentEntityClass) {
//TODO determine cacheBound based on entity class later or with node config allowing tuning, or using some heuristic based on heap size
override val cache = NonInvalidatingCache(
cacheFactory = cacheFactory,
name = name,
bound = cacheBound,
loadFunction = { key: K ->
// This gets called if a value is read and the cache has no Transactional for this key yet.
val value: V? = loadValue(key)
@ -355,12 +354,12 @@ open class AppendOnlyPersistentMap<K, V, E, out EK>(
// Same as above, but with weighted values (e.g. memory footprint sensitive).
class WeightBasedAppendOnlyPersistentMap<K, V, E, out EK>(
cacheFactory: NamedCacheFactory,
name: String,
toPersistentEntityKey: (K) -> EK,
fromPersistentEntity: (E) -> Pair<K, V>,
toPersistentEntity: (key: K, value: V) -> E,
persistentEntityClass: Class<E>,
maxWeight: Long,
weighingFunc: (K, Transactional<V>) -> Int
) : AppendOnlyPersistentMapBase<K, V, E, EK>(
toPersistentEntityKey,
@ -368,8 +367,8 @@ class WeightBasedAppendOnlyPersistentMap<K, V, E, out EK>(
toPersistentEntity,
persistentEntityClass) {
override val cache = NonInvalidatingWeightBasedCache(
name,
maxWeight = maxWeight,
cacheFactory = cacheFactory,
name = name,
weigher = Weigher { key, value -> weighingFunc(key, value) },
loadFunction = { key: K ->
val value: V? = loadValue(key)

View File

@ -1,6 +1,5 @@
package net.corda.node.utilities
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.ThreadFactory
import java.util.concurrent.atomic.AtomicInteger
@ -10,19 +9,12 @@ import java.util.concurrent.atomic.AtomicInteger
* via an executor. It will use an underlying thread factory to create the actual thread
* and then override the thread name with the prefix and an ever increasing number
*/
class NamedThreadFactory(private val name: String, private val underlyingFactory: ThreadFactory) : ThreadFactory {
val threadNumber = AtomicInteger(1)
override fun newThread(runnable: Runnable?): Thread {
val thread = underlyingFactory.newThread(runnable)
class NamedThreadFactory(private val name: String,
private val delegate: ThreadFactory = Executors.defaultThreadFactory()) : ThreadFactory {
private val threadNumber = AtomicInteger(1)
override fun newThread(runnable: Runnable): Thread {
val thread = delegate.newThread(runnable)
thread.name = name + "-" + threadNumber.getAndIncrement()
return thread
}
}
/**
* Create a single thread executor with a NamedThreadFactory based on the default thread factory
* defined in java.util.concurrent.Executors
*/
fun newNamedSingleThreadExecutor(name: String): ExecutorService {
return Executors.newSingleThreadExecutor(NamedThreadFactory(name, Executors.defaultThreadFactory()))
}

View File

@ -0,0 +1,54 @@
package net.corda.node.utilities
import com.codahale.metrics.MetricRegistry
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.serialization.SerializeAsToken
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.node.services.config.NodeConfiguration
/**
* Allow passing metrics and config to caching implementations.
*/
interface NamedCacheFactory : SerializeAsToken {
/**
* Build a new cache factory of the same type that incorporates metrics.
*/
fun bindWithMetrics(metricRegistry: MetricRegistry): NamedCacheFactory
/**
* 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>
}
class DefaultNamedCacheFactory private constructor(private val metricRegistry: MetricRegistry?, private val nodeConfiguration: NodeConfiguration?) : NamedCacheFactory, 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 <K, V> buildNamed(caffeine: Caffeine<in K, in V>, name: String): Cache<K, V> {
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, 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)
}
}

View File

@ -4,19 +4,18 @@ 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.buildNamed
class NonInvalidatingCache<K, V> private constructor(
val cache: LoadingCache<K, V>
) : LoadingCache<K, V> by cache {
constructor(name: String, bound: Long, loadFunction: (K) -> V) :
this(buildCache(name, bound, loadFunction))
constructor(cacheFactory: NamedCacheFactory, name: String, loadFunction: (K) -> V) :
this(buildCache(cacheFactory, name, loadFunction))
private companion object {
private fun <K, V> buildCache(name: String, bound: Long, loadFunction: (K) -> V): LoadingCache<K, V> {
val builder = Caffeine.newBuilder().maximumSize(bound)
return builder.buildNamed(name, NonInvalidatingCacheLoader(loadFunction))
private fun <K, V> buildCache(cacheFactory: NamedCacheFactory, name: String, loadFunction: (K) -> V): LoadingCache<K, V> {
val builder = Caffeine.newBuilder()
return cacheFactory.buildNamed(builder, name, NonInvalidatingCacheLoader(loadFunction))
}
}
@ -33,13 +32,13 @@ class NonInvalidatingCache<K, V> private constructor(
class NonInvalidatingWeightBasedCache<K, V> private constructor(
val cache: LoadingCache<K, V>
) : LoadingCache<K, V> by cache {
constructor (name: String, maxWeight: Long, weigher: Weigher<K, V>, loadFunction: (K) -> V) :
this(buildCache(name, maxWeight, weigher, loadFunction))
constructor (cacheFactory: NamedCacheFactory, name: String, weigher: Weigher<K, V>, loadFunction: (K) -> V) :
this(buildCache(cacheFactory, name, weigher, loadFunction))
private companion object {
private fun <K, V> buildCache(name: String, maxWeight: Long, weigher: Weigher<K, V>, loadFunction: (K) -> V): LoadingCache<K, V> {
val builder = Caffeine.newBuilder().maximumWeight(maxWeight).weigher(weigher)
return builder.buildNamed(name, NonInvalidatingCache.NonInvalidatingCacheLoader(loadFunction))
private fun <K, V> buildCache(cacheFactory: NamedCacheFactory, name: String, weigher: Weigher<K, V>, loadFunction: (K) -> V): LoadingCache<K, V> {
val builder = Caffeine.newBuilder().weigher(weigher)
return cacheFactory.buildNamed(builder, name, NonInvalidatingCache.NonInvalidatingCacheLoader(loadFunction))
}
}
}

View File

@ -0,0 +1,43 @@
package net.corda.node.utilities.logging
import net.corda.nodeapi.internal.addShutdownHook
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.core.LoggerContext
import org.apache.logging.log4j.core.async.AsyncLoggerContext
import org.apache.logging.log4j.core.selector.ClassLoaderContextSelector
import org.apache.logging.log4j.core.util.Constants
import org.apache.logging.log4j.util.PropertiesUtil
import java.net.URI
class AsyncLoggerContextSelectorNoThreadLocal : ClassLoaderContextSelector() {
companion object {
/**
* Returns `true` if the user specified this selector as the Log4jContextSelector, to make all loggers
* asynchronous.
*
* @return `true` if all loggers are asynchronous, `false` otherwise.
*/
@JvmStatic
fun isSelected(): Boolean {
return AsyncLoggerContextSelectorNoThreadLocal::class.java.name == PropertiesUtil.getProperties().getStringProperty(Constants.LOG4J_CONTEXT_SELECTOR)
}
}
init {
// if we use async log4j logging, we need to shut down the logging to flush the loggers on exit
addShutdownHook { LogManager.shutdown() }
}
override fun createContext(name: String?, configLocation: URI?): LoggerContext {
return AsyncLoggerContext(name, null, configLocation).also { it.setUseThreadLocals(false) }
}
override fun toContextMapKey(loader: ClassLoader?): String {
// LOG4J2-666 ensure unique name across separate instances created by webapp classloaders
return "AsyncContextNoThreadLocal@" + Integer.toHexString(System.identityHashCode(loader))
}
override fun defaultContextName(): String {
return "DefaultAsyncContextNoThreadLocal@" + Thread.currentThread().name
}
}

View File

@ -52,7 +52,9 @@ class HTTPNetworkRegistrationService(compatibilityZoneURL: URL, val versionInfo:
}
override fun submitRequest(request: PKCS10CertificationRequest): String {
return String(registrationURL.post(OpaqueBytes(request.encoded), "Client-Version" to "${versionInfo.platformVersion}"))
return String(registrationURL.post(OpaqueBytes(request.encoded),
"Platform-Version" to "${versionInfo.platformVersion}",
"Client-Version" to versionInfo.releaseVersion))
}
}

View File

@ -6,7 +6,8 @@ import net.corda.core.internal.*
import net.corda.core.utilities.contextLogger
import net.corda.node.NodeRegistrationOption
import net.corda.node.services.config.NodeConfiguration
import net.corda.nodeapi.internal.config.SSLConfiguration
import net.corda.nodeapi.internal.config.CertificateStore
import net.corda.nodeapi.internal.config.CertificateStoreSupplier
import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509KeyStore
import net.corda.nodeapi.internal.crypto.X509Utilities
@ -33,7 +34,8 @@ import javax.security.auth.x500.X500Principal
* needed.
*/
// TODO: Use content signer instead of keypairs.
open class NetworkRegistrationHelper(private val config: SSLConfiguration,
open class NetworkRegistrationHelper(private val certificatesDirectory: Path,
private val signingCertificateStore: CertificateStoreSupplier,
private val myLegalName: CordaX500Name,
private val emailAddress: String,
private val certService: NetworkRegistrationService,
@ -44,14 +46,12 @@ open class NetworkRegistrationHelper(private val config: SSLConfiguration,
private val nextIdleDuration: (Duration?) -> Duration? = FixedPeriodLimitedRetrialStrategy(10, Duration.ofMinutes(1))) {
companion object {
const val SELF_SIGNED_PRIVATE_KEY = "Self Signed Private Key"
const val SELF_SIGNED_PRIVATE_KEY = "SelfSignedPrivateKey"
val logger = contextLogger()
}
private val requestIdStore = config.certificatesDirectory / "certificate-request-id.txt"
// TODO: Use different password for private key.
private val privateKeyPassword = config.keyStorePassword
private val rootTrustStore: X509KeyStore
private val requestIdStore = certificatesDirectory / "certificate-request-id.txt"
protected val rootTrustStore: X509KeyStore
protected val rootCert: X509Certificate
init {
@ -75,12 +75,14 @@ open class NetworkRegistrationHelper(private val config: SSLConfiguration,
* @throws CertificateRequestException if the certificate retrieved by doorman is invalid.
*/
fun buildKeystore() {
config.certificatesDirectory.createDirectories()
val nodeKeyStore = config.loadNodeKeyStore(createNew = true)
certificatesDirectory.createDirectories()
val nodeKeyStore = signingCertificateStore.get(createNew = true)
if (keyAlias in nodeKeyStore) {
println("Certificate already exists, Corda node will now terminate...")
return
}
// TODO: Use different password for private key.
val privateKeyPassword = nodeKeyStore.password
val tlsCrlIssuerCert = validateAndGetTlsCrlIssuerCert()
if (tlsCrlIssuerCert == null && isTlsCrlIssuerCertRequired()) {
System.err.println("""tlsCrlIssuerCert config does not match the root certificate issuer and nor is there any other certificate in the trust store with a matching issuer.
@ -89,7 +91,7 @@ open class NetworkRegistrationHelper(private val config: SSLConfiguration,
throw IllegalArgumentException("TLS CRL issuer certificate not found in the trust store.")
}
val keyPair = nodeKeyStore.loadOrCreateKeyPair(SELF_SIGNED_PRIVATE_KEY)
val keyPair = nodeKeyStore.loadOrCreateKeyPair(SELF_SIGNED_PRIVATE_KEY, privateKeyPassword)
val requestId = try {
submitOrResumeCertificateSigningRequest(keyPair)
@ -110,7 +112,7 @@ open class NetworkRegistrationHelper(private val config: SSLConfiguration,
throw certificateRequestException
}
validateCertificates(keyPair.public, certificates)
storePrivateKeyWithCertificates(nodeKeyStore, keyPair, certificates, keyAlias)
storePrivateKeyWithCertificates(nodeKeyStore, keyPair, certificates, keyAlias, privateKeyPassword)
onSuccess(keyPair, certificates, tlsCrlIssuerCert?.subjectX500Principal?.toX500Name())
// All done, clean up temp files.
requestIdStore.deleteIfExists()
@ -148,25 +150,32 @@ open class NetworkRegistrationHelper(private val config: SSLConfiguration,
println("Certificate signing request approved, storing private key with the certificate chain.")
}
private fun storePrivateKeyWithCertificates(nodeKeystore: X509KeyStore, keyPair: KeyPair, certificates: List<X509Certificate>, keyAlias: String) {
private fun storePrivateKeyWithCertificates(nodeKeystore: CertificateStore, keyPair: KeyPair, certificates: List<X509Certificate>, keyAlias: String, keyPassword: String) {
// Save private key and certificate chain to the key store.
nodeKeystore.setPrivateKey(keyAlias, keyPair.private, certificates, keyPassword = config.keyStorePassword)
nodeKeystore.internal.deleteEntry(SELF_SIGNED_PRIVATE_KEY)
nodeKeystore.save()
println("Private key '$keyAlias' and certificate stored in ${config.nodeKeystore}.")
with(nodeKeystore.value) {
setPrivateKey(keyAlias, keyPair.private, certificates, keyPassword = keyPassword)
// The key was temporarily stored as SELF_SIGNED_PRIVATE_KEY, but now that it's signed by the Doorman we
// can delete this old record.
internal.deleteEntry(SELF_SIGNED_PRIVATE_KEY)
save()
}
println("Private key '$keyAlias' and certificate stored in node signing keystore.")
}
private fun X509KeyStore.loadOrCreateKeyPair(alias: String): KeyPair {
private fun CertificateStore.loadOrCreateKeyPair(alias: String, privateKeyPassword: 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) {
// NODE_CA should be TLS compatible due to the cert hierarchy structure.
val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val selfSignCert = X509Utilities.createSelfSignedCACertificate(myLegalName.x500Principal, keyPair)
// Save to the key store.
setPrivateKey(alias, keyPair.private, listOf(selfSignCert), keyPassword = privateKeyPassword)
save()
with(value) {
setPrivateKey(alias, keyPair.private, listOf(selfSignCert), keyPassword = privateKeyPassword)
save()
}
}
return getCertificateAndKeyPair(alias, privateKeyPassword).keyPair
return query { getCertificateAndKeyPair(alias, privateKeyPassword) }.keyPair
}
/**
@ -243,7 +252,9 @@ open class NetworkRegistrationHelper(private val config: SSLConfiguration,
class NodeRegistrationException(cause: Throwable?) : IOException("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(config,
NetworkRegistrationHelper(
config.certificatesDirectory,
config.signingCertificateStore,
config.myLegalName,
config.emailAddress,
certService,
@ -263,7 +274,7 @@ class NodeRegistrationHelper(private val config: NodeConfiguration, certService:
}
private fun createSSLKeystore(nodeCAKeyPair: KeyPair, certificates: List<X509Certificate>, tlsCertCrlIssuer: X500Name?) {
config.loadSslKeyStore(createNew = true).update {
config.p2pSslOptions.keyStore.get(createNew = true).update {
println("Generating SSL certificate for node messaging service.")
val sslKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val sslCert = X509Utilities.createCertificate(
@ -277,17 +288,26 @@ class NodeRegistrationHelper(private val config: NodeConfiguration, certService:
logger.info("Generated TLS certificate: $sslCert")
setPrivateKey(CORDA_CLIENT_TLS, sslKeyPair.private, listOf(sslCert) + certificates)
}
println("SSL private key and certificate stored in ${config.sslKeystore}.")
println("SSL private key and certificate stored in ${config.p2pSslOptions.keyStore.path}.")
}
private fun createTruststore(rootCertificate: X509Certificate) {
// Save root certificates to trust store.
config.loadTrustStore(createNew = true).update {
config.p2pSslOptions.trustStore.get(createNew = true).update {
if (this.aliases().hasNext()) {
logger.warn("The node's trust store already exists. The following certificates will be overridden: ${this.aliases().asSequence()}")
}
println("Generating trust store for corda node.")
// Assumes certificate chain always starts with client certificate and end with root certificate.
setCertificate(CORDA_ROOT_CA, rootCertificate)
// Copy remaining certificates from the network-trust-store
rootTrustStore.aliases().asSequence().filter { it != CORDA_ROOT_CA }.forEach {
val certificate = rootTrustStore.getCertificate(it)
logger.info("Copying trusted certificate to the node's trust store: Alias: $it, Certificate: $certificate")
setCertificate(it, certificate)
}
}
println("Node trust store stored in ${config.trustStoreFile}.")
println("Node trust store stored in ${config.p2pSslOptions.trustStore.path}.")
}
override fun validateAndGetTlsCrlIssuerCert(): X509Certificate? {
@ -296,8 +316,9 @@ class NodeRegistrationHelper(private val config: NodeConfiguration, certService:
if (principalMatchesCertificatePrincipal(tlsCertCrlIssuer, rootCert)) {
return rootCert
}
return if (config.trustStoreFile.exists()) {
findMatchingCertificate(tlsCertCrlIssuer, config.loadTrustStore())
val trustStore = config.p2pSslOptions.trustStore.getOptional()
return if (trustStore != null) {
findMatchingCertificate(tlsCertCrlIssuer, trustStore.value)
} else {
null
}

View File

@ -0,0 +1,2 @@
Log4jContextSelector=net.corda.node.utilities.logging.AsyncLoggerContextSelectorNoThreadLocal
AsyncLogger.RingBufferSize=262144

View File

@ -3,6 +3,7 @@
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_tx_mapping_column">
<preConditions onFail="MARK_RAN"><not><columnExists tableName="node_transactions" columnName="state_machine_run_id"/></not></preConditions>
<addColumn tableName="node_transactions">
<column name="state_machine_run_id" type="NVARCHAR(36)">
<constraints nullable="true"/>

View File

@ -1,15 +1,19 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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">
<changeSet author="R3.Corda" id="add_is_relevant_column">
<addColumn tableName="vault_states">
<column name="is_relevant" type="INT"/>
</addColumn>
<update tableName="vault_states">
<column name="is_relevant" valueNumeric="0"/>
</update>
<addNotNullConstraint tableName="vault_states" columnName="is_relevant" columnDataType="INT" />
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_relevancy_status_column">
<preConditions onFail="MARK_RAN">
<not>
<columnExists tableName="vault_states" columnName="relevancy_status"/>
</not>
</preConditions>
<addColumn tableName="vault_states">
<column name="relevancy_status" type="INT"/>
</addColumn>
<update tableName="vault_states">
<column name="relevancy_status" valueNumeric="0"/>
</update>
<addNotNullConstraint tableName="vault_states" columnName="relevancy_status" columnDataType="INT"/>
</changeSet>
</databaseChangeLog>

View File

@ -0,0 +1,141 @@
- commandName: "<main class>"
positionalParams: []
params:
- parameterName: "--base-directory"
parameterType: "java.nio.file.Path"
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
multiParam: false
acceptableValues: []
- parameterName: "--config-file"
parameterType: "java.nio.file.Path"
required: false
multiParam: true
acceptableValues: []
- parameterName: "--dev-mode"
parameterType: "java.lang.Boolean"
required: false
multiParam: false
acceptableValues: []
- parameterName: "--initial-registration"
parameterType: "boolean"
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
multiParam: false
acceptableValues: []
- parameterName: "--just-generate-rpc-ssl-settings"
parameterType: "boolean"
required: false
multiParam: false
acceptableValues: []
- parameterName: "--log-to-console"
parameterType: "boolean"
required: false
multiParam: false
acceptableValues: []
- parameterName: "--logging-level"
parameterType: "org.slf4j.event.Level"
required: false
multiParam: false
acceptableValues:
- "ERROR"
- "WARN"
- "INFO"
- "DEBUG"
- "TRACE"
- parameterName: "--network-root-truststore"
parameterType: "java.nio.file.Path"
required: false
multiParam: true
acceptableValues: []
- parameterName: "--network-root-truststore-password"
parameterType: "java.lang.String"
required: false
multiParam: false
acceptableValues: []
- parameterName: "--no-local-shell"
parameterType: "boolean"
required: false
multiParam: false
acceptableValues: []
- parameterName: "--on-unknown-config-keys"
parameterType: "net.corda.nodeapi.internal.config.UnknownConfigKeysPolicy"
required: false
multiParam: false
acceptableValues:
- "FAIL"
- "WARN"
- "IGNORE"
- parameterName: "--sshd"
parameterType: "boolean"
required: false
multiParam: false
acceptableValues: []
- parameterName: "--sshd-port"
parameterType: "int"
required: false
multiParam: false
acceptableValues: []
- parameterName: "--verbose"
parameterType: "boolean"
required: false
multiParam: false
acceptableValues: []
- parameterName: "-b"
parameterType: "java.nio.file.Path"
required: false
multiParam: true
acceptableValues: []
- parameterName: "-c"
parameterType: "boolean"
required: false
multiParam: false
acceptableValues: []
- parameterName: "-d"
parameterType: "java.lang.Boolean"
required: false
multiParam: false
acceptableValues: []
- parameterName: "-f"
parameterType: "java.nio.file.Path"
required: false
multiParam: true
acceptableValues: []
- parameterName: "-n"
parameterType: "boolean"
required: false
multiParam: false
acceptableValues: []
- parameterName: "-p"
parameterType: "java.lang.String"
required: false
multiParam: false
acceptableValues: []
- parameterName: "-t"
parameterType: "java.nio.file.Path"
required: false
multiParam: true
acceptableValues: []
- parameterName: "-v"
parameterType: "boolean"
required: false
multiParam: false
acceptableValues: []

View File

@ -3,6 +3,7 @@ keyStorePassword = "cordacadevpass"
trustStorePassword = "trustpass"
crlCheckSoftFail = true
lazyBridgeStart = true
additionalP2PAddresses = []
dataSourceProperties = {
dataSourceClassName = org.h2.jdbcx.JdbcDataSource
dataSource.url = "jdbc:h2:file:"${baseDirectory}"/persistence;DB_CLOSE_ON_EXIT=FALSE;WRITE_DELAY=0;LOCK_TIMEOUT=10000"

View File

@ -1,189 +0,0 @@
package net.corda.node
import joptsimple.OptionException
import net.corda.core.internal.delete
import net.corda.core.internal.div
import net.corda.nodeapi.internal.config.UnknownConfigKeysPolicy
import net.corda.nodeapi.internal.crypto.X509KeyStore
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.junit.BeforeClass
import org.junit.Test
import org.slf4j.event.Level
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
class NodeArgsParserTest {
private val parser = NodeArgsParser()
companion object {
private lateinit var workingDirectory: Path
private lateinit var buildDirectory: Path
@BeforeClass
@JvmStatic
fun initDirectories() {
workingDirectory = Paths.get(".").normalize().toAbsolutePath()
buildDirectory = workingDirectory.resolve("build")
}
}
@Test
fun `no command line arguments`() {
assertThat(parser.parse()).isEqualTo(CmdLineOptions(
baseDirectory = workingDirectory,
configFile = workingDirectory / "node.conf",
logToConsole = false,
loggingLevel = Level.INFO,
nodeRegistrationOption = null,
isVersion = false,
noLocalShell = false,
sshdServer = false,
justGenerateNodeInfo = false,
justGenerateRpcSslCerts = false,
bootstrapRaftCluster = false,
unknownConfigKeysPolicy = UnknownConfigKeysPolicy.FAIL,
devMode = false,
clearNetworkMapCache = false))
}
@Test
fun `base-directory with relative path`() {
val expectedBaseDir = Paths.get("tmp").normalize().toAbsolutePath()
val cmdLineOptions = parser.parse("--base-directory", "tmp")
assertThat(cmdLineOptions.baseDirectory).isEqualTo(expectedBaseDir)
assertThat(cmdLineOptions.configFile).isEqualTo(expectedBaseDir / "node.conf")
}
@Test
fun `base-directory with absolute path`() {
val baseDirectory = Paths.get("tmp").normalize().toAbsolutePath()
val cmdLineOptions = parser.parse("--base-directory", baseDirectory.toString())
assertThat(cmdLineOptions.baseDirectory).isEqualTo(baseDirectory)
assertThat(cmdLineOptions.configFile).isEqualTo(baseDirectory / "node.conf")
}
@Test
fun `config-file with relative path`() {
val cmdLineOptions = parser.parse("--config-file", "different.conf")
assertThat(cmdLineOptions.baseDirectory).isEqualTo(workingDirectory)
assertThat(cmdLineOptions.configFile).isEqualTo(workingDirectory / "different.conf")
}
@Test
fun `config-file with absolute path`() {
val configFile = Paths.get("tmp", "a.conf").normalize().toAbsolutePath()
val cmdLineOptions = parser.parse("--config-file", configFile.toString())
assertThat(cmdLineOptions.baseDirectory).isEqualTo(workingDirectory)
assertThat(cmdLineOptions.configFile).isEqualTo(configFile)
}
@Test
fun `base-directory without argument`() {
assertThatExceptionOfType(OptionException::class.java).isThrownBy {
parser.parse("--base-directory")
}.withMessageContaining("base-directory")
}
@Test
fun `config-file without argument`() {
assertThatExceptionOfType(OptionException::class.java).isThrownBy {
parser.parse("--config-file")
}.withMessageContaining("config-file")
}
@Test
fun `log-to-console`() {
val cmdLineOptions = parser.parse("--log-to-console")
assertThat(cmdLineOptions.logToConsole).isTrue()
}
@Test
fun `logging-level`() {
for (level in Level.values()) {
val cmdLineOptions = parser.parse("--logging-level", level.name)
assertThat(cmdLineOptions.loggingLevel).isEqualTo(level)
}
}
@Test
fun `logging-level without argument`() {
assertThatExceptionOfType(OptionException::class.java).isThrownBy {
parser.parse("--logging-level")
}.withMessageContaining("logging-level")
}
@Test
fun `logging-level with invalid argument`() {
assertThatExceptionOfType(OptionException::class.java).isThrownBy {
parser.parse("--logging-level", "not-a-level")
}.withMessageContaining("logging-level")
}
@Test
fun `initial-registration`() {
// Create this temporary file in the "build" directory so that "clean" can delete it.
val truststorePath = buildDirectory / "truststore" / "file.jks"
assertThatExceptionOfType(IllegalArgumentException::class.java).isThrownBy {
parser.parse("--initial-registration", "--network-root-truststore", "$truststorePath", "--network-root-truststore-password", "password-test")
}.withMessageContaining("Network root trust store path").withMessageContaining("doesn't exist")
X509KeyStore.fromFile(truststorePath, "dummy_password", createNew = true)
try {
val cmdLineOptions = parser.parse("--initial-registration", "--network-root-truststore", "$truststorePath", "--network-root-truststore-password", "password-test")
assertNotNull(cmdLineOptions.nodeRegistrationOption)
assertEquals(truststorePath.toAbsolutePath(), cmdLineOptions.nodeRegistrationOption?.networkRootTrustStorePath)
assertEquals("password-test", cmdLineOptions.nodeRegistrationOption?.networkRootTrustStorePassword)
} finally {
truststorePath.delete()
}
}
@Test
fun version() {
val cmdLineOptions = parser.parse("--version")
assertThat(cmdLineOptions.isVersion).isTrue()
}
@Test
fun `generate node infos`() {
val cmdLineOptions = parser.parse("--just-generate-node-info")
assertThat(cmdLineOptions.justGenerateNodeInfo).isTrue()
}
@Test
fun `clear network map cache`() {
val cmdLineOptions = parser.parse("--clear-network-map-cache")
assertThat(cmdLineOptions.clearNetworkMapCache).isTrue()
}
@Test
fun `bootstrap raft cluster`() {
val cmdLineOptions = parser.parse("--bootstrap-raft-cluster")
assertThat(cmdLineOptions.bootstrapRaftCluster).isTrue()
}
@Test
fun `on-unknown-config-keys options`() {
UnknownConfigKeysPolicy.values().forEach { onUnknownConfigKeyPolicy ->
val cmdLineOptions = parser.parse("--on-unknown-config-keys", onUnknownConfigKeyPolicy.name)
assertThat(cmdLineOptions.unknownConfigKeysPolicy).isEqualTo(onUnknownConfigKeyPolicy)
}
}
@Test
fun `invalid argument`() {
assertThatExceptionOfType(IllegalArgumentException::class.java).isThrownBy {
parser.parse("foo")
}.withMessageContaining("Unrecognized argument(s): foo")
}
@Test
fun `invalid arguments`() {
assertThatExceptionOfType(IllegalArgumentException::class.java).isThrownBy {
parser.parse("foo", "bar")
}.withMessageContaining("Unrecognized argument(s): foo, bar")
}
}

View File

@ -0,0 +1,5 @@
package net.corda.node.internal
import net.corda.testing.CliBackwardsCompatibleTest
class NodeStartupCompatibilityTest : CliBackwardsCompatibleTest(NodeStartup::class.java)

View File

@ -0,0 +1,52 @@
package net.corda.node.internal
import net.corda.core.internal.div
import net.corda.nodeapi.internal.config.UnknownConfigKeysPolicy
import org.assertj.core.api.Assertions.assertThat
import org.junit.BeforeClass
import org.junit.Test
import org.slf4j.event.Level
import picocli.CommandLine
import java.nio.file.Path
import java.nio.file.Paths
class NodeStartupTest {
private val startup = NodeStartup()
companion object {
private lateinit var workingDirectory: Path
@BeforeClass
@JvmStatic
fun initDirectories() {
workingDirectory = Paths.get(".").normalize().toAbsolutePath()
}
}
@Test
fun `no command line arguments`() {
CommandLine.populateCommand(startup)
assertThat(startup.cmdLineOptions.baseDirectory).isEqualTo(workingDirectory)
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")
}
@Test
fun `--base-directory`() {
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")
}
}

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