mirror of
https://github.com/corda/corda.git
synced 2025-06-13 04:38:19 +00:00
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:
19
node-api-tests/build.gradle
Normal file
19
node-api-tests/build.gradle
Normal 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')
|
||||
}
|
@ -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()))
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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 })
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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())
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user