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 valueOf(String)
|
||||||
public static net.corda.core.serialization.SerializationContext$UseCase[] values()
|
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
|
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 getCHECKPOINT_CONTEXT()
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializationContext getP2P_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.PublishSubject
|
||||||
import rx.subjects.UnicastSubject
|
import rx.subjects.UnicastSubject
|
||||||
import java.time.Duration
|
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
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
class RPCStabilityTests : IntegrationTest() {
|
class RPCStabilityTests : IntegrationTest() {
|
||||||
|
@ -41,7 +41,7 @@ class KryoClientSerializationScheme : AbstractKryoSerializationScheme() {
|
|||||||
return SerializationEnvironmentImpl(
|
return SerializationEnvironmentImpl(
|
||||||
SerializationFactoryImpl().apply {
|
SerializationFactoryImpl().apply {
|
||||||
registerScheme(KryoClientSerializationScheme())
|
registerScheme(KryoClientSerializationScheme())
|
||||||
registerScheme(AMQPClientSerializationScheme())
|
registerScheme(AMQPClientSerializationScheme(emptyList()))
|
||||||
},
|
},
|
||||||
KRYO_P2P_CONTEXT,
|
KRYO_P2P_CONTEXT,
|
||||||
rpcClientContext = KRYO_RPC_CLIENT_CONTEXT)
|
rpcClientContext = KRYO_RPC_CLIENT_CONTEXT)
|
||||||
|
@ -48,8 +48,7 @@ public class StandaloneCordaRPCJavaClientTest {
|
|||||||
port.getAndIncrement(),
|
port.getAndIncrement(),
|
||||||
port.getAndIncrement(),
|
port.getAndIncrement(),
|
||||||
true,
|
true,
|
||||||
Collections.singletonList(rpcUser),
|
Collections.singletonList(rpcUser)
|
||||||
null
|
|
||||||
);
|
);
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
|
@ -4,8 +4,4 @@ trustStorePassword : "trustpass"
|
|||||||
p2pAddress : "localhost:10002"
|
p2pAddress : "localhost:10002"
|
||||||
rpcAddress : "localhost:10003"
|
rpcAddress : "localhost:10003"
|
||||||
webAddress : "localhost:10004"
|
webAddress : "localhost:10004"
|
||||||
networkMapService : {
|
|
||||||
address : "localhost:10000"
|
|
||||||
legalName : "O=Network Map Service,OU=corda,L=London,C=GB"
|
|
||||||
}
|
|
||||||
useHTTPS : false
|
useHTTPS : false
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
gradlePluginsVersion=3.0.0
|
gradlePluginsVersion=3.0.1
|
||||||
kotlinVersion=1.1.60
|
kotlinVersion=1.1.60
|
||||||
platformVersion=1
|
platformVersion=1
|
||||||
guavaVersion=21.0
|
guavaVersion=21.0
|
||||||
|
@ -3,6 +3,7 @@ package net.corda.core.cordapp
|
|||||||
import net.corda.core.DoNotImplement
|
import net.corda.core.DoNotImplement
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
import net.corda.core.schemas.MappedSchema
|
import net.corda.core.schemas.MappedSchema
|
||||||
|
import net.corda.core.serialization.SerializationCustomSerializer
|
||||||
import net.corda.core.serialization.SerializationWhitelist
|
import net.corda.core.serialization.SerializationWhitelist
|
||||||
import net.corda.core.serialization.SerializeAsToken
|
import net.corda.core.serialization.SerializeAsToken
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
@ -22,6 +23,7 @@ import java.net.URL
|
|||||||
* @property schedulableFlows List of flows startable by the scheduler
|
* @property schedulableFlows List of flows startable by the scheduler
|
||||||
* @property services List of RPC services
|
* @property services List of RPC services
|
||||||
* @property serializationWhitelists List of Corda plugin registries
|
* @property serializationWhitelists List of Corda plugin registries
|
||||||
|
* @property serializationCustomSerializers List of serializers
|
||||||
* @property customSchemas List of custom schemas
|
* @property customSchemas List of custom schemas
|
||||||
* @property jarPath The path to the JAR for this CorDapp
|
* @property jarPath The path to the JAR for this CorDapp
|
||||||
*/
|
*/
|
||||||
@ -35,6 +37,7 @@ interface Cordapp {
|
|||||||
val schedulableFlows: List<Class<out FlowLogic<*>>>
|
val schedulableFlows: List<Class<out FlowLogic<*>>>
|
||||||
val services: List<Class<out SerializeAsToken>>
|
val services: List<Class<out SerializeAsToken>>
|
||||||
val serializationWhitelists: List<SerializationWhitelist>
|
val serializationWhitelists: List<SerializationWhitelist>
|
||||||
|
val serializationCustomSerializers: List<SerializationCustomSerializer<*, *>>
|
||||||
val customSchemas: Set<MappedSchema>
|
val customSchemas: Set<MappedSchema>
|
||||||
val jarPath: URL
|
val jarPath: URL
|
||||||
val cordappClasses: List<String>
|
val cordappClasses: List<String>
|
||||||
|
@ -3,6 +3,7 @@ package net.corda.core.internal.cordapp
|
|||||||
import net.corda.core.cordapp.Cordapp
|
import net.corda.core.cordapp.Cordapp
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
import net.corda.core.schemas.MappedSchema
|
import net.corda.core.schemas.MappedSchema
|
||||||
|
import net.corda.core.serialization.SerializationCustomSerializer
|
||||||
import net.corda.core.serialization.SerializationWhitelist
|
import net.corda.core.serialization.SerializationWhitelist
|
||||||
import net.corda.core.serialization.SerializeAsToken
|
import net.corda.core.serialization.SerializeAsToken
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@ -16,6 +17,7 @@ data class CordappImpl(
|
|||||||
override val schedulableFlows: List<Class<out FlowLogic<*>>>,
|
override val schedulableFlows: List<Class<out FlowLogic<*>>>,
|
||||||
override val services: List<Class<out SerializeAsToken>>,
|
override val services: List<Class<out SerializeAsToken>>,
|
||||||
override val serializationWhitelists: List<SerializationWhitelist>,
|
override val serializationWhitelists: List<SerializationWhitelist>,
|
||||||
|
override val serializationCustomSerializers: List<SerializationCustomSerializer<*, *>>,
|
||||||
override val customSchemas: Set<MappedSchema>,
|
override val customSchemas: Set<MappedSchema>,
|
||||||
override val jarPath: URL) : Cordapp {
|
override val jarPath: URL) : Cordapp {
|
||||||
override val name: String = File(jarPath.toURI()).name.removeSuffix(".jar")
|
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.
|
* 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>
|
val notaryIdentities: List<Party>
|
||||||
// DOCEND 1
|
// DOCEND 1
|
||||||
|
|
||||||
@ -117,7 +116,7 @@ interface NetworkMapCacheBase {
|
|||||||
fun getNotary(name: CordaX500Name): Party? = notaryIdentities.firstOrNull { it.name == name }
|
fun getNotary(name: CordaX500Name): Party? = notaryIdentities.firstOrNull { it.name == name }
|
||||||
// DOCEND 2
|
// 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
|
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
|
// 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
|
* 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
|
* AMQP deserializer to allow instances with different versions of the class on their Class Path
|
||||||
* to successfully deserialize the object
|
* to successfully deserialize the object.
|
||||||
*
|
*
|
||||||
* NOTE: Renaming of the class itself is not be done with this annotation. For class renaming
|
* NOTE: Renaming of the class itself isn't done with this annotation or, at present, supported
|
||||||
* see ???
|
* by Corda
|
||||||
*
|
*
|
||||||
* @property to [String] representation of the properties new name
|
* @property to [String] representation of the properties new name
|
||||||
* @property from [String] representation of the properties old new
|
* @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
|
* 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
|
* 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)
|
@Target(AnnotationTarget.CONSTRUCTOR)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
@ -3,6 +3,6 @@ package net.corda.core.serialization
|
|||||||
import net.corda.core.CordaException
|
import net.corda.core.CordaException
|
||||||
import net.corda.core.crypto.SecureHash
|
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
|
@CordaSerializable
|
||||||
class MissingAttachmentsException(val ids: List<SecureHash>) : CordaException()
|
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
|
* 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
|
* 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
|
* 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.
|
* Fibers and thus sucked into serialization when they are checkpointed.
|
||||||
|
@ -50,7 +50,7 @@ data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
|
|||||||
@Volatile
|
@Volatile
|
||||||
@Transient private var cachedTransaction: CoreTransaction? = null
|
@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 }
|
private val transaction: CoreTransaction get() = cachedTransaction ?: txBits.deserialize().apply { cachedTransaction = this }
|
||||||
|
|
||||||
/** The id of the contained [WireTransaction]. */
|
/** The id of the contained [WireTransaction]. */
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package net.corda.core.crypto
|
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.contracts.*
|
||||||
import net.corda.core.crypto.SecureHash.Companion.zeroHash
|
import net.corda.core.crypto.SecureHash.Companion.zeroHash
|
||||||
import net.corda.core.identity.Party
|
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.DOLLARS
|
||||||
import net.corda.finance.`issued by`
|
import net.corda.finance.`issued by`
|
||||||
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.*
|
||||||
|
import net.corda.testing.node.MockServices
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@ -35,7 +39,9 @@ class PartialMerkleTreeTest {
|
|||||||
hashed = nodes.map { it.serialize().sha256() }
|
hashed = nodes.map { it.serialize().sha256() }
|
||||||
expectedRoot = MerkleTree.getMerkleTree(hashed.toMutableList() + listOf(zeroHash, zeroHash)).hash
|
expectedRoot = MerkleTree.getMerkleTree(hashed.toMutableList() + listOf(zeroHash, zeroHash)).hash
|
||||||
merkleTree = MerkleTree.getMerkleTree(hashed)
|
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 {
|
unverifiedTransaction {
|
||||||
attachments(Cash.PROGRAM_ID)
|
attachments(Cash.PROGRAM_ID)
|
||||||
output(Cash.PROGRAM_ID, "MEGA_CORP cash",
|
output(Cash.PROGRAM_ID, "MEGA_CORP cash",
|
||||||
|
@ -50,7 +50,7 @@ class X509NameConstraintsTest {
|
|||||||
|
|
||||||
val nameConstraints = NameConstraints(acceptableNames, arrayOf())
|
val nameConstraints = NameConstraints(acceptableNames, arrayOf())
|
||||||
val pathValidator = CertPathValidator.getInstance("PKIX")
|
val pathValidator = CertPathValidator.getInstance("PKIX")
|
||||||
val certFactory = X509CertificateFactory().delegate
|
val certFactory = X509CertificateFactory()
|
||||||
|
|
||||||
assertFailsWith(CertPathValidatorException::class) {
|
assertFailsWith(CertPathValidatorException::class) {
|
||||||
val (keystore, trustStore) = makeKeyStores(X500Name("CN=Bank B"), nameConstraints)
|
val (keystore, trustStore) = makeKeyStores(X500Name("CN=Bank B"), nameConstraints)
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package net.corda.core.transactions
|
package net.corda.core.transactions
|
||||||
|
|
||||||
|
import com.nhaarman.mockito_kotlin.doReturn
|
||||||
|
import com.nhaarman.mockito_kotlin.whenever
|
||||||
import net.corda.core.contracts.Contract
|
import net.corda.core.contracts.Contract
|
||||||
import net.corda.core.contracts.ContractState
|
import net.corda.core.contracts.ContractState
|
||||||
import net.corda.core.contracts.requireThat
|
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.DOLLARS
|
||||||
import net.corda.finance.`issued by`
|
import net.corda.finance.`issued by`
|
||||||
import net.corda.finance.contracts.asset.Cash
|
import net.corda.finance.contracts.asset.Cash
|
||||||
import net.corda.testing.MEGA_CORP
|
import net.corda.node.services.api.IdentityServiceInternal
|
||||||
import net.corda.testing.MINI_CORP
|
import net.corda.testing.*
|
||||||
import net.corda.testing.SerializationEnvironmentRule
|
import net.corda.testing.node.MockServices
|
||||||
import net.corda.testing.ledger
|
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.time.Instant
|
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
|
@Test
|
||||||
fun `state can be encumbered`() {
|
fun `state can be encumbered`() {
|
||||||
ledger {
|
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||||
transaction {
|
transaction {
|
||||||
attachments(Cash.PROGRAM_ID, TEST_TIMELOCK_ID)
|
attachments(Cash.PROGRAM_ID, TEST_TIMELOCK_ID)
|
||||||
input(Cash.PROGRAM_ID, state)
|
input(Cash.PROGRAM_ID, state)
|
||||||
@ -66,7 +71,7 @@ class TransactionEncumbranceTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `state can transition if encumbrance rules are met`() {
|
fun `state can transition if encumbrance rules are met`() {
|
||||||
ledger {
|
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||||
unverifiedTransaction {
|
unverifiedTransaction {
|
||||||
attachments(Cash.PROGRAM_ID, TEST_TIMELOCK_ID)
|
attachments(Cash.PROGRAM_ID, TEST_TIMELOCK_ID)
|
||||||
output(Cash.PROGRAM_ID, "state encumbered by 5pm time-lock", state)
|
output(Cash.PROGRAM_ID, "state encumbered by 5pm time-lock", state)
|
||||||
@ -87,7 +92,7 @@ class TransactionEncumbranceTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `state cannot transition if the encumbrance contract fails to verify`() {
|
fun `state cannot transition if the encumbrance contract fails to verify`() {
|
||||||
ledger {
|
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||||
unverifiedTransaction {
|
unverifiedTransaction {
|
||||||
attachments(Cash.PROGRAM_ID, TEST_TIMELOCK_ID)
|
attachments(Cash.PROGRAM_ID, TEST_TIMELOCK_ID)
|
||||||
output(Cash.PROGRAM_ID, "state encumbered by 5pm time-lock", state)
|
output(Cash.PROGRAM_ID, "state encumbered by 5pm time-lock", state)
|
||||||
@ -108,7 +113,7 @@ class TransactionEncumbranceTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `state must be consumed along with its encumbrance`() {
|
fun `state must be consumed along with its encumbrance`() {
|
||||||
ledger {
|
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||||
unverifiedTransaction {
|
unverifiedTransaction {
|
||||||
attachments(Cash.PROGRAM_ID, TEST_TIMELOCK_ID)
|
attachments(Cash.PROGRAM_ID, TEST_TIMELOCK_ID)
|
||||||
output(Cash.PROGRAM_ID, "state encumbered by 5pm time-lock", encumbrance = 1, contractState = state)
|
output(Cash.PROGRAM_ID, "state encumbered by 5pm time-lock", encumbrance = 1, contractState = state)
|
||||||
@ -127,7 +132,7 @@ class TransactionEncumbranceTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `state cannot be encumbered by itself`() {
|
fun `state cannot be encumbered by itself`() {
|
||||||
ledger {
|
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||||
transaction {
|
transaction {
|
||||||
attachments(Cash.PROGRAM_ID)
|
attachments(Cash.PROGRAM_ID)
|
||||||
input(Cash.PROGRAM_ID, state)
|
input(Cash.PROGRAM_ID, state)
|
||||||
@ -140,7 +145,7 @@ class TransactionEncumbranceTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `encumbrance state index must be valid`() {
|
fun `encumbrance state index must be valid`() {
|
||||||
ledger {
|
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||||
transaction {
|
transaction {
|
||||||
attachments(Cash.PROGRAM_ID, TEST_TIMELOCK_ID)
|
attachments(Cash.PROGRAM_ID, TEST_TIMELOCK_ID)
|
||||||
input(Cash.PROGRAM_ID, state)
|
input(Cash.PROGRAM_ID, state)
|
||||||
@ -154,7 +159,7 @@ class TransactionEncumbranceTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `correct encumbrance state must be provided`() {
|
fun `correct encumbrance state must be provided`() {
|
||||||
ledger {
|
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||||
unverifiedTransaction {
|
unverifiedTransaction {
|
||||||
attachments(Cash.PROGRAM_ID, TEST_TIMELOCK_ID)
|
attachments(Cash.PROGRAM_ID, TEST_TIMELOCK_ID)
|
||||||
output(Cash.PROGRAM_ID, "state encumbered by some other state", encumbrance = 1, contractState = state)
|
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.*
|
||||||
import net.corda.core.crypto.CompositeKey
|
import net.corda.core.crypto.CompositeKey
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.finance.contracts.asset.DUMMY_CASH_ISSUER_KEY
|
|
||||||
import net.corda.testing.*
|
import net.corda.testing.*
|
||||||
import net.corda.testing.contracts.DummyContract
|
import net.corda.testing.contracts.DummyContract
|
||||||
import net.corda.testing.node.MockAttachment
|
import net.corda.testing.node.MockAttachment
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import java.math.BigInteger
|
||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFailsWith
|
import kotlin.test.assertFailsWith
|
||||||
@ -19,6 +19,7 @@ class TransactionTests {
|
|||||||
private companion object {
|
private companion object {
|
||||||
val DUMMY_KEY_1 = generateKeyPair()
|
val DUMMY_KEY_1 = generateKeyPair()
|
||||||
val DUMMY_KEY_2 = generateKeyPair()
|
val DUMMY_KEY_2 = generateKeyPair()
|
||||||
|
val DUMMY_CASH_ISSUER_KEY = entropyToKeyPair(BigInteger.valueOf(10))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
|
@ -9,11 +9,11 @@ a developer environment.
|
|||||||
IDE - IntelliJ
|
IDE - IntelliJ
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
IntelliJ (R3's preferred IDE) integrates well with gradle (our chosen build, deployment and CLI tool). IntelliJ understands gradle
|
IntelliJ (the preferred IDE for Corda) integrates well with gradle (Corda's default build, deployment and CLI tool).
|
||||||
tasks and dependencies, automatically loading them in the background when a project is first opened or the gradle
|
IntelliJ understands gradle tasks and dependencies, automatically loading them in the background when a project is
|
||||||
project changes. Occasionally, however, you may need to refresh the gradle project manually - but this is hinted to you
|
first opened or the gradle project changes. Occasionally, however, you may need to refresh the gradle project manually
|
||||||
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
|
- 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
|
||||||
up new libraries, etc.).
|
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
|
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/>`_.
|
you to go to the `IntelliJ docs here <https://www.jetbrains.com/idea/documentation/>`_.
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
Code style guide
|
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
|
recommendations when submitting patches for review. Please take the time to read them and internalise them, to save
|
||||||
time during code review.
|
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"
|
trustStorePassword : "trustpass"
|
||||||
useHTTPS : false
|
useHTTPS : false
|
||||||
devMode : false
|
devMode : false
|
||||||
networkMapService {
|
|
||||||
address="networkmap.foo.bar.com:10002"
|
|
||||||
legalName="O=FooBar NetworkMap, L=Dublin, C=IE"
|
|
||||||
}
|
|
||||||
rpcUsers=[
|
rpcUsers=[
|
||||||
{
|
{
|
||||||
user=corda
|
user=corda
|
||||||
@ -223,10 +219,6 @@ at boot, and means the Corda service stays running with no users connected to th
|
|||||||
extraAdvertisedServiceIds: [ "" ]
|
extraAdvertisedServiceIds: [ "" ]
|
||||||
useHTTPS : false
|
useHTTPS : false
|
||||||
devMode : false
|
devMode : false
|
||||||
networkMapService {
|
|
||||||
address="networkmap.foo.bar.com:10002"
|
|
||||||
legalName="O=FooBar NetworkMap, L=Dublin, C=IE"
|
|
||||||
}
|
|
||||||
rpcUsers=[
|
rpcUsers=[
|
||||||
{
|
{
|
||||||
user=corda
|
user=corda
|
||||||
|
@ -6,7 +6,9 @@ import net.corda.core.utilities.OpaqueBytes;
|
|||||||
import net.corda.finance.contracts.ICommercialPaperState;
|
import net.corda.finance.contracts.ICommercialPaperState;
|
||||||
import net.corda.finance.contracts.JavaCommercialPaper;
|
import net.corda.finance.contracts.JavaCommercialPaper;
|
||||||
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.SerializationEnvironmentRule;
|
import net.corda.testing.SerializationEnvironmentRule;
|
||||||
|
import net.corda.testing.node.MockServices;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
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.ledger;
|
||||||
import static net.corda.testing.NodeTestUtils.transaction;
|
import static net.corda.testing.NodeTestUtils.transaction;
|
||||||
import static net.corda.testing.TestConstants.*;
|
import static net.corda.testing.TestConstants.*;
|
||||||
|
import static org.mockito.Mockito.doReturn;
|
||||||
|
|
||||||
public class CommercialPaperTest {
|
public class CommercialPaperTest {
|
||||||
@Rule
|
@Rule
|
||||||
public final SerializationEnvironmentRule testSerialization = new SerializationEnvironmentRule();
|
public final SerializationEnvironmentRule testSerialization = new SerializationEnvironmentRule();
|
||||||
private final OpaqueBytes defaultRef = new OpaqueBytes(new byte[]{123});
|
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
|
// DOCSTART 1
|
||||||
private ICommercialPaperState getPaper() {
|
private ICommercialPaperState getPaper() {
|
||||||
@ -40,7 +52,7 @@ public class CommercialPaperTest {
|
|||||||
@Test
|
@Test
|
||||||
public void simpleCP() {
|
public void simpleCP() {
|
||||||
ICommercialPaperState inState = getPaper();
|
ICommercialPaperState inState = getPaper();
|
||||||
ledger(l -> {
|
ledger(ledgerServices, getDUMMY_NOTARY(), l -> {
|
||||||
l.transaction(tx -> {
|
l.transaction(tx -> {
|
||||||
tx.attachments(JCP_PROGRAM_ID);
|
tx.attachments(JCP_PROGRAM_ID);
|
||||||
tx.input(JCP_PROGRAM_ID, inState);
|
tx.input(JCP_PROGRAM_ID, inState);
|
||||||
@ -55,7 +67,7 @@ public class CommercialPaperTest {
|
|||||||
@Test
|
@Test
|
||||||
public void simpleCPMove() {
|
public void simpleCPMove() {
|
||||||
ICommercialPaperState inState = getPaper();
|
ICommercialPaperState inState = getPaper();
|
||||||
ledger(l -> {
|
ledger(ledgerServices, getDUMMY_NOTARY(), l -> {
|
||||||
l.transaction(tx -> {
|
l.transaction(tx -> {
|
||||||
tx.input(JCP_PROGRAM_ID, inState);
|
tx.input(JCP_PROGRAM_ID, inState);
|
||||||
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
|
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
|
||||||
@ -71,7 +83,7 @@ public class CommercialPaperTest {
|
|||||||
@Test
|
@Test
|
||||||
public void simpleCPMoveFails() {
|
public void simpleCPMoveFails() {
|
||||||
ICommercialPaperState inState = getPaper();
|
ICommercialPaperState inState = getPaper();
|
||||||
ledger(l -> {
|
ledger(ledgerServices, getDUMMY_NOTARY(), l -> {
|
||||||
l.transaction(tx -> {
|
l.transaction(tx -> {
|
||||||
tx.input(JCP_PROGRAM_ID, inState);
|
tx.input(JCP_PROGRAM_ID, inState);
|
||||||
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
|
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
|
||||||
@ -87,7 +99,7 @@ public class CommercialPaperTest {
|
|||||||
@Test
|
@Test
|
||||||
public void simpleCPMoveSuccess() {
|
public void simpleCPMoveSuccess() {
|
||||||
ICommercialPaperState inState = getPaper();
|
ICommercialPaperState inState = getPaper();
|
||||||
ledger(l -> {
|
ledger(ledgerServices, getDUMMY_NOTARY(), l -> {
|
||||||
l.transaction(tx -> {
|
l.transaction(tx -> {
|
||||||
tx.input(JCP_PROGRAM_ID, inState);
|
tx.input(JCP_PROGRAM_ID, inState);
|
||||||
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
|
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
|
||||||
@ -104,7 +116,7 @@ public class CommercialPaperTest {
|
|||||||
// DOCSTART 6
|
// DOCSTART 6
|
||||||
@Test
|
@Test
|
||||||
public void simpleIssuanceWithTweak() {
|
public void simpleIssuanceWithTweak() {
|
||||||
ledger(l -> {
|
ledger(ledgerServices, getDUMMY_NOTARY(), l -> {
|
||||||
l.transaction(tx -> {
|
l.transaction(tx -> {
|
||||||
tx.output(JCP_PROGRAM_ID, "paper", getPaper()); // Some CP is issued onto the ledger by MegaCorp.
|
tx.output(JCP_PROGRAM_ID, "paper", getPaper()); // Some CP is issued onto the ledger by MegaCorp.
|
||||||
tx.attachments(JCP_PROGRAM_ID);
|
tx.attachments(JCP_PROGRAM_ID);
|
||||||
@ -125,7 +137,7 @@ public class CommercialPaperTest {
|
|||||||
// DOCSTART 7
|
// DOCSTART 7
|
||||||
@Test
|
@Test
|
||||||
public void simpleIssuanceWithTweakTopLevelTx() {
|
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.output(JCP_PROGRAM_ID, "paper", getPaper()); // Some CP is issued onto the ledger by MegaCorp.
|
||||||
tx.attachments(JCP_PROGRAM_ID);
|
tx.attachments(JCP_PROGRAM_ID);
|
||||||
tx.tweak(tw -> {
|
tx.tweak(tw -> {
|
||||||
@ -144,7 +156,7 @@ public class CommercialPaperTest {
|
|||||||
@Test
|
@Test
|
||||||
public void chainCommercialPaper() {
|
public void chainCommercialPaper() {
|
||||||
PartyAndReference issuer = getMEGA_CORP().ref(defaultRef);
|
PartyAndReference issuer = getMEGA_CORP().ref(defaultRef);
|
||||||
ledger(l -> {
|
ledger(ledgerServices, getDUMMY_NOTARY(), l -> {
|
||||||
l.unverifiedTransaction(tx -> {
|
l.unverifiedTransaction(tx -> {
|
||||||
tx.output(Cash.PROGRAM_ID, "alice's $900",
|
tx.output(Cash.PROGRAM_ID, "alice's $900",
|
||||||
new Cash.State(issuedBy(DOLLARS(900), issuer), getALICE()));
|
new Cash.State(issuedBy(DOLLARS(900), issuer), getALICE()));
|
||||||
@ -180,7 +192,7 @@ public class CommercialPaperTest {
|
|||||||
@Test
|
@Test
|
||||||
public void chainCommercialPaperDoubleSpend() {
|
public void chainCommercialPaperDoubleSpend() {
|
||||||
PartyAndReference issuer = getMEGA_CORP().ref(defaultRef);
|
PartyAndReference issuer = getMEGA_CORP().ref(defaultRef);
|
||||||
ledger(l -> {
|
ledger(ledgerServices, getDUMMY_NOTARY(), l -> {
|
||||||
l.unverifiedTransaction(tx -> {
|
l.unverifiedTransaction(tx -> {
|
||||||
tx.output(Cash.PROGRAM_ID, "alice's $900",
|
tx.output(Cash.PROGRAM_ID, "alice's $900",
|
||||||
new Cash.State(issuedBy(DOLLARS(900), issuer), getALICE()));
|
new Cash.State(issuedBy(DOLLARS(900), issuer), getALICE()));
|
||||||
@ -226,7 +238,7 @@ public class CommercialPaperTest {
|
|||||||
@Test
|
@Test
|
||||||
public void chainCommercialPaperTweak() {
|
public void chainCommercialPaperTweak() {
|
||||||
PartyAndReference issuer = getMEGA_CORP().ref(defaultRef);
|
PartyAndReference issuer = getMEGA_CORP().ref(defaultRef);
|
||||||
ledger(l -> {
|
ledger(ledgerServices, getDUMMY_NOTARY(), l -> {
|
||||||
l.unverifiedTransaction(tx -> {
|
l.unverifiedTransaction(tx -> {
|
||||||
tx.output(Cash.PROGRAM_ID, "alice's $900",
|
tx.output(Cash.PROGRAM_ID, "alice's $900",
|
||||||
new Cash.State(issuedBy(DOLLARS(900), issuer), getALICE()));
|
new Cash.State(issuedBy(DOLLARS(900), issuer), getALICE()));
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package net.corda.docs.tutorial.testdsl
|
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.core.utilities.days
|
||||||
import net.corda.finance.DOLLARS
|
import net.corda.finance.DOLLARS
|
||||||
import net.corda.finance.`issued by`
|
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.ICommercialPaperState
|
||||||
import net.corda.finance.contracts.asset.CASH
|
import net.corda.finance.contracts.asset.CASH
|
||||||
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.*
|
||||||
|
import net.corda.testing.node.MockServices
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
@ -16,6 +20,11 @@ class CommercialPaperTest {
|
|||||||
@Rule
|
@Rule
|
||||||
@JvmField
|
@JvmField
|
||||||
val testSerialization = SerializationEnvironmentRule()
|
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
|
// DOCSTART 1
|
||||||
fun getPaper(): ICommercialPaperState = CommercialPaper.State(
|
fun getPaper(): ICommercialPaperState = CommercialPaper.State(
|
||||||
@ -30,7 +39,7 @@ class CommercialPaperTest {
|
|||||||
@Test
|
@Test
|
||||||
fun simpleCP() {
|
fun simpleCP() {
|
||||||
val inState = getPaper()
|
val inState = getPaper()
|
||||||
ledger {
|
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||||
transaction {
|
transaction {
|
||||||
attachments(CP_PROGRAM_ID)
|
attachments(CP_PROGRAM_ID)
|
||||||
input(CP_PROGRAM_ID, inState)
|
input(CP_PROGRAM_ID, inState)
|
||||||
@ -44,7 +53,7 @@ class CommercialPaperTest {
|
|||||||
@Test
|
@Test
|
||||||
fun simpleCPMove() {
|
fun simpleCPMove() {
|
||||||
val inState = getPaper()
|
val inState = getPaper()
|
||||||
ledger {
|
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||||
transaction {
|
transaction {
|
||||||
input(CP_PROGRAM_ID, inState)
|
input(CP_PROGRAM_ID, inState)
|
||||||
command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move())
|
command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move())
|
||||||
@ -59,7 +68,7 @@ class CommercialPaperTest {
|
|||||||
@Test
|
@Test
|
||||||
fun simpleCPMoveFails() {
|
fun simpleCPMoveFails() {
|
||||||
val inState = getPaper()
|
val inState = getPaper()
|
||||||
ledger {
|
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||||
transaction {
|
transaction {
|
||||||
input(CP_PROGRAM_ID, inState)
|
input(CP_PROGRAM_ID, inState)
|
||||||
command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move())
|
command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move())
|
||||||
@ -74,7 +83,7 @@ class CommercialPaperTest {
|
|||||||
@Test
|
@Test
|
||||||
fun simpleCPMoveSuccess() {
|
fun simpleCPMoveSuccess() {
|
||||||
val inState = getPaper()
|
val inState = getPaper()
|
||||||
ledger {
|
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||||
transaction {
|
transaction {
|
||||||
input(CP_PROGRAM_ID, inState)
|
input(CP_PROGRAM_ID, inState)
|
||||||
command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move())
|
command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move())
|
||||||
@ -90,7 +99,7 @@ class CommercialPaperTest {
|
|||||||
// DOCSTART 6
|
// DOCSTART 6
|
||||||
@Test
|
@Test
|
||||||
fun `simple issuance with tweak`() {
|
fun `simple issuance with tweak`() {
|
||||||
ledger {
|
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||||
transaction {
|
transaction {
|
||||||
output(CP_PROGRAM_ID, "paper", getPaper()) // Some CP is issued onto the ledger by MegaCorp.
|
output(CP_PROGRAM_ID, "paper", getPaper()) // Some CP is issued onto the ledger by MegaCorp.
|
||||||
attachments(CP_PROGRAM_ID)
|
attachments(CP_PROGRAM_ID)
|
||||||
@ -111,7 +120,7 @@ class CommercialPaperTest {
|
|||||||
// DOCSTART 7
|
// DOCSTART 7
|
||||||
@Test
|
@Test
|
||||||
fun `simple issuance with tweak and top level transaction`() {
|
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.
|
output(CP_PROGRAM_ID, "paper", getPaper()) // Some CP is issued onto the ledger by MegaCorp.
|
||||||
attachments(CP_PROGRAM_ID)
|
attachments(CP_PROGRAM_ID)
|
||||||
tweak {
|
tweak {
|
||||||
@ -131,8 +140,7 @@ class CommercialPaperTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `chain commercial paper`() {
|
fun `chain commercial paper`() {
|
||||||
val issuer = MEGA_CORP.ref(123)
|
val issuer = MEGA_CORP.ref(123)
|
||||||
|
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||||
ledger {
|
|
||||||
unverifiedTransaction {
|
unverifiedTransaction {
|
||||||
attachments(Cash.PROGRAM_ID)
|
attachments(Cash.PROGRAM_ID)
|
||||||
output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH issuedBy issuer ownedBy ALICE)
|
output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH issuedBy issuer ownedBy ALICE)
|
||||||
@ -165,7 +173,7 @@ class CommercialPaperTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `chain commercial paper double spend`() {
|
fun `chain commercial paper double spend`() {
|
||||||
val issuer = MEGA_CORP.ref(123)
|
val issuer = MEGA_CORP.ref(123)
|
||||||
ledger {
|
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||||
unverifiedTransaction {
|
unverifiedTransaction {
|
||||||
attachments(Cash.PROGRAM_ID)
|
attachments(Cash.PROGRAM_ID)
|
||||||
output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH issuedBy issuer ownedBy ALICE)
|
output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH issuedBy issuer ownedBy ALICE)
|
||||||
@ -207,7 +215,7 @@ class CommercialPaperTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `chain commercial tweak`() {
|
fun `chain commercial tweak`() {
|
||||||
val issuer = MEGA_CORP.ref(123)
|
val issuer = MEGA_CORP.ref(123)
|
||||||
ledger {
|
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||||
unverifiedTransaction {
|
unverifiedTransaction {
|
||||||
attachments(Cash.PROGRAM_ID)
|
attachments(Cash.PROGRAM_ID)
|
||||||
output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH issuedBy issuer ownedBy ALICE)
|
output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH issuedBy issuer ownedBy ALICE)
|
||||||
|
@ -10,14 +10,9 @@ dataSourceProperties : {
|
|||||||
p2pAddress : "my-corda-node:10002"
|
p2pAddress : "my-corda-node:10002"
|
||||||
rpcAddress : "my-corda-node:10003"
|
rpcAddress : "my-corda-node:10003"
|
||||||
webAddress : "localhost:10004"
|
webAddress : "localhost:10004"
|
||||||
networkMapService : {
|
|
||||||
address : "my-network-map:10000"
|
|
||||||
legalName : "O=Network Map Service,OU=corda,L=London,C=GB"
|
|
||||||
}
|
|
||||||
useHTTPS : false
|
useHTTPS : false
|
||||||
rpcUsers : [
|
rpcUsers : [
|
||||||
{ username=user1, password=letmein, permissions=[ StartProtocol.net.corda.protocols.CashProtocol ] }
|
{ username=user1, password=letmein, permissions=[ StartProtocol.net.corda.protocols.CashProtocol ] }
|
||||||
]
|
]
|
||||||
devMode : true
|
devMode : true
|
||||||
// Certificate signing service will be hosted by R3 in the near future.
|
// certificateSigningService : "https://testnet.certificate.corda.net"
|
||||||
//certificateSigningService : "https://testnet.certificate.corda.net"
|
|
||||||
|
@ -53,8 +53,6 @@ Protocol
|
|||||||
The old name for a Corda "Flow"
|
The old name for a Corda "Flow"
|
||||||
Quasar
|
Quasar
|
||||||
A library that provides performant lightweight threads that can be suspended and restored extremely quickly.
|
A library that provides performant lightweight threads that can be suspended and restored extremely quickly.
|
||||||
R3
|
|
||||||
The consortium behind Corda
|
|
||||||
SIMM
|
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.
|
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
|
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
|
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.
|
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
|
Release 2.0
|
||||||
----------
|
----------
|
||||||
Following quickly on the heels of the release of Corda 1.0, Corda version 2.0 consolidates
|
Following quickly on the heels of the release of Corda 1.0, Corda version 2.0 consolidates
|
||||||
|
@ -45,8 +45,6 @@ The most important fields regarding network configuration are:
|
|||||||
resolvable name of a machine in a VPN.
|
resolvable name of a machine in a VPN.
|
||||||
* ``rpcAddress``: The address to which Artemis will bind for RPC calls.
|
* ``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.
|
* ``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
|
Starting the nodes
|
||||||
~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
@ -1,18 +1,31 @@
|
|||||||
package net.corda.finance.contracts.universal
|
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.BusinessCalendar
|
||||||
import net.corda.finance.contracts.FixOf
|
import net.corda.finance.contracts.FixOf
|
||||||
import net.corda.finance.contracts.Frequency
|
import net.corda.finance.contracts.Frequency
|
||||||
import net.corda.finance.contracts.Tenor
|
import net.corda.finance.contracts.Tenor
|
||||||
|
import net.corda.node.services.api.IdentityServiceInternal
|
||||||
import net.corda.testing.*
|
import net.corda.testing.*
|
||||||
|
import net.corda.testing.node.MockServices
|
||||||
|
import net.corda.testing.node.makeTestIdentityService
|
||||||
import org.junit.Ignore
|
import org.junit.Ignore
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import java.math.BigInteger
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
|
|
||||||
fun transaction(script: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail) = run {
|
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 {
|
class Cap {
|
||||||
|
@ -7,10 +7,8 @@ import co.paralleluniverse.fibers.Suspendable
|
|||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.contracts.Amount.Companion.sumOrThrow
|
import net.corda.core.contracts.Amount.Companion.sumOrThrow
|
||||||
import net.corda.core.crypto.NullKeys.NULL_PARTY
|
import net.corda.core.crypto.NullKeys.NULL_PARTY
|
||||||
import net.corda.core.crypto.entropyToKeyPair
|
|
||||||
import net.corda.core.crypto.toStringShort
|
import net.corda.core.crypto.toStringShort
|
||||||
import net.corda.core.identity.AbstractParty
|
import net.corda.core.identity.AbstractParty
|
||||||
import net.corda.core.identity.CordaX500Name
|
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.identity.PartyAndCertificate
|
import net.corda.core.identity.PartyAndCertificate
|
||||||
import net.corda.core.internal.Emoji
|
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.sumCash
|
||||||
import net.corda.finance.utils.sumCashOrNull
|
import net.corda.finance.utils.sumCashOrNull
|
||||||
import net.corda.finance.utils.sumCashOrZero
|
import net.corda.finance.utils.sumCashOrZero
|
||||||
import java.math.BigInteger
|
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.util.*
|
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.
|
// 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 */
|
/** 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] */
|
/** 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)
|
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.NettableState
|
||||||
import net.corda.finance.contracts.asset.Obligation.Lifecycle.NORMAL
|
import net.corda.finance.contracts.asset.Obligation.Lifecycle.NORMAL
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.crypto.entropyToKeyPair
|
|
||||||
import net.corda.core.identity.AbstractParty
|
import net.corda.core.identity.AbstractParty
|
||||||
import net.corda.core.identity.AnonymousParty
|
import net.corda.core.identity.AnonymousParty
|
||||||
import net.corda.core.identity.CordaX500Name
|
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.internal.Emoji
|
import net.corda.core.internal.Emoji
|
||||||
import net.corda.core.internal.VisibleForTesting
|
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.sumObligations
|
||||||
import net.corda.finance.utils.sumObligationsOrNull
|
import net.corda.finance.utils.sumObligationsOrNull
|
||||||
import net.corda.finance.utils.sumObligationsOrZero
|
import net.corda.finance.utils.sumObligationsOrZero
|
||||||
import java.math.BigInteger
|
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
@ -791,8 +788,3 @@ fun <T : Any> Obligation.State<T>.ownedBy(owner: AbstractParty) = copy(beneficia
|
|||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun <T : Any> Obligation.State<T>.issuedBy(party: AnonymousParty) = copy(obligor = party)
|
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.contracts.PartyAndReference;
|
||||||
import net.corda.core.identity.AnonymousParty;
|
import net.corda.core.identity.AnonymousParty;
|
||||||
import net.corda.core.utilities.OpaqueBytes;
|
import net.corda.core.utilities.OpaqueBytes;
|
||||||
|
import net.corda.node.services.api.IdentityServiceInternal;
|
||||||
import net.corda.testing.DummyCommandData;
|
import net.corda.testing.DummyCommandData;
|
||||||
import net.corda.testing.SerializationEnvironmentRule;
|
import net.corda.testing.SerializationEnvironmentRule;
|
||||||
|
import net.corda.testing.node.MockServices;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
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.finance.Currencies.issuedBy;
|
||||||
import static net.corda.testing.CoreTestUtils.*;
|
import static net.corda.testing.CoreTestUtils.*;
|
||||||
import static net.corda.testing.NodeTestUtils.transaction;
|
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
|
* 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
|
@Test
|
||||||
public void trivial() {
|
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.attachment(Cash.PROGRAM_ID);
|
||||||
|
|
||||||
tx.input(Cash.PROGRAM_ID, inState);
|
tx.input(Cash.PROGRAM_ID, inState);
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
package net.corda.finance.contracts
|
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.contracts.*
|
||||||
|
import net.corda.core.crypto.entropyToKeyPair
|
||||||
import net.corda.core.identity.AnonymousParty
|
import net.corda.core.identity.AnonymousParty
|
||||||
|
import net.corda.core.identity.CordaX500Name
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.node.services.Vault
|
import net.corda.core.node.services.Vault
|
||||||
import net.corda.core.node.services.VaultService
|
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.DOLLARS
|
||||||
import net.corda.finance.`issued by`
|
import net.corda.finance.`issued by`
|
||||||
import net.corda.finance.contracts.asset.*
|
import net.corda.finance.contracts.asset.*
|
||||||
|
import net.corda.node.services.api.IdentityServiceInternal
|
||||||
import net.corda.testing.*
|
import net.corda.testing.*
|
||||||
import net.corda.testing.contracts.VaultFiller
|
import net.corda.testing.contracts.VaultFiller
|
||||||
import net.corda.testing.node.MockServices
|
import net.corda.testing.node.MockServices
|
||||||
@ -21,6 +26,7 @@ import org.junit.Rule
|
|||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.junit.runners.Parameterized
|
import org.junit.runners.Parameterized
|
||||||
|
import java.math.BigInteger
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.test.assertFailsWith
|
import kotlin.test.assertFailsWith
|
||||||
@ -84,6 +90,10 @@ class CommercialPaperTestsGeneric {
|
|||||||
@Parameterized.Parameters
|
@Parameterized.Parameters
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun data() = listOf(JavaCommercialPaperTest(), KotlinCommercialPaperTest(), KotlinCommercialPaperLegacyTest())
|
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
|
@Parameterized.Parameter
|
||||||
@ -92,11 +102,16 @@ class CommercialPaperTestsGeneric {
|
|||||||
@JvmField
|
@JvmField
|
||||||
val testSerialization = SerializationEnvironmentRule()
|
val testSerialization = SerializationEnvironmentRule()
|
||||||
val issuer = MEGA_CORP.ref(123)
|
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
|
@Test
|
||||||
fun `trade lifecycle test`() {
|
fun `trade lifecycle test`() {
|
||||||
val someProfits = 1200.DOLLARS `issued by` issuer
|
val someProfits = 1200.DOLLARS `issued by` issuer
|
||||||
ledger {
|
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||||
unverifiedTransaction {
|
unverifiedTransaction {
|
||||||
attachment(Cash.PROGRAM_ID)
|
attachment(Cash.PROGRAM_ID)
|
||||||
output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH issuedBy issuer ownedBy ALICE)
|
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
|
@Test
|
||||||
fun `key mismatch at issue`() {
|
fun `key mismatch at issue`() {
|
||||||
transaction {
|
transaction {
|
||||||
|
@ -3,11 +3,9 @@ package net.corda.finance.contracts.asset
|
|||||||
import com.nhaarman.mockito_kotlin.*
|
import com.nhaarman.mockito_kotlin.*
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
|
import net.corda.core.crypto.entropyToKeyPair
|
||||||
import net.corda.core.crypto.generateKeyPair
|
import net.corda.core.crypto.generateKeyPair
|
||||||
import net.corda.core.identity.AbstractParty
|
import net.corda.core.identity.*
|
||||||
import net.corda.core.identity.AnonymousParty
|
|
||||||
import net.corda.core.identity.CordaX500Name
|
|
||||||
import net.corda.core.identity.Party
|
|
||||||
import net.corda.core.node.ServiceHub
|
import net.corda.core.node.ServiceHub
|
||||||
import net.corda.core.node.services.VaultService
|
import net.corda.core.node.services.VaultService
|
||||||
import net.corda.core.node.services.queryBy
|
import net.corda.core.node.services.queryBy
|
||||||
@ -32,10 +30,15 @@ import org.junit.After
|
|||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import java.math.BigInteger
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.test.*
|
import kotlin.test.*
|
||||||
|
|
||||||
class CashTests {
|
class CashTests {
|
||||||
|
companion object {
|
||||||
|
private val DUMMY_CASH_ISSUER_IDENTITY = getTestPartyAndCertificate(Party(CordaX500Name("Snake Oil Issuer", "London", "GB"), entropyToKeyPair(BigInteger.valueOf(10)).public))
|
||||||
|
}
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
@JvmField
|
@JvmField
|
||||||
val testSerialization = SerializationEnvironmentRule()
|
val testSerialization = SerializationEnvironmentRule()
|
||||||
@ -86,7 +89,7 @@ class CashTests {
|
|||||||
ourIdentity = ourServices.myInfo.singleIdentity()
|
ourIdentity = ourServices.myInfo.singleIdentity()
|
||||||
miniCorpAnonymised = miniCorpServices.myInfo.singleIdentityAndCert().party.anonymise()
|
miniCorpAnonymised = miniCorpServices.myInfo.singleIdentityAndCert().party.anonymise()
|
||||||
(miniCorpServices.myInfo.legalIdentitiesAndCerts + megaCorpServices.myInfo.legalIdentitiesAndCerts + notaryServices.myInfo.legalIdentitiesAndCerts).forEach { identity ->
|
(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.
|
// Create some cash. Any attempt to spend >$500 will require multiple issuers to be involved.
|
||||||
@ -113,6 +116,15 @@ class CashTests {
|
|||||||
database.close()
|
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
|
@Test
|
||||||
fun trivial() {
|
fun trivial() {
|
||||||
transaction {
|
transaction {
|
||||||
@ -779,7 +791,7 @@ class CashTests {
|
|||||||
val mockService = MockServices(listOf("net.corda.finance.contracts.asset"), rigorousMock<IdentityServiceInternal>().also {
|
val mockService = MockServices(listOf("net.corda.finance.contracts.asset"), rigorousMock<IdentityServiceInternal>().also {
|
||||||
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
|
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
|
||||||
}, MEGA_CORP.name, MEGA_CORP_KEY)
|
}, MEGA_CORP.name, MEGA_CORP_KEY)
|
||||||
ledger(mockService) {
|
mockService.ledger(DUMMY_NOTARY) {
|
||||||
unverifiedTransaction {
|
unverifiedTransaction {
|
||||||
attachment(Cash.PROGRAM_ID)
|
attachment(Cash.PROGRAM_ID)
|
||||||
output(Cash.PROGRAM_ID, "MEGA_CORP cash",
|
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.contracts.*
|
||||||
import net.corda.core.crypto.NullKeys.NULL_PARTY
|
import net.corda.core.crypto.NullKeys.NULL_PARTY
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
|
import net.corda.core.crypto.entropyToKeyPair
|
||||||
import net.corda.core.crypto.sha256
|
import net.corda.core.crypto.sha256
|
||||||
import net.corda.core.identity.AbstractParty
|
import net.corda.core.identity.AbstractParty
|
||||||
import net.corda.core.identity.AnonymousParty
|
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.transactions.TransactionBuilder
|
||||||
import net.corda.core.utilities.NonEmptySet
|
import net.corda.core.utilities.NonEmptySet
|
||||||
import net.corda.core.utilities.OpaqueBytes
|
import net.corda.core.utilities.OpaqueBytes
|
||||||
@ -24,6 +27,7 @@ import net.corda.testing.contracts.DummyState
|
|||||||
import net.corda.testing.node.MockServices
|
import net.corda.testing.node.MockServices
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import java.math.BigInteger
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.time.temporal.ChronoUnit
|
import java.time.temporal.ChronoUnit
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@ -33,6 +37,10 @@ import kotlin.test.assertNotEquals
|
|||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
class ObligationTests {
|
class ObligationTests {
|
||||||
|
companion object {
|
||||||
|
private val DUMMY_OBLIGATION_ISSUER = Party(CordaX500Name("Snake Oil Issuer", "London", "GB"), entropyToKeyPair(BigInteger.valueOf(10)).public)
|
||||||
|
}
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
@JvmField
|
@JvmField
|
||||||
val testSerialization = SerializationEnvironmentRule()
|
val testSerialization = SerializationEnvironmentRule()
|
||||||
@ -56,12 +64,15 @@ class ObligationTests {
|
|||||||
private val outState = inState.copy(beneficiary = AnonymousParty(BOB_PUBKEY))
|
private val outState = inState.copy(beneficiary = AnonymousParty(BOB_PUBKEY))
|
||||||
private val miniCorpServices = MockServices(listOf("net.corda.finance.contracts.asset"), rigorousMock(), MINI_CORP.name, MINI_CORP_KEY)
|
private val miniCorpServices = MockServices(listOf("net.corda.finance.contracts.asset"), rigorousMock(), MINI_CORP.name, MINI_CORP_KEY)
|
||||||
private val notaryServices = MockServices(rigorousMock(), MEGA_CORP.name, DUMMY_NOTARY_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(ALICE_PUBKEY)
|
||||||
doReturn(null).whenever(it).partyFromKey(BOB_PUBKEY)
|
doReturn(null).whenever(it).partyFromKey(BOB_PUBKEY)
|
||||||
|
doReturn(null).whenever(it).partyFromKey(CHARLIE.owningKey)
|
||||||
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
|
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(
|
private fun cashObligationTestRoots(
|
||||||
group: LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>
|
group: LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>
|
||||||
) = group.apply {
|
) = group.apply {
|
||||||
@ -74,6 +85,10 @@ class ObligationTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun transaction(script: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail) = run {
|
||||||
|
ledgerServices.transaction(DUMMY_NOTARY, script)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun trivial() {
|
fun trivial() {
|
||||||
transaction {
|
transaction {
|
||||||
@ -347,7 +362,7 @@ class ObligationTests {
|
|||||||
@Test
|
@Test
|
||||||
fun `close-out netting`() {
|
fun `close-out netting`() {
|
||||||
// Try netting out two obligations
|
// Try netting out two obligations
|
||||||
ledger(mockService) {
|
mockService.ledger(DUMMY_NOTARY) {
|
||||||
cashObligationTestRoots(this)
|
cashObligationTestRoots(this)
|
||||||
transaction("Issuance") {
|
transaction("Issuance") {
|
||||||
attachments(Obligation.PROGRAM_ID)
|
attachments(Obligation.PROGRAM_ID)
|
||||||
@ -363,7 +378,7 @@ class ObligationTests {
|
|||||||
|
|
||||||
// Try netting out two obligations, with the third uninvolved obligation left
|
// Try netting out two obligations, with the third uninvolved obligation left
|
||||||
// as-is
|
// as-is
|
||||||
ledger(mockService) {
|
mockService.ledger(DUMMY_NOTARY) {
|
||||||
cashObligationTestRoots(this)
|
cashObligationTestRoots(this)
|
||||||
transaction("Issuance") {
|
transaction("Issuance") {
|
||||||
attachments(Obligation.PROGRAM_ID)
|
attachments(Obligation.PROGRAM_ID)
|
||||||
@ -379,7 +394,7 @@ class ObligationTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Try having outputs mis-match the inputs
|
// Try having outputs mis-match the inputs
|
||||||
ledger {
|
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||||
cashObligationTestRoots(this)
|
cashObligationTestRoots(this)
|
||||||
transaction("Issuance") {
|
transaction("Issuance") {
|
||||||
attachments(Obligation.PROGRAM_ID)
|
attachments(Obligation.PROGRAM_ID)
|
||||||
@ -393,7 +408,7 @@ class ObligationTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Have the wrong signature on the transaction
|
// Have the wrong signature on the transaction
|
||||||
ledger {
|
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||||
cashObligationTestRoots(this)
|
cashObligationTestRoots(this)
|
||||||
transaction("Issuance") {
|
transaction("Issuance") {
|
||||||
attachments(Obligation.PROGRAM_ID)
|
attachments(Obligation.PROGRAM_ID)
|
||||||
@ -409,7 +424,7 @@ class ObligationTests {
|
|||||||
@Test
|
@Test
|
||||||
fun `payment netting`() {
|
fun `payment netting`() {
|
||||||
// Try netting out two obligations
|
// Try netting out two obligations
|
||||||
ledger(mockService) {
|
mockService.ledger(DUMMY_NOTARY) {
|
||||||
cashObligationTestRoots(this)
|
cashObligationTestRoots(this)
|
||||||
transaction("Issuance") {
|
transaction("Issuance") {
|
||||||
attachments(Obligation.PROGRAM_ID)
|
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
|
// Try netting out two obligations, but only provide one signature. Unlike close-out netting, we need both
|
||||||
// signatures for payment netting
|
// signatures for payment netting
|
||||||
ledger {
|
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||||
cashObligationTestRoots(this)
|
cashObligationTestRoots(this)
|
||||||
transaction("Issuance") {
|
transaction("Issuance") {
|
||||||
attachments(Obligation.PROGRAM_ID)
|
attachments(Obligation.PROGRAM_ID)
|
||||||
@ -437,7 +452,7 @@ class ObligationTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Multilateral netting, A -> B -> C which can net down to A -> C
|
// Multilateral netting, A -> B -> C which can net down to A -> C
|
||||||
ledger(mockService) {
|
mockService.ledger(DUMMY_NOTARY) {
|
||||||
cashObligationTestRoots(this)
|
cashObligationTestRoots(this)
|
||||||
transaction("Issuance") {
|
transaction("Issuance") {
|
||||||
attachments(Obligation.PROGRAM_ID)
|
attachments(Obligation.PROGRAM_ID)
|
||||||
@ -452,7 +467,7 @@ class ObligationTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Multilateral netting without the key of the receiving party
|
// Multilateral netting without the key of the receiving party
|
||||||
ledger(mockService) {
|
mockService.ledger(DUMMY_NOTARY) {
|
||||||
cashObligationTestRoots(this)
|
cashObligationTestRoots(this)
|
||||||
transaction("Issuance") {
|
transaction("Issuance") {
|
||||||
attachments(Obligation.PROGRAM_ID)
|
attachments(Obligation.PROGRAM_ID)
|
||||||
@ -469,7 +484,7 @@ class ObligationTests {
|
|||||||
@Test
|
@Test
|
||||||
fun `cash settlement`() {
|
fun `cash settlement`() {
|
||||||
// Try settling an obligation
|
// Try settling an obligation
|
||||||
ledger {
|
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||||
cashObligationTestRoots(this)
|
cashObligationTestRoots(this)
|
||||||
transaction("Settlement") {
|
transaction("Settlement") {
|
||||||
attachments(Obligation.PROGRAM_ID)
|
attachments(Obligation.PROGRAM_ID)
|
||||||
@ -485,7 +500,7 @@ class ObligationTests {
|
|||||||
|
|
||||||
// Try partial settling of an obligation
|
// Try partial settling of an obligation
|
||||||
val halfAMillionDollars = 500000.DOLLARS `issued by` defaultIssuer
|
val halfAMillionDollars = 500000.DOLLARS `issued by` defaultIssuer
|
||||||
ledger {
|
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||||
transaction("Settlement") {
|
transaction("Settlement") {
|
||||||
attachments(Obligation.PROGRAM_ID, Cash.PROGRAM_ID)
|
attachments(Obligation.PROGRAM_ID, Cash.PROGRAM_ID)
|
||||||
input(Obligation.PROGRAM_ID, oneMillionDollars.OBLIGATION between Pair(ALICE, BOB))
|
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
|
// 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)
|
val defaultedObligation: Obligation.State<Currency> = (oneMillionDollars.OBLIGATION between Pair(ALICE, BOB)).copy(lifecycle = Lifecycle.DEFAULTED)
|
||||||
ledger {
|
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||||
transaction("Settlement") {
|
transaction("Settlement") {
|
||||||
attachments(Obligation.PROGRAM_ID, Cash.PROGRAM_ID)
|
attachments(Obligation.PROGRAM_ID, Cash.PROGRAM_ID)
|
||||||
input(Obligation.PROGRAM_ID, defaultedObligation) // Alice's defaulted $1,000,000 obligation to Bob
|
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
|
// Make sure settlement amount must match the amount leaving the ledger
|
||||||
ledger {
|
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||||
cashObligationTestRoots(this)
|
cashObligationTestRoots(this)
|
||||||
transaction("Settlement") {
|
transaction("Settlement") {
|
||||||
attachments(Obligation.PROGRAM_ID)
|
attachments(Obligation.PROGRAM_ID)
|
||||||
@ -538,7 +553,7 @@ class ObligationTests {
|
|||||||
val oneUnitFcojObligation = Obligation.State(Obligation.Lifecycle.NORMAL, ALICE,
|
val oneUnitFcojObligation = Obligation.State(Obligation.Lifecycle.NORMAL, ALICE,
|
||||||
obligationDef, oneUnitFcoj.quantity, NULL_PARTY)
|
obligationDef, oneUnitFcoj.quantity, NULL_PARTY)
|
||||||
// Try settling a simple commodity obligation
|
// Try settling a simple commodity obligation
|
||||||
ledger {
|
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||||
unverifiedTransaction {
|
unverifiedTransaction {
|
||||||
attachments(Obligation.PROGRAM_ID)
|
attachments(Obligation.PROGRAM_ID)
|
||||||
output(Obligation.PROGRAM_ID, "Alice's 1 FCOJ obligation to Bob", oneUnitFcojObligation between Pair(ALICE, BOB))
|
output(Obligation.PROGRAM_ID, "Alice's 1 FCOJ obligation to Bob", oneUnitFcojObligation between Pair(ALICE, BOB))
|
||||||
@ -560,7 +575,7 @@ class ObligationTests {
|
|||||||
@Test
|
@Test
|
||||||
fun `payment default`() {
|
fun `payment default`() {
|
||||||
// Try defaulting an obligation without a time-window.
|
// Try defaulting an obligation without a time-window.
|
||||||
ledger {
|
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||||
cashObligationTestRoots(this)
|
cashObligationTestRoots(this)
|
||||||
transaction("Settlement") {
|
transaction("Settlement") {
|
||||||
attachments(Obligation.PROGRAM_ID)
|
attachments(Obligation.PROGRAM_ID)
|
||||||
@ -584,7 +599,7 @@ class ObligationTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Try defaulting an obligation that is now in the past
|
// Try defaulting an obligation that is now in the past
|
||||||
ledger {
|
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||||
transaction {
|
transaction {
|
||||||
attachments(Obligation.PROGRAM_ID)
|
attachments(Obligation.PROGRAM_ID)
|
||||||
input(Obligation.PROGRAM_ID, oneMillionDollars.OBLIGATION between Pair(ALICE, BOB) `at` pastTestTime)
|
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.
|
// TypeSafe Config: for simple and human friendly config files.
|
||||||
compile "com.typesafe:config:$typesafe_config_version"
|
compile "com.typesafe:config:$typesafe_config_version"
|
||||||
|
|
||||||
// Bouncy Castle: for X.500 distinguished name manipulation
|
|
||||||
compile "org.bouncycastle:bcprov-jdk15on:$bouncycastle_version"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
publish {
|
publish {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package net.corda.cordform;
|
package net.corda.cordform;
|
||||||
|
|
||||||
import org.bouncycastle.asn1.x500.X500Name;
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|
||||||
public interface CordformContext {
|
public interface CordformContext {
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
package net.corda.cordform;
|
package net.corda.cordform;
|
||||||
|
|
||||||
import static java.util.Collections.emptyList;
|
import com.typesafe.config.Config;
|
||||||
import com.typesafe.config.*;
|
import com.typesafe.config.ConfigFactory;
|
||||||
|
import com.typesafe.config.ConfigValueFactory;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
|
|
||||||
public class CordformNode implements NodeDefinition {
|
public class CordformNode implements NodeDefinition {
|
||||||
/**
|
/**
|
||||||
* Path relative to the running node where the serialized NodeInfos are stored.
|
* Path relative to the running node where the serialized NodeInfos are stored.
|
||||||
|
@ -117,6 +117,16 @@ open class Cordform : DefaultTask() {
|
|||||||
.newInstance()
|
.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.
|
* This task action will create and install the nodes based on the node configurations added.
|
||||||
*/
|
*/
|
||||||
@ -127,6 +137,7 @@ open class Cordform : DefaultTask() {
|
|||||||
installRunScript()
|
installRunScript()
|
||||||
nodes.forEach(Node::build)
|
nodes.forEach(Node::build)
|
||||||
generateAndInstallNodeInfos()
|
generateAndInstallNodeInfos()
|
||||||
|
generateAndInstallNetworkParameters()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initializeConfiguration() {
|
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> {
|
private fun CordformDefinition.getMatchingCordapps(): List<File> {
|
||||||
val cordappJars = project.configuration("cordapp").files
|
val cordappJars = project.configuration("cordapp").files
|
||||||
return cordappPackages.map { `package` ->
|
return cordappPackages.map { `package` ->
|
||||||
@ -193,9 +214,10 @@ open class Cordform : DefaultTask() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun buildNodeProcesses(): Map<Node, Process> {
|
private fun buildNodeProcesses(): Map<Node, Process> {
|
||||||
return nodes
|
val command = generateNodeInfoCommand()
|
||||||
.map { buildNodeProcess(it) }
|
return nodes.map {
|
||||||
.toMap()
|
it.makeLogDirectory()
|
||||||
|
buildProcess(it, command, "generate-info.log") }.toMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun validateNodeProcessess(nodeProcesses: Map<Node, Process>) {
|
private fun validateNodeProcessess(nodeProcesses: Map<Node, Process>) {
|
||||||
@ -210,14 +232,13 @@ open class Cordform : DefaultTask() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildNodeProcess(node: Node): Pair<Node, Process> {
|
private fun buildProcess(node: Node, command: List<String>, logFile: String): Pair<Node, Process> {
|
||||||
node.makeLogDirectory()
|
val process = ProcessBuilder(command)
|
||||||
val process = ProcessBuilder(generateNodeInfoCommand())
|
|
||||||
.directory(node.fullPath().toFile())
|
.directory(node.fullPath().toFile())
|
||||||
.redirectErrorStream(true)
|
.redirectErrorStream(true)
|
||||||
// InheritIO causes hangs on windows due the gradle buffer also not being flushed.
|
// 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)
|
// 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)
|
.addEnvironment("CAPSULE_CACHE_DIR", Node.capsuleCacheDir)
|
||||||
.start()
|
.start()
|
||||||
return Pair(node, process)
|
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) }
|
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.ConfigRenderOptions
|
||||||
import com.typesafe.config.ConfigValueFactory
|
import com.typesafe.config.ConfigValueFactory
|
||||||
import net.corda.cordform.CordformNode
|
import net.corda.cordform.CordformNode
|
||||||
import org.bouncycastle.asn1.x500.X500Name
|
|
||||||
import org.bouncycastle.asn1.x500.style.BCStyle
|
|
||||||
import org.gradle.api.Project
|
import org.gradle.api.Project
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
@ -62,21 +60,6 @@ class Node(private val project: Project) : CordformNode() {
|
|||||||
config = config.withValue("useTestClock", ConfigValueFactory.fromAnyRef(useTestClock))
|
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
|
* 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")
|
project.logger.error("Node has a null name - cannot create node")
|
||||||
throw IllegalStateException("Node has a null name - cannot create node")
|
throw IllegalStateException("Node has a null name - cannot create node")
|
||||||
}
|
}
|
||||||
|
// Parsing O= part directly because importing BouncyCastle provider in Cordformation causes problems
|
||||||
val dirName = try {
|
// with loading our custom X509EdDSAEngine.
|
||||||
val o = X500Name(name).getRDNs(BCStyle.O)
|
val organizationName = name.trim().split(",").firstOrNull { it.startsWith("O=") }?.substringAfter("=")
|
||||||
if (o.isNotEmpty()) o.first().first.value.toString() else name
|
val dirName = organizationName ?: name
|
||||||
} catch (_ : IllegalArgumentException) {
|
|
||||||
// Can't parse as an X500 name, use the full string
|
|
||||||
name
|
|
||||||
}
|
|
||||||
nodeDir = File(rootDir.toFile(), dirName.replace("\\s", ""))
|
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.
|
* Installs the corda fat JAR to the node directory.
|
||||||
*/
|
*/
|
||||||
private fun installCordaJar() {
|
private fun installCordaJar() {
|
||||||
val cordaJar = verifyAndGetCordaJar()
|
val cordaJar = verifyAndGetRuntimeJar("corda")
|
||||||
project.copy {
|
project.copy {
|
||||||
it.apply {
|
it.apply {
|
||||||
from(cordaJar)
|
from(cordaJar)
|
||||||
@ -144,7 +123,7 @@ class Node(private val project: Project) : CordformNode() {
|
|||||||
* Installs the corda webserver JAR to the node directory
|
* Installs the corda webserver JAR to the node directory
|
||||||
*/
|
*/
|
||||||
private fun installWebserverJar() {
|
private fun installWebserverJar() {
|
||||||
val webJar = verifyAndGetWebserverJar()
|
val webJar = verifyAndGetRuntimeJar("corda-webserver")
|
||||||
project.copy {
|
project.copy {
|
||||||
it.apply {
|
it.apply {
|
||||||
from(webJar)
|
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 {
|
private fun verifyAndGetRuntimeJar(jarName: String): 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 {
|
|
||||||
val maybeJar = project.configuration("runtime").filter {
|
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) {
|
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 {
|
} else {
|
||||||
val jar = maybeJar.singleFile
|
val jar = maybeJar.singleFile
|
||||||
require(jar.isFile)
|
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.cordform.CordformNode
|
||||||
import net.corda.core.internal.ThreadBox
|
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.CompositeKey
|
||||||
import net.corda.core.crypto.generateKeyPair
|
import net.corda.core.crypto.generateKeyPair
|
||||||
@ -11,35 +11,40 @@ import net.corda.core.utilities.trace
|
|||||||
import net.corda.nodeapi.internal.crypto.*
|
import net.corda.nodeapi.internal.crypto.*
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
import java.security.cert.X509Certificate
|
||||||
|
|
||||||
object ServiceIdentityGenerator {
|
object ServiceIdentityGenerator {
|
||||||
private val log = LoggerFactory.getLogger(javaClass)
|
private val log = LoggerFactory.getLogger(javaClass)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates signing key pairs and a common distributed service identity for a set of nodes.
|
* 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.
|
* 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.
|
* 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 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 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>,
|
fun generateToDisk(dirs: List<Path>,
|
||||||
serviceName: CordaX500Name,
|
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()}" }
|
log.trace { "Generating a group identity \"serviceName\" for nodes: ${dirs.joinToString()}" }
|
||||||
val keyPairs = (1..dirs.size).map { generateKeyPair() }
|
val keyPairs = (1..dirs.size).map { generateKeyPair() }
|
||||||
val notaryKey = CompositeKey.Builder().addKeys(keyPairs.map { it.public }).build(threshold)
|
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 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 ->
|
keyPairs.zip(dirs) { keyPair, dir ->
|
||||||
val serviceKeyCert = X509Utilities.createCertificate(CertificateType.NODE_CA, issuer.certificate, issuer.keyPair, serviceName, keyPair.public)
|
val serviceKeyCert = X509Utilities.createCertificate(CertificateType.NODE_CA, issuer.certificate, issuer.keyPair, serviceName, keyPair.public)
|
||||||
val compositeKeyCert = X509Utilities.createCertificate(CertificateType.NODE_CA, issuer.certificate, issuer.keyPair, serviceName, notaryKey)
|
val compositeKeyCert = X509Utilities.createCertificate(CertificateType.NODE_CA, issuer.certificate, issuer.keyPair, serviceName, notaryKey)
|
||||||
val certPath = (dir / "certificates").createDirectories() / "distributedService.jks"
|
val certPath = (dir / "certificates").createDirectories() / "distributedService.jks"
|
||||||
val keystore = loadOrCreateKeyStore(certPath, "cordacadevpass")
|
val keystore = loadOrCreateKeyStore(certPath, "cordacadevpass")
|
||||||
val serviceId = serviceName.commonName
|
|
||||||
keystore.setCertificateEntry("$serviceId-composite-key", compositeKeyCert.cert)
|
keystore.setCertificateEntry("$serviceId-composite-key", compositeKeyCert.cert)
|
||||||
keystore.setKeyEntry("$serviceId-private-key", keyPair.private, "cordacadevkeypass".toCharArray(), arrayOf(serviceKeyCert.cert, issuer.certificate.cert, rootCert))
|
keystore.setKeyEntry("$serviceId-private-key", keyPair.private, "cordacadevkeypass".toCharArray(), arrayOf(serviceKeyCert.cert, issuer.certificate.cert, rootCert))
|
||||||
keystore.save(certPath, "cordacadevpass")
|
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)
|
val clientCA = certificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA)
|
||||||
// Create new keys and store in keystore.
|
// Create new keys and store in keystore.
|
||||||
val cert = X509Utilities.createCertificate(CertificateType.WELL_KNOWN_IDENTITY, clientCA.certificate, clientCA.keyPair, serviceName, pubKey)
|
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" }
|
require(certPath.certificates.isNotEmpty()) { "Certificate path cannot be empty" }
|
||||||
// TODO: X509Utilities.validateCertificateChain()
|
// TODO: X509Utilities.validateCertificateChain()
|
||||||
return certPath
|
return certPath
|
||||||
|
@ -4,8 +4,8 @@ import net.corda.core.crypto.Crypto
|
|||||||
import net.corda.core.crypto.SignatureScheme
|
import net.corda.core.crypto.SignatureScheme
|
||||||
import net.corda.core.crypto.random63BitValue
|
import net.corda.core.crypto.random63BitValue
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.identity.CordaX500Name
|
||||||
|
import net.corda.core.internal.cert
|
||||||
import net.corda.core.internal.read
|
import net.corda.core.internal.read
|
||||||
import net.corda.core.internal.write
|
|
||||||
import net.corda.core.internal.x500Name
|
import net.corda.core.internal.x500Name
|
||||||
import net.corda.core.utilities.days
|
import net.corda.core.utilities.days
|
||||||
import net.corda.core.utilities.millis
|
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.PKCS10CertificationRequest
|
||||||
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder
|
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder
|
||||||
import org.bouncycastle.util.io.pem.PemReader
|
import org.bouncycastle.util.io.pem.PemReader
|
||||||
import java.io.FileWriter
|
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
import java.nio.file.Files
|
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
@ -153,7 +151,7 @@ object X509Utilities {
|
|||||||
require(certificates.isNotEmpty()) { "Certificate path must contain at least one certificate" }
|
require(certificates.isNotEmpty()) { "Certificate path must contain at least one certificate" }
|
||||||
val params = PKIXParameters(setOf(TrustAnchor(trustedRoot, null)))
|
val params = PKIXParameters(setOf(TrustAnchor(trustedRoot, null)))
|
||||||
params.isRevocationEnabled = false
|
params.isRevocationEnabled = false
|
||||||
val certPath = X509CertificateFactory().delegate.generateCertPath(certificates.toList())
|
val certPath = X509CertificateFactory().generateCertPath(*certificates)
|
||||||
val pathValidator = CertPathValidator.getInstance("PKIX")
|
val pathValidator = CertPathValidator.getInstance("PKIX")
|
||||||
pathValidator.validate(certPath, params)
|
pathValidator.validate(certPath, params)
|
||||||
}
|
}
|
||||||
@ -164,7 +162,7 @@ object X509Utilities {
|
|||||||
* @param file Target file.
|
* @param file Target file.
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun saveCertificateAsPEMFile(x509Certificate: X509CertificateHolder, file: Path) {
|
fun saveCertificateAsPEMFile(x509Certificate: X509Certificate, file: Path) {
|
||||||
JcaPEMWriter(file.toFile().writer()).use {
|
JcaPEMWriter(file.toFile().writer()).use {
|
||||||
it.writeObject(x509Certificate)
|
it.writeObject(x509Certificate)
|
||||||
}
|
}
|
||||||
@ -176,14 +174,14 @@ object X509Utilities {
|
|||||||
* @return The X509Certificate that was encoded in the file.
|
* @return The X509Certificate that was encoded in the file.
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun loadCertificateFromPEMFile(file: Path): X509CertificateHolder {
|
fun loadCertificateFromPEMFile(file: Path): X509Certificate {
|
||||||
val cert = file.read {
|
return file.read {
|
||||||
val reader = PemReader(it.reader())
|
val reader = PemReader(it.reader())
|
||||||
val pemObject = reader.readPemObject()
|
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 {
|
class X509CertificateFactory {
|
||||||
val delegate: CertificateFactory = CertificateFactory.getInstance("X.509")
|
val delegate: CertificateFactory = CertificateFactory.getInstance("X.509")
|
||||||
|
|
||||||
fun generateCertificate(input: InputStream): X509Certificate {
|
fun generateCertificate(input: InputStream): X509Certificate {
|
||||||
return delegate.generateCertificate(input) as 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) {
|
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() {
|
class CordaClassResolver(serializationContext: SerializationContext) : DefaultClassResolver() {
|
||||||
val whitelist: ClassWhitelist = TransientClassWhiteList(serializationContext.whitelist)
|
val whitelist: ClassWhitelist = TransientClassWhiteList(serializationContext.whitelist)
|
||||||
|
|
||||||
/*
|
// These classes are assignment-compatible Java equivalents of Kotlin classes.
|
||||||
* 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.
|
||||||
* The point is that we do not want to send Kotlin types "over the wire" via RPC.
|
|
||||||
*/
|
|
||||||
private val javaAliases: Map<Class<*>, Class<*>> = mapOf(
|
private val javaAliases: Map<Class<*>, Class<*>> = mapOf(
|
||||||
listOf<Any>().javaClass to Collections.emptyList<Any>().javaClass,
|
listOf<Any>().javaClass to Collections.emptyList<Any>().javaClass,
|
||||||
setOf<Any>().javaClass to Collections.emptySet<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)
|
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 org.apache.activemq.artemis.api.core.SimpleString
|
||||||
import rx.Notification
|
import rx.Notification
|
||||||
import rx.exceptions.OnErrorNotImplementedException
|
import rx.exceptions.OnErrorNotImplementedException
|
||||||
|
import sun.security.x509.X509CertImpl
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -49,8 +50,8 @@ object DefaultWhitelist : SerializationWhitelist {
|
|||||||
java.time.YearMonth::class.java,
|
java.time.YearMonth::class.java,
|
||||||
java.time.MonthDay::class.java,
|
java.time.MonthDay::class.java,
|
||||||
java.time.Period::class.java,
|
java.time.Period::class.java,
|
||||||
java.time.DayOfWeek::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 serialiser 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.emptyMap<Any, Any>().javaClass,
|
||||||
java.util.Collections.emptySet<Any>().javaClass,
|
java.util.Collections.emptySet<Any>().javaClass,
|
||||||
@ -58,6 +59,9 @@ object DefaultWhitelist : SerializationWhitelist {
|
|||||||
java.util.LinkedHashMap::class.java,
|
java.util.LinkedHashMap::class.java,
|
||||||
BitSet::class.java,
|
BitSet::class.java,
|
||||||
OnErrorNotImplementedException::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)
|
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
|
* NEVER DELETE OR CHANGE THE ID ASSOCIATED WITH A TYPE
|
||||||
*
|
*
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
package net.corda.nodeapi.internal.serialization.amqp
|
package net.corda.nodeapi.internal.serialization.amqp
|
||||||
|
|
||||||
|
import net.corda.core.cordapp.Cordapp
|
||||||
import net.corda.core.serialization.*
|
import net.corda.core.serialization.*
|
||||||
import net.corda.core.utilities.ByteSequence
|
import net.corda.core.utilities.ByteSequence
|
||||||
import net.corda.nodeapi.internal.serialization.DefaultWhitelist
|
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 {
|
companion object {
|
||||||
private val serializationWhitelists: List<SerializationWhitelist> by lazy {
|
private val serializationWhitelists: List<SerializationWhitelist> by lazy {
|
||||||
ServiceLoader.load(SerializationWhitelist::class.java, this::class.java.classLoader).toList() + DefaultWhitelist
|
ServiceLoader.load(SerializationWhitelist::class.java, this::class.java.classLoader).toList() + DefaultWhitelist
|
||||||
@ -62,8 +64,15 @@ abstract class AbstractAMQPSerializationScheme : SerializationScheme {
|
|||||||
register(net.corda.nodeapi.internal.serialization.amqp.custom.EnumSetSerializer(this))
|
register(net.corda.nodeapi.internal.serialization.amqp.custom.EnumSetSerializer(this))
|
||||||
register(net.corda.nodeapi.internal.serialization.amqp.custom.ContractAttachmentSerializer(this))
|
register(net.corda.nodeapi.internal.serialization.amqp.custom.ContractAttachmentSerializer(this))
|
||||||
}
|
}
|
||||||
for (whitelistProvider in serializationWhitelists)
|
for (whitelistProvider in serializationWhitelists) {
|
||||||
factory.addToWhitelist(*whitelistProvider.whitelist.toTypedArray())
|
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>()
|
private val serializerFactoriesForContexts = ConcurrentHashMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>()
|
||||||
@ -97,11 +106,11 @@ abstract class AbstractAMQPSerializationScheme : SerializationScheme {
|
|||||||
return SerializationOutput(serializerFactory).serialize(obj)
|
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
|
// 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 {
|
override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory {
|
||||||
throw UnsupportedOperationException()
|
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
|
// 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 {
|
override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory {
|
||||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
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[][][]
|
// id to generate it properly (it will always return [[[Ljava.lang.type -> type[][][]
|
||||||
// for example).
|
// for example).
|
||||||
//
|
//
|
||||||
// We *need* to retain knowledge for AMQP deserialisation weather that lowest primitive
|
// We *need* to retain knowledge for AMQP deserialization weather that lowest primitive
|
||||||
// was boxed or unboxed so just infer it recursively
|
// was boxed or unboxed so just infer it recursively.
|
||||||
private fun calcTypeName(type: Type): String =
|
private fun calcTypeName(type: Type): String =
|
||||||
if (type.componentType().isArray()) {
|
if (type.componentType().isArray()) {
|
||||||
val typeName = calcTypeName(type.componentType()); "$typeName[]"
|
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 org.apache.qpid.proton.codec.Data
|
||||||
import java.lang.reflect.Type
|
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
|
* Base class for serializers of core platform types that do not conform to the usual serialization rules and thus
|
||||||
* cannot be automatically serialized.
|
* 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
|
* 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.
|
* that refer to other custom types etc.
|
||||||
*/
|
*/
|
||||||
open val additionalSerializers: Iterable<CustomSerializer<out Any>> = emptyList()
|
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
|
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.
|
* 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) {
|
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
|
||||||
data.withDescribed(descriptor) {
|
data.withDescribed(descriptor) {
|
||||||
@ -147,8 +152,8 @@ abstract class CustomSerializer<T : Any> : AMQPSerializer<T> {
|
|||||||
*
|
*
|
||||||
* @param clazz The type to be marshalled
|
* @param clazz The type to be marshalled
|
||||||
* @param withInheritance Whether subclasses of the class can also 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 maker 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 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,
|
abstract class ToString<T : Any>(clazz: Class<T>, withInheritance: Boolean = false,
|
||||||
private val maker: (String) -> T = clazz.getConstructor(String::class.java).let { `constructor` ->
|
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
|
import kotlin.reflect.jvm.javaType
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serializer for deserialising objects whose definition has changed since they
|
* Serializer for deserializing objects whose definition has changed since they
|
||||||
* were serialised
|
* were serialised.
|
||||||
*/
|
*/
|
||||||
class EvolutionSerializer(
|
class EvolutionSerializer(
|
||||||
clazz: Type,
|
clazz: Type,
|
||||||
@ -38,16 +38,16 @@ class EvolutionSerializer(
|
|||||||
|
|
||||||
companion object {
|
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
|
* 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.
|
* since serialisation we need to attempt to locate a constructor that we can use. For example,
|
||||||
* it's parameters match the serialised members and it will initialise any newly added
|
* its parameters match the serialised members and it will initialise any newly added
|
||||||
* elements
|
* elements.
|
||||||
*
|
*
|
||||||
* TODO: Type evolution
|
* TODO: Type evolution
|
||||||
* TODO: rename annotation
|
* 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()!!
|
val clazz: Class<*> = type.asClass()!!
|
||||||
if (!isConcrete(clazz)) return null
|
if (!isConcrete(clazz)) return null
|
||||||
|
|
||||||
@ -70,13 +70,15 @@ class EvolutionSerializer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build a serialization object for deserialisation only of objects serialised
|
* Build a serialization object for deserialization only of objects serialised
|
||||||
* as different versions of a class
|
* as different versions of a class.
|
||||||
*
|
*
|
||||||
* @param old is an object holding the schema that represents the object
|
* @param old is an object holding the schema that represents the object
|
||||||
* as it was serialised and the type descriptor of that type
|
* 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
|
* @param new is the Serializer built for the Class as it exists now, not
|
||||||
* how it was serialised and persisted.
|
* 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,
|
fun make(old: CompositeType, new: ObjectSerializer,
|
||||||
factory: SerializerFactory): AMQPSerializer<Any> {
|
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
|
* 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
|
* 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
|
* of the current constructor onto that list inserting nulls where new parameters are
|
||||||
* encountered
|
* encountered.
|
||||||
*
|
*
|
||||||
* TODO: Object references
|
* TODO: Object references
|
||||||
*/
|
*/
|
||||||
|
@ -82,11 +82,13 @@ private fun <T : Any> propertiesForSerializationFromConstructor(kotlinConstructo
|
|||||||
for (param in kotlinConstructor.parameters) {
|
for (param in kotlinConstructor.parameters) {
|
||||||
val name = param.name ?: throw NotSerializableException("Constructor parameter of $clazz has no name.")
|
val name = param.name ?: throw NotSerializableException("Constructor parameter of $clazz has no name.")
|
||||||
val matchingProperty = properties[name] ?:
|
val matchingProperty = properties[name] ?:
|
||||||
throw NotSerializableException("No property matching constructor parameter named '$name' of '$clazz'." +
|
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.
|
// Check that the method has a getter in java.
|
||||||
val getter = matchingProperty.readMethod ?: throw NotSerializableException("Property has no getter method for $name of $clazz." +
|
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)
|
val returnType = resolveTypeVariables(getter.genericReturnType, type)
|
||||||
if (constructorParamTakesReturnTypeOfGetter(returnType, getter.genericReturnType, param)) {
|
if (constructorParamTakesReturnTypeOfGetter(returnType, getter.genericReturnType, param)) {
|
||||||
rc += PropertySerializer.make(name, getter, returnType, factory)
|
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) {
|
open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
|
||||||
private val serializersByType = ConcurrentHashMap<Type, AMQPSerializer<Any>>()
|
private val serializersByType = ConcurrentHashMap<Type, AMQPSerializer<Any>>()
|
||||||
private val serializersByDescriptor = ConcurrentHashMap<Any, 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>>>()
|
val transformsCache = ConcurrentHashMap<String, EnumMap<TransformTypes, MutableList<Transform>>>()
|
||||||
|
|
||||||
open val classCarpenter = ClassCarpenter(cl, whitelist)
|
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
|
* 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
|
* if not, use the [ClassCarpenter] to generate a class to use in it's place.
|
||||||
*/
|
*/
|
||||||
private fun processSchema(schemaAndDescriptor: FactorySchemaAndDescriptor, sentinel: Boolean = false) {
|
private fun processSchema(schemaAndDescriptor: FactorySchemaAndDescriptor, sentinel: Boolean = false) {
|
||||||
val metaSchema = CarpenterMetaSchema.newInstance()
|
val metaSchema = CarpenterMetaSchema.newInstance()
|
||||||
@ -267,11 +274,13 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
|
|||||||
for (customSerializer in customSerializers) {
|
for (customSerializer in customSerializers) {
|
||||||
if (customSerializer.isSerializerFor(clazz)) {
|
if (customSerializer.isSerializerFor(clazz)) {
|
||||||
val declaredSuperClass = declaredType.asClass()?.superclass
|
val declaredSuperClass = declaredType.asClass()?.superclass
|
||||||
if (declaredSuperClass == null || !customSerializer.isSerializerFor(declaredSuperClass) || !customSerializer.revealSubclassesInSchema) {
|
return if (declaredSuperClass == null
|
||||||
return customSerializer
|
|| !customSerializer.isSerializerFor(declaredSuperClass)
|
||||||
|
|| !customSerializer.revealSubclassesInSchema) {
|
||||||
|
customSerializer as? AMQPSerializer<Any>
|
||||||
} else {
|
} else {
|
||||||
// Make a subclass serializer for the subclass and return that...
|
// 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.RestrictedType
|
||||||
import net.corda.nodeapi.internal.serialization.amqp.Field as AMQPField
|
import net.corda.nodeapi.internal.serialization.amqp.Field as AMQPField
|
||||||
import net.corda.nodeapi.internal.serialization.amqp.Schema as AMQPSchema
|
import net.corda.nodeapi.internal.serialization.amqp.Schema as AMQPSchema
|
||||||
|
import net.corda.core.serialization.SerializationContext
|
||||||
|
|
||||||
fun AMQPSchema.carpenterSchema(classloader: ClassLoader): CarpenterMetaSchema {
|
fun AMQPSchema.carpenterSchema(classloader: ClassLoader): CarpenterMetaSchema {
|
||||||
val rtn = CarpenterMetaSchema.newInstance()
|
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
|
* b) add the class to the dependency tree in [carpenterSchemas] if it cannot be instantiated
|
||||||
* at this time
|
* 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
|
* @param carpenterSchemas structure that holds the dependency tree and list of classes that
|
||||||
* need constructing
|
* need constructing
|
||||||
* @param force by default a schema is not added to [carpenterSchemas] if it already exists
|
* @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) {
|
fun AMQPField.getTypeAsClass(classloader: ClassLoader) = typeStrToType[Pair(type, mandatory)] ?: when (type) {
|
||||||
"string" -> String::class.java
|
"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)
|
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
|
* Known Sub Classes
|
||||||
* - [ClassSchema]
|
* - [ClassSchema]
|
||||||
@ -62,7 +62,7 @@ fun EnumMap<SchemaFlags, Boolean>.simpleFieldAccess(): Boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a concrete object
|
* Represents a concrete object.
|
||||||
*/
|
*/
|
||||||
class ClassSchema(
|
class ClassSchema(
|
||||||
name: String,
|
name: String,
|
||||||
@ -77,7 +77,7 @@ class ClassSchema(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents an interface. Carpented interfaces can be used within [ClassSchema]s
|
* 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(
|
class InterfaceSchema(
|
||||||
name: String,
|
name: String,
|
||||||
@ -91,7 +91,7 @@ class InterfaceSchema(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents an enumerated type
|
* Represents an enumerated type.
|
||||||
*/
|
*/
|
||||||
class EnumSchema(
|
class EnumSchema(
|
||||||
name: String,
|
name: String,
|
||||||
@ -111,8 +111,8 @@ class EnumSchema(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory object used by the serialiser when building [Schema]s based
|
* Factory object used by the serializer when building [Schema]s based
|
||||||
* on an AMQP schema
|
* on an AMQP schema.
|
||||||
*/
|
*/
|
||||||
object CarpenterSchemaFactory {
|
object CarpenterSchemaFactory {
|
||||||
fun newInstance(
|
fun newInstance(
|
||||||
|
@ -44,6 +44,7 @@ import org.objenesis.strategy.StdInstantiatorStrategy
|
|||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import sun.security.ec.ECPublicKeyImpl
|
import sun.security.ec.ECPublicKeyImpl
|
||||||
import sun.security.provider.certpath.X509CertPath
|
import sun.security.provider.certpath.X509CertPath
|
||||||
|
import sun.security.x509.X509CertImpl
|
||||||
import java.io.BufferedInputStream
|
import java.io.BufferedInputStream
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
@ -75,6 +76,7 @@ object DefaultKryoCustomizer {
|
|||||||
addDefaultSerializer(InputStream::class.java, InputStreamSerializer)
|
addDefaultSerializer(InputStream::class.java, InputStreamSerializer)
|
||||||
addDefaultSerializer(SerializeAsToken::class.java, SerializeAsTokenSerializer<SerializeAsToken>())
|
addDefaultSerializer(SerializeAsToken::class.java, SerializeAsTokenSerializer<SerializeAsToken>())
|
||||||
addDefaultSerializer(Logger::class.java, LoggerSerializer)
|
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
|
// 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.
|
// 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(FileInputStream::class.java, InputStreamSerializer)
|
||||||
register(CertPath::class.java, CertPathSerializer)
|
register(CertPath::class.java, CertPathSerializer)
|
||||||
register(X509CertPath::class.java, CertPathSerializer)
|
register(X509CertPath::class.java, CertPathSerializer)
|
||||||
register(X509Certificate::class.java, X509CertificateSerializer)
|
|
||||||
register(BCECPrivateKey::class.java, PrivateKeySerializer)
|
register(BCECPrivateKey::class.java, PrivateKeySerializer)
|
||||||
register(BCECPublicKey::class.java, publicKeySerializer)
|
register(BCECPublicKey::class.java, publicKeySerializer)
|
||||||
register(BCRSAPrivateCrtKey::class.java, PrivateKeySerializer)
|
register(BCRSAPrivateCrtKey::class.java, PrivateKeySerializer)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package net.corda.nodeapi
|
package net.corda.nodeapi
|
||||||
|
|
||||||
import net.corda.cordform.CordformNode
|
import net.corda.cordform.CordformNode
|
||||||
|
import net.corda.nodeapi.internal.NodeInfoFilesCopier
|
||||||
import net.corda.testing.eventually
|
import net.corda.testing.eventually
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
|
@ -71,7 +71,7 @@ class X509UtilitiesTest {
|
|||||||
fun `load and save a PEM file certificate`() {
|
fun `load and save a PEM file certificate`() {
|
||||||
val tmpCertificateFile = tempFile("cacert.pem")
|
val tmpCertificateFile = tempFile("cacert.pem")
|
||||||
val caKey = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
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)
|
X509Utilities.saveCertificateAsPEMFile(caCert, tmpCertificateFile)
|
||||||
val readCertificate = X509Utilities.loadCertificateFromPEMFile(tmpCertificateFile)
|
val readCertificate = X509Utilities.loadCertificateFromPEMFile(tmpCertificateFile)
|
||||||
assertEquals(caCert, readCertificate)
|
assertEquals(caCert, readCertificate)
|
||||||
@ -433,7 +433,7 @@ class X509UtilitiesTest {
|
|||||||
val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||||
val rootCACert = X509Utilities.createSelfSignedCACertificate(ALICE.name, rootCAKey)
|
val rootCACert = X509Utilities.createSelfSignedCACertificate(ALICE.name, rootCAKey)
|
||||||
val certificate = X509Utilities.createCertificate(CertificateType.TLS, rootCACert, rootCAKey, BOB.name.x500Name, BOB_PUBKEY)
|
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 serialized = expected.serialize(factory, context).bytes
|
||||||
val actual: CertPath = serialized.deserialize(factory, context)
|
val actual: CertPath = serialized.deserialize(factory, context)
|
||||||
assertEquals(expected, actual)
|
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.
|
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 {
|
override fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory {
|
||||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||||
}
|
}
|
||||||
|
@ -593,7 +593,7 @@ class SerializationOutputTests {
|
|||||||
fun `test transaction state`() {
|
fun `test transaction state`() {
|
||||||
val state = TransactionState(FooState(), FOO_PROGRAM_ID, MEGA_CORP)
|
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" }
|
val func = scheme::class.superclasses.single { it.simpleName == "AbstractAMQPSerializationScheme" }
|
||||||
.java.getDeclaredMethod("registerCustomSerializers", SerializerFactory::class.java)
|
.java.getDeclaredMethod("registerCustomSerializers", SerializerFactory::class.java)
|
||||||
func.isAccessible = true
|
func.isAccessible = true
|
||||||
|
@ -13,6 +13,7 @@ import net.corda.core.identity.CordaX500Name
|
|||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.internal.deleteIfExists
|
import net.corda.core.internal.deleteIfExists
|
||||||
import net.corda.core.internal.div
|
import net.corda.core.internal.div
|
||||||
|
import net.corda.core.node.services.NotaryService
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.transactions.TransactionBuilder
|
import net.corda.core.transactions.TransactionBuilder
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
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.internal.StartedNode
|
||||||
import net.corda.node.services.config.BFTSMaRtConfiguration
|
import net.corda.node.services.config.BFTSMaRtConfiguration
|
||||||
import net.corda.node.services.config.NotaryConfig
|
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.minClusterSize
|
||||||
import net.corda.node.services.transactions.minCorrectReplicas
|
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.node.utilities.ServiceIdentityGenerator
|
||||||
import net.corda.testing.IntegrationTest
|
import net.corda.testing.IntegrationTest
|
||||||
import net.corda.testing.chooseIdentity
|
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.*
|
||||||
import net.corda.testing.contracts.DummyContract
|
import net.corda.testing.contracts.DummyContract
|
||||||
import net.corda.testing.node.MockNetwork
|
import net.corda.testing.node.MockNetwork
|
||||||
@ -68,19 +72,26 @@ class BFTNotaryServiceTests : IntegrationTest() {
|
|||||||
|
|
||||||
notary = ServiceIdentityGenerator.generateToDisk(
|
notary = ServiceIdentityGenerator.generateToDisk(
|
||||||
replicaIds.map { mockNet.baseDirectory(mockNet.nextNodeId + it) },
|
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) }
|
val clusterAddresses = replicaIds.map { NetworkHostAndPort("localhost", 11000 + it * 10) }
|
||||||
|
|
||||||
replicaIds.forEach { replicaId ->
|
val nodes = replicaIds.map { replicaId ->
|
||||||
mockNet.createNode(MockNodeParameters(configOverrides = {
|
mockNet.createUnstartedNode(MockNodeParameters(configOverrides = {
|
||||||
val notary = NotaryConfig(validating = false, bftSMaRt = BFTSMaRtConfiguration(replicaId, clusterAddresses, exposeRaces = exposeRaces))
|
val notary = NotaryConfig(validating = false, bftSMaRt = BFTSMaRtConfiguration(replicaId, clusterAddresses, exposeRaces = exposeRaces))
|
||||||
doReturn(notary).whenever(it).notary
|
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. */
|
/** 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.finance.flows.CashPaymentFlow
|
||||||
import net.corda.node.services.Permissions.Companion.invokeRpc
|
import net.corda.node.services.Permissions.Companion.invokeRpc
|
||||||
import net.corda.node.services.Permissions.Companion.startFlow
|
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.nodeapi.internal.config.User
|
||||||
import net.corda.testing.*
|
import net.corda.testing.*
|
||||||
import net.corda.testing.driver.NodeHandle
|
import net.corda.testing.driver.NodeHandle
|
||||||
@ -47,7 +46,7 @@ class DistributedServiceTests : IntegrationTest() {
|
|||||||
|
|
||||||
driver(
|
driver(
|
||||||
extraCordappPackagesToScan = listOf("net.corda.finance.contracts"),
|
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()
|
alice = startNode(providedName = ALICE.name, rpcUsers = listOf(testUser)).getOrThrow()
|
||||||
raftNotaryIdentity = defaultNotaryIdentity
|
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.node.services.KeyManagementService
|
||||||
import net.corda.core.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
import net.corda.node.services.identity.InMemoryIdentityService
|
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.*
|
||||||
import net.corda.testing.node.MockKeyManagementService
|
import net.corda.testing.node.MockKeyManagementService
|
||||||
import net.corda.testing.node.makeTestIdentityService
|
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
|
val legalName = MEGA_CORP.name
|
||||||
certificatesDirectory.createDirectories()
|
certificatesDirectory.createDirectories()
|
||||||
if (!trustStoreFile.exists()) {
|
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(
|
val caKeyStore = loadKeyStore(
|
||||||
javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordadevcakeys.jks"),
|
javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"),
|
||||||
"cordacadevpass")
|
"cordacadevpass")
|
||||||
|
|
||||||
val rootCACert = caKeyStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA).toX509CertHolder()
|
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.messaging.*
|
||||||
import net.corda.core.node.*
|
import net.corda.core.node.*
|
||||||
import net.corda.core.node.services.*
|
import net.corda.core.node.services.*
|
||||||
import net.corda.core.serialization.SerializationWhitelist
|
import net.corda.core.serialization.*
|
||||||
import net.corda.core.serialization.SerializeAsToken
|
|
||||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
|
||||||
import net.corda.core.serialization.serialize
|
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
import net.corda.core.utilities.debug
|
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.config.configureWithDevSSLCertificate
|
||||||
import net.corda.node.services.events.NodeSchedulerService
|
import net.corda.node.services.events.NodeSchedulerService
|
||||||
import net.corda.node.services.events.ScheduledActivityObserver
|
import net.corda.node.services.events.ScheduledActivityObserver
|
||||||
import net.corda.node.services.api.IdentityServiceInternal
|
|
||||||
import net.corda.node.services.identity.PersistentIdentityService
|
import net.corda.node.services.identity.PersistentIdentityService
|
||||||
import net.corda.node.services.keys.PersistentKeyManagementService
|
import net.corda.node.services.keys.PersistentKeyManagementService
|
||||||
import net.corda.node.services.messaging.MessagingService
|
import net.corda.node.services.messaging.MessagingService
|
||||||
@ -62,6 +58,7 @@ import net.corda.node.services.vault.NodeVaultService
|
|||||||
import net.corda.node.services.vault.VaultSoftLockManager
|
import net.corda.node.services.vault.VaultSoftLockManager
|
||||||
import net.corda.node.shell.InteractiveShell
|
import net.corda.node.shell.InteractiveShell
|
||||||
import net.corda.node.utilities.AffinityExecutor
|
import net.corda.node.utilities.AffinityExecutor
|
||||||
|
import net.corda.nodeapi.internal.NetworkParameters
|
||||||
import net.corda.nodeapi.internal.crypto.*
|
import net.corda.nodeapi.internal.crypto.*
|
||||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||||
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
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
|
* 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.
|
* 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
|
// 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.
|
// AbstractNode. It should be possible to generate the NodeInfo outside of AbstractNode, so it can be passed in.
|
||||||
abstract class AbstractNode(val configuration: NodeConfiguration,
|
abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||||
@ -125,6 +124,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
// low-performance prototyping period.
|
// low-performance prototyping period.
|
||||||
protected abstract val serverThread: AffinityExecutor
|
protected abstract val serverThread: AffinityExecutor
|
||||||
|
|
||||||
|
protected lateinit var networkParameters: NetworkParameters
|
||||||
private val cordappServices = MutableClassToInstanceMap.create<SerializeAsToken>()
|
private val cordappServices = MutableClassToInstanceMap.create<SerializeAsToken>()
|
||||||
private val flowFactories = ConcurrentHashMap<Class<out FlowLogic<*>>, InitiatedFlowFactory<*>>()
|
private val flowFactories = ConcurrentHashMap<Class<out FlowLogic<*>>, InitiatedFlowFactory<*>>()
|
||||||
|
|
||||||
@ -137,7 +137,11 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
protected lateinit var network: MessagingService
|
protected lateinit var network: MessagingService
|
||||||
protected val runOnStop = ArrayList<() -> Any?>()
|
protected val runOnStop = ArrayList<() -> Any?>()
|
||||||
protected val _nodeReadyFuture = openFuture<Unit>()
|
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
|
lateinit var securityManager: RPCSecurityManager get
|
||||||
|
|
||||||
@ -179,7 +183,9 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
val schemaService = NodeSchemaService(cordappLoader.cordappSchemas)
|
val schemaService = NodeSchemaService(cordappLoader.cordappSchemas)
|
||||||
val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null)
|
val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null)
|
||||||
initialiseDatabasePersistence(schemaService, makeIdentityService(identity.certificate)) { database ->
|
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 (keyPairs, info) = initNodeInfo(persistentNetworkMapCache, identity, identityKeyPair)
|
||||||
val identityKeypair = keyPairs.first { it.public == info.legalIdentities.first().owningKey }
|
val identityKeypair = keyPairs.first { it.public == info.legalIdentities.first().owningKey }
|
||||||
val serialisedNodeInfo = info.serialize()
|
val serialisedNodeInfo = info.serialize()
|
||||||
@ -193,12 +199,13 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
check(started == null) { "Node has already been started" }
|
check(started == null) { "Node has already been started" }
|
||||||
log.info("Node starting up ...")
|
log.info("Node starting up ...")
|
||||||
initCertificate()
|
initCertificate()
|
||||||
|
readNetworkParameters()
|
||||||
val schemaService = NodeSchemaService(cordappLoader.cordappSchemas)
|
val schemaService = NodeSchemaService(cordappLoader.cordappSchemas)
|
||||||
val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null)
|
val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null)
|
||||||
val identityService = makeIdentityService(identity.certificate)
|
val identityService = makeIdentityService(identity.certificate)
|
||||||
// Do all of this in a database transaction so anything that might need a connection has one.
|
// Do all of this in a database transaction so anything that might need a connection has one.
|
||||||
val (startedImpl, schedulerService) = initialiseDatabasePersistence(schemaService, identityService) { database ->
|
val (startedImpl, schedulerService) = initialiseDatabasePersistence(schemaService, identityService) { database ->
|
||||||
val networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database), identityService)
|
val networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, networkParameters.notaries), identityService)
|
||||||
val (keyPairs, info) = initNodeInfo(networkMapCache, identity, identityKeyPair)
|
val (keyPairs, info) = initNodeInfo(networkMapCache, identity, identityKeyPair)
|
||||||
identityService.loadIdentities(info.legalIdentitiesAndCerts)
|
identityService.loadIdentities(info.legalIdentitiesAndCerts)
|
||||||
val transactionStorage = makeTransactionStorage(database)
|
val transactionStorage = makeTransactionStorage(database)
|
||||||
@ -282,14 +289,19 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
val keyPairs = mutableSetOf(identityKeyPair)
|
val keyPairs = mutableSetOf(identityKeyPair)
|
||||||
|
|
||||||
myNotaryIdentity = configuration.notary?.let {
|
myNotaryIdentity = configuration.notary?.let {
|
||||||
val (notaryIdentity, notaryIdentityKeyPair) = obtainIdentity(it)
|
if (it.isClusterConfig) {
|
||||||
keyPairs += notaryIdentityKeyPair
|
val (notaryIdentity, notaryIdentityKeyPair) = obtainIdentity(it)
|
||||||
notaryIdentity
|
keyPairs += notaryIdentityKeyPair
|
||||||
|
notaryIdentity
|
||||||
|
} else {
|
||||||
|
// In case of a single notary service myNotaryIdentity will be the node's single identity.
|
||||||
|
identity
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var info = NodeInfo(
|
var info = NodeInfo(
|
||||||
myAddresses(),
|
myAddresses(),
|
||||||
listOf(identity, myNotaryIdentity).filterNotNull(),
|
setOf(identity, myNotaryIdentity).filterNotNull(),
|
||||||
versionInfo.platformVersion,
|
versionInfo.platformVersion,
|
||||||
platformClock.instant().toEpochMilli()
|
platformClock.instant().toEpochMilli()
|
||||||
)
|
)
|
||||||
@ -632,6 +644,13 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
return PersistentKeyManagementService(identityService, keyPairs)
|
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 {
|
private fun makeCoreNotaryService(notaryConfig: NotaryConfig, database: CordaPersistence): NotaryService {
|
||||||
val notaryKey = myNotaryIdentity?.owningKey ?: throw IllegalArgumentException("No notary identity initialized when creating a notary service")
|
val notaryKey = myNotaryIdentity?.owningKey ?: throw IllegalArgumentException("No notary identity initialized when creating a notary service")
|
||||||
return notaryConfig.run {
|
return notaryConfig.run {
|
||||||
@ -687,22 +706,16 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
private fun obtainIdentity(notaryConfig: NotaryConfig?): Pair<PartyAndCertificate, KeyPair> {
|
private fun obtainIdentity(notaryConfig: NotaryConfig?): Pair<PartyAndCertificate, KeyPair> {
|
||||||
val keyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword)
|
val keyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword)
|
||||||
|
|
||||||
val (id, singleName) = if (notaryConfig == null) {
|
val (id, singleName) = if (notaryConfig == null || !notaryConfig.isClusterConfig) {
|
||||||
// Node's main identity
|
// Node's main identity or if it's a single node notary
|
||||||
Pair("identity", myLegalName)
|
Pair("identity", myLegalName)
|
||||||
} else {
|
} else {
|
||||||
val notaryId = notaryConfig.run {
|
val notaryId = notaryConfig.run {
|
||||||
NotaryService.constructId(validating, raft != null, bftSMaRt != null, custom)
|
NotaryService.constructId(validating, raft != null, bftSMaRt != null, custom)
|
||||||
}
|
}
|
||||||
if (!notaryConfig.isClusterConfig) {
|
// The node is part of a distributed notary whose identity must already be generated beforehand.
|
||||||
// Node's notary identity
|
Pair(notaryId, null)
|
||||||
Pair(notaryId, myLegalName.copy(commonName = notaryId))
|
|
||||||
} else {
|
|
||||||
// The node is part of a distributed notary whose identity must already be generated beforehand
|
|
||||||
Pair(notaryId, null)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Integrate with Key management service?
|
// TODO: Integrate with Key management service?
|
||||||
val privateKeyAlias = "$id-private-key"
|
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")
|
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)
|
return Pair(PartyAndCertificate(certPath), keyPair)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,11 +194,17 @@ open class Node(configuration: NodeConfiguration,
|
|||||||
return if (!AddressUtils.isPublic(host)) {
|
return if (!AddressUtils.isPublic(host)) {
|
||||||
val foundPublicIP = AddressUtils.tryDetectPublicIP()
|
val foundPublicIP = AddressUtils.tryDetectPublicIP()
|
||||||
if (foundPublicIP == null) {
|
if (foundPublicIP == null) {
|
||||||
val retrievedHostName = networkMapClient?.myPublicHostname()
|
try {
|
||||||
if (retrievedHostName != null) {
|
val retrievedHostName = networkMapClient?.myPublicHostname()
|
||||||
log.info("Retrieved public IP from Network Map Service: $this. This will be used instead of the provided \"$host\" as the advertised address.")
|
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
|
||||||
}
|
}
|
||||||
retrievedHostName
|
|
||||||
} else {
|
} else {
|
||||||
log.info("Detected public IP: ${foundPublicIP.hostAddress}. This will be used instead of the provided \"$host\" as the advertised address.")
|
log.info("Detected public IP: ${foundPublicIP.hostAddress}. This will be used instead of the provided \"$host\" as the advertised address.")
|
||||||
foundPublicIP.hostAddress
|
foundPublicIP.hostAddress
|
||||||
@ -309,11 +315,11 @@ open class Node(configuration: NodeConfiguration,
|
|||||||
nodeSerializationEnv = SerializationEnvironmentImpl(
|
nodeSerializationEnv = SerializationEnvironmentImpl(
|
||||||
SerializationFactoryImpl().apply {
|
SerializationFactoryImpl().apply {
|
||||||
registerScheme(KryoServerSerializationScheme())
|
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),
|
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))
|
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.internal.cordapp.CordappImpl
|
||||||
import net.corda.core.node.services.CordaService
|
import net.corda.core.node.services.CordaService
|
||||||
import net.corda.core.schemas.MappedSchema
|
import net.corda.core.schemas.MappedSchema
|
||||||
|
import net.corda.core.serialization.SerializationCustomSerializer
|
||||||
import net.corda.core.serialization.SerializationWhitelist
|
import net.corda.core.serialization.SerializationWhitelist
|
||||||
import net.corda.core.serialization.SerializeAsToken
|
import net.corda.core.serialization.SerializeAsToken
|
||||||
import net.corda.core.utilities.contextLogger
|
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. */
|
/** A Cordapp representing the core package which is not scanned automatically. */
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
internal val coreCordapp = CordappImpl(
|
internal val coreCordapp = CordappImpl(
|
||||||
listOf(),
|
contractClassNames = listOf(),
|
||||||
listOf(),
|
initiatedFlows = listOf(),
|
||||||
coreRPCFlows,
|
rpcFlows = coreRPCFlows,
|
||||||
listOf(),
|
serviceFlows = listOf(),
|
||||||
listOf(),
|
schedulableFlows = listOf(),
|
||||||
listOf(),
|
services = listOf(),
|
||||||
listOf(),
|
serializationWhitelists = listOf(),
|
||||||
setOf(),
|
serializationCustomSerializers = listOf(),
|
||||||
ContractUpgradeFlow.javaClass.protectionDomain.codeSource.location // Core JAR location
|
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),
|
findSchedulableFlows(scanResult),
|
||||||
findServices(scanResult),
|
findServices(scanResult),
|
||||||
findPlugins(it),
|
findPlugins(it),
|
||||||
|
findSerializers(scanResult),
|
||||||
findCustomSchemas(scanResult),
|
findCustomSchemas(scanResult),
|
||||||
it.url)
|
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.
|
} + 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> {
|
private fun findCustomSchemas(scanResult: RestrictedScanResult): Set<MappedSchema> {
|
||||||
return scanResult.getClassesWithSuperclass(MappedSchema::class).toSet()
|
return scanResult.getClassesWithSuperclass(MappedSchema::class).toSet()
|
||||||
}
|
}
|
||||||
@ -303,6 +310,14 @@ class CordappLoader private constructor(private val cordappJarPaths: List<Restri
|
|||||||
.map { it.kotlin.objectOrNewInstance() }
|
.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>> {
|
fun <T : Any> getClassesWithAnnotation(type: KClass<T>, annotation: KClass<out Annotation>): List<Class<out T>> {
|
||||||
return scanResult.getNamesOfClassesWithAnnotation(annotation.java)
|
return scanResult.getNamesOfClassesWithAnnotation(annotation.java)
|
||||||
.filter { it.startsWith(qualifiedNamePrefix) }
|
.filter { it.startsWith(qualifiedNamePrefix) }
|
||||||
|
@ -63,10 +63,10 @@ fun NodeConfiguration.configureWithDevSSLCertificate() = configureDevKeyAndTrust
|
|||||||
fun SSLConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500Name) {
|
fun SSLConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500Name) {
|
||||||
certificatesDirectory.createDirectories()
|
certificatesDirectory.createDirectories()
|
||||||
if (!trustStoreFile.exists()) {
|
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()) {
|
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)
|
createKeystoreForCordaNode(sslKeystore, nodeKeystore, keyStorePassword, keyStorePassword, caKeyStore, "cordacadevkeypass", myLegalName)
|
||||||
|
|
||||||
// Move distributed service composite key (generated by ServiceIdentityGenerator.generateToDisk) to keystore if exists.
|
// 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.
|
// This is a sanity feature do not remove.
|
||||||
require(!useTestClock || devMode) { "Cannot use test clock outside of dev mode" }
|
require(!useTestClock || devMode) { "Cannot use test clock outside of dev mode" }
|
||||||
require(devModeOptions == null || devMode) { "Cannot use devModeOptions 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()) {
|
require(security == null || rpcUsers.isEmpty()) {
|
||||||
"Cannot specify both 'rpcUsers' and 'security' in configuration"
|
"Cannot specify both 'rpcUsers' and 'security' in configuration"
|
||||||
}
|
}
|
||||||
|
@ -69,7 +69,7 @@ class InMemoryIdentityService(identities: Iterable<PartyAndCertificate>,
|
|||||||
if (firstCertWithThisName != identity.certificate) {
|
if (firstCertWithThisName != identity.certificate) {
|
||||||
val certificates = identity.certPath.certificates
|
val certificates = identity.certPath.certificates
|
||||||
val idx = certificates.lastIndexOf(firstCertWithThisName)
|
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))
|
verifyAndRegisterIdentity(PartyAndCertificate(firstPath))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,7 +133,7 @@ class PersistentIdentityService(override val trustRoot: X509Certificate,
|
|||||||
if (firstCertWithThisName != identity.certificate) {
|
if (firstCertWithThisName != identity.certificate) {
|
||||||
val certificates = identity.certPath.certificates
|
val certificates = identity.certPath.certificates
|
||||||
val idx = certificates.lastIndexOf(firstCertWithThisName)
|
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))
|
verifyAndRegisterIdentity(PartyAndCertificate(firstPath))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ fun freshCertificate(identityService: IdentityServiceInternal,
|
|||||||
val window = X509Utilities.getCertificateValidityWindow(Duration.ZERO, 3650.days, issuerCert)
|
val window = X509Utilities.getCertificateValidityWindow(Duration.ZERO, 3650.days, issuerCert)
|
||||||
val ourCertificate = X509Utilities.createCertificate(CertificateType.WELL_KNOWN_IDENTITY, issuerCert.subject,
|
val ourCertificate = X509Utilities.createCertificate(CertificateType.WELL_KNOWN_IDENTITY, issuerCert.subject,
|
||||||
issuerSigner, issuer.name, subjectPublicKey, window)
|
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)
|
val anonymisedIdentity = PartyAndCertificate(ourCertPath)
|
||||||
identityService.justVerifyAndRegisterIdentity(anonymisedIdentity)
|
identityService.justVerifyAndRegisterIdentity(anonymisedIdentity)
|
||||||
return anonymisedIdentity
|
return anonymisedIdentity
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package net.corda.node.services.network
|
package net.corda.node.services.network
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
|
||||||
import com.google.common.util.concurrent.MoreExecutors
|
import com.google.common.util.concurrent.MoreExecutors
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.crypto.SignedData
|
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.core.utilities.seconds
|
||||||
import net.corda.node.services.api.NetworkMapCacheInternal
|
import net.corda.node.services.api.NetworkMapCacheInternal
|
||||||
import net.corda.node.utilities.NamedThreadFactory
|
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.CacheControl
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
@ -20,11 +23,12 @@ import java.io.BufferedReader
|
|||||||
import java.io.Closeable
|
import java.io.Closeable
|
||||||
import java.net.HttpURLConnection
|
import java.net.HttpURLConnection
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
import java.security.cert.X509Certificate
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class NetworkMapClient(compatibilityZoneURL: URL) {
|
class NetworkMapClient(compatibilityZoneURL: URL, private val trustedRoot: X509Certificate) {
|
||||||
private val networkMapUrl = URL("$compatibilityZoneURL/network-map")
|
private val networkMapUrl = URL("$compatibilityZoneURL/network-map")
|
||||||
|
|
||||||
fun publish(signedNodeInfo: SignedData<NodeInfo>) {
|
fun publish(signedNodeInfo: SignedData<NodeInfo>) {
|
||||||
@ -42,14 +46,30 @@ class NetworkMapClient(compatibilityZoneURL: URL) {
|
|||||||
|
|
||||||
fun getNetworkMap(): NetworkMapResponse {
|
fun getNetworkMap(): NetworkMapResponse {
|
||||||
val conn = networkMapUrl.openHttpConnection()
|
val conn = networkMapUrl.openHttpConnection()
|
||||||
val response = conn.inputStream.bufferedReader().use(BufferedReader::readLine)
|
val signedNetworkMap = conn.inputStream.use { it.readBytes() }.deserialize<SignedNetworkMap>()
|
||||||
val networkMap = ObjectMapper().readValue(response, List::class.java).map { SecureHash.parse(it.toString()) }
|
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
|
val timeout = CacheControl.parse(Headers.of(conn.headerFields.filterKeys { it != null }.mapValues { it.value.first() })).maxAgeSeconds().seconds
|
||||||
return NetworkMapResponse(networkMap, timeout)
|
return NetworkMapResponse(networkMap, timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getNodeInfo(nodeInfoHash: SecureHash): NodeInfo? {
|
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) {
|
return if (conn.responseCode == HttpURLConnection.HTTP_NOT_FOUND) {
|
||||||
null
|
null
|
||||||
} else {
|
} 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,
|
class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
|
||||||
private val fileWatcher: NodeInfoWatcher,
|
private val fileWatcher: NodeInfoWatcher,
|
||||||
@ -108,21 +128,28 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
|
|||||||
val nextScheduleDelay = try {
|
val nextScheduleDelay = try {
|
||||||
val (networkMap, cacheTimeout) = networkMapClient.getNetworkMap()
|
val (networkMap, cacheTimeout) = networkMapClient.getNetworkMap()
|
||||||
val currentNodeHashes = networkMapCache.allNodeHashes
|
val currentNodeHashes = networkMapCache.allNodeHashes
|
||||||
(networkMap - currentNodeHashes).mapNotNull {
|
val hashesFromNetworkMap = networkMap.nodeInfoHashes
|
||||||
|
(hashesFromNetworkMap - currentNodeHashes).mapNotNull {
|
||||||
// Download new node info from network map
|
// Download new node info from network map
|
||||||
networkMapClient.getNodeInfo(it)
|
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 {
|
}.forEach {
|
||||||
// Add new node info to the network map cache, these could be new node info or modification of node info for existing nodes.
|
// 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)
|
networkMapCache.addNode(it)
|
||||||
}
|
}
|
||||||
// Remove node info from network map.
|
// Remove node info from network map.
|
||||||
(currentNodeHashes - networkMap - fileWatcher.processedNodeInfoHashes)
|
(currentNodeHashes - hashesFromNetworkMap - fileWatcher.processedNodeInfoHashes)
|
||||||
.mapNotNull(networkMapCache::getNodeByHash)
|
.mapNotNull(networkMapCache::getNodeByHash)
|
||||||
.forEach(networkMapCache::removeNode)
|
.forEach(networkMapCache::removeNode)
|
||||||
|
// TODO: Check NetworkParameter.
|
||||||
cacheTimeout
|
cacheTimeout
|
||||||
} catch (t: Throwable) {
|
} 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
|
retryInterval
|
||||||
}
|
}
|
||||||
// Schedule the next update.
|
// Schedule the next update.
|
||||||
@ -138,7 +165,7 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
|
|||||||
try {
|
try {
|
||||||
networkMapClient.publish(signedNodeInfo)
|
networkMapClient.publish(signedNodeInfo)
|
||||||
} catch (t: Throwable) {
|
} 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?
|
// TODO: Exponential backoff?
|
||||||
executor.schedule(this, retryInterval.toMillis(), TimeUnit.MILLISECONDS)
|
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.serialization.serialize
|
||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.contextLogger
|
||||||
import net.corda.core.utilities.seconds
|
import net.corda.core.utilities.seconds
|
||||||
import net.corda.nodeapi.NodeInfoFilesCopier
|
import net.corda.nodeapi.internal.NodeInfoFilesCopier
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.Scheduler
|
import rx.Scheduler
|
||||||
import java.io.IOException
|
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.NodeInfo
|
||||||
import net.corda.core.node.services.IdentityService
|
import net.corda.core.node.services.IdentityService
|
||||||
import net.corda.core.node.services.NetworkMapCache.MapChange
|
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.node.services.PartyInfo
|
||||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||||
import net.corda.core.serialization.serialize
|
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.CordaPersistence
|
||||||
import net.corda.nodeapi.internal.persistence.bufferUntilDatabaseCommit
|
import net.corda.nodeapi.internal.persistence.bufferUntilDatabaseCommit
|
||||||
import net.corda.nodeapi.internal.persistence.wrapWithDatabaseTransaction
|
import net.corda.nodeapi.internal.persistence.wrapWithDatabaseTransaction
|
||||||
|
import net.corda.nodeapi.internal.NotaryInfo
|
||||||
import org.hibernate.Session
|
import org.hibernate.Session
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.subjects.PublishSubject
|
import rx.subjects.PublishSubject
|
||||||
@ -34,6 +34,7 @@ import java.sql.Connection
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.annotation.concurrent.ThreadSafe
|
import javax.annotation.concurrent.ThreadSafe
|
||||||
import kotlin.collections.HashMap
|
import kotlin.collections.HashMap
|
||||||
|
import kotlin.collections.HashSet
|
||||||
|
|
||||||
class NetworkMapCacheImpl(
|
class NetworkMapCacheImpl(
|
||||||
networkMapCacheBase: NetworkMapCacheBaseInternal,
|
networkMapCacheBase: NetworkMapCacheBaseInternal,
|
||||||
@ -72,13 +73,15 @@ class NetworkMapCacheImpl(
|
|||||||
* Extremely simple in-memory cache of the network map.
|
* Extremely simple in-memory cache of the network map.
|
||||||
*/
|
*/
|
||||||
@ThreadSafe
|
@ThreadSafe
|
||||||
open class PersistentNetworkMapCache(private val database: CordaPersistence) : SingletonSerializeAsToken(), NetworkMapCacheBaseInternal {
|
open class PersistentNetworkMapCache(
|
||||||
|
private val database: CordaPersistence,
|
||||||
|
notaries: List<NotaryInfo>
|
||||||
|
) : SingletonSerializeAsToken(), NetworkMapCacheBaseInternal {
|
||||||
companion object {
|
companion object {
|
||||||
private val logger = contextLogger()
|
private val logger = contextLogger()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Small explanation, partyNodes and registeredNodes is left in memory as it was before, because it will be removed in
|
// TODO Cleanup registered and party nodes
|
||||||
// next PR that gets rid of services. These maps are used only for queries by service.
|
|
||||||
protected val registeredNodes: MutableMap<PublicKey, NodeInfo> = Collections.synchronizedMap(HashMap())
|
protected val registeredNodes: MutableMap<PublicKey, NodeInfo> = Collections.synchronizedMap(HashMap())
|
||||||
protected val partyNodes: MutableList<NodeInfo> get() = registeredNodes.map { it.value }.toMutableList()
|
protected val partyNodes: MutableList<NodeInfo> get() = registeredNodes.map { it.value }.toMutableList()
|
||||||
private val _changed = PublishSubject.create<MapChange>()
|
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
|
override val nodeReady: CordaFuture<Void?> get() = _registrationFuture
|
||||||
private var _loadDBSuccess: Boolean = false
|
private var _loadDBSuccess: Boolean = false
|
||||||
override val loadDBSuccess get() = _loadDBSuccess
|
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.
|
override val notaryIdentities: List<Party> = notaries.map { it.identity }
|
||||||
// This should eliminate the only required usage of services.
|
private val validatingNotaries = notaries.mapNotNullTo(HashSet()) { if (it.validating) it.identity else null }
|
||||||
// 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() }
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
database.transaction { loadFromDB(session) }
|
database.transaction { loadFromDB(session) }
|
||||||
@ -122,7 +112,7 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence) : S
|
|||||||
select(get<String>(NodeInfoSchemaV1.PersistentNodeInfo::hash.name))
|
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? {
|
override fun getPartyInfo(party: Party): PartyInfo? {
|
||||||
val nodes = database.transaction { queryByIdentityKey(session, party.owningKey) }
|
val nodes = database.transaction { queryByIdentityKey(session, party.owningKey) }
|
||||||
@ -310,7 +300,6 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence) : S
|
|||||||
id = 0,
|
id = 0,
|
||||||
hash = nodeInfo.serialize().hash.toString(),
|
hash = nodeInfo.serialize().hash.toString(),
|
||||||
addresses = nodeInfo.addresses.map { NodeInfoSchemaV1.DBHostAndPort.fromHostAndPort(it) },
|
addresses = nodeInfo.addresses.map { NodeInfoSchemaV1.DBHostAndPort.fromHostAndPort(it) },
|
||||||
// TODO Another ugly hack with special first identity...
|
|
||||||
legalIdentitiesAndCerts = nodeInfo.legalIdentitiesAndCerts.mapIndexed { idx, elem ->
|
legalIdentitiesAndCerts = nodeInfo.legalIdentitiesAndCerts.mapIndexed { idx, elem ->
|
||||||
NodeInfoSchemaV1.DBPartyAndCertificate(elem, isMain = idx == 0)
|
NodeInfoSchemaV1.DBPartyAndCertificate(elem, isMain = idx == 0)
|
||||||
},
|
},
|
||||||
|
@ -239,4 +239,4 @@ private fun readMap(buffer: BufferInput<out BufferInput<*>>, serializer: Seriali
|
|||||||
put(serializer.readObject(buffer), serializer.readObject(buffer))
|
put(serializer.readObject(buffer), serializer.readObject(buffer))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
|
|||||||
import org.bouncycastle.openssl.jcajce.JcaPEMWriter
|
import org.bouncycastle.openssl.jcajce.JcaPEMWriter
|
||||||
import org.bouncycastle.util.io.pem.PemObject
|
import org.bouncycastle.util.io.pem.PemObject
|
||||||
import java.io.StringWriter
|
import java.io.StringWriter
|
||||||
|
import java.nio.file.Path
|
||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
import java.security.KeyStore
|
import java.security.KeyStore
|
||||||
import java.security.cert.Certificate
|
import java.security.cert.Certificate
|
||||||
@ -30,6 +31,23 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
|
|||||||
private val keystorePassword = config.keyStorePassword
|
private val keystorePassword = config.keyStorePassword
|
||||||
// TODO: Use different password for private key.
|
// TODO: Use different password for private key.
|
||||||
private val privateKeyPassword = config.keyStorePassword
|
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.
|
* 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.addOrReplaceKey(CORDA_CLIENT_CA, keyPair.private, privateKeyPassword.toCharArray(), certificates)
|
||||||
caKeyStore.deleteEntry(SELF_SIGNED_PRIVATE_KEY)
|
caKeyStore.deleteEntry(SELF_SIGNED_PRIVATE_KEY)
|
||||||
caKeyStore.save(config.nodeKeystore, keystorePassword)
|
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}.")
|
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.")
|
println("Generating SSL certificate for node messaging service.")
|
||||||
val sslKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
val sslKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||||
val caCert = caKeyStore.getX509Certificate(CORDA_CLIENT_CA).toX509CertHolder()
|
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.contracts.*;
|
||||||
import net.corda.core.crypto.CryptoUtils;
|
import net.corda.core.crypto.CryptoUtils;
|
||||||
import net.corda.core.identity.AbstractParty;
|
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.messaging.DataFeed;
|
||||||
import net.corda.core.node.services.Vault;
|
import net.corda.core.node.services.Vault;
|
||||||
import net.corda.core.node.services.VaultQueryException;
|
import net.corda.core.node.services.VaultQueryException;
|
||||||
@ -33,17 +36,19 @@ import rx.Observable;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
import java.math.BigInteger;
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.KeyPair;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
import java.util.stream.StreamSupport;
|
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.DEFAULT_PAGE_NUM;
|
||||||
import static net.corda.core.node.services.vault.QueryCriteriaUtils.MAX_PAGE_SIZE;
|
import static net.corda.core.node.services.vault.QueryCriteriaUtils.MAX_PAGE_SIZE;
|
||||||
import static net.corda.core.utilities.ByteArrays.toHexString;
|
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.CoreTestUtils.*;
|
||||||
import static net.corda.testing.TestConstants.*;
|
import static net.corda.testing.TestConstants.*;
|
||||||
import static net.corda.testing.node.MockServices.makeTestDatabaseAndMockServices;
|
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;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
public class VaultQueryJavaTests {
|
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
|
@Rule
|
||||||
public final SerializationEnvironmentRule testSerialization = new SerializationEnvironmentRule();
|
public final SerializationEnvironmentRule testSerialization = new SerializationEnvironmentRule();
|
||||||
private VaultFiller vaultFiller;
|
private VaultFiller vaultFiller;
|
||||||
@ -61,13 +70,13 @@ public class VaultQueryJavaTests {
|
|||||||
@Before
|
@Before
|
||||||
public void setUp() throws CertificateException, InvalidAlgorithmParameterException {
|
public void setUp() throws CertificateException, InvalidAlgorithmParameterException {
|
||||||
List<String> cordappPackages = Arrays.asList("net.corda.testing.contracts", "net.corda.finance.contracts.asset", CashSchemaV1.class.getPackage().getName());
|
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(
|
Pair<CordaPersistence, MockServices> databaseAndServices = makeTestDatabaseAndMockServices(
|
||||||
Arrays.asList(getMEGA_CORP_KEY(), getDUMMY_NOTARY_KEY()),
|
Arrays.asList(getMEGA_CORP_KEY(), getDUMMY_NOTARY_KEY()),
|
||||||
identitySvc,
|
identitySvc,
|
||||||
cordappPackages,
|
cordappPackages,
|
||||||
getMEGA_CORP().getName());
|
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();
|
database = databaseAndServices.getFirst();
|
||||||
MockServices services = databaseAndServices.getSecond();
|
MockServices services = databaseAndServices.getSecond();
|
||||||
vaultFiller = new VaultFiller(services, getDUMMY_NOTARY(), getDUMMY_NOTARY_KEY());
|
vaultFiller = new VaultFiller(services, getDUMMY_NOTARY(), getDUMMY_NOTARY_KEY());
|
||||||
@ -138,7 +147,7 @@ public class VaultQueryJavaTests {
|
|||||||
new Amount<>(100, Currency.getInstance("USD")),
|
new Amount<>(100, Currency.getInstance("USD")),
|
||||||
issuerServices,
|
issuerServices,
|
||||||
3,
|
3,
|
||||||
getDUMMY_CASH_ISSUER(),
|
DUMMY_CASH_ISSUER,
|
||||||
null,
|
null,
|
||||||
new Random());
|
new Random());
|
||||||
return tx;
|
return tx;
|
||||||
@ -213,10 +222,10 @@ public class VaultQueryJavaTests {
|
|||||||
Amount<Currency> dollars100 = new Amount<>(100, Currency.getInstance("USD"));
|
Amount<Currency> dollars100 = new Amount<>(100, Currency.getInstance("USD"));
|
||||||
Amount<Currency> dollars10 = new Amount<>(10, Currency.getInstance("USD"));
|
Amount<Currency> dollars10 = new Amount<>(10, Currency.getInstance("USD"));
|
||||||
Amount<Currency> dollars1 = new Amount<>(1, Currency.getInstance("USD"));
|
Amount<Currency> dollars1 = new Amount<>(1, Currency.getInstance("USD"));
|
||||||
vaultFiller.fillWithSomeTestCash(pounds, issuerServices, 1, getDUMMY_CASH_ISSUER());
|
vaultFiller.fillWithSomeTestCash(pounds, issuerServices, 1, DUMMY_CASH_ISSUER);
|
||||||
vaultFiller.fillWithSomeTestCash(dollars100, issuerServices, 1, getDUMMY_CASH_ISSUER());
|
vaultFiller.fillWithSomeTestCash(dollars100, issuerServices, 1, DUMMY_CASH_ISSUER);
|
||||||
vaultFiller.fillWithSomeTestCash(dollars10, issuerServices, 1, getDUMMY_CASH_ISSUER());
|
vaultFiller.fillWithSomeTestCash(dollars10, issuerServices, 1, DUMMY_CASH_ISSUER);
|
||||||
vaultFiller.fillWithSomeTestCash(dollars1, issuerServices, 1, getDUMMY_CASH_ISSUER());
|
vaultFiller.fillWithSomeTestCash(dollars1, issuerServices, 1, DUMMY_CASH_ISSUER);
|
||||||
return tx;
|
return tx;
|
||||||
});
|
});
|
||||||
database.transaction(tx -> {
|
database.transaction(tx -> {
|
||||||
@ -257,7 +266,7 @@ public class VaultQueryJavaTests {
|
|||||||
new Amount<>(100, Currency.getInstance("USD")),
|
new Amount<>(100, Currency.getInstance("USD")),
|
||||||
issuerServices,
|
issuerServices,
|
||||||
3,
|
3,
|
||||||
getDUMMY_CASH_ISSUER(),
|
DUMMY_CASH_ISSUER,
|
||||||
null,
|
null,
|
||||||
new Random());
|
new Random());
|
||||||
return tx;
|
return tx;
|
||||||
@ -331,11 +340,11 @@ public class VaultQueryJavaTests {
|
|||||||
Amount<Currency> dollars300 = new Amount<>(300, Currency.getInstance("USD"));
|
Amount<Currency> dollars300 = new Amount<>(300, Currency.getInstance("USD"));
|
||||||
Amount<Currency> pounds = new Amount<>(400, Currency.getInstance("GBP"));
|
Amount<Currency> pounds = new Amount<>(400, Currency.getInstance("GBP"));
|
||||||
Amount<Currency> swissfrancs = new Amount<>(500, Currency.getInstance("CHF"));
|
Amount<Currency> swissfrancs = new Amount<>(500, Currency.getInstance("CHF"));
|
||||||
vaultFiller.fillWithSomeTestCash(dollars100, issuerServices, 1, getDUMMY_CASH_ISSUER());
|
vaultFiller.fillWithSomeTestCash(dollars100, issuerServices, 1, DUMMY_CASH_ISSUER);
|
||||||
vaultFiller.fillWithSomeTestCash(dollars200, issuerServices, 2, getDUMMY_CASH_ISSUER());
|
vaultFiller.fillWithSomeTestCash(dollars200, issuerServices, 2, DUMMY_CASH_ISSUER);
|
||||||
vaultFiller.fillWithSomeTestCash(dollars300, issuerServices, 3, getDUMMY_CASH_ISSUER());
|
vaultFiller.fillWithSomeTestCash(dollars300, issuerServices, 3, DUMMY_CASH_ISSUER);
|
||||||
vaultFiller.fillWithSomeTestCash(pounds, issuerServices, 4, getDUMMY_CASH_ISSUER());
|
vaultFiller.fillWithSomeTestCash(pounds, issuerServices, 4, DUMMY_CASH_ISSUER);
|
||||||
vaultFiller.fillWithSomeTestCash(swissfrancs, issuerServices, 5, getDUMMY_CASH_ISSUER());
|
vaultFiller.fillWithSomeTestCash(swissfrancs, issuerServices, 5, DUMMY_CASH_ISSUER);
|
||||||
return tx;
|
return tx;
|
||||||
});
|
});
|
||||||
database.transaction(tx -> {
|
database.transaction(tx -> {
|
||||||
@ -377,11 +386,11 @@ public class VaultQueryJavaTests {
|
|||||||
Amount<Currency> dollars300 = new Amount<>(300, Currency.getInstance("USD"));
|
Amount<Currency> dollars300 = new Amount<>(300, Currency.getInstance("USD"));
|
||||||
Amount<Currency> pounds = new Amount<>(400, Currency.getInstance("GBP"));
|
Amount<Currency> pounds = new Amount<>(400, Currency.getInstance("GBP"));
|
||||||
Amount<Currency> swissfrancs = new Amount<>(500, Currency.getInstance("CHF"));
|
Amount<Currency> swissfrancs = new Amount<>(500, Currency.getInstance("CHF"));
|
||||||
vaultFiller.fillWithSomeTestCash(dollars100, issuerServices, 1, getDUMMY_CASH_ISSUER());
|
vaultFiller.fillWithSomeTestCash(dollars100, issuerServices, 1, DUMMY_CASH_ISSUER);
|
||||||
vaultFiller.fillWithSomeTestCash(dollars200, issuerServices, 2, getDUMMY_CASH_ISSUER());
|
vaultFiller.fillWithSomeTestCash(dollars200, issuerServices, 2, DUMMY_CASH_ISSUER);
|
||||||
vaultFiller.fillWithSomeTestCash(dollars300, issuerServices, 3, getDUMMY_CASH_ISSUER());
|
vaultFiller.fillWithSomeTestCash(dollars300, issuerServices, 3, DUMMY_CASH_ISSUER);
|
||||||
vaultFiller.fillWithSomeTestCash(pounds, issuerServices, 4, getDUMMY_CASH_ISSUER());
|
vaultFiller.fillWithSomeTestCash(pounds, issuerServices, 4, DUMMY_CASH_ISSUER);
|
||||||
vaultFiller.fillWithSomeTestCash(swissfrancs, issuerServices, 5, getDUMMY_CASH_ISSUER());
|
vaultFiller.fillWithSomeTestCash(swissfrancs, issuerServices, 5, DUMMY_CASH_ISSUER);
|
||||||
return tx;
|
return tx;
|
||||||
});
|
});
|
||||||
database.transaction(tx -> {
|
database.transaction(tx -> {
|
||||||
@ -438,9 +447,9 @@ public class VaultQueryJavaTests {
|
|||||||
Amount<Currency> dollars200 = new Amount<>(200, Currency.getInstance("USD"));
|
Amount<Currency> dollars200 = new Amount<>(200, Currency.getInstance("USD"));
|
||||||
Amount<Currency> pounds300 = new Amount<>(300, Currency.getInstance("GBP"));
|
Amount<Currency> pounds300 = new Amount<>(300, Currency.getInstance("GBP"));
|
||||||
Amount<Currency> pounds400 = new Amount<>(400, 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(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())));
|
vaultFiller.fillWithSomeTestCash(pounds400, issuerServices, 4, getBOC().ref(new OpaqueBytes("1".getBytes())));
|
||||||
return tx;
|
return tx;
|
||||||
});
|
});
|
||||||
@ -460,13 +469,13 @@ public class VaultQueryJavaTests {
|
|||||||
assertThat(results.getOtherResults().get(1)).isEqualTo(CryptoUtils.toStringShort(getBOC_PUBKEY()));
|
assertThat(results.getOtherResults().get(1)).isEqualTo(CryptoUtils.toStringShort(getBOC_PUBKEY()));
|
||||||
assertThat(results.getOtherResults().get(2)).isEqualTo("GBP");
|
assertThat(results.getOtherResults().get(2)).isEqualTo("GBP");
|
||||||
assertThat(results.getOtherResults().get(3)).isEqualTo(300L);
|
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(5)).isEqualTo("GBP");
|
||||||
assertThat(results.getOtherResults().get(6)).isEqualTo(200L);
|
assertThat(results.getOtherResults().get(6)).isEqualTo(200L);
|
||||||
assertThat(results.getOtherResults().get(7)).isEqualTo(CryptoUtils.toStringShort(getBOC_PUBKEY()));
|
assertThat(results.getOtherResults().get(7)).isEqualTo(CryptoUtils.toStringShort(getBOC_PUBKEY()));
|
||||||
assertThat(results.getOtherResults().get(8)).isEqualTo("USD");
|
assertThat(results.getOtherResults().get(8)).isEqualTo("USD");
|
||||||
assertThat(results.getOtherResults().get(9)).isEqualTo(100L);
|
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");
|
assertThat(results.getOtherResults().get(11)).isEqualTo("USD");
|
||||||
|
|
||||||
} catch (NoSuchFieldException e) {
|
} catch (NoSuchFieldException e) {
|
||||||
|
@ -94,7 +94,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
|
|||||||
// allow interruption half way through.
|
// allow interruption half way through.
|
||||||
mockNet = MockNetwork(threadPerNode = true, cordappPackages = cordappPackages)
|
mockNet = MockNetwork(threadPerNode = true, cordappPackages = cordappPackages)
|
||||||
val ledgerIdentityService = rigorousMock<IdentityServiceInternal>()
|
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 notaryNode = mockNet.defaultNotaryNode
|
||||||
val aliceNode = mockNet.createPartyNode(ALICE_NAME)
|
val aliceNode = mockNet.createPartyNode(ALICE_NAME)
|
||||||
val bobNode = mockNet.createPartyNode(BOB_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`() {
|
fun `trade cash for commercial paper fails using soft locking`() {
|
||||||
mockNet = MockNetwork(threadPerNode = true, cordappPackages = cordappPackages)
|
mockNet = MockNetwork(threadPerNode = true, cordappPackages = cordappPackages)
|
||||||
val ledgerIdentityService = rigorousMock<IdentityServiceInternal>()
|
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 notaryNode = mockNet.defaultNotaryNode
|
||||||
val aliceNode = mockNet.createPartyNode(ALICE_NAME)
|
val aliceNode = mockNet.createPartyNode(ALICE_NAME)
|
||||||
val bobNode = mockNet.createPartyNode(BOB_NAME)
|
val bobNode = mockNet.createPartyNode(BOB_NAME)
|
||||||
@ -204,7 +204,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
|
|||||||
fun `shutdown and restore`() {
|
fun `shutdown and restore`() {
|
||||||
mockNet = MockNetwork(cordappPackages = cordappPackages)
|
mockNet = MockNetwork(cordappPackages = cordappPackages)
|
||||||
val ledgerIdentityService = rigorousMock<IdentityServiceInternal>()
|
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 notaryNode = mockNet.defaultNotaryNode
|
||||||
val aliceNode = mockNet.createPartyNode(ALICE_NAME)
|
val aliceNode = mockNet.createPartyNode(ALICE_NAME)
|
||||||
var bobNode = mockNet.createPartyNode(BOB_NAME)
|
var bobNode = mockNet.createPartyNode(BOB_NAME)
|
||||||
@ -325,9 +325,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
|
|||||||
val bob = bobNode.info.singleIdentity()
|
val bob = bobNode.info.singleIdentity()
|
||||||
val bank = bankNode.info.singleIdentity()
|
val bank = bankNode.info.singleIdentity()
|
||||||
val issuer = bank.ref(1, 2, 3)
|
val issuer = bank.ref(1, 2, 3)
|
||||||
|
aliceNode.services.ledger(DUMMY_NOTARY) {
|
||||||
ledger(aliceNode.services) {
|
|
||||||
|
|
||||||
// Insert a prospectus type attachment into the commercial paper transaction.
|
// Insert a prospectus type attachment into the commercial paper transaction.
|
||||||
val stream = ByteArrayOutputStream()
|
val stream = ByteArrayOutputStream()
|
||||||
JarOutputStream(stream).use {
|
JarOutputStream(stream).use {
|
||||||
@ -431,8 +429,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
|
|||||||
val bank: Party = bankNode.info.singleIdentity()
|
val bank: Party = bankNode.info.singleIdentity()
|
||||||
val bob = bobNode.info.singleIdentity()
|
val bob = bobNode.info.singleIdentity()
|
||||||
val issuer = bank.ref(1, 2, 3)
|
val issuer = bank.ref(1, 2, 3)
|
||||||
|
aliceNode.services.ledger(DUMMY_NOTARY) {
|
||||||
ledger(aliceNode.services) {
|
|
||||||
// Insert a prospectus type attachment into the commercial paper transaction.
|
// Insert a prospectus type attachment into the commercial paper transaction.
|
||||||
val stream = ByteArrayOutputStream()
|
val stream = ByteArrayOutputStream()
|
||||||
JarOutputStream(stream).use {
|
JarOutputStream(stream).use {
|
||||||
@ -501,7 +498,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
|
|||||||
fun `dependency with error on buyer side`() {
|
fun `dependency with error on buyer side`() {
|
||||||
mockNet = MockNetwork(cordappPackages = cordappPackages)
|
mockNet = MockNetwork(cordappPackages = cordappPackages)
|
||||||
val ledgerIdentityService = rigorousMock<IdentityServiceInternal>()
|
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")
|
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`() {
|
fun `dependency with error on seller side`() {
|
||||||
mockNet = MockNetwork(cordappPackages = cordappPackages)
|
mockNet = MockNetwork(cordappPackages = cordappPackages)
|
||||||
val ledgerIdentityService = rigorousMock<IdentityServiceInternal>()
|
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")
|
runWithError(ledgerIdentityService, false, true, "Issuances have a time-window")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,10 +28,6 @@ import kotlin.test.assertEquals
|
|||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
class NotaryChangeTests {
|
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 mockNet: MockNetwork
|
||||||
private lateinit var oldNotaryNode: StartedNode<MockNetwork.MockNode>
|
private lateinit var oldNotaryNode: StartedNode<MockNetwork.MockNode>
|
||||||
private lateinit var clientNodeA: StartedNode<MockNetwork.MockNode>
|
private lateinit var clientNodeA: StartedNode<MockNetwork.MockNode>
|
||||||
@ -42,7 +38,7 @@ class NotaryChangeTests {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
val oldNotaryName = DUMMY_NOTARY.name.copy(organisation = "Old Dummy Notary")
|
val oldNotaryName = DUMMY_REGULATOR.name
|
||||||
mockNet = MockNetwork(
|
mockNet = MockNetwork(
|
||||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY.name), NotarySpec(oldNotaryName)),
|
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY.name), NotarySpec(oldNotaryName)),
|
||||||
cordappPackages = listOf("net.corda.testing.contracts")
|
cordappPackages = listOf("net.corda.testing.contracts")
|
||||||
@ -51,8 +47,8 @@ class NotaryChangeTests {
|
|||||||
clientNodeB = mockNet.createNode(MockNodeParameters(legalName = BOB_NAME))
|
clientNodeB = mockNet.createNode(MockNodeParameters(legalName = BOB_NAME))
|
||||||
clientA = clientNodeA.info.singleIdentity()
|
clientA = clientNodeA.info.singleIdentity()
|
||||||
oldNotaryNode = mockNet.notaryNodes[1]
|
oldNotaryNode = mockNet.notaryNodes[1]
|
||||||
newNotaryParty = clientNodeA.services.networkMapCache.getNotary(DUMMY_NOTARY_SERVICE_NAME)!!
|
newNotaryParty = clientNodeA.services.networkMapCache.getNotary(DUMMY_NOTARY.name)!!
|
||||||
oldNotaryParty = clientNodeA.services.networkMapCache.getNotary(DUMMY_NOTARY_SERVICE_NAME.copy(organisation = "Old Dummy Notary"))!!
|
oldNotaryParty = clientNodeA.services.networkMapCache.getNotary(oldNotaryName)!!
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
|
@ -161,7 +161,7 @@ class InMemoryIdentityServiceTests {
|
|||||||
val issuer = getTestPartyAndCertificate(x500Name, issuerKeyPair.public)
|
val issuer = getTestPartyAndCertificate(x500Name, issuerKeyPair.public)
|
||||||
val txKey = Crypto.generateKeyPair()
|
val txKey = Crypto.generateKeyPair()
|
||||||
val txCert = X509Utilities.createCertificate(CertificateType.CONFIDENTIAL_IDENTITY, issuer.certificate.toX509CertHolder(), issuerKeyPair, x500Name, txKey.public)
|
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))
|
return Pair(issuer, PartyAndCertificate(txCertPath))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -256,7 +256,7 @@ class PersistentIdentityServiceTests {
|
|||||||
val issuer = getTestPartyAndCertificate(x500Name, issuerKeyPair.public)
|
val issuer = getTestPartyAndCertificate(x500Name, issuerKeyPair.public)
|
||||||
val txKey = Crypto.generateKeyPair()
|
val txKey = Crypto.generateKeyPair()
|
||||||
val txCert = X509Utilities.createCertificate(CertificateType.CONFIDENTIAL_IDENTITY, issuer.certificate.toX509CertHolder(), issuerKeyPair, x500Name, txKey.public)
|
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))
|
return Pair(issuer, PartyAndCertificate(txCertPath))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ class ArtemisMessagingTests {
|
|||||||
myLegalName = ALICE.name)
|
myLegalName = ALICE.name)
|
||||||
LogHelper.setLevel(PersistentUniquenessProvider::class)
|
LogHelper.setLevel(PersistentUniquenessProvider::class)
|
||||||
database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), rigorousMock())
|
database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), rigorousMock())
|
||||||
networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database), rigorousMock())
|
networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, emptyList()), rigorousMock())
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
|
@ -1,87 +1,48 @@
|
|||||||
package net.corda.node.services.network
|
package net.corda.node.services.network
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.crypto.*
|
import net.corda.core.crypto.sha256
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.internal.cert
|
||||||
import net.corda.core.identity.PartyAndCertificate
|
|
||||||
import net.corda.core.node.NodeInfo
|
|
||||||
import net.corda.core.serialization.deserialize
|
|
||||||
import net.corda.core.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
|
||||||
import net.corda.core.utilities.seconds
|
import net.corda.core.utilities.seconds
|
||||||
import net.corda.node.services.network.TestNodeInfoFactory.createNodeInfo
|
import net.corda.node.services.network.TestNodeInfoFactory.createNodeInfo
|
||||||
import net.corda.nodeapi.internal.crypto.CertificateType
|
import net.corda.testing.DEV_CA
|
||||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
import net.corda.testing.DEV_TRUST_ROOT
|
||||||
|
import net.corda.testing.ROOT_CA
|
||||||
import net.corda.testing.SerializationEnvironmentRule
|
import net.corda.testing.SerializationEnvironmentRule
|
||||||
|
import net.corda.testing.driver.PortAllocation
|
||||||
|
import net.corda.testing.node.network.NetworkMapServer
|
||||||
import net.corda.testing.TestDependencyInjectionBase
|
import net.corda.testing.TestDependencyInjectionBase
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
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.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.io.ByteArrayInputStream
|
|
||||||
import java.io.InputStream
|
|
||||||
import java.net.InetSocketAddress
|
|
||||||
import java.net.URL
|
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.assertEquals
|
||||||
|
import kotlin.test.assertNotNull
|
||||||
|
|
||||||
class NetworkMapClientTest {
|
class NetworkMapClientTest {
|
||||||
@Rule
|
@Rule
|
||||||
@JvmField
|
@JvmField
|
||||||
val testSerialization = SerializationEnvironmentRule(true)
|
val testSerialization = SerializationEnvironmentRule(true)
|
||||||
private lateinit var server: Server
|
private lateinit var server: NetworkMapServer
|
||||||
|
|
||||||
private lateinit var networkMapClient: NetworkMapClient
|
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)
|
companion object {
|
||||||
private val intermediateCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
private val cacheTimeout = 100000.seconds
|
||||||
private val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootCAKey, X500Name("CN=Corda Node Intermediate CA,L=London"), intermediateCAKey.public)
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
server = Server(InetSocketAddress("localhost", 0)).apply {
|
server = NetworkMapServer(cacheTimeout, PortAllocation.Incremental(10000).nextHostAndPort())
|
||||||
handler = HandlerCollection().apply {
|
val hostAndPort = server.start()
|
||||||
addHandler(ServletContextHandler().apply {
|
networkMapClient = NetworkMapClient(URL("http://${hostAndPort.host}:${hostAndPort.port}"), DEV_TRUST_ROOT.cert)
|
||||||
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}"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
fun tearDown() {
|
fun tearDown() {
|
||||||
server.stop()
|
server.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -94,7 +55,7 @@ class NetworkMapClientTest {
|
|||||||
|
|
||||||
val nodeInfoHash = nodeInfo.serialize().sha256()
|
val nodeInfoHash = nodeInfo.serialize().sha256()
|
||||||
|
|
||||||
assertThat(networkMapClient.getNetworkMap().networkMap).containsExactly(nodeInfoHash)
|
assertThat(networkMapClient.getNetworkMap().networkMap.nodeInfoHashes).containsExactly(nodeInfoHash)
|
||||||
assertEquals(nodeInfo, networkMapClient.getNodeInfo(nodeInfoHash))
|
assertEquals(nodeInfo, networkMapClient.getNodeInfo(nodeInfoHash))
|
||||||
|
|
||||||
val signedNodeInfo2 = createNodeInfo("Test2")
|
val signedNodeInfo2 = createNodeInfo("Test2")
|
||||||
@ -102,53 +63,22 @@ class NetworkMapClientTest {
|
|||||||
networkMapClient.publish(signedNodeInfo2)
|
networkMapClient.publish(signedNodeInfo2)
|
||||||
|
|
||||||
val nodeInfoHash2 = nodeInfo2.serialize().sha256()
|
val nodeInfoHash2 = nodeInfo2.serialize().sha256()
|
||||||
assertThat(networkMapClient.getNetworkMap().networkMap).containsExactly(nodeInfoHash, nodeInfoHash2)
|
assertThat(networkMapClient.getNetworkMap().networkMap.nodeInfoHashes).containsExactly(nodeInfoHash, nodeInfoHash2)
|
||||||
assertEquals(100000.seconds, networkMapClient.getNetworkMap().cacheMaxAge)
|
assertEquals(cacheTimeout, networkMapClient.getNetworkMap().cacheMaxAge)
|
||||||
assertEquals(nodeInfo2, networkMapClient.getNodeInfo(nodeInfoHash2))
|
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
|
@Test
|
||||||
fun `get hostname string from http response correctly`() {
|
fun `get hostname string from http response correctly`() {
|
||||||
assertEquals("test.host.name", networkMapClient.myPublicHostname())
|
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.identity.Party
|
||||||
import net.corda.core.internal.div
|
import net.corda.core.internal.div
|
||||||
import net.corda.core.internal.uncheckedCast
|
import net.corda.core.internal.uncheckedCast
|
||||||
|
import net.corda.nodeapi.internal.NetworkMap
|
||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
import net.corda.core.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
@ -95,7 +96,7 @@ class NetworkMapUpdaterTest {
|
|||||||
val signedNodeInfo: SignedData<NodeInfo> = uncheckedCast(it.arguments.first())
|
val signedNodeInfo: SignedData<NodeInfo> = uncheckedCast(it.arguments.first())
|
||||||
nodeInfoMap.put(signedNodeInfo.verified().serialize().hash, signedNodeInfo)
|
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() }
|
on { getNodeInfo(any()) }.then { nodeInfoMap[it.arguments.first()]?.verified() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,7 +150,7 @@ class NetworkMapUpdaterTest {
|
|||||||
val signedNodeInfo: SignedData<NodeInfo> = uncheckedCast(it.arguments.first())
|
val signedNodeInfo: SignedData<NodeInfo> = uncheckedCast(it.arguments.first())
|
||||||
nodeInfoMap.put(signedNodeInfo.verified().serialize().hash, signedNodeInfo)
|
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() }
|
on { getNodeInfo(any()) }.then { nodeInfoMap[it.arguments.first()]?.verified() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ object TestNodeInfoFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun buildCertPath(vararg certificates: Certificate): CertPath {
|
private fun buildCertPath(vararg certificates: Certificate): CertPath {
|
||||||
return X509CertificateFactory().delegate.generateCertPath(certificates.asList())
|
return X509CertificateFactory().generateCertPath(*certificates)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun X509CertificateHolder.toX509Certificate(): X509Certificate {
|
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.StateRef
|
||||||
import net.corda.core.contracts.TransactionState
|
import net.corda.core.contracts.TransactionState
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
|
import net.corda.core.crypto.entropyToKeyPair
|
||||||
import net.corda.core.crypto.generateKeyPair
|
import net.corda.core.crypto.generateKeyPair
|
||||||
import net.corda.core.identity.AbstractParty
|
import net.corda.core.identity.AbstractParty
|
||||||
|
import net.corda.core.identity.CordaX500Name
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.node.StatesToRecord
|
import net.corda.core.node.StatesToRecord
|
||||||
import net.corda.core.node.services.IdentityService
|
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.POUNDS
|
||||||
import net.corda.finance.SWISS_FRANCS
|
import net.corda.finance.SWISS_FRANCS
|
||||||
import net.corda.finance.contracts.asset.Cash
|
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.contracts.asset.DummyFungibleContract
|
||||||
import net.corda.finance.schemas.CashSchemaV1
|
import net.corda.finance.schemas.CashSchemaV1
|
||||||
import net.corda.finance.schemas.SampleCashSchemaV2
|
import net.corda.finance.schemas.SampleCashSchemaV2
|
||||||
@ -48,6 +48,7 @@ import org.assertj.core.api.Assertions.assertThat
|
|||||||
import org.hibernate.SessionFactory
|
import org.hibernate.SessionFactory
|
||||||
import org.junit.*
|
import org.junit.*
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
import java.math.BigInteger
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.persistence.EntityManager
|
import javax.persistence.EntityManager
|
||||||
@ -55,6 +56,12 @@ import javax.persistence.Tuple
|
|||||||
import javax.persistence.criteria.CriteriaBuilder
|
import javax.persistence.criteria.CriteriaBuilder
|
||||||
|
|
||||||
class HibernateConfigurationTest {
|
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
|
@Rule
|
||||||
@JvmField
|
@JvmField
|
||||||
val testSerialization = SerializationEnvironmentRule()
|
val testSerialization = SerializationEnvironmentRule()
|
||||||
@ -91,7 +98,7 @@ class HibernateConfigurationTest {
|
|||||||
val dataSourceProps = makeTestDataSourceProperties()
|
val dataSourceProps = makeTestDataSourceProperties()
|
||||||
val identityService = rigorousMock<IdentityService>().also { mock ->
|
val identityService = rigorousMock<IdentityService>().also { mock ->
|
||||||
doReturn(null).whenever(mock).wellKnownPartyFromAnonymous(any<AbstractParty>())
|
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).wellKnownPartyFromAnonymous(it)
|
||||||
doReturn(it).whenever(mock).wellKnownPartyFromX500Name(it.name)
|
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.argThat
|
||||||
import com.nhaarman.mockito_kotlin.doNothing
|
import com.nhaarman.mockito_kotlin.doNothing
|
||||||
import com.nhaarman.mockito_kotlin.whenever
|
import com.nhaarman.mockito_kotlin.whenever
|
||||||
import net.corda.core.contracts.Amount
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.contracts.Issued
|
|
||||||
import net.corda.core.contracts.StateAndRef
|
|
||||||
import net.corda.core.contracts.StateRef
|
|
||||||
import net.corda.core.crypto.NullKeys
|
import net.corda.core.crypto.NullKeys
|
||||||
|
import net.corda.core.crypto.entropyToKeyPair
|
||||||
import net.corda.core.crypto.generateKeyPair
|
import net.corda.core.crypto.generateKeyPair
|
||||||
import net.corda.core.identity.AbstractParty
|
import net.corda.core.identity.*
|
||||||
import net.corda.core.identity.AnonymousParty
|
|
||||||
import net.corda.core.identity.Party
|
|
||||||
import net.corda.core.identity.PartyAndCertificate
|
|
||||||
import net.corda.core.internal.packageName
|
import net.corda.core.internal.packageName
|
||||||
import net.corda.core.node.StatesToRecord
|
import net.corda.core.node.StatesToRecord
|
||||||
import net.corda.core.node.services.StatesNotAvailableException
|
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.core.utilities.toNonEmptySet
|
||||||
import net.corda.finance.*
|
import net.corda.finance.*
|
||||||
import net.corda.finance.contracts.asset.Cash
|
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.contracts.getCashBalance
|
||||||
import net.corda.finance.schemas.CashSchemaV1
|
import net.corda.finance.schemas.CashSchemaV1
|
||||||
import net.corda.finance.utils.sumCash
|
import net.corda.finance.utils.sumCash
|
||||||
@ -51,6 +43,7 @@ import org.junit.Rule
|
|||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import rx.observers.TestSubscriber
|
import rx.observers.TestSubscriber
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
import java.math.BigInteger
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
@ -59,8 +52,12 @@ import kotlin.test.assertFalse
|
|||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
class NodeVaultServiceTest {
|
class NodeVaultServiceTest {
|
||||||
companion object {
|
private companion object {
|
||||||
private val cordappPackages = listOf("net.corda.finance.contracts.asset", CashSchemaV1::class.packageName)
|
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
|
@Rule
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user