mirror of
https://github.com/corda/corda.git
synced 2025-01-14 08:49:47 +00:00
Merge remote-tracking branch 'open-hc02/master' into colljos-os-hc02-merge-121217
This commit is contained in:
commit
499de12620
@ -2756,6 +2756,10 @@ public static final class net.corda.core.serialization.SerializationContext$UseC
|
||||
public static net.corda.core.serialization.SerializationContext$UseCase valueOf(String)
|
||||
public static net.corda.core.serialization.SerializationContext$UseCase[] values()
|
||||
##
|
||||
public interface net.corda.core.serialization.SerializationCustomSerializer
|
||||
public abstract Object fromProxy(Object)
|
||||
public abstract Object toProxy(Object)
|
||||
##
|
||||
public final class net.corda.core.serialization.SerializationDefaults extends java.lang.Object
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializationContext getCHECKPOINT_CONTEXT()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializationContext getP2P_CONTEXT()
|
||||
|
@ -26,7 +26,10 @@ import rx.Observable
|
||||
import rx.subjects.PublishSubject
|
||||
import rx.subjects.UnicastSubject
|
||||
import java.time.Duration
|
||||
import java.util.concurrent.*
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.ScheduledExecutorService
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
class RPCStabilityTests : IntegrationTest() {
|
||||
|
@ -41,7 +41,7 @@ class KryoClientSerializationScheme : AbstractKryoSerializationScheme() {
|
||||
return SerializationEnvironmentImpl(
|
||||
SerializationFactoryImpl().apply {
|
||||
registerScheme(KryoClientSerializationScheme())
|
||||
registerScheme(AMQPClientSerializationScheme())
|
||||
registerScheme(AMQPClientSerializationScheme(emptyList()))
|
||||
},
|
||||
KRYO_P2P_CONTEXT,
|
||||
rpcClientContext = KRYO_RPC_CLIENT_CONTEXT)
|
||||
|
@ -48,8 +48,7 @@ public class StandaloneCordaRPCJavaClientTest {
|
||||
port.getAndIncrement(),
|
||||
port.getAndIncrement(),
|
||||
true,
|
||||
Collections.singletonList(rpcUser),
|
||||
null
|
||||
Collections.singletonList(rpcUser)
|
||||
);
|
||||
|
||||
@Before
|
||||
|
@ -4,8 +4,4 @@ trustStorePassword : "trustpass"
|
||||
p2pAddress : "localhost:10002"
|
||||
rpcAddress : "localhost:10003"
|
||||
webAddress : "localhost:10004"
|
||||
networkMapService : {
|
||||
address : "localhost:10000"
|
||||
legalName : "O=Network Map Service,OU=corda,L=London,C=GB"
|
||||
}
|
||||
useHTTPS : false
|
||||
|
@ -1,4 +1,4 @@
|
||||
gradlePluginsVersion=3.0.0
|
||||
gradlePluginsVersion=3.0.1
|
||||
kotlinVersion=1.1.60
|
||||
platformVersion=1
|
||||
guavaVersion=21.0
|
||||
|
@ -3,6 +3,7 @@ package net.corda.core.cordapp
|
||||
import net.corda.core.DoNotImplement
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.schemas.MappedSchema
|
||||
import net.corda.core.serialization.SerializationCustomSerializer
|
||||
import net.corda.core.serialization.SerializationWhitelist
|
||||
import net.corda.core.serialization.SerializeAsToken
|
||||
import java.net.URL
|
||||
@ -22,6 +23,7 @@ import java.net.URL
|
||||
* @property schedulableFlows List of flows startable by the scheduler
|
||||
* @property services List of RPC services
|
||||
* @property serializationWhitelists List of Corda plugin registries
|
||||
* @property serializationCustomSerializers List of serializers
|
||||
* @property customSchemas List of custom schemas
|
||||
* @property jarPath The path to the JAR for this CorDapp
|
||||
*/
|
||||
@ -35,6 +37,7 @@ interface Cordapp {
|
||||
val schedulableFlows: List<Class<out FlowLogic<*>>>
|
||||
val services: List<Class<out SerializeAsToken>>
|
||||
val serializationWhitelists: List<SerializationWhitelist>
|
||||
val serializationCustomSerializers: List<SerializationCustomSerializer<*, *>>
|
||||
val customSchemas: Set<MappedSchema>
|
||||
val jarPath: URL
|
||||
val cordappClasses: List<String>
|
||||
|
@ -3,6 +3,7 @@ package net.corda.core.internal.cordapp
|
||||
import net.corda.core.cordapp.Cordapp
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.schemas.MappedSchema
|
||||
import net.corda.core.serialization.SerializationCustomSerializer
|
||||
import net.corda.core.serialization.SerializationWhitelist
|
||||
import net.corda.core.serialization.SerializeAsToken
|
||||
import java.io.File
|
||||
@ -16,6 +17,7 @@ data class CordappImpl(
|
||||
override val schedulableFlows: List<Class<out FlowLogic<*>>>,
|
||||
override val services: List<Class<out SerializeAsToken>>,
|
||||
override val serializationWhitelists: List<SerializationWhitelist>,
|
||||
override val serializationCustomSerializers: List<SerializationCustomSerializer<*, *>>,
|
||||
override val customSchemas: Set<MappedSchema>,
|
||||
override val jarPath: URL) : Cordapp {
|
||||
override val name: String = File(jarPath.toURI()).name.removeSuffix(".jar")
|
||||
|
@ -53,7 +53,6 @@ interface NetworkMapCacheBase {
|
||||
*
|
||||
* Note that the identities are sorted based on legal name, and the ordering might change once new notaries are introduced.
|
||||
*/
|
||||
// TODO this list will be taken from NetworkParameters distributed by NetworkMap.
|
||||
val notaryIdentities: List<Party>
|
||||
// DOCEND 1
|
||||
|
||||
@ -117,7 +116,7 @@ interface NetworkMapCacheBase {
|
||||
fun getNotary(name: CordaX500Name): Party? = notaryIdentities.firstOrNull { it.name == name }
|
||||
// DOCEND 2
|
||||
|
||||
/** Checks whether a given party is an advertised notary identity. */
|
||||
/** Returns true if and only if the given [Party] is a notary, which is defined by the network parameters. */
|
||||
fun isNotary(party: Party): Boolean = party in notaryIdentities
|
||||
|
||||
/**
|
||||
|
@ -18,11 +18,11 @@ annotation class CordaSerializationTransformRenames(vararg val value: CordaSeria
|
||||
// TODO When we have class renaming update the docs
|
||||
/**
|
||||
* This annotation is used to mark a class has having had a property element. It is used by the
|
||||
* AMQP deserialiser to allow instances with different versions of the class on their Class Path
|
||||
* to successfully deserialize the object
|
||||
* AMQP deserializer to allow instances with different versions of the class on their Class Path
|
||||
* to successfully deserialize the object.
|
||||
*
|
||||
* NOTE: Renaming of the class itself is not be done with this annotation. For class renaming
|
||||
* see ???
|
||||
* NOTE: Renaming of the class itself isn't done with this annotation or, at present, supported
|
||||
* by Corda
|
||||
*
|
||||
* @property to [String] representation of the properties new name
|
||||
* @property from [String] representation of the properties old new
|
||||
|
@ -2,10 +2,10 @@ package net.corda.core.serialization
|
||||
|
||||
/**
|
||||
* This annotation is a marker to indicate which secondary constructors should be considered, and in which
|
||||
* order, for evolving objects during their deserialisation.
|
||||
* order, for evolving objects during their deserialization.
|
||||
*
|
||||
* Versions will be considered in descending order, currently duplicate versions will result in
|
||||
* non deterministic behaviour when deserialising objects
|
||||
* non deterministic behaviour when deserializing objects
|
||||
*/
|
||||
@Target(AnnotationTarget.CONSTRUCTOR)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
|
@ -3,6 +3,6 @@ package net.corda.core.serialization
|
||||
import net.corda.core.CordaException
|
||||
import net.corda.core.crypto.SecureHash
|
||||
|
||||
/** Thrown during deserialisation to indicate that an attachment needed to construct the [WireTransaction] is not found. */
|
||||
/** Thrown during deserialization to indicate that an attachment needed to construct the [WireTransaction] is not found. */
|
||||
@CordaSerializable
|
||||
class MissingAttachmentsException(val ids: List<SecureHash>) : CordaException()
|
@ -0,0 +1,24 @@
|
||||
package net.corda.core.serialization
|
||||
|
||||
/**
|
||||
* Allows CorDapps to provide custom serializers for third party libraries where those libraries cannot
|
||||
* be recompiled with the -parameters flag rendering their classes natively serializable by Corda. In this case
|
||||
* a proxy serializer can be written that extends this type whose purpose is to move between those an
|
||||
* unserializable types and an intermediate representation.
|
||||
*
|
||||
* NOTE: The proxy object should be specified as a seperate class. However, this can be defined within the
|
||||
* scope of the custom serializer.
|
||||
*/
|
||||
interface SerializationCustomSerializer<OBJ, PROXY> {
|
||||
/**
|
||||
* Should facilitate the conversion of the third party object into the serializable
|
||||
* local class specified by [PROXY]
|
||||
*/
|
||||
fun toProxy(obj: OBJ) : PROXY
|
||||
|
||||
/**
|
||||
* Should facilitate the conversion of the proxy object into a new instance of the
|
||||
* unserializable type
|
||||
*/
|
||||
fun fromProxy(proxy: PROXY) : OBJ
|
||||
}
|
@ -6,7 +6,7 @@ import net.corda.core.serialization.SingletonSerializationToken.Companion.single
|
||||
/**
|
||||
* The interfaces and classes in this file allow large, singleton style classes to
|
||||
* mark themselves as needing converting to some form of token representation in the serialised form
|
||||
* and converting back again when deserialising.
|
||||
* and converting back again when deserializing.
|
||||
*
|
||||
* Typically these classes would be used for node services and subsystems that might become reachable from
|
||||
* Fibers and thus sucked into serialization when they are checkpointed.
|
||||
|
@ -50,7 +50,7 @@ data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
|
||||
@Volatile
|
||||
@Transient private var cachedTransaction: CoreTransaction? = null
|
||||
|
||||
/** Lazily calculated access to the deserialised/hashed transaction data. */
|
||||
/** Lazily calculated access to the deserialized/hashed transaction data. */
|
||||
private val transaction: CoreTransaction get() = cachedTransaction ?: txBits.deserialize().apply { cachedTransaction = this }
|
||||
|
||||
/** The id of the contained [WireTransaction]. */
|
||||
|
@ -1,5 +1,7 @@
|
||||
package net.corda.core.crypto
|
||||
|
||||
import com.nhaarman.mockito_kotlin.doReturn
|
||||
import com.nhaarman.mockito_kotlin.whenever
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.SecureHash.Companion.zeroHash
|
||||
import net.corda.core.identity.Party
|
||||
@ -9,7 +11,9 @@ import net.corda.core.transactions.WireTransaction
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.finance.`issued by`
|
||||
import net.corda.finance.contracts.asset.Cash
|
||||
import net.corda.node.services.api.IdentityServiceInternal
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.node.MockServices
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
@ -35,7 +39,9 @@ class PartialMerkleTreeTest {
|
||||
hashed = nodes.map { it.serialize().sha256() }
|
||||
expectedRoot = MerkleTree.getMerkleTree(hashed.toMutableList() + listOf(zeroHash, zeroHash)).hash
|
||||
merkleTree = MerkleTree.getMerkleTree(hashed)
|
||||
testLedger = ledger {
|
||||
testLedger = MockServices(rigorousMock<IdentityServiceInternal>().also {
|
||||
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
|
||||
}, MEGA_CORP.name).ledger(DUMMY_NOTARY) {
|
||||
unverifiedTransaction {
|
||||
attachments(Cash.PROGRAM_ID)
|
||||
output(Cash.PROGRAM_ID, "MEGA_CORP cash",
|
||||
|
@ -50,7 +50,7 @@ class X509NameConstraintsTest {
|
||||
|
||||
val nameConstraints = NameConstraints(acceptableNames, arrayOf())
|
||||
val pathValidator = CertPathValidator.getInstance("PKIX")
|
||||
val certFactory = X509CertificateFactory().delegate
|
||||
val certFactory = X509CertificateFactory()
|
||||
|
||||
assertFailsWith(CertPathValidatorException::class) {
|
||||
val (keystore, trustStore) = makeKeyStores(X500Name("CN=Bank B"), nameConstraints)
|
||||
|
@ -1,5 +1,7 @@
|
||||
package net.corda.core.transactions
|
||||
|
||||
import com.nhaarman.mockito_kotlin.doReturn
|
||||
import com.nhaarman.mockito_kotlin.whenever
|
||||
import net.corda.core.contracts.Contract
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.requireThat
|
||||
@ -7,10 +9,9 @@ import net.corda.core.identity.AbstractParty
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.finance.`issued by`
|
||||
import net.corda.finance.contracts.asset.Cash
|
||||
import net.corda.testing.MEGA_CORP
|
||||
import net.corda.testing.MINI_CORP
|
||||
import net.corda.testing.SerializationEnvironmentRule
|
||||
import net.corda.testing.ledger
|
||||
import net.corda.node.services.api.IdentityServiceInternal
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.node.MockServices
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.time.Instant
|
||||
@ -50,9 +51,13 @@ class TransactionEncumbranceTests {
|
||||
}
|
||||
}
|
||||
|
||||
private val ledgerServices = MockServices(rigorousMock<IdentityServiceInternal>().also {
|
||||
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
|
||||
}, MEGA_CORP.name)
|
||||
|
||||
@Test
|
||||
fun `state can be encumbered`() {
|
||||
ledger {
|
||||
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||
transaction {
|
||||
attachments(Cash.PROGRAM_ID, TEST_TIMELOCK_ID)
|
||||
input(Cash.PROGRAM_ID, state)
|
||||
@ -66,7 +71,7 @@ class TransactionEncumbranceTests {
|
||||
|
||||
@Test
|
||||
fun `state can transition if encumbrance rules are met`() {
|
||||
ledger {
|
||||
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||
unverifiedTransaction {
|
||||
attachments(Cash.PROGRAM_ID, TEST_TIMELOCK_ID)
|
||||
output(Cash.PROGRAM_ID, "state encumbered by 5pm time-lock", state)
|
||||
@ -87,7 +92,7 @@ class TransactionEncumbranceTests {
|
||||
|
||||
@Test
|
||||
fun `state cannot transition if the encumbrance contract fails to verify`() {
|
||||
ledger {
|
||||
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||
unverifiedTransaction {
|
||||
attachments(Cash.PROGRAM_ID, TEST_TIMELOCK_ID)
|
||||
output(Cash.PROGRAM_ID, "state encumbered by 5pm time-lock", state)
|
||||
@ -108,7 +113,7 @@ class TransactionEncumbranceTests {
|
||||
|
||||
@Test
|
||||
fun `state must be consumed along with its encumbrance`() {
|
||||
ledger {
|
||||
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||
unverifiedTransaction {
|
||||
attachments(Cash.PROGRAM_ID, TEST_TIMELOCK_ID)
|
||||
output(Cash.PROGRAM_ID, "state encumbered by 5pm time-lock", encumbrance = 1, contractState = state)
|
||||
@ -127,7 +132,7 @@ class TransactionEncumbranceTests {
|
||||
|
||||
@Test
|
||||
fun `state cannot be encumbered by itself`() {
|
||||
ledger {
|
||||
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||
transaction {
|
||||
attachments(Cash.PROGRAM_ID)
|
||||
input(Cash.PROGRAM_ID, state)
|
||||
@ -140,7 +145,7 @@ class TransactionEncumbranceTests {
|
||||
|
||||
@Test
|
||||
fun `encumbrance state index must be valid`() {
|
||||
ledger {
|
||||
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||
transaction {
|
||||
attachments(Cash.PROGRAM_ID, TEST_TIMELOCK_ID)
|
||||
input(Cash.PROGRAM_ID, state)
|
||||
@ -154,7 +159,7 @@ class TransactionEncumbranceTests {
|
||||
|
||||
@Test
|
||||
fun `correct encumbrance state must be provided`() {
|
||||
ledger {
|
||||
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||
unverifiedTransaction {
|
||||
attachments(Cash.PROGRAM_ID, TEST_TIMELOCK_ID)
|
||||
output(Cash.PROGRAM_ID, "state encumbered by some other state", encumbrance = 1, contractState = state)
|
||||
|
@ -4,12 +4,12 @@ import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.finance.contracts.asset.DUMMY_CASH_ISSUER_KEY
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
import net.corda.testing.node.MockAttachment
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.math.BigInteger
|
||||
import java.security.KeyPair
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
@ -19,6 +19,7 @@ class TransactionTests {
|
||||
private companion object {
|
||||
val DUMMY_KEY_1 = generateKeyPair()
|
||||
val DUMMY_KEY_2 = generateKeyPair()
|
||||
val DUMMY_CASH_ISSUER_KEY = entropyToKeyPair(BigInteger.valueOf(10))
|
||||
}
|
||||
|
||||
@Rule
|
||||
|
@ -9,11 +9,11 @@ a developer environment.
|
||||
IDE - IntelliJ
|
||||
--------------
|
||||
|
||||
IntelliJ (R3's preferred IDE) integrates well with gradle (our chosen build, deployment and CLI tool). IntelliJ understands gradle
|
||||
tasks and dependencies, automatically loading them in the background when a project is first opened or the gradle
|
||||
project changes. Occasionally, however, you may need to refresh the gradle project manually - but this is hinted to you
|
||||
by the IDE. It's a good idea to do this before carrying on with other work (and in fact you may find it is essential to pick
|
||||
up new libraries, etc.).
|
||||
IntelliJ (the preferred IDE for Corda) integrates well with gradle (Corda's default build, deployment and CLI tool).
|
||||
IntelliJ understands gradle tasks and dependencies, automatically loading them in the background when a project is
|
||||
first opened or the gradle project changes. Occasionally, however, you may need to refresh the gradle project manually
|
||||
- but this is hinted to you by the IDE. It's a good idea to do this before carrying on with other work (and in fact you
|
||||
may find it is essential to pick up new libraries, etc.).
|
||||
|
||||
There are some great resources about how to get started using IntelliJ. As opposed to trying to repeat them here, we advise
|
||||
you to go to the `IntelliJ docs here <https://www.jetbrains.com/idea/documentation/>`_.
|
||||
|
@ -1,7 +1,7 @@
|
||||
Code style guide
|
||||
================
|
||||
|
||||
This document explains the coding style used in the R3 prototyping repository. You will be expected to follow these
|
||||
This document explains the coding style used in the Corda repository. You will be expected to follow these
|
||||
recommendations when submitting patches for review. Please take the time to read them and internalise them, to save
|
||||
time during code review.
|
||||
|
||||
|
73
docs/source/cordapp-custom-serializers.rst
Normal file
73
docs/source/cordapp-custom-serializers.rst
Normal file
@ -0,0 +1,73 @@
|
||||
Pluggable Serializers for CorDapps
|
||||
==================================
|
||||
|
||||
.. contents::
|
||||
|
||||
To be serializable by Corda Java classes must be compiled with the -parameters switch to enable matching of its properties
|
||||
to constructor parameters. This is important because Corda's internal AMQP serialization scheme will only construct
|
||||
objects using their constructors. However, when recompilation isn't possible, or classes are built in such a way that
|
||||
they cannot be easily modified for simple serialization, CorDapps can provide custom proxy serializers that Corda
|
||||
can use to move from types it cannot serialize to an interim representation that it can with the transformation to and
|
||||
from this proxy object being handled by the supplied serializer.
|
||||
|
||||
Serializer Location
|
||||
-------------------
|
||||
Custom serializer classes should follow the rules for including classes found in :doc:`cordapp-build-systems`
|
||||
|
||||
Writing a Custom Serializer
|
||||
---------------------------
|
||||
Serializers must
|
||||
* Inherit from net.corda.core.serialization.SerializationCustomSerializer
|
||||
* Provide a proxy class to transform the object to and from
|
||||
* Implement the ``toProxy`` and ``fromProxy`` methods
|
||||
|
||||
Serializers inheriting from SerializationCustomSerializer have to implement two methods and two types.
|
||||
|
||||
Example
|
||||
-------
|
||||
Consider this example class
|
||||
|
||||
|
||||
.. sourcecode:: java
|
||||
|
||||
public final class Example {
|
||||
private final Int a
|
||||
private final Int b
|
||||
|
||||
private Example(Int a, Int b) {
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
}
|
||||
|
||||
public static Example of (int[] a) { return Example(a[0], a[1]); }
|
||||
|
||||
public int getA() { return a; }
|
||||
public int getB() { return b; }
|
||||
}
|
||||
|
||||
Without a custom serializer we cannot serialize this class as there is no public constructor that facilitates the
|
||||
initialisation of all of its properties.
|
||||
|
||||
To be serializable by Corda this would require a custom serializer as follows:
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
class ExampleSerializer : SerializationCustomSerializer<Example, ExampleSerializer.Proxy> {
|
||||
data class Proxy(val a: Int, val b: Int)
|
||||
|
||||
override fun toProxy(obj: Example) = Proxy(obj.a, obj.b)
|
||||
|
||||
override fun fromProxy(proxy: Proxy) : Example {
|
||||
val constructorArg = IntArray(2);
|
||||
constructorArg[0] = proxy.a
|
||||
constructorArg[1] = proxy.b
|
||||
return Example.create(constructorArg)
|
||||
}
|
||||
}
|
||||
|
||||
Whitelisting
|
||||
------------
|
||||
By writing a custom serializer for a class it has the effect of adding that class to the whitelist, meaning such
|
||||
classes don't need explicitly adding to the CorDapp's whitelist.
|
||||
|
||||
|
@ -48,10 +48,6 @@ handling, and ensures the Corda service is run at boot.
|
||||
trustStorePassword : "trustpass"
|
||||
useHTTPS : false
|
||||
devMode : false
|
||||
networkMapService {
|
||||
address="networkmap.foo.bar.com:10002"
|
||||
legalName="O=FooBar NetworkMap, L=Dublin, C=IE"
|
||||
}
|
||||
rpcUsers=[
|
||||
{
|
||||
user=corda
|
||||
@ -223,10 +219,6 @@ at boot, and means the Corda service stays running with no users connected to th
|
||||
extraAdvertisedServiceIds: [ "" ]
|
||||
useHTTPS : false
|
||||
devMode : false
|
||||
networkMapService {
|
||||
address="networkmap.foo.bar.com:10002"
|
||||
legalName="O=FooBar NetworkMap, L=Dublin, C=IE"
|
||||
}
|
||||
rpcUsers=[
|
||||
{
|
||||
user=corda
|
||||
|
@ -6,7 +6,9 @@ import net.corda.core.utilities.OpaqueBytes;
|
||||
import net.corda.finance.contracts.ICommercialPaperState;
|
||||
import net.corda.finance.contracts.JavaCommercialPaper;
|
||||
import net.corda.finance.contracts.asset.Cash;
|
||||
import net.corda.node.services.api.IdentityServiceInternal;
|
||||
import net.corda.testing.SerializationEnvironmentRule;
|
||||
import net.corda.testing.node.MockServices;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
@ -19,11 +21,21 @@ import static net.corda.testing.CoreTestUtils.*;
|
||||
import static net.corda.testing.NodeTestUtils.ledger;
|
||||
import static net.corda.testing.NodeTestUtils.transaction;
|
||||
import static net.corda.testing.TestConstants.*;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
|
||||
public class CommercialPaperTest {
|
||||
@Rule
|
||||
public final SerializationEnvironmentRule testSerialization = new SerializationEnvironmentRule();
|
||||
private final OpaqueBytes defaultRef = new OpaqueBytes(new byte[]{123});
|
||||
private final MockServices ledgerServices;
|
||||
|
||||
{
|
||||
IdentityServiceInternal identityService = rigorousMock(IdentityServiceInternal.class);
|
||||
doReturn(getMEGA_CORP()).when(identityService).partyFromKey(getMEGA_CORP_PUBKEY());
|
||||
doReturn(null).when(identityService).partyFromKey(getBIG_CORP_PUBKEY());
|
||||
doReturn(null).when(identityService).partyFromKey(getALICE_PUBKEY());
|
||||
ledgerServices = new MockServices(identityService, getMEGA_CORP().getName());
|
||||
}
|
||||
|
||||
// DOCSTART 1
|
||||
private ICommercialPaperState getPaper() {
|
||||
@ -40,7 +52,7 @@ public class CommercialPaperTest {
|
||||
@Test
|
||||
public void simpleCP() {
|
||||
ICommercialPaperState inState = getPaper();
|
||||
ledger(l -> {
|
||||
ledger(ledgerServices, getDUMMY_NOTARY(), l -> {
|
||||
l.transaction(tx -> {
|
||||
tx.attachments(JCP_PROGRAM_ID);
|
||||
tx.input(JCP_PROGRAM_ID, inState);
|
||||
@ -55,7 +67,7 @@ public class CommercialPaperTest {
|
||||
@Test
|
||||
public void simpleCPMove() {
|
||||
ICommercialPaperState inState = getPaper();
|
||||
ledger(l -> {
|
||||
ledger(ledgerServices, getDUMMY_NOTARY(), l -> {
|
||||
l.transaction(tx -> {
|
||||
tx.input(JCP_PROGRAM_ID, inState);
|
||||
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
|
||||
@ -71,7 +83,7 @@ public class CommercialPaperTest {
|
||||
@Test
|
||||
public void simpleCPMoveFails() {
|
||||
ICommercialPaperState inState = getPaper();
|
||||
ledger(l -> {
|
||||
ledger(ledgerServices, getDUMMY_NOTARY(), l -> {
|
||||
l.transaction(tx -> {
|
||||
tx.input(JCP_PROGRAM_ID, inState);
|
||||
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
|
||||
@ -87,7 +99,7 @@ public class CommercialPaperTest {
|
||||
@Test
|
||||
public void simpleCPMoveSuccess() {
|
||||
ICommercialPaperState inState = getPaper();
|
||||
ledger(l -> {
|
||||
ledger(ledgerServices, getDUMMY_NOTARY(), l -> {
|
||||
l.transaction(tx -> {
|
||||
tx.input(JCP_PROGRAM_ID, inState);
|
||||
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
|
||||
@ -104,7 +116,7 @@ public class CommercialPaperTest {
|
||||
// DOCSTART 6
|
||||
@Test
|
||||
public void simpleIssuanceWithTweak() {
|
||||
ledger(l -> {
|
||||
ledger(ledgerServices, getDUMMY_NOTARY(), l -> {
|
||||
l.transaction(tx -> {
|
||||
tx.output(JCP_PROGRAM_ID, "paper", getPaper()); // Some CP is issued onto the ledger by MegaCorp.
|
||||
tx.attachments(JCP_PROGRAM_ID);
|
||||
@ -125,7 +137,7 @@ public class CommercialPaperTest {
|
||||
// DOCSTART 7
|
||||
@Test
|
||||
public void simpleIssuanceWithTweakTopLevelTx() {
|
||||
transaction(tx -> {
|
||||
transaction(ledgerServices, getDUMMY_NOTARY(), tx -> {
|
||||
tx.output(JCP_PROGRAM_ID, "paper", getPaper()); // Some CP is issued onto the ledger by MegaCorp.
|
||||
tx.attachments(JCP_PROGRAM_ID);
|
||||
tx.tweak(tw -> {
|
||||
@ -144,7 +156,7 @@ public class CommercialPaperTest {
|
||||
@Test
|
||||
public void chainCommercialPaper() {
|
||||
PartyAndReference issuer = getMEGA_CORP().ref(defaultRef);
|
||||
ledger(l -> {
|
||||
ledger(ledgerServices, getDUMMY_NOTARY(), l -> {
|
||||
l.unverifiedTransaction(tx -> {
|
||||
tx.output(Cash.PROGRAM_ID, "alice's $900",
|
||||
new Cash.State(issuedBy(DOLLARS(900), issuer), getALICE()));
|
||||
@ -180,7 +192,7 @@ public class CommercialPaperTest {
|
||||
@Test
|
||||
public void chainCommercialPaperDoubleSpend() {
|
||||
PartyAndReference issuer = getMEGA_CORP().ref(defaultRef);
|
||||
ledger(l -> {
|
||||
ledger(ledgerServices, getDUMMY_NOTARY(), l -> {
|
||||
l.unverifiedTransaction(tx -> {
|
||||
tx.output(Cash.PROGRAM_ID, "alice's $900",
|
||||
new Cash.State(issuedBy(DOLLARS(900), issuer), getALICE()));
|
||||
@ -226,7 +238,7 @@ public class CommercialPaperTest {
|
||||
@Test
|
||||
public void chainCommercialPaperTweak() {
|
||||
PartyAndReference issuer = getMEGA_CORP().ref(defaultRef);
|
||||
ledger(l -> {
|
||||
ledger(ledgerServices, getDUMMY_NOTARY(), l -> {
|
||||
l.unverifiedTransaction(tx -> {
|
||||
tx.output(Cash.PROGRAM_ID, "alice's $900",
|
||||
new Cash.State(issuedBy(DOLLARS(900), issuer), getALICE()));
|
||||
|
@ -1,5 +1,7 @@
|
||||
package net.corda.docs.tutorial.testdsl
|
||||
|
||||
import com.nhaarman.mockito_kotlin.doReturn
|
||||
import com.nhaarman.mockito_kotlin.whenever
|
||||
import net.corda.core.utilities.days
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.finance.`issued by`
|
||||
@ -8,7 +10,9 @@ import net.corda.finance.contracts.CommercialPaper
|
||||
import net.corda.finance.contracts.ICommercialPaperState
|
||||
import net.corda.finance.contracts.asset.CASH
|
||||
import net.corda.finance.contracts.asset.Cash
|
||||
import net.corda.node.services.api.IdentityServiceInternal
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.node.MockServices
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
@ -16,6 +20,11 @@ class CommercialPaperTest {
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
private val ledgerServices = MockServices(rigorousMock<IdentityServiceInternal>().also {
|
||||
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
|
||||
doReturn(null).whenever(it).partyFromKey(BIG_CORP_PUBKEY)
|
||||
doReturn(null).whenever(it).partyFromKey(ALICE_PUBKEY)
|
||||
}, MEGA_CORP.name)
|
||||
|
||||
// DOCSTART 1
|
||||
fun getPaper(): ICommercialPaperState = CommercialPaper.State(
|
||||
@ -30,7 +39,7 @@ class CommercialPaperTest {
|
||||
@Test
|
||||
fun simpleCP() {
|
||||
val inState = getPaper()
|
||||
ledger {
|
||||
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||
transaction {
|
||||
attachments(CP_PROGRAM_ID)
|
||||
input(CP_PROGRAM_ID, inState)
|
||||
@ -44,7 +53,7 @@ class CommercialPaperTest {
|
||||
@Test
|
||||
fun simpleCPMove() {
|
||||
val inState = getPaper()
|
||||
ledger {
|
||||
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||
transaction {
|
||||
input(CP_PROGRAM_ID, inState)
|
||||
command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move())
|
||||
@ -59,7 +68,7 @@ class CommercialPaperTest {
|
||||
@Test
|
||||
fun simpleCPMoveFails() {
|
||||
val inState = getPaper()
|
||||
ledger {
|
||||
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||
transaction {
|
||||
input(CP_PROGRAM_ID, inState)
|
||||
command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move())
|
||||
@ -74,7 +83,7 @@ class CommercialPaperTest {
|
||||
@Test
|
||||
fun simpleCPMoveSuccess() {
|
||||
val inState = getPaper()
|
||||
ledger {
|
||||
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||
transaction {
|
||||
input(CP_PROGRAM_ID, inState)
|
||||
command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move())
|
||||
@ -90,7 +99,7 @@ class CommercialPaperTest {
|
||||
// DOCSTART 6
|
||||
@Test
|
||||
fun `simple issuance with tweak`() {
|
||||
ledger {
|
||||
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||
transaction {
|
||||
output(CP_PROGRAM_ID, "paper", getPaper()) // Some CP is issued onto the ledger by MegaCorp.
|
||||
attachments(CP_PROGRAM_ID)
|
||||
@ -111,7 +120,7 @@ class CommercialPaperTest {
|
||||
// DOCSTART 7
|
||||
@Test
|
||||
fun `simple issuance with tweak and top level transaction`() {
|
||||
transaction {
|
||||
ledgerServices.transaction(DUMMY_NOTARY) {
|
||||
output(CP_PROGRAM_ID, "paper", getPaper()) // Some CP is issued onto the ledger by MegaCorp.
|
||||
attachments(CP_PROGRAM_ID)
|
||||
tweak {
|
||||
@ -131,8 +140,7 @@ class CommercialPaperTest {
|
||||
@Test
|
||||
fun `chain commercial paper`() {
|
||||
val issuer = MEGA_CORP.ref(123)
|
||||
|
||||
ledger {
|
||||
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||
unverifiedTransaction {
|
||||
attachments(Cash.PROGRAM_ID)
|
||||
output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH issuedBy issuer ownedBy ALICE)
|
||||
@ -165,7 +173,7 @@ class CommercialPaperTest {
|
||||
@Test
|
||||
fun `chain commercial paper double spend`() {
|
||||
val issuer = MEGA_CORP.ref(123)
|
||||
ledger {
|
||||
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||
unverifiedTransaction {
|
||||
attachments(Cash.PROGRAM_ID)
|
||||
output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH issuedBy issuer ownedBy ALICE)
|
||||
@ -207,7 +215,7 @@ class CommercialPaperTest {
|
||||
@Test
|
||||
fun `chain commercial tweak`() {
|
||||
val issuer = MEGA_CORP.ref(123)
|
||||
ledger {
|
||||
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||
unverifiedTransaction {
|
||||
attachments(Cash.PROGRAM_ID)
|
||||
output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH issuedBy issuer ownedBy ALICE)
|
||||
|
@ -10,14 +10,9 @@ dataSourceProperties : {
|
||||
p2pAddress : "my-corda-node:10002"
|
||||
rpcAddress : "my-corda-node:10003"
|
||||
webAddress : "localhost:10004"
|
||||
networkMapService : {
|
||||
address : "my-network-map:10000"
|
||||
legalName : "O=Network Map Service,OU=corda,L=London,C=GB"
|
||||
}
|
||||
useHTTPS : false
|
||||
rpcUsers : [
|
||||
{ username=user1, password=letmein, permissions=[ StartProtocol.net.corda.protocols.CashProtocol ] }
|
||||
]
|
||||
devMode : true
|
||||
// Certificate signing service will be hosted by R3 in the near future.
|
||||
// certificateSigningService : "https://testnet.certificate.corda.net"
|
||||
|
@ -53,8 +53,6 @@ Protocol
|
||||
The old name for a Corda "Flow"
|
||||
Quasar
|
||||
A library that provides performant lightweight threads that can be suspended and restored extremely quickly.
|
||||
R3
|
||||
The consortium behind Corda
|
||||
SIMM
|
||||
Standard Initial Margin Model. A way of determining a counterparty's margin payment to another counterparty based on a collection of trades such that, in the event of default, the receiving counterparty has limited exposure.
|
||||
Serialization
|
||||
|
@ -12,6 +12,20 @@ Unreleased
|
||||
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.
|
||||
|
||||
* **AMQP Enabled**
|
||||
|
||||
AMQP Serialization is now enabled for both peer to peer communication and writing states to the vault. This change
|
||||
brings a stable format Corda can support internally throughout it's lifetime that meets the needs of Corda and our
|
||||
users.
|
||||
|
||||
* **Custom Serializers**
|
||||
|
||||
To allow interop with third party libraries that cannot be recompiled we add functionality that allows custom serializers
|
||||
to be written for those classes. If needed, a proxy object can be created as an interim step that allows Corda's internal
|
||||
serializers to operate on those types.
|
||||
|
||||
A good example of this is the SIMM valuation demo which has a number of such serializers defined in the plugin/customserializers package
|
||||
|
||||
Release 2.0
|
||||
----------
|
||||
Following quickly on the heels of the release of Corda 1.0, Corda version 2.0 consolidates
|
||||
|
@ -45,8 +45,6 @@ The most important fields regarding network configuration are:
|
||||
resolvable name of a machine in a VPN.
|
||||
* ``rpcAddress``: The address to which Artemis will bind for RPC calls.
|
||||
* ``webAddress``: The address the webserver should bind. Note that the port must be distinct from that of ``p2pAddress`` and ``rpcAddress`` if they are on the same machine.
|
||||
* ``networkMapService``: Details of the node running the network map service. If it's this node that's running the service
|
||||
then this field must not be specified.
|
||||
|
||||
Starting the nodes
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
@ -1,18 +1,31 @@
|
||||
package net.corda.finance.contracts.universal
|
||||
|
||||
import com.nhaarman.mockito_kotlin.doReturn
|
||||
import com.nhaarman.mockito_kotlin.whenever
|
||||
import net.corda.core.crypto.entropyToKeyPair
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.finance.contracts.BusinessCalendar
|
||||
import net.corda.finance.contracts.FixOf
|
||||
import net.corda.finance.contracts.Frequency
|
||||
import net.corda.finance.contracts.Tenor
|
||||
import net.corda.node.services.api.IdentityServiceInternal
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.node.MockServices
|
||||
import net.corda.testing.node.makeTestIdentityService
|
||||
import org.junit.Ignore
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.math.BigInteger
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
|
||||
fun transaction(script: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail) = run {
|
||||
net.corda.testing.transaction(cordappPackages = listOf("net.corda.finance.contracts.universal"), dsl = script)
|
||||
MockServices(listOf("net.corda.finance.contracts.universal"), rigorousMock<IdentityServiceInternal>().also {
|
||||
listOf(acmeCorp, highStreetBank, momAndPop).forEach { party ->
|
||||
doReturn(null).whenever(it).partyFromKey(party.owningKey)
|
||||
}
|
||||
}, MEGA_CORP.name).transaction(DUMMY_NOTARY, script)
|
||||
}
|
||||
|
||||
class Cap {
|
||||
|
@ -7,10 +7,8 @@ import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.contracts.Amount.Companion.sumOrThrow
|
||||
import net.corda.core.crypto.NullKeys.NULL_PARTY
|
||||
import net.corda.core.crypto.entropyToKeyPair
|
||||
import net.corda.core.crypto.toStringShort
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.identity.PartyAndCertificate
|
||||
import net.corda.core.internal.Emoji
|
||||
@ -25,7 +23,6 @@ import net.corda.finance.schemas.CashSchemaV1
|
||||
import net.corda.finance.utils.sumCash
|
||||
import net.corda.finance.utils.sumCashOrNull
|
||||
import net.corda.finance.utils.sumCashOrZero
|
||||
import java.math.BigInteger
|
||||
import java.security.PublicKey
|
||||
import java.util.*
|
||||
|
||||
@ -342,14 +339,7 @@ class Cash : OnLedgerAsset<Currency, Cash.Commands, Cash.State>() {
|
||||
}
|
||||
|
||||
// Unit testing helpers. These could go in a separate file but it's hardly worth it for just a few functions.
|
||||
|
||||
/** A dummy, randomly generated issuer party by the name of "Snake Oil Issuer" */
|
||||
val DUMMY_CASH_ISSUER_NAME = CordaX500Name(organisation = "Snake Oil Issuer", locality = "London", country = "GB")
|
||||
/** A randomly generated key. */
|
||||
val DUMMY_CASH_ISSUER_KEY by lazy { entropyToKeyPair(BigInteger.valueOf(10)) }
|
||||
/** A dummy, randomly generated issuer party by the name of "Snake Oil Issuer" */
|
||||
val DUMMY_CASH_ISSUER by lazy { Party(DUMMY_CASH_ISSUER_NAME, DUMMY_CASH_ISSUER_KEY.public).ref(1) }
|
||||
/** An extension property that lets you write 100.DOLLARS.CASH */
|
||||
val Amount<Currency>.CASH: Cash.State get() = Cash.State(Amount(quantity, Issued(DUMMY_CASH_ISSUER, token)), NULL_PARTY)
|
||||
val Amount<Currency>.CASH: Cash.State get() = Cash.State(Amount(quantity, Issued(NULL_PARTY.ref(1), token)), NULL_PARTY)
|
||||
/** An extension property that lets you get a cash state from an issued token, under the [NULL_PARTY] */
|
||||
val Amount<Issued<Currency>>.STATE: Cash.State get() = Cash.State(this, NULL_PARTY)
|
||||
|
@ -6,10 +6,8 @@ import net.corda.finance.contracts.NetType
|
||||
import net.corda.finance.contracts.NettableState
|
||||
import net.corda.finance.contracts.asset.Obligation.Lifecycle.NORMAL
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.entropyToKeyPair
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.Emoji
|
||||
import net.corda.core.internal.VisibleForTesting
|
||||
@ -22,7 +20,6 @@ import net.corda.finance.utils.sumFungibleOrNull
|
||||
import net.corda.finance.utils.sumObligations
|
||||
import net.corda.finance.utils.sumObligationsOrNull
|
||||
import net.corda.finance.utils.sumObligationsOrZero
|
||||
import java.math.BigInteger
|
||||
import java.security.PublicKey
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
@ -791,8 +788,3 @@ fun <T : Any> Obligation.State<T>.ownedBy(owner: AbstractParty) = copy(beneficia
|
||||
|
||||
@Suppress("unused")
|
||||
fun <T : Any> Obligation.State<T>.issuedBy(party: AnonymousParty) = copy(obligor = party)
|
||||
|
||||
/** A randomly generated key. */
|
||||
val DUMMY_OBLIGATION_ISSUER_KEY by lazy { entropyToKeyPair(BigInteger.valueOf(10)) }
|
||||
/** A dummy, randomly generated issuer party by the name of "Snake Oil Issuer" */
|
||||
val DUMMY_OBLIGATION_ISSUER by lazy { Party(CordaX500Name(organisation = "Snake Oil Issuer", locality = "London", country = "GB"), DUMMY_OBLIGATION_ISSUER_KEY.public) }
|
||||
|
@ -3,8 +3,10 @@ package net.corda.finance.contracts.asset;
|
||||
import net.corda.core.contracts.PartyAndReference;
|
||||
import net.corda.core.identity.AnonymousParty;
|
||||
import net.corda.core.utilities.OpaqueBytes;
|
||||
import net.corda.node.services.api.IdentityServiceInternal;
|
||||
import net.corda.testing.DummyCommandData;
|
||||
import net.corda.testing.SerializationEnvironmentRule;
|
||||
import net.corda.testing.node.MockServices;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
@ -12,6 +14,8 @@ import static net.corda.finance.Currencies.DOLLARS;
|
||||
import static net.corda.finance.Currencies.issuedBy;
|
||||
import static net.corda.testing.CoreTestUtils.*;
|
||||
import static net.corda.testing.NodeTestUtils.transaction;
|
||||
import static net.corda.testing.TestConstants.getDUMMY_NOTARY;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
|
||||
/**
|
||||
* This is an incomplete Java replica of CashTests.kt to show how to use the Java test DSL
|
||||
@ -26,7 +30,10 @@ public class CashTestsJava {
|
||||
|
||||
@Test
|
||||
public void trivial() {
|
||||
transaction(tx -> {
|
||||
IdentityServiceInternal identityService = rigorousMock(IdentityServiceInternal.class);
|
||||
doReturn(getMEGA_CORP()).when(identityService).partyFromKey(getMEGA_CORP_PUBKEY());
|
||||
doReturn(getMINI_CORP()).when(identityService).partyFromKey(getMINI_CORP_PUBKEY());
|
||||
transaction(new MockServices(identityService, getMEGA_CORP().getName()), getDUMMY_NOTARY(), tx -> {
|
||||
tx.attachment(Cash.PROGRAM_ID);
|
||||
|
||||
tx.input(Cash.PROGRAM_ID, inState);
|
||||
|
@ -1,7 +1,11 @@
|
||||
package net.corda.finance.contracts
|
||||
|
||||
import com.nhaarman.mockito_kotlin.doReturn
|
||||
import com.nhaarman.mockito_kotlin.whenever
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.entropyToKeyPair
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.Vault
|
||||
import net.corda.core.node.services.VaultService
|
||||
@ -12,6 +16,7 @@ import net.corda.core.utilities.seconds
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.finance.`issued by`
|
||||
import net.corda.finance.contracts.asset.*
|
||||
import net.corda.node.services.api.IdentityServiceInternal
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.contracts.VaultFiller
|
||||
import net.corda.testing.node.MockServices
|
||||
@ -21,6 +26,7 @@ import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Parameterized
|
||||
import java.math.BigInteger
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
import kotlin.test.assertFailsWith
|
||||
@ -84,6 +90,10 @@ class CommercialPaperTestsGeneric {
|
||||
@Parameterized.Parameters
|
||||
@JvmStatic
|
||||
fun data() = listOf(JavaCommercialPaperTest(), KotlinCommercialPaperTest(), KotlinCommercialPaperLegacyTest())
|
||||
|
||||
private val DUMMY_CASH_ISSUER_KEY = entropyToKeyPair(BigInteger.valueOf(10))
|
||||
private val DUMMY_CASH_ISSUER_IDENTITY = getTestPartyAndCertificate(Party(CordaX500Name("Snake Oil Issuer", "London", "GB"), DUMMY_CASH_ISSUER_KEY.public))
|
||||
private val DUMMY_CASH_ISSUER = DUMMY_CASH_ISSUER_IDENTITY.party.ref(1)
|
||||
}
|
||||
|
||||
@Parameterized.Parameter
|
||||
@ -92,11 +102,16 @@ class CommercialPaperTestsGeneric {
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
val issuer = MEGA_CORP.ref(123)
|
||||
private val ledgerServices = MockServices(rigorousMock<IdentityServiceInternal>().also {
|
||||
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
|
||||
doReturn(MINI_CORP).whenever(it).partyFromKey(MINI_CORP_PUBKEY)
|
||||
doReturn(null).whenever(it).partyFromKey(ALICE_PUBKEY)
|
||||
}, MEGA_CORP.name)
|
||||
|
||||
@Test
|
||||
fun `trade lifecycle test`() {
|
||||
val someProfits = 1200.DOLLARS `issued by` issuer
|
||||
ledger {
|
||||
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||
unverifiedTransaction {
|
||||
attachment(Cash.PROGRAM_ID)
|
||||
output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH issuedBy issuer ownedBy ALICE)
|
||||
@ -162,6 +177,10 @@ class CommercialPaperTestsGeneric {
|
||||
}
|
||||
}
|
||||
|
||||
private fun transaction(script: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail) = run {
|
||||
ledgerServices.transaction(DUMMY_NOTARY, script)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `key mismatch at issue`() {
|
||||
transaction {
|
||||
|
@ -3,11 +3,9 @@ package net.corda.finance.contracts.asset
|
||||
import com.nhaarman.mockito_kotlin.*
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.entropyToKeyPair
|
||||
import net.corda.core.crypto.generateKeyPair
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.identity.*
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.node.services.VaultService
|
||||
import net.corda.core.node.services.queryBy
|
||||
@ -32,10 +30,15 @@ import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.math.BigInteger
|
||||
import java.util.*
|
||||
import kotlin.test.*
|
||||
|
||||
class CashTests {
|
||||
companion object {
|
||||
private val DUMMY_CASH_ISSUER_IDENTITY = getTestPartyAndCertificate(Party(CordaX500Name("Snake Oil Issuer", "London", "GB"), entropyToKeyPair(BigInteger.valueOf(10)).public))
|
||||
}
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
@ -86,7 +89,7 @@ class CashTests {
|
||||
ourIdentity = ourServices.myInfo.singleIdentity()
|
||||
miniCorpAnonymised = miniCorpServices.myInfo.singleIdentityAndCert().party.anonymise()
|
||||
(miniCorpServices.myInfo.legalIdentitiesAndCerts + megaCorpServices.myInfo.legalIdentitiesAndCerts + notaryServices.myInfo.legalIdentitiesAndCerts).forEach { identity ->
|
||||
ourServices.identityService.verifyAndRegisterIdentity(identity)
|
||||
ourServices.identityService.verifyAndRegisterIdentity(identity) // TODO: Configure a mock identity service instead.
|
||||
}
|
||||
|
||||
// Create some cash. Any attempt to spend >$500 will require multiple issuers to be involved.
|
||||
@ -113,6 +116,15 @@ class CashTests {
|
||||
database.close()
|
||||
}
|
||||
|
||||
private fun transaction(script: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail) = run {
|
||||
MockServices(rigorousMock<IdentityServiceInternal>().also {
|
||||
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
|
||||
doReturn(MINI_CORP).whenever(it).partyFromKey(MINI_CORP_PUBKEY)
|
||||
doReturn(null).whenever(it).partyFromKey(ALICE_PUBKEY)
|
||||
doReturn(null).whenever(it).partyFromKey(BOB_PUBKEY)
|
||||
}, MEGA_CORP.name).transaction(DUMMY_NOTARY, script)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun trivial() {
|
||||
transaction {
|
||||
@ -779,7 +791,7 @@ class CashTests {
|
||||
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) {
|
||||
mockService.ledger(DUMMY_NOTARY) {
|
||||
unverifiedTransaction {
|
||||
attachment(Cash.PROGRAM_ID)
|
||||
output(Cash.PROGRAM_ID, "MEGA_CORP cash",
|
||||
|
@ -5,9 +5,12 @@ import com.nhaarman.mockito_kotlin.whenever
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.NullKeys.NULL_PARTY
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.entropyToKeyPair
|
||||
import net.corda.core.crypto.sha256
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.NonEmptySet
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
@ -24,6 +27,7 @@ import net.corda.testing.contracts.DummyState
|
||||
import net.corda.testing.node.MockServices
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.math.BigInteger
|
||||
import java.time.Instant
|
||||
import java.time.temporal.ChronoUnit
|
||||
import java.util.*
|
||||
@ -33,6 +37,10 @@ import kotlin.test.assertNotEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class ObligationTests {
|
||||
companion object {
|
||||
private val DUMMY_OBLIGATION_ISSUER = Party(CordaX500Name("Snake Oil Issuer", "London", "GB"), entropyToKeyPair(BigInteger.valueOf(10)).public)
|
||||
}
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
@ -56,12 +64,15 @@ class ObligationTests {
|
||||
private val outState = inState.copy(beneficiary = AnonymousParty(BOB_PUBKEY))
|
||||
private val miniCorpServices = MockServices(listOf("net.corda.finance.contracts.asset"), rigorousMock(), MINI_CORP.name, MINI_CORP_KEY)
|
||||
private val notaryServices = MockServices(rigorousMock(), MEGA_CORP.name, DUMMY_NOTARY_KEY)
|
||||
private val mockService = MockServices(listOf("net.corda.finance.contracts.asset"), rigorousMock<IdentityServiceInternal>().also {
|
||||
private val identityService = rigorousMock<IdentityServiceInternal>().also {
|
||||
doReturn(null).whenever(it).partyFromKey(ALICE_PUBKEY)
|
||||
doReturn(null).whenever(it).partyFromKey(BOB_PUBKEY)
|
||||
doReturn(null).whenever(it).partyFromKey(CHARLIE.owningKey)
|
||||
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
|
||||
}, MEGA_CORP.name)
|
||||
|
||||
doReturn(MINI_CORP).whenever(it).partyFromKey(MINI_CORP_PUBKEY)
|
||||
}
|
||||
private val mockService = MockServices(listOf("net.corda.finance.contracts.asset"), identityService, MEGA_CORP.name)
|
||||
private val ledgerServices get() = MockServices(identityService, MEGA_CORP.name)
|
||||
private fun cashObligationTestRoots(
|
||||
group: LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>
|
||||
) = group.apply {
|
||||
@ -74,6 +85,10 @@ class ObligationTests {
|
||||
}
|
||||
}
|
||||
|
||||
private fun transaction(script: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail) = run {
|
||||
ledgerServices.transaction(DUMMY_NOTARY, script)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun trivial() {
|
||||
transaction {
|
||||
@ -347,7 +362,7 @@ class ObligationTests {
|
||||
@Test
|
||||
fun `close-out netting`() {
|
||||
// Try netting out two obligations
|
||||
ledger(mockService) {
|
||||
mockService.ledger(DUMMY_NOTARY) {
|
||||
cashObligationTestRoots(this)
|
||||
transaction("Issuance") {
|
||||
attachments(Obligation.PROGRAM_ID)
|
||||
@ -363,7 +378,7 @@ class ObligationTests {
|
||||
|
||||
// Try netting out two obligations, with the third uninvolved obligation left
|
||||
// as-is
|
||||
ledger(mockService) {
|
||||
mockService.ledger(DUMMY_NOTARY) {
|
||||
cashObligationTestRoots(this)
|
||||
transaction("Issuance") {
|
||||
attachments(Obligation.PROGRAM_ID)
|
||||
@ -379,7 +394,7 @@ class ObligationTests {
|
||||
}
|
||||
|
||||
// Try having outputs mis-match the inputs
|
||||
ledger {
|
||||
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||
cashObligationTestRoots(this)
|
||||
transaction("Issuance") {
|
||||
attachments(Obligation.PROGRAM_ID)
|
||||
@ -393,7 +408,7 @@ class ObligationTests {
|
||||
}
|
||||
|
||||
// Have the wrong signature on the transaction
|
||||
ledger {
|
||||
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||
cashObligationTestRoots(this)
|
||||
transaction("Issuance") {
|
||||
attachments(Obligation.PROGRAM_ID)
|
||||
@ -409,7 +424,7 @@ class ObligationTests {
|
||||
@Test
|
||||
fun `payment netting`() {
|
||||
// Try netting out two obligations
|
||||
ledger(mockService) {
|
||||
mockService.ledger(DUMMY_NOTARY) {
|
||||
cashObligationTestRoots(this)
|
||||
transaction("Issuance") {
|
||||
attachments(Obligation.PROGRAM_ID)
|
||||
@ -424,7 +439,7 @@ class ObligationTests {
|
||||
|
||||
// Try netting out two obligations, but only provide one signature. Unlike close-out netting, we need both
|
||||
// signatures for payment netting
|
||||
ledger {
|
||||
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||
cashObligationTestRoots(this)
|
||||
transaction("Issuance") {
|
||||
attachments(Obligation.PROGRAM_ID)
|
||||
@ -437,7 +452,7 @@ class ObligationTests {
|
||||
}
|
||||
|
||||
// Multilateral netting, A -> B -> C which can net down to A -> C
|
||||
ledger(mockService) {
|
||||
mockService.ledger(DUMMY_NOTARY) {
|
||||
cashObligationTestRoots(this)
|
||||
transaction("Issuance") {
|
||||
attachments(Obligation.PROGRAM_ID)
|
||||
@ -452,7 +467,7 @@ class ObligationTests {
|
||||
}
|
||||
|
||||
// Multilateral netting without the key of the receiving party
|
||||
ledger(mockService) {
|
||||
mockService.ledger(DUMMY_NOTARY) {
|
||||
cashObligationTestRoots(this)
|
||||
transaction("Issuance") {
|
||||
attachments(Obligation.PROGRAM_ID)
|
||||
@ -469,7 +484,7 @@ class ObligationTests {
|
||||
@Test
|
||||
fun `cash settlement`() {
|
||||
// Try settling an obligation
|
||||
ledger {
|
||||
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||
cashObligationTestRoots(this)
|
||||
transaction("Settlement") {
|
||||
attachments(Obligation.PROGRAM_ID)
|
||||
@ -485,7 +500,7 @@ class ObligationTests {
|
||||
|
||||
// Try partial settling of an obligation
|
||||
val halfAMillionDollars = 500000.DOLLARS `issued by` defaultIssuer
|
||||
ledger {
|
||||
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||
transaction("Settlement") {
|
||||
attachments(Obligation.PROGRAM_ID, Cash.PROGRAM_ID)
|
||||
input(Obligation.PROGRAM_ID, oneMillionDollars.OBLIGATION between Pair(ALICE, BOB))
|
||||
@ -501,7 +516,7 @@ class ObligationTests {
|
||||
|
||||
// Make sure we can't settle an obligation that's defaulted
|
||||
val defaultedObligation: Obligation.State<Currency> = (oneMillionDollars.OBLIGATION between Pair(ALICE, BOB)).copy(lifecycle = Lifecycle.DEFAULTED)
|
||||
ledger {
|
||||
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||
transaction("Settlement") {
|
||||
attachments(Obligation.PROGRAM_ID, Cash.PROGRAM_ID)
|
||||
input(Obligation.PROGRAM_ID, defaultedObligation) // Alice's defaulted $1,000,000 obligation to Bob
|
||||
@ -514,7 +529,7 @@ class ObligationTests {
|
||||
}
|
||||
|
||||
// Make sure settlement amount must match the amount leaving the ledger
|
||||
ledger {
|
||||
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||
cashObligationTestRoots(this)
|
||||
transaction("Settlement") {
|
||||
attachments(Obligation.PROGRAM_ID)
|
||||
@ -538,7 +553,7 @@ class ObligationTests {
|
||||
val oneUnitFcojObligation = Obligation.State(Obligation.Lifecycle.NORMAL, ALICE,
|
||||
obligationDef, oneUnitFcoj.quantity, NULL_PARTY)
|
||||
// Try settling a simple commodity obligation
|
||||
ledger {
|
||||
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||
unverifiedTransaction {
|
||||
attachments(Obligation.PROGRAM_ID)
|
||||
output(Obligation.PROGRAM_ID, "Alice's 1 FCOJ obligation to Bob", oneUnitFcojObligation between Pair(ALICE, BOB))
|
||||
@ -560,7 +575,7 @@ class ObligationTests {
|
||||
@Test
|
||||
fun `payment default`() {
|
||||
// Try defaulting an obligation without a time-window.
|
||||
ledger {
|
||||
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||
cashObligationTestRoots(this)
|
||||
transaction("Settlement") {
|
||||
attachments(Obligation.PROGRAM_ID)
|
||||
@ -584,7 +599,7 @@ class ObligationTests {
|
||||
}
|
||||
|
||||
// Try defaulting an obligation that is now in the past
|
||||
ledger {
|
||||
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||
transaction {
|
||||
attachments(Obligation.PROGRAM_ID)
|
||||
input(Obligation.PROGRAM_ID, oneMillionDollars.OBLIGATION between Pair(ALICE, BOB) `at` pastTestTime)
|
||||
|
@ -17,9 +17,6 @@ dependencies {
|
||||
|
||||
// TypeSafe Config: for simple and human friendly config files.
|
||||
compile "com.typesafe:config:$typesafe_config_version"
|
||||
|
||||
// Bouncy Castle: for X.500 distinguished name manipulation
|
||||
compile "org.bouncycastle:bcprov-jdk15on:$bouncycastle_version"
|
||||
}
|
||||
|
||||
publish {
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.corda.cordform;
|
||||
|
||||
import org.bouncycastle.asn1.x500.X500Name;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public interface CordformContext {
|
||||
|
@ -1,14 +1,16 @@
|
||||
package net.corda.cordform;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import com.typesafe.config.*;
|
||||
import com.typesafe.config.Config;
|
||||
import com.typesafe.config.ConfigFactory;
|
||||
import com.typesafe.config.ConfigValueFactory;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
public class CordformNode implements NodeDefinition {
|
||||
/**
|
||||
* Path relative to the running node where the serialized NodeInfos are stored.
|
||||
|
@ -117,6 +117,16 @@ open class Cordform : DefaultTask() {
|
||||
.newInstance()
|
||||
}
|
||||
|
||||
/**
|
||||
* The parametersGenerator needn't be compiled until just before our build method, so we load it manually via sourceSets.main.runtimeClasspath.
|
||||
*/
|
||||
private fun loadNetworkParamsGenClass(): Class<*> {
|
||||
val plugin = project.convention.getPlugin(JavaPluginConvention::class.java)
|
||||
val classpath = plugin.sourceSets.getByName(MAIN_SOURCE_SET_NAME).runtimeClasspath
|
||||
val urls = classpath.files.map { it.toURI().toURL() }.toTypedArray()
|
||||
return URLClassLoader(urls, javaClass.classLoader).loadClass("net.corda.nodeapi.internal.NetworkParametersGenerator")
|
||||
}
|
||||
|
||||
/**
|
||||
* This task action will create and install the nodes based on the node configurations added.
|
||||
*/
|
||||
@ -127,6 +137,7 @@ open class Cordform : DefaultTask() {
|
||||
installRunScript()
|
||||
nodes.forEach(Node::build)
|
||||
generateAndInstallNodeInfos()
|
||||
generateAndInstallNetworkParameters()
|
||||
}
|
||||
|
||||
private fun initializeConfiguration() {
|
||||
@ -153,6 +164,16 @@ open class Cordform : DefaultTask() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateAndInstallNetworkParameters() {
|
||||
project.logger.info("Generating and installing network parameters")
|
||||
val networkParamsGenClass = loadNetworkParamsGenClass()
|
||||
val nodeDirs = nodes.map(Node::fullPath)
|
||||
val networkParamsGenObject = networkParamsGenClass.newInstance()
|
||||
val runMethod = networkParamsGenClass.getMethod("run", List::class.java).apply { isAccessible = true }
|
||||
// Call NetworkParametersGenerator.run
|
||||
runMethod.invoke(networkParamsGenObject, nodeDirs)
|
||||
}
|
||||
|
||||
private fun CordformDefinition.getMatchingCordapps(): List<File> {
|
||||
val cordappJars = project.configuration("cordapp").files
|
||||
return cordappPackages.map { `package` ->
|
||||
@ -193,9 +214,10 @@ open class Cordform : DefaultTask() {
|
||||
}
|
||||
|
||||
private fun buildNodeProcesses(): Map<Node, Process> {
|
||||
return nodes
|
||||
.map { buildNodeProcess(it) }
|
||||
.toMap()
|
||||
val command = generateNodeInfoCommand()
|
||||
return nodes.map {
|
||||
it.makeLogDirectory()
|
||||
buildProcess(it, command, "generate-info.log") }.toMap()
|
||||
}
|
||||
|
||||
private fun validateNodeProcessess(nodeProcesses: Map<Node, Process>) {
|
||||
@ -210,14 +232,13 @@ open class Cordform : DefaultTask() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildNodeProcess(node: Node): Pair<Node, Process> {
|
||||
node.makeLogDirectory()
|
||||
val process = ProcessBuilder(generateNodeInfoCommand())
|
||||
private fun buildProcess(node: Node, command: List<String>, logFile: String): Pair<Node, Process> {
|
||||
val process = ProcessBuilder(command)
|
||||
.directory(node.fullPath().toFile())
|
||||
.redirectErrorStream(true)
|
||||
// InheritIO causes hangs on windows due the gradle buffer also not being flushed.
|
||||
// Must redirect to output or logger (node log is still written, this is just startup banner)
|
||||
.redirectOutput(node.logFile().toFile())
|
||||
.redirectOutput(node.logFile(logFile).toFile())
|
||||
.addEnvironment("CAPSULE_CACHE_DIR", Node.capsuleCacheDir)
|
||||
.start()
|
||||
return Pair(node, process)
|
||||
@ -260,7 +281,6 @@ open class Cordform : DefaultTask() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun Node.logFile(): Path = this.logDirectory().resolve("generate-info.log")
|
||||
|
||||
private fun Node.logFile(name: String): Path = this.logDirectory().resolve(name)
|
||||
private fun ProcessBuilder.addEnvironment(key: String, value: String) = this.apply { environment().put(key, value) }
|
||||
}
|
||||
|
@ -4,8 +4,6 @@ import com.typesafe.config.ConfigFactory
|
||||
import com.typesafe.config.ConfigRenderOptions
|
||||
import com.typesafe.config.ConfigValueFactory
|
||||
import net.corda.cordform.CordformNode
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.asn1.x500.style.BCStyle
|
||||
import org.gradle.api.Project
|
||||
import java.io.File
|
||||
import java.nio.charset.StandardCharsets
|
||||
@ -62,21 +60,6 @@ class Node(private val project: Project) : CordformNode() {
|
||||
config = config.withValue("useTestClock", ConfigValueFactory.fromAnyRef(useTestClock))
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the network map address for this node.
|
||||
*
|
||||
* @warning This should not be directly set unless you know what you are doing. Use the networkMapName in the
|
||||
* Cordform task instead.
|
||||
* @param networkMapAddress Network map node address.
|
||||
* @param networkMapLegalName Network map node legal name.
|
||||
*/
|
||||
fun networkMapAddress(networkMapAddress: String, networkMapLegalName: String) {
|
||||
val networkMapService = mutableMapOf<String, String>()
|
||||
networkMapService.put("address", networkMapAddress)
|
||||
networkMapService.put("legalName", networkMapLegalName)
|
||||
config = config.withValue("networkMapService", ConfigValueFactory.fromMap(networkMapService))
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables SSH access on given port
|
||||
*
|
||||
@ -104,14 +87,10 @@ class Node(private val project: Project) : CordformNode() {
|
||||
project.logger.error("Node has a null name - cannot create node")
|
||||
throw IllegalStateException("Node has a null name - cannot create node")
|
||||
}
|
||||
|
||||
val dirName = try {
|
||||
val o = X500Name(name).getRDNs(BCStyle.O)
|
||||
if (o.isNotEmpty()) o.first().first.value.toString() else name
|
||||
} catch (_ : IllegalArgumentException) {
|
||||
// Can't parse as an X500 name, use the full string
|
||||
name
|
||||
}
|
||||
// Parsing O= part directly because importing BouncyCastle provider in Cordformation causes problems
|
||||
// with loading our custom X509EdDSAEngine.
|
||||
val organizationName = name.trim().split(",").firstOrNull { it.startsWith("O=") }?.substringAfter("=")
|
||||
val dirName = organizationName ?: name
|
||||
nodeDir = File(rootDir.toFile(), dirName.replace("\\s", ""))
|
||||
}
|
||||
|
||||
@ -129,7 +108,7 @@ class Node(private val project: Project) : CordformNode() {
|
||||
* Installs the corda fat JAR to the node directory.
|
||||
*/
|
||||
private fun installCordaJar() {
|
||||
val cordaJar = verifyAndGetCordaJar()
|
||||
val cordaJar = verifyAndGetRuntimeJar("corda")
|
||||
project.copy {
|
||||
it.apply {
|
||||
from(cordaJar)
|
||||
@ -144,7 +123,7 @@ class Node(private val project: Project) : CordformNode() {
|
||||
* Installs the corda webserver JAR to the node directory
|
||||
*/
|
||||
private fun installWebserverJar() {
|
||||
val webJar = verifyAndGetWebserverJar()
|
||||
val webJar = verifyAndGetRuntimeJar("corda-webserver")
|
||||
project.copy {
|
||||
it.apply {
|
||||
from(webJar)
|
||||
@ -250,34 +229,17 @@ class Node(private val project: Project) : CordformNode() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the corda JAR amongst the dependencies.
|
||||
* Find the given JAR amongst the dependencies
|
||||
* @param jarName JAR name without the version part, for example for corda-2.0-SNAPSHOT.jar provide only "corda" as jarName
|
||||
*
|
||||
* @return A file representing the Corda JAR.
|
||||
* @return A file representing found JAR
|
||||
*/
|
||||
private fun verifyAndGetCordaJar(): File {
|
||||
val maybeCordaJAR = project.configuration("runtime").filter {
|
||||
it.toString().contains("corda-$releaseVersion.jar") || it.toString().contains("corda-enterprise-$releaseVersion.jar")
|
||||
}
|
||||
if (maybeCordaJAR.isEmpty) {
|
||||
throw RuntimeException("No Corda Capsule JAR found. Have you deployed the Corda project to Maven? Looked for \"corda-$releaseVersion.jar\"")
|
||||
} else {
|
||||
val cordaJar = maybeCordaJAR.singleFile
|
||||
assert(cordaJar.isFile)
|
||||
return cordaJar
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the corda JAR amongst the dependencies
|
||||
*
|
||||
* @return A file representing the Corda webserver JAR
|
||||
*/
|
||||
private fun verifyAndGetWebserverJar(): File {
|
||||
private fun verifyAndGetRuntimeJar(jarName: String): File {
|
||||
val maybeJar = project.configuration("runtime").filter {
|
||||
it.toString().contains("corda-webserver-$releaseVersion.jar")
|
||||
"$jarName-$releaseVersion.jar" in it.toString() || "$jarName-enterprise-$releaseVersion.jar" in it.toString()
|
||||
}
|
||||
if (maybeJar.isEmpty) {
|
||||
throw RuntimeException("No Corda Webserver JAR found. Have you deployed the Corda project to Maven? Looked for \"corda-webserver-$releaseVersion.jar\"")
|
||||
throw IllegalStateException("No $jarName JAR found. Have you deployed the Corda project to Maven? Looked for \"$jarName-$releaseVersion.jar\"")
|
||||
} else {
|
||||
val jar = maybeJar.singleFile
|
||||
require(jar.isFile)
|
||||
|
@ -0,0 +1,75 @@
|
||||
package net.corda.nodeapi.internal
|
||||
|
||||
import net.corda.core.crypto.DigitalSignature
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.verify
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.serialization.SerializedBytes
|
||||
import net.corda.core.serialization.deserialize
|
||||
import java.security.SignatureException
|
||||
import java.security.cert.CertPathValidatorException
|
||||
import java.security.cert.X509Certificate
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
|
||||
// TODO: Need more discussion on rather we should move this class out of internal.
|
||||
/**
|
||||
* Data class containing hash of [NetworkParameters] and network participant's [NodeInfo] hashes.
|
||||
*/
|
||||
@CordaSerializable
|
||||
data class NetworkMap(val nodeInfoHashes: List<SecureHash>, val networkParameterHash: SecureHash)
|
||||
|
||||
/**
|
||||
* @property minimumPlatformVersion
|
||||
* @property notaries
|
||||
* @property eventHorizon
|
||||
* @property maxMessageSize Maximum P2P message sent over the wire in bytes.
|
||||
* @property maxTransactionSize Maximum permitted transaction size in bytes.
|
||||
* @property modifiedTime
|
||||
* @property epoch Version number of the network parameters. Starting from 1, this will always increment on each new set
|
||||
* of parameters.
|
||||
*/
|
||||
// TODO Wire up the parameters
|
||||
@CordaSerializable
|
||||
data class NetworkParameters(
|
||||
val minimumPlatformVersion: Int,
|
||||
val notaries: List<NotaryInfo>,
|
||||
val eventHorizon: Duration,
|
||||
val maxMessageSize: Int,
|
||||
val maxTransactionSize: Int,
|
||||
val modifiedTime: Instant,
|
||||
val epoch: Int
|
||||
) {
|
||||
init {
|
||||
require(minimumPlatformVersion > 0) { "minimumPlatformVersion must be at least 1" }
|
||||
require(notaries.distinctBy { it.identity } == notaries) { "Duplicate notary identities" }
|
||||
require(epoch > 0) { "epoch must be at least 1" }
|
||||
}
|
||||
}
|
||||
|
||||
@CordaSerializable
|
||||
data class NotaryInfo(val identity: Party, val validating: Boolean)
|
||||
|
||||
/**
|
||||
* A serialized [NetworkMap] and its signature and certificate. Enforces signature validity in order to deserialize the data
|
||||
* contained within.
|
||||
*/
|
||||
@CordaSerializable
|
||||
class SignedNetworkMap(val raw: SerializedBytes<NetworkMap>, val sig: DigitalSignatureWithCert) {
|
||||
/**
|
||||
* Return the deserialized NetworkMap if the signature and certificate can be verified.
|
||||
*
|
||||
* @throws CertPathValidatorException if the certificate path is invalid.
|
||||
* @throws SignatureException if the signature is invalid.
|
||||
*/
|
||||
@Throws(SignatureException::class)
|
||||
fun verified(): NetworkMap {
|
||||
sig.by.publicKey.verify(raw.bytes, sig)
|
||||
return raw.deserialize()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This class should reside in the [DigitalSignature] class.
|
||||
/** A digital signature that identifies who the public key is owned by, and the certificate which provides prove of the identity */
|
||||
class DigitalSignatureWithCert(val by: X509Certificate, val signatureBytes: ByteArray) : DigitalSignature(signatureBytes)
|
@ -0,0 +1,32 @@
|
||||
package net.corda.nodeapi.internal
|
||||
|
||||
import net.corda.core.crypto.SignedData
|
||||
import net.corda.core.crypto.entropyToKeyPair
|
||||
import net.corda.core.crypto.sign
|
||||
import net.corda.core.internal.copyTo
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.nodeapi.internal.NetworkParameters
|
||||
import java.math.BigInteger
|
||||
import java.nio.file.FileAlreadyExistsException
|
||||
import java.nio.file.Path
|
||||
|
||||
class NetworkParametersCopier(networkParameters: NetworkParameters) {
|
||||
private companion object {
|
||||
val DUMMY_MAP_KEY = entropyToKeyPair(BigInteger.valueOf(123))
|
||||
}
|
||||
|
||||
private val serializedNetworkParameters = networkParameters.let {
|
||||
val serialize = it.serialize()
|
||||
val signature = DUMMY_MAP_KEY.sign(serialize)
|
||||
SignedData(serialize, signature).serialize()
|
||||
}
|
||||
|
||||
fun install(dir: Path) {
|
||||
try {
|
||||
serializedNetworkParameters.open().copyTo(dir / "network-parameters")
|
||||
} catch (e: FileAlreadyExistsException) {
|
||||
// Leave the file untouched if it already exists
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
package net.corda.nodeapi.internal
|
||||
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import net.corda.core.crypto.SignedData
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.list
|
||||
import net.corda.core.internal.readAll
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.internal.SerializationEnvironmentImpl
|
||||
import net.corda.core.serialization.internal._contextSerializationEnv
|
||||
import net.corda.core.utilities.ByteSequence
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.core.utilities.days
|
||||
import net.corda.nodeapi.internal.serialization.AMQP_P2P_CONTEXT
|
||||
import net.corda.nodeapi.internal.serialization.KRYO_P2P_CONTEXT
|
||||
import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl
|
||||
import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme
|
||||
import net.corda.nodeapi.internal.serialization.kryo.AbstractKryoSerializationScheme
|
||||
import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1
|
||||
import java.nio.file.Path
|
||||
import java.time.Instant
|
||||
|
||||
/**
|
||||
* This class is loaded by Cordform using reflection to generate the network parameters. It is assumed that Cordform has
|
||||
* already asked each node to generate its node info file.
|
||||
*/
|
||||
@Suppress("UNUSED")
|
||||
class NetworkParametersGenerator {
|
||||
companion object {
|
||||
private val logger = contextLogger()
|
||||
}
|
||||
|
||||
fun run(nodesDirs: List<Path>) {
|
||||
logger.info("NetworkParameters generation using node directories: $nodesDirs")
|
||||
try {
|
||||
initialiseSerialization()
|
||||
val notaryInfos = gatherNotaryIdentities(nodesDirs)
|
||||
val copier = NetworkParametersCopier(NetworkParameters(
|
||||
minimumPlatformVersion = 1,
|
||||
notaries = notaryInfos,
|
||||
modifiedTime = Instant.now(),
|
||||
eventHorizon = 10000.days,
|
||||
maxMessageSize = 40000,
|
||||
maxTransactionSize = 40000,
|
||||
epoch = 1
|
||||
))
|
||||
nodesDirs.forEach(copier::install)
|
||||
} finally {
|
||||
_contextSerializationEnv.set(null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun gatherNotaryIdentities(nodesDirs: List<Path>): List<NotaryInfo> {
|
||||
return nodesDirs.mapNotNull { nodeDir ->
|
||||
val nodeConfig = ConfigFactory.parseFile((nodeDir / "node.conf").toFile())
|
||||
if (nodeConfig.hasPath("notary")) {
|
||||
val validating = nodeConfig.getConfig("notary").getBoolean("validating")
|
||||
val nodeInfoFile = nodeDir.list { paths -> paths.filter { it.fileName.toString().startsWith("nodeInfo-") }.findFirst().get() }
|
||||
processFile(nodeInfoFile)?.let { NotaryInfo(it.notaryIdentity(), validating) }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}.distinct() // We need distinct as nodes part of a distributed notary share the same notary identity
|
||||
}
|
||||
|
||||
private fun NodeInfo.notaryIdentity(): Party {
|
||||
return when (legalIdentities.size) {
|
||||
// Single node notaries have just one identity like all other nodes. This identity is the notary identity
|
||||
1 -> legalIdentities[0]
|
||||
// Nodes which are part of a distributed notary have a second identity which is the composite identity of the
|
||||
// cluster and is shared by all the other members. This is the notary identity.
|
||||
2 -> legalIdentities[1]
|
||||
else -> throw IllegalArgumentException("Not sure how to get the notary identity in this scenerio: $this")
|
||||
}
|
||||
}
|
||||
|
||||
private fun processFile(file: Path): NodeInfo? {
|
||||
return try {
|
||||
logger.info("Reading NodeInfo from file: $file")
|
||||
val signedData = file.readAll().deserialize<SignedData<NodeInfo>>()
|
||||
signedData.verified()
|
||||
} catch (e: Exception) {
|
||||
logger.warn("Exception parsing NodeInfo from file. $file", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
// We need to to set serialization env, because generation of parameters is run from Cordform.
|
||||
// KryoServerSerializationScheme is not accessible from nodeapi.
|
||||
private fun initialiseSerialization() {
|
||||
val context = if (java.lang.Boolean.getBoolean("net.corda.testing.amqp.enable")) AMQP_P2P_CONTEXT else KRYO_P2P_CONTEXT
|
||||
_contextSerializationEnv.set(SerializationEnvironmentImpl(
|
||||
SerializationFactoryImpl().apply {
|
||||
registerScheme(KryoParametersSerializationScheme)
|
||||
registerScheme(AMQPServerSerializationScheme())
|
||||
},
|
||||
context)
|
||||
)
|
||||
}
|
||||
|
||||
private object KryoParametersSerializationScheme : AbstractKryoSerializationScheme() {
|
||||
override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean {
|
||||
return byteSequence == KryoHeaderV0_1 && target == SerializationContext.UseCase.P2P
|
||||
}
|
||||
override fun rpcClientKryoPool(context: SerializationContext) = throw UnsupportedOperationException()
|
||||
override fun rpcServerKryoPool(context: SerializationContext) = throw UnsupportedOperationException()
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package net.corda.nodeapi
|
||||
package net.corda.nodeapi.internal
|
||||
|
||||
import net.corda.cordform.CordformNode
|
||||
import net.corda.core.internal.ThreadBox
|
@ -1,4 +1,4 @@
|
||||
package net.corda.node.utilities
|
||||
package net.corda.nodeapi.internal
|
||||
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.generateKeyPair
|
||||
@ -11,35 +11,40 @@ import net.corda.core.utilities.trace
|
||||
import net.corda.nodeapi.internal.crypto.*
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.nio.file.Path
|
||||
import java.security.cert.X509Certificate
|
||||
|
||||
object ServiceIdentityGenerator {
|
||||
private val log = LoggerFactory.getLogger(javaClass)
|
||||
|
||||
/**
|
||||
* Generates signing key pairs and a common distributed service identity for a set of nodes.
|
||||
* The key pairs and the group identity get serialized to disk in the corresponding node directories.
|
||||
* This method should be called *before* any of the nodes are started.
|
||||
*
|
||||
* @param dirs List of node directories to place the generated identity and key pairs in.
|
||||
* @param serviceName The legal name of the distributed service, with service id as CN.
|
||||
* @param serviceName The legal name of the distributed service.
|
||||
* @param threshold The threshold for the generated group [CompositeKey].
|
||||
* @param customRootCert the certificate to use a Corda root CA. If not specified the one in
|
||||
* certificates/cordadevcakeys.jks is used.
|
||||
*/
|
||||
fun generateToDisk(dirs: List<Path>,
|
||||
serviceName: CordaX500Name,
|
||||
threshold: Int = 1): Party {
|
||||
serviceId: String,
|
||||
threshold: Int = 1,
|
||||
customRootCert: X509Certificate? = null): Party {
|
||||
log.trace { "Generating a group identity \"serviceName\" for nodes: ${dirs.joinToString()}" }
|
||||
val keyPairs = (1..dirs.size).map { generateKeyPair() }
|
||||
val notaryKey = CompositeKey.Builder().addKeys(keyPairs.map { it.public }).build(threshold)
|
||||
|
||||
val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordadevcakeys.jks"), "cordacadevpass")
|
||||
val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass")
|
||||
val issuer = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cordacadevkeypass")
|
||||
val rootCert = caKeyStore.getCertificate(X509Utilities.CORDA_ROOT_CA)
|
||||
val rootCert = customRootCert ?: caKeyStore.getCertificate(X509Utilities.CORDA_ROOT_CA)
|
||||
|
||||
keyPairs.zip(dirs) { keyPair, dir ->
|
||||
val serviceKeyCert = X509Utilities.createCertificate(CertificateType.NODE_CA, issuer.certificate, issuer.keyPair, serviceName, keyPair.public)
|
||||
val compositeKeyCert = X509Utilities.createCertificate(CertificateType.NODE_CA, issuer.certificate, issuer.keyPair, serviceName, notaryKey)
|
||||
val certPath = (dir / "certificates").createDirectories() / "distributedService.jks"
|
||||
val keystore = loadOrCreateKeyStore(certPath, "cordacadevpass")
|
||||
val serviceId = serviceName.commonName
|
||||
keystore.setCertificateEntry("$serviceId-composite-key", compositeKeyCert.cert)
|
||||
keystore.setKeyEntry("$serviceId-private-key", keyPair.private, "cordacadevkeypass".toCharArray(), arrayOf(serviceKeyCert.cert, issuer.certificate.cert, rootCert))
|
||||
keystore.save(certPath, "cordacadevpass")
|
@ -18,7 +18,7 @@ class KeyStoreWrapper(private val storePath: Path, private val storePassword: St
|
||||
val clientCA = certificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA)
|
||||
// Create new keys and store in keystore.
|
||||
val cert = X509Utilities.createCertificate(CertificateType.WELL_KNOWN_IDENTITY, clientCA.certificate, clientCA.keyPair, serviceName, pubKey)
|
||||
val certPath = X509CertificateFactory().delegate.generateCertPath(listOf(cert.cert) + clientCertPath)
|
||||
val certPath = X509CertificateFactory().generateCertPath(cert.cert, *clientCertPath)
|
||||
require(certPath.certificates.isNotEmpty()) { "Certificate path cannot be empty" }
|
||||
// TODO: X509Utilities.validateCertificateChain()
|
||||
return certPath
|
||||
|
@ -4,8 +4,8 @@ import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.SignatureScheme
|
||||
import net.corda.core.crypto.random63BitValue
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.cert
|
||||
import net.corda.core.internal.read
|
||||
import net.corda.core.internal.write
|
||||
import net.corda.core.internal.x500Name
|
||||
import net.corda.core.utilities.days
|
||||
import net.corda.core.utilities.millis
|
||||
@ -27,10 +27,8 @@ import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder
|
||||
import org.bouncycastle.pkcs.PKCS10CertificationRequest
|
||||
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder
|
||||
import org.bouncycastle.util.io.pem.PemReader
|
||||
import java.io.FileWriter
|
||||
import java.io.InputStream
|
||||
import java.math.BigInteger
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.security.KeyPair
|
||||
import java.security.PublicKey
|
||||
@ -153,7 +151,7 @@ object X509Utilities {
|
||||
require(certificates.isNotEmpty()) { "Certificate path must contain at least one certificate" }
|
||||
val params = PKIXParameters(setOf(TrustAnchor(trustedRoot, null)))
|
||||
params.isRevocationEnabled = false
|
||||
val certPath = X509CertificateFactory().delegate.generateCertPath(certificates.toList())
|
||||
val certPath = X509CertificateFactory().generateCertPath(*certificates)
|
||||
val pathValidator = CertPathValidator.getInstance("PKIX")
|
||||
pathValidator.validate(certPath, params)
|
||||
}
|
||||
@ -164,7 +162,7 @@ object X509Utilities {
|
||||
* @param file Target file.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun saveCertificateAsPEMFile(x509Certificate: X509CertificateHolder, file: Path) {
|
||||
fun saveCertificateAsPEMFile(x509Certificate: X509Certificate, file: Path) {
|
||||
JcaPEMWriter(file.toFile().writer()).use {
|
||||
it.writeObject(x509Certificate)
|
||||
}
|
||||
@ -176,14 +174,14 @@ object X509Utilities {
|
||||
* @return The X509Certificate that was encoded in the file.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun loadCertificateFromPEMFile(file: Path): X509CertificateHolder {
|
||||
val cert = file.read {
|
||||
fun loadCertificateFromPEMFile(file: Path): X509Certificate {
|
||||
return file.read {
|
||||
val reader = PemReader(it.reader())
|
||||
val pemObject = reader.readPemObject()
|
||||
X509CertificateHolder(pemObject.content)
|
||||
val certHolder = X509CertificateHolder(pemObject.content)
|
||||
certHolder.isValidOn(Date())
|
||||
certHolder.cert
|
||||
}
|
||||
cert.isValidOn(Date())
|
||||
return cert
|
||||
}
|
||||
|
||||
/**
|
||||
@ -310,9 +308,18 @@ object X509Utilities {
|
||||
*/
|
||||
class X509CertificateFactory {
|
||||
val delegate: CertificateFactory = CertificateFactory.getInstance("X.509")
|
||||
|
||||
fun generateCertificate(input: InputStream): X509Certificate {
|
||||
return delegate.generateCertificate(input) as X509Certificate
|
||||
}
|
||||
|
||||
fun generateCertPath(certificates: List<Certificate>): CertPath {
|
||||
return delegate.generateCertPath(certificates)
|
||||
}
|
||||
|
||||
fun generateCertPath(vararg certificates: Certificate): CertPath {
|
||||
return delegate.generateCertPath(certificates.asList())
|
||||
}
|
||||
}
|
||||
|
||||
enum class CertificateType(val keyUsage: KeyUsage, vararg val purposes: KeyPurposeId, val isCA: Boolean) {
|
||||
|
@ -27,10 +27,8 @@ import java.util.*
|
||||
class CordaClassResolver(serializationContext: SerializationContext) : DefaultClassResolver() {
|
||||
val whitelist: ClassWhitelist = TransientClassWhiteList(serializationContext.whitelist)
|
||||
|
||||
/*
|
||||
* These classes are assignment-compatible Java equivalents of Kotlin classes.
|
||||
* The point is that we do not want to send Kotlin types "over the wire" via RPC.
|
||||
*/
|
||||
// These classes are assignment-compatible Java equivalents of Kotlin classes.
|
||||
// The point is that we do not want to send Kotlin types "over the wire" via RPC.
|
||||
private val javaAliases: Map<Class<*>, Class<*>> = mapOf(
|
||||
listOf<Any>().javaClass to Collections.emptyList<Any>().javaClass,
|
||||
setOf<Any>().javaClass to Collections.emptySet<Any>().javaClass,
|
||||
@ -176,7 +174,8 @@ class GlobalTransientClassWhiteList(delegate: ClassWhitelist) : AbstractMutableC
|
||||
}
|
||||
|
||||
/**
|
||||
* A whitelist that can be customised via the [net.corda.core.node.SerializationWhitelist], since it implements [MutableClassWhitelist].
|
||||
* A whitelist that can be customised via the [net.corda.core.serialization.SerializationWhitelist],
|
||||
* since it implements [MutableClassWhitelist].
|
||||
*/
|
||||
class TransientClassWhiteList(delegate: ClassWhitelist) : AbstractMutableClassWhitelist(Collections.synchronizedSet(mutableSetOf()), delegate)
|
||||
|
||||
|
@ -6,6 +6,7 @@ import net.corda.core.utilities.NetworkHostAndPort
|
||||
import org.apache.activemq.artemis.api.core.SimpleString
|
||||
import rx.Notification
|
||||
import rx.exceptions.OnErrorNotImplementedException
|
||||
import sun.security.x509.X509CertImpl
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
@ -49,8 +50,8 @@ object DefaultWhitelist : SerializationWhitelist {
|
||||
java.time.YearMonth::class.java,
|
||||
java.time.MonthDay::class.java,
|
||||
java.time.Period::class.java,
|
||||
java.time.DayOfWeek::class.java, // No custom serialiser but it's an enum.
|
||||
java.time.Month::class.java, // No custom serialiser but it's an enum.
|
||||
java.time.DayOfWeek::class.java, // No custom serializer but it's an enum.
|
||||
java.time.Month::class.java, // No custom serializer but it's an enum.
|
||||
|
||||
java.util.Collections.emptyMap<Any, Any>().javaClass,
|
||||
java.util.Collections.emptySet<Any>().javaClass,
|
||||
@ -58,6 +59,9 @@ object DefaultWhitelist : SerializationWhitelist {
|
||||
java.util.LinkedHashMap::class.java,
|
||||
BitSet::class.java,
|
||||
OnErrorNotImplementedException::class.java,
|
||||
StackTraceElement::class.java
|
||||
StackTraceElement::class.java,
|
||||
|
||||
// Implementation of X509Certificate.
|
||||
X509CertImpl::class.java
|
||||
)
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import org.apache.qpid.proton.amqp.UnsignedLong
|
||||
const val DESCRIPTOR_TOP_32BITS: Long = 0xc562L shl(32 + 16)
|
||||
|
||||
/**
|
||||
* AMQP desriptor ID's for our custom types.
|
||||
* AMQP descriptor ID's for our custom types.
|
||||
*
|
||||
* NEVER DELETE OR CHANGE THE ID ASSOCIATED WITH A TYPE
|
||||
*
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
package net.corda.nodeapi.internal.serialization.amqp
|
||||
|
||||
import net.corda.core.cordapp.Cordapp
|
||||
import net.corda.core.serialization.*
|
||||
import net.corda.core.utilities.ByteSequence
|
||||
import net.corda.nodeapi.internal.serialization.DefaultWhitelist
|
||||
@ -24,7 +25,8 @@ fun SerializerFactory.addToWhitelist(vararg types: Class<*>) {
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AbstractAMQPSerializationScheme : SerializationScheme {
|
||||
abstract class AbstractAMQPSerializationScheme(val cordappLoader: List<Cordapp>) : SerializationScheme {
|
||||
|
||||
companion object {
|
||||
private val serializationWhitelists: List<SerializationWhitelist> by lazy {
|
||||
ServiceLoader.load(SerializationWhitelist::class.java, this::class.java.classLoader).toList() + DefaultWhitelist
|
||||
@ -62,10 +64,17 @@ abstract class AbstractAMQPSerializationScheme : SerializationScheme {
|
||||
register(net.corda.nodeapi.internal.serialization.amqp.custom.EnumSetSerializer(this))
|
||||
register(net.corda.nodeapi.internal.serialization.amqp.custom.ContractAttachmentSerializer(this))
|
||||
}
|
||||
for (whitelistProvider in serializationWhitelists)
|
||||
for (whitelistProvider in serializationWhitelists) {
|
||||
factory.addToWhitelist(*whitelistProvider.whitelist.toTypedArray())
|
||||
}
|
||||
|
||||
for (loader in cordappLoader) {
|
||||
for (schema in loader.serializationCustomSerializers) {
|
||||
factory.registerExternal(CorDappCustomSerializer(schema, factory))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val serializerFactoriesForContexts = ConcurrentHashMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>()
|
||||
|
||||
protected abstract fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory
|
||||
@ -97,11 +106,11 @@ abstract class AbstractAMQPSerializationScheme : SerializationScheme {
|
||||
return SerializationOutput(serializerFactory).serialize(obj)
|
||||
}
|
||||
|
||||
protected fun canDeserializeVersion(byteSequence: ByteSequence): Boolean = AMQP_ENABLED && byteSequence == AmqpHeaderV1_0
|
||||
protected fun canDeserializeVersion(byteSequence: ByteSequence): Boolean = byteSequence == AmqpHeaderV1_0
|
||||
}
|
||||
|
||||
// TODO: This will eventually cover server RPC as well and move to node module, but for now this is not implemented
|
||||
class AMQPServerSerializationScheme : AbstractAMQPSerializationScheme() {
|
||||
class AMQPServerSerializationScheme(cordapps: List<Cordapp> = emptyList()) : AbstractAMQPSerializationScheme(cordapps) {
|
||||
override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
@ -118,7 +127,7 @@ class AMQPServerSerializationScheme : AbstractAMQPSerializationScheme() {
|
||||
}
|
||||
|
||||
// TODO: This will eventually cover client RPC as well and move to client module, but for now this is not implemented
|
||||
class AMQPClientSerializationScheme : AbstractAMQPSerializationScheme() {
|
||||
class AMQPClientSerializationScheme(cordapps: List<Cordapp> = emptyList()) : AbstractAMQPSerializationScheme(cordapps) {
|
||||
override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
@ -21,8 +21,8 @@ open class ArraySerializer(override val type: Type, factory: SerializerFactory)
|
||||
// id to generate it properly (it will always return [[[Ljava.lang.type -> type[][][]
|
||||
// for example).
|
||||
//
|
||||
// We *need* to retain knowledge for AMQP deserialisation weather that lowest primitive
|
||||
// was boxed or unboxed so just infer it recursively
|
||||
// We *need* to retain knowledge for AMQP deserialization weather that lowest primitive
|
||||
// was boxed or unboxed so just infer it recursively.
|
||||
private fun calcTypeName(type: Type): String =
|
||||
if (type.componentType().isArray()) {
|
||||
val typeName = calcTypeName(type.componentType()); "$typeName[]"
|
||||
|
@ -0,0 +1,86 @@
|
||||
package net.corda.nodeapi.internal.serialization.amqp
|
||||
|
||||
import net.corda.core.internal.uncheckedCast
|
||||
import net.corda.core.serialization.SerializationCustomSerializer
|
||||
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory.Companion.nameForType
|
||||
import org.apache.qpid.proton.amqp.Symbol
|
||||
import org.apache.qpid.proton.codec.Data
|
||||
import java.io.NotSerializableException
|
||||
import java.lang.reflect.Type
|
||||
import kotlin.reflect.jvm.javaType
|
||||
import kotlin.reflect.jvm.jvmErasure
|
||||
|
||||
/**
|
||||
* Index into the types list of the parent type of the serializer object, should be the
|
||||
* type that this object proxies for
|
||||
*/
|
||||
const val CORDAPP_TYPE = 0
|
||||
|
||||
/**
|
||||
* Index into the types list of the parent type of the serializer object, should be the
|
||||
* type of the proxy object that we're using to represent the object we're proxying for
|
||||
*/
|
||||
const val PROXY_TYPE = 1
|
||||
|
||||
/**
|
||||
* Wrapper class for user provided serializers
|
||||
*
|
||||
* Through the CorDapp JAR scanner we will have a list of custom serializer types that implement
|
||||
* the toProxy and fromProxy methods. This class takes an instance of one of those objects and
|
||||
* embeds it within a serialization context associated with a serializer factory by creating
|
||||
* and instance of this class and registering that with a [SerializerFactory]
|
||||
*
|
||||
* Proxy serializers should transform an unserializable class into a representation that we can serialize
|
||||
*
|
||||
* @property serializer in instance of a user written serialization proxy, normally scanned and loaded
|
||||
* automatically
|
||||
* @property type the Java [Type] of the class which this serializes, inferred via reflection of the
|
||||
* [serializer]'s super type
|
||||
* @property proxyType the Java [Type] of the class into which instances of [type] are proxied for use by
|
||||
* the underlying serialization engine
|
||||
*
|
||||
* @param factory a [SerializerFactory] belonging to the context this serializer is being instantiated
|
||||
* for
|
||||
*/
|
||||
class CorDappCustomSerializer(
|
||||
private val serializer: SerializationCustomSerializer<*, *>,
|
||||
factory: SerializerFactory) : AMQPSerializer<Any>, SerializerFor {
|
||||
override val revealSubclassesInSchema: Boolean get() = false
|
||||
private val types = serializer::class.supertypes.filter { it.jvmErasure == SerializationCustomSerializer::class }
|
||||
.flatMap { it.arguments }
|
||||
.map { it.type!!.javaType }
|
||||
|
||||
init {
|
||||
if (types.size != 2) {
|
||||
throw NotSerializableException("Unable to determine serializer parent types")
|
||||
}
|
||||
}
|
||||
|
||||
override val type = types[CORDAPP_TYPE]
|
||||
val proxyType = types[PROXY_TYPE]
|
||||
override val typeDescriptor = Symbol.valueOf("$DESCRIPTOR_DOMAIN:${nameForType(type)}")
|
||||
val descriptor: Descriptor = Descriptor(typeDescriptor)
|
||||
private val proxySerializer: ObjectSerializer by lazy { ObjectSerializer(proxyType, factory) }
|
||||
|
||||
override fun writeClassInfo(output: SerializationOutput) {}
|
||||
|
||||
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
|
||||
val proxy = uncheckedCast<SerializationCustomSerializer<*, *>,
|
||||
SerializationCustomSerializer<Any?, Any?>>(serializer).toProxy(obj)
|
||||
|
||||
data.withDescribed(descriptor) {
|
||||
data.withList {
|
||||
for (property in proxySerializer.propertySerializers) {
|
||||
property.writeProperty(proxy, this, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput) =
|
||||
uncheckedCast<SerializationCustomSerializer<*, *>, SerializationCustomSerializer<Any?, Any?>>(
|
||||
serializer).fromProxy(uncheckedCast(proxySerializer.readObject(obj, schemas, input)))!!
|
||||
|
||||
override fun isSerializerFor(clazz: Class<*>) = clazz == type
|
||||
}
|
||||
|
@ -6,22 +6,27 @@ import org.apache.qpid.proton.amqp.Symbol
|
||||
import org.apache.qpid.proton.codec.Data
|
||||
import java.lang.reflect.Type
|
||||
|
||||
interface SerializerFor {
|
||||
/**
|
||||
* This method should return true if the custom serializer can serialize an instance of the class passed as the
|
||||
* parameter.
|
||||
*/
|
||||
fun isSerializerFor(clazz: Class<*>): Boolean
|
||||
|
||||
val revealSubclassesInSchema: Boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for serializers of core platform types that do not conform to the usual serialization rules and thus
|
||||
* cannot be automatically serialized.
|
||||
*/
|
||||
abstract class CustomSerializer<T : Any> : AMQPSerializer<T> {
|
||||
abstract class CustomSerializer<T : Any> : AMQPSerializer<T>, SerializerFor {
|
||||
/**
|
||||
* This is a collection of custom serializers that this custom serializer depends on. e.g. for proxy objects
|
||||
* that refer to other custom types etc.
|
||||
*/
|
||||
open val additionalSerializers: Iterable<CustomSerializer<out Any>> = emptyList()
|
||||
|
||||
/**
|
||||
* This method should return true if the custom serializer can serialize an instance of the class passed as the
|
||||
* parameter.
|
||||
*/
|
||||
abstract fun isSerializerFor(clazz: Class<*>): Boolean
|
||||
|
||||
protected abstract val descriptor: Descriptor
|
||||
/**
|
||||
@ -33,7 +38,7 @@ abstract class CustomSerializer<T : Any> : AMQPSerializer<T> {
|
||||
/**
|
||||
* Whether subclasses using this serializer via inheritance should have a mapping in the schema.
|
||||
*/
|
||||
open val revealSubclassesInSchema: Boolean = false
|
||||
override val revealSubclassesInSchema: Boolean get() = false
|
||||
|
||||
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
|
||||
data.withDescribed(descriptor) {
|
||||
@ -147,8 +152,8 @@ abstract class CustomSerializer<T : Any> : AMQPSerializer<T> {
|
||||
*
|
||||
* @param clazz The type to be marshalled
|
||||
* @param withInheritance Whether subclasses of the class can also be marshalled.
|
||||
* @param make A lambda for constructing an instance, that defaults to calling a constructor that expects a string.
|
||||
* @param unmake A lambda that extracts the string value for an instance, that defaults to the [toString] method.
|
||||
* @param maker A lambda for constructing an instance, that defaults to calling a constructor that expects a string.
|
||||
* @param unmaker A lambda that extracts the string value for an instance, that defaults to the [toString] method.
|
||||
*/
|
||||
abstract class ToString<T : Any>(clazz: Class<T>, withInheritance: Boolean = false,
|
||||
private val maker: (String) -> T = clazz.getConstructor(String::class.java).let { `constructor` ->
|
||||
|
@ -10,8 +10,8 @@ import kotlin.reflect.full.findAnnotation
|
||||
import kotlin.reflect.jvm.javaType
|
||||
|
||||
/**
|
||||
* Serializer for deserialising objects whose definition has changed since they
|
||||
* were serialised
|
||||
* Serializer for deserializing objects whose definition has changed since they
|
||||
* were serialised.
|
||||
*/
|
||||
class EvolutionSerializer(
|
||||
clazz: Type,
|
||||
@ -38,16 +38,16 @@ class EvolutionSerializer(
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Unlike the generic deserialisation case where we need to locate the primary constructor
|
||||
* Unlike the generic deserialization case where we need to locate the primary constructor
|
||||
* for the object (or our best guess) in the case of an object whose structure has changed
|
||||
* since serialisation we need to attempt to locate a constructor that we can use. I.e.
|
||||
* it's parameters match the serialised members and it will initialise any newly added
|
||||
* elements
|
||||
* since serialisation we need to attempt to locate a constructor that we can use. For example,
|
||||
* its parameters match the serialised members and it will initialise any newly added
|
||||
* elements.
|
||||
*
|
||||
* TODO: Type evolution
|
||||
* TODO: rename annotation
|
||||
*/
|
||||
internal fun getEvolverConstructor(type: Type, oldArgs: Map<String?, Type>): KFunction<Any>? {
|
||||
private fun getEvolverConstructor(type: Type, oldArgs: Map<String?, Type>): KFunction<Any>? {
|
||||
val clazz: Class<*> = type.asClass()!!
|
||||
if (!isConcrete(clazz)) return null
|
||||
|
||||
@ -70,13 +70,15 @@ class EvolutionSerializer(
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a serialization object for deserialisation only of objects serialised
|
||||
* as different versions of a class
|
||||
* Build a serialization object for deserialization only of objects serialised
|
||||
* as different versions of a class.
|
||||
*
|
||||
* @param old is an object holding the schema that represents the object
|
||||
* as it was serialised and the type descriptor of that type
|
||||
* @param new is the Serializer built for the Class as it exists now, not
|
||||
* how it was serialised and persisted.
|
||||
* @param factory the [SerializerFactory] associated with the serialization
|
||||
* context this serializer is being built for
|
||||
*/
|
||||
fun make(old: CompositeType, new: ObjectSerializer,
|
||||
factory: SerializerFactory): AMQPSerializer<Any> {
|
||||
@ -117,7 +119,7 @@ class EvolutionSerializer(
|
||||
* to the object list of values we need to map that list, which is ordered per the
|
||||
* constructor of the original state of the object, we need to map the new parameter order
|
||||
* of the current constructor onto that list inserting nulls where new parameters are
|
||||
* encountered
|
||||
* encountered.
|
||||
*
|
||||
* TODO: Object references
|
||||
*/
|
||||
|
@ -83,10 +83,12 @@ private fun <T : Any> propertiesForSerializationFromConstructor(kotlinConstructo
|
||||
val name = param.name ?: throw NotSerializableException("Constructor parameter of $clazz has no name.")
|
||||
val matchingProperty = properties[name] ?:
|
||||
throw NotSerializableException("No property matching constructor parameter named '$name' of '$clazz'. " +
|
||||
" If using Java, check that you have the -parameters option specified in the Java compiler.")
|
||||
"If using Java, check that you have the -parameters option specified in the Java compiler. " +
|
||||
"Alternately, provide a proxy serializer (SerializationCustomSerializer) if recompiling isn't an option")
|
||||
// Check that the method has a getter in java.
|
||||
val getter = matchingProperty.readMethod ?: throw NotSerializableException("Property has no getter method for $name of $clazz. " +
|
||||
" If using Java and the parameter name looks anonymous, check that you have the -parameters option specified in the Java compiler.")
|
||||
"If using Java and the parameter name looks anonymous, check that you have the -parameters option specified in the Java compiler." +
|
||||
"Alternately, provide a proxy serializer (SerializationCustomSerializer) if recompiling isn't an option")
|
||||
val returnType = resolveTypeVariables(getter.genericReturnType, type)
|
||||
if (constructorParamTakesReturnTypeOfGetter(returnType, getter.genericReturnType, param)) {
|
||||
rc += PropertySerializer.make(name, getter, returnType, factory)
|
||||
|
@ -36,7 +36,7 @@ data class FactorySchemaAndDescriptor(val schemas: SerializationSchemas, val typ
|
||||
open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
|
||||
private val serializersByType = ConcurrentHashMap<Type, AMQPSerializer<Any>>()
|
||||
private val serializersByDescriptor = ConcurrentHashMap<Any, AMQPSerializer<Any>>()
|
||||
private val customSerializers = CopyOnWriteArrayList<CustomSerializer<out Any>>()
|
||||
private val customSerializers = CopyOnWriteArrayList<SerializerFor>()
|
||||
val transformsCache = ConcurrentHashMap<String, EnumMap<TransformTypes, MutableList<Transform>>>()
|
||||
|
||||
open val classCarpenter = ClassCarpenter(cl, whitelist)
|
||||
@ -196,9 +196,16 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
|
||||
}
|
||||
}
|
||||
|
||||
fun registerExternal(customSerializer: CorDappCustomSerializer) {
|
||||
if (!serializersByDescriptor.containsKey(customSerializer.typeDescriptor)) {
|
||||
customSerializers += customSerializer
|
||||
serializersByDescriptor[customSerializer.typeDescriptor] = customSerializer
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate over an AMQP schema, for each type ascertain weather it's on ClassPath of [classloader] amd
|
||||
* if not use the [ClassCarpenter] to generate a class to use in it's place
|
||||
* Iterate over an AMQP schema, for each type ascertain whether it's on ClassPath of [classloader] and,
|
||||
* if not, use the [ClassCarpenter] to generate a class to use in it's place.
|
||||
*/
|
||||
private fun processSchema(schemaAndDescriptor: FactorySchemaAndDescriptor, sentinel: Boolean = false) {
|
||||
val metaSchema = CarpenterMetaSchema.newInstance()
|
||||
@ -267,11 +274,13 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
|
||||
for (customSerializer in customSerializers) {
|
||||
if (customSerializer.isSerializerFor(clazz)) {
|
||||
val declaredSuperClass = declaredType.asClass()?.superclass
|
||||
if (declaredSuperClass == null || !customSerializer.isSerializerFor(declaredSuperClass) || !customSerializer.revealSubclassesInSchema) {
|
||||
return customSerializer
|
||||
return if (declaredSuperClass == null
|
||||
|| !customSerializer.isSerializerFor(declaredSuperClass)
|
||||
|| !customSerializer.revealSubclassesInSchema) {
|
||||
customSerializer as? AMQPSerializer<Any>
|
||||
} else {
|
||||
// Make a subclass serializer for the subclass and return that...
|
||||
return CustomSerializer.SubClass(clazz, uncheckedCast(customSerializer))
|
||||
CustomSerializer.SubClass(clazz, uncheckedCast(customSerializer))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import net.corda.nodeapi.internal.serialization.amqp.CompositeType
|
||||
import net.corda.nodeapi.internal.serialization.amqp.RestrictedType
|
||||
import net.corda.nodeapi.internal.serialization.amqp.Field as AMQPField
|
||||
import net.corda.nodeapi.internal.serialization.amqp.Schema as AMQPSchema
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
|
||||
fun AMQPSchema.carpenterSchema(classloader: ClassLoader): CarpenterMetaSchema {
|
||||
val rtn = CarpenterMetaSchema.newInstance()
|
||||
@ -34,7 +35,7 @@ fun AMQPField.typeAsString() = if (type == "*") requires[0] else type
|
||||
* b) add the class to the dependency tree in [carpenterSchemas] if it cannot be instantiated
|
||||
* at this time
|
||||
*
|
||||
* @param classloader the class loader provided dby the [SerializationContext]
|
||||
* @param classloader the class loader provided by the [SerializationContext]
|
||||
* @param carpenterSchemas structure that holds the dependency tree and list of classes that
|
||||
* need constructing
|
||||
* @param force by default a schema is not added to [carpenterSchemas] if it already exists
|
||||
@ -121,7 +122,8 @@ val typeStrToType: Map<Pair<String, Boolean>, Class<out Any?>> = mapOf(
|
||||
|
||||
fun AMQPField.getTypeAsClass(classloader: ClassLoader) = typeStrToType[Pair(type, mandatory)] ?: when (type) {
|
||||
"string" -> String::class.java
|
||||
"*" -> classloader.loadClass(requires[0])
|
||||
"binary" -> ByteArray::class.java
|
||||
"*" -> if (requires.isEmpty()) Any::class.java else classloader.loadClass(requires[0])
|
||||
else -> classloader.loadClass(type)
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@ enum class SchemaFlags {
|
||||
}
|
||||
|
||||
/**
|
||||
* A Schema is the representation of an object the Carpenter can contsruct
|
||||
* A Schema is the representation of an object the Carpenter can construct
|
||||
*
|
||||
* Known Sub Classes
|
||||
* - [ClassSchema]
|
||||
@ -62,7 +62,7 @@ fun EnumMap<SchemaFlags, Boolean>.simpleFieldAccess(): Boolean {
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a concrete object
|
||||
* Represents a concrete object.
|
||||
*/
|
||||
class ClassSchema(
|
||||
name: String,
|
||||
@ -77,7 +77,7 @@ class ClassSchema(
|
||||
|
||||
/**
|
||||
* Represents an interface. Carpented interfaces can be used within [ClassSchema]s
|
||||
* if that class should be implementing that interface
|
||||
* if that class should be implementing that interface.
|
||||
*/
|
||||
class InterfaceSchema(
|
||||
name: String,
|
||||
@ -91,7 +91,7 @@ class InterfaceSchema(
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an enumerated type
|
||||
* Represents an enumerated type.
|
||||
*/
|
||||
class EnumSchema(
|
||||
name: String,
|
||||
@ -111,8 +111,8 @@ class EnumSchema(
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory object used by the serialiser when building [Schema]s based
|
||||
* on an AMQP schema
|
||||
* Factory object used by the serializer when building [Schema]s based
|
||||
* on an AMQP schema.
|
||||
*/
|
||||
object CarpenterSchemaFactory {
|
||||
fun newInstance(
|
||||
|
@ -44,6 +44,7 @@ import org.objenesis.strategy.StdInstantiatorStrategy
|
||||
import org.slf4j.Logger
|
||||
import sun.security.ec.ECPublicKeyImpl
|
||||
import sun.security.provider.certpath.X509CertPath
|
||||
import sun.security.x509.X509CertImpl
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.FileInputStream
|
||||
@ -75,6 +76,7 @@ object DefaultKryoCustomizer {
|
||||
addDefaultSerializer(InputStream::class.java, InputStreamSerializer)
|
||||
addDefaultSerializer(SerializeAsToken::class.java, SerializeAsTokenSerializer<SerializeAsToken>())
|
||||
addDefaultSerializer(Logger::class.java, LoggerSerializer)
|
||||
addDefaultSerializer(X509Certificate::class.java, X509CertificateSerializer)
|
||||
|
||||
// WARNING: reordering the registrations here will cause a change in the serialized form, since classes
|
||||
// with custom serializers get written as registration ids. This will break backwards-compatibility.
|
||||
@ -108,7 +110,6 @@ object DefaultKryoCustomizer {
|
||||
register(FileInputStream::class.java, InputStreamSerializer)
|
||||
register(CertPath::class.java, CertPathSerializer)
|
||||
register(X509CertPath::class.java, CertPathSerializer)
|
||||
register(X509Certificate::class.java, X509CertificateSerializer)
|
||||
register(BCECPrivateKey::class.java, PrivateKeySerializer)
|
||||
register(BCECPublicKey::class.java, publicKeySerializer)
|
||||
register(BCRSAPrivateCrtKey::class.java, PrivateKeySerializer)
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.corda.nodeapi
|
||||
|
||||
import net.corda.cordform.CordformNode
|
||||
import net.corda.nodeapi.internal.NodeInfoFilesCopier
|
||||
import net.corda.testing.eventually
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
|
@ -71,7 +71,7 @@ class X509UtilitiesTest {
|
||||
fun `load and save a PEM file certificate`() {
|
||||
val tmpCertificateFile = tempFile("cacert.pem")
|
||||
val caKey = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val caCert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Test Cert", organisation = "R3 Ltd", locality = "London", country = "GB"), caKey)
|
||||
val caCert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Test Cert", organisation = "R3 Ltd", locality = "London", country = "GB"), caKey).cert
|
||||
X509Utilities.saveCertificateAsPEMFile(caCert, tmpCertificateFile)
|
||||
val readCertificate = X509Utilities.loadCertificateFromPEMFile(tmpCertificateFile)
|
||||
assertEquals(caCert, readCertificate)
|
||||
@ -433,7 +433,7 @@ class X509UtilitiesTest {
|
||||
val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val rootCACert = X509Utilities.createSelfSignedCACertificate(ALICE.name, rootCAKey)
|
||||
val certificate = X509Utilities.createCertificate(CertificateType.TLS, rootCACert, rootCAKey, BOB.name.x500Name, BOB_PUBKEY)
|
||||
val expected = X509CertificateFactory().delegate.generateCertPath(listOf(certificate.cert, rootCACert.cert))
|
||||
val expected = X509CertificateFactory().generateCertPath(certificate.cert, rootCACert.cert)
|
||||
val serialized = expected.serialize(factory, context).bytes
|
||||
val actual: CertPath = serialized.deserialize(factory, context)
|
||||
assertEquals(expected, actual)
|
||||
|
@ -0,0 +1,148 @@
|
||||
package net.corda.nodeapi.internal.serialization.amqp
|
||||
|
||||
import org.junit.Test
|
||||
import net.corda.core.serialization.ClassWhitelist
|
||||
import net.corda.core.serialization.SerializationCustomSerializer
|
||||
import org.assertj.core.api.Assertions
|
||||
import java.io.NotSerializableException
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class CorDappSerializerTests {
|
||||
data class NeedsProxy (val a: String)
|
||||
|
||||
class NeedsProxyProxySerializer : SerializationCustomSerializer<NeedsProxy, NeedsProxyProxySerializer.Proxy> {
|
||||
data class Proxy(val proxy_a_: String)
|
||||
|
||||
override fun fromProxy(proxy: Proxy) = NeedsProxy(proxy.proxy_a_)
|
||||
override fun toProxy(obj: NeedsProxy) = Proxy(obj.a)
|
||||
}
|
||||
|
||||
// Standard proxy serializer used internally, here for comparison purposes
|
||||
class InternalProxySerializer(factory: SerializerFactory) :
|
||||
CustomSerializer.Proxy<NeedsProxy, InternalProxySerializer.Proxy> (
|
||||
NeedsProxy::class.java,
|
||||
InternalProxySerializer.Proxy::class.java,
|
||||
factory) {
|
||||
data class Proxy(val proxy_a_: String)
|
||||
|
||||
override fun toProxy(obj: NeedsProxy): Proxy {
|
||||
return Proxy(obj.a)
|
||||
}
|
||||
|
||||
override fun fromProxy(proxy: Proxy): NeedsProxy {
|
||||
return NeedsProxy(proxy.proxy_a_)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `type uses proxy`() {
|
||||
val internalProxyFactory = testDefaultFactory()
|
||||
val proxyFactory = testDefaultFactory()
|
||||
val defaultFactory = testDefaultFactory()
|
||||
|
||||
val msg = "help"
|
||||
|
||||
proxyFactory.registerExternal (CorDappCustomSerializer(NeedsProxyProxySerializer(), proxyFactory))
|
||||
internalProxyFactory.register (InternalProxySerializer(internalProxyFactory))
|
||||
|
||||
val needsProxy = NeedsProxy(msg)
|
||||
|
||||
val bAndSProxy = SerializationOutput(proxyFactory).serializeAndReturnSchema (needsProxy)
|
||||
val bAndSInternal = SerializationOutput(internalProxyFactory).serializeAndReturnSchema (needsProxy)
|
||||
val bAndSDefault = SerializationOutput(defaultFactory).serializeAndReturnSchema (needsProxy)
|
||||
|
||||
val objFromDefault = DeserializationInput(defaultFactory).deserializeAndReturnEnvelope(bAndSDefault.obj)
|
||||
val objFromInternal = DeserializationInput(internalProxyFactory).deserializeAndReturnEnvelope(bAndSInternal.obj)
|
||||
val objFromProxy = DeserializationInput(proxyFactory).deserializeAndReturnEnvelope(bAndSProxy.obj)
|
||||
|
||||
assertEquals(msg, objFromDefault.obj.a)
|
||||
assertEquals(msg, objFromInternal.obj.a)
|
||||
assertEquals(msg, objFromProxy.obj.a)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun proxiedTypeIsNested() {
|
||||
data class A (val a: Int, val b: NeedsProxy)
|
||||
|
||||
val factory = testDefaultFactory()
|
||||
factory.registerExternal (CorDappCustomSerializer(NeedsProxyProxySerializer(), factory))
|
||||
|
||||
val tv1 = 100
|
||||
val tv2 = "pants schmants"
|
||||
val bAndS = SerializationOutput(factory).serializeAndReturnSchema (A(tv1, NeedsProxy(tv2)))
|
||||
|
||||
val objFromDefault = DeserializationInput(factory).deserializeAndReturnEnvelope(bAndS.obj)
|
||||
|
||||
assertEquals(tv1, objFromDefault.obj.a)
|
||||
assertEquals(tv2, objFromDefault.obj.b.a)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testWithWhitelistNotAllowed() {
|
||||
data class A (val a: Int, val b: NeedsProxy)
|
||||
|
||||
class WL : ClassWhitelist {
|
||||
private val allowedClasses = emptySet<String>()
|
||||
|
||||
override fun hasListed(type: Class<*>): Boolean = type.name in allowedClasses
|
||||
}
|
||||
|
||||
val factory = SerializerFactory(WL(), ClassLoader.getSystemClassLoader())
|
||||
factory.registerExternal (CorDappCustomSerializer(NeedsProxyProxySerializer(), factory))
|
||||
|
||||
val tv1 = 100
|
||||
val tv2 = "pants schmants"
|
||||
Assertions.assertThatThrownBy {
|
||||
SerializationOutput(factory).serialize(A(tv1, NeedsProxy(tv2)))
|
||||
}.isInstanceOf(NotSerializableException::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testWithWhitelistAllowed() {
|
||||
data class A (val a: Int, val b: NeedsProxy)
|
||||
|
||||
class WL : ClassWhitelist {
|
||||
private val allowedClasses = hashSetOf(
|
||||
A::class.java.name,
|
||||
NeedsProxy::class.java.name)
|
||||
|
||||
override fun hasListed(type: Class<*>): Boolean = type.name in allowedClasses
|
||||
}
|
||||
|
||||
val factory = SerializerFactory(WL(), ClassLoader.getSystemClassLoader())
|
||||
factory.registerExternal (CorDappCustomSerializer(NeedsProxyProxySerializer(), factory))
|
||||
|
||||
val tv1 = 100
|
||||
val tv2 = "pants schmants"
|
||||
val obj = DeserializationInput(factory).deserialize(
|
||||
SerializationOutput(factory).serialize(A(tv1, NeedsProxy(tv2))))
|
||||
|
||||
assertEquals(tv1, obj.a)
|
||||
assertEquals(tv2, obj.b.a)
|
||||
}
|
||||
|
||||
// The custom type not being whitelisted won't matter here because the act of adding a
|
||||
// custom serializer bypasses the whitelist
|
||||
@Test
|
||||
fun testWithWhitelistAllowedOuterOnly() {
|
||||
data class A (val a: Int, val b: NeedsProxy)
|
||||
|
||||
class WL : ClassWhitelist {
|
||||
// explicitly don't add NeedsProxy
|
||||
private val allowedClasses = hashSetOf(A::class.java.name)
|
||||
|
||||
override fun hasListed(type: Class<*>): Boolean = type.name in allowedClasses
|
||||
}
|
||||
|
||||
val factory = SerializerFactory(WL(), ClassLoader.getSystemClassLoader())
|
||||
factory.registerExternal (CorDappCustomSerializer(NeedsProxyProxySerializer(), factory))
|
||||
|
||||
val tv1 = 100
|
||||
val tv2 = "pants schmants"
|
||||
val obj = DeserializationInput(factory).deserialize(
|
||||
SerializationOutput(factory).serialize(A(tv1, NeedsProxy(tv2))))
|
||||
|
||||
assertEquals(tv1, obj.a)
|
||||
assertEquals(tv2, obj.b.a)
|
||||
}
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
package net.corda.nodeapi.internal.serialization.amqp
|
||||
|
||||
import net.corda.core.serialization.SerializedBytes
|
||||
import net.corda.nodeapi.internal.serialization.AllWhitelist
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class GenericsTests {
|
||||
|
||||
@Test
|
||||
fun nestedSerializationOfGenerics() {
|
||||
data class G<T>(val a: T)
|
||||
data class Wrapper<T>(val a: Int, val b: G<T>)
|
||||
|
||||
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
|
||||
val altContextFactory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
|
||||
val ser = SerializationOutput(factory)
|
||||
|
||||
val bytes = ser.serializeAndReturnSchema(G("hi"))
|
||||
|
||||
assertEquals("hi", DeserializationInput(factory).deserialize(bytes.obj).a)
|
||||
assertEquals("hi", DeserializationInput(altContextFactory).deserialize(bytes.obj).a)
|
||||
|
||||
val bytes2 = ser.serializeAndReturnSchema(Wrapper(1, G("hi")))
|
||||
|
||||
DeserializationInput(factory).deserialize(bytes2.obj).apply {
|
||||
assertEquals(1, a)
|
||||
assertEquals("hi", b.a)
|
||||
}
|
||||
|
||||
DeserializationInput(altContextFactory).deserialize(bytes2.obj).apply {
|
||||
assertEquals(1, a)
|
||||
assertEquals("hi", b.a)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nestedGenericsReferencesByteArrayViaSerializedBytes() {
|
||||
data class G(val a : Int)
|
||||
data class Wrapper<T : Any>(val a: Int, val b: SerializedBytes<T>)
|
||||
|
||||
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
|
||||
val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
|
||||
val ser = SerializationOutput(factory)
|
||||
|
||||
val gBytes = ser.serialize(G(1))
|
||||
val bytes2 = ser.serializeAndReturnSchema(Wrapper<G>(1, gBytes))
|
||||
|
||||
DeserializationInput(factory).deserialize(bytes2.obj).apply {
|
||||
assertEquals(1, a)
|
||||
assertEquals(1, DeserializationInput(factory).deserialize(b).a)
|
||||
}
|
||||
DeserializationInput(factory2).deserialize(bytes2.obj).apply {
|
||||
assertEquals(1, a)
|
||||
assertEquals(1, DeserializationInput(factory).deserialize(b).a)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nestedSerializationInMultipleContextsDoesntColideGenericTypes() {
|
||||
data class InnerA(val a_a: Int)
|
||||
data class InnerB(val a_b: Int)
|
||||
data class InnerC(val a_c: String)
|
||||
data class Container<T>(val b: T)
|
||||
data class Wrapper<T : Any>(val c: Container<T>)
|
||||
|
||||
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
|
||||
val factories = listOf(factory, SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()))
|
||||
val ser = SerializationOutput(factory)
|
||||
|
||||
ser.serialize(Wrapper(Container(InnerA(1)))).apply {
|
||||
factories.forEach {
|
||||
DeserializationInput(it).deserialize(this).apply { assertEquals(1, c.b.a_a) }
|
||||
}
|
||||
}
|
||||
|
||||
ser.serialize(Wrapper(Container(InnerB(1)))).apply {
|
||||
factories.forEach {
|
||||
DeserializationInput(it).deserialize(this).apply { assertEquals(1, c.b.a_b) }
|
||||
}
|
||||
}
|
||||
|
||||
ser.serialize(Wrapper(Container(InnerC("Ho ho ho")))).apply {
|
||||
factories.forEach {
|
||||
DeserializationInput(it).deserialize(this).apply { assertEquals("Ho ho ho", c.b.a_c) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nestedSerializationWhereGenericDoesntImpactFingerprint() {
|
||||
data class Inner(val a : Int)
|
||||
data class Container<T : Any>(val b: Inner)
|
||||
data class Wrapper<T: Any>(val c: Container<T>)
|
||||
|
||||
val factorys = listOf(
|
||||
SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()),
|
||||
SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()))
|
||||
|
||||
val ser = SerializationOutput(factorys[0])
|
||||
|
||||
ser.serialize(Wrapper<Int>(Container(Inner(1)))).apply {
|
||||
factorys.forEach {
|
||||
assertEquals(1, DeserializationInput(it).deserialize(this).c.b.a)
|
||||
}
|
||||
}
|
||||
|
||||
ser.serialize(Wrapper<String>(Container(Inner(1)))).apply {
|
||||
factorys.forEach {
|
||||
assertEquals(1, DeserializationInput(it).deserialize(this).c.b.a)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -25,7 +25,7 @@ class OverridePKSerializerTest {
|
||||
get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
class AMQPTestSerializationScheme : AbstractAMQPSerializationScheme() {
|
||||
class AMQPTestSerializationScheme : AbstractAMQPSerializationScheme(emptyList()) {
|
||||
override fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
@ -593,7 +593,7 @@ class SerializationOutputTests {
|
||||
fun `test transaction state`() {
|
||||
val state = TransactionState(FooState(), FOO_PROGRAM_ID, MEGA_CORP)
|
||||
|
||||
val scheme = AMQPServerSerializationScheme()
|
||||
val scheme = AMQPServerSerializationScheme(emptyList())
|
||||
val func = scheme::class.superclasses.single { it.simpleName == "AbstractAMQPSerializationScheme" }
|
||||
.java.getDeclaredMethod("registerCustomSerializers", SerializerFactory::class.java)
|
||||
func.isAccessible = true
|
||||
|
@ -13,6 +13,7 @@ import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.deleteIfExists
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.node.services.NotaryService
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
@ -21,12 +22,15 @@ import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.node.internal.StartedNode
|
||||
import net.corda.node.services.config.BFTSMaRtConfiguration
|
||||
import net.corda.node.services.config.NotaryConfig
|
||||
import net.corda.node.services.transactions.BFTNonValidatingNotaryService
|
||||
import net.corda.node.services.transactions.minClusterSize
|
||||
import net.corda.node.services.transactions.minCorrectReplicas
|
||||
import net.corda.nodeapi.internal.ServiceIdentityGenerator
|
||||
import net.corda.nodeapi.internal.NotaryInfo
|
||||
import net.corda.node.utilities.ServiceIdentityGenerator
|
||||
import net.corda.testing.IntegrationTest
|
||||
import net.corda.testing.chooseIdentity
|
||||
import net.corda.nodeapi.internal.NetworkParametersCopier
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
import net.corda.testing.node.MockNetwork
|
||||
@ -68,19 +72,26 @@ class BFTNotaryServiceTests : IntegrationTest() {
|
||||
|
||||
notary = ServiceIdentityGenerator.generateToDisk(
|
||||
replicaIds.map { mockNet.baseDirectory(mockNet.nextNodeId + it) },
|
||||
CordaX500Name(BFTNonValidatingNotaryService.id, "BFT", "Zurich", "CH")
|
||||
)
|
||||
CordaX500Name("BFT", "Zurich", "CH"),
|
||||
NotaryService.constructId(validating = false, bft = true))
|
||||
|
||||
val networkParameters = NetworkParametersCopier(testNetworkParameters(listOf(NotaryInfo(notary, false))))
|
||||
|
||||
val clusterAddresses = replicaIds.map { NetworkHostAndPort("localhost", 11000 + it * 10) }
|
||||
|
||||
replicaIds.forEach { replicaId ->
|
||||
mockNet.createNode(MockNodeParameters(configOverrides = {
|
||||
val nodes = replicaIds.map { replicaId ->
|
||||
mockNet.createUnstartedNode(MockNodeParameters(configOverrides = {
|
||||
val notary = NotaryConfig(validating = false, bftSMaRt = BFTSMaRtConfiguration(replicaId, clusterAddresses, exposeRaces = exposeRaces))
|
||||
doReturn(notary).whenever(it).notary
|
||||
}))
|
||||
}
|
||||
} + mockNet.createUnstartedNode()
|
||||
|
||||
node = mockNet.createNode()
|
||||
// MockNetwork doesn't support BFT clusters, so we create all the nodes we need unstarted, and then install the
|
||||
// network-parameters in their directories before they're started.
|
||||
node = nodes.map { node ->
|
||||
networkParameters.install(mockNet.baseDirectory(node.id))
|
||||
node.start()
|
||||
}.last()
|
||||
}
|
||||
|
||||
/** Failure mode is the redundant replica gets stuck in startup, so we can't dispose it cleanly at the end. */
|
||||
|
@ -13,7 +13,6 @@ import net.corda.finance.flows.CashIssueFlow
|
||||
import net.corda.finance.flows.CashPaymentFlow
|
||||
import net.corda.node.services.Permissions.Companion.invokeRpc
|
||||
import net.corda.node.services.Permissions.Companion.startFlow
|
||||
import net.corda.node.services.transactions.RaftValidatingNotaryService
|
||||
import net.corda.nodeapi.internal.config.User
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.driver.NodeHandle
|
||||
@ -47,7 +46,7 @@ class DistributedServiceTests : IntegrationTest() {
|
||||
|
||||
driver(
|
||||
extraCordappPackagesToScan = listOf("net.corda.finance.contracts"),
|
||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY.name.copy(commonName = RaftValidatingNotaryService.id), rpcUsers = listOf(testUser), cluster = ClusterSpec.Raft(clusterSize = 3))))
|
||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY.name, rpcUsers = listOf(testUser), cluster = ClusterSpec.Raft(clusterSize = 3))))
|
||||
{
|
||||
alice = startNode(providedName = ALICE.name, rpcUsers = listOf(testUser)).getOrThrow()
|
||||
raftNotaryIdentity = defaultNotaryIdentity
|
||||
|
@ -0,0 +1,100 @@
|
||||
package net.corda.node.services.network
|
||||
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.utilities.seconds
|
||||
import net.corda.testing.ALICE
|
||||
import net.corda.testing.BOB
|
||||
import net.corda.testing.internal.CompatibilityZoneParams
|
||||
import net.corda.testing.driver.NodeHandle
|
||||
import net.corda.testing.driver.PortAllocation
|
||||
import net.corda.testing.internal.internalDriver
|
||||
import net.corda.testing.node.network.NetworkMapServer
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.net.URL
|
||||
|
||||
class NetworkMapTest {
|
||||
private val cacheTimeout = 1.seconds
|
||||
private val portAllocation = PortAllocation.Incremental(10000)
|
||||
|
||||
private lateinit var networkMapServer: NetworkMapServer
|
||||
private lateinit var compatibilityZone: CompatibilityZoneParams
|
||||
|
||||
@Before
|
||||
fun start() {
|
||||
networkMapServer = NetworkMapServer(cacheTimeout, portAllocation.nextHostAndPort())
|
||||
val address = networkMapServer.start()
|
||||
compatibilityZone = CompatibilityZoneParams(URL("http://$address"))
|
||||
}
|
||||
|
||||
@After
|
||||
fun cleanUp() {
|
||||
networkMapServer.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `nodes can see each other using the http network map`() {
|
||||
internalDriver(portAllocation = portAllocation, compatibilityZone = compatibilityZone) {
|
||||
val alice = startNode(providedName = ALICE.name)
|
||||
val bob = startNode(providedName = BOB.name)
|
||||
|
||||
val notaryNode = defaultNotaryNode.get()
|
||||
val aliceNode = alice.get()
|
||||
val bobNode = bob.get()
|
||||
|
||||
notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
|
||||
aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
|
||||
bobNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `nodes process network map add updates correctly when adding new node to network map`() {
|
||||
internalDriver(portAllocation = portAllocation, compatibilityZone = compatibilityZone) {
|
||||
val alice = startNode(providedName = ALICE.name)
|
||||
val notaryNode = defaultNotaryNode.get()
|
||||
val aliceNode = alice.get()
|
||||
|
||||
notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo)
|
||||
aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo)
|
||||
|
||||
val bob = startNode(providedName = BOB.name)
|
||||
val bobNode = bob.get()
|
||||
|
||||
// Wait for network map client to poll for the next update.
|
||||
Thread.sleep(cacheTimeout.toMillis() * 2)
|
||||
|
||||
bobNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
|
||||
notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
|
||||
aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `nodes process network map remove updates correctly`() {
|
||||
internalDriver(portAllocation = portAllocation, compatibilityZone = compatibilityZone) {
|
||||
val alice = startNode(providedName = ALICE.name)
|
||||
val bob = startNode(providedName = BOB.name)
|
||||
|
||||
val notaryNode = defaultNotaryNode.get()
|
||||
val aliceNode = alice.get()
|
||||
val bobNode = bob.get()
|
||||
|
||||
notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
|
||||
aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
|
||||
bobNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
|
||||
|
||||
networkMapServer.removeNodeInfo(aliceNode.nodeInfo)
|
||||
|
||||
// Wait for network map client to poll for the next update.
|
||||
Thread.sleep(cacheTimeout.toMillis() * 2)
|
||||
|
||||
notaryNode.onlySees(notaryNode.nodeInfo, bobNode.nodeInfo)
|
||||
bobNode.onlySees(notaryNode.nodeInfo, bobNode.nodeInfo)
|
||||
}
|
||||
}
|
||||
|
||||
private fun NodeHandle.onlySees(vararg nodes: NodeInfo) = assertThat(rpc.networkMapSnapshot()).containsOnly(*nodes)
|
||||
}
|
@ -10,7 +10,7 @@ import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.services.KeyManagementService
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.node.services.identity.InMemoryIdentityService
|
||||
import net.corda.nodeapi.NodeInfoFilesCopier
|
||||
import net.corda.nodeapi.internal.NodeInfoFilesCopier
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.node.MockKeyManagementService
|
||||
import net.corda.testing.node.makeTestIdentityService
|
||||
|
@ -0,0 +1,158 @@
|
||||
package net.corda.node.utilities.registration
|
||||
|
||||
import com.google.common.net.HostAndPort
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.cert
|
||||
import net.corda.core.internal.toX509CertHolder
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.core.utilities.minutes
|
||||
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
|
||||
import net.corda.nodeapi.internal.crypto.CertificateType
|
||||
import net.corda.nodeapi.internal.crypto.X509CertificateFactory
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_CA
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_INTERMEDIATE_CA
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
|
||||
import net.corda.testing.internal.CompatibilityZoneParams
|
||||
import net.corda.testing.driver.PortAllocation
|
||||
import net.corda.testing.internal.internalDriver
|
||||
import net.corda.testing.node.network.NetworkMapServer
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.bouncycastle.pkcs.PKCS10CertificationRequest
|
||||
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.InputStream
|
||||
import java.net.URL
|
||||
import java.security.KeyPair
|
||||
import java.security.cert.CertPath
|
||||
import java.security.cert.Certificate
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipOutputStream
|
||||
import javax.ws.rs.*
|
||||
import javax.ws.rs.core.MediaType
|
||||
import javax.ws.rs.core.Response
|
||||
|
||||
class NodeRegistrationTest {
|
||||
private val portAllocation = PortAllocation.Incremental(13000)
|
||||
private val rootCertAndKeyPair = createSelfKeyAndSelfSignedCertificate()
|
||||
private val registrationHandler = RegistrationHandler(rootCertAndKeyPair)
|
||||
|
||||
private lateinit var server: NetworkMapServer
|
||||
private lateinit var serverHostAndPort: NetworkHostAndPort
|
||||
|
||||
@Before
|
||||
fun startServer() {
|
||||
server = NetworkMapServer(1.minutes, portAllocation.nextHostAndPort(), registrationHandler)
|
||||
serverHostAndPort = server.start()
|
||||
}
|
||||
|
||||
@After
|
||||
fun stopServer() {
|
||||
server.close()
|
||||
}
|
||||
|
||||
// TODO Ideally this test should be checking that two nodes that register are able to transact with each other. However
|
||||
// starting a second node hangs so that needs to be fixed.
|
||||
@Test
|
||||
fun `node registration correct root cert`() {
|
||||
val compatibilityZone = CompatibilityZoneParams(URL("http://$serverHostAndPort"), rootCert = rootCertAndKeyPair.certificate.cert)
|
||||
internalDriver(
|
||||
portAllocation = portAllocation,
|
||||
notarySpecs = emptyList(),
|
||||
compatibilityZone = compatibilityZone
|
||||
) {
|
||||
startNode(providedName = CordaX500Name("Alice", "London", "GB")).getOrThrow()
|
||||
assertThat(registrationHandler.idsPolled).contains("Alice")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `node registration wrong root cert`() {
|
||||
val someCert = createSelfKeyAndSelfSignedCertificate().certificate.cert
|
||||
val compatibilityZone = CompatibilityZoneParams(URL("http://$serverHostAndPort"), rootCert = someCert)
|
||||
internalDriver(
|
||||
portAllocation = portAllocation,
|
||||
notarySpecs = emptyList(),
|
||||
compatibilityZone = compatibilityZone,
|
||||
// Changing the content of the truststore makes the node fail in a number of ways if started out process.
|
||||
startNodesInProcess = true
|
||||
) {
|
||||
assertThatThrownBy {
|
||||
startNode(providedName = CordaX500Name("Alice", "London", "GB")).getOrThrow()
|
||||
}.isInstanceOf(WrongRootCertException::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createSelfKeyAndSelfSignedCertificate(): CertificateAndKeyPair {
|
||||
val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val rootCACert = X509Utilities.createSelfSignedCACertificate(
|
||||
CordaX500Name(
|
||||
commonName = "Integration Test Corda Node Root CA",
|
||||
organisation = "R3 Ltd",
|
||||
locality = "London",
|
||||
country = "GB"),
|
||||
rootCAKey)
|
||||
return CertificateAndKeyPair(rootCACert, rootCAKey)
|
||||
}
|
||||
}
|
||||
|
||||
@Path("certificate")
|
||||
class RegistrationHandler(private val rootCertAndKeyPair: CertificateAndKeyPair) {
|
||||
private val certPaths = HashMap<String, CertPath>()
|
||||
val idsPolled = HashSet<String>()
|
||||
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_OCTET_STREAM)
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
fun registration(input: InputStream): Response {
|
||||
val certificationRequest = input.use { JcaPKCS10CertificationRequest(it.readBytes()) }
|
||||
val (certPath, name) = createSignedClientCertificate(
|
||||
certificationRequest,
|
||||
rootCertAndKeyPair.keyPair,
|
||||
arrayOf(rootCertAndKeyPair.certificate.cert))
|
||||
certPaths[name.organisation] = certPath
|
||||
return Response.ok(name.organisation).build()
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{id}")
|
||||
fun reply(@PathParam("id") id: String): Response {
|
||||
idsPolled += id
|
||||
return buildResponse(certPaths[id]!!.certificates)
|
||||
}
|
||||
|
||||
private fun buildResponse(certificates: List<Certificate>): Response {
|
||||
val baos = ByteArrayOutputStream()
|
||||
ZipOutputStream(baos).use { zip ->
|
||||
listOf(CORDA_CLIENT_CA, CORDA_INTERMEDIATE_CA, CORDA_ROOT_CA).zip(certificates).forEach {
|
||||
zip.putNextEntry(ZipEntry("${it.first}.cer"))
|
||||
zip.write(it.second.encoded)
|
||||
zip.closeEntry()
|
||||
}
|
||||
}
|
||||
return Response.ok(baos.toByteArray())
|
||||
.type("application/zip")
|
||||
.header("Content-Disposition", "attachment; filename=\"certificates.zip\"").build()
|
||||
}
|
||||
|
||||
private fun createSignedClientCertificate(certificationRequest: PKCS10CertificationRequest,
|
||||
caKeyPair: KeyPair,
|
||||
caCertPath: Array<Certificate>): Pair<CertPath, CordaX500Name> {
|
||||
val request = JcaPKCS10CertificationRequest(certificationRequest)
|
||||
val name = CordaX500Name.parse(request.subject.toString())
|
||||
val x509CertificateHolder = X509Utilities.createCertificate(CertificateType.NODE_CA,
|
||||
caCertPath.first().toX509CertHolder(),
|
||||
caKeyPair,
|
||||
name,
|
||||
request.publicKey,
|
||||
nameConstraints = null)
|
||||
val certPath = X509CertificateFactory().generateCertPath(x509CertificateHolder.cert, *caCertPath)
|
||||
return Pair(certPath, name)
|
||||
}
|
||||
}
|
@ -89,11 +89,11 @@ class MQSecurityAsNodeTest : MQSecurityTest() {
|
||||
val legalName = MEGA_CORP.name
|
||||
certificatesDirectory.createDirectories()
|
||||
if (!trustStoreFile.exists()) {
|
||||
javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordatruststore.jks").copyTo(trustStoreFile)
|
||||
javaClass.classLoader.getResourceAsStream("certificates/cordatruststore.jks").copyTo(trustStoreFile)
|
||||
}
|
||||
|
||||
val caKeyStore = loadKeyStore(
|
||||
javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordadevcakeys.jks"),
|
||||
javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"),
|
||||
"cordacadevpass")
|
||||
|
||||
val rootCACert = caKeyStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA).toX509CertHolder()
|
||||
|
@ -23,10 +23,7 @@ import net.corda.core.internal.concurrent.openFuture
|
||||
import net.corda.core.messaging.*
|
||||
import net.corda.core.node.*
|
||||
import net.corda.core.node.services.*
|
||||
import net.corda.core.serialization.SerializationWhitelist
|
||||
import net.corda.core.serialization.SerializeAsToken
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.serialization.*
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.debug
|
||||
@ -47,7 +44,6 @@ import net.corda.node.services.config.NotaryConfig
|
||||
import net.corda.node.services.config.configureWithDevSSLCertificate
|
||||
import net.corda.node.services.events.NodeSchedulerService
|
||||
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.keys.PersistentKeyManagementService
|
||||
import net.corda.node.services.messaging.MessagingService
|
||||
@ -62,6 +58,7 @@ import net.corda.node.services.vault.NodeVaultService
|
||||
import net.corda.node.services.vault.VaultSoftLockManager
|
||||
import net.corda.node.shell.InteractiveShell
|
||||
import net.corda.node.utilities.AffinityExecutor
|
||||
import net.corda.nodeapi.internal.NetworkParameters
|
||||
import net.corda.nodeapi.internal.crypto.*
|
||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
||||
@ -96,6 +93,8 @@ import net.corda.core.crypto.generateKeyPair as cryptoGenerateKeyPair
|
||||
* Marked as SingletonSerializeAsToken to prevent the invisible reference to AbstractNode in the ServiceHub accidentally
|
||||
* sweeping up the Node into the Kryo checkpoint serialization via any flows holding a reference to ServiceHub.
|
||||
*/
|
||||
// TODO Log warning if this node is a notary but not one of the ones specified in the network parameters, both for core and custom
|
||||
|
||||
// In theory the NodeInfo for the node should be passed in, instead, however currently this is constructed by the
|
||||
// AbstractNode. It should be possible to generate the NodeInfo outside of AbstractNode, so it can be passed in.
|
||||
abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
@ -125,6 +124,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
// low-performance prototyping period.
|
||||
protected abstract val serverThread: AffinityExecutor
|
||||
|
||||
protected lateinit var networkParameters: NetworkParameters
|
||||
private val cordappServices = MutableClassToInstanceMap.create<SerializeAsToken>()
|
||||
private val flowFactories = ConcurrentHashMap<Class<out FlowLogic<*>>, InitiatedFlowFactory<*>>()
|
||||
|
||||
@ -137,7 +137,11 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
protected lateinit var network: MessagingService
|
||||
protected val runOnStop = ArrayList<() -> Any?>()
|
||||
protected val _nodeReadyFuture = openFuture<Unit>()
|
||||
protected val networkMapClient: NetworkMapClient? by lazy { configuration.compatibilityZoneURL?.let(::NetworkMapClient) }
|
||||
protected val networkMapClient: NetworkMapClient? by lazy {
|
||||
configuration.compatibilityZoneURL?.let {
|
||||
NetworkMapClient(it, services.identityService.trustRoot)
|
||||
}
|
||||
}
|
||||
|
||||
lateinit var securityManager: RPCSecurityManager get
|
||||
|
||||
@ -179,7 +183,9 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
val schemaService = NodeSchemaService(cordappLoader.cordappSchemas)
|
||||
val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null)
|
||||
initialiseDatabasePersistence(schemaService, makeIdentityService(identity.certificate)) { database ->
|
||||
val persistentNetworkMapCache = PersistentNetworkMapCache(database)
|
||||
// TODO The fact that we need to specify an empty list of notaries just to generate our node info looks like
|
||||
// a code smell.
|
||||
val persistentNetworkMapCache = PersistentNetworkMapCache(database, notaries = emptyList())
|
||||
val (keyPairs, info) = initNodeInfo(persistentNetworkMapCache, identity, identityKeyPair)
|
||||
val identityKeypair = keyPairs.first { it.public == info.legalIdentities.first().owningKey }
|
||||
val serialisedNodeInfo = info.serialize()
|
||||
@ -193,12 +199,13 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
check(started == null) { "Node has already been started" }
|
||||
log.info("Node starting up ...")
|
||||
initCertificate()
|
||||
readNetworkParameters()
|
||||
val schemaService = NodeSchemaService(cordappLoader.cordappSchemas)
|
||||
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.
|
||||
val (startedImpl, schedulerService) = initialiseDatabasePersistence(schemaService, identityService) { database ->
|
||||
val networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database), identityService)
|
||||
val networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, networkParameters.notaries), identityService)
|
||||
val (keyPairs, info) = initNodeInfo(networkMapCache, identity, identityKeyPair)
|
||||
identityService.loadIdentities(info.legalIdentitiesAndCerts)
|
||||
val transactionStorage = makeTransactionStorage(database)
|
||||
@ -282,14 +289,19 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
val keyPairs = mutableSetOf(identityKeyPair)
|
||||
|
||||
myNotaryIdentity = configuration.notary?.let {
|
||||
if (it.isClusterConfig) {
|
||||
val (notaryIdentity, notaryIdentityKeyPair) = obtainIdentity(it)
|
||||
keyPairs += notaryIdentityKeyPair
|
||||
notaryIdentity
|
||||
} else {
|
||||
// In case of a single notary service myNotaryIdentity will be the node's single identity.
|
||||
identity
|
||||
}
|
||||
}
|
||||
|
||||
var info = NodeInfo(
|
||||
myAddresses(),
|
||||
listOf(identity, myNotaryIdentity).filterNotNull(),
|
||||
setOf(identity, myNotaryIdentity).filterNotNull(),
|
||||
versionInfo.platformVersion,
|
||||
platformClock.instant().toEpochMilli()
|
||||
)
|
||||
@ -632,6 +644,13 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
return PersistentKeyManagementService(identityService, keyPairs)
|
||||
}
|
||||
|
||||
private fun readNetworkParameters() {
|
||||
val file = configuration.baseDirectory / "network-parameters"
|
||||
networkParameters = file.readAll().deserialize<SignedData<NetworkParameters>>().verified()
|
||||
log.info(networkParameters.toString())
|
||||
check(networkParameters.minimumPlatformVersion <= versionInfo.platformVersion) { "Node is too old for the network" }
|
||||
}
|
||||
|
||||
private fun makeCoreNotaryService(notaryConfig: NotaryConfig, database: CordaPersistence): NotaryService {
|
||||
val notaryKey = myNotaryIdentity?.owningKey ?: throw IllegalArgumentException("No notary identity initialized when creating a notary service")
|
||||
return notaryConfig.run {
|
||||
@ -687,22 +706,16 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
private fun obtainIdentity(notaryConfig: NotaryConfig?): Pair<PartyAndCertificate, KeyPair> {
|
||||
val keyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword)
|
||||
|
||||
val (id, singleName) = if (notaryConfig == null) {
|
||||
// Node's main identity
|
||||
val (id, singleName) = if (notaryConfig == null || !notaryConfig.isClusterConfig) {
|
||||
// Node's main identity or if it's a single node notary
|
||||
Pair("identity", myLegalName)
|
||||
} else {
|
||||
val notaryId = notaryConfig.run {
|
||||
NotaryService.constructId(validating, raft != null, bftSMaRt != null, custom)
|
||||
}
|
||||
if (!notaryConfig.isClusterConfig) {
|
||||
// Node's notary identity
|
||||
Pair(notaryId, myLegalName.copy(commonName = notaryId))
|
||||
} else {
|
||||
// The node is part of a distributed notary whose identity must already be generated beforehand
|
||||
// The node is part of a distributed notary whose identity must already be generated beforehand.
|
||||
Pair(notaryId, null)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Integrate with Key management service?
|
||||
val privateKeyAlias = "$id-private-key"
|
||||
|
||||
@ -740,7 +753,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
throw ConfigurationException("The name '$singleName' for $id doesn't match what's in the key store: $subject")
|
||||
}
|
||||
|
||||
val certPath = X509CertificateFactory().delegate.generateCertPath(certificates)
|
||||
val certPath = X509CertificateFactory().generateCertPath(certificates)
|
||||
return Pair(PartyAndCertificate(certPath), keyPair)
|
||||
}
|
||||
|
||||
|
@ -194,11 +194,17 @@ open class Node(configuration: NodeConfiguration,
|
||||
return if (!AddressUtils.isPublic(host)) {
|
||||
val foundPublicIP = AddressUtils.tryDetectPublicIP()
|
||||
if (foundPublicIP == null) {
|
||||
try {
|
||||
val retrievedHostName = networkMapClient?.myPublicHostname()
|
||||
if (retrievedHostName != null) {
|
||||
log.info("Retrieved public IP from Network Map Service: $this. This will be used instead of the provided \"$host\" as the advertised address.")
|
||||
}
|
||||
retrievedHostName
|
||||
} catch (ignore: Throwable) {
|
||||
// Cannot reach the network map service, ignore the exception and use provided P2P address instead.
|
||||
log.warn("Cannot connect to the network map service for public IP detection.")
|
||||
null
|
||||
}
|
||||
} else {
|
||||
log.info("Detected public IP: ${foundPublicIP.hostAddress}. This will be used instead of the provided \"$host\" as the advertised address.")
|
||||
foundPublicIP.hostAddress
|
||||
@ -309,11 +315,11 @@ open class Node(configuration: NodeConfiguration,
|
||||
nodeSerializationEnv = SerializationEnvironmentImpl(
|
||||
SerializationFactoryImpl().apply {
|
||||
registerScheme(KryoServerSerializationScheme())
|
||||
registerScheme(AMQPServerSerializationScheme())
|
||||
registerScheme(AMQPServerSerializationScheme(cordappLoader.cordapps))
|
||||
},
|
||||
KRYO_P2P_CONTEXT.withClassLoader(classloader),
|
||||
p2pContext = AMQP_P2P_CONTEXT.withClassLoader(classloader),
|
||||
rpcServerContext = KRYO_RPC_SERVER_CONTEXT.withClassLoader(classloader),
|
||||
storageContext = KRYO_STORAGE_CONTEXT.withClassLoader(classloader),
|
||||
storageContext = AMQP_STORAGE_CONTEXT.withClassLoader(classloader),
|
||||
checkpointContext = KRYO_CHECKPOINT_CONTEXT.withClassLoader(classloader))
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ import net.corda.core.internal.*
|
||||
import net.corda.core.internal.cordapp.CordappImpl
|
||||
import net.corda.core.node.services.CordaService
|
||||
import net.corda.core.schemas.MappedSchema
|
||||
import net.corda.core.serialization.SerializationCustomSerializer
|
||||
import net.corda.core.serialization.SerializationWhitelist
|
||||
import net.corda.core.serialization.SerializeAsToken
|
||||
import net.corda.core.utilities.contextLogger
|
||||
@ -175,15 +176,16 @@ class CordappLoader private constructor(private val cordappJarPaths: List<Restri
|
||||
/** A Cordapp representing the core package which is not scanned automatically. */
|
||||
@VisibleForTesting
|
||||
internal val coreCordapp = CordappImpl(
|
||||
listOf(),
|
||||
listOf(),
|
||||
coreRPCFlows,
|
||||
listOf(),
|
||||
listOf(),
|
||||
listOf(),
|
||||
listOf(),
|
||||
setOf(),
|
||||
ContractUpgradeFlow.javaClass.protectionDomain.codeSource.location // Core JAR location
|
||||
contractClassNames = listOf(),
|
||||
initiatedFlows = listOf(),
|
||||
rpcFlows = coreRPCFlows,
|
||||
serviceFlows = listOf(),
|
||||
schedulableFlows = listOf(),
|
||||
services = listOf(),
|
||||
serializationWhitelists = listOf(),
|
||||
serializationCustomSerializers = listOf(),
|
||||
customSchemas = setOf(),
|
||||
jarPath = ContractUpgradeFlow.javaClass.protectionDomain.codeSource.location // Core JAR location
|
||||
)
|
||||
}
|
||||
|
||||
@ -197,6 +199,7 @@ class CordappLoader private constructor(private val cordappJarPaths: List<Restri
|
||||
findSchedulableFlows(scanResult),
|
||||
findServices(scanResult),
|
||||
findPlugins(it),
|
||||
findSerializers(scanResult),
|
||||
findCustomSchemas(scanResult),
|
||||
it.url)
|
||||
}
|
||||
@ -247,6 +250,10 @@ class CordappLoader private constructor(private val cordappJarPaths: List<Restri
|
||||
} + DefaultWhitelist // Always add the DefaultWhitelist to the whitelist for an app.
|
||||
}
|
||||
|
||||
private fun findSerializers(scanResult: RestrictedScanResult): List<SerializationCustomSerializer<*, *>> {
|
||||
return scanResult.getClassesImplementing(SerializationCustomSerializer::class)
|
||||
}
|
||||
|
||||
private fun findCustomSchemas(scanResult: RestrictedScanResult): Set<MappedSchema> {
|
||||
return scanResult.getClassesWithSuperclass(MappedSchema::class).toSet()
|
||||
}
|
||||
@ -303,6 +310,14 @@ class CordappLoader private constructor(private val cordappJarPaths: List<Restri
|
||||
.map { it.kotlin.objectOrNewInstance() }
|
||||
}
|
||||
|
||||
fun <T : Any> getClassesImplementing(type: KClass<T>): List<T> {
|
||||
return scanResult.getNamesOfClassesImplementing(type.java)
|
||||
.filter { it.startsWith(qualifiedNamePrefix) }
|
||||
.mapNotNull { loadClass(it, type) }
|
||||
.filterNot { Modifier.isAbstract(it.modifiers) }
|
||||
.map { it.kotlin.objectOrNewInstance() }
|
||||
}
|
||||
|
||||
fun <T : Any> getClassesWithAnnotation(type: KClass<T>, annotation: KClass<out Annotation>): List<Class<out T>> {
|
||||
return scanResult.getNamesOfClassesWithAnnotation(annotation.java)
|
||||
.filter { it.startsWith(qualifiedNamePrefix) }
|
||||
|
@ -63,10 +63,10 @@ fun NodeConfiguration.configureWithDevSSLCertificate() = configureDevKeyAndTrust
|
||||
fun SSLConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500Name) {
|
||||
certificatesDirectory.createDirectories()
|
||||
if (!trustStoreFile.exists()) {
|
||||
loadKeyStore(javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordatruststore.jks"), "trustpass").save(trustStoreFile, trustStorePassword)
|
||||
loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordatruststore.jks"), "trustpass").save(trustStoreFile, trustStorePassword)
|
||||
}
|
||||
if (!sslKeystore.exists() || !nodeKeystore.exists()) {
|
||||
val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordadevcakeys.jks"), "cordacadevpass")
|
||||
val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass")
|
||||
createKeystoreForCordaNode(sslKeystore, nodeKeystore, keyStorePassword, keyStorePassword, caKeyStore, "cordacadevkeypass", myLegalName)
|
||||
|
||||
// Move distributed service composite key (generated by ServiceIdentityGenerator.generateToDisk) to keystore if exists.
|
||||
|
@ -129,7 +129,6 @@ data class NodeConfigurationImpl(
|
||||
// This is a sanity feature do not remove.
|
||||
require(!useTestClock || devMode) { "Cannot use test clock outside of dev mode" }
|
||||
require(devModeOptions == null || devMode) { "Cannot use devModeOptions outside of dev mode" }
|
||||
require(myLegalName.commonName == null) { "Common name must be null: $myLegalName" }
|
||||
require(security == null || rpcUsers.isEmpty()) {
|
||||
"Cannot specify both 'rpcUsers' and 'security' in configuration"
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ class InMemoryIdentityService(identities: Iterable<PartyAndCertificate>,
|
||||
if (firstCertWithThisName != identity.certificate) {
|
||||
val certificates = identity.certPath.certificates
|
||||
val idx = certificates.lastIndexOf(firstCertWithThisName)
|
||||
val firstPath = X509CertificateFactory().delegate.generateCertPath(certificates.slice(idx until certificates.size))
|
||||
val firstPath = X509CertificateFactory().generateCertPath(certificates.slice(idx until certificates.size))
|
||||
verifyAndRegisterIdentity(PartyAndCertificate(firstPath))
|
||||
}
|
||||
|
||||
|
@ -133,7 +133,7 @@ class PersistentIdentityService(override val trustRoot: X509Certificate,
|
||||
if (firstCertWithThisName != identity.certificate) {
|
||||
val certificates = identity.certPath.certificates
|
||||
val idx = certificates.lastIndexOf(firstCertWithThisName)
|
||||
val firstPath = X509CertificateFactory().delegate.generateCertPath(certificates.slice(idx until certificates.size))
|
||||
val firstPath = X509CertificateFactory().generateCertPath(certificates.slice(idx until certificates.size))
|
||||
verifyAndRegisterIdentity(PartyAndCertificate(firstPath))
|
||||
}
|
||||
|
||||
|
@ -37,7 +37,7 @@ fun freshCertificate(identityService: IdentityServiceInternal,
|
||||
val window = X509Utilities.getCertificateValidityWindow(Duration.ZERO, 3650.days, issuerCert)
|
||||
val ourCertificate = X509Utilities.createCertificate(CertificateType.WELL_KNOWN_IDENTITY, issuerCert.subject,
|
||||
issuerSigner, issuer.name, subjectPublicKey, window)
|
||||
val ourCertPath = X509CertificateFactory().delegate.generateCertPath(listOf(ourCertificate.cert) + issuer.certPath.certificates)
|
||||
val ourCertPath = X509CertificateFactory().generateCertPath(listOf(ourCertificate.cert) + issuer.certPath.certificates)
|
||||
val anonymisedIdentity = PartyAndCertificate(ourCertPath)
|
||||
identityService.justVerifyAndRegisterIdentity(anonymisedIdentity)
|
||||
return anonymisedIdentity
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.corda.node.services.network
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.google.common.util.concurrent.MoreExecutors
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.SignedData
|
||||
@ -13,6 +12,10 @@ import net.corda.core.utilities.minutes
|
||||
import net.corda.core.utilities.seconds
|
||||
import net.corda.node.services.api.NetworkMapCacheInternal
|
||||
import net.corda.node.utilities.NamedThreadFactory
|
||||
import net.corda.nodeapi.internal.NetworkMap
|
||||
import net.corda.nodeapi.internal.NetworkParameters
|
||||
import net.corda.nodeapi.internal.SignedNetworkMap
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import okhttp3.CacheControl
|
||||
import okhttp3.Headers
|
||||
import rx.Subscription
|
||||
@ -20,11 +23,12 @@ import java.io.BufferedReader
|
||||
import java.io.Closeable
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.security.cert.X509Certificate
|
||||
import java.time.Duration
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class NetworkMapClient(compatibilityZoneURL: URL) {
|
||||
class NetworkMapClient(compatibilityZoneURL: URL, private val trustedRoot: X509Certificate) {
|
||||
private val networkMapUrl = URL("$compatibilityZoneURL/network-map")
|
||||
|
||||
fun publish(signedNodeInfo: SignedData<NodeInfo>) {
|
||||
@ -42,14 +46,30 @@ class NetworkMapClient(compatibilityZoneURL: URL) {
|
||||
|
||||
fun getNetworkMap(): NetworkMapResponse {
|
||||
val conn = networkMapUrl.openHttpConnection()
|
||||
val response = conn.inputStream.bufferedReader().use(BufferedReader::readLine)
|
||||
val networkMap = ObjectMapper().readValue(response, List::class.java).map { SecureHash.parse(it.toString()) }
|
||||
val signedNetworkMap = conn.inputStream.use { it.readBytes() }.deserialize<SignedNetworkMap>()
|
||||
val networkMap = signedNetworkMap.verified()
|
||||
// Assume network map cert is issued by the root.
|
||||
X509Utilities.validateCertificateChain(trustedRoot, signedNetworkMap.sig.by, trustedRoot)
|
||||
val timeout = CacheControl.parse(Headers.of(conn.headerFields.filterKeys { it != null }.mapValues { it.value.first() })).maxAgeSeconds().seconds
|
||||
return NetworkMapResponse(networkMap, timeout)
|
||||
}
|
||||
|
||||
fun getNodeInfo(nodeInfoHash: SecureHash): NodeInfo? {
|
||||
val conn = URL("$networkMapUrl/$nodeInfoHash").openHttpConnection()
|
||||
val conn = URL("$networkMapUrl/node-info/$nodeInfoHash").openHttpConnection()
|
||||
return if (conn.responseCode == HttpURLConnection.HTTP_NOT_FOUND) {
|
||||
null
|
||||
} else {
|
||||
val signedNodeInfo = conn.inputStream.use { it.readBytes() }.deserialize<SignedData<NodeInfo>>()
|
||||
val nodeInfo = signedNodeInfo.verified()
|
||||
// Verify node info is signed by node identity
|
||||
// TODO : Validate multiple signatures when NodeInfo supports multiple identities.
|
||||
require(nodeInfo.legalIdentities.any { it.owningKey == signedNodeInfo.sig.by }) { "NodeInfo must be signed by the node owning key." }
|
||||
nodeInfo
|
||||
}
|
||||
}
|
||||
|
||||
fun getNetworkParameter(networkParameterHash: SecureHash): NetworkParameters? {
|
||||
val conn = URL("$networkMapUrl/network-parameter/$networkParameterHash").openHttpConnection()
|
||||
return if (conn.responseCode == HttpURLConnection.HTTP_NOT_FOUND) {
|
||||
null
|
||||
} else {
|
||||
@ -63,7 +83,7 @@ class NetworkMapClient(compatibilityZoneURL: URL) {
|
||||
}
|
||||
}
|
||||
|
||||
data class NetworkMapResponse(val networkMap: List<SecureHash>, val cacheMaxAge: Duration)
|
||||
data class NetworkMapResponse(val networkMap: NetworkMap, val cacheMaxAge: Duration)
|
||||
|
||||
class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
|
||||
private val fileWatcher: NodeInfoWatcher,
|
||||
@ -108,21 +128,28 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
|
||||
val nextScheduleDelay = try {
|
||||
val (networkMap, cacheTimeout) = networkMapClient.getNetworkMap()
|
||||
val currentNodeHashes = networkMapCache.allNodeHashes
|
||||
(networkMap - currentNodeHashes).mapNotNull {
|
||||
val hashesFromNetworkMap = networkMap.nodeInfoHashes
|
||||
(hashesFromNetworkMap - currentNodeHashes).mapNotNull {
|
||||
// Download new node info from network map
|
||||
try {
|
||||
networkMapClient.getNodeInfo(it)
|
||||
} catch (t: Throwable) {
|
||||
// Failure to retrieve one node info shouldn't stop the whole update, log and return null instead.
|
||||
logger.warn("Error encountered when downloading node info '$it', skipping...", t)
|
||||
null
|
||||
}
|
||||
}.forEach {
|
||||
// Add new node info to the network map cache, these could be new node info or modification of node info for existing nodes.
|
||||
networkMapCache.addNode(it)
|
||||
}
|
||||
// Remove node info from network map.
|
||||
(currentNodeHashes - networkMap - fileWatcher.processedNodeInfoHashes)
|
||||
(currentNodeHashes - hashesFromNetworkMap - fileWatcher.processedNodeInfoHashes)
|
||||
.mapNotNull(networkMapCache::getNodeByHash)
|
||||
.forEach(networkMapCache::removeNode)
|
||||
|
||||
// TODO: Check NetworkParameter.
|
||||
cacheTimeout
|
||||
} catch (t: Throwable) {
|
||||
logger.warn("Error encountered while updating network map, will retry in $retryInterval", t)
|
||||
logger.warn("Error encountered while updating network map, will retry in ${retryInterval.seconds} seconds", t)
|
||||
retryInterval
|
||||
}
|
||||
// Schedule the next update.
|
||||
@ -138,7 +165,7 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
|
||||
try {
|
||||
networkMapClient.publish(signedNodeInfo)
|
||||
} catch (t: Throwable) {
|
||||
logger.warn("Error encountered while publishing node info, will retry in $retryInterval.", t)
|
||||
logger.warn("Error encountered while publishing node info, will retry in ${retryInterval.seconds} seconds.", t)
|
||||
// TODO: Exponential backoff?
|
||||
executor.schedule(this, retryInterval.toMillis(), TimeUnit.MILLISECONDS)
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.core.utilities.seconds
|
||||
import net.corda.nodeapi.NodeInfoFilesCopier
|
||||
import net.corda.nodeapi.internal.NodeInfoFilesCopier
|
||||
import rx.Observable
|
||||
import rx.Scheduler
|
||||
import java.io.IOException
|
||||
|
@ -14,7 +14,6 @@ import net.corda.core.messaging.DataFeed
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.core.node.services.NetworkMapCache.MapChange
|
||||
import net.corda.core.node.services.NotaryService
|
||||
import net.corda.core.node.services.PartyInfo
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.serialization.serialize
|
||||
@ -26,6 +25,7 @@ import net.corda.node.services.api.NetworkMapCacheInternal
|
||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.nodeapi.internal.persistence.bufferUntilDatabaseCommit
|
||||
import net.corda.nodeapi.internal.persistence.wrapWithDatabaseTransaction
|
||||
import net.corda.nodeapi.internal.NotaryInfo
|
||||
import org.hibernate.Session
|
||||
import rx.Observable
|
||||
import rx.subjects.PublishSubject
|
||||
@ -34,6 +34,7 @@ import java.sql.Connection
|
||||
import java.util.*
|
||||
import javax.annotation.concurrent.ThreadSafe
|
||||
import kotlin.collections.HashMap
|
||||
import kotlin.collections.HashSet
|
||||
|
||||
class NetworkMapCacheImpl(
|
||||
networkMapCacheBase: NetworkMapCacheBaseInternal,
|
||||
@ -72,13 +73,15 @@ class NetworkMapCacheImpl(
|
||||
* Extremely simple in-memory cache of the network map.
|
||||
*/
|
||||
@ThreadSafe
|
||||
open class PersistentNetworkMapCache(private val database: CordaPersistence) : SingletonSerializeAsToken(), NetworkMapCacheBaseInternal {
|
||||
open class PersistentNetworkMapCache(
|
||||
private val database: CordaPersistence,
|
||||
notaries: List<NotaryInfo>
|
||||
) : SingletonSerializeAsToken(), NetworkMapCacheBaseInternal {
|
||||
companion object {
|
||||
private val logger = contextLogger()
|
||||
}
|
||||
|
||||
// TODO Small explanation, partyNodes and registeredNodes is left in memory as it was before, because it will be removed in
|
||||
// next PR that gets rid of services. These maps are used only for queries by service.
|
||||
// TODO Cleanup registered and party nodes
|
||||
protected val registeredNodes: MutableMap<PublicKey, NodeInfo> = Collections.synchronizedMap(HashMap())
|
||||
protected val partyNodes: MutableList<NodeInfo> get() = registeredNodes.map { it.value }.toMutableList()
|
||||
private val _changed = PublishSubject.create<MapChange>()
|
||||
@ -92,22 +95,9 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence) : S
|
||||
override val nodeReady: CordaFuture<Void?> get() = _registrationFuture
|
||||
private var _loadDBSuccess: Boolean = false
|
||||
override val loadDBSuccess get() = _loadDBSuccess
|
||||
// TODO From the NetworkMapService redesign doc: Remove the concept of network services.
|
||||
// As a temporary hack, just assume for now that every network has a notary service named "Notary Service" that can be looked up in the map.
|
||||
// This should eliminate the only required usage of services.
|
||||
// It is ensured on node startup when constructing a notary that the name contains "notary".
|
||||
override val notaryIdentities: List<Party>
|
||||
get() {
|
||||
return partyNodes
|
||||
.flatMap {
|
||||
// TODO: validate notary identity certificates before loading into network map cache.
|
||||
// Notary certificates have to be signed by the doorman directly
|
||||
it.legalIdentities
|
||||
}
|
||||
.filter { it.name.commonName?.startsWith(NotaryService.ID_PREFIX) ?: false }
|
||||
.toSet() // Distinct, because of distributed service nodes
|
||||
.sortedBy { it.name.toString() }
|
||||
}
|
||||
|
||||
override val notaryIdentities: List<Party> = notaries.map { it.identity }
|
||||
private val validatingNotaries = notaries.mapNotNullTo(HashSet()) { if (it.validating) it.identity else null }
|
||||
|
||||
init {
|
||||
database.transaction { loadFromDB(session) }
|
||||
@ -122,7 +112,7 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence) : S
|
||||
select(get<String>(NodeInfoSchemaV1.PersistentNodeInfo::hash.name))
|
||||
}
|
||||
}
|
||||
session.createQuery(query).resultList.map { SecureHash.sha256(it) }
|
||||
session.createQuery(query).resultList.map { SecureHash.parse(it) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -138,7 +128,7 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence) : S
|
||||
}
|
||||
}
|
||||
|
||||
override fun isValidatingNotary(party: Party): Boolean = isNotary(party) && "validating" in party.name.commonName!!
|
||||
override fun isValidatingNotary(party: Party): Boolean = party in validatingNotaries
|
||||
|
||||
override fun getPartyInfo(party: Party): PartyInfo? {
|
||||
val nodes = database.transaction { queryByIdentityKey(session, party.owningKey) }
|
||||
@ -310,7 +300,6 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence) : S
|
||||
id = 0,
|
||||
hash = nodeInfo.serialize().hash.toString(),
|
||||
addresses = nodeInfo.addresses.map { NodeInfoSchemaV1.DBHostAndPort.fromHostAndPort(it) },
|
||||
// TODO Another ugly hack with special first identity...
|
||||
legalIdentitiesAndCerts = nodeInfo.legalIdentitiesAndCerts.mapIndexed { idx, elem ->
|
||||
NodeInfoSchemaV1.DBPartyAndCertificate(elem, isMain = idx == 0)
|
||||
},
|
||||
|
@ -12,6 +12,7 @@ import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
|
||||
import org.bouncycastle.openssl.jcajce.JcaPEMWriter
|
||||
import org.bouncycastle.util.io.pem.PemObject
|
||||
import java.io.StringWriter
|
||||
import java.nio.file.Path
|
||||
import java.security.KeyPair
|
||||
import java.security.KeyStore
|
||||
import java.security.cert.Certificate
|
||||
@ -30,6 +31,23 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
|
||||
private val keystorePassword = config.keyStorePassword
|
||||
// TODO: Use different password for private key.
|
||||
private val privateKeyPassword = config.keyStorePassword
|
||||
private val trustStore: KeyStore
|
||||
private val rootCert: Certificate
|
||||
|
||||
init {
|
||||
require(config.trustStoreFile.exists()) {
|
||||
"${config.trustStoreFile} does not exist. This file must contain the root CA cert of your compatibility zone. " +
|
||||
"Please contact your CZ operator."
|
||||
}
|
||||
trustStore = loadKeyStore(config.trustStoreFile, config.trustStorePassword)
|
||||
val rootCert = trustStore.getCertificate(CORDA_ROOT_CA)
|
||||
require(rootCert != null) {
|
||||
"${config.trustStoreFile} does not contain a certificate with the key $CORDA_ROOT_CA." +
|
||||
"This file must contain the root CA cert of your compatibility zone. " +
|
||||
"Please contact your CZ operator."
|
||||
}
|
||||
this.rootCert = rootCert
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the initial keystore for a node is set up.
|
||||
@ -74,13 +92,14 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
|
||||
caKeyStore.addOrReplaceKey(CORDA_CLIENT_CA, keyPair.private, privateKeyPassword.toCharArray(), certificates)
|
||||
caKeyStore.deleteEntry(SELF_SIGNED_PRIVATE_KEY)
|
||||
caKeyStore.save(config.nodeKeystore, keystorePassword)
|
||||
// Save root certificates to trust store.
|
||||
val trustStore = loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword)
|
||||
// Assumes certificate chain always starts with client certificate and end with root certificate.
|
||||
trustStore.addOrReplaceCertificate(CORDA_ROOT_CA, certificates.last())
|
||||
trustStore.save(config.trustStoreFile, config.trustStorePassword)
|
||||
println("Node private key and certificate stored in ${config.nodeKeystore}.")
|
||||
|
||||
// Check that the root of the signed certificate matches the expected certificate in the truststore.
|
||||
if (rootCert != certificates.last()) {
|
||||
// Assumes certificate chain always starts with client certificate and end with root certificate.
|
||||
throw WrongRootCertException(rootCert, certificates.last(), config.trustStoreFile)
|
||||
}
|
||||
|
||||
println("Generating SSL certificate for node messaging service.")
|
||||
val sslKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val caCert = caKeyStore.getX509Certificate(CORDA_CLIENT_CA).toX509CertHolder()
|
||||
@ -150,3 +169,17 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception thrown when the doorman root certificate doesn't match the expected (out-of-band) root certificate.
|
||||
* This usually means that there has been a Man-in-the-middle attack when contacting the doorman.
|
||||
*/
|
||||
class WrongRootCertException(expected: Certificate,
|
||||
actual: Certificate,
|
||||
expectedFilePath: Path):
|
||||
Exception("""
|
||||
The Root CA returned back from the registration process does not match the expected Root CA
|
||||
expected: $expected
|
||||
actual: $actual
|
||||
the expected certificate is stored in: $expectedFilePath with alias $CORDA_ROOT_CA
|
||||
""".trimMargin())
|
||||
|
@ -6,6 +6,9 @@ import kotlin.Triple;
|
||||
import net.corda.core.contracts.*;
|
||||
import net.corda.core.crypto.CryptoUtils;
|
||||
import net.corda.core.identity.AbstractParty;
|
||||
import net.corda.core.identity.CordaX500Name;
|
||||
import net.corda.core.identity.Party;
|
||||
import net.corda.core.identity.PartyAndCertificate;
|
||||
import net.corda.core.messaging.DataFeed;
|
||||
import net.corda.core.node.services.Vault;
|
||||
import net.corda.core.node.services.VaultQueryException;
|
||||
@ -33,17 +36,19 @@ import rx.Observable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.math.BigInteger;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import static net.corda.core.crypto.CryptoUtils.entropyToKeyPair;
|
||||
import static net.corda.core.node.services.vault.QueryCriteriaUtils.DEFAULT_PAGE_NUM;
|
||||
import static net.corda.core.node.services.vault.QueryCriteriaUtils.MAX_PAGE_SIZE;
|
||||
import static net.corda.core.utilities.ByteArrays.toHexString;
|
||||
import static net.corda.finance.contracts.asset.CashUtilities.*;
|
||||
import static net.corda.testing.CoreTestUtils.*;
|
||||
import static net.corda.testing.TestConstants.*;
|
||||
import static net.corda.testing.node.MockServices.makeTestDatabaseAndMockServices;
|
||||
@ -51,6 +56,10 @@ import static net.corda.testing.node.MockServicesKt.makeTestIdentityService;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class VaultQueryJavaTests {
|
||||
private static final CordaX500Name DUMMY_CASH_ISSUER_NAME = new CordaX500Name("Snake Oil Issuer", "London", "GB");
|
||||
private static final KeyPair DUMMY_CASH_ISSUER_KEY = entropyToKeyPair(BigInteger.valueOf(10));
|
||||
private static final PartyAndCertificate DUMMY_CASH_ISSUER_IDENTITY = getTestPartyAndCertificate(new Party(DUMMY_CASH_ISSUER_NAME, DUMMY_CASH_ISSUER_KEY.getPublic()));
|
||||
private static final PartyAndReference DUMMY_CASH_ISSUER = DUMMY_CASH_ISSUER_IDENTITY.getParty().ref((byte) 1);
|
||||
@Rule
|
||||
public final SerializationEnvironmentRule testSerialization = new SerializationEnvironmentRule();
|
||||
private VaultFiller vaultFiller;
|
||||
@ -61,13 +70,13 @@ public class VaultQueryJavaTests {
|
||||
@Before
|
||||
public void setUp() throws CertificateException, InvalidAlgorithmParameterException {
|
||||
List<String> cordappPackages = Arrays.asList("net.corda.testing.contracts", "net.corda.finance.contracts.asset", CashSchemaV1.class.getPackage().getName());
|
||||
IdentityServiceInternal identitySvc = makeTestIdentityService(Arrays.asList(getMEGA_CORP_IDENTITY(), getDUMMY_CASH_ISSUER_IDENTITY(), getDUMMY_NOTARY_IDENTITY()));
|
||||
IdentityServiceInternal identitySvc = makeTestIdentityService(Arrays.asList(getMEGA_CORP_IDENTITY(), DUMMY_CASH_ISSUER_IDENTITY, getDUMMY_NOTARY_IDENTITY()));
|
||||
Pair<CordaPersistence, MockServices> databaseAndServices = makeTestDatabaseAndMockServices(
|
||||
Arrays.asList(getMEGA_CORP_KEY(), getDUMMY_NOTARY_KEY()),
|
||||
identitySvc,
|
||||
cordappPackages,
|
||||
getMEGA_CORP().getName());
|
||||
issuerServices = new MockServices(cordappPackages, rigorousMock(IdentityServiceInternal.class), getDUMMY_CASH_ISSUER_NAME(), getDUMMY_CASH_ISSUER_KEY(), getBOC_KEY());
|
||||
issuerServices = new MockServices(cordappPackages, rigorousMock(IdentityServiceInternal.class), DUMMY_CASH_ISSUER_NAME, DUMMY_CASH_ISSUER_KEY, getBOC_KEY());
|
||||
database = databaseAndServices.getFirst();
|
||||
MockServices services = databaseAndServices.getSecond();
|
||||
vaultFiller = new VaultFiller(services, getDUMMY_NOTARY(), getDUMMY_NOTARY_KEY());
|
||||
@ -138,7 +147,7 @@ public class VaultQueryJavaTests {
|
||||
new Amount<>(100, Currency.getInstance("USD")),
|
||||
issuerServices,
|
||||
3,
|
||||
getDUMMY_CASH_ISSUER(),
|
||||
DUMMY_CASH_ISSUER,
|
||||
null,
|
||||
new Random());
|
||||
return tx;
|
||||
@ -213,10 +222,10 @@ public class VaultQueryJavaTests {
|
||||
Amount<Currency> dollars100 = new Amount<>(100, Currency.getInstance("USD"));
|
||||
Amount<Currency> dollars10 = new Amount<>(10, Currency.getInstance("USD"));
|
||||
Amount<Currency> dollars1 = new Amount<>(1, Currency.getInstance("USD"));
|
||||
vaultFiller.fillWithSomeTestCash(pounds, issuerServices, 1, getDUMMY_CASH_ISSUER());
|
||||
vaultFiller.fillWithSomeTestCash(dollars100, issuerServices, 1, getDUMMY_CASH_ISSUER());
|
||||
vaultFiller.fillWithSomeTestCash(dollars10, issuerServices, 1, getDUMMY_CASH_ISSUER());
|
||||
vaultFiller.fillWithSomeTestCash(dollars1, issuerServices, 1, getDUMMY_CASH_ISSUER());
|
||||
vaultFiller.fillWithSomeTestCash(pounds, issuerServices, 1, DUMMY_CASH_ISSUER);
|
||||
vaultFiller.fillWithSomeTestCash(dollars100, issuerServices, 1, DUMMY_CASH_ISSUER);
|
||||
vaultFiller.fillWithSomeTestCash(dollars10, issuerServices, 1, DUMMY_CASH_ISSUER);
|
||||
vaultFiller.fillWithSomeTestCash(dollars1, issuerServices, 1, DUMMY_CASH_ISSUER);
|
||||
return tx;
|
||||
});
|
||||
database.transaction(tx -> {
|
||||
@ -257,7 +266,7 @@ public class VaultQueryJavaTests {
|
||||
new Amount<>(100, Currency.getInstance("USD")),
|
||||
issuerServices,
|
||||
3,
|
||||
getDUMMY_CASH_ISSUER(),
|
||||
DUMMY_CASH_ISSUER,
|
||||
null,
|
||||
new Random());
|
||||
return tx;
|
||||
@ -331,11 +340,11 @@ public class VaultQueryJavaTests {
|
||||
Amount<Currency> dollars300 = new Amount<>(300, Currency.getInstance("USD"));
|
||||
Amount<Currency> pounds = new Amount<>(400, Currency.getInstance("GBP"));
|
||||
Amount<Currency> swissfrancs = new Amount<>(500, Currency.getInstance("CHF"));
|
||||
vaultFiller.fillWithSomeTestCash(dollars100, issuerServices, 1, getDUMMY_CASH_ISSUER());
|
||||
vaultFiller.fillWithSomeTestCash(dollars200, issuerServices, 2, getDUMMY_CASH_ISSUER());
|
||||
vaultFiller.fillWithSomeTestCash(dollars300, issuerServices, 3, getDUMMY_CASH_ISSUER());
|
||||
vaultFiller.fillWithSomeTestCash(pounds, issuerServices, 4, getDUMMY_CASH_ISSUER());
|
||||
vaultFiller.fillWithSomeTestCash(swissfrancs, issuerServices, 5, getDUMMY_CASH_ISSUER());
|
||||
vaultFiller.fillWithSomeTestCash(dollars100, issuerServices, 1, DUMMY_CASH_ISSUER);
|
||||
vaultFiller.fillWithSomeTestCash(dollars200, issuerServices, 2, DUMMY_CASH_ISSUER);
|
||||
vaultFiller.fillWithSomeTestCash(dollars300, issuerServices, 3, DUMMY_CASH_ISSUER);
|
||||
vaultFiller.fillWithSomeTestCash(pounds, issuerServices, 4, DUMMY_CASH_ISSUER);
|
||||
vaultFiller.fillWithSomeTestCash(swissfrancs, issuerServices, 5, DUMMY_CASH_ISSUER);
|
||||
return tx;
|
||||
});
|
||||
database.transaction(tx -> {
|
||||
@ -377,11 +386,11 @@ public class VaultQueryJavaTests {
|
||||
Amount<Currency> dollars300 = new Amount<>(300, Currency.getInstance("USD"));
|
||||
Amount<Currency> pounds = new Amount<>(400, Currency.getInstance("GBP"));
|
||||
Amount<Currency> swissfrancs = new Amount<>(500, Currency.getInstance("CHF"));
|
||||
vaultFiller.fillWithSomeTestCash(dollars100, issuerServices, 1, getDUMMY_CASH_ISSUER());
|
||||
vaultFiller.fillWithSomeTestCash(dollars200, issuerServices, 2, getDUMMY_CASH_ISSUER());
|
||||
vaultFiller.fillWithSomeTestCash(dollars300, issuerServices, 3, getDUMMY_CASH_ISSUER());
|
||||
vaultFiller.fillWithSomeTestCash(pounds, issuerServices, 4, getDUMMY_CASH_ISSUER());
|
||||
vaultFiller.fillWithSomeTestCash(swissfrancs, issuerServices, 5, getDUMMY_CASH_ISSUER());
|
||||
vaultFiller.fillWithSomeTestCash(dollars100, issuerServices, 1, DUMMY_CASH_ISSUER);
|
||||
vaultFiller.fillWithSomeTestCash(dollars200, issuerServices, 2, DUMMY_CASH_ISSUER);
|
||||
vaultFiller.fillWithSomeTestCash(dollars300, issuerServices, 3, DUMMY_CASH_ISSUER);
|
||||
vaultFiller.fillWithSomeTestCash(pounds, issuerServices, 4, DUMMY_CASH_ISSUER);
|
||||
vaultFiller.fillWithSomeTestCash(swissfrancs, issuerServices, 5, DUMMY_CASH_ISSUER);
|
||||
return tx;
|
||||
});
|
||||
database.transaction(tx -> {
|
||||
@ -438,9 +447,9 @@ public class VaultQueryJavaTests {
|
||||
Amount<Currency> dollars200 = new Amount<>(200, Currency.getInstance("USD"));
|
||||
Amount<Currency> pounds300 = new Amount<>(300, Currency.getInstance("GBP"));
|
||||
Amount<Currency> pounds400 = new Amount<>(400, Currency.getInstance("GBP"));
|
||||
vaultFiller.fillWithSomeTestCash(dollars100, issuerServices, 1, getDUMMY_CASH_ISSUER());
|
||||
vaultFiller.fillWithSomeTestCash(dollars100, issuerServices, 1, DUMMY_CASH_ISSUER);
|
||||
vaultFiller.fillWithSomeTestCash(dollars200, issuerServices, 2, getBOC().ref(new OpaqueBytes("1".getBytes())));
|
||||
vaultFiller.fillWithSomeTestCash(pounds300, issuerServices, 3, getDUMMY_CASH_ISSUER());
|
||||
vaultFiller.fillWithSomeTestCash(pounds300, issuerServices, 3, DUMMY_CASH_ISSUER);
|
||||
vaultFiller.fillWithSomeTestCash(pounds400, issuerServices, 4, getBOC().ref(new OpaqueBytes("1".getBytes())));
|
||||
return tx;
|
||||
});
|
||||
@ -460,13 +469,13 @@ public class VaultQueryJavaTests {
|
||||
assertThat(results.getOtherResults().get(1)).isEqualTo(CryptoUtils.toStringShort(getBOC_PUBKEY()));
|
||||
assertThat(results.getOtherResults().get(2)).isEqualTo("GBP");
|
||||
assertThat(results.getOtherResults().get(3)).isEqualTo(300L);
|
||||
assertThat(results.getOtherResults().get(4)).isEqualTo(CryptoUtils.toStringShort(getDUMMY_CASH_ISSUER().getParty().getOwningKey()));
|
||||
assertThat(results.getOtherResults().get(4)).isEqualTo(CryptoUtils.toStringShort(DUMMY_CASH_ISSUER_KEY.getPublic()));
|
||||
assertThat(results.getOtherResults().get(5)).isEqualTo("GBP");
|
||||
assertThat(results.getOtherResults().get(6)).isEqualTo(200L);
|
||||
assertThat(results.getOtherResults().get(7)).isEqualTo(CryptoUtils.toStringShort(getBOC_PUBKEY()));
|
||||
assertThat(results.getOtherResults().get(8)).isEqualTo("USD");
|
||||
assertThat(results.getOtherResults().get(9)).isEqualTo(100L);
|
||||
assertThat(results.getOtherResults().get(10)).isEqualTo(CryptoUtils.toStringShort(getDUMMY_CASH_ISSUER().getParty().getOwningKey()));
|
||||
assertThat(results.getOtherResults().get(10)).isEqualTo(CryptoUtils.toStringShort(DUMMY_CASH_ISSUER_KEY.getPublic()));
|
||||
assertThat(results.getOtherResults().get(11)).isEqualTo("USD");
|
||||
|
||||
} catch (NoSuchFieldException e) {
|
||||
|
@ -94,7 +94,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
|
||||
// allow interruption half way through.
|
||||
mockNet = MockNetwork(threadPerNode = true, cordappPackages = cordappPackages)
|
||||
val ledgerIdentityService = rigorousMock<IdentityServiceInternal>()
|
||||
ledger(MockServices(cordappPackages, ledgerIdentityService, MEGA_CORP.name)) {
|
||||
MockServices(cordappPackages, ledgerIdentityService, MEGA_CORP.name).ledger(DUMMY_NOTARY) {
|
||||
val notaryNode = mockNet.defaultNotaryNode
|
||||
val aliceNode = mockNet.createPartyNode(ALICE_NAME)
|
||||
val bobNode = mockNet.createPartyNode(BOB_NAME)
|
||||
@ -146,7 +146,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
|
||||
fun `trade cash for commercial paper fails using soft locking`() {
|
||||
mockNet = MockNetwork(threadPerNode = true, cordappPackages = cordappPackages)
|
||||
val ledgerIdentityService = rigorousMock<IdentityServiceInternal>()
|
||||
ledger(MockServices(cordappPackages, ledgerIdentityService, MEGA_CORP.name)) {
|
||||
MockServices(cordappPackages, ledgerIdentityService, MEGA_CORP.name).ledger(DUMMY_NOTARY) {
|
||||
val notaryNode = mockNet.defaultNotaryNode
|
||||
val aliceNode = mockNet.createPartyNode(ALICE_NAME)
|
||||
val bobNode = mockNet.createPartyNode(BOB_NAME)
|
||||
@ -204,7 +204,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
|
||||
fun `shutdown and restore`() {
|
||||
mockNet = MockNetwork(cordappPackages = cordappPackages)
|
||||
val ledgerIdentityService = rigorousMock<IdentityServiceInternal>()
|
||||
ledger(MockServices(cordappPackages, ledgerIdentityService, MEGA_CORP.name)) {
|
||||
MockServices(cordappPackages, ledgerIdentityService, MEGA_CORP.name).ledger(DUMMY_NOTARY) {
|
||||
val notaryNode = mockNet.defaultNotaryNode
|
||||
val aliceNode = mockNet.createPartyNode(ALICE_NAME)
|
||||
var bobNode = mockNet.createPartyNode(BOB_NAME)
|
||||
@ -325,9 +325,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
|
||||
val bob = bobNode.info.singleIdentity()
|
||||
val bank = bankNode.info.singleIdentity()
|
||||
val issuer = bank.ref(1, 2, 3)
|
||||
|
||||
ledger(aliceNode.services) {
|
||||
|
||||
aliceNode.services.ledger(DUMMY_NOTARY) {
|
||||
// Insert a prospectus type attachment into the commercial paper transaction.
|
||||
val stream = ByteArrayOutputStream()
|
||||
JarOutputStream(stream).use {
|
||||
@ -431,8 +429,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
|
||||
val bank: Party = bankNode.info.singleIdentity()
|
||||
val bob = bobNode.info.singleIdentity()
|
||||
val issuer = bank.ref(1, 2, 3)
|
||||
|
||||
ledger(aliceNode.services) {
|
||||
aliceNode.services.ledger(DUMMY_NOTARY) {
|
||||
// Insert a prospectus type attachment into the commercial paper transaction.
|
||||
val stream = ByteArrayOutputStream()
|
||||
JarOutputStream(stream).use {
|
||||
@ -501,7 +498,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
|
||||
fun `dependency with error on buyer side`() {
|
||||
mockNet = MockNetwork(cordappPackages = cordappPackages)
|
||||
val ledgerIdentityService = rigorousMock<IdentityServiceInternal>()
|
||||
ledger(MockServices(cordappPackages, ledgerIdentityService, MEGA_CORP.name)) {
|
||||
MockServices(cordappPackages, ledgerIdentityService, MEGA_CORP.name).ledger(DUMMY_NOTARY) {
|
||||
runWithError(ledgerIdentityService, true, false, "at least one cash input")
|
||||
}
|
||||
}
|
||||
@ -510,7 +507,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
|
||||
fun `dependency with error on seller side`() {
|
||||
mockNet = MockNetwork(cordappPackages = cordappPackages)
|
||||
val ledgerIdentityService = rigorousMock<IdentityServiceInternal>()
|
||||
ledger(MockServices(cordappPackages, ledgerIdentityService, MEGA_CORP.name)) {
|
||||
MockServices(cordappPackages, ledgerIdentityService, MEGA_CORP.name).ledger(DUMMY_NOTARY) {
|
||||
runWithError(ledgerIdentityService, false, true, "Issuances have a time-window")
|
||||
}
|
||||
}
|
||||
|
@ -28,10 +28,6 @@ import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class NotaryChangeTests {
|
||||
companion object {
|
||||
private val DUMMY_NOTARY_SERVICE_NAME: CordaX500Name = DUMMY_NOTARY.name.copy(commonName = "corda.notary.validating")
|
||||
}
|
||||
|
||||
private lateinit var mockNet: MockNetwork
|
||||
private lateinit var oldNotaryNode: StartedNode<MockNetwork.MockNode>
|
||||
private lateinit var clientNodeA: StartedNode<MockNetwork.MockNode>
|
||||
@ -42,7 +38,7 @@ class NotaryChangeTests {
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
val oldNotaryName = DUMMY_NOTARY.name.copy(organisation = "Old Dummy Notary")
|
||||
val oldNotaryName = DUMMY_REGULATOR.name
|
||||
mockNet = MockNetwork(
|
||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY.name), NotarySpec(oldNotaryName)),
|
||||
cordappPackages = listOf("net.corda.testing.contracts")
|
||||
@ -51,8 +47,8 @@ class NotaryChangeTests {
|
||||
clientNodeB = mockNet.createNode(MockNodeParameters(legalName = BOB_NAME))
|
||||
clientA = clientNodeA.info.singleIdentity()
|
||||
oldNotaryNode = mockNet.notaryNodes[1]
|
||||
newNotaryParty = clientNodeA.services.networkMapCache.getNotary(DUMMY_NOTARY_SERVICE_NAME)!!
|
||||
oldNotaryParty = clientNodeA.services.networkMapCache.getNotary(DUMMY_NOTARY_SERVICE_NAME.copy(organisation = "Old Dummy Notary"))!!
|
||||
newNotaryParty = clientNodeA.services.networkMapCache.getNotary(DUMMY_NOTARY.name)!!
|
||||
oldNotaryParty = clientNodeA.services.networkMapCache.getNotary(oldNotaryName)!!
|
||||
}
|
||||
|
||||
@After
|
||||
|
@ -161,7 +161,7 @@ class InMemoryIdentityServiceTests {
|
||||
val issuer = getTestPartyAndCertificate(x500Name, issuerKeyPair.public)
|
||||
val txKey = Crypto.generateKeyPair()
|
||||
val txCert = X509Utilities.createCertificate(CertificateType.CONFIDENTIAL_IDENTITY, issuer.certificate.toX509CertHolder(), issuerKeyPair, x500Name, txKey.public)
|
||||
val txCertPath = X509CertificateFactory().delegate.generateCertPath(listOf(txCert.cert) + issuer.certPath.certificates)
|
||||
val txCertPath = X509CertificateFactory().generateCertPath(listOf(txCert.cert) + issuer.certPath.certificates)
|
||||
return Pair(issuer, PartyAndCertificate(txCertPath))
|
||||
}
|
||||
|
||||
|
@ -256,7 +256,7 @@ class PersistentIdentityServiceTests {
|
||||
val issuer = getTestPartyAndCertificate(x500Name, issuerKeyPair.public)
|
||||
val txKey = Crypto.generateKeyPair()
|
||||
val txCert = X509Utilities.createCertificate(CertificateType.CONFIDENTIAL_IDENTITY, issuer.certificate.toX509CertHolder(), issuerKeyPair, x500Name, txKey.public)
|
||||
val txCertPath = X509CertificateFactory().delegate.generateCertPath(listOf(txCert.cert) + issuer.certPath.certificates)
|
||||
val txCertPath = X509CertificateFactory().generateCertPath(listOf(txCert.cert) + issuer.certPath.certificates)
|
||||
return Pair(issuer, PartyAndCertificate(txCertPath))
|
||||
}
|
||||
|
||||
|
@ -69,7 +69,7 @@ class ArtemisMessagingTests {
|
||||
myLegalName = ALICE.name)
|
||||
LogHelper.setLevel(PersistentUniquenessProvider::class)
|
||||
database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), rigorousMock())
|
||||
networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database), rigorousMock())
|
||||
networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, emptyList()), rigorousMock())
|
||||
}
|
||||
|
||||
@After
|
||||
|
@ -1,87 +1,48 @@
|
||||
package net.corda.node.services.network
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.PartyAndCertificate
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.sha256
|
||||
import net.corda.core.internal.cert
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.seconds
|
||||
import net.corda.node.services.network.TestNodeInfoFactory.createNodeInfo
|
||||
import net.corda.nodeapi.internal.crypto.CertificateType
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import net.corda.testing.DEV_CA
|
||||
import net.corda.testing.DEV_TRUST_ROOT
|
||||
import net.corda.testing.ROOT_CA
|
||||
import net.corda.testing.SerializationEnvironmentRule
|
||||
import net.corda.testing.driver.PortAllocation
|
||||
import net.corda.testing.node.network.NetworkMapServer
|
||||
import net.corda.testing.TestDependencyInjectionBase
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import org.eclipse.jetty.server.Server
|
||||
import org.eclipse.jetty.server.ServerConnector
|
||||
import org.eclipse.jetty.server.handler.HandlerCollection
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler
|
||||
import org.eclipse.jetty.servlet.ServletHolder
|
||||
import org.glassfish.jersey.server.ResourceConfig
|
||||
import org.glassfish.jersey.servlet.ServletContainer
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.InputStream
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.URL
|
||||
import java.security.cert.CertPath
|
||||
import java.security.cert.Certificate
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.X509Certificate
|
||||
import javax.ws.rs.*
|
||||
import javax.ws.rs.core.MediaType
|
||||
import javax.ws.rs.core.Response
|
||||
import javax.ws.rs.core.Response.ok
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
|
||||
class NetworkMapClientTest {
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule(true)
|
||||
private lateinit var server: Server
|
||||
|
||||
private lateinit var server: NetworkMapServer
|
||||
private lateinit var networkMapClient: NetworkMapClient
|
||||
private val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
private val rootCACert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Corda Node Root CA", organisation = "R3 LTD", locality = "London", country = "GB"), rootCAKey)
|
||||
private val intermediateCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
private val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootCAKey, X500Name("CN=Corda Node Intermediate CA,L=London"), intermediateCAKey.public)
|
||||
|
||||
companion object {
|
||||
private val cacheTimeout = 100000.seconds
|
||||
}
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
server = Server(InetSocketAddress("localhost", 0)).apply {
|
||||
handler = HandlerCollection().apply {
|
||||
addHandler(ServletContextHandler().apply {
|
||||
contextPath = "/"
|
||||
val resourceConfig = ResourceConfig().apply {
|
||||
// Add your API provider classes (annotated for JAX-RS) here
|
||||
register(MockNetworkMapServer())
|
||||
}
|
||||
val jerseyServlet = ServletHolder(ServletContainer(resourceConfig)).apply { initOrder = 0 }// Initialise at server start
|
||||
addServlet(jerseyServlet, "/*")
|
||||
})
|
||||
}
|
||||
}
|
||||
server.start()
|
||||
|
||||
while (!server.isStarted) {
|
||||
Thread.sleep(100)
|
||||
}
|
||||
|
||||
val hostAndPort = server.connectors.mapNotNull { it as? ServerConnector }.first()
|
||||
networkMapClient = NetworkMapClient(URL("http://${hostAndPort.host}:${hostAndPort.localPort}"))
|
||||
server = NetworkMapServer(cacheTimeout, PortAllocation.Incremental(10000).nextHostAndPort())
|
||||
val hostAndPort = server.start()
|
||||
networkMapClient = NetworkMapClient(URL("http://${hostAndPort.host}:${hostAndPort.port}"), DEV_TRUST_ROOT.cert)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
server.stop()
|
||||
server.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -94,7 +55,7 @@ class NetworkMapClientTest {
|
||||
|
||||
val nodeInfoHash = nodeInfo.serialize().sha256()
|
||||
|
||||
assertThat(networkMapClient.getNetworkMap().networkMap).containsExactly(nodeInfoHash)
|
||||
assertThat(networkMapClient.getNetworkMap().networkMap.nodeInfoHashes).containsExactly(nodeInfoHash)
|
||||
assertEquals(nodeInfo, networkMapClient.getNodeInfo(nodeInfoHash))
|
||||
|
||||
val signedNodeInfo2 = createNodeInfo("Test2")
|
||||
@ -102,53 +63,22 @@ class NetworkMapClientTest {
|
||||
networkMapClient.publish(signedNodeInfo2)
|
||||
|
||||
val nodeInfoHash2 = nodeInfo2.serialize().sha256()
|
||||
assertThat(networkMapClient.getNetworkMap().networkMap).containsExactly(nodeInfoHash, nodeInfoHash2)
|
||||
assertEquals(100000.seconds, networkMapClient.getNetworkMap().cacheMaxAge)
|
||||
assertThat(networkMapClient.getNetworkMap().networkMap.nodeInfoHashes).containsExactly(nodeInfoHash, nodeInfoHash2)
|
||||
assertEquals(cacheTimeout, networkMapClient.getNetworkMap().cacheMaxAge)
|
||||
assertEquals(nodeInfo2, networkMapClient.getNodeInfo(nodeInfoHash2))
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun `download NetworkParameter correctly`() {
|
||||
// The test server returns same network parameter for any hash.
|
||||
val networkParameter = networkMapClient.getNetworkParameter(SecureHash.randomSHA256())
|
||||
assertNotNull(networkParameter)
|
||||
assertEquals(NetworkMapServer.stubNetworkParameter, networkParameter)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `get hostname string from http response correctly`() {
|
||||
assertEquals("test.host.name", networkMapClient.myPublicHostname())
|
||||
}
|
||||
}
|
||||
|
||||
@Path("network-map")
|
||||
// This is a stub implementation of the network map rest API.
|
||||
internal class MockNetworkMapServer {
|
||||
val nodeInfoMap = mutableMapOf<SecureHash, NodeInfo>()
|
||||
@POST
|
||||
@Path("publish")
|
||||
@Consumes(MediaType.APPLICATION_OCTET_STREAM)
|
||||
fun publishNodeInfo(input: InputStream): Response {
|
||||
val registrationData = input.readBytes().deserialize<SignedData<NodeInfo>>()
|
||||
val nodeInfo = registrationData.verified()
|
||||
val nodeInfoHash = nodeInfo.serialize().sha256()
|
||||
nodeInfoMap.put(nodeInfoHash, nodeInfo)
|
||||
return ok().build()
|
||||
}
|
||||
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
fun getNetworkMap(): Response {
|
||||
return Response.ok(ObjectMapper().writeValueAsString(nodeInfoMap.keys.map { it.toString() })).header("Cache-Control", "max-age=100000").build()
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{var}")
|
||||
@Produces(MediaType.APPLICATION_OCTET_STREAM)
|
||||
fun getNodeInfo(@PathParam("var") nodeInfoHash: String): Response {
|
||||
val nodeInfo = nodeInfoMap[SecureHash.parse(nodeInfoHash)]
|
||||
return if (nodeInfo != null) {
|
||||
Response.ok(nodeInfo.serialize().bytes)
|
||||
} else {
|
||||
Response.status(Response.Status.NOT_FOUND)
|
||||
}.build()
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("my-hostname")
|
||||
fun getHostName(): Response {
|
||||
return Response.ok("test.host.name").build()
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import net.corda.core.crypto.SignedData
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.uncheckedCast
|
||||
import net.corda.nodeapi.internal.NetworkMap
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
@ -95,7 +96,7 @@ class NetworkMapUpdaterTest {
|
||||
val signedNodeInfo: SignedData<NodeInfo> = uncheckedCast(it.arguments.first())
|
||||
nodeInfoMap.put(signedNodeInfo.verified().serialize().hash, signedNodeInfo)
|
||||
}
|
||||
on { getNetworkMap() }.then { NetworkMapResponse(nodeInfoMap.keys.toList(), 100.millis) }
|
||||
on { getNetworkMap() }.then { NetworkMapResponse(NetworkMap(nodeInfoMap.keys.toList(), SecureHash.randomSHA256()), 100.millis) }
|
||||
on { getNodeInfo(any()) }.then { nodeInfoMap[it.arguments.first()]?.verified() }
|
||||
}
|
||||
|
||||
@ -149,7 +150,7 @@ class NetworkMapUpdaterTest {
|
||||
val signedNodeInfo: SignedData<NodeInfo> = uncheckedCast(it.arguments.first())
|
||||
nodeInfoMap.put(signedNodeInfo.verified().serialize().hash, signedNodeInfo)
|
||||
}
|
||||
on { getNetworkMap() }.then { NetworkMapResponse(nodeInfoMap.keys.toList(), 100.millis) }
|
||||
on { getNetworkMap() }.then { NetworkMapResponse(NetworkMap(nodeInfoMap.keys.toList(), SecureHash.randomSHA256()), 100.millis) }
|
||||
on { getNodeInfo(any()) }.then { nodeInfoMap[it.arguments.first()]?.verified() }
|
||||
}
|
||||
|
||||
|
@ -39,7 +39,7 @@ object TestNodeInfoFactory {
|
||||
}
|
||||
|
||||
private fun buildCertPath(vararg certificates: Certificate): CertPath {
|
||||
return X509CertificateFactory().delegate.generateCertPath(certificates.asList())
|
||||
return X509CertificateFactory().generateCertPath(*certificates)
|
||||
}
|
||||
|
||||
private fun X509CertificateHolder.toX509Certificate(): X509Certificate {
|
||||
|
@ -6,8 +6,10 @@ import net.corda.core.contracts.StateAndRef
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.contracts.TransactionState
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.entropyToKeyPair
|
||||
import net.corda.core.crypto.generateKeyPair
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.StatesToRecord
|
||||
import net.corda.core.node.services.IdentityService
|
||||
@ -22,8 +24,6 @@ import net.corda.finance.DOLLARS
|
||||
import net.corda.finance.POUNDS
|
||||
import net.corda.finance.SWISS_FRANCS
|
||||
import net.corda.finance.contracts.asset.Cash
|
||||
import net.corda.finance.contracts.asset.DUMMY_CASH_ISSUER_KEY
|
||||
import net.corda.finance.contracts.asset.DUMMY_CASH_ISSUER_NAME
|
||||
import net.corda.finance.contracts.asset.DummyFungibleContract
|
||||
import net.corda.finance.schemas.CashSchemaV1
|
||||
import net.corda.finance.schemas.SampleCashSchemaV2
|
||||
@ -48,6 +48,7 @@ import org.assertj.core.api.Assertions.assertThat
|
||||
import org.hibernate.SessionFactory
|
||||
import org.junit.*
|
||||
import java.math.BigDecimal
|
||||
import java.math.BigInteger
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
import javax.persistence.EntityManager
|
||||
@ -55,6 +56,12 @@ import javax.persistence.Tuple
|
||||
import javax.persistence.criteria.CriteriaBuilder
|
||||
|
||||
class HibernateConfigurationTest {
|
||||
private companion object {
|
||||
val DUMMY_CASH_ISSUER_NAME = CordaX500Name("Snake Oil Issuer", "London", "GB")
|
||||
val DUMMY_CASH_ISSUER_KEY = entropyToKeyPair(BigInteger.valueOf(10))
|
||||
val DUMMY_CASH_ISSUER = Party(DUMMY_CASH_ISSUER_NAME, DUMMY_CASH_ISSUER_KEY.public)
|
||||
}
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
@ -91,7 +98,7 @@ class HibernateConfigurationTest {
|
||||
val dataSourceProps = makeTestDataSourceProperties()
|
||||
val identityService = rigorousMock<IdentityService>().also { mock ->
|
||||
doReturn(null).whenever(mock).wellKnownPartyFromAnonymous(any<AbstractParty>())
|
||||
listOf(DUMMY_CASH_ISSUER_IDENTITY.party, DUMMY_NOTARY).forEach {
|
||||
listOf(DUMMY_CASH_ISSUER, DUMMY_NOTARY).forEach {
|
||||
doReturn(it).whenever(mock).wellKnownPartyFromAnonymous(it)
|
||||
doReturn(it).whenever(mock).wellKnownPartyFromX500Name(it.name)
|
||||
}
|
||||
|
@ -4,16 +4,11 @@ import co.paralleluniverse.fibers.Suspendable
|
||||
import com.nhaarman.mockito_kotlin.argThat
|
||||
import com.nhaarman.mockito_kotlin.doNothing
|
||||
import com.nhaarman.mockito_kotlin.whenever
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.Issued
|
||||
import net.corda.core.contracts.StateAndRef
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.NullKeys
|
||||
import net.corda.core.crypto.entropyToKeyPair
|
||||
import net.corda.core.crypto.generateKeyPair
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.identity.PartyAndCertificate
|
||||
import net.corda.core.identity.*
|
||||
import net.corda.core.internal.packageName
|
||||
import net.corda.core.node.StatesToRecord
|
||||
import net.corda.core.node.services.StatesNotAvailableException
|
||||
@ -31,9 +26,6 @@ import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.toNonEmptySet
|
||||
import net.corda.finance.*
|
||||
import net.corda.finance.contracts.asset.Cash
|
||||
import net.corda.finance.contracts.asset.DUMMY_CASH_ISSUER
|
||||
import net.corda.finance.contracts.asset.DUMMY_CASH_ISSUER_KEY
|
||||
import net.corda.finance.contracts.asset.DUMMY_CASH_ISSUER_NAME
|
||||
import net.corda.finance.contracts.getCashBalance
|
||||
import net.corda.finance.schemas.CashSchemaV1
|
||||
import net.corda.finance.utils.sumCash
|
||||
@ -51,6 +43,7 @@ import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import rx.observers.TestSubscriber
|
||||
import java.math.BigDecimal
|
||||
import java.math.BigInteger
|
||||
import java.util.*
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.Executors
|
||||
@ -59,8 +52,12 @@ import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class NodeVaultServiceTest {
|
||||
companion object {
|
||||
private val cordappPackages = listOf("net.corda.finance.contracts.asset", CashSchemaV1::class.packageName)
|
||||
private companion object {
|
||||
val cordappPackages = listOf("net.corda.finance.contracts.asset", CashSchemaV1::class.packageName)
|
||||
val DUMMY_CASH_ISSUER_NAME = CordaX500Name("Snake Oil Issuer", "London", "GB")
|
||||
val DUMMY_CASH_ISSUER_KEY = entropyToKeyPair(BigInteger.valueOf(10))
|
||||
val DUMMY_CASH_ISSUER_IDENTITY = getTestPartyAndCertificate(Party(DUMMY_CASH_ISSUER_NAME, DUMMY_CASH_ISSUER_KEY.public))
|
||||
val DUMMY_CASH_ISSUER = DUMMY_CASH_ISSUER_IDENTITY.party.ref(1)
|
||||
}
|
||||
|
||||
@Rule
|
||||
|
@ -21,9 +21,6 @@ import net.corda.finance.contracts.CommercialPaper
|
||||
import net.corda.finance.contracts.Commodity
|
||||
import net.corda.finance.contracts.DealState
|
||||
import net.corda.finance.contracts.asset.Cash
|
||||
import net.corda.finance.contracts.asset.DUMMY_CASH_ISSUER
|
||||
import net.corda.finance.contracts.asset.DUMMY_CASH_ISSUER_KEY
|
||||
import net.corda.finance.contracts.asset.DUMMY_OBLIGATION_ISSUER
|
||||
import net.corda.finance.schemas.CashSchemaV1
|
||||
import net.corda.finance.schemas.CashSchemaV1.PersistentCashState
|
||||
import net.corda.finance.schemas.CommercialPaperSchemaV1
|
||||
@ -51,6 +48,12 @@ import java.time.temporal.ChronoUnit
|
||||
import java.util.*
|
||||
|
||||
open class VaultQueryTests {
|
||||
private companion object {
|
||||
val DUMMY_CASH_ISSUER_KEY = entropyToKeyPair(BigInteger.valueOf(10))
|
||||
val DUMMY_CASH_ISSUER_IDENTITY = getTestPartyAndCertificate(Party(CordaX500Name("Snake Oil Issuer", "London", "GB"), DUMMY_CASH_ISSUER_KEY.public))
|
||||
val DUMMY_CASH_ISSUER = DUMMY_CASH_ISSUER_IDENTITY.party.ref(1)
|
||||
val DUMMY_OBLIGATION_ISSUER = Party(CordaX500Name("Snake Oil Issuer", "London", "GB"), entropyToKeyPair(BigInteger.valueOf(10)).public)
|
||||
}
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user