ENT-4494 split nodeapi tests (#6024)

* Split out node-api tests that require test-utils/node-driver

* Add node-api test artefacts to publication list.

* Make test-common a transient dependency - downstream tests assume that it's available.

* Switch dependencies to java-library

* Fix magic package name for cordapp scanning in test
This commit is contained in:
Matthew Nesbit
2020-03-04 17:59:15 +00:00
committed by GitHub
parent e639091626
commit 9a406839fa
18 changed files with 148 additions and 47 deletions

View File

@ -0,0 +1,19 @@
apply plugin: 'kotlin'
description 'NodeAPI tests that require node etc'
dependencies {
testCompile project(":node-api")
testCompile project(path: ':node-api', configuration:'testArtifacts')
testImplementation "org.junit.jupiter:junit-jupiter-api:${junit_jupiter_version}"
testImplementation "junit:junit:$junit_version"
testRuntimeOnly "org.junit.vintage:junit-vintage-engine:${junit_vintage_version}"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${junit_jupiter_version}"
testRuntimeOnly "org.junit.platform:junit-platform-launcher:${junit_platform_version}"
// Unit testing helpers.
testCompile "org.assertj:assertj-core:$assertj_version"
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
testCompile project(':node-driver')
testCompile project(':test-utils')
}

View File

@ -0,0 +1,117 @@
package net.corda.nodeapitests.internal
import com.nhaarman.mockito_kotlin.any
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.node.ServicesForResolution
import net.corda.core.node.services.AttachmentStorage
import net.corda.core.node.services.NetworkParametersService
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.nodeapi.internal.cordapp.CordappLoader
import net.corda.node.internal.cordapp.CordappProviderImpl
import net.corda.node.internal.cordapp.JarScanningCordappLoader
import net.corda.nodeapitests.internal.AttachmentsClassLoaderStaticContractTests.AttachmentDummyContract.Companion.ATTACHMENT_PROGRAM_ID
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.TestIdentity
import net.corda.testing.internal.MockCordappConfigProvider
import net.corda.coretesting.internal.rigorousMock
import net.corda.testing.node.internal.cordappWithPackages
import net.corda.testing.services.MockAttachmentStorage
import org.assertj.core.api.Assertions.assertThat
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
class AttachmentsClassLoaderStaticContractTests {
private companion object {
val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party
val MEGA_CORP = TestIdentity(CordaX500Name("MegaCorp", "London", "GB")).party
}
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule()
class AttachmentDummyContract : Contract {
companion object {
const val ATTACHMENT_PROGRAM_ID = "net.corda.nodeapitests.internal.AttachmentsClassLoaderStaticContractTests\$AttachmentDummyContract"
}
data class State(val magicNumber: Int = 0) : ContractState {
override val participants: List<AbstractParty>
get() = listOf()
}
interface Commands : CommandData {
class Create : TypeOnlyCommandData(), Commands
}
override fun verify(tx: LedgerTransaction) {
// Always accepts.
}
fun generateInitial(owner: PartyAndReference, magicNumber: Int, notary: Party): TransactionBuilder {
val state = State(magicNumber)
return TransactionBuilder(notary)
.withItems(StateAndContract(state, ATTACHMENT_PROGRAM_ID), Command(Commands.Create(), owner.party.owningKey))
}
}
private val networkParameters = testNetworkParameters()
private val networkParametersService get() = mock<NetworkParametersService>().also {
doReturn(networkParameters.serialize().hash).whenever(it).currentHash
}
private val serviceHub get() = rigorousMock<ServicesForResolution>().also {
val cordappProviderImpl = CordappProviderImpl(cordappLoaderForPackages(listOf("net.corda.nodeapitests.internal")), MockCordappConfigProvider(), MockAttachmentStorage())
cordappProviderImpl.start()
doReturn(cordappProviderImpl).whenever(it).cordappProvider
doReturn(networkParametersService).whenever(it).networkParametersService
doReturn(networkParameters).whenever(it).networkParameters
val attachmentStorage = rigorousMock<AttachmentStorage>()
doReturn(attachmentStorage).whenever(it).attachments
val attachment = rigorousMock<ContractAttachment>()
doReturn(attachment).whenever(attachmentStorage).openAttachment(any())
doReturn(it.cordappProvider.getContractAttachmentID(AttachmentDummyContract.ATTACHMENT_PROGRAM_ID)).whenever(attachment).id
doReturn(setOf(AttachmentDummyContract.ATTACHMENT_PROGRAM_ID)).whenever(attachment).allContracts
doReturn("app").whenever(attachment).uploader
doReturn(emptyList<Party>()).whenever(attachment).signerKeys
val contractAttachmentId = SecureHash.randomSHA256()
doReturn(listOf(contractAttachmentId)).whenever(attachmentStorage)
.getLatestContractAttachments(AttachmentDummyContract.ATTACHMENT_PROGRAM_ID)
}
@Test(timeout=300_000)
fun `test serialization of WireTransaction with statically loaded contract`() {
val tx = AttachmentDummyContract()
.generateInitial(MEGA_CORP.ref(0), 42, DUMMY_NOTARY)
val wireTransaction = tx.toWireTransaction(serviceHub)
val bytes = wireTransaction.serialize()
val copiedWireTransaction = bytes.deserialize()
assertEquals(1, copiedWireTransaction.outputs.size)
assertEquals(42, (copiedWireTransaction.outputs[0].data as AttachmentDummyContract.State).magicNumber)
}
@Test(timeout=300_000)
fun `verify that contract DummyContract is in classPath`() {
val contractClass = Class.forName(ATTACHMENT_PROGRAM_ID)
assertThat(contractClass.getDeclaredConstructor().newInstance()).isInstanceOf(Contract::class.java)
}
private fun cordappLoaderForPackages(packages: Collection<String>): CordappLoader {
return JarScanningCordappLoader.fromJarUrls(listOf(cordappWithPackages(*packages.toTypedArray()).jarFile.toUri().toURL()))
}
}

View File

@ -0,0 +1,99 @@
package net.corda.nodeapitests.internal
import net.corda.node.services.schema.NodeSchemaService
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.internal.configureDatabase
import net.corda.testing.node.MockServices
import org.junit.After
import org.junit.Test
import java.util.concurrent.Phaser
import java.util.concurrent.atomic.AtomicInteger
import kotlin.concurrent.thread
import kotlin.test.assertEquals
class CordaPersistenceTest {
private val database = configureDatabase(MockServices.makeTestDataSourceProperties(),
DatabaseConfig(),
{ null }, { null },
NodeSchemaService(emptySet()))
@After
fun closeDatabase() {
database.close()
}
@Test(timeout=300_000)
fun `onAllOpenTransactionsClosed with zero transactions calls back immediately`() {
val counter = AtomicInteger(0)
database.onAllOpenTransactionsClosed { counter.incrementAndGet() }
assertEquals(1, counter.get())
}
@Test(timeout=300_000)
fun `onAllOpenTransactionsClosed with one transaction calls back after closing`() {
val counter = AtomicInteger(0)
database.transaction {
database.onAllOpenTransactionsClosed { counter.incrementAndGet() }
assertEquals(0, counter.get())
}
assertEquals(1, counter.get())
}
@Test(timeout=300_000)
fun `onAllOpenTransactionsClosed after one transaction has closed calls back immediately`() {
val counter = AtomicInteger(0)
database.transaction {
database.onAllOpenTransactionsClosed { counter.incrementAndGet() }
assertEquals(0, counter.get())
}
assertEquals(1, counter.get())
database.onAllOpenTransactionsClosed { counter.incrementAndGet() }
assertEquals(2, counter.get())
}
@Test(timeout=300_000)
fun `onAllOpenTransactionsClosed with two transactions calls back after closing both`() {
val counter = AtomicInteger(0)
val phaser = openTransactionInOtherThreadAndCloseWhenISay()
// Wait for tx to be started.
phaser.arriveAndAwaitAdvance()
database.transaction {
database.onAllOpenTransactionsClosed { counter.incrementAndGet() }
assertEquals(0, counter.get())
}
assertEquals(0, counter.get())
phaser.arriveAndAwaitAdvance()
phaser.arriveAndAwaitAdvance()
assertEquals(1, counter.get())
}
@Test(timeout = 10_000)
fun `onAllOpenTransactionsClosed with two transactions calls back after closing both - instigator closes last`() {
val counter = AtomicInteger(0)
val phaser = openTransactionInOtherThreadAndCloseWhenISay()
// Wait for tx to be started.
phaser.arriveAndAwaitAdvance()
database.transaction {
database.onAllOpenTransactionsClosed { counter.incrementAndGet() }
assertEquals(0, counter.get())
phaser.arriveAndAwaitAdvance()
phaser.arriveAndAwaitAdvance()
assertEquals(0, counter.get())
}
assertEquals(1, counter.get())
}
private fun openTransactionInOtherThreadAndCloseWhenISay(): Phaser {
val phaser = Phaser()
phaser.bulkRegister(2)
thread {
database.transaction {
phaser.arriveAndAwaitAdvance()
phaser.arriveAndAwaitAdvance()
}
// Tell caller we have committed.
phaser.arriveAndAwaitAdvance()
}
return phaser
}
}

View File

@ -0,0 +1,564 @@
package net.corda.nodeapitests.internal.crypto
import io.netty.handler.ssl.ClientAuth
import io.netty.handler.ssl.SslContextBuilder
import io.netty.handler.ssl.SslProvider
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.Crypto.COMPOSITE_KEY
import net.corda.core.crypto.Crypto.ECDSA_SECP256K1_SHA256
import net.corda.core.crypto.Crypto.ECDSA_SECP256R1_SHA256
import net.corda.core.crypto.Crypto.EDDSA_ED25519_SHA512
import net.corda.core.crypto.Crypto.RSA_SHA256
import net.corda.core.crypto.Crypto.SPHINCS256_SHA256
import net.corda.core.crypto.Crypto.generateKeyPair
import net.corda.core.crypto.SignatureScheme
import net.corda.core.crypto.newSecureRandom
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.div
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.core.utilities.days
import net.corda.core.utilities.hours
import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme
import net.corda.nodeapi.internal.config.MutualSslConfiguration
import net.corda.nodeapi.internal.createDevNodeCa
import net.corda.nodeapi.internal.crypto.X509Utilities.DEFAULT_IDENTITY_SIGNATURE_SCHEME
import net.corda.nodeapi.internal.crypto.X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME
import net.corda.nodeapi.internal.installDevNodeCaCertPath
import net.corda.nodeapi.internal.protonwrapper.netty.init
import net.corda.nodeapi.internal.registerDevP2pCertificates
import net.corda.serialization.internal.AllWhitelist
import net.corda.serialization.internal.SerializationContextImpl
import net.corda.serialization.internal.SerializationFactoryImpl
import net.corda.serialization.internal.amqp.amqpMagic
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.TestIdentity
import net.corda.testing.driver.internal.incrementalPortAllocation
import net.corda.coretesting.internal.NettyTestClient
import net.corda.coretesting.internal.NettyTestHandler
import net.corda.coretesting.internal.NettyTestServer
import net.corda.testing.internal.createDevIntermediateCaCertPath
import net.corda.coretesting.internal.stubs.CertificateStoreStubs
import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.crypto.checkValidity
import net.corda.nodeapi.internal.crypto.getSupportedKey
import net.corda.nodeapi.internal.crypto.loadOrCreateKeyStore
import net.corda.nodeapi.internal.crypto.save
import net.corda.nodeapi.internal.crypto.toBc
import net.corda.nodeapi.internal.crypto.x509
import net.i2p.crypto.eddsa.EdDSAPrivateKey
import org.assertj.core.api.Assertions.assertThat
import org.bouncycastle.asn1.x509.*
import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PrivateKey
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.io.DataInputStream
import java.io.DataOutputStream
import java.io.IOException
import java.net.InetAddress
import java.net.InetSocketAddress
import java.nio.file.Path
import java.security.Key
import java.security.KeyPair
import java.security.PrivateKey
import java.security.cert.CertPath
import java.security.cert.X509Certificate
import java.util.*
import javax.net.ssl.*
import javax.security.auth.x500.X500Principal
import kotlin.concurrent.thread
import kotlin.test.*
class X509UtilitiesTest {
private companion object {
val ALICE = TestIdentity(ALICE_NAME, 70).party
val BOB = TestIdentity(BOB_NAME, 80)
val MEGA_CORP = TestIdentity(CordaX500Name("MegaCorp", "London", "GB")).party
val CIPHER_SUITES = arrayOf(
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
)
val portAllocation = incrementalPortAllocation()
// We ensure that all of the algorithms are both used (at least once) as first and second in the following [Pair]s.
// We also add [DEFAULT_TLS_SIGNATURE_SCHEME] and [DEFAULT_IDENTITY_SIGNATURE_SCHEME] combinations for consistency.
val certChainSchemeCombinations = listOf(
Pair(DEFAULT_TLS_SIGNATURE_SCHEME, DEFAULT_TLS_SIGNATURE_SCHEME),
Pair(DEFAULT_IDENTITY_SIGNATURE_SCHEME, DEFAULT_IDENTITY_SIGNATURE_SCHEME),
Pair(DEFAULT_TLS_SIGNATURE_SCHEME, DEFAULT_IDENTITY_SIGNATURE_SCHEME),
Pair(ECDSA_SECP256R1_SHA256, SPHINCS256_SHA256),
Pair(ECDSA_SECP256K1_SHA256, RSA_SHA256),
Pair(EDDSA_ED25519_SHA512, ECDSA_SECP256K1_SHA256),
Pair(RSA_SHA256, EDDSA_ED25519_SHA512),
Pair(SPHINCS256_SHA256, ECDSA_SECP256R1_SHA256)
)
val schemeToKeyTypes = listOf(
// By default, JKS returns SUN EC key.
Triple(ECDSA_SECP256R1_SHA256,java.security.interfaces.ECPrivateKey::class.java, org.bouncycastle.jce.interfaces.ECPrivateKey::class.java),
Triple(ECDSA_SECP256K1_SHA256,java.security.interfaces.ECPrivateKey::class.java, org.bouncycastle.jce.interfaces.ECPrivateKey::class.java),
Triple(EDDSA_ED25519_SHA512, EdDSAPrivateKey::class.java, EdDSAPrivateKey::class.java),
// By default, JKS returns SUN RSA key.
Triple(SPHINCS256_SHA256, BCSphincs256PrivateKey::class.java, BCSphincs256PrivateKey::class.java)
)
}
@Rule
@JvmField
val tempFolder = TemporaryFolder()
@Test(timeout=300_000)
fun `create valid self-signed CA certificate`() {
Crypto.supportedSignatureSchemes().filter { it != COMPOSITE_KEY }.forEach { validSelfSignedCertificate(it) }
}
private fun validSelfSignedCertificate(signatureScheme: SignatureScheme) {
val caKey = generateKeyPair(signatureScheme)
val subject = X500Principal("CN=Test Cert,O=R3 Ltd,L=London,C=GB")
val caCert = X509Utilities.createSelfSignedCACertificate(subject, caKey)
assertEquals(subject, caCert.subjectX500Principal) // using our subject common name
assertEquals(caCert.issuerX500Principal, caCert.subjectX500Principal) //self-signed
caCert.checkValidity(Date()) // throws on verification problems
caCert.verify(caKey.public) // throws on verification problems
caCert.toBc().run {
val basicConstraints = BasicConstraints.getInstance(getExtension(Extension.basicConstraints).parsedValue)
val keyUsage = KeyUsage.getInstance(getExtension(Extension.keyUsage).parsedValue)
assertFalse { keyUsage.hasUsages(5) } // Bit 5 == keyCertSign according to ASN.1 spec (see full comment on KeyUsage property)
assertNull(basicConstraints.pathLenConstraint) // No length constraint specified on this CA certificate
}
}
@Test(timeout=300_000)
fun `load and save a PEM file certificate`() {
Crypto.supportedSignatureSchemes().filter { it != COMPOSITE_KEY }.forEach { loadSavePEMCert(it) }
}
private fun loadSavePEMCert(signatureScheme: SignatureScheme) {
val tmpCertificateFile = tempFile("cacert.pem")
val caKey = generateKeyPair(signatureScheme)
val caCert = X509Utilities.createSelfSignedCACertificate(X500Principal("CN=Test Cert,O=R3 Ltd,L=London,C=GB"), caKey)
X509Utilities.saveCertificateAsPEMFile(caCert, tmpCertificateFile)
val readCertificate = X509Utilities.loadCertificateFromPEMFile(tmpCertificateFile)
assertEquals(caCert, readCertificate)
}
@Test(timeout=300_000)
fun `create valid server certificate chain`() {
certChainSchemeCombinations.forEach { createValidServerCertChain(it.first, it.second) }
}
private fun createValidServerCertChain(signatureSchemeRoot: SignatureScheme, signatureSchemeChild: SignatureScheme) {
val (caKeyPair, caCert, _, childCert, _, childSubject)
= genCaAndChildKeysCertsAndSubjects(signatureSchemeRoot, signatureSchemeChild)
assertEquals(childSubject, childCert.subjectX500Principal) // Using our subject common name.
assertEquals(caCert.issuerX500Principal, childCert.issuerX500Principal) // Issued by our CA cert.
childCert.checkValidity(Date()) // Throws on verification problems.
childCert.verify(caKeyPair.public) // Throws on verification problems.
childCert.toBc().run {
val basicConstraints = BasicConstraints.getInstance(getExtension(Extension.basicConstraints).parsedValue)
val keyUsage = KeyUsage.getInstance(getExtension(Extension.keyUsage).parsedValue)
assertFalse { keyUsage.hasUsages(5) } // Bit 5 == keyCertSign according to ASN.1 spec (see full comment on KeyUsage property).
assertNull(basicConstraints.pathLenConstraint) // Non-CA certificate.
}
}
private data class CaAndChildKeysCertsAndSubjects(val caKeyPair: KeyPair,
val caCert: X509Certificate,
val childKeyPair: KeyPair,
val childCert: X509Certificate,
val caSubject: X500Principal,
val childSubject: X500Principal)
private fun genCaAndChildKeysCertsAndSubjects(signatureSchemeRoot: SignatureScheme,
signatureSchemeChild: SignatureScheme,
rootSubject: X500Principal = X500Principal("CN=Test CA Cert,O=R3 Ltd,L=London,C=GB"),
childSubject: X500Principal = X500Principal("CN=Test Child Cert,O=R3 Ltd,L=London,C=GB")): CaAndChildKeysCertsAndSubjects {
val caKeyPair = generateKeyPair(signatureSchemeRoot)
val caCert = X509Utilities.createSelfSignedCACertificate(rootSubject, caKeyPair)
val childKeyPair = generateKeyPair(signatureSchemeChild)
val childCert = X509Utilities.createCertificate(CertificateType.TLS, caCert, caKeyPair, childSubject, childKeyPair.public)
return CaAndChildKeysCertsAndSubjects(caKeyPair, caCert, childKeyPair, childCert, rootSubject, childSubject)
}
@Test(timeout=300_000)
fun `create valid server certificate chain includes CRL info`() {
certChainSchemeCombinations.forEach { createValidServerCertIncludeCRL(it.first, it.second) }
}
private fun createValidServerCertIncludeCRL(signatureSchemeRoot: SignatureScheme, signatureSchemeChild: SignatureScheme) {
val caKey = generateKeyPair(signatureSchemeRoot)
val caCert = X509Utilities.createSelfSignedCACertificate(X500Principal("CN=Test CA Cert,O=R3 Ltd,L=London,C=GB"), caKey)
val caSubjectKeyIdentifier = SubjectKeyIdentifier.getInstance(caCert.toBc().getExtension(Extension.subjectKeyIdentifier).parsedValue)
val keyPair = generateKeyPair(signatureSchemeChild)
val crlDistPoint = "http://test.com"
val serverCert = X509Utilities.createCertificate(
CertificateType.TLS,
caCert,
caKey,
X500Principal("CN=Server Cert,O=R3 Ltd,L=London,C=GB"),
keyPair.public,
crlDistPoint = crlDistPoint)
serverCert.toBc().run {
val certCrlDistPoint = CRLDistPoint.getInstance(getExtension(Extension.cRLDistributionPoints).parsedValue)
assertTrue(certCrlDistPoint.distributionPoints.first().distributionPoint.toString().contains(crlDistPoint))
val certCaAuthorityKeyIdentifier = AuthorityKeyIdentifier.getInstance(getExtension(Extension.authorityKeyIdentifier).parsedValue)
assertTrue(Arrays.equals(caSubjectKeyIdentifier.keyIdentifier, certCaAuthorityKeyIdentifier.keyIdentifier))
}
}
@Test(timeout=300_000)
fun `storing all supported key types in java keystore`() {
Crypto.supportedSignatureSchemes().filter { it != COMPOSITE_KEY }.forEach { storeKeyToKeystore(it) }
}
private fun storeKeyToKeystore(signatureScheme: SignatureScheme) {
val tmpKeyStore = tempFile("keystore.jks")
val keyPair = generateKeyPair(signatureScheme)
val testName = X500Principal("CN=Test,O=R3 Ltd,L=London,C=GB")
val selfSignCert = X509Utilities.createSelfSignedCACertificate(testName, keyPair)
assertTrue(Arrays.equals(selfSignCert.publicKey.encoded, keyPair.public.encoded))
// Save the private key with self sign cert in the keystore.
val keyStore = loadOrCreateKeyStore(tmpKeyStore, "keystorepass")
keyStore.setKeyEntry("Key", keyPair.private, "password".toCharArray(), arrayOf(selfSignCert))
keyStore.save(tmpKeyStore, "keystorepass")
// Load the keystore from file and make sure keys are intact.
val reloadedKeystore = loadOrCreateKeyStore(tmpKeyStore, "keystorepass")
val reloadedPrivateKey = reloadedKeystore.getKey("Key", "password".toCharArray())
val reloadedPublicKey = reloadedKeystore.getCertificate("Key").publicKey
assertNotNull(reloadedPublicKey)
assertNotNull(reloadedPrivateKey)
assertEquals(keyPair.public, reloadedPublicKey)
assertEquals(keyPair.private, reloadedPrivateKey)
}
@Test(timeout=300_000)
fun `create server certificate in keystore for SSL`() {
val certificatesDirectory = tempFolder.root.toPath()
val signingCertStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory, "serverstorepass")
val p2pSslConfig = CertificateStoreStubs.P2P.withCertificatesDirectory(certificatesDirectory, keyStorePassword = "serverstorepass")
val (rootCa, intermediateCa) = createDevIntermediateCaCertPath()
// Generate server cert and private key and populate another keystore suitable for SSL
val nodeCa = createDevNodeCa(intermediateCa, MEGA_CORP.name)
signingCertStore.get(createNew = true).also { it.installDevNodeCaCertPath(MEGA_CORP.name, rootCa.certificate, intermediateCa, nodeCa) }
p2pSslConfig.keyStore.get(createNew = true).also { it.registerDevP2pCertificates(MEGA_CORP.name, rootCa.certificate, intermediateCa, nodeCa) }
// Load back server certificate
val certStore = signingCertStore.get()
val serverKeyStore = certStore.value
val (serverCert, serverKeyPair) = serverKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA, certStore.entryPassword)
serverCert.checkValidity()
serverCert.verify(intermediateCa.certificate.publicKey)
assertThat(CordaX500Name.build(serverCert.subjectX500Principal)).isEqualTo(MEGA_CORP.name)
// Load back SSL certificate
val sslKeyStoreReloaded = p2pSslConfig.keyStore.get()
val (sslCert) = sslKeyStoreReloaded.query { getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_TLS, sslKeyStoreReloaded.entryPassword) }
sslCert.checkValidity()
sslCert.verify(serverCert.publicKey)
assertThat(CordaX500Name.build(sslCert.subjectX500Principal)).isEqualTo(MEGA_CORP.name)
// Now sign something with private key and verify against certificate public key
val testData = "123456".toByteArray()
val signature = Crypto.doSign(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, serverKeyPair.private, testData)
assertTrue { Crypto.isValid(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, serverCert.publicKey, signature, testData) }
}
@Test(timeout=300_000)
fun `create server cert and use in SSL socket`() {
val sslConfig = CertificateStoreStubs.P2P.withCertificatesDirectory(tempFolder.root.toPath(), keyStorePassword = "serverstorepass")
val (rootCa, intermediateCa) = createDevIntermediateCaCertPath()
// Generate server cert and private key and populate another keystore suitable for SSL
sslConfig.keyStore.get(true).registerDevP2pCertificates(MEGA_CORP.name, rootCa.certificate, intermediateCa)
sslConfig.createTrustStore(rootCa.certificate)
val keyStore = sslConfig.keyStore.get()
val trustStore = sslConfig.trustStore.get()
val context = SSLContext.getInstance("TLS")
val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
keyManagerFactory.init(keyStore)
val keyManagers = keyManagerFactory.keyManagers
val trustMgrFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
trustMgrFactory.init(trustStore)
val trustManagers = trustMgrFactory.trustManagers
context.init(keyManagers, trustManagers, newSecureRandom())
val serverSocketFactory = context.serverSocketFactory
val clientSocketFactory = context.socketFactory
val serverSocket = serverSocketFactory.createServerSocket(0) as SSLServerSocket // use 0 to get first free socket
val serverParams = SSLParameters(CIPHER_SUITES,
arrayOf("TLSv1.2"))
serverParams.wantClientAuth = true
serverParams.needClientAuth = true
serverParams.endpointIdentificationAlgorithm = null // Reconfirm default no server name indication, use our own validator.
serverSocket.sslParameters = serverParams
serverSocket.useClientMode = false
val clientSocket = clientSocketFactory.createSocket() as SSLSocket
val clientParams = SSLParameters(CIPHER_SUITES,
arrayOf("TLSv1.2"))
clientParams.endpointIdentificationAlgorithm = null // Reconfirm default no server name indication, use our own validator.
clientSocket.sslParameters = clientParams
clientSocket.useClientMode = true
// We need to specify this explicitly because by default the client binds to 'localhost' and we want it to bind
// to whatever <hostname> resolves to(as that's what the server binds to). In particular on Debian <hostname>
// resolves to 127.0.1.1 instead of the external address of the interface, so the ssl handshake fails.
clientSocket.bind(InetSocketAddress(InetAddress.getLocalHost(), 0))
val lock = Object()
var done = false
var serverError = false
val serverThread = thread {
try {
val sslServerSocket = serverSocket.accept()
assertTrue(sslServerSocket.isConnected)
val serverInput = DataInputStream(sslServerSocket.inputStream)
val receivedString = serverInput.readUTF()
assertEquals("Hello World", receivedString)
synchronized(lock) {
done = true
lock.notifyAll()
}
sslServerSocket.close()
} catch (ex: Exception) {
serverError = true
}
}
clientSocket.connect(InetSocketAddress(InetAddress.getLocalHost(), serverSocket.localPort))
assertTrue(clientSocket.isConnected)
// Double check hostname manually
val peerChain = clientSocket.session.peerCertificates.x509
val peerX500Principal = peerChain[0].subjectX500Principal
assertEquals(MEGA_CORP.name.x500Principal, peerX500Principal)
X509Utilities.validateCertificateChain(rootCa.certificate, peerChain)
val output = DataOutputStream(clientSocket.outputStream)
output.writeUTF("Hello World")
var timeout = 0
synchronized(lock) {
while (!done) {
timeout++
if (timeout > 10) throw IOException("Timed out waiting for server to complete")
lock.wait(1000)
}
}
clientSocket.close()
serverThread.join(1000)
assertFalse { serverError }
serverSocket.close()
assertTrue(done)
}
@Test(timeout=300_000)
fun `create server cert and use in OpenSSL channel`() {
val sslConfig = CertificateStoreStubs.P2P.withCertificatesDirectory(tempFolder.root.toPath(), keyStorePassword = "serverstorepass")
val (rootCa, intermediateCa) = createDevIntermediateCaCertPath()
// Generate server cert and private key and populate another keystore suitable for SSL
sslConfig.keyStore.get(true).registerDevP2pCertificates(MEGA_CORP.name, rootCa.certificate, intermediateCa)
sslConfig.createTrustStore(rootCa.certificate)
val keyStore = sslConfig.keyStore.get()
val trustStore = sslConfig.trustStore.get()
val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
keyManagerFactory.init(keyStore)
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
trustManagerFactory.init(trustStore)
val sslServerContext = SslContextBuilder
.forServer(keyManagerFactory)
.trustManager(trustManagerFactory)
.clientAuth(ClientAuth.REQUIRE)
.ciphers(CIPHER_SUITES.toMutableList())
.sslProvider(SslProvider.OPENSSL)
.protocols("TLSv1.2")
.build()
val sslClientContext = SslContextBuilder
.forClient()
.keyManager(keyManagerFactory)
.trustManager(trustManagerFactory)
.ciphers(CIPHER_SUITES.toMutableList())
.sslProvider(SslProvider.OPENSSL)
.protocols("TLSv1.2")
.build()
val serverHandler = NettyTestHandler { ctx, msg -> ctx?.writeAndFlush(msg) }
val clientHandler = NettyTestHandler { _, msg -> assertEquals("Hello", NettyTestHandler.readString(msg)) }
NettyTestServer(sslServerContext, serverHandler, portAllocation.nextPort()).use { server ->
server.start()
NettyTestClient(sslClientContext, InetAddress.getLocalHost().canonicalHostName, server.port, clientHandler).use { client ->
client.start()
clientHandler.writeString("Hello")
val readCalled = clientHandler.waitForReadCalled()
clientHandler.rethrowIfFailed()
serverHandler.rethrowIfFailed()
assertTrue(readCalled)
assertEquals(1, serverHandler.readCalledCounter)
assertEquals(1, clientHandler.readCalledCounter)
val peerChain = client.engine!!.session.peerCertificates.x509
val peerX500Principal = peerChain[0].subjectX500Principal
assertEquals(MEGA_CORP.name.x500Principal, peerX500Principal)
X509Utilities.validateCertificateChain(rootCa.certificate, peerChain)
}
}
}
private fun tempFile(name: String): Path = tempFolder.root.toPath() / name
private fun MutualSslConfiguration.createTrustStore(rootCert: X509Certificate) {
val trustStore = this.trustStore.get(true)
trustStore[X509Utilities.CORDA_ROOT_CA] = rootCert
}
@Test(timeout=300_000)
fun `get correct private key type from Keystore`() {
schemeToKeyTypes.forEach { getCorrectKeyFromKeystore(it.first, it.second, it.third) }
}
private fun <U, C> getCorrectKeyFromKeystore(signatureScheme: SignatureScheme, uncastedClass: Class<U>, castedClass: Class<C>) {
val keyPair = generateKeyPair(signatureScheme)
val (keyFromKeystore, keyFromKeystoreCasted) = storeAndGetKeysFromKeystore(keyPair)
assertThat(keyFromKeystore).isInstanceOf(uncastedClass)
assertThat(keyFromKeystoreCasted).isInstanceOf(castedClass)
}
private fun storeAndGetKeysFromKeystore(keyPair: KeyPair): Pair<Key, PrivateKey> {
val testName = X500Principal("CN=Test,O=R3 Ltd,L=London,C=GB")
val selfSignCert = X509Utilities.createSelfSignedCACertificate(testName, keyPair)
val keyStore = loadOrCreateKeyStore(tempFile("testKeystore.jks"), "keystorepassword")
keyStore.setKeyEntry("Key", keyPair.private, "keypassword".toCharArray(), arrayOf(selfSignCert))
val keyFromKeystore = keyStore.getKey("Key", "keypassword".toCharArray())
val keyFromKeystoreCasted = keyStore.getSupportedKey("Key", "keypassword")
return Pair(keyFromKeystore, keyFromKeystoreCasted)
}
@Test(timeout=300_000)
fun `serialize - deserialize X509Certificate`() {
Crypto.supportedSignatureSchemes().filter { it != COMPOSITE_KEY }.forEach { serializeDeserializeX509Cert(it) }
}
private fun serializeDeserializeX509Cert(signatureScheme: SignatureScheme) {
val factory = SerializationFactoryImpl().apply { registerScheme(AMQPServerSerializationScheme()) }
val context = SerializationContextImpl(amqpMagic,
javaClass.classLoader,
AllWhitelist,
emptyMap(),
true,
SerializationContext.UseCase.P2P,
null)
val expected = X509Utilities.createSelfSignedCACertificate(ALICE.name.x500Principal, generateKeyPair(signatureScheme))
val serialized = expected.serialize(factory, context).bytes
val actual = serialized.deserialize<X509Certificate>(factory, context)
assertEquals(expected, actual)
}
@Test(timeout=300_000)
fun `serialize - deserialize X509CertPath`() {
Crypto.supportedSignatureSchemes().filter { it != COMPOSITE_KEY }.forEach { serializeDeserializeX509CertPath(it) }
}
private fun serializeDeserializeX509CertPath(signatureScheme: SignatureScheme) {
val factory = SerializationFactoryImpl().apply { registerScheme(AMQPServerSerializationScheme()) }
val context = SerializationContextImpl(
amqpMagic,
javaClass.classLoader,
AllWhitelist,
emptyMap(),
true,
SerializationContext.UseCase.P2P,
null
)
val rootCAKey = generateKeyPair(signatureScheme)
val rootCACert = X509Utilities.createSelfSignedCACertificate(ALICE_NAME.x500Principal, rootCAKey)
val certificate = X509Utilities.createCertificate(CertificateType.TLS, rootCACert, rootCAKey, BOB_NAME.x500Principal, BOB.publicKey)
val expected = X509Utilities.buildCertPath(certificate, rootCACert)
val serialized = expected.serialize(factory, context).bytes
val actual: CertPath = serialized.deserialize(factory, context)
assertEquals(expected, actual)
}
@Test(timeout=300_000)
fun `signing a key type with another key type certificate then store and reload correctly from keystore`() {
certChainSchemeCombinations.forEach { signCertWithOtherKeyTypeAndTestKeystoreReload(it.first, it.second) }
}
private fun signCertWithOtherKeyTypeAndTestKeystoreReload(signatureSchemeRoot: SignatureScheme, signatureSchemeChild: SignatureScheme) {
val tmpKeyStore = tempFile("keystore.jks")
val (_, caCert, childKeyPair, childCert) = genCaAndChildKeysCertsAndSubjects(signatureSchemeRoot, signatureSchemeChild)
// Save the child private key with cert chains.
val keyStore = loadOrCreateKeyStore(tmpKeyStore, "keystorepass")
keyStore.setKeyEntry("Key", childKeyPair.private, "password".toCharArray(), arrayOf(caCert, childCert))
keyStore.save(tmpKeyStore, "keystorepass")
// Load the keystore from file and make sure keys are intact.
val reloadedKeystore = loadOrCreateKeyStore(tmpKeyStore, "keystorepass")
val reloadedPrivateKey = reloadedKeystore.getKey("Key", "password".toCharArray())
val reloadedCerts = reloadedKeystore.getCertificateChain("Key")
val reloadedPublicKey = reloadedCerts.last().publicKey
assertEquals(2, reloadedCerts.size)
assertNotNull(reloadedPublicKey)
assertNotNull(reloadedPrivateKey)
assertEquals(childKeyPair.public, reloadedPublicKey)
assertEquals(childKeyPair.private, reloadedPrivateKey)
}
@Test(timeout=300_000)
fun `check certificate validity or print warning if expiry is within 30 days`() {
val keyPair = generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME)
val testName = X500Principal("CN=Test,O=R3 Ltd,L=London,C=GB")
val cert = X509Utilities.createSelfSignedCACertificate(testName, keyPair, 0.days to 50.days)
val today = cert.notBefore
var warnings = 0
cert.checkValidity({ "No error expected" }, { fail("No warning expected") }, today)
cert.checkValidity({ "No error expected" }, { fail("No warning expected") }, Date.from(today.toInstant() + 20.days))
cert.checkValidity({ "No error expected" }, { daysToExpiry ->
assertEquals(30, daysToExpiry)
warnings++
}, Date.from(today.toInstant() + 20.days + 3.hours))
cert.checkValidity({ "No error expected" }, { daysToExpiry ->
assertEquals(11, daysToExpiry)
warnings++
}, Date.from(today.toInstant() + 40.days))
cert.checkValidity({ "No error expected" }, { daysToExpiry ->
assertEquals(1, daysToExpiry)
warnings++
}, Date.from(today.toInstant() + 49.days + 20.hours))
assertEquals(3, warnings)
assertFailsWith(IllegalArgumentException::class, "Error text") {
cert.checkValidity({ "Error text" }, { }, Date.from(today.toInstant() + 50.days + 1.hours))
}
assertFailsWith(IllegalArgumentException::class, "Error text") {
cert.checkValidity({ "Error text" }, { }, Date.from(today.toInstant() + 51.days))
}
}
}

View File

@ -0,0 +1,424 @@
package net.corda.nodeapitests.internal.network
import com.typesafe.config.ConfigFactory
import net.corda.core.crypto.secureRandomBytes
import net.corda.core.crypto.sha256
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.*
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NodeInfo
import net.corda.core.serialization.serialize
import net.corda.node.services.config.NotaryConfig
import net.corda.nodeapi.internal.DEV_ROOT_CA
import net.corda.core.internal.NODE_INFO_DIRECTORY
import net.corda.core.utilities.days
import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.config.parseAs
import net.corda.nodeapi.internal.config.toConfig
import net.corda.nodeapi.internal.network.NetworkBootstrapper.Companion.DEFAULT_MAX_MESSAGE_SIZE
import net.corda.nodeapi.internal.network.NetworkBootstrapper.Companion.DEFAULT_MAX_TRANSACTION_SIZE
import net.corda.nodeapi.internal.network.NodeInfoFilesCopier.Companion.NODE_INFO_FILE_NAME_PREFIX
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.TestIdentity
import net.corda.coretesting.internal.createNodeInfoAndSigned
import net.corda.nodeapi.internal.network.CopyCordapps
import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME
import net.corda.nodeapi.internal.network.NetworkBootstrapper
import net.corda.nodeapi.internal.network.NetworkParametersOverrides
import net.corda.nodeapi.internal.network.PackageOwner
import net.corda.nodeapi.internal.network.SignedNetworkParameters
import net.corda.nodeapi.internal.network.TestContractsJar
import net.corda.nodeapi.internal.network.verifiedNetworkParametersCert
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.After
import org.junit.AfterClass
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
import org.junit.rules.TemporaryFolder
import java.nio.file.Files
import java.nio.file.Path
import java.security.PublicKey
import java.time.Duration
import kotlin.streams.toList
class NetworkBootstrapperTest {
@Rule
@JvmField
val tempFolder = TemporaryFolder()
@Rule
@JvmField
val expectedEx: ExpectedException = ExpectedException.none()
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule()
companion object {
private val fakeEmbeddedCorda = fakeFileBytes()
private val fakeEmbeddedCordaJar = Files.createTempFile("corda", ".jar").write(fakeEmbeddedCorda)
private fun fakeFileBytes(writeToFile: Path? = null): ByteArray {
val bytes = secureRandomBytes(128)
writeToFile?.write(bytes)
return bytes
}
@JvmStatic
@AfterClass
fun cleanUp() {
Files.delete(fakeEmbeddedCordaJar)
}
}
private val contractsJars = hashMapOf<Path, TestContractsJar>()
private val bootstrapper = NetworkBootstrapper(
initSerEnv = false,
embeddedCordaJar = {
fakeEmbeddedCordaJar.toUri().toURL()
},
nodeInfosGenerator = { nodeDirs ->
nodeDirs.map { nodeDir ->
val name = nodeDir.fakeNodeConfig.myLegalName
val file = nodeDir / "$NODE_INFO_FILE_NAME_PREFIX${name.serialize().hash}"
if (!file.exists()) {
createNodeInfoAndSigned(name).signed.serialize().open().copyTo(file)
}
file
}
},
contractsJarConverter = { contractsJars[it]!! }
)
private val aliceConfig = FakeNodeConfig(ALICE_NAME)
private val bobConfig = FakeNodeConfig(BOB_NAME)
private val notaryConfig = FakeNodeConfig(DUMMY_NOTARY_NAME, NotaryConfig(validating = true))
private var providedCordaJar: ByteArray? = null
private val configFiles = HashMap<Path, String>()
@After
fun `check config files are preserved`() {
configFiles.forEach { file, text ->
assertThat(file).hasContent(text)
}
}
@After
fun `check provided corda jar is preserved`() {
if (providedCordaJar == null) {
// Make sure we clean up if we used the embedded jar
assertThat(rootDir / "corda.jar").doesNotExist()
} else {
// Make sure we don't delete it if it was provided by the user
assertThat(rootDir / "corda.jar").hasBinaryContent(providedCordaJar)
}
}
@Test(timeout=300_000)
fun `empty dir`() {
assertThatThrownBy {
bootstrap()
}.hasMessage("No nodes found")
}
@Test(timeout=300_000)
fun `single node conf file`() {
createNodeConfFile("node1", bobConfig)
bootstrap()
val networkParameters = assertBootstrappedNetwork(fakeEmbeddedCorda, "node1" to bobConfig)
networkParameters.run {
assertThat(epoch).isEqualTo(1)
assertThat(notaries).isEmpty()
assertThat(whitelistedContractImplementations).isEmpty()
}
}
@Test(timeout=300_000)
fun `node conf file and corda jar`() {
createNodeConfFile("node1", bobConfig)
val fakeCordaJar = fakeFileBytes(rootDir / "corda.jar")
bootstrap()
assertBootstrappedNetwork(fakeCordaJar, "node1" to bobConfig)
}
@Test(timeout=300_000)
fun `single node directory with just node conf file`() {
createNodeDir("bob", bobConfig)
bootstrap()
assertBootstrappedNetwork(fakeEmbeddedCorda, "bob" to bobConfig)
}
@Test(timeout=300_000)
fun `single node directory with node conf file and corda jar`() {
val nodeDir = createNodeDir("bob", bobConfig)
val fakeCordaJar = fakeFileBytes(nodeDir / "corda.jar")
bootstrap()
assertBootstrappedNetwork(fakeCordaJar, "bob" to bobConfig)
}
@Test(timeout=300_000)
fun `single node directory with just corda jar`() {
val nodeCordaJar = (rootDir / "alice").createDirectories() / "corda.jar"
val fakeCordaJar = fakeFileBytes(nodeCordaJar)
assertThatThrownBy {
bootstrap()
}.hasMessageStartingWith("Missing node.conf in node directory alice")
assertThat(nodeCordaJar).hasBinaryContent(fakeCordaJar) // Make sure the corda.jar is left untouched
}
@Test(timeout=300_000)
fun `two node conf files, one of which is a notary`() {
createNodeConfFile("alice", aliceConfig)
createNodeConfFile("notary", notaryConfig)
bootstrap()
val networkParameters = assertBootstrappedNetwork(fakeEmbeddedCorda, "alice" to aliceConfig, "notary" to notaryConfig)
networkParameters.assertContainsNotary("notary")
}
@Test(timeout=300_000)
fun `two node conf files with the same legal name`() {
createNodeConfFile("node1", aliceConfig)
createNodeConfFile("node2", aliceConfig)
assertThatThrownBy {
bootstrap()
}.hasMessageContaining("Nodes must have unique legal names")
}
@Test(timeout=300_000)
fun `one node directory and one node conf file`() {
createNodeConfFile("alice", aliceConfig)
createNodeDir("bob", bobConfig)
bootstrap()
assertBootstrappedNetwork(fakeEmbeddedCorda, "alice" to aliceConfig, "bob" to bobConfig)
}
@Test(timeout=300_000)
fun `node conf file and CorDapp jar`() {
createNodeConfFile("alice", aliceConfig)
val cordappBytes = createFakeCordappJar("sample-app", listOf("contract.class"))
bootstrap()
val networkParameters = assertBootstrappedNetwork(fakeEmbeddedCorda, "alice" to aliceConfig)
assertThat(rootDir / "alice" / "cordapps" / "sample-app.jar").hasBinaryContent(cordappBytes)
assertThat(networkParameters.whitelistedContractImplementations).isEqualTo(mapOf(
"contract.class" to listOf(cordappBytes.sha256())
))
}
@Test(timeout=300_000)
fun `no copy CorDapps`() {
createNodeConfFile("alice", aliceConfig)
val cordappBytes = createFakeCordappJar("sample-app", listOf("contract.class"))
bootstrap(copyCordapps = CopyCordapps.No)
val networkParameters = assertBootstrappedNetwork(fakeEmbeddedCorda, "alice" to aliceConfig)
assertThat(rootDir / "alice" / "cordapps" / "sample-app.jar").doesNotExist()
assertThat(networkParameters.whitelistedContractImplementations).isEqualTo(mapOf(
"contract.class" to listOf(cordappBytes.sha256())
))
}
@Test(timeout=300_000)
fun `add node to existing network`() {
createNodeConfFile("alice", aliceConfig)
bootstrap()
val networkParameters1 = (rootDir / "alice").networkParameters
createNodeConfFile("bob", bobConfig)
bootstrap()
val networkParameters2 = assertBootstrappedNetwork(fakeEmbeddedCorda, "alice" to aliceConfig, "bob" to bobConfig)
assertThat(networkParameters1).isEqualTo(networkParameters2)
}
@Test(timeout=300_000)
fun `add notary to existing network`() {
createNodeConfFile("alice", aliceConfig)
bootstrap()
createNodeConfFile("notary", notaryConfig)
bootstrap()
val networkParameters = assertBootstrappedNetwork(fakeEmbeddedCorda, "alice" to aliceConfig, "notary" to notaryConfig)
networkParameters.assertContainsNotary("notary")
assertThat(networkParameters.epoch).isEqualTo(2)
}
@Test(timeout=300_000)
fun `network parameters overrides`() {
createNodeConfFile("alice", aliceConfig)
val minimumPlatformVersion = 2
val maxMessageSize = 10000
val maxTransactionSize = 20000
val eventHorizon = 7.days
bootstrap(minimumPlatformVerison = minimumPlatformVersion,
maxMessageSize = maxMessageSize,
maxTransactionSize = maxTransactionSize,
eventHorizon = eventHorizon)
val networkParameters = assertBootstrappedNetwork(fakeEmbeddedCorda, "alice" to aliceConfig)
assertThat(networkParameters.minimumPlatformVersion).isEqualTo(minimumPlatformVersion)
assertThat(networkParameters.maxMessageSize).isEqualTo(maxMessageSize)
assertThat(networkParameters.maxTransactionSize).isEqualTo(maxTransactionSize)
assertThat(networkParameters.eventHorizon).isEqualTo(eventHorizon)
}
private val ALICE = TestIdentity(ALICE_NAME, 70)
private val BOB = TestIdentity(BOB_NAME, 80)
private val alicePackageName = "com.example.alice"
private val bobPackageName = "com.example.bob"
@Test(timeout=300_000)
fun `register new package namespace in existing network`() {
createNodeConfFile("alice", aliceConfig)
bootstrap(packageOwnership = mapOf(Pair(alicePackageName, ALICE.publicKey)))
assertContainsPackageOwner("alice", mapOf(Pair(alicePackageName, ALICE.publicKey)))
}
@Test(timeout=300_000)
fun `register additional package namespace in existing network`() {
createNodeConfFile("alice", aliceConfig)
bootstrap(packageOwnership = mapOf(Pair(alicePackageName, ALICE.publicKey)))
assertContainsPackageOwner("alice", mapOf(Pair(alicePackageName, ALICE.publicKey)))
// register additional package name
createNodeConfFile("bob", bobConfig)
bootstrap(packageOwnership = mapOf(Pair(alicePackageName, ALICE.publicKey), Pair(bobPackageName, BOB.publicKey)))
assertContainsPackageOwner("bob", mapOf(Pair(alicePackageName, ALICE.publicKey), Pair(bobPackageName, BOB.publicKey)))
}
@Test(timeout=300_000)
fun `attempt to register overlapping namespaces in existing network`() {
createNodeConfFile("alice", aliceConfig)
val greedyNamespace = "com.example"
bootstrap(packageOwnership = mapOf(Pair(greedyNamespace, ALICE.publicKey)))
assertContainsPackageOwner("alice", mapOf(Pair(greedyNamespace, ALICE.publicKey)))
// register overlapping package name
createNodeConfFile("bob", bobConfig)
expectedEx.expect(IllegalArgumentException::class.java)
expectedEx.expectMessage("Multiple packages added to the packageOwnership overlap.")
bootstrap(packageOwnership = mapOf(Pair(greedyNamespace, ALICE.publicKey), Pair(bobPackageName, BOB.publicKey)))
}
@Test(timeout=300_000)
fun `unregister single package namespace in network of one`() {
createNodeConfFile("alice", aliceConfig)
bootstrap(packageOwnership = mapOf(Pair(alicePackageName, ALICE.publicKey)))
assertContainsPackageOwner("alice", mapOf(Pair(alicePackageName, ALICE.publicKey)))
// unregister package name
bootstrap(packageOwnership = emptyMap())
assertContainsPackageOwner("alice", emptyMap())
}
@Test(timeout=300_000)
fun `unregister single package namespace in network of many`() {
createNodeConfFile("alice", aliceConfig)
bootstrap(packageOwnership = mapOf(Pair(alicePackageName, ALICE.publicKey), Pair(bobPackageName, BOB.publicKey)))
// unregister package name
bootstrap(packageOwnership = mapOf(Pair(alicePackageName, ALICE.publicKey)))
assertContainsPackageOwner("alice", mapOf(Pair(alicePackageName, ALICE.publicKey)))
}
@Test(timeout=300_000)
fun `unregister all package namespaces in existing network`() {
createNodeConfFile("alice", aliceConfig)
bootstrap(packageOwnership = mapOf(Pair(alicePackageName, ALICE.publicKey), Pair(bobPackageName, BOB.publicKey)))
// unregister all package names
bootstrap(packageOwnership = emptyMap())
assertContainsPackageOwner("alice", emptyMap())
}
private val rootDir get() = tempFolder.root.toPath()
private fun bootstrap(copyCordapps: CopyCordapps = CopyCordapps.FirstRunOnly,
packageOwnership: Map<String, PublicKey>? = emptyMap(),
minimumPlatformVerison: Int? = PLATFORM_VERSION,
maxMessageSize: Int? = DEFAULT_MAX_MESSAGE_SIZE,
maxTransactionSize: Int? = DEFAULT_MAX_TRANSACTION_SIZE,
eventHorizon: Duration? = 30.days) {
providedCordaJar = (rootDir / "corda.jar").let { if (it.exists()) it.readAll() else null }
bootstrapper.bootstrap(rootDir, copyCordapps, NetworkParametersOverrides(
minimumPlatformVersion = minimumPlatformVerison,
maxMessageSize = maxMessageSize,
maxTransactionSize = maxTransactionSize,
eventHorizon = eventHorizon,
packageOwnership = packageOwnership?.map { PackageOwner(it.key, it.value) }
))
}
private fun createNodeConfFile(nodeDirName: String, config: FakeNodeConfig) {
writeNodeConfFile(rootDir / "${nodeDirName}_node.conf", config)
}
private fun createNodeDir(nodeDirName: String, config: FakeNodeConfig): Path {
val nodeDir = (rootDir / nodeDirName).createDirectories()
writeNodeConfFile(nodeDir / "node.conf", config)
return nodeDir
}
private fun writeNodeConfFile(file: Path, config: FakeNodeConfig) {
val configText = config.toConfig().root().render()
file.writeText(configText)
configFiles[file] = configText
}
private fun createFakeCordappJar(cordappName: String, contractClassNames: List<String>): ByteArray {
val cordappJarFile = rootDir / "$cordappName.jar"
val cordappBytes = fakeFileBytes(cordappJarFile)
contractsJars[cordappJarFile] = TestContractsJar(cordappBytes.sha256(), contractClassNames)
return cordappBytes
}
private val Path.networkParameters: NetworkParameters
get() {
return (this / NETWORK_PARAMS_FILE_NAME).readObject<SignedNetworkParameters>()
.verifiedNetworkParametersCert(DEV_ROOT_CA.certificate)
}
private val Path.nodeInfoFile: Path
get() {
return list { it.filter { it.fileName.toString().startsWith(NODE_INFO_FILE_NAME_PREFIX) }.toList() }.single()
}
private val Path.nodeInfo: NodeInfo get() = nodeInfoFile.readObject<SignedNodeInfo>().verified()
private val Path.fakeNodeConfig: FakeNodeConfig
get() {
return ConfigFactory.parseFile((this / "node.conf").toFile()).parseAs(FakeNodeConfig::class)
}
private fun assertBootstrappedNetwork(cordaJar: ByteArray, vararg nodes: Pair<String, FakeNodeConfig>): NetworkParameters {
val networkParameters = (rootDir / nodes[0].first).networkParameters
val allNodeInfoFiles = nodes.map { (rootDir / it.first).nodeInfoFile }.associateBy({ it }, Path::readAll)
for ((nodeDirName, config) in nodes) {
val nodeDir = rootDir / nodeDirName
assertThat(nodeDir / "corda.jar").hasBinaryContent(cordaJar)
assertThat(nodeDir.fakeNodeConfig).isEqualTo(config)
assertThat(nodeDir.networkParameters).isEqualTo(networkParameters)
// Make sure all the nodes have all of each others' node-info files
allNodeInfoFiles.forEach { nodeInfoFile, bytes ->
assertThat(nodeDir / NODE_INFO_DIRECTORY / nodeInfoFile.fileName.toString()).hasBinaryContent(bytes)
}
}
return networkParameters
}
private fun NetworkParameters.assertContainsNotary(dirName: String) {
val notaryParty = (rootDir / dirName).nodeInfo.legalIdentities.single()
assertThat(notaries).hasSize(1)
notaries[0].run {
assertThat(validating).isTrue()
assertThat(identity.name).isEqualTo(notaryParty.name)
assertThat(identity.owningKey).isEqualTo(notaryParty.owningKey)
}
}
private fun assertContainsPackageOwner(nodeDirName: String, packageOwners: Map<String, PublicKey>) {
val networkParams = (rootDir / nodeDirName).networkParameters
assertThat(networkParams.packageOwnership).isEqualTo(packageOwners)
}
data class FakeNodeConfig(val myLegalName: CordaX500Name, val notary: NotaryConfig? = null)
}

View File

@ -0,0 +1,74 @@
package net.corda.nodeapitests.internal.persistence
import net.corda.core.schemas.MappedSchema
import net.corda.core.schemas.PersistentState
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.nodeapi.internal.persistence.MissingMigrationException
import net.corda.nodeapi.internal.persistence.SchemaMigration
import net.corda.node.internal.DataSourceFactory
import net.corda.node.services.persistence.DBCheckpointStorage
import net.corda.node.services.schema.NodeSchemaService
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.TestIdentity
import net.corda.testing.node.MockServices
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Before
import org.junit.Test
import org.junit.jupiter.api.assertDoesNotThrow
import java.util.*
import javax.persistence.Column
import javax.persistence.Entity
import javax.sql.DataSource
class MissingSchemaMigrationTest {
object TestSchemaFamily
object GoodSchema : MappedSchema(schemaFamily = TestSchemaFamily.javaClass, version = 1, mappedTypes = listOf(State::class.java)) {
@Entity
class State(
@Column
var id: String
) : PersistentState()
}
lateinit var hikariProperties: Properties
lateinit var dataSource: DataSource
@Before
fun setUp() {
hikariProperties = MockServices.makeTestDataSourceProperties()
dataSource = DataSourceFactory.createDataSource(hikariProperties)
}
private fun createSchemaMigration(schemasToMigrate: Set<MappedSchema>, forceThrowOnMissingMigration: Boolean): SchemaMigration {
val databaseConfig = DatabaseConfig()
return SchemaMigration(schemasToMigrate, dataSource, databaseConfig, null, null,
TestIdentity(ALICE_NAME, 70).name, forceThrowOnMissingMigration)
}
@Test(timeout=300_000)
fun `test that an error is thrown when forceThrowOnMissingMigration is set and a mapped schema is missing a migration`() {
assertThatThrownBy {
createSchemaMigration(setOf(GoodSchema), true)
.nodeStartup(dataSource.connection.use { DBCheckpointStorage().getCheckpointCount(it) != 0L })
}.isInstanceOf(MissingMigrationException::class.java)
}
@Test(timeout=300_000)
fun `test that an error is not thrown when forceThrowOnMissingMigration is not set and a mapped schema is missing a migration`() {
assertDoesNotThrow {
createSchemaMigration(setOf(GoodSchema), false)
.nodeStartup(dataSource.connection.use { DBCheckpointStorage().getCheckpointCount(it) != 0L })
}
}
@Test(timeout=300_000)
fun `test that there are no missing migrations for the node`() {
assertDoesNotThrow("This test failure indicates " +
"a new table has been added to the node without the appropriate migration scripts being present") {
createSchemaMigration(NodeSchemaService().internalSchemas(), false)
.nodeStartup(dataSource.connection.use { DBCheckpointStorage().getCheckpointCount(it) != 0L })
}
}
}

View File

@ -0,0 +1,65 @@
package net.corda.nodeapitests.internal.serialization.kryo
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.crypto.SecureHash
import net.corda.core.serialization.EncodingWhitelist
import net.corda.core.serialization.internal.CheckpointSerializationContext
import net.corda.core.serialization.internal.checkpointDeserialize
import net.corda.core.serialization.internal.checkpointSerialize
import net.corda.coretesting.internal.rigorousMock
import net.corda.node.services.persistence.NodeAttachmentService
import net.corda.serialization.internal.AllWhitelist
import net.corda.serialization.internal.CheckpointSerializationContextImpl
import net.corda.serialization.internal.CordaSerializationEncoding
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.TestIdentity
import net.corda.testing.core.internal.CheckpointSerializationEnvironmentRule
import org.junit.Assert
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import java.io.InputStream
@RunWith(Parameterized::class)
class KryoAttachmentTest(private val compression: CordaSerializationEncoding?) {
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun compression() = arrayOf<CordaSerializationEncoding?>(null) + CordaSerializationEncoding.values()
}
@get:Rule
val serializationRule = CheckpointSerializationEnvironmentRule()
private lateinit var context: CheckpointSerializationContext
@Before
fun setup() {
context = CheckpointSerializationContextImpl(
javaClass.classLoader,
AllWhitelist,
emptyMap(),
true,
compression,
rigorousMock<EncodingWhitelist>().also {
if (compression != null) doReturn(true).whenever(it).acceptEncoding(compression)
})
}
@Test(timeout=300_000)
fun `HashCheckingStream (de)serialize`() {
val rubbish = ByteArray(12345) { (it * it * 0.12345).toByte() }
val readRubbishStream: InputStream = NodeAttachmentService.HashCheckingStream(
SecureHash.sha256(rubbish),
rubbish.size,
rubbish.inputStream()
).checkpointSerialize(context).checkpointDeserialize(context)
for (i in 0..12344) {
Assert.assertEquals(rubbish[i], readRubbishStream.read().toByte())
}
Assert.assertEquals(-1, readRubbishStream.read())
}
}