Merge pull request #1524 from corda/merges/os-mere-2018-10-31

OS -> ENT merge, resolved conflicts: README.md,  docs/source/api-persistence.rst, docs/source/node-database.rst.
This commit is contained in:
szymonsztuka 2018-11-01 10:31:59 +00:00 committed by GitHub
commit eaea6546fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 612 additions and 287 deletions

View File

@ -1,4 +1,6 @@
![Corda](https://www.corda.net/wp-content/uploads/2016/11/fg005_corda_b.png)
<p align="center">
<img src="https://www.corda.net/wp-content/uploads/2016/11/fg005_corda_b.png" alt="Corda" width="500">
</p>
<a href="https://ci-master.corda.r3cev.com/viewType.html?buildTypeId=CordaEnterprise_Build&tab=buildTypeStatusDiv"><img src="https://ci.corda.r3cev.com/app/rest/builds/buildType:Corda_CordaBuild/statusIcon"/></a>

View File

@ -1,20 +1,23 @@
package net.corda.client.rpc
import com.github.benmanes.caffeine.cache.Caffeine
import net.corda.client.rpc.internal.RPCClient
import net.corda.client.rpc.internal.serialization.amqp.AMQPClientSerializationScheme
import net.corda.core.context.Actor
import net.corda.core.context.Trace
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.PLATFORM_VERSION
import net.corda.core.messaging.ClientRpcSslOptions
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.internal.effectiveSerializationEnv
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.messaging.ClientRpcSslOptions
import net.corda.core.utilities.days
import net.corda.core.utilities.minutes
import net.corda.core.utilities.seconds
import net.corda.nodeapi.internal.ArtemisTcpTransport.Companion.rpcConnectorTcpTransport
import net.corda.core.internal.PLATFORM_VERSION
import net.corda.serialization.internal.AMQP_RPC_CLIENT_CONTEXT
import net.corda.serialization.internal.amqp.SerializerFactory
import java.time.Duration
/**
@ -293,7 +296,7 @@ class CordaRPCClient private constructor(
effectiveSerializationEnv
} catch (e: IllegalStateException) {
try {
AMQPClientSerializationScheme.initialiseSerialization(classLoader)
AMQPClientSerializationScheme.initialiseSerialization(classLoader, Caffeine.newBuilder().maximumSize(128).build<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>().asMap())
} catch (e: IllegalStateException) {
// Race e.g. two of these constructed in parallel, ignore.
}

View File

@ -3,7 +3,7 @@ package net.corda.client.rpc.internal.serialization.amqp
import net.corda.core.cordapp.Cordapp
import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationContext.*
import net.corda.core.serialization.SerializationContext.UseCase
import net.corda.core.serialization.SerializationCustomSerializer
import net.corda.core.serialization.internal.SerializationEnvironment
import net.corda.core.serialization.internal.nodeSerializationEnv
@ -19,24 +19,25 @@ import net.corda.serialization.internal.amqp.custom.RxNotificationSerializer
* This scheme is for use by the RPC Client calls.
*/
class AMQPClientSerializationScheme(
cordappCustomSerializers: Set<SerializationCustomSerializer<*,*>>,
serializerFactoriesForContexts: AccessOrderLinkedHashMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>
cordappCustomSerializers: Set<SerializationCustomSerializer<*,*>>,
serializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>
) : AbstractAMQPSerializationScheme(cordappCustomSerializers, serializerFactoriesForContexts) {
constructor(cordapps: List<Cordapp>) : this(cordapps.customSerializers, AccessOrderLinkedHashMap { 128 })
constructor(cordapps: List<Cordapp>, serializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>) : this(cordapps.customSerializers, serializerFactoriesForContexts)
@Suppress("UNUSED")
constructor() : this(emptySet(), AccessOrderLinkedHashMap { 128 })
companion object {
/** Call from main only. */
fun initialiseSerialization(classLoader: ClassLoader? = null) {
nodeSerializationEnv = createSerializationEnv(classLoader)
fun initialiseSerialization(classLoader: ClassLoader? = null, serializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory> = AccessOrderLinkedHashMap { 128 }) {
nodeSerializationEnv = createSerializationEnv(classLoader, serializerFactoriesForContexts)
}
fun createSerializationEnv(classLoader: ClassLoader? = null): SerializationEnvironment {
fun createSerializationEnv(classLoader: ClassLoader? = null, serializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory> = AccessOrderLinkedHashMap { 128 }): SerializationEnvironment {
return SerializationEnvironment.with(
SerializationFactoryImpl().apply {
registerScheme(AMQPClientSerializationScheme(emptyList()))
registerScheme(AMQPClientSerializationScheme(emptyList(), serializerFactoriesForContexts))
},
storageContext = AMQP_STORAGE_CONTEXT,
p2pContext = if (classLoader != null) AMQP_P2P_CONTEXT.withClassLoader(classLoader) else AMQP_P2P_CONTEXT,

View File

@ -13,170 +13,166 @@ import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class ContractsDSLTests {
class UnwantedCommand : CommandData
class UnwantedCommand : CommandData
interface TestCommands : CommandData {
class CommandOne : TypeOnlyCommandData(), TestCommands
class CommandTwo : TypeOnlyCommandData(), TestCommands
interface TestCommands : CommandData {
class CommandOne : TypeOnlyCommandData(), TestCommands
class CommandTwo : TypeOnlyCommandData(), TestCommands
}
val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB"))
val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB"))
val validCommandOne = CommandWithParties(listOf(megaCorp.publicKey, miniCorp.publicKey), listOf(megaCorp.party, miniCorp.party), TestCommands.CommandOne())
val validCommandTwo = CommandWithParties(listOf(megaCorp.publicKey), listOf(megaCorp.party), TestCommands.CommandTwo())
val invalidCommand = CommandWithParties(emptyList(), emptyList(), UnwantedCommand())
@RunWith(Parameterized::class)
class RequireSingleCommandTests(private val testFunction: (Collection<CommandWithParties<CommandData>>) -> CommandWithParties<CommandData>,
@Suppress("UNUSED_PARAMETER") description: String) {
companion object {
@JvmStatic
@Parameterized.Parameters(name = "{1}")
fun data(): Collection<Array<Any>> = listOf(
arrayOf<Any>({ commands: Collection<CommandWithParties<CommandData>> -> commands.requireSingleCommand<TestCommands>() }, "Inline version"),
arrayOf<Any>({ commands: Collection<CommandWithParties<CommandData>> -> commands.requireSingleCommand(TestCommands::class.java) }, "Interop version")
)
}
private companion object {
val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB"))
val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB"))
val validCommandOne = CommandWithParties(listOf(megaCorp.publicKey, miniCorp.publicKey), listOf(megaCorp.party, miniCorp.party), TestCommands.CommandOne())
val validCommandTwo = CommandWithParties(listOf(megaCorp.publicKey), listOf(megaCorp.party), TestCommands.CommandTwo())
val invalidCommand = CommandWithParties(emptyList(), emptyList(), UnwantedCommand())
@Test
fun `check function returns one value`() {
val commands = listOf(validCommandOne, invalidCommand)
val returnedCommand = testFunction(commands)
assertEquals(returnedCommand, validCommandOne, "they should be the same")
}
@RunWith(Parameterized::class)
class RequireSingleCommandTests(private val testFunction: (Collection<CommandWithParties<CommandData>>) -> CommandWithParties<CommandData>,
@Test(expected = IllegalArgumentException::class)
fun `check error is thrown if more than one valid command`() {
val commands = listOf(validCommandOne, validCommandTwo)
testFunction(commands)
}
@Test
fun `check error is thrown when command is of wrong type`() {
val commands = listOf(invalidCommand)
Assertions.assertThatThrownBy { testFunction(commands) }
.isInstanceOf(IllegalStateException::class.java)
.hasMessage("Required net.corda.core.contracts.TestCommands command")
}
}
@RunWith(Parameterized::class)
class SelectWithSingleInputsTests(private val testFunction: (Collection<CommandWithParties<CommandData>>, PublicKey?, AbstractParty?) -> Iterable<CommandWithParties<CommandData>>,
@Suppress("UNUSED_PARAMETER") description: String) {
companion object {
@JvmStatic
@Parameterized.Parameters(name = "{1}")
fun data(): Collection<Array<Any>> = listOf(
arrayOf<Any>({ commands: Collection<CommandWithParties<CommandData>>, signer: PublicKey?, party: AbstractParty? -> commands.select<TestCommands>(signer, party) }, "Inline version"),
arrayOf<Any>({ commands: Collection<CommandWithParties<CommandData>>, signer: PublicKey?, party: AbstractParty? -> commands.select(TestCommands::class.java, signer, party) }, "Interop version")
)
}
@Test
fun `check that function returns all values`() {
val commands = listOf(validCommandOne, validCommandTwo)
testFunction(commands, null, null)
assertEquals(2, commands.size)
assertTrue(commands.contains(validCommandOne))
assertTrue(commands.contains(validCommandTwo))
}
@Test
fun `check that function does not return invalid command types`() {
val commands = listOf(validCommandOne, invalidCommand)
val filteredCommands = testFunction(commands, null, null).toList()
assertEquals(1, filteredCommands.size)
assertTrue(filteredCommands.contains(validCommandOne))
assertFalse(filteredCommands.contains(invalidCommand))
}
@Test
fun `check that function returns commands from valid signers`() {
val commands = listOf(validCommandOne, validCommandTwo)
val filteredCommands = testFunction(commands, miniCorp.publicKey, null).toList()
assertEquals(1, filteredCommands.size)
assertTrue(filteredCommands.contains(validCommandOne))
assertFalse(filteredCommands.contains(validCommandTwo))
}
@Test
fun `check that function returns commands from valid parties`() {
val commands = listOf(validCommandOne, validCommandTwo)
val filteredCommands = testFunction(commands, null, miniCorp.party).toList()
assertEquals(1, filteredCommands.size)
assertTrue(filteredCommands.contains(validCommandOne))
assertFalse(filteredCommands.contains(validCommandTwo))
}
}
@RunWith(Parameterized::class)
class SelectWithMultipleInputsTests(private val testFunction: (Collection<CommandWithParties<CommandData>>, Collection<PublicKey>?, Collection<Party>?) -> Iterable<CommandWithParties<CommandData>>,
@Suppress("UNUSED_PARAMETER") description: String) {
companion object {
@JvmStatic
@Parameterized.Parameters(name = "{1}")
fun data(): Collection<Array<Any>> = listOf(
arrayOf<Any>({ commands: Collection<CommandWithParties<CommandData>> -> commands.requireSingleCommand<TestCommands>() }, "Inline version"),
arrayOf<Any>({ commands: Collection<CommandWithParties<CommandData>> -> commands.requireSingleCommand(TestCommands::class.java) }, "Interop version")
)
}
@Test
fun `check function returns one value`() {
val commands = listOf(validCommandOne, invalidCommand)
val returnedCommand = testFunction(commands)
assertEquals(returnedCommand, validCommandOne, "they should be the same")
}
@Test(expected = IllegalArgumentException::class)
fun `check error is thrown if more than one valid command`() {
val commands = listOf(validCommandOne, validCommandTwo)
testFunction(commands)
}
@Test
fun `check error is thrown when command is of wrong type`() {
val commands = listOf(invalidCommand)
Assertions.assertThatThrownBy { testFunction(commands) }
.isInstanceOf(IllegalStateException::class.java)
.hasMessage("Required net.corda.core.contracts.ContractsDSLTests.TestCommands command")
}
companion object {
@JvmStatic
@Parameterized.Parameters(name = "{1}")
fun data(): Collection<Array<Any>> = listOf(
arrayOf<Any>({ commands: Collection<CommandWithParties<CommandData>>, signers: Collection<PublicKey>?, party: Collection<Party>? -> commands.select<TestCommands>(signers, party) }, "Inline version"),
arrayOf<Any>({ commands: Collection<CommandWithParties<CommandData>>, signers: Collection<PublicKey>?, party: Collection<Party>? -> commands.select(TestCommands::class.java, signers, party) }, "Interop version")
)
}
@RunWith(Parameterized::class)
class SelectWithSingleInputsTests(private val testFunction: (Collection<CommandWithParties<CommandData>>, PublicKey?, AbstractParty?) -> Iterable<CommandWithParties<CommandData>>,
@Suppress("UNUSED_PARAMETER") description: String) {
companion object {
@JvmStatic
@Parameterized.Parameters(name = "{1}")
fun data(): Collection<Array<Any>> = listOf(
arrayOf<Any>({ commands: Collection<CommandWithParties<CommandData>>, signer: PublicKey?, party: AbstractParty? -> commands.select<TestCommands>(signer, party) }, "Inline version"),
arrayOf<Any>({ commands: Collection<CommandWithParties<CommandData>>, signer: PublicKey?, party: AbstractParty? -> commands.select(TestCommands::class.java, signer, party) }, "Interop version")
)
}
@Test
fun `check that function returns all values`() {
val commands = listOf(validCommandOne, validCommandTwo)
testFunction(commands, null, null)
assertEquals(2, commands.size)
assertTrue(commands.contains(validCommandOne))
assertTrue(commands.contains(validCommandTwo))
}
@Test
fun `check that function does not return invalid command types`() {
val commands = listOf(validCommandOne, invalidCommand)
val filteredCommands = testFunction(commands, null, null).toList()
assertEquals(1, filteredCommands.size)
assertTrue(filteredCommands.contains(validCommandOne))
assertFalse(filteredCommands.contains(invalidCommand))
}
@Test
fun `check that function returns commands from valid signers`() {
val commands = listOf(validCommandOne, validCommandTwo)
val filteredCommands = testFunction(commands, miniCorp.publicKey, null).toList()
assertEquals(1, filteredCommands.size)
assertTrue(filteredCommands.contains(validCommandOne))
assertFalse(filteredCommands.contains(validCommandTwo))
}
@Test
fun `check that function returns commands from valid parties`() {
val commands = listOf(validCommandOne, validCommandTwo)
val filteredCommands = testFunction(commands, null, miniCorp.party).toList()
assertEquals(1, filteredCommands.size)
assertTrue(filteredCommands.contains(validCommandOne))
assertFalse(filteredCommands.contains(validCommandTwo))
}
@Test
fun `check that function returns all values`() {
val commands = listOf(validCommandOne, validCommandTwo)
testFunction(commands, null, null)
assertEquals(2, commands.size)
assertTrue(commands.contains(validCommandOne))
assertTrue(commands.contains(validCommandTwo))
}
@RunWith(Parameterized::class)
class SelectWithMultipleInputsTests(private val testFunction: (Collection<CommandWithParties<CommandData>>, Collection<PublicKey>?, Collection<Party>?) -> Iterable<CommandWithParties<CommandData>>,
@Suppress("UNUSED_PARAMETER") description: String) {
companion object {
@JvmStatic
@Parameterized.Parameters(name = "{1}")
fun data(): Collection<Array<Any>> = listOf(
arrayOf<Any>({ commands: Collection<CommandWithParties<CommandData>>, signers: Collection<PublicKey>?, party: Collection<Party>? -> commands.select<TestCommands>(signers, party) }, "Inline version"),
arrayOf<Any>({ commands: Collection<CommandWithParties<CommandData>>, signers: Collection<PublicKey>?, party: Collection<Party>? -> commands.select(TestCommands::class.java, signers, party) }, "Interop version")
)
}
@Test
fun `check that function returns all values`() {
val commands = listOf(validCommandOne, validCommandTwo)
testFunction(commands, null, null)
assertEquals(2, commands.size)
assertTrue(commands.contains(validCommandOne))
assertTrue(commands.contains(validCommandTwo))
}
@Test
fun `check that function does not return invalid command types`() {
val commands = listOf(validCommandOne, invalidCommand)
val filteredCommands = testFunction(commands, null, null).toList()
assertEquals(1, filteredCommands.size)
assertTrue(filteredCommands.contains(validCommandOne))
assertFalse(filteredCommands.contains(invalidCommand))
}
@Test
fun `check that function returns commands from valid signers`() {
val commands = listOf(validCommandOne, validCommandTwo)
val filteredCommands = testFunction(commands, listOf(megaCorp.publicKey), null).toList()
assertEquals(2, filteredCommands.size)
assertTrue(filteredCommands.contains(validCommandOne))
assertTrue(filteredCommands.contains(validCommandTwo))
}
@Test
fun `check that function returns commands from all valid signers`() {
val commands = listOf(validCommandOne, validCommandTwo)
val filteredCommands = testFunction(commands, listOf(miniCorp.publicKey, megaCorp.publicKey), null).toList()
assertEquals(1, filteredCommands.size)
assertTrue(filteredCommands.contains(validCommandOne))
assertFalse(filteredCommands.contains(validCommandTwo))
}
@Test
fun `check that function returns commands from valid parties`() {
val commands = listOf(validCommandOne, validCommandTwo)
val filteredCommands = testFunction(commands, null, listOf(megaCorp.party)).toList()
assertEquals(2, filteredCommands.size)
assertTrue(filteredCommands.contains(validCommandOne))
assertTrue(filteredCommands.contains(validCommandTwo))
}
@Test
fun `check that function returns commands from all valid parties`() {
val commands = listOf(validCommandOne, validCommandTwo)
val filteredCommands = testFunction(commands, null, listOf(miniCorp.party, megaCorp.party)).toList()
assertEquals(1, filteredCommands.size)
assertTrue(filteredCommands.contains(validCommandOne))
assertFalse(filteredCommands.contains(validCommandTwo))
}
@Test
fun `check that function does not return invalid command types`() {
val commands = listOf(validCommandOne, invalidCommand)
val filteredCommands = testFunction(commands, null, null).toList()
assertEquals(1, filteredCommands.size)
assertTrue(filteredCommands.contains(validCommandOne))
assertFalse(filteredCommands.contains(invalidCommand))
}
}
@Test
fun `check that function returns commands from valid signers`() {
val commands = listOf(validCommandOne, validCommandTwo)
val filteredCommands = testFunction(commands, listOf(megaCorp.publicKey), null).toList()
assertEquals(2, filteredCommands.size)
assertTrue(filteredCommands.contains(validCommandOne))
assertTrue(filteredCommands.contains(validCommandTwo))
}
@Test
fun `check that function returns commands from all valid signers`() {
val commands = listOf(validCommandOne, validCommandTwo)
val filteredCommands = testFunction(commands, listOf(miniCorp.publicKey, megaCorp.publicKey), null).toList()
assertEquals(1, filteredCommands.size)
assertTrue(filteredCommands.contains(validCommandOne))
assertFalse(filteredCommands.contains(validCommandTwo))
}
@Test
fun `check that function returns commands from valid parties`() {
val commands = listOf(validCommandOne, validCommandTwo)
val filteredCommands = testFunction(commands, null, listOf(megaCorp.party)).toList()
assertEquals(2, filteredCommands.size)
assertTrue(filteredCommands.contains(validCommandOne))
assertTrue(filteredCommands.contains(validCommandTwo))
}
@Test
fun `check that function returns commands from all valid parties`() {
val commands = listOf(validCommandOne, validCommandTwo)
val filteredCommands = testFunction(commands, null, listOf(miniCorp.party, megaCorp.party)).toList()
assertEquals(1, filteredCommands.size)
assertTrue(filteredCommands.contains(validCommandOne))
assertFalse(filteredCommands.contains(validCommandTwo))
}
}

View File

@ -94,6 +94,8 @@ time effectively stop being a part of the network.
**Signature constraints.** These are not yet supported, but once implemented they will allow a state to require a JAR
signed by a specified identity, via the regular Java ``jarsigner`` tool. This will be the most flexible type
and the smoothest to deploy: no restarts or contract upgrade transactions are needed.
When CorDapp is build using :ref:`corda-gradle-plugin <cordapp_build_system_signing_cordapp_jar_ref>` the JAR is signed
by Corda development key by default, an external keystore can be configured or signing can be disabled.
**Defaults.** Currently, the default constraint type is either a zone constraint, if the network parameters in effect when the
transaction is built contain an entry for that contract class, or a hash constraint if not. Once the Signature Constraints are introduced,

View File

@ -113,7 +113,7 @@ Several examples of entities and mappings are provided in the codebase, includin
:language: kotlin
.. note:: Ensure table and column names are compatible with the naming convention of database vendors for which the Cordapp will be deployed,
e.g. prior to Oracle 12.2 the maximum length of table/column name is 30 bytes (the exact number of characters depends on the database encoding).
e.g. for Oracle database, prior to version 12.2 the maximum length of table/column name is 30 bytes (the exact number of characters depends on the database encoding).
Identity mapping
----------------

View File

@ -11,20 +11,23 @@ Interacting with a node
Overview
--------
You should interact with your node using the `CordaRPCClient`_ library. This library that allows you to easily
write clients in a JVM-compatible language to interact with a running node. The library connects to the node using a
message queue protocol and then provides a simple RPC interface to interact with the node. You make calls on a JVM
object as normal, and the marshalling back and forth is handled for you.
To interact with your node, you need to write a client in a JVM-compatible language using the `CordaRPCClient`_ class.
This class allows you to connect to your node via a message queue protocol and provides a simple RPC interface for
interacting with the node. You make calls on a JVM object as normal, and the marshalling back-and-forth is handled for
you.
.. warning:: The built-in Corda webserver is deprecated and unsuitable for production use. If you want to interact with
your node via HTTP, you will need to stand up your own webserver, then create an RPC connection between your node
and this webserver using the `CordaRPCClient`_ library. You can find an example of how to do this using the popular
Spring Boot server `here <https://github.com/corda/spring-webserver>`_.
your node via HTTP, you will need to stand up your own webserver that connects to your node using the
`CordaRPCClient`_ class. You can find an example of how to do this using the popular Spring Boot server
`here <https://github.com/corda/spring-webserver>`_.
Connecting to a node via RPC
----------------------------
`CordaRPCClient`_ provides a ``start`` method that takes the node's RPC address and returns a `CordaRPCConnection`_.
`CordaRPCConnection`_ provides a ``proxy`` method that takes an RPC username and password and returns a `CordaRPCOps`_
To use `CordaRPCClient`_, you must add ``net.corda:corda-rpc:$corda_release_version`` as a ``cordaCompile`` dependency
in your client's ``build.gradle`` file.
`CordaRPCClient`_ has a ``start`` method that takes the node's RPC address and returns a `CordaRPCConnection`_.
`CordaRPCConnection`_ has a ``proxy`` method that takes an RPC username and password and returns a `CordaRPCOps`_
object that you can use to interact with the node.
Here is an example of using `CordaRPCClient`_ to connect to a node and log the current time on its internal clock:

View File

@ -109,6 +109,108 @@ in Gradle. See the example below, specifically the ``apache-commons`` include.
For further information about managing dependencies, see
`the Gradle docs <https://docs.gradle.org/current/userguide/dependency_management.html>`_.
.. _cordapp_build_system_signing_cordapp_jar_ref:
Signing the CorDapp JAR
^^^^^^^^^^^^^^^^^^^^^^^
The ``cordapp`` plugin can sign the generated CorDapp JAR file using `JAR signing and verification tool <https://docs.oracle.com/javase/tutorial/deployment/jar/signing.html>`_.
Signing the CorDapp enables its contract classes to use signature constraints instead of other types of the constraints,
for constraints explanation refer to :doc:`api-contract-constraints`.
By default the JAR file is signed by Corda development certificate.
The signing process can be disabled or configured to use an external keystore.
The ``signing`` entry may contain the following parameters:
* ``enabled`` the control flag to enable signing process, by default is set to ``true``, set to ``false`` to disable signing
* ``options`` any relevant parameters of `SignJar ANT task <https://ant.apache.org/manual/Tasks/signjar.html>`_,
by default the JAR file is signed with Corda development key, the external keystore can be specified,
the minimal list of required options is shown below, for other options referer to `SignJar task <https://ant.apache.org/manual/Tasks/signjar.html>`_:
* ``keystore`` the path to the keystore file, by default *cordadevcakeys.jks* keystore is shipped with the plugin
* ``alias`` the alias to sign under, the default value is *cordaintermediateca*
* ``storepass`` the keystore password, the default value is *cordacadevpass*
* ``keypass`` the private key password if it's different than the password for the keystore, the default value is *cordacadevkeypass*
* ``storetype`` the keystore type, the default value is *JKS*
The parameters can be also set by system properties passed to Gradle build process.
The system properties should be named as the relevant option name prefixed with '*signing.*', e.g.
a value for ``alias`` can be taken from the ``signing.alias`` system property. The following system properties can be used:
``signing.enabled``, ``signing.keystore``, ``signing.alias``, ``signing.storepass``, ``signing.keypass``, ``signing.storetype``.
The resolution order of a configuration value is as follows: the signing process takes a value specified in the ``signing`` entry first,
the empty string *""* is also considered as the correct value.
If the option is not set, the relevant system property named *signing.option* is tried.
If the system property is not set then the value defaults to the configuration of the Corda development certificate.
The example ``cordapp`` plugin with plugin ``signing`` configuration:
.. sourcecode:: groovy
cordapp {
signing {
enabled true
options {
keystore "/path/to/jarSignKeystore.p12"
alias "cordapp-signer"
storepass "secret1!"
keypass "secret1!"
storetype "PKCS12"
}
}
//...
CorDapp auto-signing allows to use signature constraints for contracts from the CorDapp
without need to create a keystore and configure the ``cordapp`` plugin.
For production deployment ensure to sign the CorDapp using your own certificate e.g. by setting system properties to point to an external keystore
or by disabling signing in ``cordapp`` plugin and signing the CordDapp JAR downstream in your build pipeline.
CorDapp signed by Corda development certificate is accepted by Corda node only when running in the development mode.
Signing options can be contextually overwritten by the relevant system properties as described above.
This allows the single ``build.gradle`` file to be used for a development build (defaulting to the Corda development keystore)
and for a production build (using an external keystore).
The example system properties setup for the build process which overrides signing options:
.. sourcecode:: shell
./gradlew -Dsigning.keystore="/path/to/keystore.jks" -Dsigning.alias="alias" -Dsigning.storepass="password" -Dsigning.keypass="password"
Without providing the system properties, the build will sign the CorDapp with the default Corda development keystore:
.. sourcecode:: shell
./gradlew
CorDapp signing can be disabled for a build:
.. sourcecode:: shell
./gradlew -Dsigning.enabled=false
Other system properties can be explicitly assigned to options by calling ``System.getProperty`` in ``cordapp`` plugin configuration.
For example the below configuration sets the specific signing algorithm when a system property is available otherwise defaults to an empty string:
.. sourcecode:: groovy
cordapp {
signing {
options {
sigalg System.getProperty('custom.sigalg','')
}
}
//...
Then the build process can set the value for *custom.sigalg* system property and other system properties recognized by ``cordapp`` plugin:
.. sourcecode:: shell
./gradlew -Dcustom.sigalg="SHA256withECDSA" -Dsigning.keystore="/path/to/keystore.jks" -Dsigning.alias="alias" -Dsigning.storepass="password" -Dsigning.keypass="password"
To check if CorDapp is signed use `JAR signing and verification tool <https://docs.oracle.com/javase/tutorial/deployment/jar/verify.html>`_:
.. sourcecode:: shell
jarsigner --verify path/to/cordapp.jar
Cordformation plugin can also sign CorDapps JARs, when deploying set of nodes, see :doc:`generating-a-node`.
Example
^^^^^^^
Below is a sample of what a CorDapp's Gradle dependencies block might look like. When building your own CorDapp, you should

View File

@ -143,6 +143,66 @@ To copy the same file to all nodes `ext.drivers` can be defined in the top level
}
}
Signing Cordapp JARs
^^^^^^^^^^^^^^^^^^^^
Cordform entry ``signing`` configures the signing of CorDapp JARs.
Signing the CorDapp enables its contract classes to use signature constraints instead of other types of the constraints :doc:`api-contract-constraints`.
By default all CorDapp JARs are signed by Corda development certificate.
The sign task may use an external keystore, or create a new one.
The ``signing`` entry may contain the following parameters:
* ``enabled`` the control flag to enable signing process, by default is set to ``true``, set to ``false`` to disable signing
* ``all`` if set to ``true`` (by default) all CorDapps inside *cordapp* subdirectory will be signed, otherwise if ``false`` then only the generated Cordapp will be signed
* ``options`` any relevant parameters of `SignJar ANT task <https://ant.apache.org/manual/Tasks/signjar.html>`_ and `GenKey ANT task <https://ant.apache.org/manual/Tasks/genkey.html>`_,
by default the JAR file is signed by Corda development key, the external keystore can be specified,
the minimal list of required options is shown below, for other options referer to `SignJar task <https://ant.apache.org/manual/Tasks/signjar.html>`_:
* ``keystore`` the path to the keystore file, by default *cordadevcakeys.jks* keystore is shipped with the plugin
* ``alias`` the alias to sign under, the default value is *cordaintermediateca*
* ``storepass`` the keystore password, the default value is *cordacadevpass*
* ``keypass`` the private key password if it's different than the password for the keystore, the default value is *cordacadevkeypass*
* ``storetype`` the keystore type, the default value is *JKS*
* ``dname`` the distinguished name for entity, the option is used when ``generateKeystore true`` only
* ``keyalg`` the method to use when generating name-value pair, the value defaults to *RSA* as Corda doesn't support *DSA*, the option is used when ``generateKeystore true`` only
* ``generateKeystore`` the flag to generate a keystore, it is set to ``false`` by default. If set to ``true`` then ad hock keystore is created and its key isused
instead of the default Corda development key or any external key.
The same ``options`` to specify an external keystore are used to define the newly created keystore. Additionally
``dname`` and ``keyalg`` are required. Other options are described in `GenKey task <https://ant.apache.org/manual/Tasks/genkey.html>`_.
If the existing keystore is already present the task will reuse it, however if the file is inside the *build* directory,
then it will be deleted when Gradle *clean* task is run.
The example below shows the minimal set of ``options`` needed to create a dummy keystore:
.. sourcecode:: groovy
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
signing {
enabled true
generateKeystore true
all false
options {
keystore "./build/nodes/jarSignKeystore.p12"
alias "cordapp-signer"
storepass "secret1!"
storetype "PKCS12"
dname "OU=Dummy Cordapp Distributor, O=Corda, L=London, C=GB"
keyalg "RSA"
}
}
//...
Contracts classes from signed CorDapp JARs will be checked by signature constraints by default.
You can force them to be checked by zone constraints by adding contract class names to ``includeWhitelist`` entry,
the list will generate *include_whitelist.txt* file used internally by :doc:`network-bootstrapper` tool.
Refer to :doc:`api-contract-constraints` to understand implication of different constraint types before adding ``includeWhitelist`` to ``deployNodes`` task.
The snippet below configures contracts classes from Finance CorDapp to be verified using zone constraints instead of signature constraints:
.. sourcecode:: groovy
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
includeWhitelist = [ "net.corda.finance.contracts.asset.Cash", "net.corda.finance.contracts.asset.CommercialPaper" ]
//...
Specifying a custom webserver
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -324,7 +324,7 @@ Example node configuration for PostgreSQL:
dataSourceProperties = {
dataSourceClassName = "org.postgresql.ds.PGSimpleDataSource"
dataSource.url = "jdbc:postgresql://[HOST]:[PORT]/postgres"
dataSource.url = "jdbc:postgresql://[HOST]:[PORT]/[DATABASE]"
dataSource.user = [USER]
dataSource.password = [PASSWORD]
}
@ -353,6 +353,60 @@ To delete existing data from the database, drop the existing schema and recreate
DROP SCHEMA IF EXISTS "[SCHEMA]" CASCADE;
Node database tables
^^^^^^^^^^^^^^^^^^^^
By default, the node database has the following tables:
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table name | Columns |
+=============================+==========================================================================================================================================================================================================+
| DATABASECHANGELOG | ID, AUTHOR, FILENAME, DATEEXECUTED, ORDEREXECUTED, EXECTYPE, MD5SUM, DESCRIPTION, COMMENTS, TAG, LIQUIBASE, CONTEXTS, LABELS, DEPLOYMENT_ID |
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| DATABASECHANGELOGLOCK | ID, LOCKED, LOCKGRANTED, LOCKEDBY |
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| NODE_ATTACHMENTS | ATT_ID, CONTENT, FILENAME, INSERTION_DATE, UPLOADER |
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| NODE_ATTACHMENTS_CONTRACTS | ATT_ID, CONTRACT_CLASS_NAME |
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| NODE_CHECKPOINTS | CHECKPOINT_ID, CHECKPOINT_VALUE |
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| NODE_CONTRACT_UPGRADES | STATE_REF, CONTRACT_CLASS_NAME |
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| NODE_IDENTITIES | PK_HASH, IDENTITY_VALUE |
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| NODE_INFOS | NODE_INFO_ID, NODE_INFO_HASH, PLATFORM_VERSION, SERIAL |
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| NODE_INFO_HOSTS | HOST_NAME, PORT, NODE_INFO_ID, HOSTS_ID |
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| NODE_INFO_PARTY_CERT | PARTY_NAME, ISMAIN, OWNING_KEY_HASH, PARTY_CERT_BINARY |
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| NODE_LINK_NODEINFO_PARTY | NODE_INFO_ID, PARTY_NAME |
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| NODE_MESSAGE_IDS | MESSAGE_ID, INSERTION_TIME, SENDER, SEQUENCE_NUMBER |
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| NODE_NAMES_IDENTITIES | NAME, PK_HASH |
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| NODE_OUR_KEY_PAIRS | PUBLIC_KEY_HASH, PRIVATE_KEY, PUBLIC_KEY |
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| NODE_PROPERTIES | PROPERTY_KEY, PROPERTY_VALUE |
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| NODE_SCHEDULED_STATES | OUTPUT_INDEXTRANSACTION_IDSCHEDULED_AT |
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| NODE_TRANSACTIONS | TX_ID, TRANSACTION_VALUE, STATE_MACHINE_RUN_ID |
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| VAULT_FUNGIBLE_STATES | OUTPUT_INDEX, TRANSACTION_ID, ISSUER_NAME, ISSUER_REF, OWNER_NAME, QUANTITY |
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| VAULT_FUNGIBLE_STATES_PARTS | OUTPUT_INDEX, TRANSACTION_ID, PARTICIPANTS |
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| VAULT_LINEAR_STATES | OUTPUT_INDEX, TRANSACTION_ID, EXTERNAL_ID, UUID |
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| VAULT_LINEAR_STATES_PARTS | OUTPUT_INDEX, TRANSACTION_ID, PARTICIPANTS |
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| VAULT_STATES | OUTPUT_INDEX, TRANSACTION_ID, CONSUMED_TIMESTAMP, CONTRACT_STATE_CLASS_NAME, LOCK_ID, LOCK_TIMESTAMP, NOTARY_NAME, RECORDED_TIMESTAMP, STATE_STATUS, RELEVANCY_STATUS, CONSTRAINT_TYPE, CONSTRAINT_DATA |
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| VAULT_TRANSACTION_NOTES | SEQ_NO, NOTE, TRANSACTION_ID |
+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
Guideline for adding support for other databases
````````````````````````````````````````````````

View File

@ -39,22 +39,23 @@ weakly or untyped string-based serialisation schemes like JSON or XML. The prima
Whitelisting
------------
In classic Java serialization, any class on the JVM classpath can be deserialized. This has shown to be a source of exploits
and vulnerabilities by exploiting the large set of 3rd party libraries on the classpath as part of the dependencies of
a JVM application and a carefully crafted stream of bytes to be deserialized. In Corda, we prevent just any class from
being deserialized (and pro-actively during serialization) by insisting that each object's class belongs on a whitelist
of allowed classes.
In classic Java serialization, any class on the JVM classpath can be deserialized. This is a source of exploits and
vulnerabilities that exploit the large set of third-party libraries that are added to the classpath as part of a JVM
application's dependencies and carefully craft a malicious stream of bytes to be deserialized. In Corda, we strictly
control which classes can be deserialized (and, pro-actively, serialized) by insisting that each (de)serializable class
is part of a whitelist of allowed classes.
Classes get onto the whitelist via one of three mechanisms:
To add a class to the whitelist, you must use either of the following mechanisms:
#. Via the ``@CordaSerializable`` annotation. In order to whitelist a class, this annotation can be present on the
class itself, on any of the super classes or on any interface implemented by the class or super classes or any
interface extended by an interface implemented by the class or superclasses.
#. By implementing the ``SerializationWhitelist`` interface and specifying a list of `whitelist` classes.
#. Via the built in Corda whitelist (see the class ``DefaultWhitelist``). Whilst this is not user editable, it does list
common JDK classes that have been whitelisted for your convenience.
#. Add the ``@CordaSerializable`` annotation to the class. This annotation can be present on the
class itself, on any super class of the class, on any interface implemented by the class or its super classes, or any
interface extended by an interface implemented by the class or its super classes.
#. Implement the ``SerializationWhitelist`` interface and specify a list of whitelisted classes.
The annotation is the preferred method for whitelisting. An example is shown in :doc:`tutorial-clientrpc-api`.
There is also a built-in Corda whitelist (see the ``DefaultWhitelist`` class) that whitelists common JDK classes for
convenience. This whitelist is not user-editable.
The annotation is the preferred method for whitelisting. An example is shown in :doc:`tutorial-clientrpc-api`.
It's reproduced here as an example of both ways you can do this for a couple of example classes.
.. literalinclude:: example-code/src/main/kotlin/net/corda/docs/kotlin/ClientRpcTutorial.kt

View File

@ -90,6 +90,8 @@ cordapp {
targetPlatformVersion corda_platform_version.toInteger()
minimumPlatformVersion 1
}
// By default the Cordapp is signed by Corda development certificate, for production build pass the following system properties to Gradle to use specific keystore e.g:
// ./gradlew -Dsigning.enabled="true" -Dsigning.keystore="/path/to/keystore.jks" -Dsigning.alias="alias" -Dsigning.storepass="password" -Dsigning.keypass="password"
}
publish {

View File

@ -3,6 +3,7 @@ package net.corda.node.internal
import com.codahale.metrics.JmxReporter
import com.codahale.metrics.MetricFilter
import com.codahale.metrics.MetricRegistry
import com.github.benmanes.caffeine.cache.Caffeine
import com.palominolabs.metrics.newrelic.AllEnabledMetricAttributeFilter
import com.palominolabs.metrics.newrelic.NewRelicReporter
import net.corda.client.rpc.internal.serialization.amqp.AMQPClientSerializationScheme
@ -22,6 +23,7 @@ import net.corda.core.messaging.RPCOps
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NodeInfo
import net.corda.core.node.ServiceHub
import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.internal.SerializationEnvironment
import net.corda.core.serialization.internal.nodeSerializationEnv
import net.corda.core.utilities.NetworkHostAndPort
@ -56,6 +58,7 @@ import net.corda.nodeapi.internal.config.User
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.persistence.CouldNotCreateDataSourceException
import net.corda.serialization.internal.*
import net.corda.serialization.internal.amqp.SerializerFactory
import org.apache.commons.lang.SystemUtils
import org.h2.jdbc.JdbcSQLException
import org.slf4j.Logger
@ -502,8 +505,8 @@ open class Node(configuration: NodeConfiguration,
val classloader = cordappLoader.appClassLoader
nodeSerializationEnv = SerializationEnvironment.with(
SerializationFactoryImpl().apply {
registerScheme(AMQPServerSerializationScheme(cordappLoader.cordapps))
registerScheme(AMQPClientSerializationScheme(cordappLoader.cordapps))
registerScheme(AMQPServerSerializationScheme(cordappLoader.cordapps, Caffeine.newBuilder().maximumSize(128).build<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>().asMap()))
registerScheme(AMQPClientSerializationScheme(cordappLoader.cordapps, Caffeine.newBuilder().maximumSize(128).build<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>().asMap()))
},
p2pContext = AMQP_P2P_CONTEXT.withClassLoader(classloader),
rpcServerContext = AMQP_RPC_SERVER_CONTEXT.withClassLoader(classloader),

View File

@ -9,7 +9,6 @@ import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme
import net.corda.serialization.internal.amqp.AccessOrderLinkedHashMap
import net.corda.serialization.internal.amqp.SerializerFactory
import net.corda.serialization.internal.amqp.custom.RxNotificationSerializer
import java.util.concurrent.ConcurrentHashMap
/**
* When set as the serialization scheme, defines the RPC Server serialization scheme as using the Corda
@ -17,9 +16,10 @@ import java.util.concurrent.ConcurrentHashMap
*/
class AMQPServerSerializationScheme(
cordappCustomSerializers: Set<SerializationCustomSerializer<*, *>>,
serializerFactoriesForContexts: AccessOrderLinkedHashMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>
serializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>
) : AbstractAMQPSerializationScheme(cordappCustomSerializers, serializerFactoriesForContexts) {
constructor(cordapps: List<Cordapp>) : this(cordapps.customSerializers, AccessOrderLinkedHashMap { 128 })
constructor(cordapps: List<Cordapp>, serializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>) : this(cordapps.customSerializers, serializerFactoriesForContexts)
constructor() : this(emptySet(), AccessOrderLinkedHashMap { 128 })

View File

@ -2,6 +2,7 @@ package net.corda.node.services.persistence
import net.corda.core.node.services.AttachmentId
import net.corda.core.node.services.AttachmentStorage
import net.corda.nodeapi.exceptions.DuplicateAttachmentException
import java.io.InputStream
interface AttachmentStorageInternal : AttachmentStorage {
@ -10,4 +11,9 @@ interface AttachmentStorageInternal : AttachmentStorage {
* and is only for the node.
*/
fun privilegedImportAttachment(jar: InputStream, uploader: String, filename: String?): AttachmentId
/**
* Similar to above but returns existing [AttachmentId] instead of throwing [DuplicateAttachmentException]
*/
fun privilegedImportOrGetAttachment(jar: InputStream, uploader: String, filename: String?): AttachmentId
}

View File

@ -6,13 +6,11 @@ import com.google.common.hash.HashCode
import com.google.common.hash.Hashing
import com.google.common.hash.HashingInputStream
import com.google.common.io.CountingInputStream
import net.corda.core.ClientRelevantError
import net.corda.core.CordaRuntimeException
import net.corda.core.contracts.Attachment
import net.corda.core.contracts.ContractAttachment
import net.corda.core.contracts.ContractClassName
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.isFulfilledBy
import net.corda.core.crypto.sha256
import net.corda.core.internal.*
import net.corda.core.node.ServicesForResolution
@ -286,6 +284,14 @@ class NodeAttachmentService(
return import(jar, uploader, filename)
}
override fun privilegedImportOrGetAttachment(jar: InputStream, uploader: String, filename: String?): AttachmentId {
return try {
import(jar, uploader, filename)
} catch (faee: java.nio.file.FileAlreadyExistsException) {
AttachmentId.parse(faee.message!!)
}
}
override fun hasAttachment(attachmentId: AttachmentId): Boolean = database.transaction {
currentDBSession().find(NodeAttachmentService.DBAttachment::class.java, attachmentId.toString()) != null
}
@ -306,9 +312,7 @@ class NodeAttachmentService(
val id = bytes.sha256()
if (!hasAttachment(id)) {
checkIsAValidJAR(bytes.inputStream())
val jarSigners = getSigners(bytes)
val session = currentDBSession()
val attachment = NodeAttachmentService.DBAttachment(
attId = id.toString(),
@ -318,14 +322,24 @@ class NodeAttachmentService(
contractClassNames = contractClassNames,
signers = jarSigners
)
session.save(attachment)
attachmentCount.inc()
log.info("Stored new attachment $id")
id
} else {
throw DuplicateAttachmentException(id.toString())
return@withContractsInJar id
}
if (isUploaderTrusted(uploader)) {
val session = currentDBSession()
val attachment = session.get(NodeAttachmentService.DBAttachment::class.java, id.toString())
// update the `upLoader` field (as the existing attachment may have been resolved from a peer)
if (attachment.uploader != uploader) {
attachment.uploader = uploader
session.saveOrUpdate(attachment)
log.info("Updated attachment $id with uploader $uploader")
attachmentCache.invalidate(id)
attachmentContentCache.invalidate(id)
}
}
throw DuplicateAttachmentException(id.toString())
}
}
}

View File

@ -9,6 +9,7 @@ import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.JarSignatureTestUtils.createJar
import net.corda.core.JarSignatureTestUtils.generateKey
import net.corda.core.JarSignatureTestUtils.signJar
import net.corda.core.contracts.ContractAttachment
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256
import net.corda.core.flows.FlowLogic
@ -48,6 +49,7 @@ import javax.tools.StandardLocation
import javax.tools.ToolProvider
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertNotEquals
import kotlin.test.assertNull
@ -124,6 +126,30 @@ class NodeAttachmentServiceTest {
}
}
@Test
fun `insert contract attachment as an untrusted uploader and then as trusted CorDapp uploader`() {
val contractJarName = makeTestContractJar("com.example.MyContract")
val testJar = dir.resolve(contractJarName)
val expectedHash = testJar.readAll().sha256()
// PRIVILEGED_UPLOADERS = listOf(DEPLOYED_CORDAPP_UPLOADER, RPC_UPLOADER, P2P_UPLOADER, UNKNOWN_UPLOADER)
// TRUSTED_UPLOADERS = listOf(DEPLOYED_CORDAPP_UPLOADER, RPC_UPLOADER)
database.transaction {
val id = testJar.read { storage.privilegedImportOrGetAttachment(it, P2P_UPLOADER, null) }
assertEquals(expectedHash, id)
val attachment1 = storage.openAttachment(expectedHash)
val id2 = testJar.read { storage.privilegedImportOrGetAttachment(it, DEPLOYED_CORDAPP_UPLOADER, null) }
assertEquals(expectedHash, id2)
val attachment2 = storage.openAttachment(expectedHash)
assertNotEquals(attachment1, attachment2)
assertEquals(P2P_UPLOADER, (attachment1 as ContractAttachment).uploader)
assertEquals(DEPLOYED_CORDAPP_UPLOADER, (attachment2 as ContractAttachment).uploader)
}
}
@Test
fun `missing is not cached`() {
val (testJar, expectedHash) = makeTestJar()

View File

@ -1,13 +1,12 @@
package net.corda.serialization.internal
import net.corda.core.crypto.SecureHash
import java.lang.ClassLoader
/**
* Drop-in replacement for [AttachmentsClassLoaderBuilder] in the serialization module.
* This version is not strongly-coupled to [net.corda.core.node.ServiceHub].
*/
@Suppress("UNUSED", "UNUSED_PARAMETER")
internal class AttachmentsClassLoaderBuilder(private val properties: Map<Any, Any>, private val deserializationClassLoader: ClassLoader) {
fun build(attachmentHashes: List<SecureHash>): AttachmentsClassLoader? = null
internal class AttachmentsClassLoaderBuilder() {
fun build(attachmentHashes: List<SecureHash>, properties: Map<Any, Any>, deserializationClassLoader: ClassLoader): AttachmentsClassLoader? = null
}

View File

@ -2,8 +2,11 @@ package net.corda.serialization.internal
import net.corda.core.KeepForDJVM
import net.corda.core.crypto.SecureHash
import net.corda.core.serialization.*
import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.EncodingWhitelist
import net.corda.core.serialization.SerializationEncoding
import net.corda.core.serialization.internal.CheckpointSerializationContext
import java.lang.UnsupportedOperationException
@KeepForDJVM
data class CheckpointSerializationContextImpl @JvmOverloads constructor(
@ -13,17 +16,13 @@ data class CheckpointSerializationContextImpl @JvmOverloads constructor(
override val objectReferencesEnabled: Boolean,
override val encoding: SerializationEncoding?,
override val encodingWhitelist: EncodingWhitelist = NullEncodingWhitelist) : CheckpointSerializationContext {
private val builder = AttachmentsClassLoaderBuilder(properties, deserializationClassLoader)
/**
* {@inheritDoc}
*
* We need to cache the AttachmentClassLoaders to avoid too many contexts, since the class loader is part of cache key for the context.
* Unsupported for checkpoints.
*/
override fun withAttachmentsClassLoader(attachmentHashes: List<SecureHash>): CheckpointSerializationContext {
properties[attachmentsClassLoaderEnabledPropertyName] as? Boolean == true || return this
val classLoader = builder.build(attachmentHashes) ?: return this
return withClassLoader(classLoader)
throw UnsupportedOperationException()
}
override fun withProperty(property: Any, value: Any): CheckpointSerializationContext {

View File

@ -31,8 +31,10 @@ data class SerializationContextImpl @JvmOverloads constructor(override val prefe
override val useCase: SerializationContext.UseCase,
override val encoding: SerializationEncoding?,
override val encodingWhitelist: EncodingWhitelist = NullEncodingWhitelist,
override val lenientCarpenterEnabled: Boolean = false) : SerializationContext {
private val builder = AttachmentsClassLoaderBuilder(properties, deserializationClassLoader)
override val lenientCarpenterEnabled: Boolean = false,
private val builder: AttachmentsClassLoaderBuilder = AttachmentsClassLoaderBuilder()
) : SerializationContext {
/**
* {@inheritDoc}
@ -41,7 +43,7 @@ data class SerializationContextImpl @JvmOverloads constructor(override val prefe
*/
override fun withAttachmentsClassLoader(attachmentHashes: List<SecureHash>): SerializationContext {
properties[attachmentsClassLoaderEnabledPropertyName] as? Boolean == true || return this
val classLoader = builder.build(attachmentHashes) ?: return this
val classLoader = builder.build(attachmentHashes, properties, deserializationClassLoader) ?: return this
return withClassLoader(classLoader)
}
@ -75,13 +77,13 @@ data class SerializationContextImpl @JvmOverloads constructor(override val prefe
* can replace it with an alternative version.
*/
@DeleteForDJVM
internal class AttachmentsClassLoaderBuilder(private val properties: Map<Any, Any>, private val deserializationClassLoader: ClassLoader) {
private val cache: Cache<List<SecureHash>, AttachmentsClassLoader> = Caffeine.newBuilder().weakValues().maximumSize(1024).build()
class AttachmentsClassLoaderBuilder() {
private val cache: Cache<Pair<List<SecureHash>, ClassLoader>, AttachmentsClassLoader> = Caffeine.newBuilder().weakValues().maximumSize(1024).build()
fun build(attachmentHashes: List<SecureHash>): AttachmentsClassLoader? {
fun build(attachmentHashes: List<SecureHash>, properties: Map<Any, Any>, deserializationClassLoader: ClassLoader): AttachmentsClassLoader? {
val serializationContext = properties[serializationContextKey] as? SerializeAsTokenContext ?: return null // Some tests don't set one.
try {
return cache.get(attachmentHashes) {
return cache.get(Pair(attachmentHashes, deserializationClassLoader)) {
val missing = ArrayList<SecureHash>()
val attachments = ArrayList<Attachment>()
attachmentHashes.forEach { id ->
@ -120,12 +122,13 @@ open class SerializationFactoryImpl(
// truncate sequence to at most magicSize, and make sure it's a copy to avoid holding onto large ByteArrays
val magic = CordaSerializationMagic(byteSequence.slice(end = magicSize).copyBytes())
val lookupKey = magic to target
return schemes.computeIfAbsent(lookupKey) {
// ConcurrentHashMap.get() is lock free, but computeIfAbsent is not, even if the key is in the map already.
return (schemes[lookupKey] ?: schemes.computeIfAbsent(lookupKey) {
registeredSchemes.filter { it.canDeserializeVersion(magic, target) }.forEach { return@computeIfAbsent it } // XXX: Not single?
logger.warn("Cannot find serialization scheme for: [$lookupKey, " +
"${if (magic == amqpMagic) "AMQP" else "UNKNOWN MAGIC"}] registeredSchemes are: $registeredSchemes")
throw UnsupportedOperationException("Serialization scheme $lookupKey not supported.")
} to magic
}) to magic
}
@Throws(NotSerializableException::class)

View File

@ -40,12 +40,19 @@ interface SerializerFactoryFactory {
@KeepForDJVM
abstract class AbstractAMQPSerializationScheme(
private val cordappCustomSerializers: Set<SerializationCustomSerializer<*, *>>,
private val serializerFactoriesForContexts: AccessOrderLinkedHashMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>,
maybeNotConcurrentSerializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>,
val sff: SerializerFactoryFactory = createSerializerFactoryFactory()
) : SerializationScheme {
@DeleteForDJVM
constructor(cordapps: List<Cordapp>) : this(cordapps.customSerializers, AccessOrderLinkedHashMap(128))
// This is a bit gross but a broader check for ConcurrentMap is not allowed inside DJVM.
private val serializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory> = if (maybeNotConcurrentSerializerFactoriesForContexts is AccessOrderLinkedHashMap<*, *>) {
Collections.synchronizedMap(maybeNotConcurrentSerializerFactoriesForContexts)
} else {
maybeNotConcurrentSerializerFactoriesForContexts
}
// TODO: This method of initialisation for the Whitelist and plugin serializers will have to change
// when we have per-cordapp contexts and dynamic app reloading but for now it's the easiest way
companion object {
@ -166,8 +173,9 @@ abstract class AbstractAMQPSerializationScheme(
open val publicKeySerializer: CustomSerializer<*> = net.corda.serialization.internal.amqp.custom.PublicKeySerializer
private fun getSerializerFactory(context: SerializationContext): SerializerFactory {
return synchronized(serializerFactoriesForContexts) {
serializerFactoriesForContexts.computeIfAbsent(Pair(context.whitelist, context.deserializationClassLoader)) {
val key = Pair(context.whitelist, context.deserializationClassLoader)
// ConcurrentHashMap.get() is lock free, but computeIfAbsent is not, even if the key is in the map already.
return serializerFactoriesForContexts[key] ?: serializerFactoriesForContexts.computeIfAbsent(key) {
when (context.useCase) {
SerializationContext.UseCase.RPCClient ->
rpcClientSerializerFactory(context)
@ -178,7 +186,6 @@ abstract class AbstractAMQPSerializationScheme(
registerCustomSerializers(context, it)
}
}
}
}
override fun <T : Any> deserialize(byteSequence: ByteSequence, clazz: Class<T>, context: SerializationContext): T {

View File

@ -7,7 +7,10 @@ import net.corda.core.StubOutForDJVM
import net.corda.core.internal.kotlinObjectInstance
import net.corda.core.internal.uncheckedCast
import net.corda.core.serialization.ClassWhitelist
import net.corda.core.utilities.*
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.trace
import net.corda.serialization.internal.carpenter.*
import org.apache.qpid.proton.amqp.*
import java.io.NotSerializableException
@ -95,6 +98,9 @@ open class SerializerFactory(
val classloader: ClassLoader get() = classCarpenter.classloader
// Used to short circuit any computation for a given input, for performance.
private data class MemoType(val actualClass: Class<*>?, val declaredType: Type) : Type
/**
* Look up, and manufacture if necessary, a serializer for the given type.
*
@ -106,50 +112,56 @@ open class SerializerFactory(
// can be useful to enable but will be *extremely* chatty if you do
logger.trace { "Get Serializer for $actualClass ${declaredType.typeName}" }
val declaredClass = declaredType.asClass()
val actualType: Type = if (actualClass == null) declaredType
else inferTypeVariables(actualClass, declaredClass, declaredType) ?: declaredType
val ourType = MemoType(actualClass, declaredType)
// ConcurrentHashMap.get() is lock free, but computeIfAbsent is not, even if the key is in the map already.
return serializersByType[ourType] ?: run {
val serializer = when {
val declaredClass = declaredType.asClass()
val actualType: Type = if (actualClass == null) declaredType
else inferTypeVariables(actualClass, declaredClass, declaredType) ?: declaredType
val serializer = when {
// Declared class may not be set to Collection, but actual class could be a collection.
// In this case use of CollectionSerializer is perfectly appropriate.
(Collection::class.java.isAssignableFrom(declaredClass) ||
(actualClass != null && Collection::class.java.isAssignableFrom(actualClass))) &&
!EnumSet::class.java.isAssignableFrom(actualClass ?: declaredClass) -> {
val declaredTypeAmended = CollectionSerializer.deriveParameterizedType(declaredType, declaredClass, actualClass)
serializersByType.computeIfAbsent(declaredTypeAmended) {
CollectionSerializer(declaredTypeAmended, this)
(Collection::class.java.isAssignableFrom(declaredClass) ||
(actualClass != null && Collection::class.java.isAssignableFrom(actualClass))) &&
!EnumSet::class.java.isAssignableFrom(actualClass ?: declaredClass) -> {
val declaredTypeAmended = CollectionSerializer.deriveParameterizedType(declaredType, declaredClass, actualClass)
serializersByType.computeIfAbsent(declaredTypeAmended) {
CollectionSerializer(declaredTypeAmended, this)
}
}
}
// Declared class may not be set to Map, but actual class could be a map.
// In this case use of MapSerializer is perfectly appropriate.
(Map::class.java.isAssignableFrom(declaredClass) ||
(actualClass != null && Map::class.java.isAssignableFrom(actualClass))) -> {
val declaredTypeAmended = MapSerializer.deriveParameterizedType(declaredType, declaredClass, actualClass)
serializersByType.computeIfAbsent(declaredTypeAmended) {
makeMapSerializer(declaredTypeAmended)
}
}
Enum::class.java.isAssignableFrom(actualClass ?: declaredClass) -> {
logger.trace {
"class=[${actualClass?.simpleName} | $declaredClass] is an enumeration " +
"declaredType=${declaredType.typeName} " +
"isEnum=${declaredType::class.java.isEnum}"
(Map::class.java.isAssignableFrom(declaredClass) ||
(actualClass != null && Map::class.java.isAssignableFrom(actualClass))) -> {
val declaredTypeAmended = MapSerializer.deriveParameterizedType(declaredType, declaredClass, actualClass)
serializersByType.computeIfAbsent(declaredTypeAmended) {
makeMapSerializer(declaredTypeAmended)
}
}
Enum::class.java.isAssignableFrom(actualClass ?: declaredClass) -> {
logger.trace {
"class=[${actualClass?.simpleName} | $declaredClass] is an enumeration " +
"declaredType=${declaredType.typeName} " +
"isEnum=${declaredType::class.java.isEnum}"
}
serializersByType.computeIfAbsent(actualClass ?: declaredClass) {
whitelist.requireWhitelisted(actualType)
EnumSerializer(actualType, actualClass ?: declaredClass, this)
serializersByType.computeIfAbsent(actualClass ?: declaredClass) {
whitelist.requireWhitelisted(actualType)
EnumSerializer(actualType, actualClass ?: declaredClass, this)
}
}
else -> {
makeClassSerializer(actualClass ?: declaredClass, actualType, declaredType)
}
}
else -> {
makeClassSerializer(actualClass ?: declaredClass, actualType, declaredType)
}
serializersByDescriptor.putIfAbsent(serializer.typeDescriptor, serializer)
// Always store the short-circuit too, for performance.
serializersByType.putIfAbsent(ourType, serializer)
return serializer
}
serializersByDescriptor.putIfAbsent(serializer.typeDescriptor, serializer)
return serializer
}
/**

View File

@ -63,9 +63,9 @@ class StaticInitialisationOfSerializedObjectTest {
// we can't actually construct one
sf.get(null, D::class.java)
// post creation of the serializer we should have one element in the map, this
// post creation of the serializer we should have two elements in the map, this
// proves we didn't statically construct an instance of C when building the serializer
assertEquals(1, serialisersByType.size)
assertEquals(2, serialisersByType.size)
}
@Test

View File

@ -1,5 +1,7 @@
package net.corda.bootstrapper;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.control.Alert;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
@ -9,6 +11,7 @@ import javafx.stage.StageStyle;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.concurrent.CountDownLatch;
public class GuiUtils {
@ -18,31 +21,40 @@ public class GuiUtils {
alert.setTitle("Exception");
alert.setHeaderText(title);
alert.setContentText(message);
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
exception.printStackTrace(pw);
String exceptionText = sw.toString();
Label label = new Label("Details:");
TextArea textArea = new TextArea(exceptionText);
textArea.setEditable(false);
textArea.setWrapText(true);
textArea.setMaxWidth(Double.MAX_VALUE);
textArea.setMaxHeight(Double.MAX_VALUE);
GridPane.setVgrow(textArea, Priority.ALWAYS);
GridPane.setHgrow(textArea, Priority.ALWAYS);
GridPane expContent = new GridPane();
expContent.setMaxWidth(Double.MAX_VALUE);
expContent.add(label, 0, 0);
expContent.add(textArea, 0, 1);
alert.getDialogPane().setExpandableContent(expContent);
if (exception != null) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
exception.printStackTrace(pw);
String exceptionText = sw.toString();
TextArea textArea = new TextArea(exceptionText);
textArea.setEditable(false);
textArea.setWrapText(true);
textArea.setMaxWidth(Double.MAX_VALUE);
textArea.setMaxHeight(Double.MAX_VALUE);
Label label = new Label("Details:");
GridPane.setVgrow(textArea, Priority.ALWAYS);
GridPane.setHgrow(textArea, Priority.ALWAYS);
GridPane expContent = new GridPane();
expContent.setMaxWidth(Double.MAX_VALUE);
expContent.add(label, 0, 0);
expContent.add(textArea, 0, 1);
alert.getDialogPane().setExpandableContent(expContent);
}
alert.showAndWait();
}
public static void showAndQuit(String title, String message, Throwable exception) {
CountDownLatch countDownLatch = new CountDownLatch(1);
new JFXPanel();
Platform.runLater(() -> {
showException(title, message, exception);
countDownLatch.countDown();
System.exit(1);
});
try {
countDownLatch.await();
} catch (InterruptedException e) {
}
}
}

View File

@ -1,4 +1,5 @@
@file:JvmName("Main")
package net.corda.bootstrapper
import javafx.application.Application
@ -7,15 +8,19 @@ import net.corda.bootstrapper.backends.Backend.BackendType.AZURE
import net.corda.bootstrapper.cli.AzureParser
import net.corda.bootstrapper.cli.CliParser
import net.corda.bootstrapper.cli.CommandLineInterface
import net.corda.bootstrapper.docker.DockerUtils
import net.corda.bootstrapper.gui.Gui
import net.corda.bootstrapper.serialization.SerializationEngine
import picocli.CommandLine
import javax.ws.rs.ProcessingException
import kotlin.system.exitProcess
val baseArgs = CliParser()
fun main(args: Array<String>) {
SerializationEngine.init()
CommandLine(baseArgs).parse(*args)
testDockerConnectivity()
if (baseArgs.gui) {
Application.launch(Gui::class.java)
@ -32,3 +37,16 @@ fun main(args: Array<String>) {
}
CommandLineInterface().run(argParser)
}
private fun testDockerConnectivity() {
try {
DockerUtils.createLocalDockerClient().listImagesCmd().exec()
} catch (se: ProcessingException) {
if (baseArgs.gui) {
GuiUtils.showAndQuit("Could not connect to Docker", "Please ensure that docker is running locally", null)
} else {
System.err.println("Could not connect to Docker, please ensure that docker is running locally")
exitProcess(1)
}
}
}

View File

@ -1,7 +1,7 @@
package net.corda.bootstrapper.gui
import javafx.stage.Stage
import tornadofx.*
import tornadofx.App
class Gui : App(BootstrapperView::class) {
override fun start(stage: Stage) {