Merge branch 'master' into shams-master-merge-081217

# Conflicts:
#	node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/KeyStoreWrapper.kt
#	node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt
#	node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt
#	node/src/test/kotlin/net/corda/node/services/identity/InMemoryIdentityServiceTests.kt
#	node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt
#	samples/irs-demo/src/integration-test/kotlin/net/corda/test/spring/SpringDriver.kt
#	testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt
#	testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt
#	testing/node-driver/src/main/kotlin/net/corda/testing/driver/DriverDSL.kt
#	testing/node-driver/src/main/kotlin/net/corda/testing/internal/DriverDSLImpl.kt
#	testing/node-driver/src/main/kotlin/net/corda/testing/internal/NodeBasedTest.kt
#	testing/node-driver/src/main/kotlin/net/corda/testing/internal/RPCDriver.kt
#	testing/node-driver/src/main/kotlin/net/corda/testing/internal/demorun/DemoRunner.kt
#	testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt
#	verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt
This commit is contained in:
Shams Asari
2017-12-08 16:03:16 +00:00
160 changed files with 2044 additions and 1285 deletions

View File

@ -1873,7 +1873,7 @@ public final class net.corda.core.node.services.TimeWindowChecker extends java.l
@org.jetbrains.annotations.NotNull public final java.time.Clock getClock() @org.jetbrains.annotations.NotNull public final java.time.Clock getClock()
public final boolean isValid(net.corda.core.contracts.TimeWindow) public final boolean isValid(net.corda.core.contracts.TimeWindow)
## ##
@net.corda.core.DoNotImplement public interface net.corda.core.node.services.TransactionStorage @net.corda.core.DoNotImplement public interface net.corda.core.node.services.TransactionStorage extends net.corda.core.node.StateLoader
@org.jetbrains.annotations.Nullable public abstract net.corda.core.transactions.SignedTransaction getTransaction(net.corda.core.crypto.SecureHash) @org.jetbrains.annotations.Nullable public abstract net.corda.core.transactions.SignedTransaction getTransaction(net.corda.core.crypto.SecureHash)
@org.jetbrains.annotations.NotNull public abstract rx.Observable getUpdates() @org.jetbrains.annotations.NotNull public abstract rx.Observable getUpdates()
@org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.DataFeed track() @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.DataFeed track()

View File

@ -47,8 +47,9 @@ buildscript {
ext.dependency_checker_version = '3.0.1' ext.dependency_checker_version = '3.0.1'
ext.commons_collections_version = '4.1' ext.commons_collections_version = '4.1'
ext.beanutils_version = '1.9.3' ext.beanutils_version = '1.9.3'
ext.crash_version = 'faba68332800f21278c5b600bf14ad55cef5989e' ext.crash_version = 'cce5a00f114343c1145c1d7756e1dd6df3ea984e'
ext.jsr305_version = constants.getProperty("jsr305Version") ext.jsr305_version = constants.getProperty("jsr305Version")
ext.artifactory_plugin_version = constants.getProperty('artifactoryPluginVersion')
// Update 121 is required for ObjectInputFilter and at time of writing 131 was latest: // Update 121 is required for ObjectInputFilter and at time of writing 131 was latest:
ext.java8_minUpdateVersion = '131' ext.java8_minUpdateVersion = '131'
@ -72,6 +73,7 @@ buildscript {
classpath "org.ajoberstar:grgit:1.1.0" classpath "org.ajoberstar:grgit:1.1.0"
classpath "net.i2p.crypto:eddsa:$eddsa_version" // Needed for ServiceIdentityGenerator in the build environment. classpath "net.i2p.crypto:eddsa:$eddsa_version" // Needed for ServiceIdentityGenerator in the build environment.
classpath "org.owasp:dependency-check-gradle:${dependency_checker_version}" classpath "org.owasp:dependency-check-gradle:${dependency_checker_version}"
classpath "org.jfrog.buildinfo:build-info-extractor-gradle:$artifactory_plugin_version"
} }
} }
@ -80,7 +82,6 @@ plugins {
// but the DSL has some restrictions e.g can't be used on the allprojects section. So we should revisit this if there are improvements in Gradle. // but the DSL has some restrictions e.g can't be used on the allprojects section. So we should revisit this if there are improvements in Gradle.
// Version 1.0.2 of this plugin uses capsule:1.0.1 // Version 1.0.2 of this plugin uses capsule:1.0.1
id "us.kirchmeier.capsule" version "1.0.2" id "us.kirchmeier.capsule" version "1.0.2"
id "com.jfrog.artifactory" version "4.4.18"
} }
ext { ext {
@ -92,6 +93,7 @@ apply plugin: 'com.github.ben-manes.versions'
apply plugin: 'net.corda.plugins.publish-utils' apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'net.corda.plugins.cordformation' apply plugin: 'net.corda.plugins.cordformation'
apply plugin: 'maven-publish' apply plugin: 'maven-publish'
apply plugin: 'com.jfrog.artifactory'
// We need the following three lines even though they're inside an allprojects {} block below because otherwise // We need the following three lines even though they're inside an allprojects {} block below because otherwise
// IntelliJ gets confused when importing the project and ends up erasing and recreating the .idea directory, along // IntelliJ gets confused when importing the project and ends up erasing and recreating the .idea directory, along

View File

@ -13,7 +13,7 @@ import net.corda.core.utilities.*
import net.corda.node.services.messaging.RPCServerConfiguration import net.corda.node.services.messaging.RPCServerConfiguration
import net.corda.nodeapi.RPCApi import net.corda.nodeapi.RPCApi
import net.corda.testing.SerializationEnvironmentRule import net.corda.testing.SerializationEnvironmentRule
import net.corda.testing.driver.poll import net.corda.testing.internal.poll
import net.corda.testing.internal.* import net.corda.testing.internal.*
import org.apache.activemq.artemis.api.core.SimpleString import org.apache.activemq.artemis.api.core.SimpleString
import org.junit.After import org.junit.After
@ -234,6 +234,7 @@ class RPCStabilityTests {
override val protocolVersion = 0 override val protocolVersion = 0
override fun ping() = "pong" override fun ping() = "pong"
} }
val serverFollower = shutdownManager.follower() val serverFollower = shutdownManager.follower()
val serverPort = startRpcServer<ReconnectOps>(ops = ops).getOrThrow().broker.hostAndPort!! val serverPort = startRpcServer<ReconnectOps>(ops = ops).getOrThrow().broker.hostAndPort!!
serverFollower.unfollow() serverFollower.unfollow()
@ -351,7 +352,7 @@ class RPCStabilityTests {
} }
fun RPCDriverExposedDSLInterface.pollUntilClientNumber(server: RpcServerHandle, expected: Int) { fun RPCDriverDSL.pollUntilClientNumber(server: RpcServerHandle, expected: Int) {
pollUntilTrue("number of RPC clients to become $expected") { pollUntilTrue("number of RPC clients to become $expected") {
val clientAddresses = server.broker.serverControl.addressNames.filter { it.startsWith(RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX) } val clientAddresses = server.broker.serverControl.addressNames.filter { it.startsWith(RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX) }
clientAddresses.size == expected clientAddresses.size == expected

View File

@ -7,7 +7,7 @@ import net.corda.core.messaging.RPCOps
import net.corda.node.services.messaging.RPCServerConfiguration import net.corda.node.services.messaging.RPCServerConfiguration
import net.corda.nodeapi.internal.config.User import net.corda.nodeapi.internal.config.User
import net.corda.testing.SerializationEnvironmentRule import net.corda.testing.SerializationEnvironmentRule
import net.corda.testing.internal.RPCDriverExposedDSLInterface import net.corda.testing.internal.RPCDriverDSL
import net.corda.testing.internal.rpcTestUser import net.corda.testing.internal.rpcTestUser
import net.corda.testing.internal.startInVmRpcClient import net.corda.testing.internal.startInVmRpcClient
import net.corda.testing.internal.startRpcClient import net.corda.testing.internal.startRpcClient
@ -41,7 +41,7 @@ open class AbstractRPCTest {
val createSession: () -> ClientSession val createSession: () -> ClientSession
) )
inline fun <reified I : RPCOps> RPCDriverExposedDSLInterface.testProxy( inline fun <reified I : RPCOps> RPCDriverDSL.testProxy(
ops: I, ops: I,
rpcUser: User = rpcTestUser, rpcUser: User = rpcTestUser,
clientConfiguration: RPCClientConfiguration = RPCClientConfiguration.default, clientConfiguration: RPCClientConfiguration = RPCClientConfiguration.default,
@ -55,9 +55,9 @@ open class AbstractRPCTest {
} }
} }
RPCTestMode.Netty -> RPCTestMode.Netty ->
startRpcServer(ops = ops, rpcUser = rpcUser, configuration = serverConfiguration).flatMap { server -> startRpcServer(ops = ops, rpcUser = rpcUser, configuration = serverConfiguration).flatMap { (broker) ->
startRpcClient<I>(server.broker.hostAndPort!!, rpcUser.username, rpcUser.password, clientConfiguration).map { startRpcClient<I>(broker.hostAndPort!!, rpcUser.username, rpcUser.password, clientConfiguration).map {
TestProxy(it, { startArtemisSession(server.broker.hostAndPort!!, rpcUser.username, rpcUser.password) }) TestProxy(it, { startArtemisSession(broker.hostAndPort!!, rpcUser.username, rpcUser.password) })
} }
} }
}.get() }.get()

View File

@ -7,7 +7,7 @@ import net.corda.core.internal.concurrent.thenMatch
import net.corda.core.messaging.RPCOps import net.corda.core.messaging.RPCOps
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.node.services.messaging.rpcContext import net.corda.node.services.messaging.rpcContext
import net.corda.testing.internal.RPCDriverExposedDSLInterface import net.corda.testing.internal.RPCDriverDSL
import net.corda.testing.internal.rpcDriver import net.corda.testing.internal.rpcDriver
import net.corda.testing.internal.rpcTestUser import net.corda.testing.internal.rpcTestUser
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
@ -26,7 +26,7 @@ import kotlin.test.assertTrue
class ClientRPCInfrastructureTests : AbstractRPCTest() { class ClientRPCInfrastructureTests : AbstractRPCTest() {
// TODO: Test that timeouts work // TODO: Test that timeouts work
private fun RPCDriverExposedDSLInterface.testProxy(): TestOps { private fun RPCDriverDSL.testProxy(): TestOps {
return testProxy<TestOps>(TestOpsImpl()).ops return testProxy<TestOps>(TestOpsImpl()).ops
} }

View File

@ -1,15 +1,15 @@
package net.corda.client.rpc package net.corda.client.rpc
import net.corda.client.rpc.internal.RPCClientConfiguration import net.corda.client.rpc.internal.RPCClientConfiguration
import net.corda.core.messaging.RPCOps
import net.corda.core.utilities.millis
import net.corda.core.crypto.random63BitValue import net.corda.core.crypto.random63BitValue
import net.corda.core.internal.concurrent.fork import net.corda.core.internal.concurrent.fork
import net.corda.core.internal.concurrent.transpose import net.corda.core.internal.concurrent.transpose
import net.corda.core.messaging.RPCOps
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.millis
import net.corda.node.services.messaging.RPCServerConfiguration import net.corda.node.services.messaging.RPCServerConfiguration
import net.corda.testing.internal.RPCDriverExposedDSLInterface import net.corda.testing.internal.RPCDriverDSL
import net.corda.testing.internal.rpcDriver import net.corda.testing.internal.rpcDriver
import net.corda.testing.internal.testThreadFactory import net.corda.testing.internal.testThreadFactory
import org.apache.activemq.artemis.utils.collections.ConcurrentHashSet import org.apache.activemq.artemis.utils.collections.ConcurrentHashSet
@ -20,7 +20,10 @@ import org.junit.runners.Parameterized
import rx.Observable import rx.Observable
import rx.subjects.UnicastSubject import rx.subjects.UnicastSubject
import java.util.* import java.util.*
import java.util.concurrent.* import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CountDownLatch
import java.util.concurrent.Executor
import java.util.concurrent.Executors
@RunWith(Parameterized::class) @RunWith(Parameterized::class)
class RPCConcurrencyTests : AbstractRPCTest() { class RPCConcurrencyTests : AbstractRPCTest() {
@ -84,7 +87,7 @@ class RPCConcurrencyTests : AbstractRPCTest() {
} }
} }
private fun RPCDriverExposedDSLInterface.testProxy(): TestProxy<TestOps> { private fun RPCDriverDSL.testProxy(): TestProxy<TestOps> {
return testProxy<TestOps>( return testProxy<TestOps>(
TestOpsImpl(pool), TestOpsImpl(pool),
clientConfiguration = RPCClientConfiguration.default.copy( clientConfiguration = RPCClientConfiguration.default.copy(

View File

@ -5,14 +5,14 @@ import net.corda.client.rpc.internal.RPCClientConfiguration
import net.corda.core.messaging.RPCOps import net.corda.core.messaging.RPCOps
import net.corda.core.utilities.minutes import net.corda.core.utilities.minutes
import net.corda.core.utilities.seconds import net.corda.core.utilities.seconds
import net.corda.testing.internal.performance.div
import net.corda.node.services.messaging.RPCServerConfiguration import net.corda.node.services.messaging.RPCServerConfiguration
import net.corda.testing.internal.RPCDriverExposedDSLInterface import net.corda.testing.internal.RPCDriverDSL
import net.corda.testing.measure import net.corda.testing.internal.performance.div
import net.corda.testing.internal.performance.startPublishingFixedRateInjector import net.corda.testing.internal.performance.startPublishingFixedRateInjector
import net.corda.testing.internal.performance.startReporter import net.corda.testing.internal.performance.startReporter
import net.corda.testing.internal.performance.startTightLoopInjector import net.corda.testing.internal.performance.startTightLoopInjector
import net.corda.testing.internal.rpcDriver import net.corda.testing.internal.rpcDriver
import net.corda.testing.measure
import org.junit.Ignore import org.junit.Ignore
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@ -42,7 +42,7 @@ class RPCPerformanceTests : AbstractRPCTest() {
} }
} }
private fun RPCDriverExposedDSLInterface.testProxy( private fun RPCDriverDSL.testProxy(
clientConfiguration: RPCClientConfiguration, clientConfiguration: RPCClientConfiguration,
serverConfiguration: RPCServerConfiguration serverConfiguration: RPCServerConfiguration
): TestProxy<TestOps> { ): TestProxy<TestOps> {

View File

@ -5,7 +5,7 @@ import net.corda.core.messaging.RPCOps
import net.corda.node.services.Permissions.Companion.invokeRpc import net.corda.node.services.Permissions.Companion.invokeRpc
import net.corda.node.services.messaging.rpcContext import net.corda.node.services.messaging.rpcContext
import net.corda.nodeapi.internal.config.User import net.corda.nodeapi.internal.config.User
import net.corda.testing.internal.RPCDriverExposedDSLInterface import net.corda.testing.internal.RPCDriverDSL
import net.corda.testing.internal.rpcDriver import net.corda.testing.internal.rpcDriver
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@ -37,7 +37,7 @@ class RPCPermissionsTests : AbstractRPCTest() {
/** /**
* Create an RPC proxy for the given user. * Create an RPC proxy for the given user.
*/ */
private fun RPCDriverExposedDSLInterface.testProxyFor(rpcUser: User) = testProxy<TestOps>(TestOpsImpl(), rpcUser).ops private fun RPCDriverDSL.testProxyFor(rpcUser: User) = testProxy<TestOps>(TestOpsImpl(), rpcUser).ops
private fun userOf(name: String, permissions: Set<String>) = User(name, "password", permissions) private fun userOf(name: String, permissions: Set<String>) = User(name, "password", permissions)

View File

@ -5,3 +5,4 @@ guavaVersion=21.0
bouncycastleVersion=1.57 bouncycastleVersion=1.57
typesafeConfigVersion=1.3.1 typesafeConfigVersion=1.3.1
jsr305Version=3.0.2 jsr305Version=3.0.2
artifactoryPluginVersion=4.4.18

View File

@ -2,9 +2,9 @@
package net.corda.core.internal package net.corda.core.internal
import net.corda.core.cordapp.CordappProvider
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256 import net.corda.core.crypto.sha256
import net.corda.core.node.ServiceHub
import net.corda.core.node.ServicesForResolution import net.corda.core.node.ServicesForResolution
import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
@ -295,15 +295,15 @@ fun <K, V> Iterable<Pair<K, V>>.toMultiMap(): Map<K, List<V>> = this.groupBy({ i
* Provide access to internal method for AttachmentClassLoaderTests * Provide access to internal method for AttachmentClassLoaderTests
* @suppress * @suppress
*/ */
fun TransactionBuilder.toWireTransaction(services: ServicesForResolution, serializationContext: SerializationContext): WireTransaction { fun TransactionBuilder.toWireTransaction(cordappProvider: CordappProvider, serializationContext: SerializationContext): WireTransaction {
return toWireTransactionWithContext(services, serializationContext) return toWireTransactionWithContext(cordappProvider, serializationContext)
} }
/** /**
* Provide access to internal method for AttachmentClassLoaderTests * Provide access to internal method for AttachmentClassLoaderTests
* @suppress * @suppress
*/ */
fun TransactionBuilder.toLedgerTransaction(services: ServiceHub, serializationContext: SerializationContext) = toLedgerTransactionWithContext(services, serializationContext) fun TransactionBuilder.toLedgerTransaction(services: ServicesForResolution, serializationContext: SerializationContext) = toLedgerTransactionWithContext(services, serializationContext)
/** Convenience method to get the package name of a class literal. */ /** Convenience method to get the package name of a class literal. */
val KClass<*>.packageName: String get() = java.`package`.name val KClass<*>.packageName: String get() = java.`package`.name

View File

@ -38,7 +38,9 @@ interface StateLoader {
// TODO: future implementation to use a Vault state ref -> contract state BLOB table and perform single query bulk load // TODO: future implementation to use a Vault state ref -> contract state BLOB table and perform single query bulk load
// as the existing transaction store will become encrypted at some point // as the existing transaction store will become encrypted at some point
@Throws(TransactionResolutionException::class) @Throws(TransactionResolutionException::class)
fun loadStates(stateRefs: Set<StateRef>): Set<StateAndRef<ContractState>> fun loadStates(stateRefs: Set<StateRef>): Set<StateAndRef<ContractState>> {
return stateRefs.map { StateAndRef(loadState(it), it) }.toSet()
}
} }
/** /**

View File

@ -1,8 +1,12 @@
package net.corda.core.node.services package net.corda.core.node.services
import net.corda.core.DoNotImplement import net.corda.core.DoNotImplement
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TransactionResolutionException
import net.corda.core.contracts.TransactionState
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.messaging.DataFeed import net.corda.core.messaging.DataFeed
import net.corda.core.node.StateLoader
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import rx.Observable import rx.Observable
@ -10,12 +14,18 @@ import rx.Observable
* Thread-safe storage of transactions. * Thread-safe storage of transactions.
*/ */
@DoNotImplement @DoNotImplement
interface TransactionStorage { interface TransactionStorage : StateLoader {
/** /**
* Return the transaction with the given [id], or null if no such transaction exists. * Return the transaction with the given [id], or null if no such transaction exists.
*/ */
fun getTransaction(id: SecureHash): SignedTransaction? fun getTransaction(id: SecureHash): SignedTransaction?
@Throws(TransactionResolutionException::class)
override fun loadState(stateRef: StateRef): TransactionState<*> {
val stx = getTransaction(stateRef.txhash) ?: throw TransactionResolutionException(stateRef.txhash)
return stx.resolveBaseTransaction(this).outputs[stateRef.index]
}
/** /**
* Get a synchronous Observable of updates. When observations are pushed to the Observer, the vault will already * Get a synchronous Observable of updates. When observations are pushed to the Observer, the vault will already
* incorporate the update. * incorporate the update.

View File

@ -2,6 +2,7 @@ package net.corda.core.transactions
import co.paralleluniverse.strands.Strand import co.paralleluniverse.strands.Strand
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.cordapp.CordappProvider
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.SignableData import net.corda.core.crypto.SignableData
import net.corda.core.crypto.SignatureMetadata import net.corda.core.crypto.SignatureMetadata
@ -82,16 +83,16 @@ open class TransactionBuilder(
* @returns A new [WireTransaction] that will be unaffected by further changes to this [TransactionBuilder]. * @returns A new [WireTransaction] that will be unaffected by further changes to this [TransactionBuilder].
*/ */
@Throws(MissingContractAttachments::class) @Throws(MissingContractAttachments::class)
fun toWireTransaction(services: ServicesForResolution): WireTransaction = toWireTransactionWithContext(services) fun toWireTransaction(services: ServicesForResolution): WireTransaction = toWireTransactionWithContext(services.cordappProvider)
internal fun toWireTransactionWithContext(services: ServicesForResolution, serializationContext: SerializationContext? = null): WireTransaction { internal fun toWireTransactionWithContext(cordappProvider: CordappProvider, serializationContext: SerializationContext? = null): WireTransaction {
// Resolves the AutomaticHashConstraints to HashAttachmentConstraints for convenience. The AutomaticHashConstraint // Resolves the AutomaticHashConstraints to HashAttachmentConstraints for convenience. The AutomaticHashConstraint
// allows for less boiler plate when constructing transactions since for the typical case the named contract // allows for less boiler plate when constructing transactions since for the typical case the named contract
// will be available when building the transaction. In exceptional cases the TransactionStates must be created // will be available when building the transaction. In exceptional cases the TransactionStates must be created
// with an explicit [AttachmentConstraint] // with an explicit [AttachmentConstraint]
val resolvedOutputs = outputs.map { state -> val resolvedOutputs = outputs.map { state ->
if (state.constraint is AutomaticHashConstraint) { if (state.constraint is AutomaticHashConstraint) {
services.cordappProvider.getContractAttachmentID(state.contract)?.let { cordappProvider.getContractAttachmentID(state.contract)?.let {
state.copy(constraint = HashAttachmentConstraint(it)) state.copy(constraint = HashAttachmentConstraint(it))
} ?: throw MissingContractAttachments(listOf(state)) } ?: throw MissingContractAttachments(listOf(state))
} else { } else {
@ -106,8 +107,7 @@ open class TransactionBuilder(
@Throws(AttachmentResolutionException::class, TransactionResolutionException::class) @Throws(AttachmentResolutionException::class, TransactionResolutionException::class)
fun toLedgerTransaction(services: ServiceHub) = toWireTransaction(services).toLedgerTransaction(services) fun toLedgerTransaction(services: ServiceHub) = toWireTransaction(services).toLedgerTransaction(services)
internal fun toLedgerTransactionWithContext(services: ServiceHub, serializationContext: SerializationContext) = toWireTransactionWithContext(services, serializationContext).toLedgerTransaction(services) internal fun toLedgerTransactionWithContext(services: ServicesForResolution, serializationContext: SerializationContext) = toWireTransactionWithContext(services.cordappProvider, serializationContext).toLedgerTransaction(services)
@Throws(AttachmentResolutionException::class, TransactionResolutionException::class, TransactionVerificationException::class) @Throws(AttachmentResolutionException::class, TransactionResolutionException::class, TransactionVerificationException::class)
fun verify(services: ServiceHub) { fun verify(services: ServiceHub) {
toLedgerTransaction(services).verify() toLedgerTransaction(services).verify()

View File

@ -1,13 +1,14 @@
package net.corda.core.contracts package net.corda.core.contracts
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.cordapp.CordappProvider
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.internal.UpgradeCommand import net.corda.core.internal.UpgradeCommand
import net.corda.testing.ALICE import net.corda.core.node.ServicesForResolution
import net.corda.testing.DUMMY_NOTARY import net.corda.testing.*
import net.corda.testing.contracts.DummyContract import net.corda.testing.contracts.DummyContract
import net.corda.testing.contracts.DummyContractV2 import net.corda.testing.contracts.DummyContractV2
import net.corda.testing.node.MockServices
import net.corda.testing.SerializationEnvironmentRule
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
@ -23,7 +24,9 @@ class DummyContractV2Tests {
@Test @Test
fun `upgrade from v1`() { fun `upgrade from v1`() {
val services = MockServices() val services = rigorousMock<ServicesForResolution>().also {
doReturn(rigorousMock<CordappProvider>()).whenever(it).cordappProvider
}
val contractUpgrade = DummyContractV2() val contractUpgrade = DummyContractV2()
val v1State = TransactionState(DummyContract.SingleOwnerState(0, ALICE), DummyContract.PROGRAM_ID, DUMMY_NOTARY, constraint = AlwaysAcceptAttachmentConstraint) val v1State = TransactionState(DummyContract.SingleOwnerState(0, ALICE), DummyContract.PROGRAM_ID, DUMMY_NOTARY, constraint = AlwaysAcceptAttachmentConstraint)
val v1Ref = StateRef(SecureHash.randomSHA256(), 0) val v1Ref = StateRef(SecureHash.randomSHA256(), 0)

View File

@ -337,7 +337,7 @@ class CompositeKeyTests {
val ca = X509Utilities.createSelfSignedCACertificate(caName, caKeyPair) val ca = X509Utilities.createSelfSignedCACertificate(caName, caKeyPair)
// Sign the composite key with the self sign CA. // Sign the composite key with the self sign CA.
val compositeKeyCert = X509Utilities.createCertificate(CertificateType.IDENTITY, ca, caKeyPair, caName.copy(commonName = "CompositeKey"), compositeKey) val compositeKeyCert = X509Utilities.createCertificate(CertificateType.WELL_KNOWN_IDENTITY, ca, caKeyPair, caName.copy(commonName = "CompositeKey"), compositeKey)
// Store certificate to keystore. // Store certificate to keystore.
val keystorePath = tempFolder.root.toPath() / "keystore.jks" val keystorePath = tempFolder.root.toPath() / "keystore.jks"

View File

@ -129,7 +129,7 @@ class CollectSignaturesFlowTests {
@Test @Test
fun `fails when not signed by initiator`() { fun `fails when not signed by initiator`() {
val onePartyDummyContract = DummyContract.generateInitial(1337, notary, alice.ref(1)) val onePartyDummyContract = DummyContract.generateInitial(1337, notary, alice.ref(1))
val miniCorpServices = MockServices(listOf("net.corda.testing.contracts"), MINI_CORP.name, MINI_CORP_KEY) val miniCorpServices = MockServices(listOf("net.corda.testing.contracts"), rigorousMock(), MINI_CORP.name, MINI_CORP_KEY)
val ptx = miniCorpServices.signInitialTransaction(onePartyDummyContract) val ptx = miniCorpServices.signInitialTransaction(onePartyDummyContract)
val flow = aliceNode.services.startFlow(CollectSignaturesFlow(ptx, emptySet())) val flow = aliceNode.services.startFlow(CollectSignaturesFlow(ptx, emptySet()))
mockNet.runNetwork() mockNet.runNetwork()

View File

@ -20,14 +20,17 @@ import net.corda.node.internal.SecureCordaRPCOps
import net.corda.node.internal.StartedNode import net.corda.node.internal.StartedNode
import net.corda.node.services.Permissions.Companion.startFlow import net.corda.node.services.Permissions.Companion.startFlow
import net.corda.nodeapi.internal.config.User import net.corda.nodeapi.internal.config.User
import net.corda.testing.* import net.corda.testing.ALICE_NAME
import net.corda.testing.BOB_NAME
import net.corda.testing.contracts.DummyContract import net.corda.testing.contracts.DummyContract
import net.corda.testing.contracts.DummyContractV2 import net.corda.testing.contracts.DummyContractV2
import net.corda.testing.internal.RPCDriverExposedDSLInterface import net.corda.testing.internal.RPCDriverDSL
import net.corda.testing.internal.rpcDriver import net.corda.testing.internal.rpcDriver
import net.corda.testing.internal.rpcTestUser import net.corda.testing.internal.rpcTestUser
import net.corda.testing.internal.startRpcClient import net.corda.testing.internal.startRpcClient
import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNetwork
import net.corda.testing.singleIdentity
import net.corda.testing.startFlow
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
@ -120,7 +123,7 @@ class ContractUpgradeFlowTest {
check(bobNode) check(bobNode)
} }
private fun RPCDriverExposedDSLInterface.startProxy(node: StartedNode<*>, user: User): CordaRPCOps { private fun RPCDriverDSL.startProxy(node: StartedNode<*>, user: User): CordaRPCOps {
return startRpcClient<CordaRPCOps>( return startRpcClient<CordaRPCOps>(
rpcAddress = startRpcServer( rpcAddress = startRpcServer(
rpcUser = user, rpcUser = user,

View File

@ -1,6 +1,7 @@
package net.corda.core.serialization package net.corda.core.serialization
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.crypto.generateKeyPair
import net.corda.core.identity.AbstractParty import net.corda.core.identity.AbstractParty
import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
@ -49,9 +50,8 @@ class TransactionSerializationTests {
val inputState = StateAndRef(TransactionState(TestCash.State(depositRef, 100.POUNDS, MEGA_CORP), TEST_CASH_PROGRAM_ID, DUMMY_NOTARY), fakeStateRef) val inputState = StateAndRef(TransactionState(TestCash.State(depositRef, 100.POUNDS, MEGA_CORP), TEST_CASH_PROGRAM_ID, DUMMY_NOTARY), fakeStateRef)
val outputState = TransactionState(TestCash.State(depositRef, 600.POUNDS, MEGA_CORP), TEST_CASH_PROGRAM_ID, DUMMY_NOTARY) val outputState = TransactionState(TestCash.State(depositRef, 600.POUNDS, MEGA_CORP), TEST_CASH_PROGRAM_ID, DUMMY_NOTARY)
val changeState = TransactionState(TestCash.State(depositRef, 400.POUNDS, MEGA_CORP), TEST_CASH_PROGRAM_ID, DUMMY_NOTARY) val changeState = TransactionState(TestCash.State(depositRef, 400.POUNDS, MEGA_CORP), TEST_CASH_PROGRAM_ID, DUMMY_NOTARY)
val megaCorpServices = MockServices(listOf("net.corda.core.serialization"), rigorousMock(), MEGA_CORP.name, MEGA_CORP_KEY)
val megaCorpServices = MockServices(listOf("net.corda.core.serialization"), MEGA_CORP.name, MEGA_CORP_KEY) val notaryServices = MockServices(listOf("net.corda.core.serialization"), rigorousMock(), DUMMY_NOTARY.name, DUMMY_NOTARY_KEY)
val notaryServices = MockServices(listOf("net.corda.core.serialization"), DUMMY_NOTARY.name, DUMMY_NOTARY_KEY)
lateinit var tx: TransactionBuilder lateinit var tx: TransactionBuilder
@Before @Before
@ -88,14 +88,14 @@ class TransactionSerializationTests {
assertFailsWith(IllegalArgumentException::class) { assertFailsWith(IllegalArgumentException::class) {
stx.copy(sigs = emptyList()) stx.copy(sigs = emptyList())
} }
val DUMMY_KEY_2 = generateKeyPair()
// If the signature was replaced in transit, we don't like it. // If the signature was replaced in transit, we don't like it.
assertFailsWith(SignatureException::class) { assertFailsWith(SignatureException::class) {
val tx2 = TransactionBuilder(DUMMY_NOTARY).withItems(inputState, outputState, changeState, val tx2 = TransactionBuilder(DUMMY_NOTARY).withItems(inputState, outputState, changeState,
Command(TestCash.Commands.Move(), DUMMY_KEY_2.public)) Command(TestCash.Commands.Move(), DUMMY_KEY_2.public))
val ptx2 = notaryServices.signInitialTransaction(tx2) val ptx2 = notaryServices.signInitialTransaction(tx2)
val dummyServices = MockServices(DUMMY_KEY_2) val dummyServices = MockServices(rigorousMock(), MEGA_CORP.name, DUMMY_KEY_2)
val stx2 = dummyServices.addSignature(ptx2) val stx2 = dummyServices.addSignature(ptx2)
stx.copy(sigs = stx2.sigs).verifyRequiredSignatures() stx.copy(sigs = stx2.sigs).verifyRequiredSignatures()

View File

@ -2,9 +2,11 @@ package net.corda.core.serialization
import net.corda.core.contracts.StateRef import net.corda.core.contracts.StateRef
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.generateKeyPair
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.node.services.UniquenessException import net.corda.core.node.services.UniquenessException
import net.corda.core.node.services.UniquenessProvider import net.corda.core.node.services.UniquenessProvider
import net.corda.testing.DUMMY_PARTY
import net.corda.testing.SerializationEnvironmentRule import net.corda.testing.SerializationEnvironmentRule
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
@ -19,7 +21,8 @@ class UniquenessExceptionSerializationTest {
fun testSerializationRoundTrip() { fun testSerializationRoundTrip() {
val txhash = SecureHash.randomSHA256() val txhash = SecureHash.randomSHA256()
val txHash2 = SecureHash.randomSHA256() val txHash2 = SecureHash.randomSHA256()
val stateHistory: Map<StateRef, UniquenessProvider.ConsumingTx> = mapOf(StateRef(txhash, 0) to UniquenessProvider.ConsumingTx(txHash2, 1, DUMMY_PARTY)) val dummyParty = Party(CordaX500Name("Dummy", "Madrid", "ES"), generateKeyPair().public)
val stateHistory: Map<StateRef, UniquenessProvider.ConsumingTx> = mapOf(StateRef(txhash, 0) to UniquenessProvider.ConsumingTx(txHash2, 1, dummyParty))
val conflict = UniquenessProvider.Conflict(stateHistory) val conflict = UniquenessProvider.Conflict(stateHistory)
val instance = UniquenessException(conflict) val instance = UniquenessException(conflict)

View File

@ -15,6 +15,11 @@ import java.util.function.Predicate
import kotlin.test.* import kotlin.test.*
class CompatibleTransactionTests { class CompatibleTransactionTests {
private companion object {
val DUMMY_KEY_1 = generateKeyPair()
val DUMMY_KEY_2 = generateKeyPair()
}
@Rule @Rule
@JvmField @JvmField
val testSerialization = SerializationEnvironmentRule() val testSerialization = SerializationEnvironmentRule()

View File

@ -1,14 +1,15 @@
package net.corda.core.transactions package net.corda.core.transactions
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.crypto.generateKeyPair
import net.corda.core.identity.AbstractParty import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.testing.DUMMY_NOTARY import net.corda.node.services.api.IdentityServiceInternal
import net.corda.testing.SerializationEnvironmentRule import net.corda.testing.*
import net.corda.testing.contracts.DummyContract import net.corda.testing.contracts.DummyContract
import net.corda.testing.dummyCommand
import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices
import net.corda.testing.singleIdentity
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
@ -21,7 +22,10 @@ class LedgerTransactionQueryTests {
@Rule @Rule
@JvmField @JvmField
val testSerialization = SerializationEnvironmentRule() val testSerialization = SerializationEnvironmentRule()
private val services: MockServices = MockServices() private val keyPair = generateKeyPair()
private val services = MockServices(rigorousMock<IdentityServiceInternal>().also {
doReturn(null).whenever(it).partyFromKey(keyPair.public)
}, MEGA_CORP.name, keyPair)
private val identity: Party = services.myInfo.singleIdentity() private val identity: Party = services.myInfo.singleIdentity()
@Before @Before

View File

@ -16,6 +16,11 @@ import kotlin.test.assertFailsWith
import kotlin.test.assertNotEquals import kotlin.test.assertNotEquals
class TransactionTests { class TransactionTests {
private companion object {
val DUMMY_KEY_1 = generateKeyPair()
val DUMMY_KEY_2 = generateKeyPair()
}
@Rule @Rule
@JvmField @JvmField
val testSerialization = SerializationEnvironmentRule() val testSerialization = SerializationEnvironmentRule()

View File

@ -71,7 +71,8 @@ a:visited {
} }
.wy-nav-content { .wy-nav-content {
background-color: #fff max-width: 1000px; background-color: #fff;
max-width: none;
} }
.wy-nav-side { .wy-nav-side {

View File

@ -176,6 +176,17 @@ define a ``PageSpecification`` to correctly process results with efficient memor
place to alert API users to the need for pagination where a single query returns more than 200 results and no place to alert API users to the need for pagination where a single query returns more than 200 results and no
``PageSpecification`` has been supplied. ``PageSpecification`` has been supplied.
Here's a query that extracts every unconsumed ``ContractState`` from the vault in pages of size 200, starting from the
default page number (page one):
.. container:: codeset
.. sourcecode:: kotlin
val vaultSnapshot = proxy.vaultQueryBy<ContractState>(
QueryCriteria.VaultQueryCriteria(Vault.StateStatus.UNCONSUMED),
PageSpecification(DEFAULT_PAGE_NUM, 200))
.. note:: A pages maximum size ``MAX_PAGE_SIZE`` is defined as ``Int.MAX_VALUE`` and should be used with extreme .. note:: A pages maximum size ``MAX_PAGE_SIZE`` is defined as ``Int.MAX_VALUE`` and should be used with extreme
caution as results returned may exceed your JVM's memory footprint. caution as results returned may exceed your JVM's memory footprint.

View File

@ -115,19 +115,24 @@ is already correctly configured and this is for reference only;
Creating the CorDapp JAR Creating the CorDapp JAR
------------------------ ------------------------
The gradle ``jar`` task included in the CorDapp template build file will automatically build your CorDapp JAR correctly Once your dependencies are set correctly, you can build your CorDapp JAR using the gradle ``jar`` task:
as long as your dependencies are set correctly.
* Unix/Mac OSX: ``./gradlew jar``
* Windows: ``gradlew.bat jar``
The CorDapp JAR will be output to the ``build/libs`` folder.
.. warning:: The hash of the generated CorDapp JAR is not deterministic, as it depends on variables such as the .. warning:: The hash of the generated CorDapp JAR is not deterministic, as it depends on variables such as the
timestamp at creation. Nodes running the same CorDapp must therefore ensure they are using the exact same CorDapp timestamp at creation. Nodes running the same CorDapp must therefore ensure they are using the exact same CorDapp
jar, and not different versions of the JAR created from identical sources. JAR, and not different versions of the JAR created from identical sources.
The filename of the JAR must include a unique identifier to deduplicate it from other releases of the same CorDapp. The filename of the JAR must include a unique identifier to deduplicate it from other releases of the same CorDapp.
This is typically done by appending the version string to the CorDapp's name. This unique identifier should not change This is typically done by appending the version string to the CorDapp's name. This unique identifier should not change
once the JAR has been deployed on a node. If it does, make sure no one is relying on ``FlowContext.appName`` in their once the JAR has been deployed on a node. If it does, make sure no one is relying on ``FlowContext.appName`` in their
flows (see :doc:`versioning`). flows (see :doc:`versioning`).
Installing the CorDapp jar Installing the CorDapp JAR
-------------------------- --------------------------
.. note:: Before installing a CorDapp, you must create one or more nodes to install it on. For instructions, please see .. note:: Before installing a CorDapp, you must create one or more nodes to install it on. For instructions, please see
@ -136,6 +141,3 @@ Installing the CorDapp jar
At runtime, nodes will load any CorDapps present in their ``cordapps`` folder. Therefore in order to install a CorDapp on At runtime, nodes will load any CorDapps present in their ``cordapps`` folder. Therefore in order to install a CorDapp on
a node, the CorDapp JAR must be added to the ``<node_dir>/cordapps/`` folder, where ``node_dir`` is the folder in which a node, the CorDapp JAR must be added to the ``<node_dir>/cordapps/`` folder, where ``node_dir`` is the folder in which
the node's JAR and configuration files are stored. the node's JAR and configuration files are stored.
The ``deployNodes`` gradle task, if correctly configured, will automatically place your CorDapp JAR as well as any
dependent CorDapp JARs specified into the ``cordapps`` folder automatically.

View File

@ -7,9 +7,9 @@ Deploying a node
whether they have developed and tested a CorDapp following the instructions in :doc:`generating-a-node` whether they have developed and tested a CorDapp following the instructions in :doc:`generating-a-node`
or are deploying a third-party CorDapp. or are deploying a third-party CorDapp.
Linux (systemd): Installing and running Corda as a systemd service Linux: Installing and running Corda as a system service
------------------------------------------------------------------ -------------------------------------------------------
We recommend creating systemd services to run a node and the optional webserver. This provides logging and service We recommend creating system services to run a node and the optional webserver. This provides logging and service
handling, and ensures the Corda service is run at boot. handling, and ensures the Corda service is run at boot.
**Prerequisites**: **Prerequisites**:
@ -27,10 +27,13 @@ handling, and ensures the Corda service is run at boot.
3. Download the `Corda jar <https://r3.bintray.com/corda/net/corda/corda/>`_ 3. Download the `Corda jar <https://r3.bintray.com/corda/net/corda/corda/>`_
(under ``/VERSION_NUMBER/corda-VERSION_NUMBER.jar``) and place it in ``/opt/corda`` (under ``/VERSION_NUMBER/corda-VERSION_NUMBER.jar``) and place it in ``/opt/corda``
3. Create a directory called ``plugins`` in ``/opt/corda`` and save your CorDapp jar file to it. Alternatively, download one of 4. (Optional) Download the `Corda webserver jar <http://r3.bintray.com/corda/net/corda/corda-webserver/>`_
(under ``/VERSION_NUMBER/corda-VERSION_NUMBER.jar``) and place it in ``/opt/corda``
5. Create a directory called ``plugins`` in ``/opt/corda`` and save your CorDapp jar file to it. Alternatively, download one of
our `sample CorDapps <https://www.corda.net/samples/>`_ to the ``plugins`` directory our `sample CorDapps <https://www.corda.net/samples/>`_ to the ``plugins`` directory
4. Save the below as ``/opt/corda/node.conf``. See :doc:`corda-configuration-file` for a description of these options 6. Save the below as ``/opt/corda/node.conf``. See :doc:`corda-configuration-file` for a description of these options
.. code-block:: json .. code-block:: json
@ -55,7 +58,7 @@ handling, and ensures the Corda service is run at boot.
} }
] ]
5. Make the following changes to ``/opt/corda/node.conf``: 7. Make the following changes to ``/opt/corda/node.conf``:
* Change the ``p2pAddress`` and ``rpcAddress`` values to start with your server's hostname or external IP address. * Change the ``p2pAddress`` and ``rpcAddress`` values to start with your server's hostname or external IP address.
This is the address other nodes or RPC interfaces will use to communicate with your node This is the address other nodes or RPC interfaces will use to communicate with your node
@ -70,7 +73,12 @@ handling, and ensures the Corda service is run at boot.
* Country (``C=``) is the `ISO 3166-1 alpha-2 code <https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2>`_ * Country (``C=``) is the `ISO 3166-1 alpha-2 code <https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2>`_
* Change the RPC username and password * Change the RPC username and password
6. Create a ``corda.service`` file based on the example below and save it in the ``/etc/systemd/system/`` directory .. note:: Ubuntu 16.04 and most current Linux distributions use SystemD, so if you are running one of these
distributions follow the steps marked **SystemD**.
If you are running Ubuntu 14.04, follow the instructions for **Upstart**.
8. **SystemD**: Create a ``corda.service`` file based on the example below and save it in the ``/etc/systemd/system/``
directory
.. code-block:: shell .. code-block:: shell
@ -88,20 +96,41 @@ handling, and ensures the Corda service is run at boot.
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
7. Make the following changes to ``corda.service``: 8. **Upstart**: Create a ``corda.conf`` file based on the example below and save it in the ``/etc/init/`` directory
.. code-block:: shell
description "Corda Node - Bank of Breakfast Tea"
start on runlevel [2345]
stop on runlevel [!2345]
respawn
setuid corda
chdir /opt/corda
exec java -Xmx2048m -jar /opt/corda/corda.jar
9. Make the following changes to ``corda.service`` or ``corda.conf``:
* Make sure the service description is informative - particularly if you plan to run multiple nodes. * Make sure the service description is informative - particularly if you plan to run multiple nodes.
* Change the username to the user account you want to use to run Corda. **We recommend that this is not root** * Change the username to the user account you want to use to run Corda. **We recommend that this user account is
not root**
* Set the maximum amount of memory available to the Corda process by changing the ``-Xmx2048m`` parameter * Set the maximum amount of memory available to the Corda process by changing the ``-Xmx2048m`` parameter
* Make sure the ``corda.service`` file is owned by root with the correct permissions: * **SystemD**: Make sure the ``corda.service`` file is owned by root with the correct permissions:
* ``sudo chown root:root /etc/systemd/system/corda.service`` * ``sudo chown root:root /etc/systemd/system/corda.service``
* ``sudo chmod 644 /etc/systemd/system/corda.service`` * ``sudo chmod 644 /etc/systemd/system/corda.service``
* **Upstart**: Make sure the ``corda.conf`` file is owned by root with the correct permissions:
* ``sudo chown root:root /etc/init/corda.conf``
* ``sudo chmod 644 /etc/init/corda.conf``
.. note:: The Corda webserver provides a simple interface for interacting with your installed CorDapps in a browser. .. note:: The Corda webserver provides a simple interface for interacting with your installed CorDapps in a browser.
Running the webserver is optional. Running the webserver is optional.
8. Create a ``corda-webserver.service`` file based on the example below and save it in the ``/etc/systemd/system/`` 10. **SystemD**: Create a ``corda-webserver.service`` file based on the example below and save it in the ``/etc/systemd/system/``
directory. directory
.. code-block:: shell .. code-block:: shell
@ -111,7 +140,7 @@ handling, and ensures the Corda service is run at boot.
[Service] [Service]
Type=simple Type=simple
User=username User=corda
WorkingDirectory=/opt/corda WorkingDirectory=/opt/corda
ExecStart=/usr/bin/java -jar /opt/corda/corda-webserver.jar ExecStart=/usr/bin/java -jar /opt/corda/corda-webserver.jar
Restart=on-failure Restart=on-failure
@ -119,17 +148,40 @@ handling, and ensures the Corda service is run at boot.
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
9. Provision the required certificates to your node. Contact the network permissioning service or see 10. **Upstart**: Create a ``corda-webserver.conf`` file based on the example below and save it in the ``/etc/init/``
directory
.. code-block:: shell
description "Webserver for Corda Node - Bank of Breakfast Tea"
start on runlevel [2345]
stop on runlevel [!2345]
respawn
setuid corda
chdir /opt/corda
exec java -jar /opt/corda/corda-webserver.jar
11. Provision the required certificates to your node. Contact the network permissioning service or see
:doc:`permissioning` :doc:`permissioning`
10. You can now start a node and its webserver and set the services to start on boot by running the following ``systemctl`` commands: 12. **SystemD**: You can now start a node and its webserver and set the services to start on boot by running the
following ``systemctl`` commands:
* ``sudo systemctl daemon-reload`` * ``sudo systemctl daemon-reload``
* ``sudo systemctl enable --now corda`` * ``sudo systemctl enable --now corda``
* ``sudo systemctl enable --now corda-webserver`` * ``sudo systemctl enable --now corda-webserver``
12. **Upstart**: You can now start a node and its webserver by running the following commands:
* ``sudo start corda``
* ``sudo start corda-webserver``
The Upstart configuration files created above tell Upstart to start the Corda services on boot so there is no need to explicitly enable them.
You can run multiple nodes by creating multiple directories and Corda services, modifying the ``node.conf`` and You can run multiple nodes by creating multiple directories and Corda services, modifying the ``node.conf`` and
``service`` files so they are unique. SystemD or Upstart configuration files so they are unique.
Windows: Installing and running Corda as a Windows service Windows: Installing and running Corda as a Windows service
---------------------------------------------------------- ----------------------------------------------------------

View File

@ -6,6 +6,12 @@ Here are release notes for each snapshot release from M9 onwards.
Unreleased Unreleased
---------- ----------
* **Enum Class Evolution**
With the addition of AMQP serialization Corda now supports enum constant evolution.
That is the ability to alter an enum constant and, as long as certain rules are followed and the correct
annotations applied, have older and newer instances of that enumeration be understood.
Release 2.0 Release 2.0
---------- ----------
Following quickly on the heels of the release of Corda 1.0, Corda version 2.0 consolidates Following quickly on the heels of the release of Corda 1.0, Corda version 2.0 consolidates

View File

@ -16,7 +16,7 @@ import net.corda.testing.*
import net.corda.testing.contracts.VaultFiller import net.corda.testing.contracts.VaultFiller
import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseAndMockServices import net.corda.testing.node.MockServices.Companion.makeTestDatabaseAndMockServices
import org.junit.Ignore import net.corda.testing.node.makeTestIdentityService
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@ -223,16 +223,16 @@ class CommercialPaperTestsGeneric {
private lateinit var aliceServices: MockServices private lateinit var aliceServices: MockServices
private lateinit var aliceVaultService: VaultService private lateinit var aliceVaultService: VaultService
private lateinit var alicesVault: Vault<ContractState> private lateinit var alicesVault: Vault<ContractState>
private val notaryServices = MockServices(rigorousMock(), MEGA_CORP.name, DUMMY_NOTARY_KEY)
private val notaryServices = MockServices(DUMMY_NOTARY_KEY) private val issuerServices = MockServices(listOf("net.corda.finance.contracts"), rigorousMock(), MEGA_CORP.name, DUMMY_CASH_ISSUER_KEY)
private val issuerServices = MockServices(DUMMY_CASH_ISSUER_KEY)
private lateinit var moveTX: SignedTransaction private lateinit var moveTX: SignedTransaction
@Test
// @Test fun `issue move and then redeem`() {
@Ignore val aliceDatabaseAndServices = makeTestDatabaseAndMockServices(
fun `issue move and then redeem`() = withTestSerialization { listOf(ALICE_KEY),
val aliceDatabaseAndServices = makeTestDatabaseAndMockServices(keys = listOf(ALICE_KEY)) makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY)),
listOf("net.corda.finance.contracts"),
MEGA_CORP.name)
val databaseAlice = aliceDatabaseAndServices.first val databaseAlice = aliceDatabaseAndServices.first
aliceServices = aliceDatabaseAndServices.second aliceServices = aliceDatabaseAndServices.second
aliceVaultService = aliceServices.vaultService aliceVaultService = aliceServices.vaultService
@ -241,8 +241,11 @@ class CommercialPaperTestsGeneric {
alicesVault = VaultFiller(aliceServices, DUMMY_NOTARY, DUMMY_NOTARY_KEY, rngFactory = ::Random).fillWithSomeTestCash(9000.DOLLARS, issuerServices, 1, DUMMY_CASH_ISSUER) alicesVault = VaultFiller(aliceServices, DUMMY_NOTARY, DUMMY_NOTARY_KEY, rngFactory = ::Random).fillWithSomeTestCash(9000.DOLLARS, issuerServices, 1, DUMMY_CASH_ISSUER)
aliceVaultService = aliceServices.vaultService aliceVaultService = aliceServices.vaultService
} }
val bigCorpDatabaseAndServices = makeTestDatabaseAndMockServices(
val bigCorpDatabaseAndServices = makeTestDatabaseAndMockServices(keys = listOf(BIG_CORP_KEY)) listOf(BIG_CORP_KEY),
makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY)),
listOf("net.corda.finance.contracts"),
MEGA_CORP.name)
val databaseBigCorp = bigCorpDatabaseAndServices.first val databaseBigCorp = bigCorpDatabaseAndServices.first
bigCorpServices = bigCorpDatabaseAndServices.second bigCorpServices = bigCorpDatabaseAndServices.second
bigCorpVaultService = bigCorpServices.vaultService bigCorpVaultService = bigCorpServices.vaultService

View File

@ -1,5 +1,6 @@
package net.corda.finance.contracts.asset package net.corda.finance.contracts.asset
import com.nhaarman.mockito_kotlin.*
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.generateKeyPair import net.corda.core.crypto.generateKeyPair
@ -18,6 +19,7 @@ import net.corda.finance.utils.sumCash
import net.corda.finance.utils.sumCashBy import net.corda.finance.utils.sumCashBy
import net.corda.finance.utils.sumCashOrNull import net.corda.finance.utils.sumCashOrNull
import net.corda.finance.utils.sumCashOrZero import net.corda.finance.utils.sumCashOrZero
import net.corda.node.services.api.IdentityServiceInternal
import net.corda.node.services.vault.NodeVaultService import net.corda.node.services.vault.NodeVaultService
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.testing.* import net.corda.testing.*
@ -25,6 +27,7 @@ import net.corda.testing.contracts.DummyState
import net.corda.testing.contracts.VaultFiller import net.corda.testing.contracts.VaultFiller
import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseAndMockServices import net.corda.testing.node.MockServices.Companion.makeTestDatabaseAndMockServices
import net.corda.testing.node.makeTestIdentityService
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
@ -66,13 +69,16 @@ class CashTests {
@Before @Before
fun setUp() { fun setUp() {
LogHelper.setLevel(NodeVaultService::class) LogHelper.setLevel(NodeVaultService::class)
megaCorpServices = MockServices(listOf("net.corda.finance.contracts.asset"), MEGA_CORP.name, MEGA_CORP_KEY) megaCorpServices = MockServices(listOf("net.corda.finance.contracts.asset"), rigorousMock(), MEGA_CORP.name, MEGA_CORP_KEY)
miniCorpServices = MockServices(listOf("net.corda.finance.contracts.asset"), MINI_CORP.name, MINI_CORP_KEY) miniCorpServices = MockServices(listOf("net.corda.finance.contracts.asset"), rigorousMock<IdentityServiceInternal>().also {
val notaryServices = MockServices(listOf("net.corda.finance.contracts.asset"), DUMMY_NOTARY.name, DUMMY_NOTARY_KEY) doNothing().whenever(it).justVerifyAndRegisterIdentity(argThat { name == MINI_CORP.name })
}, MINI_CORP.name, MINI_CORP_KEY)
val notaryServices = MockServices(listOf("net.corda.finance.contracts.asset"), rigorousMock(), DUMMY_NOTARY.name, DUMMY_NOTARY_KEY)
val databaseAndServices = makeTestDatabaseAndMockServices( val databaseAndServices = makeTestDatabaseAndMockServices(
cordappPackages = listOf("net.corda.finance.contracts.asset"), listOf(generateKeyPair()),
initialIdentityName = CordaX500Name(organisation = "Me", locality = "London", country = "GB"), makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY)),
keys = listOf(generateKeyPair())) listOf("net.corda.finance.contracts.asset"),
CordaX500Name("Me", "London", "GB"))
database = databaseAndServices.first database = databaseAndServices.first
ourServices = databaseAndServices.second ourServices = databaseAndServices.second
@ -500,10 +506,9 @@ class CashTests {
private fun makeSpend(services: ServiceHub, amount: Amount<Currency>, dest: AbstractParty): WireTransaction { private fun makeSpend(services: ServiceHub, amount: Amount<Currency>, dest: AbstractParty): WireTransaction {
val ourIdentity = services.myInfo.singleIdentityAndCert() val ourIdentity = services.myInfo.singleIdentityAndCert()
val changeIdentity = services.keyManagementService.freshKeyAndCert(ourIdentity, false)
val tx = TransactionBuilder(DUMMY_NOTARY) val tx = TransactionBuilder(DUMMY_NOTARY)
database.transaction { database.transaction {
Cash.generateSpend(services, tx, amount, changeIdentity, dest) Cash.generateSpend(services, tx, amount, ourIdentity, dest)
} }
return tx.toWireTransaction(services) return tx.toWireTransaction(services)
} }
@ -599,11 +604,10 @@ class CashTests {
@Test @Test
fun generateSimpleSpendWithParties() { fun generateSimpleSpendWithParties() {
val changeIdentity = ourServices.keyManagementService.freshKeyAndCert(ourServices.myInfo.singleIdentityAndCert(), false)
database.transaction { database.transaction {
val tx = TransactionBuilder(DUMMY_NOTARY) val tx = TransactionBuilder(DUMMY_NOTARY)
Cash.generateSpend(ourServices, tx, 80.DOLLARS, changeIdentity, ALICE, setOf(MINI_CORP)) Cash.generateSpend(ourServices, tx, 80.DOLLARS, ourServices.myInfo.singleIdentityAndCert(), ALICE, setOf(MINI_CORP))
assertEquals(vaultStatesUnconsumed.elementAt(2).ref, tx.inputStates()[0]) assertEquals(vaultStatesUnconsumed.elementAt(2).ref, tx.inputStates()[0])
} }
@ -772,8 +776,9 @@ class CashTests {
// Double spend. // Double spend.
@Test @Test
fun chainCashDoubleSpendFailsWith() { fun chainCashDoubleSpendFailsWith() {
val mockService = MockServices(listOf("net.corda.finance.contracts.asset"), MEGA_CORP.name, MEGA_CORP_KEY) val mockService = MockServices(listOf("net.corda.finance.contracts.asset"), rigorousMock<IdentityServiceInternal>().also {
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
}, MEGA_CORP.name, MEGA_CORP_KEY)
ledger(mockService) { ledger(mockService) {
unverifiedTransaction { unverifiedTransaction {
attachment(Cash.PROGRAM_ID) attachment(Cash.PROGRAM_ID)
@ -811,12 +816,11 @@ class CashTests {
fun multiSpend() { fun multiSpend() {
val tx = TransactionBuilder(DUMMY_NOTARY) val tx = TransactionBuilder(DUMMY_NOTARY)
database.transaction { database.transaction {
val changeIdentity = ourServices.keyManagementService.freshKeyAndCert(ourServices.myInfo.singleIdentityAndCert(), false)
val payments = listOf( val payments = listOf(
PartyAndAmount(miniCorpAnonymised, 400.DOLLARS), PartyAndAmount(miniCorpAnonymised, 400.DOLLARS),
PartyAndAmount(CHARLIE_ANONYMISED, 150.DOLLARS) PartyAndAmount(CHARLIE_ANONYMISED, 150.DOLLARS)
) )
Cash.generateSpend(ourServices, tx, payments, changeIdentity) Cash.generateSpend(ourServices, tx, payments, ourServices.myInfo.singleIdentityAndCert())
} }
val wtx = tx.toWireTransaction(ourServices) val wtx = tx.toWireTransaction(ourServices)
fun out(i: Int) = wtx.getOutput(i) as Cash.State fun out(i: Int) = wtx.getOutput(i) as Cash.State

View File

@ -1,5 +1,7 @@
package net.corda.finance.contracts.asset package net.corda.finance.contracts.asset
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.crypto.NullKeys.NULL_PARTY import net.corda.core.crypto.NullKeys.NULL_PARTY
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
@ -15,6 +17,7 @@ import net.corda.finance.*
import net.corda.finance.contracts.Commodity import net.corda.finance.contracts.Commodity
import net.corda.finance.contracts.NetType import net.corda.finance.contracts.NetType
import net.corda.finance.contracts.asset.Obligation.Lifecycle import net.corda.finance.contracts.asset.Obligation.Lifecycle
import net.corda.node.services.api.IdentityServiceInternal
import net.corda.testing.* import net.corda.testing.*
import net.corda.testing.contracts.DummyContract import net.corda.testing.contracts.DummyContract
import net.corda.testing.contracts.DummyState import net.corda.testing.contracts.DummyState
@ -51,9 +54,13 @@ class ObligationTests {
beneficiary = CHARLIE beneficiary = CHARLIE
) )
private val outState = inState.copy(beneficiary = AnonymousParty(BOB_PUBKEY)) private val outState = inState.copy(beneficiary = AnonymousParty(BOB_PUBKEY))
private val miniCorpServices = MockServices(listOf("net.corda.finance.contracts.asset"), MINI_CORP.name, MINI_CORP_KEY) private val miniCorpServices = MockServices(listOf("net.corda.finance.contracts.asset"), rigorousMock(), MINI_CORP.name, MINI_CORP_KEY)
private val notaryServices = MockServices(DUMMY_NOTARY_KEY) private val notaryServices = MockServices(rigorousMock(), MEGA_CORP.name, DUMMY_NOTARY_KEY)
private val mockService = MockServices(listOf("net.corda.finance.contracts.asset")) private val mockService = MockServices(listOf("net.corda.finance.contracts.asset"), rigorousMock<IdentityServiceInternal>().also {
doReturn(null).whenever(it).partyFromKey(ALICE_PUBKEY)
doReturn(null).whenever(it).partyFromKey(BOB_PUBKEY)
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
}, MEGA_CORP.name)
private fun cashObligationTestRoots( private fun cashObligationTestRoots(
group: LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter> group: LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>

View File

@ -1,5 +1,6 @@
apply plugin: 'java' apply plugin: 'java'
apply plugin: 'net.corda.plugins.publish-utils' apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'com.jfrog.artifactory'
description "Generates a summary of the artifact's public API" description "Generates a summary of the artifact's public API"

View File

@ -7,11 +7,14 @@ buildscript {
file("$projectDir/../constants.properties").withInputStream { constants.load(it) } file("$projectDir/../constants.properties").withInputStream { constants.load(it) }
// If you bump this version you must re-bootstrap the codebase. See the README for more information. // If you bump this version you must re-bootstrap the codebase. See the README for more information.
ext.gradle_plugins_version = constants.getProperty("gradlePluginsVersion") ext {
ext.bouncycastle_version = constants.getProperty("bouncycastleVersion") gradle_plugins_version = constants.getProperty("gradlePluginsVersion")
ext.typesafe_config_version = constants.getProperty("typesafeConfigVersion") bouncycastle_version = constants.getProperty("bouncycastleVersion")
ext.jsr305_version = constants.getProperty("jsr305Version") typesafe_config_version = constants.getProperty("typesafeConfigVersion")
ext.kotlin_version = constants.getProperty("kotlinVersion") jsr305_version = constants.getProperty("jsr305Version")
kotlin_version = constants.getProperty("kotlinVersion")
artifactory_plugin_version = constants.getProperty('artifactoryPluginVersion')
}
repositories { repositories {
mavenLocal() mavenLocal()
@ -22,10 +25,12 @@ buildscript {
classpath "net.corda.plugins:publish-utils:$gradle_plugins_version" classpath "net.corda.plugins:publish-utils:$gradle_plugins_version"
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jfrog.buildinfo:build-info-extractor-gradle:$artifactory_plugin_version"
} }
} }
apply plugin: 'net.corda.plugins.publish-utils' apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'com.jfrog.artifactory'
allprojects { allprojects {
version gradle_plugins_version version gradle_plugins_version
@ -54,3 +59,25 @@ bintrayConfig {
email = 'dev@corda.net' email = 'dev@corda.net'
} }
} }
artifactory {
publish {
contextUrl = 'https://ci-artifactory.corda.r3cev.com/artifactory'
repository {
repoKey = 'corda-dev'
username = 'teamcity'
password = System.getenv('CORDA_ARTIFACTORY_PASSWORD')
}
defaults {
// Publish utils does not have a publish block because it would be circular for it to apply it's own
// extensions to itself
if(project.name == 'publish-utils') {
publications('publishUtils')
// Root project applies the plugin (for this block) but does not need to be published
} else if(project != rootProject) {
publications(project.extensions.publish.name())
}
}
}
}

View File

@ -1,5 +1,6 @@
apply plugin: 'kotlin' apply plugin: 'kotlin'
apply plugin: 'net.corda.plugins.publish-utils' apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'com.jfrog.artifactory'
description 'Turns a project into a cordapp project that produces cordapp fat JARs' description 'Turns a project into a cordapp project that produces cordapp fat JARs'

View File

@ -1,6 +1,7 @@
apply plugin: 'java' apply plugin: 'java'
apply plugin: 'maven-publish' apply plugin: 'maven-publish'
apply plugin: 'net.corda.plugins.publish-utils' apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'com.jfrog.artifactory'
repositories { repositories {
mavenCentral() mavenCentral()

View File

@ -10,6 +10,7 @@ buildscript {
apply plugin: 'kotlin' apply plugin: 'kotlin'
apply plugin: 'net.corda.plugins.publish-utils' apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'com.jfrog.artifactory'
description 'A small gradle plugin for adding some basic Quasar tasks and configurations to reduce build.gradle bloat.' description 'A small gradle plugin for adding some basic Quasar tasks and configurations to reduce build.gradle bloat.'

View File

@ -1,13 +1,17 @@
apply plugin: 'groovy' apply plugin: 'groovy'
apply plugin: 'maven-publish' apply plugin: 'maven-publish'
apply plugin: 'com.jfrog.bintray' apply plugin: 'com.jfrog.bintray'
apply plugin: 'com.jfrog.artifactory'
// Used for bootstrapping project // Used for bootstrapping project
buildscript { buildscript {
Properties constants = new Properties() Properties constants = new Properties()
file("../../constants.properties").withInputStream { constants.load(it) } file("../../constants.properties").withInputStream { constants.load(it) }
ext.gradle_plugins_version = constants.getProperty("gradlePluginsVersion") ext {
gradle_plugins_version = constants.getProperty("gradlePluginsVersion")
artifactory_plugin_version = constants.getProperty('artifactoryPluginVersion')
}
repositories { repositories {
jcenter() jcenter()
@ -15,6 +19,7 @@ buildscript {
dependencies { dependencies {
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.4' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.4'
classpath "org.jfrog.buildinfo:build-info-extractor-gradle:$artifactory_plugin_version"
} }
} }

View File

@ -1,6 +1,7 @@
apply plugin: 'groovy' apply plugin: 'groovy'
apply plugin: 'maven-publish' apply plugin: 'maven-publish'
apply plugin: 'net.corda.plugins.publish-utils' apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'com.jfrog.artifactory'
description 'A small gradle plugin for adding some basic Quasar tasks and configurations to reduce build.gradle bloat.' description 'A small gradle plugin for adding some basic Quasar tasks and configurations to reduce build.gradle bloat.'

View File

@ -41,8 +41,8 @@ object ServiceIdentityGenerator {
val rootCert = customRootCert ?: caKeyStore.getCertificate(X509Utilities.CORDA_ROOT_CA) val rootCert = customRootCert ?: caKeyStore.getCertificate(X509Utilities.CORDA_ROOT_CA)
keyPairs.zip(dirs) { keyPair, dir -> keyPairs.zip(dirs) { keyPair, dir ->
val serviceKeyCert = X509Utilities.createCertificate(CertificateType.CLIENT_CA, issuer.certificate, issuer.keyPair, serviceName, keyPair.public) val serviceKeyCert = X509Utilities.createCertificate(CertificateType.NODE_CA, issuer.certificate, issuer.keyPair, serviceName, keyPair.public)
val compositeKeyCert = X509Utilities.createCertificate(CertificateType.CLIENT_CA, issuer.certificate, issuer.keyPair, serviceName, notaryKey) val compositeKeyCert = X509Utilities.createCertificate(CertificateType.NODE_CA, issuer.certificate, issuer.keyPair, serviceName, notaryKey)
val certPath = (dir / "certificates").createDirectories() / "distributedService.jks" val certPath = (dir / "certificates").createDirectories() / "distributedService.jks"
val keystore = loadOrCreateKeyStore(certPath, "cordacadevpass") val keystore = loadOrCreateKeyStore(certPath, "cordacadevpass")
keystore.setCertificateEntry("$serviceId-composite-key", compositeKeyCert.cert) keystore.setCertificateEntry("$serviceId-composite-key", compositeKeyCert.cert)

View File

@ -8,7 +8,6 @@ import java.security.KeyPair
import java.security.PublicKey import java.security.PublicKey
import java.security.cert.CertPath import java.security.cert.CertPath
import java.security.cert.Certificate import java.security.cert.Certificate
import java.security.cert.CertificateFactory
class KeyStoreWrapper(private val storePath: Path, private val storePassword: String) { class KeyStoreWrapper(private val storePath: Path, private val storePassword: String) {
private val keyStore = storePath.read { loadKeyStore(it, storePassword) } private val keyStore = storePath.read { loadKeyStore(it, storePassword) }
@ -18,7 +17,7 @@ class KeyStoreWrapper(private val storePath: Path, private val storePassword: St
// Assume key password = store password. // Assume key password = store password.
val clientCA = certificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA) val clientCA = certificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA)
// Create new keys and store in keystore. // Create new keys and store in keystore.
val cert = X509Utilities.createCertificate(CertificateType.IDENTITY, clientCA.certificate, clientCA.keyPair, serviceName, pubKey) val cert = X509Utilities.createCertificate(CertificateType.WELL_KNOWN_IDENTITY, clientCA.certificate, clientCA.keyPair, serviceName, pubKey)
val certPath = X509CertificateFactory().generateCertPath(cert.cert, *clientCertPath) val certPath = X509CertificateFactory().generateCertPath(cert.cert, *clientCertPath)
require(certPath.certificates.isNotEmpty()) { "Certificate path cannot be empty" } require(certPath.certificates.isNotEmpty()) { "Certificate path cannot be empty" }
// TODO: X509Utilities.validateCertificateChain() // TODO: X509Utilities.validateCertificateChain()

View File

@ -339,7 +339,7 @@ enum class CertificateType(val keyUsage: KeyUsage, vararg val purposes: KeyPurpo
isCA = true isCA = true
), ),
CLIENT_CA( NODE_CA(
KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyCertSign or KeyUsage.cRLSign), KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyCertSign or KeyUsage.cRLSign),
KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_serverAuth,
KeyPurposeId.id_kp_clientAuth, KeyPurposeId.id_kp_clientAuth,
@ -356,12 +356,20 @@ enum class CertificateType(val keyUsage: KeyUsage, vararg val purposes: KeyPurpo
), ),
// TODO: Identity certs should have only limited depth (i.e. 1) CA signing capability, with tight name constraints // TODO: Identity certs should have only limited depth (i.e. 1) CA signing capability, with tight name constraints
IDENTITY( WELL_KNOWN_IDENTITY(
KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyCertSign), KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyCertSign),
KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_serverAuth,
KeyPurposeId.id_kp_clientAuth, KeyPurposeId.id_kp_clientAuth,
KeyPurposeId.anyExtendedKeyUsage, KeyPurposeId.anyExtendedKeyUsage,
isCA = true isCA = true
),
CONFIDENTIAL_IDENTITY(
KeyUsage(KeyUsage.digitalSignature),
KeyPurposeId.id_kp_serverAuth,
KeyPurposeId.id_kp_clientAuth,
KeyPurposeId.anyExtendedKeyUsage,
isCA = false
) )
} }

View File

@ -35,5 +35,5 @@ interface AMQPSerializer<out T> {
/** /**
* Read the given object from the input. The envelope is provided in case the schema is required. * Read the given object from the input. The envelope is provided in case the schema is required.
*/ */
fun readObject(obj: Any, schema: SerializationSchemas, input: DeserializationInput): T fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): T
} }

View File

@ -0,0 +1,136 @@
package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.internal.uncheckedCast
import org.apache.qpid.proton.amqp.Symbol
import org.apache.qpid.proton.codec.Data
import java.io.NotSerializableException
import java.lang.UnsupportedOperationException
import java.lang.reflect.Type
import java.util.*
/**
* Used whenever a deserialized enums fingerprint doesn't match the fingerprint of the generated
* serializer object. I.e. the deserializing code has a different version of the code either newer or
* older). The changes will have been documented using the transformation annotations, a copy of which
* are encoded as part of the AMQP envelope.
*
* This function ascertains which version of the enumeration is newer by comparing the length of the
* transformations list. Since transformation annotations should only ever be added, never removed even
* when seemingly unneeded (such as repeated renaming of a single constant), the longer list will dictate
* which is more up to date.
*
* The list of transforms come from two places, the class as it exists on the current class path and the
* class as it exists as it was serialized. In the case of the former we can build the list by using
* reflection on the class. In the case of the latter the transforms are retrieved from the AMQP envelope.
*
* With a set of transforms chosen we calculate the set of all possible constants, then using the
* transformation rules we create a mapping between those values and the values that exist on the
* current class
*
* @property clazz The enum as it exists now, not as it did when it was serialized (either in the past
* or future).
* @property factory the [SerializerFactory] that is building this serialization object.
* @property conversions A mapping between all potential enum constants that could've been assigned to
* an instance of the enum as it existed at time of serialisation and those that exist now
* @property ordinals Convenience mapping of constant to ordinality
*/
class EnumEvolutionSerializer(
override val type: Type,
factory: SerializerFactory,
private val conversions: Map<String, String>,
private val ordinals: Map<String, Int>) : AMQPSerializer<Any> {
override val typeDescriptor = Symbol.valueOf("$DESCRIPTOR_DOMAIN:${fingerprintForType(type, factory)}")!!
companion object {
private fun MutableMap<String, String>.mapInPlace(f: (String) -> String) {
val i = iterator()
while (i.hasNext()) {
val curr = i.next()
curr.setValue(f(curr.value))
}
}
/**
* Builds an Enum Evolver serializer.
*
* @param old The description of the enum as it existed at the time of serialisation taken from the
* received AMQP header
* @param new The Serializer object we built based on the current state of the enum class on our classpath
* @param factory the [SerializerFactory] that is building this serialization object.
* @param transformsFromBlob the transforms attached to the class in the AMQP header, i.e. the transforms
* known at serialization time
*/
fun make(old: RestrictedType,
new: AMQPSerializer<Any>,
factory: SerializerFactory,
schemas: SerializationSchemas): AMQPSerializer<Any> {
val wireTransforms = schemas.transforms.types[old.name] ?: EnumMap<TransformTypes, MutableList<Transform>>(TransformTypes::class.java)
val localTransforms = TransformsSchema.get(old.name, factory)
// remember, the longer the list the newer we're assuming the transform set it as we assume
// evolution annotations are never removed, only added to
val transforms = if (wireTransforms.size > localTransforms.size) wireTransforms else localTransforms
// if either of these isn't of the cast type then something has gone terribly wrong
// elsewhere in the code
val defaultRules: List<EnumDefaultSchemaTransform>? = uncheckedCast(transforms[TransformTypes.EnumDefault])
val renameRules: List<RenameSchemaTransform>? = uncheckedCast(transforms[TransformTypes.Rename])
// What values exist on the enum as it exists on the class path
val localValues = new.type.asClass()!!.enumConstants.map { it.toString() }
val conversions: MutableMap<String, String> = localValues
.union(defaultRules?.map { it.new }?.toSet() ?: emptySet())
.union(renameRules?.map { it.to } ?: emptySet())
.associateBy({ it }, { it })
.toMutableMap()
val rules: MutableMap<String, String> = mutableMapOf()
rules.putAll(defaultRules?.associateBy({ it.new }, { it.old }) ?: emptyMap())
val renameRulesMap = renameRules?.associateBy({ it.to }, { it.from }) ?: emptyMap()
rules.putAll(renameRulesMap)
// take out set of all possible constants and build a map from those to the
// existing constants applying the rename and defaulting rules as defined
// in the schema
while (conversions.filterNot { it.value in localValues }.isNotEmpty()) {
conversions.mapInPlace { rules[it] ?: it }
}
// you'd think this was overkill to get access to the ordinal values for each constant but it's actually
// rather tricky when you don't have access to the actual type, so this is a nice way to be able
// to precompute and pass to the actual object
val ordinals = localValues.mapIndexed { i, s -> Pair(s, i) }.toMap()
// create a mapping between the ordinal value and the name as it was serialised converted
// to the name as it exists. We want to test any new constants have been added to the end
// of the enum class
val serialisedOrds = ((schemas.schema.types.find { it.name == old.name } as RestrictedType).choices
.associateBy ({ it.value.toInt() }, { conversions[it.name] }))
if (ordinals.filterNot { serialisedOrds[it.value] == it.key }.isNotEmpty()) {
throw NotSerializableException("Constants have been reordered, additions must be appended to the end")
}
return EnumEvolutionSerializer(new.type, factory, conversions, ordinals)
}
}
override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): Any {
val enumName = (obj as List<*>)[0] as String
if (enumName !in conversions) {
throw NotSerializableException("No rule to evolve enum constant $type::$enumName")
}
return type.asClass()!!.enumConstants[ordinals[conversions[enumName]]!!]
}
override fun writeClassInfo(output: SerializationOutput) {
throw UnsupportedOperationException("It should be impossible to write an evolution serializer")
}
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
throw UnsupportedOperationException("It should be impossible to write an evolution serializer")
}
}

View File

@ -109,7 +109,7 @@ class EvolutionSerializer(
} }
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) { override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
throw IllegalAccessException("It should be impossible to write an evolution serializer") throw UnsupportedOperationException("It should be impossible to write an evolution serializer")
} }
/** /**

View File

@ -46,11 +46,11 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
private fun getEvolutionSerializer( private fun getEvolutionSerializer(
typeNotation: TypeNotation, typeNotation: TypeNotation,
newSerializer: AMQPSerializer<Any>, newSerializer: AMQPSerializer<Any>,
transforms: TransformsSchema): AMQPSerializer<Any> { schemas: SerializationSchemas): AMQPSerializer<Any> {
return serializersByDescriptor.computeIfAbsent(typeNotation.descriptor.name!!) { return serializersByDescriptor.computeIfAbsent(typeNotation.descriptor.name!!) {
when (typeNotation) { when (typeNotation) {
is CompositeType -> EvolutionSerializer.make(typeNotation, newSerializer as ObjectSerializer, this) is CompositeType -> EvolutionSerializer.make(typeNotation, newSerializer as ObjectSerializer, this)
is RestrictedType -> throw NotSerializableException("Enum evolution is not currently supported") is RestrictedType -> EnumEvolutionSerializer.make(typeNotation, newSerializer, this, schemas)
} }
} }
} }
@ -210,7 +210,7 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
// doesn't match that of the serialised object then we are dealing with different // doesn't match that of the serialised object then we are dealing with different
// instance of the class, as such we need to build an EvolutionSerialiser // instance of the class, as such we need to build an EvolutionSerialiser
if (serialiser.typeDescriptor != typeNotation.descriptor.name) { if (serialiser.typeDescriptor != typeNotation.descriptor.name) {
getEvolutionSerializer(typeNotation, serialiser, schemaAndDescriptor.schemas.transforms) getEvolutionSerializer(typeNotation, serialiser, schemaAndDescriptor.schemas)
} }
} catch (e: ClassNotFoundException) { } catch (e: ClassNotFoundException) {
if (sentinel) throw e if (sentinel) throw e

View File

@ -1,5 +1,6 @@
package net.corda.nodeapi.internal.serialization.amqp package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.internal.uncheckedCast
import net.corda.core.serialization.CordaSerializationTransformEnumDefault import net.corda.core.serialization.CordaSerializationTransformEnumDefault
import net.corda.core.serialization.CordaSerializationTransformEnumDefaults import net.corda.core.serialization.CordaSerializationTransformEnumDefaults
import net.corda.core.serialization.CordaSerializationTransformRename import net.corda.core.serialization.CordaSerializationTransformRename
@ -27,14 +28,72 @@ enum class TransformTypes(val build: (Annotation) -> Transform) : DescribedType
Unknown({ UnknownTransform() }) { Unknown({ UnknownTransform() }) {
override fun getDescriptor(): Any = DESCRIPTOR override fun getDescriptor(): Any = DESCRIPTOR
override fun getDescribed(): Any = ordinal override fun getDescribed(): Any = ordinal
override fun validate(l : List<Transform>, constants: Map<String, Int>) { }
}, },
EnumDefault({ a -> EnumDefaultSchemaTransform((a as CordaSerializationTransformEnumDefault).old, a.new) }) { EnumDefault({ a -> EnumDefaultSchemaTransform((a as CordaSerializationTransformEnumDefault).old, a.new) }) {
override fun getDescriptor(): Any = DESCRIPTOR override fun getDescriptor(): Any = DESCRIPTOR
override fun getDescribed(): Any = ordinal override fun getDescribed(): Any = ordinal
/**
* Validates a list of constant additions to an enumerated type. To be valid a default (the value
* that should be used when we cannot use the new value) must refer to a constant that exists in the
* enum class as it exists now and it cannot refer to itself.
*
* @param l The list of transforms representing new constants and the mapping from that constant to an
* existing value
* @param constants The list of enum constants on the type the transforms are being applied to
*/
override fun validate(list : List<Transform>, constants: Map<String, Int>) {
uncheckedCast<List<Transform>, List<EnumDefaultSchemaTransform>>(list).forEach {
if (!constants.contains(it.new)) {
throw NotSerializableException("Unknown enum constant ${it.new}")
}
if (!constants.contains(it.old)) {
throw NotSerializableException(
"Enum extension defaults must be to a valid constant: ${it.new} -> ${it.old}. ${it.old} " +
"doesn't exist in constant set $constants")
}
if (it.old == it.new) {
throw NotSerializableException("Enum extension ${it.new} cannot default to itself")
}
if (constants[it.old]!! >= constants[it.new]!!) {
throw NotSerializableException(
"Enum extensions must default to older constants. ${it.new}[${constants[it.new]}] " +
"defaults to ${it.old}[${constants[it.old]}] which is greater")
}
}
}
}, },
Rename({ a -> RenameSchemaTransform((a as CordaSerializationTransformRename).from, a.to) }) { Rename({ a -> RenameSchemaTransform((a as CordaSerializationTransformRename).from, a.to) }) {
override fun getDescriptor(): Any = DESCRIPTOR override fun getDescriptor(): Any = DESCRIPTOR
override fun getDescribed(): Any = ordinal override fun getDescribed(): Any = ordinal
/**
* Validates a list of rename transforms is valid. Such a list isn't valid if we detect a cyclic chain,
* that is a constant is renamed to something that used to exist in the enum. We do this for both
* the same constant (i.e. C -> D -> C) and multiple constants (C->D, B->C)
*
* @param l The list of transforms representing the renamed constants and the mapping between their new
* and old values
* @param constants The list of enum constants on the type the transforms are being applied to
*/
override fun validate(l : List<Transform>, constants: Map<String, Int>) {
object : Any() {
val from : MutableSet<String> = mutableSetOf()
val to : MutableSet<String> = mutableSetOf() }.apply {
@Suppress("UNCHECKED_CAST") (l as List<RenameSchemaTransform>).forEach { rename ->
if (rename.to in this.to || rename.from in this.from) {
throw NotSerializableException("Cyclic renames are not allowed (${rename.to})")
}
this.to.add(rename.from)
this.from.add(rename.to)
}
}
}
} }
// Transform used to test the unknown handler, leave this at as the final constant, uncomment // Transform used to test the unknown handler, leave this at as the final constant, uncomment
// when regenerating test cases - if Java had a pre-processor this would be much neater // when regenerating test cases - if Java had a pre-processor this would be much neater
@ -45,6 +104,8 @@ enum class TransformTypes(val build: (Annotation) -> Transform) : DescribedType
//} //}
; ;
abstract fun validate(l: List<Transform>, constants: Map<String, Int>)
companion object : DescribedTypeConstructor<TransformTypes> { companion object : DescribedTypeConstructor<TransformTypes> {
val DESCRIPTOR = AMQPDescriptorRegistry.TRANSFORM_ELEMENT_KEY.amqpDescriptor val DESCRIPTOR = AMQPDescriptorRegistry.TRANSFORM_ELEMENT_KEY.amqpDescriptor

View File

@ -146,8 +146,8 @@ class EnumDefaultSchemaTransform(val old: String, val new: String) : Transform()
/** /**
* Transform applied to either a class or enum where a property is renamed * Transform applied to either a class or enum where a property is renamed
* *
* @property from the name at time of change of the property * @property from the name of the property or constant prior to being changed, i.e. what it was
* @property to the new name of the property * @property to the new name of the property or constant after the change has been made, i.e. what it is now
*/ */
class RenameSchemaTransform(val from: String, val to: String) : Transform() { class RenameSchemaTransform(val from: String, val to: String) : Transform() {
companion object : DescribedTypeConstructor<RenameSchemaTransform> { companion object : DescribedTypeConstructor<RenameSchemaTransform> {
@ -193,21 +193,17 @@ data class TransformsSchema(val types: Map<String, EnumMap<TransformTypes, Mutab
val DESCRIPTOR = AMQPDescriptorRegistry.TRANSFORM_SCHEMA.amqpDescriptor val DESCRIPTOR = AMQPDescriptorRegistry.TRANSFORM_SCHEMA.amqpDescriptor
/** /**
* Prepare a schema for encoding, takes all of the types being transmitted and inspects each * Takes a class name and either returns a cached instance of the TransformSet for it or, on a cache miss,
* one for any transform annotations. If there are any build up a set that can be * instantiates the transform set before inserting into the cache and returning it.
* encoded into the AMQP [Envelope]
* *
* @param schema should be a [Schema] generated for a serialised data structure * @param name fully qualified class name to lookup transforms for
* @param sf should be provided by the same serialization context that generated the schema * @param sf the [SerializerFactory] building this transform set. Needed as each can define it's own
* class loader and this dictates which classes we can and cannot see
*/ */
fun build(schema: Schema, sf: SerializerFactory): TransformsSchema { fun get(name: String, sf: SerializerFactory) = sf.transformsCache.computeIfAbsent(name) {
val rtn = mutableMapOf<String, EnumMap<TransformTypes, MutableList<Transform>>>()
schema.types.forEach { type ->
sf.transformsCache.computeIfAbsent(type.name) {
val transforms = EnumMap<TransformTypes, MutableList<Transform>>(TransformTypes::class.java) val transforms = EnumMap<TransformTypes, MutableList<Transform>>(TransformTypes::class.java)
try { try {
val clazz = sf.classloader.loadClass(type.name) val clazz = sf.classloader.loadClass(name)
supportedTransforms.forEach { transform -> supportedTransforms.forEach { transform ->
clazz.getAnnotation(transform.type)?.let { list -> clazz.getAnnotation(transform.type)?.let { list ->
@ -218,13 +214,18 @@ data class TransformsSchema(val types: Map<String, EnumMap<TransformTypes, Mutab
// ignore them it feels like a good thing to alert the user to since this is // ignore them it feels like a good thing to alert the user to since this is
// more than likely a typo in their code so best make it an actual error // more than likely a typo in their code so best make it an actual error
if (transforms.computeIfAbsent(transform.enum) { mutableListOf() } if (transforms.computeIfAbsent(transform.enum) { mutableListOf() }
.filter { t == it }.isNotEmpty()) { .filter { t == it }
.isNotEmpty()) {
throw NotSerializableException( throw NotSerializableException(
"Repeated unique transformation annotation of type ${t.name}") "Repeated unique transformation annotation of type ${t.name}")
} }
transforms[transform.enum]!!.add(t) transforms[transform.enum]!!.add(t)
} }
transform.enum.validate(
transforms[transform.enum] ?: emptyList(),
clazz.enumConstants.mapIndexed { i, s -> Pair(s.toString(), i) }.toMap())
} }
} }
} catch (_: ClassNotFoundException) { } catch (_: ClassNotFoundException) {
@ -233,15 +234,31 @@ data class TransformsSchema(val types: Map<String, EnumMap<TransformTypes, Mutab
} }
transforms transforms
}.apply { }
private fun getAndAdd(
type: String,
sf: SerializerFactory,
map: MutableMap<String, EnumMap<TransformTypes, MutableList<Transform>>>) {
get(type, sf).apply {
if (isNotEmpty()) { if (isNotEmpty()) {
rtn[type.name] = this map[type] = this
} }
} }
} }
return TransformsSchema(rtn) /**
} * Prepare a schema for encoding, takes all of the types being transmitted and inspects each
* one for any transform annotations. If there are any build up a set that can be
* encoded into the AMQP [Envelope]
*
* @param schema should be a [Schema] generated for a serialised data structure
* @param sf should be provided by the same serialization context that generated the schema
*/
fun build(schema: Schema, sf: SerializerFactory) = TransformsSchema(
mutableMapOf<String, EnumMap<TransformTypes, MutableList<Transform>>>().apply {
schema.types.forEach { type -> getAndAdd(type.name, sf, this) }
})
override fun getTypeClass(): Class<*> = TransformsSchema::class.java override fun getTypeClass(): Class<*> = TransformsSchema::class.java
@ -286,6 +303,7 @@ data class TransformsSchema(val types: Map<String, EnumMap<TransformTypes, Mutab
override fun getDescribed(): Any = types override fun getDescribed(): Any = types
@Suppress("NAME_SHADOWING")
override fun toString(): String { override fun toString(): String {
data class Indent(val indent: String) { data class Indent(val indent: String) {
@Suppress("UNUSED") constructor(i: Indent) : this(" ${i.indent}") @Suppress("UNUSED") constructor(i: Indent) : this(" ${i.indent}")

View File

@ -1,16 +1,20 @@
package net.corda.nodeapi.internal package net.corda.nodeapi.internal
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.identity.AbstractParty import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.node.ServicesForResolution
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.internal.cordapp.CordappProviderImpl
import net.corda.testing.* import net.corda.testing.*
import net.corda.testing.node.MockServices import net.corda.testing.node.MockAttachmentStorage
import org.junit.Assert.* import org.junit.Assert.*
import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
@ -44,11 +48,8 @@ class AttachmentsClassLoaderStaticContractTests {
} }
} }
private lateinit var serviceHub: MockServices private val serviceHub = rigorousMock<ServicesForResolution>().also {
doReturn(CordappProviderImpl(CordappLoader.createWithTestPackages(listOf("net.corda.nodeapi.internal")), MockAttachmentStorage())).whenever(it).cordappProvider
@Before
fun `create service hub`() {
serviceHub = MockServices(cordappPackages = listOf("net.corda.nodeapi.internal"))
} }
@Test @Test

View File

@ -19,10 +19,8 @@ import net.corda.nodeapi.internal.serialization.attachmentsClassLoaderEnabledPro
import net.corda.nodeapi.internal.serialization.withTokenContext import net.corda.nodeapi.internal.serialization.withTokenContext
import net.corda.testing.* import net.corda.testing.*
import net.corda.testing.node.MockAttachmentStorage import net.corda.testing.node.MockAttachmentStorage
import net.corda.testing.node.MockServices
import org.apache.commons.io.IOUtils import org.apache.commons.io.IOUtils
import org.junit.Assert.* import org.junit.Assert.*
import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
@ -52,14 +50,13 @@ class AttachmentsClassLoaderTests {
@Rule @Rule
@JvmField @JvmField
val testSerialization = SerializationEnvironmentRule() val testSerialization = SerializationEnvironmentRule()
private lateinit var serviceHub: DummyServiceHub private val attachments = MockAttachmentStorage()
private val cordappProvider = CordappProviderImpl(CordappLoader.createDevMode(listOf(ISOLATED_CONTRACTS_JAR_PATH)), attachments)
class DummyServiceHub : MockServices() {
override val cordappProvider: CordappProviderImpl
= CordappProviderImpl(CordappLoader.createDevMode(listOf(ISOLATED_CONTRACTS_JAR_PATH)), attachments)
private val cordapp get() = cordappProvider.cordapps.first() private val cordapp get() = cordappProvider.cordapps.first()
val attachmentId get() = cordappProvider.getCordappAttachmentId(cordapp)!! private val attachmentId get() = cordappProvider.getCordappAttachmentId(cordapp)!!
val appContext get() = cordappProvider.getAppContext(cordapp) private val appContext get() = cordappProvider.getAppContext(cordapp)
private val serviceHub = rigorousMock<ServiceHub>().also {
doReturn(attachments).whenever(it).attachments
} }
// These ClassLoaders work together to load 'AnotherDummyContract' in a disposable way, such that even though // These ClassLoaders work together to load 'AnotherDummyContract' in a disposable way, such that even though
@ -77,12 +74,6 @@ class AttachmentsClassLoaderTests {
} }
class ClassLoaderForTests : URLClassLoader(arrayOf(ISOLATED_CONTRACTS_JAR_PATH), FilteringClassLoader) class ClassLoaderForTests : URLClassLoader(arrayOf(ISOLATED_CONTRACTS_JAR_PATH), FilteringClassLoader)
@Before
fun `create service hub`() {
serviceHub = DummyServiceHub()
}
@Test @Test
fun `dynamically load AnotherDummyContract from isolated contracts jar`() { fun `dynamically load AnotherDummyContract from isolated contracts jar`() {
ClassLoaderForTests().use { child -> ClassLoaderForTests().use { child ->
@ -112,8 +103,8 @@ class AttachmentsClassLoaderTests {
@Test @Test
fun `test MockAttachmentStorage open as jar`() { fun `test MockAttachmentStorage open as jar`() {
val storage = serviceHub.attachments val storage = attachments
val key = serviceHub.attachmentId val key = attachmentId
val attachment = storage.openAttachment(key)!! val attachment = storage.openAttachment(key)!!
val jar = attachment.openAsJAR() val jar = attachment.openAsJAR()
@ -123,9 +114,8 @@ class AttachmentsClassLoaderTests {
@Test @Test
fun `test overlapping file exception`() { fun `test overlapping file exception`() {
val storage = serviceHub.attachments val storage = attachments
val att0 = attachmentId
val att0 = serviceHub.attachmentId
val att1 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file.txt", "some data"))) val att1 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file.txt", "some data")))
val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file.txt", "some other data"))) val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file.txt", "some other data")))
@ -136,9 +126,8 @@ class AttachmentsClassLoaderTests {
@Test @Test
fun `basic`() { fun `basic`() {
val storage = serviceHub.attachments val storage = attachments
val att0 = attachmentId
val att0 = serviceHub.attachmentId
val att1 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file1.txt", "some data"))) val att1 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file1.txt", "some data")))
val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file2.txt", "some other data"))) val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file2.txt", "some other data")))
@ -169,9 +158,8 @@ class AttachmentsClassLoaderTests {
@Test @Test
fun `loading class AnotherDummyContract`() { fun `loading class AnotherDummyContract`() {
val storage = serviceHub.attachments val storage = attachments
val att0 = attachmentId
val att0 = serviceHub.attachmentId
val att1 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file1.txt", "some data"))) val att1 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file1.txt", "some data")))
val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file2.txt", "some other data"))) val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file2.txt", "some other data")))
@ -194,10 +182,8 @@ class AttachmentsClassLoaderTests {
val contract = createContract2Cash() val contract = createContract2Cash()
val bytes = contract.serialize() val bytes = contract.serialize()
val storage = attachments
val storage = serviceHub.attachments val att0 = attachmentId
val att0 = serviceHub.attachmentId
val att1 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file1.txt", "some data"))) val att1 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file1.txt", "some data")))
val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file2.txt", "some other data"))) val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file2.txt", "some other data")))
@ -222,10 +208,8 @@ class AttachmentsClassLoaderTests {
val context2 = SerializationFactory.defaultFactory.defaultContext.withWhitelisted(data.contract.javaClass) val context2 = SerializationFactory.defaultFactory.defaultContext.withWhitelisted(data.contract.javaClass)
val bytes = data.serialize(context = context2) val bytes = data.serialize(context = context2)
val storage = attachments
val storage = serviceHub.attachments val att0 = attachmentId
val att0 = serviceHub.attachmentId
val att1 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file1.txt", "some data"))) val att1 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file1.txt", "some data")))
val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file2.txt", "some other data"))) val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file2.txt", "some other data")))
@ -276,7 +260,7 @@ class AttachmentsClassLoaderTests {
@Test @Test
fun `test serialization of WireTransaction with dynamically loaded contract`() { fun `test serialization of WireTransaction with dynamically loaded contract`() {
val child = serviceHub.appContext.classLoader val child = appContext.classLoader
val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, child) val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, child)
val contract = contractClass.newInstance() as DummyContractBackdoor val contract = contractClass.newInstance() as DummyContractBackdoor
val tx = contract.generateInitial(MEGA_CORP.ref(0), 42, DUMMY_NOTARY) val tx = contract.generateInitial(MEGA_CORP.ref(0), 42, DUMMY_NOTARY)
@ -288,7 +272,7 @@ class AttachmentsClassLoaderTests {
.withClassLoader(child) .withClassLoader(child)
val bytes = run { val bytes = run {
val wireTransaction = tx.toWireTransaction(serviceHub, context) val wireTransaction = tx.toWireTransaction(cordappProvider, context)
wireTransaction.serialize(context = context) wireTransaction.serialize(context = context)
} }
val copiedWireTransaction = bytes.deserialize(context = context) val copiedWireTransaction = bytes.deserialize(context = context)
@ -307,13 +291,12 @@ class AttachmentsClassLoaderTests {
val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, child) val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, child)
val contract = contractClass.newInstance() as DummyContractBackdoor val contract = contractClass.newInstance() as DummyContractBackdoor
val tx = contract.generateInitial(MEGA_CORP.ref(0), 42, DUMMY_NOTARY) val tx = contract.generateInitial(MEGA_CORP.ref(0), 42, DUMMY_NOTARY)
val attachmentRef = attachmentId
val attachmentRef = serviceHub.attachmentId
val bytes = run { val bytes = run {
val outboundContext = SerializationFactory.defaultFactory.defaultContext val outboundContext = SerializationFactory.defaultFactory.defaultContext
.withServiceHub(serviceHub) .withServiceHub(serviceHub)
.withClassLoader(child) .withClassLoader(child)
val wireTransaction = tx.toWireTransaction(serviceHub, outboundContext) val wireTransaction = tx.toWireTransaction(cordappProvider, outboundContext)
wireTransaction.serialize(context = outboundContext) wireTransaction.serialize(context = outboundContext)
} }
// use empty attachmentStorage // use empty attachmentStorage
@ -340,7 +323,7 @@ class AttachmentsClassLoaderTests {
val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, child) val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, child)
val contract = contractClass.newInstance() as DummyContractBackdoor val contract = contractClass.newInstance() as DummyContractBackdoor
val outboundContext = SerializationFactory.defaultFactory.defaultContext.withClassLoader(child) val outboundContext = SerializationFactory.defaultFactory.defaultContext.withClassLoader(child)
val attachmentRef = serviceHub.attachmentId val attachmentRef = attachmentId
// We currently ignore annotations in attachments, so manually whitelist. // We currently ignore annotations in attachments, so manually whitelist.
val inboundContext = SerializationFactory val inboundContext = SerializationFactory
.defaultFactory .defaultFactory

View File

@ -2,7 +2,7 @@ package net.corda.nodeapi.internal.serialization
import net.corda.core.contracts.ContractAttachment import net.corda.core.contracts.ContractAttachment
import net.corda.core.serialization.* import net.corda.core.serialization.*
import net.corda.testing.SerializationEnvironmentRule import net.corda.testing.*
import net.corda.testing.contracts.DummyContract import net.corda.testing.contracts.DummyContract
import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
@ -22,9 +22,7 @@ class ContractAttachmentSerializerTest {
private lateinit var factory: SerializationFactory private lateinit var factory: SerializationFactory
private lateinit var context: SerializationContext private lateinit var context: SerializationContext
private lateinit var contextWithToken: SerializationContext private lateinit var contextWithToken: SerializationContext
private val mockServices = MockServices(rigorousMock(), MEGA_CORP.name)
private val mockServices = MockServices()
@Before @Before
fun setup() { fun setup() {
factory = testSerialization.env.serializationFactory factory = testSerialization.env.serializationFactory

View File

@ -1,6 +1,7 @@
package net.corda.nodeapi.internal.serialization.amqp package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.serialization.* import net.corda.core.serialization.*
import net.corda.testing.common.internal.ProjectStructure.projectRootDir
import org.assertj.core.api.Assertions import org.assertj.core.api.Assertions
import org.junit.Test import org.junit.Test
import java.io.File import java.io.File
@ -10,8 +11,9 @@ import kotlin.test.assertEquals
import kotlin.test.assertTrue import kotlin.test.assertTrue
class EnumEvolvabilityTests { class EnumEvolvabilityTests {
var localPath = "file:///home/katelyn/srcs/corda/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp" @Suppress("UNUSED")
var localPath = projectRootDir.toUri().resolve(
"node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp")
companion object { companion object {
val VERBOSE = false val VERBOSE = false
@ -21,11 +23,6 @@ class EnumEvolvabilityTests {
A, B, C, D A, B, C, D
} }
@CordaSerializationTransformEnumDefaults()
enum class MissingDefaults {
A, B, C, D
}
@CordaSerializationTransformRenames() @CordaSerializationTransformRenames()
enum class MissingRenames { enum class MissingRenames {
A, B, C, D A, B, C, D
@ -48,13 +45,6 @@ class EnumEvolvabilityTests {
A, B, C, E A, B, C, E
} }
@CordaSerializationTransformRenames(
CordaSerializationTransformRename("E", "C"),
CordaSerializationTransformRename("F", "D"))
enum class RenameEnumTwice {
A, B, E, F
}
@Test @Test
fun noAnnotation() { fun noAnnotation() {
data class C (val n: NotAnnotated) data class C (val n: NotAnnotated)
@ -66,6 +56,11 @@ class EnumEvolvabilityTests {
assertEquals(0, bAndS.transformsSchema.types.size) assertEquals(0, bAndS.transformsSchema.types.size)
} }
@CordaSerializationTransformEnumDefaults()
enum class MissingDefaults {
A, B, C, D
}
@Test @Test
fun missingDefaults() { fun missingDefaults() {
data class C (val m: MissingDefaults) data class C (val m: MissingDefaults)
@ -228,6 +223,13 @@ class EnumEvolvabilityTests {
assertEquals("E", (deserialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).to) assertEquals("E", (deserialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).to)
} }
@CordaSerializationTransformRenames(
CordaSerializationTransformRename("E", "C"),
CordaSerializationTransformRename("F", "D"))
enum class RenameEnumTwice {
A, B, E, F
}
@Test @Test
fun doubleRenameAnnotationIsAdded() { fun doubleRenameAnnotationIsAdded() {
data class C (val annotatedEnum: RenameEnumTwice) data class C (val annotatedEnum: RenameEnumTwice)
@ -433,4 +435,98 @@ class EnumEvolvabilityTests {
assertTrue(envelope.transformsSchema.types.containsKey(WithUnknownTest::class.java.name)) assertTrue(envelope.transformsSchema.types.containsKey(WithUnknownTest::class.java.name))
assertTrue(envelope.transformsSchema.types[WithUnknownTest::class.java.name]!!.containsKey(TransformTypes.Unknown)) assertTrue(envelope.transformsSchema.types[WithUnknownTest::class.java.name]!!.containsKey(TransformTypes.Unknown))
} }
//
// In this example we will have attempted to rename D back to C
//
// The life cycle of the class would've looked like this
//
// 1. enum class RejectCyclicRename { A, B, C }
// 2. enum class RejectCyclicRename { A, B, D }
// 3. enum class RejectCyclicRename { A, B, C }
//
// And we're not at 3. However, we ban this rename
//
@CordaSerializationTransformRenames (
CordaSerializationTransformRename("D", "C"),
CordaSerializationTransformRename("C", "D")
)
enum class RejectCyclicRename { A, B, C }
@Test
fun rejectCyclicRename() {
data class C (val e: RejectCyclicRename)
val sf = testDefaultFactory()
Assertions.assertThatThrownBy {
SerializationOutput(sf).serialize(C(RejectCyclicRename.A))
}.isInstanceOf(NotSerializableException::class.java)
}
//
// In this test, like the above, we're looking to ensure repeated renames are rejected as
// unserailzble. However, in this case, it isn't a struct cycle, rather one element
// is renamed to match what a different element used to be called
//
@CordaSerializationTransformRenames (
CordaSerializationTransformRename(from = "B", to = "C"),
CordaSerializationTransformRename(from = "C", to = "D")
)
enum class RejectCyclicRenameAlt { A, C, D }
@Test
fun rejectCyclicRenameAlt() {
data class C (val e: RejectCyclicRenameAlt)
val sf = testDefaultFactory()
Assertions.assertThatThrownBy {
SerializationOutput(sf).serialize(C(RejectCyclicRenameAlt.A))
}.isInstanceOf(NotSerializableException::class.java)
}
@CordaSerializationTransformRenames (
CordaSerializationTransformRename("G", "C"),
CordaSerializationTransformRename("F", "G"),
CordaSerializationTransformRename("E", "F"),
CordaSerializationTransformRename("D", "E"),
CordaSerializationTransformRename("C", "D")
)
enum class RejectCyclicRenameRedux { A, B, C }
@Test
fun rejectCyclicRenameRedux() {
data class C (val e: RejectCyclicRenameRedux)
val sf = testDefaultFactory()
Assertions.assertThatThrownBy {
SerializationOutput(sf).serialize(C(RejectCyclicRenameRedux.A))
}.isInstanceOf(NotSerializableException::class.java)
}
@CordaSerializationTransformEnumDefault (new = "D", old = "X")
enum class RejectBadDefault { A, B, C, D }
@Test
fun rejectBadDefault() {
data class C (val e: RejectBadDefault)
val sf = testDefaultFactory()
Assertions.assertThatThrownBy {
SerializationOutput(sf).serialize(C(RejectBadDefault.D))
}.isInstanceOf(NotSerializableException::class.java)
}
@CordaSerializationTransformEnumDefault (new = "D", old = "D")
enum class RejectBadDefaultToSelf { A, B, C, D }
@Test
fun rejectBadDefaultToSelf() {
data class C (val e: RejectBadDefaultToSelf)
val sf = testDefaultFactory()
Assertions.assertThatThrownBy {
SerializationOutput(sf).serialize(C(RejectBadDefaultToSelf.D))
}.isInstanceOf(NotSerializableException::class.java)
}
} }

View File

@ -1,18 +1,19 @@
package net.corda.nodeapi.internal.serialization.amqp package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.serialization.CordaSerializationTransformEnumDefault import net.corda.core.serialization.*
import net.corda.core.serialization.SerializedBytes
import net.corda.testing.common.internal.ProjectStructure.projectRootDir import net.corda.testing.common.internal.ProjectStructure.projectRootDir
import org.assertj.core.api.Assertions import org.assertj.core.api.Assertions
import org.junit.Test import org.junit.Test
import java.io.File import java.io.File
import java.io.NotSerializableException import java.io.NotSerializableException
import java.net.URI import java.net.URI
import kotlin.test.assertEquals
// NOTE: To recreate the test files used by these tests uncomment the original test classes and comment // NOTE: To recreate the test files used by these tests uncomment the original test classes and comment
// the new ones out, then change each test to write out the serialized bytes rather than read // the new ones out, then change each test to write out the serialized bytes rather than read
// the file. // the file.
class EnumEvolveTests { class EnumEvolveTests {
@Suppress("UNUSED")
var localPath = projectRootDir.toUri().resolve( var localPath = projectRootDir.toUri().resolve(
"node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp") "node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp")
@ -26,7 +27,7 @@ class EnumEvolveTests {
@Test @Test
fun deserialiseNewerSetToUnknown() { fun deserialiseNewerSetToUnknown() {
val resource = "${this.javaClass.simpleName}.${testName()}" val resource = "${javaClass.simpleName}.${testName()}"
val sf = testDefaultFactory() val sf = testDefaultFactory()
data class C (val e : DeserializeNewerSetToUnknown) data class C (val e : DeserializeNewerSetToUnknown)
@ -35,6 +36,376 @@ class EnumEvolveTests {
// File(URI("$localPath/$resource")).writeBytes( // File(URI("$localPath/$resource")).writeBytes(
// SerializationOutput(sf).serialize(C(DeserializeNewerSetToUnknown.D)).bytes) // SerializationOutput(sf).serialize(C(DeserializeNewerSetToUnknown.D)).bytes)
val path = EvolvabilityTests::class.java.getResource(resource)
val obj = DeserializationInput(sf).deserialize(SerializedBytes<C>(File(path.toURI()).readBytes()))
assertEquals (DeserializeNewerSetToUnknown.C, obj.e)
}
// Version of the class as it was serialised
//
// @CordaSerializationTransformEnumDefaults (
// CordaSerializationTransformEnumDefault("D", "C"),
// CordaSerializationTransformEnumDefault("E", "D"))
// enum class DeserializeNewerSetToUnknown2 { A, B, C, D, E }
//
// Version of the class as it's used in the test
enum class DeserializeNewerSetToUnknown2 { A, B, C }
@Test
fun deserialiseNewerSetToUnknown2() {
val resource = "${javaClass.simpleName}.${testName()}"
val sf = testDefaultFactory()
data class C(val e: DeserializeNewerSetToUnknown2)
// Uncomment to re-generate test files
// val so = SerializationOutput(sf)
// File(URI("$localPath/$resource.C")).writeBytes(so.serialize(C(DeserializeNewerSetToUnknown2.C)).bytes)
// File(URI("$localPath/$resource.D")).writeBytes(so.serialize(C(DeserializeNewerSetToUnknown2.D)).bytes)
// File(URI("$localPath/$resource.E")).writeBytes(so.serialize(C(DeserializeNewerSetToUnknown2.E)).bytes)
val path1 = EvolvabilityTests::class.java.getResource("$resource.C")
val path2 = EvolvabilityTests::class.java.getResource("$resource.D")
val path3 = EvolvabilityTests::class.java.getResource("$resource.E")
// C will just work
val obj1 = DeserializationInput(sf).deserialize(SerializedBytes<C>(File(path1.toURI()).readBytes()))
// D will transform directly to C
val obj2 = DeserializationInput(sf).deserialize(SerializedBytes<C>(File(path2.toURI()).readBytes()))
// E will have to transform from E -> D -> C to work, so this should exercise that part
// of the evolution code
val obj3 = DeserializationInput(sf).deserialize(SerializedBytes<C>(File(path3.toURI()).readBytes()))
assertEquals (DeserializeNewerSetToUnknown2.C, obj1.e)
assertEquals (DeserializeNewerSetToUnknown2.C, obj2.e)
assertEquals (DeserializeNewerSetToUnknown2.C, obj3.e)
}
// Version of the class as it was serialised, evolve rule purposfuly not included to
// test failure conditions
//
// enum class DeserializeNewerWithNoRule { A, B, C, D }
//
// Class as it exists for the test
enum class DeserializeNewerWithNoRule { A, B, C }
// Lets test to see if they forgot to provide an upgrade rule
@Test
fun deserialiseNewerWithNoRule() {
val resource = "${javaClass.simpleName}.${testName()}"
val sf = testDefaultFactory()
data class C(val e: DeserializeNewerWithNoRule)
// Uncomment to re-generate test files
// val so = SerializationOutput(sf)
// File(URI("$localPath/$resource")).writeBytes(so.serialize(C(DeserializeNewerWithNoRule.D)).bytes)
val path = EvolvabilityTests::class.java.getResource(resource)
Assertions.assertThatThrownBy {
DeserializationInput(sf).deserialize(SerializedBytes<C>(File(path.toURI()).readBytes()))
}.isInstanceOf(NotSerializableException::class.java)
}
// Version of class as it was serialized, at some point in the "future" several
// values have been renamed
//
// First Change
// A -> AA
// @CordaSerializationTransformRenames (
// CordaSerializationTransformRename(from ="A", to = "AA")
// )
// enum class DeserializeWithRename { AA, B, C }
//
// Second Change
// B -> BB
// @CordaSerializationTransformRenames (
// CordaSerializationTransformRename(from = "B", to = "BB"),
// CordaSerializationTransformRename(from = "A", to = "AA")
// )
// enum class DeserializeWithRename { AA, BB, C }
//
// Third Change
// BB -> XX
// @CordaSerializationTransformRenames (
// CordaSerializationTransformRename(from = "B", to = "BB"),
// CordaSerializationTransformRename(from = "BB", to = "XX"),
// CordaSerializationTransformRename(from = "A", to = "AA")
// )
// enum class DeserializeWithRename { AA, XX, C }
//
// Finally, the version we're using to test with
enum class DeserializeWithRename { A, B, C }
@Test
fun deserializeWithRename() {
val resource = "${javaClass.simpleName}.${testName()}"
val sf = testDefaultFactory()
data class C(val e: DeserializeWithRename)
// Uncomment to re-generate test files, needs to be done in three stages
val so = SerializationOutput(sf)
// First change
// File(URI("$localPath/$resource.1.AA")).writeBytes(so.serialize(C(DeserializeWithRename.AA)).bytes)
// File(URI("$localPath/$resource.1.B")).writeBytes(so.serialize(C(DeserializeWithRename.B)).bytes)
// File(URI("$localPath/$resource.1.C")).writeBytes(so.serialize(C(DeserializeWithRename.C)).bytes)
// Second change
// File(URI("$localPath/$resource.2.AA")).writeBytes(so.serialize(C(DeserializeWithRename.AA)).bytes)
// File(URI("$localPath/$resource.2.BB")).writeBytes(so.serialize(C(DeserializeWithRename.BB)).bytes)
// File(URI("$localPath/$resource.2.C")).writeBytes(so.serialize(C(DeserializeWithRename.C)).bytes)
// Third change
// File(URI("$localPath/$resource.3.AA")).writeBytes(so.serialize(C(DeserializeWithRename.AA)).bytes)
// File(URI("$localPath/$resource.3.XX")).writeBytes(so.serialize(C(DeserializeWithRename.XX)).bytes)
// File(URI("$localPath/$resource.3.C")).writeBytes(so.serialize(C(DeserializeWithRename.C)).bytes)
//
// Test we can deserialize instances of the class after its first transformation
//
val path1_AA = EvolvabilityTests::class.java.getResource("$resource.1.AA")
val path1_B = EvolvabilityTests::class.java.getResource("$resource.1.B")
val path1_C = EvolvabilityTests::class.java.getResource("$resource.1.C")
val obj1_AA = DeserializationInput(sf).deserialize(SerializedBytes<C>(File(path1_AA.toURI()).readBytes()))
val obj1_B = DeserializationInput(sf).deserialize(SerializedBytes<C>(File(path1_B.toURI()).readBytes()))
val obj1_C = DeserializationInput(sf).deserialize(SerializedBytes<C>(File(path1_C.toURI()).readBytes()))
assertEquals(DeserializeWithRename.A, obj1_AA.e)
assertEquals(DeserializeWithRename.B, obj1_B.e)
assertEquals(DeserializeWithRename.C, obj1_C.e)
//
// Test we can deserialize instances of the class after its second transformation
//
val path2_AA = EvolvabilityTests::class.java.getResource("$resource.2.AA")
val path2_BB = EvolvabilityTests::class.java.getResource("$resource.2.BB")
val path2_C = EvolvabilityTests::class.java.getResource("$resource.2.C")
val obj2_AA = DeserializationInput(sf).deserialize(SerializedBytes<C>(File(path2_AA.toURI()).readBytes()))
val obj2_BB = DeserializationInput(sf).deserialize(SerializedBytes<C>(File(path2_BB.toURI()).readBytes()))
val obj2_C = DeserializationInput(sf).deserialize(SerializedBytes<C>(File(path2_C.toURI()).readBytes()))
assertEquals(DeserializeWithRename.A, obj2_AA.e)
assertEquals(DeserializeWithRename.B, obj2_BB.e)
assertEquals(DeserializeWithRename.C, obj2_C.e)
//
// Test we can deserialize instances of the class after its third transformation
//
val path3_AA = EvolvabilityTests::class.java.getResource("$resource.3.AA")
val path3_XX = EvolvabilityTests::class.java.getResource("$resource.3.XX")
val path3_C = EvolvabilityTests::class.java.getResource("$resource.3.C")
val obj3_AA = DeserializationInput(sf).deserialize(SerializedBytes<C>(File(path3_AA.toURI()).readBytes()))
val obj3_XX = DeserializationInput(sf).deserialize(SerializedBytes<C>(File(path3_XX.toURI()).readBytes()))
val obj3_C = DeserializationInput(sf).deserialize(SerializedBytes<C>(File(path3_C.toURI()).readBytes()))
assertEquals(DeserializeWithRename.A, obj3_AA.e)
assertEquals(DeserializeWithRename.B, obj3_XX.e)
assertEquals(DeserializeWithRename.C, obj3_C.e)
}
// The origional version of the enum, what we'll be eventually deserialising into
// enum class MultiOperations { A, B, C }
//
// First alteration, add D
// @CordaSerializationTransformEnumDefault(old = "C", new = "D")
// enum class MultiOperations { A, B, C, D }
//
// Second, add E
// @CordaSerializationTransformEnumDefaults(
// CordaSerializationTransformEnumDefault(old = "C", new = "D"),
// CordaSerializationTransformEnumDefault(old = "D", new = "E")
// )
// enum class MultiOperations { A, B, C, D, E }
//
// Third, Rename E to BOB
// @CordaSerializationTransformEnumDefaults(
// CordaSerializationTransformEnumDefault(old = "C", new = "D"),
// CordaSerializationTransformEnumDefault(old = "D", new = "E")
// )
// @CordaSerializationTransformRename(to = "BOB", from = "E")
// enum class MultiOperations { A, B, C, D, BOB }
//
// Fourth, Rename C to CAT, ADD F and G
// @CordaSerializationTransformEnumDefaults(
// CordaSerializationTransformEnumDefault(old = "F", new = "G"),
// CordaSerializationTransformEnumDefault(old = "BOB", new = "F"),
// CordaSerializationTransformEnumDefault(old = "D", new = "E"),
// CordaSerializationTransformEnumDefault(old = "C", new = "D")
// )
// @CordaSerializationTransformRenames (
// CordaSerializationTransformRename(to = "CAT", from = "C"),
// CordaSerializationTransformRename(to = "BOB", from = "E")
// )
// enum class MultiOperations { A, B, CAT, D, BOB, F, G}
//
// Fifth, Rename F to FLUMP, Rename BOB to BBB, Rename A to APPLE
// @CordaSerializationTransformEnumDefaults(
// CordaSerializationTransformEnumDefault(old = "F", new = "G"),
// CordaSerializationTransformEnumDefault(old = "BOB", new = "F"),
// CordaSerializationTransformEnumDefault(old = "D", new = "E"),
// CordaSerializationTransformEnumDefault(old = "C", new = "D")
// )
// @CordaSerializationTransformRenames (
// CordaSerializationTransformRename(to = "APPLE", from = "A"),
// CordaSerializationTransformRename(to = "BBB", from = "BOB"),
// CordaSerializationTransformRename(to = "FLUMP", from = "F"),
// CordaSerializationTransformRename(to = "CAT", from = "C"),
// CordaSerializationTransformRename(to = "BOB", from = "E")
// )
// enum class MultiOperations { APPLE, B, CAT, D, BBB, FLUMP, G}
//
// Finally, the original version of teh class that we're going to be testing with
enum class MultiOperations { A, B, C }
@Test
fun multiOperations() {
val resource = "${javaClass.simpleName}.${testName()}"
val sf = testDefaultFactory()
data class C(val e: MultiOperations)
// Uncomment to re-generate test files, needs to be done in three stages
val so = SerializationOutput(sf)
// First change
// File(URI("$localPath/$resource.1.A")).writeBytes(so.serialize(C(MultiOperations.A)).bytes)
// File(URI("$localPath/$resource.1.B")).writeBytes(so.serialize(C(MultiOperations.B)).bytes)
// File(URI("$localPath/$resource.1.C")).writeBytes(so.serialize(C(MultiOperations.C)).bytes)
// File(URI("$localPath/$resource.1.D")).writeBytes(so.serialize(C(MultiOperations.D)).bytes)
// Second change
// File(URI("$localPath/$resource.2.A")).writeBytes(so.serialize(C(MultiOperations.A)).bytes)
// File(URI("$localPath/$resource.2.B")).writeBytes(so.serialize(C(MultiOperations.B)).bytes)
// File(URI("$localPath/$resource.2.C")).writeBytes(so.serialize(C(MultiOperations.C)).bytes)
// File(URI("$localPath/$resource.2.D")).writeBytes(so.serialize(C(MultiOperations.D)).bytes)
// File(URI("$localPath/$resource.2.E")).writeBytes(so.serialize(C(MultiOperations.E)).bytes)
// Third change
// File(URI("$localPath/$resource.3.A")).writeBytes(so.serialize(C(MultiOperations.A)).bytes)
// File(URI("$localPath/$resource.3.B")).writeBytes(so.serialize(C(MultiOperations.B)).bytes)
// File(URI("$localPath/$resource.3.C")).writeBytes(so.serialize(C(MultiOperations.C)).bytes)
// File(URI("$localPath/$resource.3.D")).writeBytes(so.serialize(C(MultiOperations.D)).bytes)
// File(URI("$localPath/$resource.3.BOB")).writeBytes(so.serialize(C(MultiOperations.BOB)).bytes)
// Fourth change
// File(URI("$localPath/$resource.4.A")).writeBytes(so.serialize(C(MultiOperations.A)).bytes)
// File(URI("$localPath/$resource.4.B")).writeBytes(so.serialize(C(MultiOperations.B)).bytes)
// File(URI("$localPath/$resource.4.CAT")).writeBytes(so.serialize(C(MultiOperations.CAT)).bytes)
// File(URI("$localPath/$resource.4.D")).writeBytes(so.serialize(C(MultiOperations.D)).bytes)
// File(URI("$localPath/$resource.4.BOB")).writeBytes(so.serialize(C(MultiOperations.BOB)).bytes)
// File(URI("$localPath/$resource.4.F")).writeBytes(so.serialize(C(MultiOperations.F)).bytes)
// File(URI("$localPath/$resource.4.G")).writeBytes(so.serialize(C(MultiOperations.G)).bytes)
// Fifth change - { APPLE, B, CAT, D, BBB, FLUMP, G}
// File(URI("$localPath/$resource.5.APPLE")).writeBytes(so.serialize(C(MultiOperations.APPLE)).bytes)
// File(URI("$localPath/$resource.5.B")).writeBytes(so.serialize(C(MultiOperations.B)).bytes)
// File(URI("$localPath/$resource.5.CAT")).writeBytes(so.serialize(C(MultiOperations.CAT)).bytes)
// File(URI("$localPath/$resource.5.D")).writeBytes(so.serialize(C(MultiOperations.D)).bytes)
// File(URI("$localPath/$resource.5.BBB")).writeBytes(so.serialize(C(MultiOperations.BBB)).bytes)
// File(URI("$localPath/$resource.5.FLUMP")).writeBytes(so.serialize(C(MultiOperations.FLUMP)).bytes)
// File(URI("$localPath/$resource.5.G")).writeBytes(so.serialize(C(MultiOperations.G)).bytes)
val stage1Resources = listOf(
Pair("$resource.1.A", MultiOperations.A),
Pair("$resource.1.B", MultiOperations.B),
Pair("$resource.1.C", MultiOperations.C),
Pair("$resource.1.D", MultiOperations.C))
val stage2Resources = listOf(
Pair("$resource.2.A", MultiOperations.A),
Pair("$resource.2.B", MultiOperations.B),
Pair("$resource.2.C", MultiOperations.C),
Pair("$resource.2.D", MultiOperations.C),
Pair("$resource.2.E", MultiOperations.C))
val stage3Resources = listOf(
Pair("$resource.3.A", MultiOperations.A),
Pair("$resource.3.B", MultiOperations.B),
Pair("$resource.3.C", MultiOperations.C),
Pair("$resource.3.D", MultiOperations.C),
Pair("$resource.3.BOB", MultiOperations.C))
val stage4Resources = listOf(
Pair("$resource.4.A", MultiOperations.A),
Pair("$resource.4.B", MultiOperations.B),
Pair("$resource.4.CAT", MultiOperations.C),
Pair("$resource.4.D", MultiOperations.C),
Pair("$resource.4.BOB", MultiOperations.C),
Pair("$resource.4.F", MultiOperations.C),
Pair("$resource.4.G", MultiOperations.C))
val stage5Resources = listOf(
Pair("$resource.5.APPLE", MultiOperations.A),
Pair("$resource.5.B", MultiOperations.B),
Pair("$resource.5.CAT", MultiOperations.C),
Pair("$resource.5.D", MultiOperations.C),
Pair("$resource.5.BBB", MultiOperations.C),
Pair("$resource.5.FLUMP", MultiOperations.C),
Pair("$resource.5.G", MultiOperations.C))
fun load(l: List<Pair<String, MultiOperations>>) = l.map {
Pair (DeserializationInput(sf).deserialize(SerializedBytes<C>(
File(EvolvabilityTests::class.java.getResource(it.first).toURI()).readBytes())), it.second)
}
load (stage1Resources).forEach { assertEquals(it.second, it.first.e) }
load (stage2Resources).forEach { assertEquals(it.second, it.first.e) }
load (stage3Resources).forEach { assertEquals(it.second, it.first.e) }
load (stage4Resources).forEach { assertEquals(it.second, it.first.e) }
load (stage5Resources).forEach { assertEquals(it.second, it.first.e) }
}
@CordaSerializationTransformEnumDefault(old = "A", new = "F")
enum class BadNewValue { A, B, C, D }
@Test
fun badNewValue() {
val sf = testDefaultFactory()
data class C (val e : BadNewValue)
Assertions.assertThatThrownBy {
SerializationOutput(sf).serialize(C(BadNewValue.A))
}.isInstanceOf(NotSerializableException::class.java)
}
@CordaSerializationTransformEnumDefaults(
CordaSerializationTransformEnumDefault(new = "D", old = "E"),
CordaSerializationTransformEnumDefault(new = "E", old = "A")
)
enum class OutOfOrder { A, B, C, D, E}
@Test
fun outOfOrder() {
val sf = testDefaultFactory()
data class C (val e : OutOfOrder)
Assertions.assertThatThrownBy {
SerializationOutput(sf).serialize(C(OutOfOrder.A))
}.isInstanceOf(NotSerializableException::class.java)
}
// class as it existed as it was serialized
//
// enum class ChangedOrdinality { A, B, C }
//
// class as it exists for the tests
@CordaSerializationTransformEnumDefault("D", "A")
enum class ChangedOrdinality { A, B, D, C }
@Test
fun changedOrdinality() {
val resource = "${javaClass.simpleName}.${testName()}"
val sf = testDefaultFactory()
data class C(val e: ChangedOrdinality)
// Uncomment to re-generate test files, needs to be done in three stages
// File(URI("$localPath/$resource")).writeBytes(
// SerializationOutput(sf).serialize(C(ChangedOrdinality.A)).bytes)
Assertions.assertThatThrownBy { Assertions.assertThatThrownBy {
DeserializationInput(sf).deserialize(SerializedBytes<C>( DeserializationInput(sf).deserialize(SerializedBytes<C>(
File(EvolvabilityTests::class.java.getResource(resource).toURI()).readBytes())) File(EvolvabilityTests::class.java.getResource(resource).toURI()).readBytes()))

View File

@ -2,6 +2,7 @@ package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.serialization.DeprecatedConstructorForDeserialization import net.corda.core.serialization.DeprecatedConstructorForDeserialization
import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.SerializedBytes
import net.corda.testing.common.internal.ProjectStructure.projectRootDir
import org.junit.Test import org.junit.Test
import java.io.File import java.io.File
import java.io.NotSerializableException import java.io.NotSerializableException
@ -18,7 +19,8 @@ import kotlin.test.assertEquals
// 5. Comment back out the generation code and uncomment the actual test // 5. Comment back out the generation code and uncomment the actual test
class EvolvabilityTests { class EvolvabilityTests {
// When regenerating the test files this needs to be set to the file system location of the resource files // When regenerating the test files this needs to be set to the file system location of the resource files
var localPath = "file:///<path>/<to>/<toplevel of>/corda/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp" var localPath = projectRootDir.toUri().resolve(
"node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp")
@Test @Test
fun simpleOrderSwapSameType() { fun simpleOrderSwapSameType() {

View File

@ -0,0 +1,63 @@
package net.corda.node
import net.corda.core.crypto.Crypto
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.cert
import net.corda.core.internal.div
import net.corda.core.utilities.getOrThrow
import net.corda.node.services.config.configureDevKeyAndTrustStores
import net.corda.nodeapi.config.SSLConfiguration
import net.corda.nodeapi.internal.crypto.*
import net.corda.testing.ALICE_NAME
import net.corda.testing.driver.driver
import org.junit.Test
import java.nio.file.Path
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
class NodeKeystoreCheckTest {
@Test
fun `node should throw exception if cert path doesn't chain to the trust root`() {
driver(startNodesInProcess = true) {
// This will fail because there are no keystore configured.
assertFailsWith(IllegalArgumentException::class) {
startNode(customOverrides = mapOf("devMode" to false)).getOrThrow()
}.apply {
assertTrue(message?.startsWith("Identity certificate not found. ") ?: false)
}
// Create keystores
val keystorePassword = "password"
val config = object : SSLConfiguration {
override val keyStorePassword: String = keystorePassword
override val trustStorePassword: String = keystorePassword
override val certificatesDirectory: Path = baseDirectory(ALICE_NAME.toString()) / "certificates"
}
config.configureDevKeyAndTrustStores(ALICE_NAME)
// This should pass with correct keystore.
val node = startNode(providedName = ALICE_NAME, customOverrides = mapOf("devMode" to false,
"keyStorePassword" to keystorePassword,
"trustStorePassword" to keystorePassword)).get()
node.stop()
// Fiddle with node keystore.
val keystore = loadKeyStore(config.nodeKeystore, config.keyStorePassword)
// Self signed root
val badRootKeyPair = Crypto.generateKeyPair()
val badRoot = X509Utilities.createSelfSignedCACertificate(CordaX500Name("Bad Root", "Lodnon", "GB"), badRootKeyPair)
val nodeCA = keystore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA, config.keyStorePassword)
val badNodeCACert = X509Utilities.createCertificate(CertificateType.CLIENT_CA, badRoot, badRootKeyPair, ALICE_NAME, nodeCA.keyPair.public)
keystore.setKeyEntry(X509Utilities.CORDA_CLIENT_CA, nodeCA.keyPair.private, config.keyStorePassword.toCharArray(), arrayOf(badNodeCACert.cert, badRoot.cert))
keystore.save(config.nodeKeystore, config.keyStorePassword)
assertFailsWith(IllegalArgumentException::class) {
startNode(providedName = ALICE_NAME, customOverrides = mapOf("devMode" to false)).getOrThrow()
}.apply {
assertEquals("Client CA certificate must chain to the trusted root.", message)
}
}
}
}

View File

@ -17,11 +17,12 @@ import net.corda.nodeapi.internal.config.User
import net.corda.testing.DUMMY_NOTARY import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.NodeHandle
import net.corda.testing.driver.driver import net.corda.testing.driver.driver
import net.corda.testing.node.NotarySpec import net.corda.testing.internal.InternalDriverDSL
import net.corda.testing.internal.performance.div import net.corda.testing.internal.performance.div
import net.corda.testing.internal.performance.startPublishingFixedRateInjector import net.corda.testing.internal.performance.startPublishingFixedRateInjector
import net.corda.testing.internal.performance.startReporter import net.corda.testing.internal.performance.startReporter
import net.corda.testing.internal.performance.startTightLoopInjector import net.corda.testing.internal.performance.startTightLoopInjector
import net.corda.testing.node.NotarySpec
import org.junit.Before import org.junit.Before
import org.junit.Ignore import org.junit.Ignore
import org.junit.Test import org.junit.Test
@ -91,7 +92,7 @@ class NodePerformanceTests {
driver(startNodesInProcess = true) { driver(startNodesInProcess = true) {
val a = startNode(rpcUsers = listOf(User("A", "A", setOf(startFlow<EmptyFlow>())))).get() val a = startNode(rpcUsers = listOf(User("A", "A", setOf(startFlow<EmptyFlow>())))).get()
a as NodeHandle.InProcess a as NodeHandle.InProcess
val metricRegistry = startReporter(shutdownManager, a.node.services.monitoringService.metrics) val metricRegistry = startReporter((this as InternalDriverDSL).shutdownManager, a.node.services.monitoringService.metrics)
a.rpcClientToNode().use("A", "A") { connection -> a.rpcClientToNode().use("A", "A") { connection ->
startPublishingFixedRateInjector(metricRegistry, 8, 5.minutes, 2000L / TimeUnit.SECONDS) { startPublishingFixedRateInjector(metricRegistry, 8, 5.minutes, 2000L / TimeUnit.SECONDS) {
connection.proxy.startFlow(::EmptyFlow).returnValue.get() connection.proxy.startFlow(::EmptyFlow).returnValue.get()
@ -109,7 +110,7 @@ class NodePerformanceTests {
extraCordappPackagesToScan = listOf("net.corda.finance") extraCordappPackagesToScan = listOf("net.corda.finance")
) { ) {
val notary = defaultNotaryNode.getOrThrow() as NodeHandle.InProcess val notary = defaultNotaryNode.getOrThrow() as NodeHandle.InProcess
val metricRegistry = startReporter(shutdownManager, notary.node.services.monitoringService.metrics) val metricRegistry = startReporter((this as InternalDriverDSL).shutdownManager, notary.node.services.monitoringService.metrics)
notary.rpcClientToNode().use("A", "A") { connection -> notary.rpcClientToNode().use("A", "A") { connection ->
println("ISSUING") println("ISSUING")
val doneFutures = (1..100).toList().parallelStream().map { val doneFutures = (1..100).toList().parallelStream().map {

View File

@ -1,8 +1,9 @@
package net.corda.node.services package net.corda.node.services
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.contracts.Contract import net.corda.core.contracts.Contract
import net.corda.core.contracts.PartyAndReference import net.corda.core.contracts.PartyAndReference
import net.corda.core.cordapp.CordappProvider
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.flows.UnexpectedFlowEndException import net.corda.core.flows.UnexpectedFlowEndException
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
@ -11,34 +12,34 @@ import net.corda.core.internal.concurrent.transpose
import net.corda.core.internal.createDirectories import net.corda.core.internal.createDirectories
import net.corda.core.internal.div import net.corda.core.internal.div
import net.corda.core.internal.toLedgerTransaction import net.corda.core.internal.toLedgerTransaction
import net.corda.core.node.ServicesForResolution
import net.corda.core.node.services.IdentityService
import net.corda.core.serialization.SerializationFactory import net.corda.core.serialization.SerializationFactory
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.node.internal.cordapp.CordappLoader import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.internal.cordapp.CordappProviderImpl import net.corda.node.internal.cordapp.CordappProviderImpl
import net.corda.testing.*
import net.corda.testing.DUMMY_BANK_A import net.corda.testing.DUMMY_BANK_A
import net.corda.testing.DUMMY_NOTARY import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.driver.DriverDSL import net.corda.testing.driver.DriverDSL
import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.NodeHandle
import net.corda.testing.driver.driver import net.corda.testing.driver.driver
import net.corda.testing.node.MockServices import net.corda.testing.node.MockAttachmentStorage
import net.corda.testing.rigorousMock
import net.corda.testing.withTestSerialization
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test import org.junit.Test
import java.net.URLClassLoader import java.net.URLClassLoader
import java.nio.file.Files import java.nio.file.Files
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
class AttachmentLoadingTests { class AttachmentLoadingTests {
private class Services : MockServices() { private val attachments = MockAttachmentStorage()
private val provider = CordappProviderImpl(CordappLoader.createDevMode(listOf(isolatedJAR)), attachments) private val provider = CordappProviderImpl(CordappLoader.createDevMode(listOf(isolatedJAR)), attachments)
private val cordapp get() = provider.cordapps.first() private val cordapp get() = provider.cordapps.first()
val attachmentId get() = provider.getCordappAttachmentId(cordapp)!! private val attachmentId get() = provider.getCordappAttachmentId(cordapp)!!
val appContext get() = provider.getAppContext(cordapp) private val appContext get() = provider.getAppContext(cordapp)
override val cordappProvider: CordappProvider = provider
}
private companion object { private companion object {
private val logger = contextLogger() private val logger = contextLogger()
@ -60,7 +61,7 @@ class AttachmentLoadingTests {
private fun DriverDSL.installIsolatedCordappTo(nodeName: CordaX500Name) { private fun DriverDSL.installIsolatedCordappTo(nodeName: CordaX500Name) {
// Copy the app jar to the first node. The second won't have it. // Copy the app jar to the first node. The second won't have it.
val path = (baseDirectory(nodeName.toString()) / "cordapps").createDirectories() / "isolated.jar" val path = (baseDirectory(nodeName) / "cordapps").createDirectories() / "isolated.jar"
logger.info("Installing isolated jar to $path") logger.info("Installing isolated jar to $path")
isolatedJAR.openStream().buffered().use { input -> isolatedJAR.openStream().buffered().use { input ->
Files.newOutputStream(path).buffered().use { output -> Files.newOutputStream(path).buffered().use { output ->
@ -70,16 +71,17 @@ class AttachmentLoadingTests {
} }
} }
private lateinit var services: Services private val services = rigorousMock<ServicesForResolution>().also {
doReturn(attachments).whenever(it).attachments
@Before doReturn(provider).whenever(it).cordappProvider
fun setup() { doReturn(rigorousMock<IdentityService>().also {
services = Services() doReturn(null).whenever(it).partyFromKey(DUMMY_BANK_A.owningKey)
}).whenever(it).identityService
} }
@Test @Test
fun `test a wire transaction has loaded the correct attachment`() = withTestSerialization { fun `test a wire transaction has loaded the correct attachment`() = withTestSerialization {
val appClassLoader = services.appContext.classLoader val appClassLoader = appContext.classLoader
val contractClass = appClassLoader.loadClass(ISOLATED_CONTRACT_ID).asSubclass(Contract::class.java) val contractClass = appClassLoader.loadClass(ISOLATED_CONTRACT_ID).asSubclass(Contract::class.java)
val generateInitialMethod = contractClass.getDeclaredMethod("generateInitial", PartyAndReference::class.java, Integer.TYPE, Party::class.java) val generateInitialMethod = contractClass.getDeclaredMethod("generateInitial", PartyAndReference::class.java, Integer.TYPE, Party::class.java)
val contract = contractClass.newInstance() val contract = contractClass.newInstance()
@ -89,8 +91,7 @@ class AttachmentLoadingTests {
contract.verify(ledgerTx) contract.verify(ledgerTx)
val actual = ledgerTx.attachments.first() val actual = ledgerTx.attachments.first()
val expected = services.attachments.openAttachment(services.attachmentId)!! val expected = attachments.openAttachment(attachmentId)!!
assertEquals(expected, actual) assertEquals(expected, actual)
} }

View File

@ -13,6 +13,7 @@ import net.corda.node.services.identity.InMemoryIdentityService
import net.corda.nodeapi.internal.NodeInfoFilesCopier import net.corda.nodeapi.internal.NodeInfoFilesCopier
import net.corda.testing.* import net.corda.testing.*
import net.corda.testing.node.MockKeyManagementService import net.corda.testing.node.MockKeyManagementService
import net.corda.testing.node.makeTestIdentityService
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.contentOf import org.assertj.core.api.Assertions.contentOf
import org.junit.Before import org.junit.Before
@ -47,7 +48,7 @@ class NodeInfoWatcherTest {
@Before @Before
fun start() { fun start() {
val identityService = InMemoryIdentityService(trustRoot = DEV_TRUST_ROOT) val identityService = makeTestIdentityService()
keyManagementService = MockKeyManagementService(identityService, ALICE_KEY) keyManagementService = MockKeyManagementService(identityService, ALICE_KEY)
nodeInfoWatcher = NodeInfoWatcher(tempFolder.root.toPath(), scheduler) nodeInfoWatcher = NodeInfoWatcher(tempFolder.root.toPath(), scheduler)
nodeInfoPath = tempFolder.root.toPath() / CordformNode.NODE_INFO_DIRECTORY nodeInfoPath = tempFolder.root.toPath() / CordformNode.NODE_INFO_DIRECTORY

View File

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

View File

@ -43,6 +43,7 @@ import net.corda.node.services.config.NotaryConfig
import net.corda.node.services.config.configureWithDevSSLCertificate import net.corda.node.services.config.configureWithDevSSLCertificate
import net.corda.node.services.events.NodeSchedulerService import net.corda.node.services.events.NodeSchedulerService
import net.corda.node.services.events.ScheduledActivityObserver import net.corda.node.services.events.ScheduledActivityObserver
import net.corda.node.services.api.IdentityServiceInternal
import net.corda.node.services.identity.PersistentIdentityService import net.corda.node.services.identity.PersistentIdentityService
import net.corda.node.services.keys.PersistentKeyManagementService import net.corda.node.services.keys.PersistentKeyManagementService
import net.corda.node.services.messaging.MessagingService import net.corda.node.services.messaging.MessagingService
@ -179,28 +180,34 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
check(started == null) { "Node has already been started" } check(started == null) { "Node has already been started" }
log.info("Generating nodeInfo ...") log.info("Generating nodeInfo ...")
initCertificate() initCertificate()
val (keyPairs, info) = initNodeInfo() val schemaService = NodeSchemaService(cordappLoader.cordappSchemas)
val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null)
initialiseDatabasePersistence(schemaService, makeIdentityService(identity.certificate)) { database ->
val persistentNetworkMapCache = PersistentNetworkMapCache(database)
val (keyPairs, info) = initNodeInfo(persistentNetworkMapCache, identity, identityKeyPair)
val identityKeypair = keyPairs.first { it.public == info.legalIdentities.first().owningKey } val identityKeypair = keyPairs.first { it.public == info.legalIdentities.first().owningKey }
val serialisedNodeInfo = info.serialize() val serialisedNodeInfo = info.serialize()
val signature = identityKeypair.sign(serialisedNodeInfo) val signature = identityKeypair.sign(serialisedNodeInfo)
// TODO: Signed data might not be sufficient for multiple identities, as it only contains one signature. // TODO: Signed data might not be sufficient for multiple identities, as it only contains one signature.
NodeInfoWatcher.saveToFile(configuration.baseDirectory, SignedData(serialisedNodeInfo, signature)) NodeInfoWatcher.saveToFile(configuration.baseDirectory, SignedData(serialisedNodeInfo, signature))
} }
}
open fun start(): StartedNode<AbstractNode> { open fun start(): StartedNode<AbstractNode> {
check(started == null) { "Node has already been started" } check(started == null) { "Node has already been started" }
log.info("Node starting up ...") log.info("Node starting up ...")
initCertificate() initCertificate()
val (keyPairs, info) = initNodeInfo()
readNetworkParameters() readNetworkParameters()
val schemaService = NodeSchemaService(cordappLoader.cordappSchemas) val schemaService = NodeSchemaService(cordappLoader.cordappSchemas)
val identityService = makeIdentityService(info) val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null)
val identityService = makeIdentityService(identity.certificate)
// Do all of this in a database transaction so anything that might need a connection has one. // Do all of this in a database transaction so anything that might need a connection has one.
val (startedImpl, schedulerService) = initialiseDatabasePersistence(schemaService, identityService) { database -> val (startedImpl, schedulerService) = initialiseDatabasePersistence(schemaService, identityService) { database ->
val networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database), identityService)
val (keyPairs, info) = initNodeInfo(networkMapCache, identity, identityKeyPair)
identityService.loadIdentities(info.legalIdentitiesAndCerts) identityService.loadIdentities(info.legalIdentitiesAndCerts)
val transactionStorage = makeTransactionStorage(database) val transactionStorage = makeTransactionStorage(database)
val stateLoader = StateLoaderImpl(transactionStorage) val nodeServices = makeServices(keyPairs, schemaService, transactionStorage, database, info, identityService, networkMapCache)
val nodeServices = makeServices(keyPairs, schemaService, transactionStorage, stateLoader, database, info, identityService)
val notaryService = makeNotaryService(nodeServices, database) val notaryService = makeNotaryService(nodeServices, database)
val smm = makeStateMachineManager(database) val smm = makeStateMachineManager(database)
val flowStarter = FlowStarterImpl(serverThread, smm) val flowStarter = FlowStarterImpl(serverThread, smm)
@ -208,7 +215,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
platformClock, platformClock,
database, database,
flowStarter, flowStarter,
stateLoader, transactionStorage,
unfinishedSchedules = busyNodeLatch, unfinishedSchedules = busyNodeLatch,
serverThread = serverThread) serverThread = serverThread)
if (serverThread is ExecutorService) { if (serverThread is ExecutorService) {
@ -245,7 +252,6 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
networkMapUpdater.subscribeToNetworkMap() networkMapUpdater.subscribeToNetworkMap()
// If we successfully loaded network data from database, we set this future to Unit. // If we successfully loaded network data from database, we set this future to Unit.
services.networkMapCache.addNode(info)
_nodeReadyFuture.captureLater(services.networkMapCache.nodeReady.map { Unit }) _nodeReadyFuture.captureLater(services.networkMapCache.nodeReady.map { Unit })
return startedImpl.apply { return startedImpl.apply {
@ -269,8 +275,9 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
InteractiveShell.startShell(configuration, rpcOps, userService, _services.identityService, _services.database) InteractiveShell.startShell(configuration, rpcOps, userService, _services.identityService, _services.database)
} }
private fun initNodeInfo(): Pair<Set<KeyPair>, NodeInfo> { private fun initNodeInfo(networkMapCache: NetworkMapCacheBaseInternal,
val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null) identity: PartyAndCertificate,
identityKeyPair: KeyPair): Pair<Set<KeyPair>, NodeInfo> {
val keyPairs = mutableSetOf(identityKeyPair) val keyPairs = mutableSetOf(identityKeyPair)
myNotaryIdentity = configuration.notary?.let { myNotaryIdentity = configuration.notary?.let {
@ -283,12 +290,20 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
identity identity
} }
} }
val info = NodeInfo(
var info = NodeInfo(
myAddresses(), myAddresses(),
setOf(identity, myNotaryIdentity).filterNotNull(), setOf(identity, myNotaryIdentity).filterNotNull(),
versionInfo.platformVersion, versionInfo.platformVersion,
platformClock.instant().toEpochMilli() platformClock.instant().toEpochMilli()
) )
// Check if we have already stored a version of 'our own' NodeInfo, this is to avoid regenerating it with
// a different timestamp.
networkMapCache.getNodesByLegalName(myLegalName).firstOrNull()?.let {
if (info.copy(serial = it.serial) == it) {
info = it
}
}
return Pair(keyPairs, info) return Pair(keyPairs, info)
} }
@ -510,7 +525,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
* Builds node internal, advertised, and plugin services. * Builds node internal, advertised, and plugin services.
* Returns a list of tokenizable services to be added to the serialisation context. * Returns a list of tokenizable services to be added to the serialisation context.
*/ */
private fun makeServices(keyPairs: Set<KeyPair>, schemaService: SchemaService, transactionStorage: WritableTransactionStorage, stateLoader: StateLoader, database: CordaPersistence, info: NodeInfo, identityService: IdentityService): MutableList<Any> { private fun makeServices(keyPairs: Set<KeyPair>, schemaService: SchemaService, transactionStorage: WritableTransactionStorage, database: CordaPersistence, info: NodeInfo, identityService: IdentityServiceInternal, networkMapCache: NetworkMapCacheInternal): MutableList<Any> {
checkpointStorage = DBCheckpointStorage() checkpointStorage = DBCheckpointStorage()
val metrics = MetricRegistry() val metrics = MetricRegistry()
attachments = NodeAttachmentService(metrics) attachments = NodeAttachmentService(metrics)
@ -521,11 +536,11 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
keyManagementService, keyManagementService,
schemaService, schemaService,
transactionStorage, transactionStorage,
stateLoader,
MonitoringService(metrics), MonitoringService(metrics),
cordappProvider, cordappProvider,
database, database,
info) info,
networkMapCache)
network = makeMessagingService(database, info) network = makeMessagingService(database, info)
val tokenizableServices = mutableListOf(attachments, network, services.vaultService, val tokenizableServices = mutableListOf(attachments, network, services.vaultService,
services.keyManagementService, services.identityService, platformClock, services.keyManagementService, services.identityService, platformClock,
@ -564,6 +579,17 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
"or if you don't have one yet, fill out the config file and run corda.jar --initial-registration. " + "or if you don't have one yet, fill out the config file and run corda.jar --initial-registration. " +
"Read more at: https://docs.corda.net/permissioning.html" "Read more at: https://docs.corda.net/permissioning.html"
} }
// Check all cert path chain to the trusted root
val sslKeystore = loadKeyStore(configuration.sslKeystore, configuration.keyStorePassword)
val identitiesKeystore = loadKeyStore(configuration.nodeKeystore, configuration.keyStorePassword)
val trustStore = loadKeyStore(configuration.trustStoreFile, configuration.trustStorePassword)
val sslRoot = sslKeystore.getCertificateChain(X509Utilities.CORDA_CLIENT_TLS).last()
val clientCARoot = identitiesKeystore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA).last()
val trustRoot = trustStore.getCertificate(X509Utilities.CORDA_ROOT_CA)
require(sslRoot == trustRoot) { "TLS certificate must chain to the trusted root." }
require(clientCARoot == trustRoot) { "Client CA certificate must chain to the trusted root." }
} }
// Specific class so that MockNode can catch it. // Specific class so that MockNode can catch it.
@ -605,7 +631,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
} }
} }
protected open fun makeKeyManagementService(identityService: IdentityService, keyPairs: Set<KeyPair>): KeyManagementService { protected open fun makeKeyManagementService(identityService: IdentityServiceInternal, keyPairs: Set<KeyPair>): KeyManagementService {
return PersistentKeyManagementService(identityService, keyPairs) return PersistentKeyManagementService(identityService, keyPairs)
} }
@ -639,12 +665,12 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
} }
} }
private fun makeIdentityService(info: NodeInfo): PersistentIdentityService { private fun makeIdentityService(identityCert: X509Certificate): PersistentIdentityService {
val trustStore = KeyStoreWrapper(configuration.trustStoreFile, configuration.trustStorePassword) val trustStore = KeyStoreWrapper(configuration.trustStoreFile, configuration.trustStorePassword)
val caKeyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword) val caKeyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword)
val trustRoot = trustStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA) val trustRoot = trustStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA)
val clientCa = caKeyStore.certificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA) val clientCa = caKeyStore.certificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA)
val caCertificates = arrayOf(info.legalIdentitiesAndCerts[0].certificate, clientCa.certificate.cert) val caCertificates = arrayOf(identityCert, clientCa.certificate.cert)
return PersistentIdentityService(trustRoot, *caCertificates) return PersistentIdentityService(trustRoot, *caCertificates)
} }
@ -735,24 +761,17 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
override val keyManagementService: KeyManagementService, override val keyManagementService: KeyManagementService,
override val schemaService: SchemaService, override val schemaService: SchemaService,
override val validatedTransactions: WritableTransactionStorage, override val validatedTransactions: WritableTransactionStorage,
private val stateLoader: StateLoader,
override val monitoringService: MonitoringService, override val monitoringService: MonitoringService,
override val cordappProvider: CordappProviderInternal, override val cordappProvider: CordappProviderInternal,
override val database: CordaPersistence, override val database: CordaPersistence,
override val myInfo: NodeInfo override val myInfo: NodeInfo,
) : SingletonSerializeAsToken(), ServiceHubInternal, StateLoader by stateLoader { override val networkMapCache: NetworkMapCacheInternal
) : SingletonSerializeAsToken(), ServiceHubInternal, StateLoader by validatedTransactions {
override val rpcFlows = ArrayList<Class<out FlowLogic<*>>>() override val rpcFlows = ArrayList<Class<out FlowLogic<*>>>()
override val stateMachineRecordedTransactionMapping = DBTransactionMappingStorage() override val stateMachineRecordedTransactionMapping = DBTransactionMappingStorage()
override val auditService = DummyAuditService() override val auditService = DummyAuditService()
override val transactionVerifierService by lazy { makeTransactionVerifierService() } override val transactionVerifierService by lazy { makeTransactionVerifierService() }
override val networkMapCache by lazy { override val vaultService by lazy { makeVaultService(keyManagementService, validatedTransactions, database.hibernateConfig) }
NetworkMapCacheImpl(
PersistentNetworkMapCache(
database,
networkParameters.notaries),
identityService)
}
override val vaultService by lazy { makeVaultService(keyManagementService, stateLoader, database.hibernateConfig) }
override val contractUpgradeService by lazy { ContractUpgradeServiceImpl() } override val contractUpgradeService by lazy { ContractUpgradeServiceImpl() }
override val attachments: AttachmentStorage get() = this@AbstractNode.attachments override val attachments: AttachmentStorage get() = this@AbstractNode.attachments
override val networkService: MessagingService get() = network override val networkService: MessagingService get() = network

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