mirror of
https://github.com/corda/corda.git
synced 2025-05-02 08:43:15 +00:00
Merge branch 'master' into shams-master-merge-291117
# Conflicts: # node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt # node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt # node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt # samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt # testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt # testing/node-driver/src/main/kotlin/net/corda/testing/internal/demorun/DemoRunner.kt # testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt
This commit is contained in:
commit
71763ff1d3
@ -1,15 +0,0 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="BankOfCordaDriverKt - Issue Web" type="JetRunConfigurationType" factoryName="Kotlin">
|
|
||||||
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
|
||||||
<option name="MAIN_CLASS_NAME" value="net.corda.bank.BankOfCordaDriverKt" />
|
|
||||||
<option name="VM_PARAMETERS" value="" />
|
|
||||||
<option name="PROGRAM_PARAMETERS" value="--role ISSUE_CASH_WEB --quantity 100 --currency USD" />
|
|
||||||
<option name="WORKING_DIRECTORY" value="" />
|
|
||||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
|
||||||
<option name="ALTERNATIVE_JRE_PATH" />
|
|
||||||
<option name="PASS_PARENT_ENVS" value="true" />
|
|
||||||
<module name="bank-of-corda-demo_main" />
|
|
||||||
<envs />
|
|
||||||
<method />
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
@ -1,15 +0,0 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="BankOfCordaDriverKt - Run Stack" type="JetRunConfigurationType" factoryName="Kotlin">
|
|
||||||
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
|
||||||
<option name="MAIN_CLASS_NAME" value="net.corda.bank.BankOfCordaDriverKt" />
|
|
||||||
<option name="VM_PARAMETERS" value="" />
|
|
||||||
<option name="PROGRAM_PARAMETERS" value="--role ISSUER" />
|
|
||||||
<option name="WORKING_DIRECTORY" value="" />
|
|
||||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
|
||||||
<option name="ALTERNATIVE_JRE_PATH" />
|
|
||||||
<option name="PASS_PARENT_ENVS" value="true" />
|
|
||||||
<module name="bank-of-corda-demo_main" />
|
|
||||||
<envs />
|
|
||||||
<method />
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
@ -6,8 +6,7 @@ buildscript {
|
|||||||
// Our version: bump this on release.
|
// Our version: bump this on release.
|
||||||
ext.corda_release_version = "3.0-NETWORKMAP-SNAPSHOT"
|
ext.corda_release_version = "3.0-NETWORKMAP-SNAPSHOT"
|
||||||
// Increment this on any release that changes public APIs anywhere in the Corda platform
|
// Increment this on any release that changes public APIs anywhere in the Corda platform
|
||||||
// TODO This is going to be difficult until we have a clear separation throughout the code of what is public and what is internal
|
ext.corda_platform_version = constants.getProperty("platformVersion")
|
||||||
ext.corda_platform_version = 2
|
|
||||||
ext.gradle_plugins_version = constants.getProperty("gradlePluginsVersion")
|
ext.gradle_plugins_version = constants.getProperty("gradlePluginsVersion")
|
||||||
|
|
||||||
// Dependency versions. Can run 'gradle dependencyUpdates' to find new versions of things.
|
// Dependency versions. Can run 'gradle dependencyUpdates' to find new versions of things.
|
||||||
@ -49,6 +48,7 @@ buildscript {
|
|||||||
ext.commons_collections_version = '4.1'
|
ext.commons_collections_version = '4.1'
|
||||||
ext.beanutils_version = '1.9.3'
|
ext.beanutils_version = '1.9.3'
|
||||||
ext.crash_version = 'faba68332800f21278c5b600bf14ad55cef5989e'
|
ext.crash_version = 'faba68332800f21278c5b600bf14ad55cef5989e'
|
||||||
|
ext.jsr305_version = constants.getProperty("jsr305Version")
|
||||||
|
|
||||||
// Update 121 is required for ObjectInputFilter and at time of writing 131 was latest:
|
// Update 121 is required for ObjectInputFilter and at time of writing 131 was latest:
|
||||||
ext.java8_minUpdateVersion = '131'
|
ext.java8_minUpdateVersion = '131'
|
||||||
|
@ -12,11 +12,14 @@ import net.corda.core.serialization.serialize
|
|||||||
import net.corda.core.utilities.*
|
import net.corda.core.utilities.*
|
||||||
import net.corda.node.services.messaging.RPCServerConfiguration
|
import net.corda.node.services.messaging.RPCServerConfiguration
|
||||||
import net.corda.nodeapi.RPCApi
|
import net.corda.nodeapi.RPCApi
|
||||||
|
import net.corda.testing.SerializationEnvironmentRule
|
||||||
import net.corda.testing.driver.poll
|
import net.corda.testing.driver.poll
|
||||||
import net.corda.testing.internal.*
|
import net.corda.testing.internal.*
|
||||||
import org.apache.activemq.artemis.api.core.SimpleString
|
import org.apache.activemq.artemis.api.core.SimpleString
|
||||||
|
import org.junit.After
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertTrue
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.subjects.PublishSubject
|
import rx.subjects.PublishSubject
|
||||||
@ -26,6 +29,14 @@ import java.util.concurrent.*
|
|||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
class RPCStabilityTests {
|
class RPCStabilityTests {
|
||||||
|
@Rule
|
||||||
|
@JvmField
|
||||||
|
val testSerialization = SerializationEnvironmentRule(true)
|
||||||
|
private val pool = Executors.newFixedThreadPool(10, testThreadFactory())
|
||||||
|
@After
|
||||||
|
fun shutdown() {
|
||||||
|
pool.shutdown()
|
||||||
|
}
|
||||||
|
|
||||||
object DummyOps : RPCOps {
|
object DummyOps : RPCOps {
|
||||||
override val protocolVersion = 0
|
override val protocolVersion = 0
|
||||||
@ -197,9 +208,9 @@ class RPCStabilityTests {
|
|||||||
val proxy = startRpcClient<LeakObservableOps>(server.get().broker.hostAndPort!!).get()
|
val proxy = startRpcClient<LeakObservableOps>(server.get().broker.hostAndPort!!).get()
|
||||||
// Leak many observables
|
// Leak many observables
|
||||||
val N = 200
|
val N = 200
|
||||||
(1..N).toList().parallelStream().forEach {
|
(1..N).map {
|
||||||
proxy.leakObservable()
|
pool.fork { proxy.leakObservable(); Unit }
|
||||||
}
|
}.transpose().getOrThrow()
|
||||||
// In a loop force GC and check whether the server is notified
|
// In a loop force GC and check whether the server is notified
|
||||||
while (true) {
|
while (true) {
|
||||||
System.gc()
|
System.gc()
|
||||||
@ -231,7 +242,7 @@ class RPCStabilityTests {
|
|||||||
assertEquals("pong", client.ping())
|
assertEquals("pong", client.ping())
|
||||||
serverFollower.shutdown()
|
serverFollower.shutdown()
|
||||||
startRpcServer<ReconnectOps>(ops = ops, customPort = serverPort).getOrThrow()
|
startRpcServer<ReconnectOps>(ops = ops, customPort = serverPort).getOrThrow()
|
||||||
val pingFuture = ForkJoinPool.commonPool().fork(client::ping)
|
val pingFuture = pool.fork(client::ping)
|
||||||
assertEquals("pong", pingFuture.getOrThrow(10.seconds))
|
assertEquals("pong", pingFuture.getOrThrow(10.seconds))
|
||||||
clientFollower.shutdown() // Driver would do this after the new server, causing hang.
|
clientFollower.shutdown() // Driver would do this after the new server, causing hang.
|
||||||
}
|
}
|
||||||
|
@ -74,7 +74,7 @@ public class StandaloneCordaRPCJavaClientTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void copyFinanceCordapp() {
|
private void copyFinanceCordapp() {
|
||||||
Path cordappsDir = (factory.baseDirectory(notaryConfig).resolve("cordapps"));
|
Path cordappsDir = (factory.baseDirectory(notaryConfig).resolve(NodeProcess.CORDAPPS_DIR_NAME));
|
||||||
try {
|
try {
|
||||||
Files.createDirectories(cordappsDir);
|
Files.createDirectories(cordappsDir);
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
|
@ -86,7 +86,7 @@ class StandaloneCordaRPClientTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun copyFinanceCordapp() {
|
private fun copyFinanceCordapp() {
|
||||||
val cordappsDir = (factory.baseDirectory(notaryConfig) / "cordapps").createDirectories()
|
val cordappsDir = (factory.baseDirectory(notaryConfig) / NodeProcess.CORDAPPS_DIR_NAME).createDirectories()
|
||||||
// Find the finance jar file for the smoke tests of this module
|
// Find the finance jar file for the smoke tests of this module
|
||||||
val financeJar = Paths.get("build", "resources", "smokeTest").list {
|
val financeJar = Paths.get("build", "resources", "smokeTest").list {
|
||||||
it.filter { "corda-finance" in it.toString() }.toList().single()
|
it.filter { "corda-finance" in it.toString() }.toList().single()
|
||||||
|
@ -6,14 +6,20 @@ import net.corda.core.internal.concurrent.map
|
|||||||
import net.corda.core.messaging.RPCOps
|
import net.corda.core.messaging.RPCOps
|
||||||
import net.corda.node.services.messaging.RPCServerConfiguration
|
import net.corda.node.services.messaging.RPCServerConfiguration
|
||||||
import net.corda.nodeapi.User
|
import net.corda.nodeapi.User
|
||||||
|
import net.corda.testing.SerializationEnvironmentRule
|
||||||
import net.corda.testing.internal.RPCDriverExposedDSLInterface
|
import net.corda.testing.internal.RPCDriverExposedDSLInterface
|
||||||
import net.corda.testing.internal.rpcTestUser
|
import net.corda.testing.internal.rpcTestUser
|
||||||
import net.corda.testing.internal.startInVmRpcClient
|
import net.corda.testing.internal.startInVmRpcClient
|
||||||
import net.corda.testing.internal.startRpcClient
|
import net.corda.testing.internal.startRpcClient
|
||||||
import org.apache.activemq.artemis.api.core.client.ClientSession
|
import org.apache.activemq.artemis.api.core.client.ClientSession
|
||||||
|
import org.junit.Rule
|
||||||
import org.junit.runners.Parameterized
|
import org.junit.runners.Parameterized
|
||||||
|
|
||||||
open class AbstractRPCTest {
|
open class AbstractRPCTest {
|
||||||
|
@Rule
|
||||||
|
@JvmField
|
||||||
|
val testSerialization = SerializationEnvironmentRule(true)
|
||||||
|
|
||||||
enum class RPCTestMode {
|
enum class RPCTestMode {
|
||||||
InVm,
|
InVm,
|
||||||
Netty
|
Netty
|
||||||
|
@ -5,19 +5,22 @@ import net.corda.core.messaging.RPCOps
|
|||||||
import net.corda.core.utilities.millis
|
import net.corda.core.utilities.millis
|
||||||
import net.corda.core.crypto.random63BitValue
|
import net.corda.core.crypto.random63BitValue
|
||||||
import net.corda.core.internal.concurrent.fork
|
import net.corda.core.internal.concurrent.fork
|
||||||
|
import net.corda.core.internal.concurrent.transpose
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
|
import net.corda.core.utilities.getOrThrow
|
||||||
import net.corda.node.services.messaging.RPCServerConfiguration
|
import net.corda.node.services.messaging.RPCServerConfiguration
|
||||||
import net.corda.testing.internal.RPCDriverExposedDSLInterface
|
import net.corda.testing.internal.RPCDriverExposedDSLInterface
|
||||||
import net.corda.testing.internal.rpcDriver
|
import net.corda.testing.internal.rpcDriver
|
||||||
|
import net.corda.testing.internal.testThreadFactory
|
||||||
|
import org.apache.activemq.artemis.utils.collections.ConcurrentHashSet
|
||||||
|
import org.junit.After
|
||||||
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 rx.Observable
|
import rx.Observable
|
||||||
import rx.subjects.UnicastSubject
|
import rx.subjects.UnicastSubject
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.*
|
||||||
import java.util.concurrent.CountDownLatch
|
|
||||||
import java.util.concurrent.ForkJoinPool
|
|
||||||
|
|
||||||
@RunWith(Parameterized::class)
|
@RunWith(Parameterized::class)
|
||||||
class RPCConcurrencyTests : AbstractRPCTest() {
|
class RPCConcurrencyTests : AbstractRPCTest() {
|
||||||
@ -36,7 +39,7 @@ class RPCConcurrencyTests : AbstractRPCTest() {
|
|||||||
fun getParallelObservableTree(depth: Int, branchingFactor: Int): ObservableRose<Int>
|
fun getParallelObservableTree(depth: Int, branchingFactor: Int): ObservableRose<Int>
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestOpsImpl : TestOps {
|
class TestOpsImpl(private val pool: Executor) : TestOps {
|
||||||
private val latches = ConcurrentHashMap<Long, CountDownLatch>()
|
private val latches = ConcurrentHashMap<Long, CountDownLatch>()
|
||||||
override val protocolVersion = 0
|
override val protocolVersion = 0
|
||||||
|
|
||||||
@ -68,24 +71,22 @@ class RPCConcurrencyTests : AbstractRPCTest() {
|
|||||||
val branches = if (depth == 0) {
|
val branches = if (depth == 0) {
|
||||||
Observable.empty<ObservableRose<Int>>()
|
Observable.empty<ObservableRose<Int>>()
|
||||||
} else {
|
} else {
|
||||||
val publish = UnicastSubject.create<ObservableRose<Int>>()
|
UnicastSubject.create<ObservableRose<Int>>().also { publish ->
|
||||||
ForkJoinPool.commonPool().fork {
|
(1..branchingFactor).map {
|
||||||
(1..branchingFactor).toList().parallelStream().forEach {
|
pool.fork { publish.onNext(getParallelObservableTree(depth - 1, branchingFactor)) }
|
||||||
publish.onNext(getParallelObservableTree(depth - 1, branchingFactor))
|
}.transpose().then {
|
||||||
|
it.getOrThrow()
|
||||||
|
publish.onCompleted()
|
||||||
}
|
}
|
||||||
publish.onCompleted()
|
|
||||||
}
|
}
|
||||||
publish
|
|
||||||
}
|
}
|
||||||
return ObservableRose(depth, branches)
|
return ObservableRose(depth, branches)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var testOpsImpl: TestOpsImpl
|
|
||||||
private fun RPCDriverExposedDSLInterface.testProxy(): TestProxy<TestOps> {
|
private fun RPCDriverExposedDSLInterface.testProxy(): TestProxy<TestOps> {
|
||||||
testOpsImpl = TestOpsImpl()
|
|
||||||
return testProxy<TestOps>(
|
return testProxy<TestOps>(
|
||||||
testOpsImpl,
|
TestOpsImpl(pool),
|
||||||
clientConfiguration = RPCClientConfiguration.default.copy(
|
clientConfiguration = RPCClientConfiguration.default.copy(
|
||||||
reapInterval = 100.millis,
|
reapInterval = 100.millis,
|
||||||
cacheConcurrencyLevel = 16
|
cacheConcurrencyLevel = 16
|
||||||
@ -96,6 +97,12 @@ class RPCConcurrencyTests : AbstractRPCTest() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val pool = Executors.newFixedThreadPool(10, testThreadFactory())
|
||||||
|
@After
|
||||||
|
fun shutdown() {
|
||||||
|
pool.shutdown()
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `call multiple RPCs in parallel`() {
|
fun `call multiple RPCs in parallel`() {
|
||||||
rpcDriver {
|
rpcDriver {
|
||||||
@ -103,19 +110,17 @@ class RPCConcurrencyTests : AbstractRPCTest() {
|
|||||||
val numberOfBlockedCalls = 2
|
val numberOfBlockedCalls = 2
|
||||||
val numberOfDownsRequired = 100
|
val numberOfDownsRequired = 100
|
||||||
val id = proxy.ops.newLatch(numberOfDownsRequired)
|
val id = proxy.ops.newLatch(numberOfDownsRequired)
|
||||||
val done = CountDownLatch(numberOfBlockedCalls)
|
|
||||||
// Start a couple of blocking RPC calls
|
// Start a couple of blocking RPC calls
|
||||||
(1..numberOfBlockedCalls).forEach {
|
val done = (1..numberOfBlockedCalls).map {
|
||||||
ForkJoinPool.commonPool().fork {
|
pool.fork {
|
||||||
proxy.ops.waitLatch(id)
|
proxy.ops.waitLatch(id)
|
||||||
done.countDown()
|
|
||||||
}
|
}
|
||||||
}
|
}.transpose()
|
||||||
// Down the latch that the others are waiting for concurrently
|
// Down the latch that the others are waiting for concurrently
|
||||||
(1..numberOfDownsRequired).toList().parallelStream().forEach {
|
(1..numberOfDownsRequired).map {
|
||||||
proxy.ops.downLatch(id)
|
pool.fork { proxy.ops.downLatch(id) }
|
||||||
}
|
}.transpose().getOrThrow()
|
||||||
done.await()
|
done.getOrThrow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,7 +151,7 @@ class RPCConcurrencyTests : AbstractRPCTest() {
|
|||||||
fun ObservableRose<Int>.subscribeToAll() {
|
fun ObservableRose<Int>.subscribeToAll() {
|
||||||
remainingLatch.countDown()
|
remainingLatch.countDown()
|
||||||
this.branches.subscribe { tree ->
|
this.branches.subscribe { tree ->
|
||||||
(tree.value + 1..treeDepth - 1).forEach {
|
(tree.value + 1 until treeDepth).forEach {
|
||||||
require(it in depthsSeen) { "Got ${tree.value} before $it" }
|
require(it in depthsSeen) { "Got ${tree.value} before $it" }
|
||||||
}
|
}
|
||||||
depthsSeen.add(tree.value)
|
depthsSeen.add(tree.value)
|
||||||
@ -165,11 +170,11 @@ class RPCConcurrencyTests : AbstractRPCTest() {
|
|||||||
val treeDepth = 2
|
val treeDepth = 2
|
||||||
val treeBranchingFactor = 10
|
val treeBranchingFactor = 10
|
||||||
val remainingLatch = CountDownLatch((intPower(treeBranchingFactor, treeDepth + 1) - 1) / (treeBranchingFactor - 1))
|
val remainingLatch = CountDownLatch((intPower(treeBranchingFactor, treeDepth + 1) - 1) / (treeBranchingFactor - 1))
|
||||||
val depthsSeen = Collections.synchronizedSet(HashSet<Int>())
|
val depthsSeen = ConcurrentHashSet<Int>()
|
||||||
fun ObservableRose<Int>.subscribeToAll() {
|
fun ObservableRose<Int>.subscribeToAll() {
|
||||||
remainingLatch.countDown()
|
remainingLatch.countDown()
|
||||||
branches.subscribe { tree ->
|
branches.subscribe { tree ->
|
||||||
(tree.value + 1..treeDepth - 1).forEach {
|
(tree.value + 1 until treeDepth).forEach {
|
||||||
require(it in depthsSeen) { "Got ${tree.value} before $it" }
|
require(it in depthsSeen) { "Got ${tree.value} before $it" }
|
||||||
}
|
}
|
||||||
depthsSeen.add(tree.value)
|
depthsSeen.add(tree.value)
|
||||||
|
@ -5,12 +5,18 @@ import net.corda.core.concurrent.CordaFuture
|
|||||||
import net.corda.core.internal.concurrent.openFuture
|
import net.corda.core.internal.concurrent.openFuture
|
||||||
import net.corda.core.messaging.*
|
import net.corda.core.messaging.*
|
||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.core.utilities.getOrThrow
|
||||||
|
import net.corda.testing.SerializationEnvironmentRule
|
||||||
import net.corda.testing.internal.rpcDriver
|
import net.corda.testing.internal.rpcDriver
|
||||||
import net.corda.testing.internal.startRpcClient
|
import net.corda.testing.internal.startRpcClient
|
||||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||||
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class RPCFailureTests {
|
class RPCFailureTests {
|
||||||
|
@Rule
|
||||||
|
@JvmField
|
||||||
|
val testSerialization = SerializationEnvironmentRule(true)
|
||||||
|
|
||||||
class Unserializable
|
class Unserializable
|
||||||
interface Ops : RPCOps {
|
interface Ops : RPCOps {
|
||||||
fun getUnserializable(): Unserializable
|
fun getUnserializable(): Unserializable
|
||||||
|
@ -49,8 +49,7 @@ class IdentitySyncFlowTests {
|
|||||||
val alice: Party = aliceNode.info.singleIdentity()
|
val alice: Party = aliceNode.info.singleIdentity()
|
||||||
val bob: Party = bobNode.info.singleIdentity()
|
val bob: Party = bobNode.info.singleIdentity()
|
||||||
val notary = mockNet.defaultNotaryIdentity
|
val notary = mockNet.defaultNotaryIdentity
|
||||||
bobNode.internals.registerInitiatedFlow(Receive::class.java)
|
bobNode.registerInitiatedFlow(Receive::class.java)
|
||||||
|
|
||||||
// Alice issues then pays some cash to a new confidential identity that Bob doesn't know about
|
// Alice issues then pays some cash to a new confidential identity that Bob doesn't know about
|
||||||
val anonymous = true
|
val anonymous = true
|
||||||
val ref = OpaqueBytes.of(0x01)
|
val ref = OpaqueBytes.of(0x01)
|
||||||
@ -80,8 +79,7 @@ class IdentitySyncFlowTests {
|
|||||||
val bob: Party = bobNode.info.singleIdentity()
|
val bob: Party = bobNode.info.singleIdentity()
|
||||||
val charlie: Party = charlieNode.info.singleIdentity()
|
val charlie: Party = charlieNode.info.singleIdentity()
|
||||||
val notary = mockNet.defaultNotaryIdentity
|
val notary = mockNet.defaultNotaryIdentity
|
||||||
bobNode.internals.registerInitiatedFlow(Receive::class.java)
|
bobNode.registerInitiatedFlow(Receive::class.java)
|
||||||
|
|
||||||
// Charlie issues then pays some cash to a new confidential identity
|
// Charlie issues then pays some cash to a new confidential identity
|
||||||
val anonymous = true
|
val anonymous = true
|
||||||
val ref = OpaqueBytes.of(0x01)
|
val ref = OpaqueBytes.of(0x01)
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
gradlePluginsVersion=2.0.9
|
gradlePluginsVersion=3.0.0
|
||||||
kotlinVersion=1.1.60
|
kotlinVersion=1.1.60
|
||||||
|
platformVersion=2
|
||||||
guavaVersion=21.0
|
guavaVersion=21.0
|
||||||
bouncycastleVersion=1.57
|
bouncycastleVersion=1.57
|
||||||
typesafeConfigVersion=1.3.1
|
typesafeConfigVersion=1.3.1
|
||||||
|
jsr305Version=3.0.2
|
||||||
|
@ -78,7 +78,7 @@ dependencies {
|
|||||||
compileOnly "co.paralleluniverse:quasar-core:$quasar_version:jdk8"
|
compileOnly "co.paralleluniverse:quasar-core:$quasar_version:jdk8"
|
||||||
|
|
||||||
// Thread safety annotations
|
// Thread safety annotations
|
||||||
compile "com.google.code.findbugs:jsr305:3.0.1"
|
compile "com.google.code.findbugs:jsr305:$jsr305_version"
|
||||||
|
|
||||||
// Log4J: logging framework (ONLY explicitly referenced by net.corda.core.utilities.Logging.kt)
|
// Log4J: logging framework (ONLY explicitly referenced by net.corda.core.utilities.Logging.kt)
|
||||||
compile "org.apache.logging.log4j:log4j-core:${log4j_version}"
|
compile "org.apache.logging.log4j:log4j-core:${log4j_version}"
|
||||||
|
@ -31,6 +31,8 @@ import java.time.Duration
|
|||||||
import java.time.temporal.Temporal
|
import java.time.temporal.Temporal
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.Spliterator.*
|
import java.util.Spliterator.*
|
||||||
|
import java.util.concurrent.ExecutorService
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.stream.IntStream
|
import java.util.stream.IntStream
|
||||||
import java.util.stream.Stream
|
import java.util.stream.Stream
|
||||||
import java.util.stream.StreamSupport
|
import java.util.stream.StreamSupport
|
||||||
@ -307,3 +309,10 @@ fun TransactionBuilder.toLedgerTransaction(services: ServiceHub, serializationCo
|
|||||||
val KClass<*>.packageName: String get() = java.`package`.name
|
val KClass<*>.packageName: String get() = java.`package`.name
|
||||||
|
|
||||||
fun URL.openHttpConnection(): HttpURLConnection = openConnection() as HttpURLConnection
|
fun URL.openHttpConnection(): HttpURLConnection = openConnection() as HttpURLConnection
|
||||||
|
/** Analogous to [Thread.join]. */
|
||||||
|
fun ExecutorService.join() {
|
||||||
|
shutdown() // Do not change to shutdownNow, tests use this method to assert the executor has no more tasks.
|
||||||
|
while (!awaitTermination(1, TimeUnit.SECONDS)) {
|
||||||
|
// Try forever. Do not give up, tests use this method to assert the executor has no more tasks.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -43,7 +43,7 @@ class ThreadLocalToggleField<T>(name: String) : ToggleField<T>(name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** The named thread has leaked from a previous test. */
|
/** The named thread has leaked from a previous test. */
|
||||||
class ThreadLeakException : RuntimeException("Leaked thread detected: ${Thread.currentThread().name}")
|
class ThreadLeakException(valueToString: String) : RuntimeException("Leaked thread '${Thread.currentThread().name}' detected, value was: $valueToString")
|
||||||
|
|
||||||
/** @param isAGlobalThreadBeingCreated whether a global thread (that should not inherit any value) is being created. */
|
/** @param isAGlobalThreadBeingCreated whether a global thread (that should not inherit any value) is being created. */
|
||||||
class InheritableThreadLocalToggleField<T>(name: String,
|
class InheritableThreadLocalToggleField<T>(name: String,
|
||||||
@ -54,16 +54,12 @@ class InheritableThreadLocalToggleField<T>(name: String,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private inner class Holder(value: T) : AtomicReference<T?>(value) {
|
private inner class Holder(value: T) : AtomicReference<T?>(value) {
|
||||||
fun valueOrDeclareLeak() = get() ?: throw ThreadLeakException()
|
private val valueToString = value.toString() // We never set another non-null value.
|
||||||
|
fun valueOrDeclareLeak() = get() ?: throw ThreadLeakException(valueToString)
|
||||||
fun childValue(): Holder? {
|
fun childValue(): Holder? {
|
||||||
val e = ThreadLeakException() // Expensive, but so is starting the new thread.
|
val e = ThreadLeakException(valueToString) // Expensive, but so is starting the new thread.
|
||||||
return if (isAGlobalThreadBeingCreated(e.stackTrace)) {
|
get() ?: log.warn(e.message)
|
||||||
get() ?: log.warn(e.message)
|
return if (isAGlobalThreadBeingCreated(e.stackTrace)) null else this
|
||||||
null
|
|
||||||
} else {
|
|
||||||
get() ?: log.error(e.message)
|
|
||||||
this
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ interface SerializationEnvironment {
|
|||||||
val checkpointContext: SerializationContext
|
val checkpointContext: SerializationContext
|
||||||
}
|
}
|
||||||
|
|
||||||
class SerializationEnvironmentImpl(
|
open class SerializationEnvironmentImpl(
|
||||||
override val serializationFactory: SerializationFactory,
|
override val serializationFactory: SerializationFactory,
|
||||||
override val p2pContext: SerializationContext,
|
override val p2pContext: SerializationContext,
|
||||||
rpcServerContext: SerializationContext? = null,
|
rpcServerContext: SerializationContext? = null,
|
||||||
|
@ -0,0 +1,75 @@
|
|||||||
|
package net.corda.core
|
||||||
|
|
||||||
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
|
import net.corda.core.flows.FlowLogic
|
||||||
|
import net.corda.core.flows.StartableByRPC
|
||||||
|
import net.corda.core.identity.CordaX500Name
|
||||||
|
import net.corda.core.internal.*
|
||||||
|
import net.corda.core.messaging.startFlow
|
||||||
|
import net.corda.core.utilities.getOrThrow
|
||||||
|
import net.corda.nodeapi.User
|
||||||
|
import net.corda.smoketesting.NodeConfig
|
||||||
|
import net.corda.smoketesting.NodeProcess
|
||||||
|
import net.corda.testing.common.internal.ProjectStructure
|
||||||
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.junit.Test
|
||||||
|
import java.nio.file.Paths
|
||||||
|
import java.util.*
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
import java.util.jar.JarFile
|
||||||
|
import kotlin.streams.toList
|
||||||
|
|
||||||
|
class NodeVersioningTest {
|
||||||
|
private companion object {
|
||||||
|
val user = User("user1", "test", permissions = setOf("ALL"))
|
||||||
|
val port = AtomicInteger(15100)
|
||||||
|
|
||||||
|
val expectedPlatformVersion = (ProjectStructure.projectRootDir / "constants.properties").read {
|
||||||
|
val constants = Properties()
|
||||||
|
constants.load(it)
|
||||||
|
constants.getProperty("platformVersion").toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val factory = NodeProcess.Factory()
|
||||||
|
|
||||||
|
private val aliceConfig = NodeConfig(
|
||||||
|
legalName = CordaX500Name(organisation = "Alice Corp", locality = "Madrid", country = "ES"),
|
||||||
|
p2pPort = port.andIncrement,
|
||||||
|
rpcPort = port.andIncrement,
|
||||||
|
webPort = port.andIncrement,
|
||||||
|
isNotary = false,
|
||||||
|
users = listOf(user)
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `platform version in manifest file`() {
|
||||||
|
val manifest = JarFile(factory.cordaJar.toFile()).manifest
|
||||||
|
assertThat(manifest.mainAttributes.getValue("Corda-Platform-Version").toInt()).isEqualTo(expectedPlatformVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `platform version from RPC`() {
|
||||||
|
val cordappsDir = (factory.baseDirectory(aliceConfig) / NodeProcess.CORDAPPS_DIR_NAME).createDirectories()
|
||||||
|
// Find the jar file for the smoke tests of this module
|
||||||
|
val selfCordapp = Paths.get("build", "libs").list {
|
||||||
|
it.filter { "-smokeTests" in it.toString() }.toList().single()
|
||||||
|
}
|
||||||
|
selfCordapp.copyToDirectory(cordappsDir)
|
||||||
|
|
||||||
|
factory.create(aliceConfig).use { alice ->
|
||||||
|
alice.connect().use {
|
||||||
|
val rpc = it.proxy
|
||||||
|
assertThat(rpc.protocolVersion).isEqualTo(expectedPlatformVersion)
|
||||||
|
assertThat(rpc.nodeInfo().platformVersion).isEqualTo(expectedPlatformVersion)
|
||||||
|
assertThat(rpc.startFlow(NodeVersioningTest::GetPlatformVersionFlow).returnValue.getOrThrow()).isEqualTo(expectedPlatformVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@StartableByRPC
|
||||||
|
class GetPlatformVersionFlow : FlowLogic<Int>() {
|
||||||
|
@Suspendable
|
||||||
|
override fun call(): Int = serviceHub.myInfo.platformVersion
|
||||||
|
}
|
||||||
|
}
|
@ -14,6 +14,7 @@ import net.corda.core.utilities.unwrap
|
|||||||
import net.corda.nodeapi.User
|
import net.corda.nodeapi.User
|
||||||
import net.corda.smoketesting.NodeConfig
|
import net.corda.smoketesting.NodeConfig
|
||||||
import net.corda.smoketesting.NodeProcess
|
import net.corda.smoketesting.NodeProcess
|
||||||
|
import net.corda.smoketesting.NodeProcess.Companion.CORDAPPS_DIR_NAME
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
@ -22,7 +23,6 @@ import kotlin.streams.toList
|
|||||||
|
|
||||||
class CordappSmokeTest {
|
class CordappSmokeTest {
|
||||||
private companion object {
|
private companion object {
|
||||||
private const val CORDAPPS_DIR_NAME = "cordapps"
|
|
||||||
val user = User("user1", "test", permissions = setOf("ALL"))
|
val user = User("user1", "test", permissions = setOf("ALL"))
|
||||||
val port = AtomicInteger(15100)
|
val port = AtomicInteger(15100)
|
||||||
}
|
}
|
||||||
@ -38,7 +38,6 @@ class CordappSmokeTest {
|
|||||||
users = listOf(user)
|
users = listOf(user)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `FlowContent appName returns the filename of the CorDapp jar`() {
|
fun `FlowContent appName returns the filename of the CorDapp jar`() {
|
||||||
val cordappsDir = (factory.baseDirectory(aliceConfig) / CORDAPPS_DIR_NAME).createDirectories()
|
val cordappsDir = (factory.baseDirectory(aliceConfig) / CORDAPPS_DIR_NAME).createDirectories()
|
||||||
|
@ -38,7 +38,7 @@ public class FlowsInJavaTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void suspendableActionInsideUnwrap() throws Exception {
|
public void suspendableActionInsideUnwrap() throws Exception {
|
||||||
bobNode.getInternals().registerInitiatedFlow(SendHelloAndThenReceive.class);
|
bobNode.registerInitiatedFlow(SendHelloAndThenReceive.class);
|
||||||
Future<String> result = startFlow(aliceNode.getServices(), new SendInUnwrapFlow(bob)).getResultFuture();
|
Future<String> result = startFlow(aliceNode.getServices(), new SendInUnwrapFlow(bob)).getResultFuture();
|
||||||
mockNet.runNetwork();
|
mockNet.runNetwork();
|
||||||
assertThat(result.get()).isEqualTo("Hello");
|
assertThat(result.get()).isEqualTo("Hello");
|
||||||
|
@ -52,10 +52,8 @@ class AttachmentTests {
|
|||||||
val bobNode = mockNet.createPartyNode(BOB.name)
|
val bobNode = mockNet.createPartyNode(BOB.name)
|
||||||
|
|
||||||
val alice = aliceNode.info.singleIdentity()
|
val alice = aliceNode.info.singleIdentity()
|
||||||
|
aliceNode.registerInitiatedFlow(FetchAttachmentsResponse::class.java)
|
||||||
aliceNode.internals.registerInitiatedFlow(FetchAttachmentsResponse::class.java)
|
bobNode.registerInitiatedFlow(FetchAttachmentsResponse::class.java)
|
||||||
bobNode.internals.registerInitiatedFlow(FetchAttachmentsResponse::class.java)
|
|
||||||
|
|
||||||
// Insert an attachment into node zero's store directly.
|
// Insert an attachment into node zero's store directly.
|
||||||
val id = aliceNode.database.transaction {
|
val id = aliceNode.database.transaction {
|
||||||
aliceNode.attachments.importAttachment(ByteArrayInputStream(fakeAttachment()))
|
aliceNode.attachments.importAttachment(ByteArrayInputStream(fakeAttachment()))
|
||||||
@ -85,10 +83,8 @@ class AttachmentTests {
|
|||||||
fun `missing`() {
|
fun `missing`() {
|
||||||
val aliceNode = mockNet.createPartyNode(ALICE.name)
|
val aliceNode = mockNet.createPartyNode(ALICE.name)
|
||||||
val bobNode = mockNet.createPartyNode(BOB.name)
|
val bobNode = mockNet.createPartyNode(BOB.name)
|
||||||
|
aliceNode.registerInitiatedFlow(FetchAttachmentsResponse::class.java)
|
||||||
aliceNode.internals.registerInitiatedFlow(FetchAttachmentsResponse::class.java)
|
bobNode.registerInitiatedFlow(FetchAttachmentsResponse::class.java)
|
||||||
bobNode.internals.registerInitiatedFlow(FetchAttachmentsResponse::class.java)
|
|
||||||
|
|
||||||
// Get node one to fetch a non-existent attachment.
|
// Get node one to fetch a non-existent attachment.
|
||||||
val hash = SecureHash.randomSHA256()
|
val hash = SecureHash.randomSHA256()
|
||||||
val alice = aliceNode.info.singleIdentity()
|
val alice = aliceNode.info.singleIdentity()
|
||||||
@ -108,10 +104,8 @@ class AttachmentTests {
|
|||||||
})
|
})
|
||||||
val bobNode = mockNet.createNode(MockNodeParameters(legalName = BOB.name))
|
val bobNode = mockNet.createNode(MockNodeParameters(legalName = BOB.name))
|
||||||
val alice = aliceNode.services.myInfo.identityFromX500Name(ALICE_NAME)
|
val alice = aliceNode.services.myInfo.identityFromX500Name(ALICE_NAME)
|
||||||
|
aliceNode.registerInitiatedFlow(FetchAttachmentsResponse::class.java)
|
||||||
aliceNode.internals.registerInitiatedFlow(FetchAttachmentsResponse::class.java)
|
bobNode.registerInitiatedFlow(FetchAttachmentsResponse::class.java)
|
||||||
bobNode.internals.registerInitiatedFlow(FetchAttachmentsResponse::class.java)
|
|
||||||
|
|
||||||
val attachment = fakeAttachment()
|
val attachment = fakeAttachment()
|
||||||
// Insert an attachment into node zero's store directly.
|
// Insert an attachment into node zero's store directly.
|
||||||
val id = aliceNode.database.transaction {
|
val id = aliceNode.database.transaction {
|
||||||
|
@ -50,7 +50,7 @@ class CollectSignaturesFlowTests {
|
|||||||
|
|
||||||
private fun registerFlowOnAllNodes(flowClass: KClass<out FlowLogic<*>>) {
|
private fun registerFlowOnAllNodes(flowClass: KClass<out FlowLogic<*>>) {
|
||||||
listOf(aliceNode, bobNode, charlieNode).forEach {
|
listOf(aliceNode, bobNode, charlieNode).forEach {
|
||||||
it.internals.registerInitiatedFlow(flowClass.java)
|
it.registerInitiatedFlow(flowClass.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,7 +133,7 @@ class ContractUpgradeFlowTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `2 parties contract upgrade using RPC`() {
|
fun `2 parties contract upgrade using RPC`() {
|
||||||
rpcDriver(initialiseSerialization = false) {
|
rpcDriver {
|
||||||
// Create dummy contract.
|
// Create dummy contract.
|
||||||
val twoPartyDummyContract = DummyContract.generateInitial(0, notary, alice.ref(1), bob.ref(1))
|
val twoPartyDummyContract = DummyContract.generateInitial(0, notary, alice.ref(1), bob.ref(1))
|
||||||
val signedByA = aliceNode.services.signInitialTransaction(twoPartyDummyContract)
|
val signedByA = aliceNode.services.signInitialTransaction(twoPartyDummyContract)
|
||||||
|
@ -38,14 +38,14 @@ class NoAnswer(private val closure: () -> Unit = {}) : FlowLogic<Unit>() {
|
|||||||
* Allows to register a flow of type [R] against an initiating flow of type [I].
|
* Allows to register a flow of type [R] against an initiating flow of type [I].
|
||||||
*/
|
*/
|
||||||
inline fun <I : FlowLogic<*>, reified R : FlowLogic<*>> StartedNode<*>.registerInitiatedFlow(initiatingFlowType: KClass<I>, crossinline construct: (session: FlowSession) -> R) {
|
inline fun <I : FlowLogic<*>, reified R : FlowLogic<*>> StartedNode<*>.registerInitiatedFlow(initiatingFlowType: KClass<I>, crossinline construct: (session: FlowSession) -> R) {
|
||||||
internals.internalRegisterFlowFactory(initiatingFlowType.java, InitiatedFlowFactory.Core { session -> construct(session) }, R::class.javaObjectType, true)
|
internalRegisterFlowFactory(initiatingFlowType.java, InitiatedFlowFactory.Core { session -> construct(session) }, R::class.javaObjectType, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows to register a flow of type [Answer] against an initiating flow of type [I], returning a valure of type [R].
|
* Allows to register a flow of type [Answer] against an initiating flow of type [I], returning a valure of type [R].
|
||||||
*/
|
*/
|
||||||
inline fun <I : FlowLogic<*>, reified R : Any> StartedNode<*>.registerAnswer(initiatingFlowType: KClass<I>, value: R) {
|
inline fun <I : FlowLogic<*>, reified R : Any> StartedNode<*>.registerAnswer(initiatingFlowType: KClass<I>, value: R) {
|
||||||
internals.internalRegisterFlowFactory(initiatingFlowType.java, InitiatedFlowFactory.Core { session -> Answer(session, value) }, Answer::class.javaObjectType, true)
|
internalRegisterFlowFactory(initiatingFlowType.java, InitiatedFlowFactory.Core { session -> Answer(session, value) }, Answer::class.javaObjectType, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -42,8 +42,8 @@ class ResolveTransactionsFlowTest {
|
|||||||
notaryNode = mockNet.defaultNotaryNode
|
notaryNode = mockNet.defaultNotaryNode
|
||||||
megaCorpNode = mockNet.createPartyNode(MEGA_CORP.name)
|
megaCorpNode = mockNet.createPartyNode(MEGA_CORP.name)
|
||||||
miniCorpNode = mockNet.createPartyNode(MINI_CORP.name)
|
miniCorpNode = mockNet.createPartyNode(MINI_CORP.name)
|
||||||
megaCorpNode.internals.registerInitiatedFlow(TestResponseFlow::class.java)
|
megaCorpNode.registerInitiatedFlow(TestResponseFlow::class.java)
|
||||||
miniCorpNode.internals.registerInitiatedFlow(TestResponseFlow::class.java)
|
miniCorpNode.registerInitiatedFlow(TestResponseFlow::class.java)
|
||||||
notary = mockNet.defaultNotaryIdentity
|
notary = mockNet.defaultNotaryIdentity
|
||||||
megaCorp = megaCorpNode.info.singleIdentity()
|
megaCorp = megaCorpNode.info.singleIdentity()
|
||||||
miniCorp = miniCorpNode.info.singleIdentity()
|
miniCorp = miniCorpNode.info.singleIdentity()
|
||||||
|
@ -14,7 +14,6 @@ import org.junit.runners.model.Statement
|
|||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import java.util.concurrent.ExecutorService
|
import java.util.concurrent.ExecutorService
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertNull
|
import kotlin.test.assertNull
|
||||||
|
|
||||||
@ -23,10 +22,7 @@ private fun <T> withSingleThreadExecutor(callable: ExecutorService.() -> T) = Ex
|
|||||||
fork {}.getOrThrow() // Start the thread.
|
fork {}.getOrThrow() // Start the thread.
|
||||||
callable()
|
callable()
|
||||||
} finally {
|
} finally {
|
||||||
shutdown()
|
join()
|
||||||
while (!awaitTermination(1, TimeUnit.SECONDS)) {
|
|
||||||
// Do nothing.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,6 +130,7 @@ class ToggleFieldTest {
|
|||||||
assertThatThrownBy { future.getOrThrow() }
|
assertThatThrownBy { future.getOrThrow() }
|
||||||
.isInstanceOf(ThreadLeakException::class.java)
|
.isInstanceOf(ThreadLeakException::class.java)
|
||||||
.hasMessageContaining(threadName)
|
.hasMessageContaining(threadName)
|
||||||
|
.hasMessageContaining("hello")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
withSingleThreadExecutor {
|
withSingleThreadExecutor {
|
||||||
@ -141,9 +138,9 @@ class ToggleFieldTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** We log an error rather than failing-fast as the new thread may be an undetected global. */
|
/** We log a warning rather than failing-fast as the new thread may be an undetected global. */
|
||||||
@Test
|
@Test
|
||||||
fun `leaked thread propagates holder to non-global thread, with error`() {
|
fun `leaked thread propagates holder to non-global thread, with warning`() {
|
||||||
val field = inheritableThreadLocalToggleField<String>()
|
val field = inheritableThreadLocalToggleField<String>()
|
||||||
field.set("hello")
|
field.set("hello")
|
||||||
withSingleThreadExecutor {
|
withSingleThreadExecutor {
|
||||||
@ -153,17 +150,18 @@ class ToggleFieldTest {
|
|||||||
val leakedThreadName = Thread.currentThread().name
|
val leakedThreadName = Thread.currentThread().name
|
||||||
verifyNoMoreInteractions(log)
|
verifyNoMoreInteractions(log)
|
||||||
withSingleThreadExecutor {
|
withSingleThreadExecutor {
|
||||||
// If ThreadLeakException is seen in practice, these errors form a trail of where the holder has been:
|
// If ThreadLeakException is seen in practice, these warnings form a trail of where the holder has been:
|
||||||
verify(log).error(argThat { contains(leakedThreadName) })
|
verify(log).warn(argThat { contains(leakedThreadName) && contains("hello") })
|
||||||
val newThreadName = fork { Thread.currentThread().name }.getOrThrow()
|
val newThreadName = fork { Thread.currentThread().name }.getOrThrow()
|
||||||
val future = fork(field::get)
|
val future = fork(field::get)
|
||||||
assertThatThrownBy { future.getOrThrow() }
|
assertThatThrownBy { future.getOrThrow() }
|
||||||
.isInstanceOf(ThreadLeakException::class.java)
|
.isInstanceOf(ThreadLeakException::class.java)
|
||||||
.hasMessageContaining(newThreadName)
|
.hasMessageContaining(newThreadName)
|
||||||
|
.hasMessageContaining("hello")
|
||||||
fork {
|
fork {
|
||||||
verifyNoMoreInteractions(log)
|
verifyNoMoreInteractions(log)
|
||||||
withSingleThreadExecutor {
|
withSingleThreadExecutor {
|
||||||
verify(log).error(argThat { contains(newThreadName) })
|
verify(log).warn(argThat { contains(newThreadName) && contains("hello") })
|
||||||
}
|
}
|
||||||
}.getOrThrow()
|
}.getOrThrow()
|
||||||
}
|
}
|
||||||
@ -183,7 +181,7 @@ class ToggleFieldTest {
|
|||||||
globalThreadCreationMethod {
|
globalThreadCreationMethod {
|
||||||
verifyNoMoreInteractions(log)
|
verifyNoMoreInteractions(log)
|
||||||
withSingleThreadExecutor {
|
withSingleThreadExecutor {
|
||||||
verify(log).warn(argThat { contains(leakedThreadName) })
|
verify(log).warn(argThat { contains(leakedThreadName) && contains("hello") })
|
||||||
// In practice the new thread is for example a static thread we can't get rid of:
|
// In practice the new thread is for example a static thread we can't get rid of:
|
||||||
assertNull(fork(field::get).getOrThrow())
|
assertNull(fork(field::get).getOrThrow())
|
||||||
}
|
}
|
||||||
|
@ -2,13 +2,13 @@ package net.corda.core.internal.concurrent
|
|||||||
|
|
||||||
import com.nhaarman.mockito_kotlin.*
|
import com.nhaarman.mockito_kotlin.*
|
||||||
import net.corda.core.concurrent.CordaFuture
|
import net.corda.core.concurrent.CordaFuture
|
||||||
|
import net.corda.core.internal.join
|
||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.core.utilities.getOrThrow
|
||||||
import net.corda.testing.rigorousMock
|
import net.corda.testing.rigorousMock
|
||||||
import org.assertj.core.api.Assertions
|
import org.assertj.core.api.Assertions
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFalse
|
import kotlin.test.assertFalse
|
||||||
@ -108,10 +108,7 @@ class CordaFutureTest {
|
|||||||
val throwable = Exception("Boom")
|
val throwable = Exception("Boom")
|
||||||
val executor = Executors.newSingleThreadExecutor()
|
val executor = Executors.newSingleThreadExecutor()
|
||||||
executor.fork { throw throwable }.andForget(log)
|
executor.fork { throw throwable }.andForget(log)
|
||||||
executor.shutdown()
|
executor.join()
|
||||||
while (!executor.awaitTermination(1, TimeUnit.SECONDS)) {
|
|
||||||
// Do nothing.
|
|
||||||
}
|
|
||||||
verify(log).error(any(), same(throwable))
|
verify(log).error(any(), same(throwable))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ import net.corda.core.utilities.unwrap
|
|||||||
import net.corda.node.internal.InitiatedFlowFactory
|
import net.corda.node.internal.InitiatedFlowFactory
|
||||||
import net.corda.node.internal.StartedNode
|
import net.corda.node.internal.StartedNode
|
||||||
import net.corda.node.services.persistence.NodeAttachmentService
|
import net.corda.node.services.persistence.NodeAttachmentService
|
||||||
import net.corda.node.utilities.currentDBSession
|
import net.corda.nodeapi.internal.persistence.currentDBSession
|
||||||
import net.corda.testing.ALICE_NAME
|
import net.corda.testing.ALICE_NAME
|
||||||
import net.corda.testing.BOB_NAME
|
import net.corda.testing.BOB_NAME
|
||||||
import net.corda.testing.node.MockNetwork
|
import net.corda.testing.node.MockNetwork
|
||||||
@ -148,7 +148,7 @@ class AttachmentSerializationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun launchFlow(clientLogic: ClientLogic, rounds: Int, sendData: Boolean = false) {
|
private fun launchFlow(clientLogic: ClientLogic, rounds: Int, sendData: Boolean = false) {
|
||||||
server.internals.internalRegisterFlowFactory(
|
server.internalRegisterFlowFactory(
|
||||||
ClientLogic::class.java,
|
ClientLogic::class.java,
|
||||||
InitiatedFlowFactory.Core { ServerLogic(it, sendData) },
|
InitiatedFlowFactory.Core { ServerLogic(it, sendData) },
|
||||||
ServerLogic::class.java,
|
ServerLogic::class.java,
|
||||||
|
@ -6,6 +6,9 @@ from the previous milestone release.
|
|||||||
|
|
||||||
UNRELEASED
|
UNRELEASED
|
||||||
----------
|
----------
|
||||||
|
* Removed confusing property database.initDatabase, enabling its guarded behaviour with the dev-mode.
|
||||||
|
In devMode Hibernate will try to create or update database schemas, otherwise it will expect relevant schemas to be present
|
||||||
|
in the database (pre configured via DDL scripts or equivalent), and validate these are correct.
|
||||||
|
|
||||||
* ``AttachmentStorage`` now allows providing metadata on attachments upload - username and filename, currently as plain
|
* ``AttachmentStorage`` now allows providing metadata on attachments upload - username and filename, currently as plain
|
||||||
strings. Those can be then used for querying, utilizing ``queryAttachments`` method of the same interface.
|
strings. Those can be then used for querying, utilizing ``queryAttachments`` method of the same interface.
|
||||||
|
@ -70,7 +70,6 @@ path to the node's base directory.
|
|||||||
|
|
||||||
:database: Database configuration:
|
:database: Database configuration:
|
||||||
|
|
||||||
:initDatabase: Boolean on whether to initialise the database or just validate the schema. Defaults to true.
|
|
||||||
:serverNameTablePrefix: Prefix string to apply to all the database tables. The default is no prefix.
|
:serverNameTablePrefix: Prefix string to apply to all the database tables. The default is no prefix.
|
||||||
:transactionIsolationLevel: Transaction isolation level as defined by the ``TRANSACTION_`` constants in
|
:transactionIsolationLevel: Transaction isolation level as defined by the ``TRANSACTION_`` constants in
|
||||||
``java.sql.Connection``, but without the "TRANSACTION_" prefix. Defaults to REPEATABLE_READ.
|
``java.sql.Connection``, but without the "TRANSACTION_" prefix. Defaults to REPEATABLE_READ.
|
||||||
@ -103,24 +102,26 @@ path to the node's base directory.
|
|||||||
:notary: Optional configuration object which if present configures the node to run as a notary. If part of a Raft or BFT SMaRt
|
:notary: Optional configuration object which if present configures the node to run as a notary. If part of a Raft or BFT SMaRt
|
||||||
cluster then specify ``raft`` or ``bftSMaRt`` respectively as described below. If a single node notary then omit both.
|
cluster then specify ``raft`` or ``bftSMaRt`` respectively as described below. If a single node notary then omit both.
|
||||||
|
|
||||||
:validating: Boolean to determine whether the notary is a validating or non-validating one.
|
:validating: Boolean to determine whether the notary is a validating or non-validating one.
|
||||||
|
|
||||||
:raft: If part of a distributed Raft cluster specify this config object, with the following settings:
|
:raft: If part of a distributed Raft cluster specify this config object, with the following settings:
|
||||||
|
|
||||||
:nodeAddress: The host and port to which to bind the embedded Raft server. Note that the Raft cluster uses a
|
:nodeAddress: The host and port to which to bind the embedded Raft server. Note that the Raft cluster uses a
|
||||||
separate transport layer for communication that does not integrate with ArtemisMQ messaging services.
|
separate transport layer for communication that does not integrate with ArtemisMQ messaging services.
|
||||||
|
|
||||||
:clusterAddresses: List of Raft cluster member addresses used to join the cluster. At least one of the specified
|
:clusterAddresses: Must list the addresses of all the members in the cluster. At least one of the members must
|
||||||
members must be active and be able to communicate with the cluster leader for joining. If empty, a new
|
be active and be able to communicate with the cluster leader for the node to join the cluster. If empty, a
|
||||||
cluster will be bootstrapped.
|
new cluster will be bootstrapped.
|
||||||
|
|
||||||
:bftSMaRt: If part of a distributed BFT-SMaRt cluster specify this config object, with the following settings:
|
:bftSMaRt: If part of a distributed BFT-SMaRt cluster specify this config object, with the following settings:
|
||||||
|
|
||||||
:replicaId: The zero-based index of the current replica. All replicas must specify a unique replica id.
|
:replicaId: The zero-based index of the current replica. All replicas must specify a unique replica id.
|
||||||
|
|
||||||
:clusterAddresses: List of all BFT-SMaRt cluster member addresses.
|
:clusterAddresses: Must list the addresses of all the members in the cluster. At least one of the members must
|
||||||
|
be active and be able to communicate with the cluster leader for the node to join the cluster. If empty, a
|
||||||
|
new cluster will be bootstrapped.
|
||||||
|
|
||||||
:custom: If `true`, will load and install a notary service from a CorDapp. See :doc:`tutorial-custom-notary`.
|
:custom: If `true`, will load and install a notary service from a CorDapp. See :doc:`tutorial-custom-notary`.
|
||||||
|
|
||||||
Only one of ``raft``, ``bftSMaRt`` or ``custom`` configuration values may be specified.
|
Only one of ``raft``, ``bftSMaRt`` or ``custom`` configuration values may be specified.
|
||||||
|
|
||||||
@ -131,17 +132,19 @@ path to the node's base directory.
|
|||||||
:rpcUsers: A list of users who are authorised to access the RPC system. Each user in the list is a config object with the
|
:rpcUsers: A list of users who are authorised to access the RPC system. Each user in the list is a config object with the
|
||||||
following fields:
|
following fields:
|
||||||
|
|
||||||
:username: Username consisting only of word characters (a-z, A-Z, 0-9 and _)
|
:username: Username consisting only of word characters (a-z, A-Z, 0-9 and _)
|
||||||
:password: The password
|
:password: The password
|
||||||
:permissions: A list of permission strings which RPC methods can use to control access
|
:permissions: A list of permissions for starting flows via RPC. To give the user the permission to start the flow
|
||||||
|
``foo.bar.FlowClass``, add the string ``StartFlow.foo.bar.FlowClass`` to the list. If the list
|
||||||
If this field is absent or an empty list then RPC is effectively locked down. Alternatively, if it contains the string
|
contains the string ``ALL``, the user can start any flow via RPC. This value is intended for administrator
|
||||||
``ALL`` then the user is permitted to use *any* RPC method. This value is intended for administrator users and for developers.
|
users and for development.
|
||||||
|
|
||||||
:devMode: This flag sets the node to run in development mode. On startup, if the keystore ``<workspace>/certificates/sslkeystore.jks``
|
:devMode: This flag sets the node to run in development mode. On startup, if the keystore ``<workspace>/certificates/sslkeystore.jks``
|
||||||
does not exist, a developer keystore will be used if ``devMode`` is true. The node will exit if ``devMode`` is false
|
does not exist, a developer keystore will be used if ``devMode`` is true. The node will exit if ``devMode`` is false
|
||||||
and the keystore does not exist. ``devMode`` also turns on background checking of flow checkpoints to shake out any
|
and the keystore does not exist. ``devMode`` also turns on background checking of flow checkpoints to shake out any
|
||||||
bugs in the checkpointing process.
|
bugs in the checkpointing process. Also, if ``devMode`` is true, Hibernate will try to automatically create the schema required by Corda
|
||||||
|
or update an existing schema in the SQL database; if ``devMode`` is false, Hibernate will simply validate an existing schema
|
||||||
|
failing on node start if this schema is either not present or not compatible.
|
||||||
|
|
||||||
:detectPublicIp: This flag toggles the auto IP detection behaviour, it is enabled by default. On startup the node will
|
:detectPublicIp: This flag toggles the auto IP detection behaviour, it is enabled by default. On startup the node will
|
||||||
attempt to discover its externally visible IP address first by looking for any public addresses on its network
|
attempt to discover its externally visible IP address first by looking for any public addresses on its network
|
||||||
@ -163,4 +166,4 @@ path to the node's base directory.
|
|||||||
:sshd: If provided, node will start internal SSH server which will provide a management shell. It uses the same credentials
|
:sshd: If provided, node will start internal SSH server which will provide a management shell. It uses the same credentials
|
||||||
and permissions as RPC subsystem. It has one required parameter.
|
and permissions as RPC subsystem. It has one required parameter.
|
||||||
|
|
||||||
:port: - the port to start SSH server on
|
:port: The port to start SSH server on
|
||||||
|
@ -4,8 +4,9 @@ Corda nodes
|
|||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
deploying-a-node
|
generating-a-node
|
||||||
running-a-node
|
running-a-node
|
||||||
|
deploying-a-node
|
||||||
corda-configuration-file
|
corda-configuration-file
|
||||||
clientrpc
|
clientrpc
|
||||||
shell
|
shell
|
||||||
|
@ -118,9 +118,9 @@ Creating the CorDapp JAR
|
|||||||
The gradle ``jar`` task included in the CorDapp template build file will automatically build your CorDapp JAR correctly
|
The gradle ``jar`` task included in the CorDapp template build file will automatically build your CorDapp JAR correctly
|
||||||
as long as your dependencies are set correctly.
|
as long as your dependencies are set correctly.
|
||||||
|
|
||||||
Note that the hash of the resulting CorDapp JAR is not deterministic, as it depends on variables such as the timestamp
|
.. warning:: The hash of the generated CorDapp JAR is not deterministic, as it depends on variables such as the
|
||||||
at creation. Nodes running the same CorDapp must therefore ensure they are using the exact same CorDapp jar, and not
|
timestamp at creation. Nodes running the same CorDapp must therefore ensure they are using the exact same CorDapp
|
||||||
different versions of the JAR created from identical sources.
|
jar, and not different versions of the JAR created from identical sources.
|
||||||
|
|
||||||
The filename of the JAR must include a unique identifier to deduplicate it from other releases of the same CorDapp.
|
The filename of the JAR must include a unique identifier to deduplicate it from other releases of the same CorDapp.
|
||||||
This is typically done by appending the version string to the CorDapp's name. This unique identifier should not change
|
This is typically done by appending the version string to the CorDapp's name. This unique identifier should not change
|
||||||
@ -131,7 +131,7 @@ Installing the CorDapp jar
|
|||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
.. note:: Before installing a CorDapp, you must create one or more nodes to install it on. For instructions, please see
|
.. note:: Before installing a CorDapp, you must create one or more nodes to install it on. For instructions, please see
|
||||||
:doc:`deploying-a-node`.
|
:doc:`generating-a-node`.
|
||||||
|
|
||||||
At runtime, nodes will load any CorDapps present in their ``cordapps`` folder. Therefore in order to install a CorDapp on
|
At runtime, nodes will load any CorDapps present in their ``cordapps`` folder. Therefore in order to install a CorDapp on
|
||||||
a node, the CorDapp JAR must be added to the ``<node_dir>/cordapps/`` folder, where ``node_dir`` is the folder in which
|
a node, the CorDapp JAR must be added to the ``<node_dir>/cordapps/`` folder, where ``node_dir`` is the folder in which
|
||||||
|
@ -1,141 +1,242 @@
|
|||||||
Deploying a node
|
Deploying a node
|
||||||
================
|
================
|
||||||
|
|
||||||
Node structure
|
.. contents::
|
||||||
--------------
|
|
||||||
Each Corda node has the following structure:
|
|
||||||
|
|
||||||
.. sourcecode:: none
|
.. note:: These instructions are intended for people who want to deploy a Corda node to a server,
|
||||||
|
whether they have developed and tested a CorDapp following the instructions in :doc:`generating-a-node`
|
||||||
|
or are deploying a third-party CorDapp.
|
||||||
|
|
||||||
.
|
Linux (systemd): Installing and running Corda as a systemd service
|
||||||
├── certificates // The node's doorman certificates
|
------------------------------------------------------------------
|
||||||
├── corda-webserver.jar // The built-in node webserver
|
We recommend creating systemd services to run a node and the optional webserver. This provides logging and service
|
||||||
├── corda.jar // The core Corda libraries
|
handling, and ensures the Corda service is run at boot.
|
||||||
├── logs // The node logs
|
|
||||||
├── node.conf // The node's configuration files
|
|
||||||
├── persistence.mv.db // The node's database
|
|
||||||
└── cordapps // The CorDapps jars installed on the node
|
|
||||||
|
|
||||||
The node is configured by editing its ``node.conf`` file. You install CorDapps on the node by dropping the CorDapp JARs
|
**Prerequisites**:
|
||||||
into the ``cordapps`` folder.
|
|
||||||
|
|
||||||
Node naming
|
* Oracle Java 8. The supported versions are listed in :doc:`getting-set-up`
|
||||||
-----------
|
|
||||||
A node's name must be a valid X500 name that obeys the following additional constraints:
|
|
||||||
|
|
||||||
* The fields of the name have the following maximum character lengths:
|
1. Add a system user which will be used to run Corda:
|
||||||
|
|
||||||
* Common name: 64
|
``sudo adduser --system --no-create-home --group corda``
|
||||||
* Organisation: 128
|
|
||||||
* Organisation unit: 64
|
|
||||||
* Locality: 64
|
|
||||||
* State: 64
|
|
||||||
|
|
||||||
* The country code is a valid ISO 3166-1 two letter code in upper-case
|
2. Create a directory called ``/opt/corda`` and change its ownership to the user you want to use to run Corda:
|
||||||
|
|
||||||
* The organisation, locality and country attributes are present
|
``mkdir /opt/corda; chown corda:corda /opt/corda``
|
||||||
|
|
||||||
* The organisation field of the name obeys the following constraints:
|
3. Download the `Corda jar <https://r3.bintray.com/corda/net/corda/corda/>`_
|
||||||
|
(under ``/VERSION_NUMBER/corda-VERSION_NUMBER.jar``) and place it in ``/opt/corda``
|
||||||
|
|
||||||
* Has at least two letters
|
3. Create a directory called ``plugins`` in ``/opt/corda`` and save your CorDapp jar file to it. Alternatively, download one of
|
||||||
* No leading or trailing whitespace
|
our `sample CorDapps <https://www.corda.net/samples/>`_ to the ``plugins`` directory
|
||||||
* No double-spacing
|
|
||||||
* Upper-case first letter
|
|
||||||
* Does not contain the words "node" or "server"
|
|
||||||
* Does not include the characters ',' or '=' or '$' or '"' or '\'' or '\\'
|
|
||||||
* Is in NFKC normalization form
|
|
||||||
* Only the latin, common and inherited unicode scripts are supported
|
|
||||||
|
|
||||||
The deployNodes task
|
4. Save the below as ``/opt/corda/node.conf``. See :doc:`corda-configuration-file` for a description of these options
|
||||||
--------------------
|
|
||||||
The CorDapp template defines a ``deployNodes`` task that allows you to automatically generate and configure a set of
|
|
||||||
nodes:
|
|
||||||
|
|
||||||
.. sourcecode:: groovy
|
.. code-block:: json
|
||||||
|
|
||||||
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
basedir : "/opt/corda"
|
||||||
directory "./build/nodes"
|
p2pAddress : "example.com:10002"
|
||||||
networkMap "O=Controller,L=London,C=GB"
|
rpcAddress : "example.com:10003"
|
||||||
node {
|
webAddress : "0.0.0.0:10004"
|
||||||
name "O=Controller,L=London,C=GB"
|
h2port : 11000
|
||||||
// The notary will offer a validating notary service.
|
emailAddress : "you@example.com"
|
||||||
notary = [validating : true]
|
myLegalName : "O=Bank of Breakfast Tea, L=London, C=GB"
|
||||||
p2pPort 10002
|
keyStorePassword : "cordacadevpass"
|
||||||
rpcPort 10003
|
trustStorePassword : "trustpass"
|
||||||
// No webport property, so no webserver will be created.
|
useHTTPS : false
|
||||||
h2Port 10004
|
devMode : false
|
||||||
sshdPort 22
|
networkMapService {
|
||||||
// Includes the corda-finance CorDapp on our node.
|
address="networkmap.foo.bar.com:10002"
|
||||||
cordapps = ["net.corda:corda-finance:$corda_release_version"]
|
legalName="O=FooBar NetworkMap, L=Dublin, C=IE"
|
||||||
|
}
|
||||||
|
rpcUsers=[
|
||||||
|
{
|
||||||
|
user=corda
|
||||||
|
password=portal_password
|
||||||
|
permissions=[
|
||||||
|
ALL
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
5. Make the following changes to ``/opt/corda/node.conf``:
|
||||||
|
|
||||||
|
* Change the ``p2pAddress`` and ``rpcAddress`` values to start with your server's hostname or external IP address.
|
||||||
|
This is the address other nodes or RPC interfaces will use to communicate with your node
|
||||||
|
* Change the ports if necessary, for example if you are running multiple nodes on one server (see below)
|
||||||
|
* Enter an email address which will be used as an administrative contact during the registration process. This is
|
||||||
|
only visible to the permissioning service
|
||||||
|
* Enter your node's desired legal name. This will be used during the issuance of your certificate and should rarely
|
||||||
|
change as it should represent the legal identity of your node
|
||||||
|
|
||||||
|
* Organization (``O=``) should be a unique and meaningful identifier (e.g. Bank of Breakfast Tea)
|
||||||
|
* Location (``L=``) is your nearest city
|
||||||
|
* Country (``C=``) is the `ISO 3166-1 alpha-2 code <https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2>`_
|
||||||
|
* Change the RPC username and password
|
||||||
|
|
||||||
|
6. Create a ``corda.service`` file based on the example below and save it in the ``/etc/systemd/system/`` directory
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
Description=Corda Node - Bank of Breakfast Tea
|
||||||
|
Requires=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=corda
|
||||||
|
WorkingDirectory=/opt/corda
|
||||||
|
ExecStart=/usr/bin/java -Xmx2048m -jar /opt/corda/corda.jar
|
||||||
|
Restart=on-failure
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
|
||||||
|
7. Make the following changes to ``corda.service``:
|
||||||
|
|
||||||
|
* Make sure the service description is informative - particularly if you plan to run multiple nodes.
|
||||||
|
* Change the username to the user account you want to use to run Corda. **We recommend that this is not root**
|
||||||
|
* Set the maximum amount of memory available to the Corda process by changing the ``-Xmx2048m`` parameter
|
||||||
|
* Make sure the ``corda.service`` file is owned by root with the correct permissions:
|
||||||
|
* ``sudo chown root:root /etc/systemd/system/corda.service``
|
||||||
|
* ``sudo chmod 644 /etc/systemd/system/corda.service``
|
||||||
|
|
||||||
|
.. note:: The Corda webserver provides a simple interface for interacting with your installed CorDapps in a browser.
|
||||||
|
Running the webserver is optional.
|
||||||
|
|
||||||
|
8. Create a ``corda-webserver.service`` file based on the example below and save it in the ``/etc/systemd/system/``
|
||||||
|
directory.
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
Description=Webserver for Corda Node - Bank of Breakfast Tea
|
||||||
|
Requires=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=username
|
||||||
|
WorkingDirectory=/opt/corda
|
||||||
|
ExecStart=/usr/bin/java -jar /opt/corda/corda-webserver.jar
|
||||||
|
Restart=on-failure
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
|
||||||
|
9. Provision the required certificates to your node. Contact the network permissioning service or see
|
||||||
|
:doc:`permissioning`
|
||||||
|
|
||||||
|
10. You can now start a node and its webserver by running the following ``systemctl`` commands:
|
||||||
|
|
||||||
|
* ``sudo systemctl daemon-reload``
|
||||||
|
* ``sudo systemctl corda start``
|
||||||
|
* ``sudo systemctl corda-webserver start``
|
||||||
|
|
||||||
|
You can run multiple nodes by creating multiple directories and Corda services, modifying the ``node.conf`` and
|
||||||
|
``service`` files so they are unique.
|
||||||
|
|
||||||
|
Windows: Installing and running Corda as a Windows service
|
||||||
|
----------------------------------------------------------
|
||||||
|
We recommend running Corda as a Windows service. This provides service handling, ensures the Corda service is run
|
||||||
|
at boot, and means the Corda service stays running with no users connected to the server.
|
||||||
|
|
||||||
|
**Prerequisites**:
|
||||||
|
|
||||||
|
* Oracle Java 8. The supported versions are listed in :doc:`getting-set-up`
|
||||||
|
|
||||||
|
1. Create a Corda directory and download the Corda jar. Replace ``VERSION_NUMBER`` with the desired version. Here's an
|
||||||
|
example using PowerShell:
|
||||||
|
|
||||||
|
.. code-block:: PowerShell
|
||||||
|
|
||||||
|
mkdir C:\Corda
|
||||||
|
wget http://jcenter.bintray.com/net/corda/corda/VERSION_NUMBER/corda-VERSION_NUMBER.jar -OutFile C:\Corda\corda.jar
|
||||||
|
|
||||||
|
2. Create a directory called ``plugins`` in ``/opt/corda`` and save your CorDapp jar file to it. Alternatively,
|
||||||
|
download one of our `sample CorDapps <https://www.corda.net/samples/>`_ to the ``plugins`` directory
|
||||||
|
|
||||||
|
3. Save the below as ``C:\Corda\node.conf``. See :doc:`corda-configuration-file` for a description of these options
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
basedir : "C:\\Corda"
|
||||||
|
p2pAddress : "example.com:10002"
|
||||||
|
rpcAddress : "example.com:10003"
|
||||||
|
webAddress : "0.0.0.0:10004"
|
||||||
|
h2port : 11000
|
||||||
|
emailAddress: "you@example.com"
|
||||||
|
myLegalName : "O=Bank of Breakfast Tea, L=London, C=GB"
|
||||||
|
keyStorePassword : "cordacadevpass"
|
||||||
|
trustStorePassword : "trustpass"
|
||||||
|
extraAdvertisedServiceIds: [ "" ]
|
||||||
|
useHTTPS : false
|
||||||
|
devMode : false
|
||||||
|
networkMapService {
|
||||||
|
address="networkmap.foo.bar.com:10002"
|
||||||
|
legalName="O=FooBar NetworkMap, L=Dublin, C=IE"
|
||||||
}
|
}
|
||||||
node {
|
rpcUsers=[
|
||||||
name "O=PartyA,L=London,C=GB"
|
{
|
||||||
advertisedServices = []
|
user=corda
|
||||||
p2pPort 10005
|
password=portal_password
|
||||||
rpcPort 10006
|
permissions=[
|
||||||
webPort 10007
|
ALL
|
||||||
h2Port 10008
|
]
|
||||||
sshdPort 22
|
}
|
||||||
cordapps = ["net.corda:corda-finance:$corda_release_version"]
|
]
|
||||||
// Grants user1 all RPC permissions.
|
|
||||||
rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]]
|
|
||||||
}
|
|
||||||
node {
|
|
||||||
name "O=PartyB,L=New York,C=US"
|
|
||||||
advertisedServices = []
|
|
||||||
p2pPort 10009
|
|
||||||
rpcPort 10010
|
|
||||||
webPort 10011
|
|
||||||
h2Port 10012
|
|
||||||
sshdPort 22
|
|
||||||
cordapps = ["net.corda:corda-finance:$corda_release_version"]
|
|
||||||
// Grants user1 the ability to start the MyFlow flow.
|
|
||||||
rpcUsers = [[ user: "user1", "password": "test", "permissions": ["StartFlow.net.corda.flows.MyFlow"]]]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Running this task will create three nodes in the ``build/nodes`` folder:
|
4. Make the following changes to ``C:\Corda\node.conf``:
|
||||||
|
|
||||||
* A ``Controller`` node that:
|
* Change the ``p2pAddress`` and ``rpcAddress`` values to start with your server's hostname or external IP address.
|
||||||
|
This is the address other nodes or RPC interfaces will use to communicate with your node
|
||||||
|
* Change the ports if necessary, for example if you are running multiple nodes on one server (see below)
|
||||||
|
* Enter an email address which will be used as an administrative contact during the registration process. This is
|
||||||
|
only visible to the permissioning service
|
||||||
|
* Enter your node's desired legal name. This will be used during the issuance of your certificate and should rarely
|
||||||
|
change as it should represent the legal identity of your node
|
||||||
|
|
||||||
* Serves as the network map
|
* Organization (``O=``) should be a unique and meaningful identifier (e.g. Bank of Breakfast Tea)
|
||||||
* Offers a validating notary service
|
* Location (``L=``) is your nearest city
|
||||||
* Will not have a webserver (since ``webPort`` is not defined)
|
* Country (``C=``) is the `ISO 3166-1 alpha-2 code <https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2>`_
|
||||||
* Is running the ``corda-finance`` CorDapp
|
* Change the RPC username and password
|
||||||
|
|
||||||
* ``PartyA`` and ``PartyB`` nodes that:
|
5. Copy the required Java keystores to the node. See :doc:`permissioning`
|
||||||
|
|
||||||
* Are pointing at the ``Controller`` as the network map service
|
6. Download the `NSSM service manager <nssm.cc>`_
|
||||||
* Are not offering any services
|
|
||||||
* Will have a webserver (since ``webPort`` is defined)
|
|
||||||
* Are running the ``corda-finance`` CorDapp
|
|
||||||
* Have an RPC user, ``user1``, that can be used to log into the node via RPC
|
|
||||||
|
|
||||||
Additionally, all three nodes will include any CorDapps defined in the project's source folders, even though these
|
7. Unzip ``nssm-2.24\win64\nssm.exe`` to ``C:\Corda``
|
||||||
CorDapps are not listed in each node's ``cordapps`` entry. This means that running the ``deployNodes`` task from the
|
|
||||||
template CorDapp, for example, would automatically build and add the template CorDapp to each node.
|
|
||||||
|
|
||||||
You can extend ``deployNodes`` to generate additional nodes. The only requirement is that you must specify
|
8. Save the following as ``C:\Corda\nssm.bat``:
|
||||||
a single node to run the network map service, by putting their name in the ``networkMap`` field.
|
|
||||||
|
|
||||||
.. warning:: When adding nodes, make sure that there are no port clashes!
|
.. code-block:: batch
|
||||||
|
|
||||||
Running deployNodes
|
nssm install cordanode1 C:\ProgramData\Oracle\Java\javapath\java.exe
|
||||||
-------------------
|
nssm set cordanode1 AppDirectory C:\Corda
|
||||||
To create the nodes defined in our ``deployNodes`` task, we'd run the following command in a terminal window from the
|
nssm set cordanode1 AppParameters "-jar corda.jar -Xmx2048m --config-file=C:\corda\node.conf"
|
||||||
root of the project:
|
nssm set cordanode1 AppStdout C:\Corda\service.log
|
||||||
|
nssm set cordanode1 AppStderr C:\Corda\service.log
|
||||||
|
nssm set cordanode1 Description Corda Node - Bank of Breakfast Tea
|
||||||
|
sc start cordanode1
|
||||||
|
|
||||||
* Unix/Mac OSX: ``./gradlew deployNodes``
|
9. Modify the batch file:
|
||||||
* Windows: ``gradlew.bat deployNodes``
|
|
||||||
|
|
||||||
This will create the nodes in the ``build/nodes`` folder.
|
* If you are installing multiple nodes, use a different service name (``cordanode1``) for each node
|
||||||
|
* Set the amount of Java heap memory available to this node by modifying the -Xmx argument
|
||||||
|
* Set an informative description
|
||||||
|
|
||||||
.. note:: During the build process each node generates a NodeInfo file which is written in its own root directory,
|
10. Run the batch file by clicking on it or from a command prompt
|
||||||
the plug-in proceeds and copies each node NodeInfo to every other node ``additional-node-infos`` directory.
|
|
||||||
The NodeInfo file contains a node hostname and port, legal name and security certificate.
|
|
||||||
|
|
||||||
There will be a node folder generated for each node you defined, plus a ``runnodes`` shell script (or batch file on
|
11. Run ``services.msc`` and verify that a service called ``cordanode1`` is present and running
|
||||||
Windows) to run all the nodes at once. If you make any changes to your ``deployNodes`` task, you will need to re-run
|
|
||||||
the task to see the changes take effect.
|
|
||||||
|
|
||||||
You can now run the nodes by following the instructions in :doc:`Running a node <running-a-node>`.
|
12. Run ``netstat -ano`` and check for the ports you configured in ``node.conf``
|
||||||
|
|
||||||
|
13. You may need to open the ports on the Windows firewall
|
||||||
|
|
||||||
|
Testing your installation
|
||||||
|
-------------------------
|
||||||
|
You can verify Corda is running by connecting to your RPC port from another host, e.g.:
|
||||||
|
|
||||||
|
``telnet your-hostname.example.com 10002``
|
||||||
|
|
||||||
|
If you receive the message "Escape character is ^]", Corda is running and accessible. Press Ctrl-] and Ctrl-D to exit
|
||||||
|
telnet.
|
@ -28,7 +28,7 @@ class CustomVaultQueryTest {
|
|||||||
mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.finance", "net.corda.docs"))
|
mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.finance", "net.corda.docs"))
|
||||||
nodeA = mockNet.createPartyNode()
|
nodeA = mockNet.createPartyNode()
|
||||||
nodeB = mockNet.createPartyNode()
|
nodeB = mockNet.createPartyNode()
|
||||||
nodeA.internals.registerInitiatedFlow(TopupIssuerFlow.TopupIssuer::class.java)
|
nodeA.registerInitiatedFlow(TopupIssuerFlow.TopupIssuer::class.java)
|
||||||
notary = mockNet.defaultNotaryIdentity
|
notary = mockNet.defaultNotaryIdentity
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ class FxTransactionBuildTutorialTest {
|
|||||||
mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.finance"))
|
mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.finance"))
|
||||||
nodeA = mockNet.createPartyNode()
|
nodeA = mockNet.createPartyNode()
|
||||||
nodeB = mockNet.createPartyNode()
|
nodeB = mockNet.createPartyNode()
|
||||||
nodeB.internals.registerInitiatedFlow(ForeignExchangeRemoteFlow::class.java)
|
nodeB.registerInitiatedFlow(ForeignExchangeRemoteFlow::class.java)
|
||||||
notary = mockNet.defaultNotaryIdentity
|
notary = mockNet.defaultNotaryIdentity
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ class WorkflowTransactionBuildTutorialTest {
|
|||||||
mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.docs"))
|
mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.docs"))
|
||||||
val aliceNode = mockNet.createPartyNode(ALICE_NAME)
|
val aliceNode = mockNet.createPartyNode(ALICE_NAME)
|
||||||
val bobNode = mockNet.createPartyNode(BOB_NAME)
|
val bobNode = mockNet.createPartyNode(BOB_NAME)
|
||||||
aliceNode.internals.registerInitiatedFlow(RecordCompletionFlow::class.java)
|
aliceNode.registerInitiatedFlow(RecordCompletionFlow::class.java)
|
||||||
aliceServices = aliceNode.services
|
aliceServices = aliceNode.services
|
||||||
bobServices = bobNode.services
|
bobServices = bobNode.services
|
||||||
alice = aliceNode.services.myInfo.identityFromX500Name(ALICE_NAME)
|
alice = aliceNode.services.myInfo.identityFromX500Name(ALICE_NAME)
|
||||||
|
136
docs/source/generating-a-node.rst
Normal file
136
docs/source/generating-a-node.rst
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
Creating nodes locally
|
||||||
|
======================
|
||||||
|
|
||||||
|
.. contents::
|
||||||
|
|
||||||
|
Node structure
|
||||||
|
--------------
|
||||||
|
Each Corda node has the following structure:
|
||||||
|
|
||||||
|
.. sourcecode:: none
|
||||||
|
|
||||||
|
.
|
||||||
|
├── certificates // The node's certificates
|
||||||
|
├── corda-webserver.jar // The built-in node webserver
|
||||||
|
├── corda.jar // The core Corda libraries
|
||||||
|
├── logs // The node logs
|
||||||
|
├── node.conf // The node's configuration files
|
||||||
|
├── persistence.mv.db // The node's database
|
||||||
|
└── cordapps // The CorDapps jars installed on the node
|
||||||
|
|
||||||
|
The node is configured by editing its ``node.conf`` file. You install CorDapps on the node by dropping the CorDapp JARs
|
||||||
|
into the ``cordapps`` folder.
|
||||||
|
|
||||||
|
Node naming
|
||||||
|
-----------
|
||||||
|
A node's name must be a valid X.500 name that obeys the following additional constraints:
|
||||||
|
|
||||||
|
* The fields of the name have the following maximum character lengths:
|
||||||
|
|
||||||
|
* Common name: 64
|
||||||
|
* Organisation: 128
|
||||||
|
* Organisation unit: 64
|
||||||
|
* Locality: 64
|
||||||
|
* State: 64
|
||||||
|
|
||||||
|
* The country code is a valid ISO 3166-1 two letter code in upper-case
|
||||||
|
|
||||||
|
* The organisation, locality and country attributes are present
|
||||||
|
|
||||||
|
* The organisation field of the name obeys the following constraints:
|
||||||
|
|
||||||
|
* Has at least two letters
|
||||||
|
* No leading or trailing whitespace
|
||||||
|
* No double-spacing
|
||||||
|
* Upper-case first letter
|
||||||
|
* Does not contain the words "node" or "server"
|
||||||
|
* Does not include the characters ',' or '=' or '$' or '"' or '\'' or '\\'
|
||||||
|
* Is in NFKC normalization form
|
||||||
|
* Only the latin, common and inherited unicode scripts are supported
|
||||||
|
|
||||||
|
The Cordform task
|
||||||
|
-----------------
|
||||||
|
Corda provides a gradle plugin called ``Cordform`` that allows you to automatically generate and configure a set of
|
||||||
|
nodes. Here is an example ``Cordform`` task called ``deployNodes`` that creates three nodes, defined in the
|
||||||
|
`Kotlin CorDapp Template <https://github.com/corda/cordapp-template-kotlin/blob/release-V2/build.gradle#L97>`_:
|
||||||
|
|
||||||
|
.. sourcecode:: groovy
|
||||||
|
|
||||||
|
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
||||||
|
directory "./build/nodes"
|
||||||
|
networkMap "O=Controller,L=London,C=GB"
|
||||||
|
node {
|
||||||
|
name "O=Controller,L=London,C=GB"
|
||||||
|
// The notary will offer a validating notary service.
|
||||||
|
notary = [validating : true]
|
||||||
|
p2pPort 10002
|
||||||
|
rpcPort 10003
|
||||||
|
// No webport property, so no webserver will be created.
|
||||||
|
h2Port 10004
|
||||||
|
// Includes the corda-finance CorDapp on our node.
|
||||||
|
cordapps = ["net.corda:corda-finance:$corda_release_version"]
|
||||||
|
}
|
||||||
|
node {
|
||||||
|
name "O=PartyA,L=London,C=GB"
|
||||||
|
advertisedServices = []
|
||||||
|
p2pPort 10005
|
||||||
|
rpcPort 10006
|
||||||
|
webPort 10007
|
||||||
|
h2Port 10008
|
||||||
|
cordapps = ["net.corda:corda-finance:$corda_release_version"]
|
||||||
|
// Grants user1 all RPC permissions.
|
||||||
|
rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]]
|
||||||
|
}
|
||||||
|
node {
|
||||||
|
name "O=PartyB,L=New York,C=US"
|
||||||
|
advertisedServices = []
|
||||||
|
p2pPort 10009
|
||||||
|
rpcPort 10010
|
||||||
|
webPort 10011
|
||||||
|
h2Port 10012
|
||||||
|
cordapps = ["net.corda:corda-finance:$corda_release_version"]
|
||||||
|
// Grants user1 the ability to start the MyFlow flow.
|
||||||
|
rpcUsers = [[ user: "user1", "password": "test", "permissions": ["StartFlow.net.corda.flows.MyFlow"]]]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Running this task will create three nodes in the ``build/nodes`` folder:
|
||||||
|
|
||||||
|
* A ``Controller`` node that:
|
||||||
|
|
||||||
|
* Serves as the network map
|
||||||
|
* Offers a validating notary service
|
||||||
|
* Will not have a webserver (since ``webPort`` is not defined)
|
||||||
|
* Is running the ``corda-finance`` CorDapp
|
||||||
|
|
||||||
|
* ``PartyA`` and ``PartyB`` nodes that:
|
||||||
|
|
||||||
|
* Are pointing at the ``Controller`` as the network map service
|
||||||
|
* Are not offering any services
|
||||||
|
* Will have a webserver (since ``webPort`` is defined)
|
||||||
|
* Are running the ``corda-finance`` CorDapp
|
||||||
|
* Have an RPC user, ``user1``, that can be used to log into the node via RPC
|
||||||
|
|
||||||
|
Additionally, all three nodes will include any CorDapps defined in the project's source folders, even though these
|
||||||
|
CorDapps are not listed in each node's ``cordapps`` entry. This means that running the ``deployNodes`` task from the
|
||||||
|
template CorDapp, for example, would automatically build and add the template CorDapp to each node.
|
||||||
|
|
||||||
|
You can extend ``deployNodes`` to generate additional nodes. The only requirement is that you must specify
|
||||||
|
a single node to run the network map service, by putting its name in the ``networkMap`` field.
|
||||||
|
|
||||||
|
.. warning:: When adding nodes, make sure that there are no port clashes!
|
||||||
|
|
||||||
|
Running deployNodes
|
||||||
|
-------------------
|
||||||
|
To create the nodes defined in our ``deployNodes`` task, run the following command in a terminal window from the root
|
||||||
|
of the project where the ``deployNodes`` task is defined:
|
||||||
|
|
||||||
|
* Linux/macOS: ``./gradlew deployNodes``
|
||||||
|
* Windows: ``gradlew.bat deployNodes``
|
||||||
|
|
||||||
|
This will create the nodes in the ``build/nodes`` folder. There will be a node folder generated for each node defined
|
||||||
|
in the ``deployNodes`` task, plus a ``runnodes`` shell script (or batch file on Windows) to run all the nodes at once
|
||||||
|
for testing and development purposes. If you make any changes to your CorDapp source or ``deployNodes`` task, you will
|
||||||
|
need to re-run the task to see the changes take effect.
|
||||||
|
|
||||||
|
You can now run the nodes by following the instructions in :doc:`Running a node <running-a-node>`.
|
@ -1,25 +1,49 @@
|
|||||||
Running a node
|
Running nodes locally
|
||||||
==============
|
=====================
|
||||||
|
|
||||||
Starting your node
|
.. contents::
|
||||||
------------------
|
|
||||||
After following the steps in :doc:`deploying-a-node`, you should have deployed your node(s) with any chosen CorDapps
|
.. note:: You should already have generated your node(s) with their CorDapps installed by following the instructions in
|
||||||
already installed. You run each node by navigating to ``<node_dir>`` in a terminal window and running:
|
:doc:`generating-a-node`.
|
||||||
|
|
||||||
|
There are several ways to run a Corda node locally for testing purposes.
|
||||||
|
|
||||||
|
Starting all nodes at once
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
.. note:: ``runnodes`` is a shell script (or batch file on Windows) that is generated by ``deployNodes`` to allow you
|
||||||
|
to quickly start up all nodes and their webservers. ``runnodes`` should only be used for testing purposes.
|
||||||
|
|
||||||
|
Start the nodes with ``runnodes`` by running the following command from the root of the project:
|
||||||
|
|
||||||
|
* Linux/macOS: ``build/nodes/runnodes``
|
||||||
|
* Windows: ``call build\nodes\runnodes.bat``
|
||||||
|
|
||||||
|
.. warn:: On macOS, do not click/change focus until all the node terminal windows have opened, or some processes may
|
||||||
|
fail to start.
|
||||||
|
|
||||||
|
Starting an individual Corda node
|
||||||
|
---------------------------------
|
||||||
|
Run the node by opening a terminal window in the node's folder and running:
|
||||||
|
|
||||||
.. code-block:: shell
|
.. code-block:: shell
|
||||||
|
|
||||||
java -jar corda.jar
|
java -jar corda.jar
|
||||||
|
|
||||||
.. warning:: If your working directory is not ``<node_dir>`` your cordapps and configuration will not be used.
|
.. warning:: By default, the node will look for a configuration file called ``node.conf`` and a CorDapps folder called
|
||||||
|
``cordapps`` in the current working directory. You can override the configuration file and workspace paths on the
|
||||||
|
command line (e.g. ``./corda.jar --config-file=test.conf --base-directory=/opt/r3corda/nodes/test``).
|
||||||
|
|
||||||
The configuration file and workspace paths can be overridden on the command line. For example:
|
Optionally run the node's webserver as well by opening a terminal window in the node's folder and running:
|
||||||
|
|
||||||
``./corda.jar --config-file=test.conf --base-directory=/opt/r3corda/nodes/test``.
|
.. code-block:: shell
|
||||||
|
|
||||||
Otherwise the workspace folder for the node is the current working path.
|
java -jar corda-webserver.jar
|
||||||
|
|
||||||
Debugging your node
|
.. warning:: The node webserver is for testing purposes only and will be removed soon.
|
||||||
-------------------
|
|
||||||
|
Starting a node with remote debugging enabled
|
||||||
|
---------------------------------------------
|
||||||
To enable remote debugging of the node, run the following from the terminal window:
|
To enable remote debugging of the node, run the following from the terminal window:
|
||||||
|
|
||||||
``java -Dcapsule.jvm.args="-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005" -jar corda.jar``
|
``java -Dcapsule.jvm.args="-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005" -jar corda.jar``
|
||||||
|
@ -137,7 +137,7 @@ which could be represented as ``{ first: foo, second: 123 }``.
|
|||||||
|
|
||||||
.. note:: If your CorDapp is written in Java,
|
.. note:: If your CorDapp is written in Java,
|
||||||
named arguments won't work unless you compiled using the ``-parameters`` argument to javac.
|
named arguments won't work unless you compiled using the ``-parameters`` argument to javac.
|
||||||
See :doc:`deploying-a-node` for how to specify it via Gradle.
|
See :doc:`generating-a-node` for how to specify it via Gradle.
|
||||||
|
|
||||||
The same syntax is also used to specify the parameters for RPCs, accessed via the ``run`` command, like this:
|
The same syntax is also used to specify the parameters for RPCs, accessed via the ``run`` command, like this:
|
||||||
|
|
||||||
|
@ -26,7 +26,8 @@ for gradle and IntelliJ, but it's possible this option is not present in your en
|
|||||||
"No matching constructor found: - [arg0: int, arg1: Party]: missing parameter arg0"
|
"No matching constructor found: - [arg0: int, arg1: Party]: missing parameter arg0"
|
||||||
***********************************************************************************
|
***********************************************************************************
|
||||||
|
|
||||||
Your CorDapp is written in Java and you haven't specified the ``-parameters`` compiler argument. See :doc:`deploying-a-node` for how it can be done using Gradle.
|
Your CorDapp is written in Java and you haven't specified the ``-parameters`` compiler argument. See
|
||||||
|
:doc:`generating-a-node` for how it can be done using Gradle.
|
||||||
|
|
||||||
IDEA issues
|
IDEA issues
|
||||||
-----------
|
-----------
|
||||||
|
@ -176,7 +176,7 @@ There are two ways to run the example CorDapp:
|
|||||||
* Via IntelliJ
|
* Via IntelliJ
|
||||||
|
|
||||||
In both cases, we will deploy a set of test nodes with our CorDapp installed, then run the nodes. You can read more
|
In both cases, we will deploy a set of test nodes with our CorDapp installed, then run the nodes. You can read more
|
||||||
about how we define the nodes to be deployed :doc:`here <deploying-a-node>`.
|
about how we define the nodes to be deployed :doc:`here <generating-a-node>`.
|
||||||
|
|
||||||
Terminal
|
Terminal
|
||||||
~~~~~~~~
|
~~~~~~~~
|
||||||
|
@ -13,7 +13,7 @@ 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.testing.*
|
import net.corda.testing.*
|
||||||
import net.corda.testing.contracts.fillWithSomeTestCash
|
import net.corda.testing.contracts.VaultFiller
|
||||||
import net.corda.testing.node.MockServices
|
import net.corda.testing.node.MockServices
|
||||||
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseAndMockServices
|
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseAndMockServices
|
||||||
import org.junit.Ignore
|
import org.junit.Ignore
|
||||||
@ -240,7 +240,7 @@ class CommercialPaperTestsGeneric {
|
|||||||
aliceVaultService = aliceServices.vaultService
|
aliceVaultService = aliceServices.vaultService
|
||||||
|
|
||||||
databaseAlice.transaction {
|
databaseAlice.transaction {
|
||||||
alicesVault = aliceServices.fillWithSomeTestCash(9000.DOLLARS, issuerServices, atLeastThisManyStates = 1, atMostThisManyStates = 1, issuedBy = DUMMY_CASH_ISSUER)
|
alicesVault = VaultFiller(aliceServices, DUMMY_NOTARY, DUMMY_NOTARY_KEY, rngFactory = ::Random).fillWithSomeTestCash(9000.DOLLARS, issuerServices, 1, DUMMY_CASH_ISSUER)
|
||||||
aliceVaultService = aliceServices.vaultService
|
aliceVaultService = aliceServices.vaultService
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,7 +250,7 @@ class CommercialPaperTestsGeneric {
|
|||||||
bigCorpVaultService = bigCorpServices.vaultService
|
bigCorpVaultService = bigCorpServices.vaultService
|
||||||
|
|
||||||
databaseBigCorp.transaction {
|
databaseBigCorp.transaction {
|
||||||
bigCorpVault = bigCorpServices.fillWithSomeTestCash(13000.DOLLARS, issuerServices, atLeastThisManyStates = 1, atMostThisManyStates = 1, issuedBy = DUMMY_CASH_ISSUER)
|
bigCorpVault = VaultFiller(bigCorpServices, DUMMY_NOTARY, DUMMY_NOTARY_KEY, rngFactory = ::Random).fillWithSomeTestCash(13000.DOLLARS, issuerServices, 1, DUMMY_CASH_ISSUER)
|
||||||
bigCorpVaultService = bigCorpServices.vaultService
|
bigCorpVaultService = bigCorpServices.vaultService
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,10 @@ package net.corda.finance.contracts.asset
|
|||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.crypto.generateKeyPair
|
import net.corda.core.crypto.generateKeyPair
|
||||||
import net.corda.core.identity.*
|
import net.corda.core.identity.AbstractParty
|
||||||
|
import net.corda.core.identity.AnonymousParty
|
||||||
|
import net.corda.core.identity.CordaX500Name
|
||||||
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.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
|
||||||
@ -16,10 +19,10 @@ import net.corda.finance.utils.sumCashBy
|
|||||||
import net.corda.finance.utils.sumCashOrNull
|
import net.corda.finance.utils.sumCashOrNull
|
||||||
import net.corda.finance.utils.sumCashOrZero
|
import net.corda.finance.utils.sumCashOrZero
|
||||||
import net.corda.node.services.vault.NodeVaultService
|
import net.corda.node.services.vault.NodeVaultService
|
||||||
import net.corda.node.utilities.CordaPersistence
|
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||||
import net.corda.testing.*
|
import net.corda.testing.*
|
||||||
import net.corda.testing.contracts.DummyState
|
import net.corda.testing.contracts.DummyState
|
||||||
import net.corda.testing.contracts.fillWithSomeTestCash
|
import net.corda.testing.contracts.VaultFiller
|
||||||
import net.corda.testing.node.MockServices
|
import net.corda.testing.node.MockServices
|
||||||
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseAndMockServices
|
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseAndMockServices
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
@ -81,15 +84,12 @@ class CashTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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.
|
||||||
database.transaction {
|
database.transaction {
|
||||||
ourServices.fillWithSomeTestCash(howMuch = 100.DOLLARS, atLeastThisManyStates = 1, atMostThisManyStates = 1,
|
val vaultFiller = VaultFiller(ourServices, DUMMY_NOTARY, DUMMY_NOTARY_KEY, rngFactory = ::Random)
|
||||||
owner = ourIdentity, issuedBy = MEGA_CORP.ref(1), issuerServices = megaCorpServices)
|
vaultFiller.fillWithSomeTestCash(100.DOLLARS, megaCorpServices, 1, MEGA_CORP.ref(1), ourIdentity)
|
||||||
ourServices.fillWithSomeTestCash(howMuch = 400.DOLLARS, atLeastThisManyStates = 1, atMostThisManyStates = 1,
|
vaultFiller.fillWithSomeTestCash(400.DOLLARS, megaCorpServices, 1, MEGA_CORP.ref(1), ourIdentity)
|
||||||
owner = ourIdentity, issuedBy = MEGA_CORP.ref(1), issuerServices = megaCorpServices)
|
vaultFiller.fillWithSomeTestCash(80.DOLLARS, miniCorpServices, 1, MINI_CORP.ref(1), ourIdentity)
|
||||||
ourServices.fillWithSomeTestCash(howMuch = 80.DOLLARS, atLeastThisManyStates = 1, atMostThisManyStates = 1,
|
vaultFiller.fillWithSomeTestCash(80.SWISS_FRANCS, miniCorpServices, 1, MINI_CORP.ref(1), ourIdentity)
|
||||||
owner = ourIdentity, issuedBy = MINI_CORP.ref(1), issuerServices = miniCorpServices)
|
|
||||||
ourServices.fillWithSomeTestCash(howMuch = 80.SWISS_FRANCS, atLeastThisManyStates = 1, atMostThisManyStates = 1,
|
|
||||||
owner = ourIdentity, issuedBy = MINI_CORP.ref(1), issuerServices = miniCorpServices)
|
|
||||||
}
|
}
|
||||||
database.transaction {
|
database.transaction {
|
||||||
vaultStatesUnconsumed = ourServices.vaultService.queryBy<Cash.State>().states
|
vaultStatesUnconsumed = ourServices.vaultService.queryBy<Cash.State>().states
|
||||||
|
@ -10,6 +10,7 @@ buildscript {
|
|||||||
ext.gradle_plugins_version = constants.getProperty("gradlePluginsVersion")
|
ext.gradle_plugins_version = constants.getProperty("gradlePluginsVersion")
|
||||||
ext.bouncycastle_version = constants.getProperty("bouncycastleVersion")
|
ext.bouncycastle_version = constants.getProperty("bouncycastleVersion")
|
||||||
ext.typesafe_config_version = constants.getProperty("typesafeConfigVersion")
|
ext.typesafe_config_version = constants.getProperty("typesafeConfigVersion")
|
||||||
|
ext.jsr305_version = constants.getProperty("jsr305Version")
|
||||||
ext.kotlin_version = constants.getProperty("kotlinVersion")
|
ext.kotlin_version = constants.getProperty("kotlinVersion")
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
|
@ -11,6 +11,9 @@ version gradle_plugins_version
|
|||||||
group 'net.corda.plugins'
|
group 'net.corda.plugins'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
// JSR 305: Nullability annotations
|
||||||
|
compile "com.google.code.findbugs:jsr305:$jsr305_version"
|
||||||
|
|
||||||
// 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"
|
||||||
|
|
||||||
|
@ -1,24 +1,40 @@
|
|||||||
package net.corda.cordform;
|
package net.corda.cordform;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public abstract class CordformDefinition {
|
public abstract class CordformDefinition {
|
||||||
public final Path driverDirectory;
|
private Path nodesDirectory = Paths.get("build", "nodes");
|
||||||
public final ArrayList<Consumer<? super CordformNode>> nodeConfigurers = new ArrayList<>();
|
private final List<Consumer<CordformNode>> nodeConfigurers = new ArrayList<>();
|
||||||
|
private final List<String> cordappPackages = new ArrayList<>();
|
||||||
|
|
||||||
public CordformDefinition(Path driverDirectory) {
|
public Path getNodesDirectory() {
|
||||||
this.driverDirectory = driverDirectory;
|
return nodesDirectory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addNode(Consumer<? super CordformNode> configurer) {
|
public void setNodesDirectory(Path nodesDirectory) {
|
||||||
|
this.nodesDirectory = nodesDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Consumer<CordformNode>> getNodeConfigurers() {
|
||||||
|
return nodeConfigurers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addNode(Consumer<CordformNode> configurer) {
|
||||||
nodeConfigurers.add(configurer);
|
nodeConfigurers.add(configurer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<String> getCordappPackages() {
|
||||||
|
return cordappPackages;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make arbitrary changes to the node directories before they are started.
|
* Make arbitrary changes to the node directories before they are started.
|
||||||
* @param context Lookup of node directory by node name.
|
* @param context Lookup of node directory by node name.
|
||||||
*/
|
*/
|
||||||
public abstract void setup(CordformContext context);
|
public abstract void setup(@Nonnull CordformContext context);
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,9 @@ package net.corda.cordform;
|
|||||||
|
|
||||||
import static java.util.Collections.emptyList;
|
import static java.util.Collections.emptyList;
|
||||||
import com.typesafe.config.*;
|
import com.typesafe.config.*;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -54,7 +57,7 @@ public class CordformNode implements NodeDefinition {
|
|||||||
*/
|
*/
|
||||||
public void name(String name) {
|
public void name(String name) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
config = config.withValue("myLegalName", ConfigValueFactory.fromAnyRef(name));
|
setValue("myLegalName", name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -62,6 +65,7 @@ public class CordformNode implements NodeDefinition {
|
|||||||
*
|
*
|
||||||
* @return This node's P2P address.
|
* @return This node's P2P address.
|
||||||
*/
|
*/
|
||||||
|
@Nonnull
|
||||||
public String getP2pAddress() {
|
public String getP2pAddress() {
|
||||||
return config.getString("p2pAddress");
|
return config.getString("p2pAddress");
|
||||||
}
|
}
|
||||||
@ -71,8 +75,8 @@ public class CordformNode implements NodeDefinition {
|
|||||||
*
|
*
|
||||||
* @param p2pPort The Artemis messaging queue port.
|
* @param p2pPort The Artemis messaging queue port.
|
||||||
*/
|
*/
|
||||||
public void p2pPort(Integer p2pPort) {
|
public void p2pPort(int p2pPort) {
|
||||||
config = config.withValue("p2pAddress", ConfigValueFactory.fromAnyRef(DEFAULT_HOST + ':' + p2pPort));
|
p2pAddress(DEFAULT_HOST + ':' + p2pPort);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -81,7 +85,15 @@ public class CordformNode implements NodeDefinition {
|
|||||||
* @param p2pAddress The Artemis messaging queue host and port.
|
* @param p2pAddress The Artemis messaging queue host and port.
|
||||||
*/
|
*/
|
||||||
public void p2pAddress(String p2pAddress) {
|
public void p2pAddress(String p2pAddress) {
|
||||||
config = config.withValue("p2pAddress", ConfigValueFactory.fromAnyRef(p2pAddress));
|
setValue("p2pAddress", p2pAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the RPC address for this node, or null if one hasn't been specified.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public String getRpcAddress() {
|
||||||
|
return getOptionalString("rpcAddress");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -89,8 +101,8 @@ public class CordformNode implements NodeDefinition {
|
|||||||
*
|
*
|
||||||
* @param rpcPort The Artemis RPC queue port.
|
* @param rpcPort The Artemis RPC queue port.
|
||||||
*/
|
*/
|
||||||
public void rpcPort(Integer rpcPort) {
|
public void rpcPort(int rpcPort) {
|
||||||
config = config.withValue("rpcAddress", ConfigValueFactory.fromAnyRef(DEFAULT_HOST + ':' + rpcPort));
|
rpcAddress(DEFAULT_HOST + ':' + rpcPort);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -99,7 +111,31 @@ public class CordformNode implements NodeDefinition {
|
|||||||
* @param rpcAddress The Artemis RPC queue host and port.
|
* @param rpcAddress The Artemis RPC queue host and port.
|
||||||
*/
|
*/
|
||||||
public void rpcAddress(String rpcAddress) {
|
public void rpcAddress(String rpcAddress) {
|
||||||
config = config.withValue("rpcAddress", ConfigValueFactory.fromAnyRef(rpcAddress));
|
setValue("rpcAddress", rpcAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the address of the web server that will connect to the node, or null if one hasn't been specified.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public String getWebAddress() {
|
||||||
|
return getOptionalString("webAddress");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure a webserver to connect to the node via RPC. This port will specify the port it will listen on. The node
|
||||||
|
* must have an RPC address configured.
|
||||||
|
*/
|
||||||
|
public void webPort(int webPort) {
|
||||||
|
webAddress(DEFAULT_HOST + ':' + webPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure a webserver to connect to the node via RPC. This address will specify the port it will listen on. The node
|
||||||
|
* must have an RPC address configured.
|
||||||
|
*/
|
||||||
|
public void webAddress(String webAddress) {
|
||||||
|
setValue("webAddress", webAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -108,6 +144,14 @@ public class CordformNode implements NodeDefinition {
|
|||||||
* @param configFile The file path.
|
* @param configFile The file path.
|
||||||
*/
|
*/
|
||||||
public void configFile(String configFile) {
|
public void configFile(String configFile) {
|
||||||
config = config.withValue("configFile", ConfigValueFactory.fromAnyRef(configFile));
|
setValue("configFile", configFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getOptionalString(String path) {
|
||||||
|
return config.hasPath(path) ? config.getString(path) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setValue(String path, Object value) {
|
||||||
|
config = config.withValue(path, ConfigValueFactory.fromAnyRef(value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,10 +11,10 @@ import org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME
|
|||||||
import org.gradle.api.tasks.TaskAction
|
import org.gradle.api.tasks.TaskAction
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.net.URLClassLoader
|
import java.net.URLClassLoader
|
||||||
import java.nio.file.Files
|
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
import java.util.jar.JarInputStream
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates nodes based on the configuration of this task in the gradle configuration DSL.
|
* Creates nodes based on the configuration of this task in the gradle configuration DSL.
|
||||||
@ -23,12 +23,16 @@ import java.util.concurrent.TimeUnit
|
|||||||
*/
|
*/
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
open class Cordform : DefaultTask() {
|
open class Cordform : DefaultTask() {
|
||||||
|
private companion object {
|
||||||
|
private val defaultDirectory: Path = Paths.get("build", "nodes")
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Optionally the name of a CordformDefinition subclass to which all configuration will be delegated.
|
* Optionally the name of a CordformDefinition subclass to which all configuration will be delegated.
|
||||||
*/
|
*/
|
||||||
@Suppress("MemberVisibilityCanPrivate")
|
@Suppress("MemberVisibilityCanPrivate")
|
||||||
var definitionClass: String? = null
|
var definitionClass: String? = null
|
||||||
private var directory = Paths.get("build", "nodes")
|
private var directory = defaultDirectory
|
||||||
private val nodes = mutableListOf<Node>()
|
private val nodes = mutableListOf<Node>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -116,7 +120,6 @@ open class Cordform : DefaultTask() {
|
|||||||
/**
|
/**
|
||||||
* 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.
|
||||||
*/
|
*/
|
||||||
@Suppress("unused")
|
|
||||||
@TaskAction
|
@TaskAction
|
||||||
fun build() {
|
fun build() {
|
||||||
project.logger.info("Running Cordform task")
|
project.logger.info("Running Cordform task")
|
||||||
@ -129,10 +132,18 @@ open class Cordform : DefaultTask() {
|
|||||||
private fun initializeConfiguration() {
|
private fun initializeConfiguration() {
|
||||||
if (definitionClass != null) {
|
if (definitionClass != null) {
|
||||||
val cd = loadCordformDefinition()
|
val cd = loadCordformDefinition()
|
||||||
|
// If the user has specified their own directory (even if it's the same default path) then let them know
|
||||||
|
// it's not used and should just rely on the one in CordformDefinition
|
||||||
|
require(directory === defaultDirectory) {
|
||||||
|
"'directory' cannot be used when 'definitionClass' is specified. Use CordformDefinition.nodesDirectory instead."
|
||||||
|
}
|
||||||
|
directory = cd.nodesDirectory
|
||||||
|
val cordapps = cd.getMatchingCordapps()
|
||||||
cd.nodeConfigurers.forEach {
|
cd.nodeConfigurers.forEach {
|
||||||
val node = node { }
|
val node = node { }
|
||||||
it.accept(node)
|
it.accept(node)
|
||||||
node.rootDir(directory)
|
node.rootDir(directory)
|
||||||
|
node.installCordapps(cordapps)
|
||||||
}
|
}
|
||||||
cd.setup { nodeName -> project.projectDir.toPath().resolve(getNodeByName(nodeName)!!.nodeDir.toPath()) }
|
cd.setup { nodeName -> project.projectDir.toPath().resolve(getNodeByName(nodeName)!!.nodeDir.toPath()) }
|
||||||
} else {
|
} else {
|
||||||
@ -142,6 +153,30 @@ open class Cordform : DefaultTask() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun CordformDefinition.getMatchingCordapps(): List<File> {
|
||||||
|
val cordappJars = project.configuration("cordapp").files
|
||||||
|
return cordappPackages.map { `package` ->
|
||||||
|
val cordappsWithPackage = cordappJars.filter { it.containsPackage(`package`) }
|
||||||
|
when (cordappsWithPackage.size) {
|
||||||
|
0 -> throw IllegalArgumentException("There are no cordapp dependencies containing the package $`package`")
|
||||||
|
1 -> cordappsWithPackage[0]
|
||||||
|
else -> throw IllegalArgumentException("More than one cordapp dependency contains the package $`package`: $cordappsWithPackage")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun File.containsPackage(`package`: String): Boolean {
|
||||||
|
JarInputStream(inputStream()).use {
|
||||||
|
while (true) {
|
||||||
|
val name = it.nextJarEntry?.name ?: break
|
||||||
|
if (name.endsWith(".class") && name.replace('/', '.').startsWith(`package`)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun generateAndInstallNodeInfos() {
|
private fun generateAndInstallNodeInfos() {
|
||||||
generateNodeInfos()
|
generateNodeInfos()
|
||||||
installNodeInfos()
|
installNodeInfos()
|
||||||
@ -149,7 +184,7 @@ open class Cordform : DefaultTask() {
|
|||||||
|
|
||||||
private fun generateNodeInfos() {
|
private fun generateNodeInfos() {
|
||||||
project.logger.info("Generating node infos")
|
project.logger.info("Generating node infos")
|
||||||
var nodeProcesses = buildNodeProcesses()
|
val nodeProcesses = buildNodeProcesses()
|
||||||
try {
|
try {
|
||||||
validateNodeProcessess(nodeProcesses)
|
validateNodeProcessess(nodeProcesses)
|
||||||
} finally {
|
} finally {
|
||||||
@ -177,7 +212,7 @@ open class Cordform : DefaultTask() {
|
|||||||
|
|
||||||
private fun buildNodeProcess(node: Node): Pair<Node, Process> {
|
private fun buildNodeProcess(node: Node): Pair<Node, Process> {
|
||||||
node.makeLogDirectory()
|
node.makeLogDirectory()
|
||||||
var process = ProcessBuilder(generateNodeInfoCommand())
|
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.
|
||||||
@ -224,6 +259,8 @@ open class Cordform : DefaultTask() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Node.logFile(): Path = this.logDirectory().resolve("generate-info.log")
|
private fun Node.logFile(): Path = this.logDirectory().resolve("generate-info.log")
|
||||||
|
|
||||||
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) }
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ package net.corda.plugins
|
|||||||
import com.typesafe.config.*
|
import com.typesafe.config.*
|
||||||
import net.corda.cordform.CordformNode
|
import net.corda.cordform.CordformNode
|
||||||
import org.bouncycastle.asn1.x500.X500Name
|
import org.bouncycastle.asn1.x500.X500Name
|
||||||
import org.bouncycastle.asn1.x500.RDN
|
|
||||||
import org.bouncycastle.asn1.x500.style.BCStyle
|
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
|
||||||
@ -39,6 +38,7 @@ class Node(private val project: Project) : CordformNode() {
|
|||||||
|
|
||||||
private val releaseVersion = project.rootProject.ext<String>("corda_release_version")
|
private val releaseVersion = project.rootProject.ext<String>("corda_release_version")
|
||||||
internal lateinit var nodeDir: File
|
internal lateinit var nodeDir: File
|
||||||
|
private set
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets whether this node will use HTTPS communication.
|
* Sets whether this node will use HTTPS communication.
|
||||||
@ -60,26 +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 HTTP web server port for this node. Will use localhost as the address.
|
|
||||||
*
|
|
||||||
* @param webPort The web port number for this node.
|
|
||||||
*/
|
|
||||||
fun webPort(webPort: Int) {
|
|
||||||
config = config.withValue("webAddress",
|
|
||||||
ConfigValueFactory.fromAnyRef("$DEFAULT_HOST:$webPort"))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the HTTP web server address and port for this node.
|
|
||||||
*
|
|
||||||
* @param webAddress The web address for this node.
|
|
||||||
*/
|
|
||||||
fun webAddress(webAddress: String) {
|
|
||||||
config = config.withValue("webAddress",
|
|
||||||
ConfigValueFactory.fromAnyRef(webAddress))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the network map address for this node.
|
* Set the network map address for this node.
|
||||||
*
|
*
|
||||||
@ -104,7 +84,6 @@ class Node(private val project: Project) : CordformNode() {
|
|||||||
config = config.withValue("sshd.port", ConfigValueFactory.fromAnyRef(sshdPort))
|
config = config.withValue("sshd.port", ConfigValueFactory.fromAnyRef(sshdPort))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
internal fun build() {
|
internal fun build() {
|
||||||
configureProperties()
|
configureProperties()
|
||||||
installCordaJar()
|
installCordaJar()
|
||||||
@ -118,19 +97,15 @@ class Node(private val project: Project) : CordformNode() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal fun rootDir(rootDir: Path) {
|
internal fun rootDir(rootDir: Path) {
|
||||||
if(name == null) {
|
if (name == null) {
|
||||||
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")
|
||||||
}
|
}
|
||||||
|
|
||||||
val dirName = try {
|
val dirName = try {
|
||||||
val o = X500Name(name).getRDNs(BCStyle.O)
|
val o = X500Name(name).getRDNs(BCStyle.O)
|
||||||
if (o.size > 0) {
|
if (o.isNotEmpty()) o.first().first.value.toString() else name
|
||||||
o.first().first.value.toString()
|
} catch (_ : IllegalArgumentException) {
|
||||||
} else {
|
|
||||||
name
|
|
||||||
}
|
|
||||||
} catch(_ : IllegalArgumentException) {
|
|
||||||
// Can't parse as an X500 name, use the full string
|
// Can't parse as an X500 name, use the full string
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
@ -192,9 +167,8 @@ class Node(private val project: Project) : CordformNode() {
|
|||||||
/**
|
/**
|
||||||
* Installs other cordapps to this node's cordapps directory.
|
* Installs other cordapps to this node's cordapps directory.
|
||||||
*/
|
*/
|
||||||
private fun installCordapps() {
|
internal fun installCordapps(cordapps: Collection<File> = getCordappList()) {
|
||||||
val cordappsDir = File(nodeDir, "cordapps")
|
val cordappsDir = File(nodeDir, "cordapps")
|
||||||
val cordapps = getCordappList()
|
|
||||||
project.copy {
|
project.copy {
|
||||||
it.apply {
|
it.apply {
|
||||||
from(cordapps)
|
from(cordapps)
|
||||||
@ -280,7 +254,7 @@ class Node(private val project: Project) : CordformNode() {
|
|||||||
throw RuntimeException("No Corda Webserver JAR found. Have you deployed the Corda project to Maven? Looked for \"corda-webserver-$releaseVersion.jar\"")
|
throw RuntimeException("No Corda Webserver JAR found. Have you deployed the Corda project to Maven? Looked for \"corda-webserver-$releaseVersion.jar\"")
|
||||||
} else {
|
} else {
|
||||||
val jar = maybeJar.singleFile
|
val jar = maybeJar.singleFile
|
||||||
assert(jar.isFile)
|
require(jar.isFile)
|
||||||
return jar
|
return jar
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
3
gradle/wrapper/gradle-wrapper.properties
vendored
3
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,5 +1,6 @@
|
|||||||
|
#Sat Nov 25 22:21:50 GMT 2017
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-all.zip
|
||||||
|
@ -3,7 +3,7 @@ apply plugin: 'net.corda.plugins.quasar-utils'
|
|||||||
apply plugin: 'net.corda.plugins.publish-utils'
|
apply plugin: 'net.corda.plugins.publish-utils'
|
||||||
apply plugin: 'com.jfrog.artifactory'
|
apply plugin: 'com.jfrog.artifactory'
|
||||||
|
|
||||||
description 'Corda node Artemis API'
|
description 'Corda node API'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile project(":core")
|
compile project(":core")
|
||||||
|
@ -1,63 +1,79 @@
|
|||||||
package net.corda.node.utilities
|
package net.corda.nodeapi.internal.persistence
|
||||||
|
|
||||||
import com.zaxxer.hikari.HikariConfig
|
import net.corda.core.schemas.MappedSchema
|
||||||
import com.zaxxer.hikari.HikariDataSource
|
|
||||||
import net.corda.core.node.services.IdentityService
|
|
||||||
import net.corda.node.services.api.SchemaService
|
|
||||||
import net.corda.node.services.config.DatabaseConfig
|
|
||||||
import net.corda.node.services.persistence.HibernateConfiguration
|
|
||||||
import net.corda.node.services.schema.NodeSchemaService
|
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.Subscriber
|
import rx.Subscriber
|
||||||
import rx.subjects.UnicastSubject
|
import rx.subjects.UnicastSubject
|
||||||
import java.io.Closeable
|
import java.io.Closeable
|
||||||
import java.sql.Connection
|
import java.sql.Connection
|
||||||
import java.sql.SQLException
|
import java.sql.SQLException
|
||||||
import java.util.*
|
|
||||||
import java.util.concurrent.CopyOnWriteArrayList
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
|
import javax.persistence.AttributeConverter
|
||||||
|
import javax.sql.DataSource
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Table prefix for all tables owned by the node module.
|
* Table prefix for all tables owned by the node module.
|
||||||
*/
|
*/
|
||||||
const val NODE_DATABASE_PREFIX = "node_"
|
const val NODE_DATABASE_PREFIX = "node_"
|
||||||
|
|
||||||
//HikariDataSource implements Closeable which allows CordaPersistence to be Closeable
|
// This class forms part of the node config and so any changes to it must be handled with care
|
||||||
|
data class DatabaseConfig(
|
||||||
|
val initialiseSchema: Boolean = true,
|
||||||
|
val serverNameTablePrefix: String = "",
|
||||||
|
val transactionIsolationLevel: TransactionIsolationLevel = TransactionIsolationLevel.REPEATABLE_READ
|
||||||
|
)
|
||||||
|
|
||||||
|
// This class forms part of the node config and so any changes to it must be handled with care
|
||||||
|
enum class TransactionIsolationLevel {
|
||||||
|
NONE,
|
||||||
|
READ_UNCOMMITTED,
|
||||||
|
READ_COMMITTED,
|
||||||
|
REPEATABLE_READ,
|
||||||
|
SERIALIZABLE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The JDBC constant value of the same name but prefixed with TRANSACTION_ defined in [java.sql.Connection].
|
||||||
|
*/
|
||||||
|
val jdbcValue: Int = java.sql.Connection::class.java.getField("TRANSACTION_$name").get(null) as Int
|
||||||
|
}
|
||||||
|
|
||||||
class CordaPersistence(
|
class CordaPersistence(
|
||||||
val dataSource: HikariDataSource,
|
val dataSource: DataSource,
|
||||||
private val schemaService: SchemaService,
|
databaseConfig: DatabaseConfig,
|
||||||
private val identityService: IdentityService,
|
schemas: Set<MappedSchema>,
|
||||||
databaseConfig: DatabaseConfig
|
attributeConverters: Collection<AttributeConverter<*, *>> = emptySet()
|
||||||
) : Closeable {
|
) : Closeable {
|
||||||
val transactionIsolationLevel = databaseConfig.transactionIsolationLevel.jdbcValue
|
val defaultIsolationLevel = databaseConfig.transactionIsolationLevel
|
||||||
val hibernateConfig: HibernateConfiguration by lazy {
|
val hibernateConfig: HibernateConfiguration by lazy {
|
||||||
transaction {
|
transaction {
|
||||||
HibernateConfiguration(schemaService, databaseConfig, identityService)
|
HibernateConfiguration(schemas, databaseConfig, attributeConverters)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val entityManagerFactory get() = hibernateConfig.sessionFactoryForRegisteredSchemas
|
val entityManagerFactory get() = hibernateConfig.sessionFactoryForRegisteredSchemas
|
||||||
|
|
||||||
companion object {
|
init {
|
||||||
fun connect(dataSource: HikariDataSource, schemaService: SchemaService, identityService: IdentityService, databaseConfig: DatabaseConfig): CordaPersistence {
|
DatabaseTransactionManager(this)
|
||||||
return CordaPersistence(dataSource, schemaService, identityService, databaseConfig).apply {
|
// Check not in read-only mode.
|
||||||
DatabaseTransactionManager(this)
|
transaction {
|
||||||
|
dataSource.connection.use {
|
||||||
|
check(!it.metaData.isReadOnly) { "Database should not be readonly." }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance of [DatabaseTransaction], with the given isolation level.
|
* Creates an instance of [DatabaseTransaction], with the given transaction isolation level.
|
||||||
* @param isolationLevel isolation level for the transaction. If not specified the default (i.e. provided at the creation time) is used.
|
|
||||||
*/
|
*/
|
||||||
fun createTransaction(isolationLevel: Int): DatabaseTransaction {
|
fun createTransaction(isolationLevel: TransactionIsolationLevel): DatabaseTransaction {
|
||||||
// We need to set the database for the current [Thread] or [Fiber] here as some tests share threads across databases.
|
// We need to set the database for the current [Thread] or [Fiber] here as some tests share threads across databases.
|
||||||
DatabaseTransactionManager.dataSource = this
|
DatabaseTransactionManager.dataSource = this
|
||||||
return DatabaseTransactionManager.currentOrNew(isolationLevel)
|
return DatabaseTransactionManager.currentOrNew(isolationLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance of [DatabaseTransaction], with the transaction isolation level specified at the creation time.
|
* Creates an instance of [DatabaseTransaction], with the default transaction isolation level.
|
||||||
*/
|
*/
|
||||||
fun createTransaction(): DatabaseTransaction = createTransaction(transactionIsolationLevel)
|
fun createTransaction(): DatabaseTransaction = createTransaction(defaultIsolationLevel)
|
||||||
|
|
||||||
fun createSession(): Connection {
|
fun createSession(): Connection {
|
||||||
// We need to set the database for the current [Thread] or [Fiber] here as some tests share threads across databases.
|
// We need to set the database for the current [Thread] or [Fiber] here as some tests share threads across databases.
|
||||||
@ -71,7 +87,7 @@ class CordaPersistence(
|
|||||||
* @param isolationLevel isolation level for the transaction.
|
* @param isolationLevel isolation level for the transaction.
|
||||||
* @param statement to be executed in the scope of this transaction.
|
* @param statement to be executed in the scope of this transaction.
|
||||||
*/
|
*/
|
||||||
fun <T> transaction(isolationLevel: Int, statement: DatabaseTransaction.() -> T): T {
|
fun <T> transaction(isolationLevel: TransactionIsolationLevel, statement: DatabaseTransaction.() -> T): T {
|
||||||
DatabaseTransactionManager.dataSource = this
|
DatabaseTransactionManager.dataSource = this
|
||||||
return transaction(isolationLevel, 3, statement)
|
return transaction(isolationLevel, 3, statement)
|
||||||
}
|
}
|
||||||
@ -80,22 +96,21 @@ class CordaPersistence(
|
|||||||
* Executes given statement in the scope of transaction with the transaction level specified at the creation time.
|
* Executes given statement in the scope of transaction with the transaction level specified at the creation time.
|
||||||
* @param statement to be executed in the scope of this transaction.
|
* @param statement to be executed in the scope of this transaction.
|
||||||
*/
|
*/
|
||||||
fun <T> transaction(statement: DatabaseTransaction.() -> T): T = transaction(transactionIsolationLevel, statement)
|
fun <T> transaction(statement: DatabaseTransaction.() -> T): T = transaction(defaultIsolationLevel, statement)
|
||||||
|
|
||||||
private fun <T> transaction(transactionIsolation: Int, repetitionAttempts: Int, statement: DatabaseTransaction.() -> T): T {
|
private fun <T> transaction(isolationLevel: TransactionIsolationLevel, repetitionAttempts: Int, statement: DatabaseTransaction.() -> T): T {
|
||||||
val outer = DatabaseTransactionManager.currentOrNull()
|
val outer = DatabaseTransactionManager.currentOrNull()
|
||||||
|
|
||||||
return if (outer != null) {
|
return if (outer != null) {
|
||||||
outer.statement()
|
outer.statement()
|
||||||
} else {
|
} else {
|
||||||
inTopLevelTransaction(transactionIsolation, repetitionAttempts, statement)
|
inTopLevelTransaction(isolationLevel, repetitionAttempts, statement)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <T> inTopLevelTransaction(transactionIsolation: Int, repetitionAttempts: Int, statement: DatabaseTransaction.() -> T): T {
|
private fun <T> inTopLevelTransaction(isolationLevel: TransactionIsolationLevel, repetitionAttempts: Int, statement: DatabaseTransaction.() -> T): T {
|
||||||
var repetitions = 0
|
var repetitions = 0
|
||||||
while (true) {
|
while (true) {
|
||||||
val transaction = DatabaseTransactionManager.currentOrNew(transactionIsolation)
|
val transaction = DatabaseTransactionManager.currentOrNew(isolationLevel)
|
||||||
try {
|
try {
|
||||||
val answer = transaction.statement()
|
val answer = transaction.statement()
|
||||||
transaction.commit()
|
transaction.commit()
|
||||||
@ -116,23 +131,11 @@ class CordaPersistence(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
dataSource.close()
|
// DataSource doesn't implement AutoCloseable so we just have to hope that the implementation does so that we can close it
|
||||||
|
(dataSource as? AutoCloseable)?.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun configureDatabase(dataSourceProperties: Properties, databaseConfig: DatabaseConfig, identityService: IdentityService, schemaService: SchemaService = NodeSchemaService(null)): CordaPersistence {
|
|
||||||
val config = HikariConfig(dataSourceProperties)
|
|
||||||
val dataSource = HikariDataSource(config)
|
|
||||||
val persistence = CordaPersistence.connect(dataSource, schemaService, identityService, databaseConfig)
|
|
||||||
// Check not in read-only mode.
|
|
||||||
persistence.transaction {
|
|
||||||
persistence.dataSource.connection.use {
|
|
||||||
check(!it.metaData.isReadOnly) { "Database should not be readonly." }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return persistence
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Buffer observations until after the current database transaction has been closed. Observations are never
|
* Buffer observations until after the current database transaction has been closed. Observations are never
|
||||||
* dropped, simply delayed.
|
* dropped, simply delayed.
|
||||||
@ -144,7 +147,7 @@ fun configureDatabase(dataSourceProperties: Properties, databaseConfig: Database
|
|||||||
*/
|
*/
|
||||||
fun <T : Any> rx.Observer<T>.bufferUntilDatabaseCommit(): rx.Observer<T> {
|
fun <T : Any> rx.Observer<T>.bufferUntilDatabaseCommit(): rx.Observer<T> {
|
||||||
val currentTxId = DatabaseTransactionManager.transactionId
|
val currentTxId = DatabaseTransactionManager.transactionId
|
||||||
val databaseTxBoundary: Observable<DatabaseTransactionManager.Boundary> = DatabaseTransactionManager.transactionBoundaries.filter { it.txId == currentTxId }.first()
|
val databaseTxBoundary: Observable<DatabaseTransactionManager.Boundary> = DatabaseTransactionManager.transactionBoundaries.first { it.txId == currentTxId }
|
||||||
val subject = UnicastSubject.create<T>()
|
val subject = UnicastSubject.create<T>()
|
||||||
subject.delaySubscription(databaseTxBoundary).subscribe(this)
|
subject.delaySubscription(databaseTxBoundary).subscribe(this)
|
||||||
databaseTxBoundary.doOnCompleted { subject.onCompleted() }
|
databaseTxBoundary.doOnCompleted { subject.onCompleted() }
|
||||||
@ -183,14 +186,9 @@ private class DatabaseTransactionWrappingSubscriber<U>(val db: CordaPersistence?
|
|||||||
|
|
||||||
// A subscriber that wraps another but does not pass on observations to it.
|
// A subscriber that wraps another but does not pass on observations to it.
|
||||||
private class NoOpSubscriber<U>(t: Subscriber<in U>) : Subscriber<U>(t) {
|
private class NoOpSubscriber<U>(t: Subscriber<in U>) : Subscriber<U>(t) {
|
||||||
override fun onCompleted() {
|
override fun onCompleted() {}
|
||||||
}
|
override fun onError(e: Throwable?) {}
|
||||||
|
override fun onNext(s: U) {}
|
||||||
override fun onError(e: Throwable?) {
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onNext(s: U) {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
@ -0,0 +1,61 @@
|
|||||||
|
package net.corda.nodeapi.internal.persistence
|
||||||
|
|
||||||
|
import org.hibernate.Session
|
||||||
|
import org.hibernate.Transaction
|
||||||
|
import rx.subjects.Subject
|
||||||
|
import java.sql.Connection
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class DatabaseTransaction(
|
||||||
|
isolation: Int,
|
||||||
|
private val threadLocal: ThreadLocal<DatabaseTransaction>,
|
||||||
|
private val transactionBoundaries: Subject<DatabaseTransactionManager.Boundary, DatabaseTransactionManager.Boundary>,
|
||||||
|
val cordaPersistence: CordaPersistence
|
||||||
|
) {
|
||||||
|
val id: UUID = UUID.randomUUID()
|
||||||
|
|
||||||
|
val connection: Connection by lazy(LazyThreadSafetyMode.NONE) {
|
||||||
|
cordaPersistence.dataSource.connection.apply {
|
||||||
|
autoCommit = false
|
||||||
|
transactionIsolation = isolation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val sessionDelegate = lazy {
|
||||||
|
val session = cordaPersistence.entityManagerFactory.withOptions().connection(connection).openSession()
|
||||||
|
hibernateTransaction = session.beginTransaction()
|
||||||
|
session
|
||||||
|
}
|
||||||
|
|
||||||
|
val session: Session by sessionDelegate
|
||||||
|
private lateinit var hibernateTransaction: Transaction
|
||||||
|
|
||||||
|
private val outerTransaction: DatabaseTransaction? = threadLocal.get()
|
||||||
|
|
||||||
|
fun commit() {
|
||||||
|
if (sessionDelegate.isInitialized()) {
|
||||||
|
hibernateTransaction.commit()
|
||||||
|
}
|
||||||
|
connection.commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun rollback() {
|
||||||
|
if (sessionDelegate.isInitialized() && session.isOpen) {
|
||||||
|
session.clear()
|
||||||
|
}
|
||||||
|
if (!connection.isClosed) {
|
||||||
|
connection.rollback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun close() {
|
||||||
|
if (sessionDelegate.isInitialized() && session.isOpen) {
|
||||||
|
session.close()
|
||||||
|
}
|
||||||
|
connection.close()
|
||||||
|
threadLocal.set(outerTransaction)
|
||||||
|
if (outerTransaction == null) {
|
||||||
|
transactionBoundaries.onNext(DatabaseTransactionManager.Boundary(id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,68 +1,14 @@
|
|||||||
package net.corda.node.utilities
|
package net.corda.nodeapi.internal.persistence
|
||||||
|
|
||||||
import co.paralleluniverse.strands.Strand
|
import co.paralleluniverse.strands.Strand
|
||||||
import org.hibernate.Session
|
import org.hibernate.Session
|
||||||
import org.hibernate.Transaction
|
|
||||||
import rx.subjects.PublishSubject
|
import rx.subjects.PublishSubject
|
||||||
import rx.subjects.Subject
|
import rx.subjects.Subject
|
||||||
import java.sql.Connection
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
class DatabaseTransaction(isolation: Int, val threadLocal: ThreadLocal<DatabaseTransaction>,
|
fun currentDBSession(): Session = DatabaseTransactionManager.current().session
|
||||||
val transactionBoundaries: Subject<DatabaseTransactionManager.Boundary, DatabaseTransactionManager.Boundary>,
|
|
||||||
val cordaPersistence: CordaPersistence) {
|
|
||||||
|
|
||||||
val id: UUID = UUID.randomUUID()
|
|
||||||
|
|
||||||
val connection: Connection by lazy(LazyThreadSafetyMode.NONE) {
|
|
||||||
cordaPersistence.dataSource.connection
|
|
||||||
.apply {
|
|
||||||
autoCommit = false
|
|
||||||
transactionIsolation = isolation
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val sessionDelegate = lazy {
|
|
||||||
val session = cordaPersistence.entityManagerFactory.withOptions().connection(connection).openSession()
|
|
||||||
hibernateTransaction = session.beginTransaction()
|
|
||||||
session
|
|
||||||
}
|
|
||||||
|
|
||||||
val session: Session by sessionDelegate
|
|
||||||
private lateinit var hibernateTransaction: Transaction
|
|
||||||
|
|
||||||
private val outerTransaction: DatabaseTransaction? = threadLocal.get()
|
|
||||||
|
|
||||||
fun commit() {
|
|
||||||
if (sessionDelegate.isInitialized()) {
|
|
||||||
hibernateTransaction.commit()
|
|
||||||
}
|
|
||||||
connection.commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun rollback() {
|
|
||||||
if (sessionDelegate.isInitialized() && session.isOpen) {
|
|
||||||
session.clear()
|
|
||||||
}
|
|
||||||
if (!connection.isClosed) {
|
|
||||||
connection.rollback()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun close() {
|
|
||||||
if (sessionDelegate.isInitialized() && session.isOpen) {
|
|
||||||
session.close()
|
|
||||||
}
|
|
||||||
connection.close()
|
|
||||||
threadLocal.set(outerTransaction)
|
|
||||||
if (outerTransaction == null) {
|
|
||||||
transactionBoundaries.onNext(DatabaseTransactionManager.Boundary(id))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun currentDBSession() = DatabaseTransactionManager.current().session
|
|
||||||
class DatabaseTransactionManager(initDataSource: CordaPersistence) {
|
class DatabaseTransactionManager(initDataSource: CordaPersistence) {
|
||||||
companion object {
|
companion object {
|
||||||
private val threadLocalDb = ThreadLocal<CordaPersistence>()
|
private val threadLocalDb = ThreadLocal<CordaPersistence>()
|
||||||
@ -95,11 +41,15 @@ class DatabaseTransactionManager(initDataSource: CordaPersistence) {
|
|||||||
|
|
||||||
fun currentOrNull(): DatabaseTransaction? = manager.currentOrNull()
|
fun currentOrNull(): DatabaseTransaction? = manager.currentOrNull()
|
||||||
|
|
||||||
fun currentOrNew(isolation: Int = dataSource.transactionIsolationLevel) = currentOrNull() ?: manager.newTransaction(isolation)
|
fun currentOrNew(isolation: TransactionIsolationLevel = dataSource.defaultIsolationLevel): DatabaseTransaction {
|
||||||
|
return currentOrNull() ?: manager.newTransaction(isolation.jdbcValue)
|
||||||
|
}
|
||||||
|
|
||||||
fun current(): DatabaseTransaction = currentOrNull() ?: error("No transaction in context.")
|
fun current(): DatabaseTransaction = currentOrNull() ?: error("No transaction in context.")
|
||||||
|
|
||||||
fun newTransaction(isolation: Int = dataSource.transactionIsolationLevel) = manager.newTransaction(isolation)
|
fun newTransaction(isolation: TransactionIsolationLevel = dataSource.defaultIsolationLevel): DatabaseTransaction {
|
||||||
|
return manager.newTransaction(isolation.jdbcValue)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Boundary(val txId: UUID)
|
data class Boundary(val txId: UUID)
|
@ -1,13 +1,9 @@
|
|||||||
package net.corda.node.services.persistence
|
package net.corda.nodeapi.internal.persistence
|
||||||
|
|
||||||
import net.corda.core.internal.castIfPossible
|
import net.corda.core.internal.castIfPossible
|
||||||
import net.corda.core.node.services.IdentityService
|
|
||||||
import net.corda.core.schemas.MappedSchema
|
import net.corda.core.schemas.MappedSchema
|
||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.contextLogger
|
||||||
import net.corda.core.utilities.toHexString
|
import net.corda.core.utilities.toHexString
|
||||||
import net.corda.node.services.api.SchemaService
|
|
||||||
import net.corda.node.services.config.DatabaseConfig
|
|
||||||
import net.corda.node.utilities.DatabaseTransactionManager
|
|
||||||
import org.hibernate.SessionFactory
|
import org.hibernate.SessionFactory
|
||||||
import org.hibernate.boot.MetadataSources
|
import org.hibernate.boot.MetadataSources
|
||||||
import org.hibernate.boot.model.naming.Identifier
|
import org.hibernate.boot.model.naming.Identifier
|
||||||
@ -18,14 +14,18 @@ import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider
|
|||||||
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment
|
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment
|
||||||
import org.hibernate.service.UnknownUnwrapTypeException
|
import org.hibernate.service.UnknownUnwrapTypeException
|
||||||
import org.hibernate.type.AbstractSingleColumnStandardBasicType
|
import org.hibernate.type.AbstractSingleColumnStandardBasicType
|
||||||
import org.hibernate.type.descriptor.java.JavaTypeDescriptorRegistry
|
|
||||||
import org.hibernate.type.descriptor.java.PrimitiveByteArrayTypeDescriptor
|
import org.hibernate.type.descriptor.java.PrimitiveByteArrayTypeDescriptor
|
||||||
import org.hibernate.type.descriptor.sql.BlobTypeDescriptor
|
import org.hibernate.type.descriptor.sql.BlobTypeDescriptor
|
||||||
import org.hibernate.type.descriptor.sql.VarbinaryTypeDescriptor
|
import org.hibernate.type.descriptor.sql.VarbinaryTypeDescriptor
|
||||||
import java.sql.Connection
|
import java.sql.Connection
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import javax.persistence.AttributeConverter
|
||||||
|
|
||||||
class HibernateConfiguration(val schemaService: SchemaService, private val databaseConfig: DatabaseConfig, private val identityService: IdentityService) {
|
class HibernateConfiguration(
|
||||||
|
schemas: Set<MappedSchema>,
|
||||||
|
private val databaseConfig: DatabaseConfig,
|
||||||
|
private val attributeConverters: Collection<AttributeConverter<*, *>>
|
||||||
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
private val logger = contextLogger()
|
private val logger = contextLogger()
|
||||||
}
|
}
|
||||||
@ -33,13 +33,8 @@ class HibernateConfiguration(val schemaService: SchemaService, private val datab
|
|||||||
// TODO: make this a guava cache or similar to limit ability for this to grow forever.
|
// TODO: make this a guava cache or similar to limit ability for this to grow forever.
|
||||||
private val sessionFactories = ConcurrentHashMap<Set<MappedSchema>, SessionFactory>()
|
private val sessionFactories = ConcurrentHashMap<Set<MappedSchema>, SessionFactory>()
|
||||||
|
|
||||||
val sessionFactoryForRegisteredSchemas = schemaService.schemaOptions.keys.let {
|
val sessionFactoryForRegisteredSchemas = schemas.let {
|
||||||
logger.info("Init HibernateConfiguration for schemas: $it")
|
logger.info("Init HibernateConfiguration for schemas: $it")
|
||||||
// Register the AbstractPartyDescriptor so Hibernate doesn't warn when encountering AbstractParty. Unfortunately
|
|
||||||
// Hibernate warns about not being able to find a descriptor if we don't provide one, but won't use it by default
|
|
||||||
// so we end up providing both descriptor and converter. We should re-examine this in later versions to see if
|
|
||||||
// either Hibernate can be convinced to stop warning, use the descriptor by default, or something else.
|
|
||||||
JavaTypeDescriptorRegistry.INSTANCE.addDescriptor(AbstractPartyDescriptor(identityService))
|
|
||||||
sessionFactoryForSchemas(it)
|
sessionFactoryForSchemas(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,7 +49,7 @@ class HibernateConfiguration(val schemaService: SchemaService, private val datab
|
|||||||
// necessarily remain and would likely be replaced by something like Liquibase. For now it is very convenient though.
|
// necessarily remain and would likely be replaced by something like Liquibase. For now it is very convenient though.
|
||||||
// TODO: replace auto schema generation as it isn't intended for production use, according to Hibernate docs.
|
// TODO: replace auto schema generation as it isn't intended for production use, according to Hibernate docs.
|
||||||
val config = Configuration(metadataSources).setProperty("hibernate.connection.provider_class", NodeDatabaseConnectionProvider::class.java.name)
|
val config = Configuration(metadataSources).setProperty("hibernate.connection.provider_class", NodeDatabaseConnectionProvider::class.java.name)
|
||||||
.setProperty("hibernate.hbm2ddl.auto", if (databaseConfig.initDatabase) "update" else "validate")
|
.setProperty("hibernate.hbm2ddl.auto", if (databaseConfig.initialiseSchema) "update" else "validate")
|
||||||
.setProperty("hibernate.format_sql", "true")
|
.setProperty("hibernate.format_sql", "true")
|
||||||
.setProperty("hibernate.connection.isolation", databaseConfig.transactionIsolationLevel.jdbcValue.toString())
|
.setProperty("hibernate.connection.isolation", databaseConfig.transactionIsolationLevel.jdbcValue.toString())
|
||||||
|
|
||||||
@ -78,7 +73,7 @@ class HibernateConfiguration(val schemaService: SchemaService, private val datab
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
// register custom converters
|
// register custom converters
|
||||||
applyAttributeConverter(AbstractPartyToX500NameAsStringConverter(identityService))
|
attributeConverters.forEach { applyAttributeConverter(it) }
|
||||||
// Register a tweaked version of `org.hibernate.type.MaterializedBlobType` that truncates logged messages.
|
// Register a tweaked version of `org.hibernate.type.MaterializedBlobType` that truncates logged messages.
|
||||||
// to avoid OOM when large blobs might get logged.
|
// to avoid OOM when large blobs might get logged.
|
||||||
applyBasicType(CordaMaterializedBlobType, CordaMaterializedBlobType.name)
|
applyBasicType(CordaMaterializedBlobType, CordaMaterializedBlobType.name)
|
@ -26,5 +26,8 @@ class AMQPPrimitiveSerializer(clazz: Class<*>) : AMQPSerializer<Any> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): Any = (obj as? Binary)?.array ?: obj
|
override fun readObject(
|
||||||
|
obj: Any,
|
||||||
|
schemas: SerializationSchemas,
|
||||||
|
input: DeserializationInput): Any = (obj as? Binary)?.array ?: obj
|
||||||
}
|
}
|
@ -35,5 +35,5 @@ interface AMQPSerializer<out T> {
|
|||||||
/**
|
/**
|
||||||
* Read the given object from the input. The envelope is provided in case the schema is required.
|
* Read the given object from the input. The envelope is provided in case the schema is required.
|
||||||
*/
|
*/
|
||||||
fun readObject(obj: Any, schema: Schema, input: DeserializationInput): T
|
fun readObject(obj: Any, schema: SerializationSchemas, input: DeserializationInput): T
|
||||||
}
|
}
|
@ -56,9 +56,9 @@ open class ArraySerializer(override val type: Type, factory: SerializerFactory)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): Any {
|
override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): Any {
|
||||||
if (obj is List<*>) {
|
if (obj is List<*>) {
|
||||||
return obj.map { input.readObjectOrNull(it, schema, elementType) }.toArrayOfType(elementType)
|
return obj.map { input.readObjectOrNull(it, schemas, elementType) }.toArrayOfType(elementType)
|
||||||
} else throw NotSerializableException("Expected a List but found $obj")
|
} else throw NotSerializableException("Expected a List but found $obj")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,8 +77,8 @@ class CollectionSerializer(val declaredType: ParameterizedType, factory: Seriali
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): Any = ifThrowsAppend({ declaredType.typeName }) {
|
override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): Any = ifThrowsAppend({ declaredType.typeName }) {
|
||||||
// TODO: Can we verify the entries in the list?
|
// TODO: Can we verify the entries in the list?
|
||||||
concreteBuilder((obj as List<*>).map { input.readObjectOrNull(it, schema, declaredType.actualTypeArguments[0]) })
|
concreteBuilder((obj as List<*>).map { input.readObjectOrNull(it, schemas, declaredType.actualTypeArguments[0]) })
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -67,8 +67,8 @@ abstract class CustomSerializer<T : Any> : AMQPSerializer<T> {
|
|||||||
superClassSerializer.writeDescribedObject(obj, data, type, output)
|
superClassSerializer.writeDescribedObject(obj, data, type, output)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): T {
|
override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): T {
|
||||||
return superClassSerializer.readObject(obj, schema, input)
|
return superClassSerializer.readObject(obj, schemas, input)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,8 +133,8 @@ abstract class CustomSerializer<T : Any> : AMQPSerializer<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): T {
|
override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): T {
|
||||||
val proxy: P = uncheckedCast(proxySerializer.readObject(obj, schema, input))
|
val proxy: P = uncheckedCast(proxySerializer.readObject(obj, schemas, input))
|
||||||
return fromProxy(proxy)
|
return fromProxy(proxy)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -166,7 +166,7 @@ abstract class CustomSerializer<T : Any> : AMQPSerializer<T> {
|
|||||||
data.putString(unmaker(obj))
|
data.putString(unmaker(obj))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): T {
|
override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): T {
|
||||||
val proxy = obj as String
|
val proxy = obj as String
|
||||||
return maker(proxy)
|
return maker(proxy)
|
||||||
}
|
}
|
||||||
|
@ -97,21 +97,21 @@ class DeserializationInput(internal val serializerFactory: SerializerFactory) {
|
|||||||
@Throws(NotSerializableException::class)
|
@Throws(NotSerializableException::class)
|
||||||
fun <T : Any> deserialize(bytes: ByteSequence, clazz: Class<T>): T = des {
|
fun <T : Any> deserialize(bytes: ByteSequence, clazz: Class<T>): T = des {
|
||||||
val envelope = getEnvelope(bytes)
|
val envelope = getEnvelope(bytes)
|
||||||
clazz.cast(readObjectOrNull(envelope.obj, envelope.schema, clazz))
|
clazz.cast(readObjectOrNull(envelope.obj, SerializationSchemas(envelope.schema, envelope.transformsSchema), clazz))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(NotSerializableException::class)
|
@Throws(NotSerializableException::class)
|
||||||
fun <T : Any> deserializeAndReturnEnvelope(bytes: SerializedBytes<T>, clazz: Class<T>): ObjectAndEnvelope<T> = des {
|
fun <T : Any> deserializeAndReturnEnvelope(bytes: SerializedBytes<T>, clazz: Class<T>): ObjectAndEnvelope<T> = des {
|
||||||
val envelope = getEnvelope(bytes)
|
val envelope = getEnvelope(bytes)
|
||||||
// Now pick out the obj and schema from the envelope.
|
// Now pick out the obj and schema from the envelope.
|
||||||
ObjectAndEnvelope(clazz.cast(readObjectOrNull(envelope.obj, envelope.schema, clazz)), envelope)
|
ObjectAndEnvelope(clazz.cast(readObjectOrNull(envelope.obj, SerializationSchemas(envelope.schema, envelope.transformsSchema), clazz)), envelope)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun readObjectOrNull(obj: Any?, schema: Schema, type: Type): Any? {
|
internal fun readObjectOrNull(obj: Any?, schema: SerializationSchemas, type: Type): Any? {
|
||||||
return if (obj == null) null else readObject(obj, schema, type)
|
return if (obj == null) null else readObject(obj, schema, type)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun readObject(obj: Any, schema: Schema, type: Type): Any =
|
internal fun readObject(obj: Any, schemas: SerializationSchemas, type: Type): Any =
|
||||||
if (obj is DescribedType && ReferencedObject.DESCRIPTOR == obj.descriptor) {
|
if (obj is DescribedType && ReferencedObject.DESCRIPTOR == obj.descriptor) {
|
||||||
// It must be a reference to an instance that has already been read, cheaply and quickly returning it by reference.
|
// It must be a reference to an instance that has already been read, cheaply and quickly returning it by reference.
|
||||||
val objectIndex = (obj.described as UnsignedInteger).toInt()
|
val objectIndex = (obj.described as UnsignedInteger).toInt()
|
||||||
@ -127,11 +127,11 @@ class DeserializationInput(internal val serializerFactory: SerializerFactory) {
|
|||||||
val objectRead = when (obj) {
|
val objectRead = when (obj) {
|
||||||
is DescribedType -> {
|
is DescribedType -> {
|
||||||
// Look up serializer in factory by descriptor
|
// Look up serializer in factory by descriptor
|
||||||
val serializer = serializerFactory.get(obj.descriptor, schema)
|
val serializer = serializerFactory.get(obj.descriptor, schemas)
|
||||||
if (SerializerFactory.AnyType != type && serializer.type != type && with(serializer.type) { !isSubClassOf(type) && !materiallyEquivalentTo(type) })
|
if (SerializerFactory.AnyType != type && serializer.type != type && with(serializer.type) { !isSubClassOf(type) && !materiallyEquivalentTo(type) })
|
||||||
throw NotSerializableException("Described type with descriptor ${obj.descriptor} was " +
|
throw NotSerializableException("Described type with descriptor ${obj.descriptor} was " +
|
||||||
"expected to be of type $type but was ${serializer.type}")
|
"expected to be of type $type but was ${serializer.type}")
|
||||||
serializer.readObject(obj.described, schema, this)
|
serializer.readObject(obj.described, schemas, this)
|
||||||
}
|
}
|
||||||
is Binary -> obj.array
|
is Binary -> obj.array
|
||||||
else -> obj // this will be the case for primitive types like [boolean] et al.
|
else -> obj // this will be the case for primitive types like [boolean] et al.
|
||||||
|
@ -27,7 +27,7 @@ class EnumSerializer(declaredType: Type, declaredClass: Class<*>, factory: Seria
|
|||||||
output.writeTypeNotations(typeNotation)
|
output.writeTypeNotations(typeNotation)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): Any {
|
override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): Any {
|
||||||
val enumName = (obj as List<*>)[0] as String
|
val enumName = (obj as List<*>)[0] as String
|
||||||
val enumOrd = obj[1] as Int
|
val enumOrd = obj[1] as Int
|
||||||
val fromOrd = type.asClass()!!.enumConstants[enumOrd] as Enum<*>?
|
val fromOrd = type.asClass()!!.enumConstants[enumOrd] as Enum<*>?
|
||||||
|
@ -32,8 +32,8 @@ class EvolutionSerializer(
|
|||||||
* @param property object to read the actual property value
|
* @param property object to read the actual property value
|
||||||
*/
|
*/
|
||||||
data class OldParam(val type: Type, val idx: Int, val property: PropertySerializer) {
|
data class OldParam(val type: Type, val idx: Int, val property: PropertySerializer) {
|
||||||
fun readProperty(paramValues: List<*>, schema: Schema, input: DeserializationInput) =
|
fun readProperty(paramValues: List<*>, schemas: SerializationSchemas, input: DeserializationInput) =
|
||||||
property.readProperty(paramValues[idx], schema, input)
|
property.readProperty(paramValues[idx], schemas, input)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -121,10 +121,10 @@ class EvolutionSerializer(
|
|||||||
*
|
*
|
||||||
* TODO: Object references
|
* TODO: Object references
|
||||||
*/
|
*/
|
||||||
override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): Any {
|
override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): Any {
|
||||||
if (obj !is List<*>) throw NotSerializableException("Body of described type is unexpected $obj")
|
if (obj !is List<*>) throw NotSerializableException("Body of described type is unexpected $obj")
|
||||||
|
|
||||||
return construct(readers.map { it?.readProperty(obj, schema, input) })
|
return construct(readers.map { it?.readProperty(obj, schemas, input) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,15 +88,15 @@ class MapSerializer(private val declaredType: ParameterizedType, factory: Serial
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): Any = ifThrowsAppend({ declaredType.typeName }) {
|
override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): Any = ifThrowsAppend({ declaredType.typeName }) {
|
||||||
// TODO: General generics question. Do we need to validate that entries in Maps and Collections match the generic type? Is it a security hole?
|
// TODO: General generics question. Do we need to validate that entries in Maps and Collections match the generic type? Is it a security hole?
|
||||||
val entries: Iterable<Pair<Any?, Any?>> = (obj as Map<*, *>).map { readEntry(schema, input, it) }
|
val entries: Iterable<Pair<Any?, Any?>> = (obj as Map<*, *>).map { readEntry(schemas, input, it) }
|
||||||
concreteBuilder(entries.toMap())
|
concreteBuilder(entries.toMap())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun readEntry(schema: Schema, input: DeserializationInput, entry: Map.Entry<Any?, Any?>) =
|
private fun readEntry(schemas: SerializationSchemas, input: DeserializationInput, entry: Map.Entry<Any?, Any?>) =
|
||||||
input.readObjectOrNull(entry.key, schema, declaredType.actualTypeArguments[0]) to
|
input.readObjectOrNull(entry.key, schemas, declaredType.actualTypeArguments[0]) to
|
||||||
input.readObjectOrNull(entry.value, schema, declaredType.actualTypeArguments[1])
|
input.readObjectOrNull(entry.value, schemas, declaredType.actualTypeArguments[1])
|
||||||
|
|
||||||
// Cannot use * as a bound for EnumMap and EnumSet since * is not an enum. So, we use a sample enum instead.
|
// Cannot use * as a bound for EnumMap and EnumSet since * is not an enum. So, we use a sample enum instead.
|
||||||
// We don't actually care about the type, we just need to make the compiler happier.
|
// We don't actually care about the type, we just need to make the compiler happier.
|
||||||
|
@ -55,10 +55,15 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): Any = ifThrowsAppend({ clazz.typeName }) {
|
override fun readObject(
|
||||||
|
obj: Any,
|
||||||
|
schemas: SerializationSchemas,
|
||||||
|
input: DeserializationInput): Any = ifThrowsAppend({ clazz.typeName }) {
|
||||||
if (obj is List<*>) {
|
if (obj is List<*>) {
|
||||||
if (obj.size > propertySerializers.size) throw NotSerializableException("Too many properties in described type $typeName")
|
if (obj.size > propertySerializers.size) {
|
||||||
val params = obj.zip(propertySerializers).map { it.second.readProperty(it.first, schema, input) }
|
throw NotSerializableException("Too many properties in described type $typeName")
|
||||||
|
}
|
||||||
|
val params = obj.zip(propertySerializers).map { it.second.readProperty(it.first, schemas, input) }
|
||||||
construct(params)
|
construct(params)
|
||||||
} else throw NotSerializableException("Body of described type is unexpected $obj")
|
} else throw NotSerializableException("Body of described type is unexpected $obj")
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ import kotlin.reflect.jvm.javaGetter
|
|||||||
sealed class PropertySerializer(val name: String, val readMethod: Method?, val resolvedType: Type) {
|
sealed class PropertySerializer(val name: String, val readMethod: Method?, val resolvedType: Type) {
|
||||||
abstract fun writeClassInfo(output: SerializationOutput)
|
abstract fun writeClassInfo(output: SerializationOutput)
|
||||||
abstract fun writeProperty(obj: Any?, data: Data, output: SerializationOutput)
|
abstract fun writeProperty(obj: Any?, data: Data, output: SerializationOutput)
|
||||||
abstract fun readProperty(obj: Any?, schema: Schema, input: DeserializationInput): Any?
|
abstract fun readProperty(obj: Any?, schemas: SerializationSchemas, input: DeserializationInput): Any?
|
||||||
|
|
||||||
val type: String = generateType()
|
val type: String = generateType()
|
||||||
val requires: List<String> = generateRequires()
|
val requires: List<String> = generateRequires()
|
||||||
@ -91,8 +91,8 @@ sealed class PropertySerializer(val name: String, val readMethod: Method?, val r
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun readProperty(obj: Any?, schema: Schema, input: DeserializationInput): Any? = ifThrowsAppend({ nameForDebug }) {
|
override fun readProperty(obj: Any?, schemas: SerializationSchemas, input: DeserializationInput): Any? = ifThrowsAppend({ nameForDebug }) {
|
||||||
input.readObjectOrNull(obj, schema, resolvedType)
|
input.readObjectOrNull(obj, schemas, resolvedType)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput) = ifThrowsAppend({ nameForDebug }) {
|
override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput) = ifThrowsAppend({ nameForDebug }) {
|
||||||
@ -108,7 +108,7 @@ sealed class PropertySerializer(val name: String, val readMethod: Method?, val r
|
|||||||
class AMQPPrimitivePropertySerializer(name: String, readMethod: Method?, resolvedType: Type) : PropertySerializer(name, readMethod, resolvedType) {
|
class AMQPPrimitivePropertySerializer(name: String, readMethod: Method?, resolvedType: Type) : PropertySerializer(name, readMethod, resolvedType) {
|
||||||
override fun writeClassInfo(output: SerializationOutput) {}
|
override fun writeClassInfo(output: SerializationOutput) {}
|
||||||
|
|
||||||
override fun readProperty(obj: Any?, schema: Schema, input: DeserializationInput): Any? {
|
override fun readProperty(obj: Any?, schemas: SerializationSchemas, input: DeserializationInput): Any? {
|
||||||
return if (obj is Binary) obj.array else obj
|
return if (obj is Binary) obj.array else obj
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,7 +131,7 @@ sealed class PropertySerializer(val name: String, val readMethod: Method?, val r
|
|||||||
PropertySerializer(name, readMethod, Character::class.java) {
|
PropertySerializer(name, readMethod, Character::class.java) {
|
||||||
override fun writeClassInfo(output: SerializationOutput) {}
|
override fun writeClassInfo(output: SerializationOutput) {}
|
||||||
|
|
||||||
override fun readProperty(obj: Any?, schema: Schema, input: DeserializationInput): Any? {
|
override fun readProperty(obj: Any?, schemas: SerializationSchemas, input: DeserializationInput): Any? {
|
||||||
return if (obj == null) null else (obj as Short).toChar()
|
return if (obj == null) null else (obj as Short).toChar()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,9 @@ import com.google.common.primitives.Primitives
|
|||||||
import com.google.common.reflect.TypeResolver
|
import com.google.common.reflect.TypeResolver
|
||||||
import net.corda.core.internal.uncheckedCast
|
import net.corda.core.internal.uncheckedCast
|
||||||
import net.corda.core.serialization.ClassWhitelist
|
import net.corda.core.serialization.ClassWhitelist
|
||||||
import net.corda.nodeapi.internal.serialization.carpenter.*
|
import net.corda.nodeapi.internal.serialization.carpenter.CarpenterMetaSchema
|
||||||
|
import net.corda.nodeapi.internal.serialization.carpenter.ClassCarpenter
|
||||||
|
import net.corda.nodeapi.internal.serialization.carpenter.MetaCarpenter
|
||||||
import org.apache.qpid.proton.amqp.*
|
import org.apache.qpid.proton.amqp.*
|
||||||
import java.io.NotSerializableException
|
import java.io.NotSerializableException
|
||||||
import java.lang.reflect.*
|
import java.lang.reflect.*
|
||||||
@ -13,7 +15,8 @@ import java.util.concurrent.ConcurrentHashMap
|
|||||||
import java.util.concurrent.CopyOnWriteArrayList
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
import javax.annotation.concurrent.ThreadSafe
|
import javax.annotation.concurrent.ThreadSafe
|
||||||
|
|
||||||
data class FactorySchemaAndDescriptor(val schema: Schema, val typeDescriptor: Any)
|
data class SerializationSchemas(val schema: Schema, val transforms: TransformsSchema)
|
||||||
|
data class FactorySchemaAndDescriptor(val schemas: SerializationSchemas, val typeDescriptor: Any)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory of serializers designed to be shared across threads and invocations.
|
* Factory of serializers designed to be shared across threads and invocations.
|
||||||
@ -40,7 +43,10 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
|
|||||||
val classloader: ClassLoader
|
val classloader: ClassLoader
|
||||||
get() = classCarpenter.classloader
|
get() = classCarpenter.classloader
|
||||||
|
|
||||||
private fun getEvolutionSerializer(typeNotation: TypeNotation, newSerializer: AMQPSerializer<Any>): AMQPSerializer<Any> {
|
private fun getEvolutionSerializer(
|
||||||
|
typeNotation: TypeNotation,
|
||||||
|
newSerializer: AMQPSerializer<Any>,
|
||||||
|
transforms: TransformsSchema): AMQPSerializer<Any> {
|
||||||
return serializersByDescriptor.computeIfAbsent(typeNotation.descriptor.name!!) {
|
return serializersByDescriptor.computeIfAbsent(typeNotation.descriptor.name!!) {
|
||||||
when (typeNotation) {
|
when (typeNotation) {
|
||||||
is CompositeType -> EvolutionSerializer.make(typeNotation, newSerializer as ObjectSerializer, this)
|
is CompositeType -> EvolutionSerializer.make(typeNotation, newSerializer as ObjectSerializer, this)
|
||||||
@ -168,7 +174,7 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
|
|||||||
* contained in the [Schema].
|
* contained in the [Schema].
|
||||||
*/
|
*/
|
||||||
@Throws(NotSerializableException::class)
|
@Throws(NotSerializableException::class)
|
||||||
fun get(typeDescriptor: Any, schema: Schema): AMQPSerializer<Any> {
|
fun get(typeDescriptor: Any, schema: SerializationSchemas): AMQPSerializer<Any> {
|
||||||
return serializersByDescriptor[typeDescriptor] ?: {
|
return serializersByDescriptor[typeDescriptor] ?: {
|
||||||
processSchema(FactorySchemaAndDescriptor(schema, typeDescriptor))
|
processSchema(FactorySchemaAndDescriptor(schema, typeDescriptor))
|
||||||
serializersByDescriptor[typeDescriptor] ?: throw NotSerializableException(
|
serializersByDescriptor[typeDescriptor] ?: throw NotSerializableException(
|
||||||
@ -194,9 +200,9 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
|
|||||||
* 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 weather it's on ClassPath of [classloader] amd
|
||||||
* 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(schema: FactorySchemaAndDescriptor, sentinel: Boolean = false) {
|
private fun processSchema(schemaAndDescriptor: FactorySchemaAndDescriptor, sentinel: Boolean = false) {
|
||||||
val metaSchema = CarpenterMetaSchema.newInstance()
|
val metaSchema = CarpenterMetaSchema.newInstance()
|
||||||
for (typeNotation in schema.schema.types) {
|
for (typeNotation in schemaAndDescriptor.schemas.schema.types) {
|
||||||
try {
|
try {
|
||||||
val serialiser = processSchemaEntry(typeNotation)
|
val serialiser = processSchemaEntry(typeNotation)
|
||||||
|
|
||||||
@ -204,7 +210,7 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
|
|||||||
// doesn't match that of the serialised object then we are dealing with different
|
// doesn't match that of the serialised object then we are dealing with different
|
||||||
// instance of the class, as such we need to build an EvolutionSerialiser
|
// instance of the class, as such we need to build an EvolutionSerialiser
|
||||||
if (serialiser.typeDescriptor != typeNotation.descriptor.name) {
|
if (serialiser.typeDescriptor != typeNotation.descriptor.name) {
|
||||||
getEvolutionSerializer(typeNotation, serialiser)
|
getEvolutionSerializer(typeNotation, serialiser, schemaAndDescriptor.schemas.transforms)
|
||||||
}
|
}
|
||||||
} catch (e: ClassNotFoundException) {
|
} catch (e: ClassNotFoundException) {
|
||||||
if (sentinel) throw e
|
if (sentinel) throw e
|
||||||
@ -215,7 +221,7 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
|
|||||||
if (metaSchema.isNotEmpty()) {
|
if (metaSchema.isNotEmpty()) {
|
||||||
val mc = MetaCarpenter(metaSchema, classCarpenter)
|
val mc = MetaCarpenter(metaSchema, classCarpenter)
|
||||||
mc.build()
|
mc.build()
|
||||||
processSchema(schema, true)
|
processSchema(schemaAndDescriptor, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ class SingletonSerializer(override val type: Class<*>, val singleton: Any, facto
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): Any {
|
override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): Any {
|
||||||
return singleton
|
return singleton
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -85,6 +85,9 @@ class UnknownTransform : Transform() {
|
|||||||
override val name: String get() = typeName
|
override val name: String get() = typeName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used by the unit testing framework
|
||||||
|
*/
|
||||||
class UnknownTestTransform(val a: Int, val b: Int, val c: Int) : Transform() {
|
class UnknownTestTransform(val a: Int, val b: Int, val c: Int) : Transform() {
|
||||||
companion object : DescribedTypeConstructor<UnknownTestTransform> {
|
companion object : DescribedTypeConstructor<UnknownTestTransform> {
|
||||||
val typeName = "UnknownTest"
|
val typeName = "UnknownTest"
|
||||||
|
@ -34,8 +34,8 @@ object InputStreamSerializer : CustomSerializer.Implements<InputStream>(InputStr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): InputStream {
|
override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): InputStream {
|
||||||
val bits = input.readObject(obj, schema, ByteArray::class.java) as ByteArray
|
val bits = input.readObject(obj, schemas, ByteArray::class.java) as ByteArray
|
||||||
return ByteArrayInputStream(bits)
|
return ByteArrayInputStream(bits)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -20,8 +20,8 @@ object PrivateKeySerializer : CustomSerializer.Implements<PrivateKey>(PrivateKey
|
|||||||
output.writeObject(obj.encoded, data, clazz)
|
output.writeObject(obj.encoded, data, clazz)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): PrivateKey {
|
override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): PrivateKey {
|
||||||
val bits = input.readObject(obj, schema, ByteArray::class.java) as ByteArray
|
val bits = input.readObject(obj, schemas, ByteArray::class.java) as ByteArray
|
||||||
return Crypto.decodePrivateKey(bits)
|
return Crypto.decodePrivateKey(bits)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -17,8 +17,8 @@ object PublicKeySerializer : CustomSerializer.Implements<PublicKey>(PublicKey::c
|
|||||||
output.writeObject(obj.encoded, data, clazz)
|
output.writeObject(obj.encoded, data, clazz)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): PublicKey {
|
override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): PublicKey {
|
||||||
val bits = input.readObject(obj, schema, ByteArray::class.java) as ByteArray
|
val bits = input.readObject(obj, schemas, ByteArray::class.java) as ByteArray
|
||||||
return Crypto.decodePublicKey(bits)
|
return Crypto.decodePublicKey(bits)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -20,8 +20,8 @@ object X509CertificateSerializer : CustomSerializer.Implements<X509Certificate>(
|
|||||||
output.writeObject(obj.encoded, data, clazz)
|
output.writeObject(obj.encoded, data, clazz)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): X509Certificate {
|
override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): X509Certificate {
|
||||||
val bits = input.readObject(obj, schema, ByteArray::class.java) as ByteArray
|
val bits = input.readObject(obj, schemas, ByteArray::class.java) as ByteArray
|
||||||
return X509CertificateFactory().generateCertificate(bits.inputStream())
|
return X509CertificateFactory().generateCertificate(bits.inputStream())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package net.corda.node.utilities
|
package net.corda.nodeapi.internal.crypto
|
||||||
|
|
||||||
import net.corda.core.crypto.Crypto
|
import net.corda.core.crypto.Crypto
|
||||||
import net.corda.core.crypto.Crypto.EDDSA_ED25519_SHA512
|
import net.corda.core.crypto.Crypto.EDDSA_ED25519_SHA512
|
||||||
@ -13,7 +13,6 @@ import net.corda.core.serialization.deserialize
|
|||||||
import net.corda.core.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
import net.corda.node.serialization.KryoServerSerializationScheme
|
import net.corda.node.serialization.KryoServerSerializationScheme
|
||||||
import net.corda.node.services.config.createKeystoreForCordaNode
|
import net.corda.node.services.config.createKeystoreForCordaNode
|
||||||
import net.corda.nodeapi.internal.crypto.*
|
|
||||||
import net.corda.nodeapi.internal.serialization.AllWhitelist
|
import net.corda.nodeapi.internal.serialization.AllWhitelist
|
||||||
import net.corda.nodeapi.internal.serialization.SerializationContextImpl
|
import net.corda.nodeapi.internal.serialization.SerializationContextImpl
|
||||||
import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl
|
import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl
|
@ -0,0 +1,43 @@
|
|||||||
|
package net.corda.nodeapi.internal.serialization.amqp
|
||||||
|
|
||||||
|
import net.corda.core.serialization.CordaSerializationTransformEnumDefault
|
||||||
|
import net.corda.core.serialization.SerializedBytes
|
||||||
|
import net.corda.testing.common.internal.ProjectStructure.projectRootDir
|
||||||
|
import org.assertj.core.api.Assertions
|
||||||
|
import org.junit.Test
|
||||||
|
import java.io.File
|
||||||
|
import java.io.NotSerializableException
|
||||||
|
import java.net.URI
|
||||||
|
|
||||||
|
// NOTE: To recreate the test files used by these tests uncomment the original test classes and comment
|
||||||
|
// the new ones out, then change each test to write out the serialized bytes rather than read
|
||||||
|
// the file.
|
||||||
|
class EnumEvolveTests {
|
||||||
|
var localPath = projectRootDir.toUri().resolve(
|
||||||
|
"node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp")
|
||||||
|
|
||||||
|
// Version of the class as it was serialised
|
||||||
|
//
|
||||||
|
// @CordaSerializationTransformEnumDefault("D", "C")
|
||||||
|
// enum class DeserializeNewerSetToUnknown { A, B, C, D }
|
||||||
|
//
|
||||||
|
// Version of the class as it's used in the test
|
||||||
|
enum class DeserializeNewerSetToUnknown { A, B, C }
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun deserialiseNewerSetToUnknown() {
|
||||||
|
val resource = "${this.javaClass.simpleName}.${testName()}"
|
||||||
|
val sf = testDefaultFactory()
|
||||||
|
|
||||||
|
data class C (val e : DeserializeNewerSetToUnknown)
|
||||||
|
|
||||||
|
// Uncomment to re-generate test files
|
||||||
|
// File(URI("$localPath/$resource")).writeBytes(
|
||||||
|
// SerializationOutput(sf).serialize(C(DeserializeNewerSetToUnknown.D)).bytes)
|
||||||
|
|
||||||
|
Assertions.assertThatThrownBy {
|
||||||
|
DeserializationInput(sf).deserialize(SerializedBytes<C>(
|
||||||
|
File(EvolvabilityTests::class.java.getResource(resource).toURI()).readBytes()))
|
||||||
|
}.isInstanceOf(NotSerializableException::class.java)
|
||||||
|
}
|
||||||
|
}
|
@ -17,7 +17,7 @@ class OverridePKSerializerTest {
|
|||||||
throw SerializerTestException("Custom write call")
|
throw SerializerTestException("Custom write call")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): PublicKey {
|
override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): PublicKey {
|
||||||
throw SerializerTestException("Custom read call")
|
throw SerializerTestException("Custom read call")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Binary file not shown.
@ -54,8 +54,6 @@ dependencies {
|
|||||||
compile project(':client:rpc')
|
compile project(':client:rpc')
|
||||||
compile "net.corda.plugins:cordform-common:$gradle_plugins_version"
|
compile "net.corda.plugins:cordform-common:$gradle_plugins_version"
|
||||||
|
|
||||||
compile "com.google.code.findbugs:jsr305:3.0.1"
|
|
||||||
|
|
||||||
// Log4J: logging framework (with SLF4J bindings)
|
// Log4J: logging framework (with SLF4J bindings)
|
||||||
compile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}"
|
compile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}"
|
||||||
compile "org.apache.logging.log4j:log4j-web:${log4j_version}"
|
compile "org.apache.logging.log4j:log4j-web:${log4j_version}"
|
||||||
|
@ -10,7 +10,7 @@ import net.corda.node.internal.NodeStartup
|
|||||||
import net.corda.node.services.Permissions.Companion.startFlow
|
import net.corda.node.services.Permissions.Companion.startFlow
|
||||||
import net.corda.nodeapi.User
|
import net.corda.nodeapi.User
|
||||||
import net.corda.testing.ALICE
|
import net.corda.testing.ALICE
|
||||||
import net.corda.testing.ProjectStructure.projectRootDir
|
import net.corda.testing.common.internal.ProjectStructure.projectRootDir
|
||||||
import net.corda.testing.driver.driver
|
import net.corda.testing.driver.driver
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||||
|
@ -213,7 +213,7 @@ abstract class MQSecurityTest : NodeBasedTest() {
|
|||||||
|
|
||||||
private fun startBobAndCommunicateWithAlice(): Party {
|
private fun startBobAndCommunicateWithAlice(): Party {
|
||||||
val bob = startNode(BOB.name)
|
val bob = startNode(BOB.name)
|
||||||
bob.internals.registerInitiatedFlow(ReceiveFlow::class.java)
|
bob.registerInitiatedFlow(ReceiveFlow::class.java)
|
||||||
val bobParty = bob.info.chooseIdentity()
|
val bobParty = bob.info.chooseIdentity()
|
||||||
// Perform a protocol exchange to force the peer queue to be created
|
// Perform a protocol exchange to force the peer queue to be created
|
||||||
alice.services.startFlow(SendFlow(bobParty, 0)).resultFuture.getOrThrow()
|
alice.services.startFlow(SendFlow(bobParty, 0)).resultFuture.getOrThrow()
|
||||||
|
@ -65,6 +65,40 @@ class NodeStatePersistenceTests {
|
|||||||
val retrievedMessage = stateAndRef!!.state.data.message
|
val retrievedMessage = stateAndRef!!.state.data.message
|
||||||
assertEquals(message, retrievedMessage)
|
assertEquals(message, retrievedMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `persistent state survives node restart without reinitialising database schema`() {
|
||||||
|
// Temporary disable this test when executed on Windows. It is known to be sporadically failing.
|
||||||
|
// More investigation is needed to establish why.
|
||||||
|
assumeFalse(System.getProperty("os.name").toLowerCase().startsWith("win"))
|
||||||
|
|
||||||
|
val user = User("mark", "dadada", setOf(startFlow<SendMessageFlow>(), invokeRpc("vaultQuery")))
|
||||||
|
val message = Message("Hello world!")
|
||||||
|
val stateAndRef: StateAndRef<MessageState>? = driver(isDebug = true, startNodesInProcess = isQuasarAgentSpecified()) {
|
||||||
|
val nodeName = {
|
||||||
|
val nodeHandle = startNode(rpcUsers = listOf(user)).getOrThrow()
|
||||||
|
val nodeName = nodeHandle.nodeInfo.chooseIdentity().name
|
||||||
|
// Ensure the notary node has finished starting up, before starting a flow that needs a notary
|
||||||
|
defaultNotaryNode.getOrThrow()
|
||||||
|
nodeHandle.rpcClientToNode().start(user.username, user.password).use {
|
||||||
|
it.proxy.startFlow(::SendMessageFlow, message, defaultNotaryIdentity).returnValue.getOrThrow()
|
||||||
|
}
|
||||||
|
nodeHandle.stop()
|
||||||
|
nodeName
|
||||||
|
}()
|
||||||
|
|
||||||
|
val nodeHandle = startNode(providedName = nodeName, rpcUsers = listOf(user), customOverrides = mapOf("devMode" to "false")).getOrThrow()
|
||||||
|
val result = nodeHandle.rpcClientToNode().start(user.username, user.password).use {
|
||||||
|
val page = it.proxy.vaultQuery(MessageState::class.java)
|
||||||
|
page.states.singleOrNull()
|
||||||
|
}
|
||||||
|
nodeHandle.stop()
|
||||||
|
result
|
||||||
|
}
|
||||||
|
assertNotNull(stateAndRef)
|
||||||
|
val retrievedMessage = stateAndRef!!.state.data.message
|
||||||
|
assertEquals(message, retrievedMessage)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isQuasarAgentSpecified(): Boolean {
|
fun isQuasarAgentSpecified(): Boolean {
|
||||||
|
@ -3,6 +3,8 @@ package net.corda.node.internal
|
|||||||
import com.codahale.metrics.MetricRegistry
|
import com.codahale.metrics.MetricRegistry
|
||||||
import com.google.common.collect.MutableClassToInstanceMap
|
import com.google.common.collect.MutableClassToInstanceMap
|
||||||
import com.google.common.util.concurrent.MoreExecutors
|
import com.google.common.util.concurrent.MoreExecutors
|
||||||
|
import com.zaxxer.hikari.HikariConfig
|
||||||
|
import com.zaxxer.hikari.HikariDataSource
|
||||||
import net.corda.confidential.SwapIdentitiesFlow
|
import net.corda.confidential.SwapIdentitiesFlow
|
||||||
import net.corda.confidential.SwapIdentitiesHandler
|
import net.corda.confidential.SwapIdentitiesHandler
|
||||||
import net.corda.core.CordaException
|
import net.corda.core.CordaException
|
||||||
@ -55,11 +57,13 @@ 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.node.utilities.CordaPersistence
|
|
||||||
import net.corda.node.utilities.configureDatabase
|
|
||||||
import net.corda.nodeapi.internal.NetworkParameters
|
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.DatabaseConfig
|
||||||
|
import net.corda.nodeapi.internal.persistence.HibernateConfiguration
|
||||||
import org.apache.activemq.artemis.utils.ReusableLatch
|
import org.apache.activemq.artemis.utils.ReusableLatch
|
||||||
|
import org.hibernate.type.descriptor.java.JavaTypeDescriptorRegistry
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
@ -125,7 +129,6 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
private lateinit var _services: ServiceHubInternalImpl
|
private lateinit var _services: ServiceHubInternalImpl
|
||||||
protected var myNotaryIdentity: PartyAndCertificate? = null
|
protected var myNotaryIdentity: PartyAndCertificate? = null
|
||||||
protected lateinit var checkpointStorage: CheckpointStorage
|
protected lateinit var checkpointStorage: CheckpointStorage
|
||||||
protected lateinit var smm: StateMachineManager
|
|
||||||
private lateinit var tokenizableServices: List<Any>
|
private lateinit var tokenizableServices: List<Any>
|
||||||
protected lateinit var attachments: NodeAttachmentService
|
protected lateinit var attachments: NodeAttachmentService
|
||||||
protected lateinit var network: MessagingService
|
protected lateinit var network: MessagingService
|
||||||
@ -158,7 +161,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
@Volatile private var _started: StartedNode<AbstractNode>? = null
|
@Volatile private var _started: StartedNode<AbstractNode>? = null
|
||||||
|
|
||||||
/** The implementation of the [CordaRPCOps] interface used by this node. */
|
/** The implementation of the [CordaRPCOps] interface used by this node. */
|
||||||
open fun makeRPCOps(flowStarter: FlowStarter, database: CordaPersistence): CordaRPCOps {
|
open fun makeRPCOps(flowStarter: FlowStarter, database: CordaPersistence, smm: StateMachineManager): CordaRPCOps {
|
||||||
return SecureCordaRPCOps(services, smm, database, flowStarter)
|
return SecureCordaRPCOps(services, smm, database, flowStarter)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,7 +191,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
initCertificate()
|
initCertificate()
|
||||||
val (keyPairs, info) = initNodeInfo()
|
val (keyPairs, info) = initNodeInfo()
|
||||||
readNetworkParameters()
|
readNetworkParameters()
|
||||||
val schemaService = NodeSchemaService(cordappLoader)
|
val schemaService = NodeSchemaService(cordappLoader.cordappSchemas)
|
||||||
val identityService = makeIdentityService(info)
|
val identityService = makeIdentityService(info)
|
||||||
// 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 ->
|
||||||
@ -197,7 +200,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
val stateLoader = StateLoaderImpl(transactionStorage)
|
val stateLoader = StateLoaderImpl(transactionStorage)
|
||||||
val nodeServices = makeServices(keyPairs, schemaService, transactionStorage, stateLoader, database, info, identityService)
|
val nodeServices = makeServices(keyPairs, schemaService, transactionStorage, stateLoader, database, info, identityService)
|
||||||
val notaryService = makeNotaryService(nodeServices, database)
|
val notaryService = makeNotaryService(nodeServices, database)
|
||||||
smm = makeStateMachineManager(database)
|
val smm = makeStateMachineManager(database)
|
||||||
val flowStarter = FlowStarterImpl(serverThread, smm)
|
val flowStarter = FlowStarterImpl(serverThread, smm)
|
||||||
val schedulerService = NodeSchedulerService(
|
val schedulerService = NodeSchedulerService(
|
||||||
platformClock,
|
platformClock,
|
||||||
@ -214,13 +217,13 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
MoreExecutors.shutdownAndAwaitTermination(serverThread as ExecutorService, 50, SECONDS)
|
MoreExecutors.shutdownAndAwaitTermination(serverThread as ExecutorService, 50, SECONDS)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
makeVaultObservers(schedulerService, database.hibernateConfig)
|
makeVaultObservers(schedulerService, database.hibernateConfig, smm, schemaService)
|
||||||
val rpcOps = makeRPCOps(flowStarter, database)
|
val rpcOps = makeRPCOps(flowStarter, database, smm)
|
||||||
startMessagingService(rpcOps)
|
startMessagingService(rpcOps)
|
||||||
installCoreFlows()
|
installCoreFlows()
|
||||||
val cordaServices = installCordaServices(flowStarter)
|
val cordaServices = installCordaServices(flowStarter)
|
||||||
tokenizableServices = nodeServices + cordaServices + schedulerService
|
tokenizableServices = nodeServices + cordaServices + schedulerService
|
||||||
registerCordappFlows()
|
registerCordappFlows(smm)
|
||||||
_services.rpcFlows += cordappLoader.cordapps.flatMap { it.rpcFlows }
|
_services.rpcFlows += cordappLoader.cordapps.flatMap { it.rpcFlows }
|
||||||
FlowLogicRefFactoryImpl.classloader = cordappLoader.appClassLoader
|
FlowLogicRefFactoryImpl.classloader = cordappLoader.appClassLoader
|
||||||
startShell(rpcOps)
|
startShell(rpcOps)
|
||||||
@ -408,11 +411,11 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
installCoreFlow(NotaryFlow.Client::class, service::createServiceFlow)
|
installCoreFlow(NotaryFlow.Client::class, service::createServiceFlow)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun registerCordappFlows() {
|
private fun registerCordappFlows(smm: StateMachineManager) {
|
||||||
cordappLoader.cordapps.flatMap { it.initiatedFlows }
|
cordappLoader.cordapps.flatMap { it.initiatedFlows }
|
||||||
.forEach {
|
.forEach {
|
||||||
try {
|
try {
|
||||||
registerInitiatedFlowInternal(it, track = false)
|
registerInitiatedFlowInternal(smm, it, track = false)
|
||||||
} catch (e: NoSuchMethodException) {
|
} catch (e: NoSuchMethodException) {
|
||||||
log.error("${it.name}, as an initiated flow, must have a constructor with a single parameter " +
|
log.error("${it.name}, as an initiated flow, must have a constructor with a single parameter " +
|
||||||
"of type ${Party::class.java.name}")
|
"of type ${Party::class.java.name}")
|
||||||
@ -422,13 +425,8 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
internal fun <T : FlowLogic<*>> registerInitiatedFlow(smm: StateMachineManager, initiatedFlowClass: Class<T>): Observable<T> {
|
||||||
* Use this method to register your initiated flows in your tests. This is automatically done by the node when it
|
return registerInitiatedFlowInternal(smm, initiatedFlowClass, track = true)
|
||||||
* starts up for all [FlowLogic] classes it finds which are annotated with [InitiatedBy].
|
|
||||||
* @return An [Observable] of the initiated flows started by counter-parties.
|
|
||||||
*/
|
|
||||||
fun <T : FlowLogic<*>> registerInitiatedFlow(initiatedFlowClass: Class<T>): Observable<T> {
|
|
||||||
return registerInitiatedFlowInternal(initiatedFlowClass, track = true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO remove once not needed
|
// TODO remove once not needed
|
||||||
@ -437,7 +435,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
"It should accept a ${FlowSession::class.java.simpleName} instead"
|
"It should accept a ${FlowSession::class.java.simpleName} instead"
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <F : FlowLogic<*>> registerInitiatedFlowInternal(initiatedFlow: Class<F>, track: Boolean): Observable<F> {
|
private fun <F : FlowLogic<*>> registerInitiatedFlowInternal(smm: StateMachineManager, initiatedFlow: Class<F>, track: Boolean): Observable<F> {
|
||||||
val constructors = initiatedFlow.declaredConstructors.associateBy { it.parameterTypes.toList() }
|
val constructors = initiatedFlow.declaredConstructors.associateBy { it.parameterTypes.toList() }
|
||||||
val flowSessionCtor = constructors[listOf(FlowSession::class.java)]?.apply { isAccessible = true }
|
val flowSessionCtor = constructors[listOf(FlowSession::class.java)]?.apply { isAccessible = true }
|
||||||
val ctor: (FlowSession) -> F = if (flowSessionCtor == null) {
|
val ctor: (FlowSession) -> F = if (flowSessionCtor == null) {
|
||||||
@ -458,16 +456,16 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
"${InitiatedBy::class.java.name} must point to ${classWithAnnotation.name} and not ${initiatingFlow.name}"
|
"${InitiatedBy::class.java.name} must point to ${classWithAnnotation.name} and not ${initiatingFlow.name}"
|
||||||
}
|
}
|
||||||
val flowFactory = InitiatedFlowFactory.CorDapp(version, initiatedFlow.appName, ctor)
|
val flowFactory = InitiatedFlowFactory.CorDapp(version, initiatedFlow.appName, ctor)
|
||||||
val observable = internalRegisterFlowFactory(initiatingFlow, flowFactory, initiatedFlow, track)
|
val observable = internalRegisterFlowFactory(smm, initiatingFlow, flowFactory, initiatedFlow, track)
|
||||||
log.info("Registered ${initiatingFlow.name} to initiate ${initiatedFlow.name} (version $version)")
|
log.info("Registered ${initiatingFlow.name} to initiate ${initiatedFlow.name} (version $version)")
|
||||||
return observable
|
return observable
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
internal fun <F : FlowLogic<*>> internalRegisterFlowFactory(smm: StateMachineManager,
|
||||||
fun <F : FlowLogic<*>> internalRegisterFlowFactory(initiatingFlowClass: Class<out FlowLogic<*>>,
|
initiatingFlowClass: Class<out FlowLogic<*>>,
|
||||||
flowFactory: InitiatedFlowFactory<F>,
|
flowFactory: InitiatedFlowFactory<F>,
|
||||||
initiatedFlowClass: Class<F>,
|
initiatedFlowClass: Class<F>,
|
||||||
track: Boolean): Observable<F> {
|
track: Boolean): Observable<F> {
|
||||||
val observable = if (track) {
|
val observable = if (track) {
|
||||||
smm.changes.filter { it is StateMachineManager.Change.Add }.map { it.logic }.ofType(initiatedFlowClass)
|
smm.changes.filter { it is StateMachineManager.Change.Add }.map { it.logic }.ofType(initiatedFlowClass)
|
||||||
} else {
|
} else {
|
||||||
@ -530,10 +528,11 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected open fun makeTransactionStorage(database: CordaPersistence): WritableTransactionStorage = DBTransactionStorage()
|
protected open fun makeTransactionStorage(database: CordaPersistence): WritableTransactionStorage = DBTransactionStorage()
|
||||||
private fun makeVaultObservers(schedulerService: SchedulerService, hibernateConfig: HibernateConfiguration) {
|
|
||||||
|
private fun makeVaultObservers(schedulerService: SchedulerService, hibernateConfig: HibernateConfiguration, smm: StateMachineManager, schemaService: SchemaService) {
|
||||||
VaultSoftLockManager.install(services.vaultService, smm)
|
VaultSoftLockManager.install(services.vaultService, smm)
|
||||||
ScheduledActivityObserver.install(services.vaultService, schedulerService)
|
ScheduledActivityObserver.install(services.vaultService, schedulerService)
|
||||||
HibernateObserver.install(services.vaultService.rawUpdates, hibernateConfig)
|
HibernateObserver.install(services.vaultService.rawUpdates, hibernateConfig, schemaService)
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
@ -777,7 +776,23 @@ internal class FlowStarterImpl(private val serverThread: AffinityExecutor, priva
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ConfigurationException(message: String) : CordaException(message)
|
class ConfigurationException(message: String) : CordaException(message)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thrown when a node is about to start and its network map cache doesn't contain any node.
|
* Thrown when a node is about to start and its network map cache doesn't contain any node.
|
||||||
*/
|
*/
|
||||||
internal class NetworkMapCacheEmptyException : Exception()
|
internal class NetworkMapCacheEmptyException : Exception()
|
||||||
|
|
||||||
|
fun configureDatabase(dataSourceProperties: Properties,
|
||||||
|
databaseConfig: DatabaseConfig,
|
||||||
|
identityService: IdentityService,
|
||||||
|
schemaService: SchemaService = NodeSchemaService()): CordaPersistence {
|
||||||
|
// Register the AbstractPartyDescriptor so Hibernate doesn't warn when encountering AbstractParty. Unfortunately
|
||||||
|
// Hibernate warns about not being able to find a descriptor if we don't provide one, but won't use it by default
|
||||||
|
// so we end up providing both descriptor and converter. We should re-examine this in later versions to see if
|
||||||
|
// either Hibernate can be convinced to stop warning, use the descriptor by default, or something else.
|
||||||
|
JavaTypeDescriptorRegistry.INSTANCE.addDescriptor(AbstractPartyDescriptor(identityService))
|
||||||
|
val config = HikariConfig(dataSourceProperties)
|
||||||
|
val dataSource = HikariDataSource(config)
|
||||||
|
val attributeConverters = listOf(AbstractPartyToX500NameAsStringConverter(identityService))
|
||||||
|
return CordaPersistence(dataSource, databaseConfig, schemaService.schemaOptions.keys, attributeConverters)
|
||||||
|
}
|
||||||
|
@ -26,7 +26,7 @@ import net.corda.node.services.api.FlowStarter
|
|||||||
import net.corda.node.services.api.ServiceHubInternal
|
import net.corda.node.services.api.ServiceHubInternal
|
||||||
import net.corda.node.services.messaging.context
|
import net.corda.node.services.messaging.context
|
||||||
import net.corda.node.services.statemachine.StateMachineManager
|
import net.corda.node.services.statemachine.StateMachineManager
|
||||||
import net.corda.node.utilities.CordaPersistence
|
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
|
@ -10,9 +10,9 @@ import net.corda.core.node.NodeInfo
|
|||||||
import net.corda.core.node.ServiceHub
|
import net.corda.core.node.ServiceHub
|
||||||
import net.corda.core.node.services.IdentityService
|
import net.corda.core.node.services.IdentityService
|
||||||
import net.corda.core.node.services.TransactionVerifierService
|
import net.corda.core.node.services.TransactionVerifierService
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
|
||||||
import net.corda.core.serialization.internal.SerializationEnvironmentImpl
|
import net.corda.core.serialization.internal.SerializationEnvironmentImpl
|
||||||
import net.corda.core.serialization.internal.nodeSerializationEnv
|
import net.corda.core.serialization.internal.nodeSerializationEnv
|
||||||
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.contextLogger
|
||||||
import net.corda.node.VersionInfo
|
import net.corda.node.VersionInfo
|
||||||
import net.corda.node.internal.cordapp.CordappLoader
|
import net.corda.node.internal.cordapp.CordappLoader
|
||||||
@ -25,10 +25,10 @@ import net.corda.node.services.messaging.*
|
|||||||
import net.corda.node.services.transactions.InMemoryTransactionVerifierService
|
import net.corda.node.services.transactions.InMemoryTransactionVerifierService
|
||||||
import net.corda.node.utilities.AddressUtils
|
import net.corda.node.utilities.AddressUtils
|
||||||
import net.corda.node.utilities.AffinityExecutor
|
import net.corda.node.utilities.AffinityExecutor
|
||||||
import net.corda.node.utilities.CordaPersistence
|
|
||||||
import net.corda.node.utilities.DemoClock
|
import net.corda.node.utilities.DemoClock
|
||||||
import net.corda.nodeapi.internal.ShutdownHook
|
import net.corda.nodeapi.internal.ShutdownHook
|
||||||
import net.corda.nodeapi.internal.addShutdownHook
|
import net.corda.nodeapi.internal.addShutdownHook
|
||||||
|
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||||
import net.corda.nodeapi.internal.serialization.*
|
import net.corda.nodeapi.internal.serialization.*
|
||||||
import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme
|
import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
|
@ -5,7 +5,7 @@ import net.corda.node.services.api.FlowStarter
|
|||||||
import net.corda.node.services.api.ServiceHubInternal
|
import net.corda.node.services.api.ServiceHubInternal
|
||||||
import net.corda.node.services.messaging.rpcContext
|
import net.corda.node.services.messaging.rpcContext
|
||||||
import net.corda.node.services.statemachine.StateMachineManager
|
import net.corda.node.services.statemachine.StateMachineManager
|
||||||
import net.corda.node.utilities.CordaPersistence
|
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of [CordaRPCOps] that checks authorisation.
|
* Implementation of [CordaRPCOps] that checks authorisation.
|
||||||
|
@ -2,6 +2,8 @@ package net.corda.node.internal
|
|||||||
|
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
|
import net.corda.core.flows.InitiatedBy
|
||||||
|
import net.corda.core.internal.VisibleForTesting
|
||||||
import net.corda.core.messaging.CordaRPCOps
|
import net.corda.core.messaging.CordaRPCOps
|
||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
import net.corda.core.node.StateLoader
|
import net.corda.core.node.StateLoader
|
||||||
@ -12,7 +14,8 @@ import net.corda.node.services.api.StartedNodeServices
|
|||||||
import net.corda.node.services.messaging.MessagingService
|
import net.corda.node.services.messaging.MessagingService
|
||||||
import net.corda.node.services.persistence.NodeAttachmentService
|
import net.corda.node.services.persistence.NodeAttachmentService
|
||||||
import net.corda.node.services.statemachine.StateMachineManager
|
import net.corda.node.services.statemachine.StateMachineManager
|
||||||
import net.corda.node.utilities.CordaPersistence
|
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||||
|
import rx.Observable
|
||||||
|
|
||||||
interface StartedNode<out N : AbstractNode> {
|
interface StartedNode<out N : AbstractNode> {
|
||||||
val internals: N
|
val internals: N
|
||||||
@ -26,7 +29,20 @@ interface StartedNode<out N : AbstractNode> {
|
|||||||
val rpcOps: CordaRPCOps
|
val rpcOps: CordaRPCOps
|
||||||
val notaryService: NotaryService?
|
val notaryService: NotaryService?
|
||||||
fun dispose() = internals.stop()
|
fun dispose() = internals.stop()
|
||||||
fun <T : FlowLogic<*>> registerInitiatedFlow(initiatedFlowClass: Class<T>) = internals.registerInitiatedFlow(initiatedFlowClass)
|
/**
|
||||||
|
* Use this method to register your initiated flows in your tests. This is automatically done by the node when it
|
||||||
|
* starts up for all [FlowLogic] classes it finds which are annotated with [InitiatedBy].
|
||||||
|
* @return An [Observable] of the initiated flows started by counter-parties.
|
||||||
|
*/
|
||||||
|
fun <T : FlowLogic<*>> registerInitiatedFlow(initiatedFlowClass: Class<T>) = internals.registerInitiatedFlow(smm, initiatedFlowClass)
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
fun <F : FlowLogic<*>> internalRegisterFlowFactory(initiatingFlowClass: Class<out FlowLogic<*>>,
|
||||||
|
flowFactory: InitiatedFlowFactory<F>,
|
||||||
|
initiatedFlowClass: Class<F>,
|
||||||
|
track: Boolean): Observable<F> {
|
||||||
|
return internals.internalRegisterFlowFactory(smm, initiatingFlowClass, flowFactory, initiatedFlowClass, track)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class StateLoaderImpl(private val validatedTransactions: TransactionStorage) : StateLoader {
|
class StateLoaderImpl(private val validatedTransactions: TransactionStorage) : StateLoader {
|
||||||
|
@ -52,12 +52,14 @@ class CordappLoader private constructor(private val cordappJarPaths: List<Restri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val cordappSchemas: Set<MappedSchema> get() = cordapps.flatMap { it.customSchemas }.toSet()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val logger = contextLogger()
|
private val logger = contextLogger()
|
||||||
/**
|
/**
|
||||||
* Default cordapp dir name
|
* Default cordapp dir name
|
||||||
*/
|
*/
|
||||||
val CORDAPPS_DIR_NAME = "cordapps"
|
private const val CORDAPPS_DIR_NAME = "cordapps"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a default CordappLoader intended to be used in non-dev or non-test environments.
|
* Creates a default CordappLoader intended to be used in non-dev or non-test environments.
|
||||||
@ -79,7 +81,9 @@ class CordappLoader private constructor(private val cordappJarPaths: List<Restri
|
|||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
fun createDefaultWithTestPackages(configuration: NodeConfiguration, testPackages: List<String>): CordappLoader {
|
fun createDefaultWithTestPackages(configuration: NodeConfiguration, testPackages: List<String>): CordappLoader {
|
||||||
check(configuration.devMode) { "Package scanning can only occur in dev mode" }
|
if (!configuration.devMode) {
|
||||||
|
logger.warn("Package scanning should only occur in dev mode!")
|
||||||
|
}
|
||||||
val paths = getCordappsInDirectory(getCordappsPath(configuration.baseDirectory)) + testPackages.flatMap(this::createScanPackage)
|
val paths = getCordappsInDirectory(getCordappsPath(configuration.baseDirectory)) + testPackages.flatMap(this::createScanPackage)
|
||||||
return cordappLoadersCache.computeIfAbsent(paths, { CordappLoader(paths) })
|
return cordappLoadersCache.computeIfAbsent(paths, { CordappLoader(paths) })
|
||||||
}
|
}
|
||||||
@ -92,8 +96,9 @@ class CordappLoader private constructor(private val cordappJarPaths: List<Restri
|
|||||||
* CorDapps.
|
* CorDapps.
|
||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
fun createWithTestPackages(testPackages: List<String>)
|
fun createWithTestPackages(testPackages: List<String>): CordappLoader {
|
||||||
= cordappLoadersCache.computeIfAbsent(testPackages, { CordappLoader(testPackages.flatMap(this::createScanPackage)) })
|
return cordappLoadersCache.computeIfAbsent(testPackages, { CordappLoader(testPackages.flatMap(this::createScanPackage)) })
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a dev mode CordappLoader intended only to be used in test environments
|
* Creates a dev mode CordappLoader intended only to be used in test environments
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
package net.corda.node.services.api
|
package net.corda.node.services.api
|
||||||
|
|
||||||
import net.corda.core.concurrent.CordaFuture
|
import net.corda.core.concurrent.CordaFuture
|
||||||
|
import net.corda.core.context.InvocationContext
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
import net.corda.core.flows.StateMachineRunId
|
import net.corda.core.flows.StateMachineRunId
|
||||||
import net.corda.core.internal.FlowStateMachine
|
import net.corda.core.internal.FlowStateMachine
|
||||||
import net.corda.core.context.InvocationContext
|
|
||||||
import net.corda.core.internal.uncheckedCast
|
import net.corda.core.internal.uncheckedCast
|
||||||
import net.corda.core.messaging.DataFeed
|
import net.corda.core.messaging.DataFeed
|
||||||
import net.corda.core.messaging.StateMachineTransactionMapping
|
import net.corda.core.messaging.StateMachineTransactionMapping
|
||||||
@ -23,7 +23,7 @@ import net.corda.node.services.config.NodeConfiguration
|
|||||||
import net.corda.node.services.messaging.MessagingService
|
import net.corda.node.services.messaging.MessagingService
|
||||||
import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl
|
import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl
|
||||||
import net.corda.node.services.statemachine.FlowStateMachineImpl
|
import net.corda.node.services.statemachine.FlowStateMachineImpl
|
||||||
import net.corda.node.utilities.CordaPersistence
|
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||||
|
|
||||||
interface NetworkMapCacheInternal : NetworkMapCache, NetworkMapCacheBaseInternal
|
interface NetworkMapCacheInternal : NetworkMapCache, NetworkMapCacheBaseInternal
|
||||||
interface NetworkMapCacheBaseInternal : NetworkMapCacheBase {
|
interface NetworkMapCacheBaseInternal : NetworkMapCacheBase {
|
||||||
|
@ -5,6 +5,7 @@ import net.corda.core.identity.CordaX500Name
|
|||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
import net.corda.core.utilities.seconds
|
import net.corda.core.utilities.seconds
|
||||||
import net.corda.node.services.messaging.CertificateChainCheckPolicy
|
import net.corda.node.services.messaging.CertificateChainCheckPolicy
|
||||||
|
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
||||||
import net.corda.nodeapi.User
|
import net.corda.nodeapi.User
|
||||||
import net.corda.nodeapi.config.NodeSSLConfiguration
|
import net.corda.nodeapi.config.NodeSSLConfiguration
|
||||||
import net.corda.nodeapi.config.parseAs
|
import net.corda.nodeapi.config.parseAs
|
||||||
@ -19,7 +20,6 @@ interface NodeConfiguration : NodeSSLConfiguration {
|
|||||||
val emailAddress: String
|
val emailAddress: String
|
||||||
val exportJMXto: String
|
val exportJMXto: String
|
||||||
val dataSourceProperties: Properties
|
val dataSourceProperties: Properties
|
||||||
val database: DatabaseConfig
|
|
||||||
val rpcUsers: List<User>
|
val rpcUsers: List<User>
|
||||||
val devMode: Boolean
|
val devMode: Boolean
|
||||||
val devModeOptions: DevModeOptions?
|
val devModeOptions: DevModeOptions?
|
||||||
@ -30,6 +30,7 @@ interface NodeConfiguration : NodeSSLConfiguration {
|
|||||||
val notary: NotaryConfig?
|
val notary: NotaryConfig?
|
||||||
val activeMQServer: ActiveMqServerConfiguration
|
val activeMQServer: ActiveMqServerConfiguration
|
||||||
val additionalNodeInfoPollingFrequencyMsec: Long
|
val additionalNodeInfoPollingFrequencyMsec: Long
|
||||||
|
// TODO Remove as this is only used by the driver
|
||||||
val useHTTPS: Boolean
|
val useHTTPS: Boolean
|
||||||
val p2pAddress: NetworkHostAndPort
|
val p2pAddress: NetworkHostAndPort
|
||||||
val rpcAddress: NetworkHostAndPort?
|
val rpcAddress: NetworkHostAndPort?
|
||||||
@ -38,29 +39,11 @@ interface NodeConfiguration : NodeSSLConfiguration {
|
|||||||
val useTestClock: Boolean get() = false
|
val useTestClock: Boolean get() = false
|
||||||
val detectPublicIp: Boolean get() = true
|
val detectPublicIp: Boolean get() = true
|
||||||
val sshd: SSHDConfiguration?
|
val sshd: SSHDConfiguration?
|
||||||
|
val database: DatabaseConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
data class DevModeOptions(val disableCheckpointChecker: Boolean = false)
|
data class DevModeOptions(val disableCheckpointChecker: Boolean = false)
|
||||||
|
|
||||||
data class DatabaseConfig(
|
|
||||||
val initDatabase: Boolean = true,
|
|
||||||
val serverNameTablePrefix: String = "",
|
|
||||||
val transactionIsolationLevel: TransactionIsolationLevel = TransactionIsolationLevel.REPEATABLE_READ
|
|
||||||
)
|
|
||||||
|
|
||||||
enum class TransactionIsolationLevel {
|
|
||||||
NONE,
|
|
||||||
READ_UNCOMMITTED,
|
|
||||||
READ_COMMITTED,
|
|
||||||
REPEATABLE_READ,
|
|
||||||
SERIALIZABLE;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The JDBC constant value of the same name but with prefixed with TRANSACTION_ defined in [java.sql.Connection].
|
|
||||||
*/
|
|
||||||
val jdbcValue: Int = java.sql.Connection::class.java.getField("TRANSACTION_$name").get(null) as Int
|
|
||||||
}
|
|
||||||
|
|
||||||
fun NodeConfiguration.shouldCheckCheckpoints(): Boolean {
|
fun NodeConfiguration.shouldCheckCheckpoints(): Boolean {
|
||||||
return this.devMode && this.devModeOptions?.disableCheckpointChecker != true
|
return this.devMode && this.devModeOptions?.disableCheckpointChecker != true
|
||||||
}
|
}
|
||||||
@ -108,7 +91,6 @@ data class NodeConfigurationImpl(
|
|||||||
override val keyStorePassword: String,
|
override val keyStorePassword: String,
|
||||||
override val trustStorePassword: String,
|
override val trustStorePassword: String,
|
||||||
override val dataSourceProperties: Properties,
|
override val dataSourceProperties: Properties,
|
||||||
override val database: DatabaseConfig = DatabaseConfig(),
|
|
||||||
override val compatibilityZoneURL: URL? = null,
|
override val compatibilityZoneURL: URL? = null,
|
||||||
override val rpcUsers: List<User>,
|
override val rpcUsers: List<User>,
|
||||||
override val verifierType: VerifierType,
|
override val verifierType: VerifierType,
|
||||||
@ -130,9 +112,9 @@ data class NodeConfigurationImpl(
|
|||||||
override val activeMQServer: ActiveMqServerConfiguration,
|
override val activeMQServer: ActiveMqServerConfiguration,
|
||||||
// TODO See TODO above. Rename this to nodeInfoPollingFrequency and make it of type Duration
|
// TODO See TODO above. Rename this to nodeInfoPollingFrequency and make it of type Duration
|
||||||
override val additionalNodeInfoPollingFrequencyMsec: Long = 5.seconds.toMillis(),
|
override val additionalNodeInfoPollingFrequencyMsec: Long = 5.seconds.toMillis(),
|
||||||
override val sshd: SSHDConfiguration? = null
|
override val sshd: SSHDConfiguration? = null,
|
||||||
|
override val database: DatabaseConfig = DatabaseConfig(initialiseSchema = devMode)
|
||||||
) : NodeConfiguration {
|
) : NodeConfiguration {
|
||||||
override val exportJMXto: String get() = "http"
|
override val exportJMXto: String get() = "http"
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
@ -3,6 +3,7 @@ package net.corda.node.services.events
|
|||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import com.google.common.util.concurrent.ListenableFuture
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
import net.corda.core.context.InvocationContext
|
import net.corda.core.context.InvocationContext
|
||||||
|
import net.corda.core.context.Origin
|
||||||
import net.corda.core.contracts.SchedulableState
|
import net.corda.core.contracts.SchedulableState
|
||||||
import net.corda.core.contracts.ScheduledActivity
|
import net.corda.core.contracts.ScheduledActivity
|
||||||
import net.corda.core.contracts.ScheduledStateRef
|
import net.corda.core.contracts.ScheduledStateRef
|
||||||
@ -12,7 +13,6 @@ import net.corda.core.flows.FlowLogic
|
|||||||
import net.corda.core.internal.ThreadBox
|
import net.corda.core.internal.ThreadBox
|
||||||
import net.corda.core.internal.VisibleForTesting
|
import net.corda.core.internal.VisibleForTesting
|
||||||
import net.corda.core.internal.concurrent.flatMap
|
import net.corda.core.internal.concurrent.flatMap
|
||||||
import net.corda.core.context.Origin
|
|
||||||
import net.corda.core.internal.until
|
import net.corda.core.internal.until
|
||||||
import net.corda.core.node.StateLoader
|
import net.corda.core.node.StateLoader
|
||||||
import net.corda.core.schemas.PersistentStateRef
|
import net.corda.core.schemas.PersistentStateRef
|
||||||
@ -24,9 +24,9 @@ import net.corda.node.services.api.FlowStarter
|
|||||||
import net.corda.node.services.api.SchedulerService
|
import net.corda.node.services.api.SchedulerService
|
||||||
import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl
|
import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl
|
||||||
import net.corda.node.utilities.AffinityExecutor
|
import net.corda.node.utilities.AffinityExecutor
|
||||||
import net.corda.node.utilities.CordaPersistence
|
|
||||||
import net.corda.node.utilities.NODE_DATABASE_PREFIX
|
|
||||||
import net.corda.node.utilities.PersistentMap
|
import net.corda.node.utilities.PersistentMap
|
||||||
|
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||||
|
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
|
||||||
import org.apache.activemq.artemis.utils.ReusableLatch
|
import org.apache.activemq.artemis.utils.ReusableLatch
|
||||||
import java.time.Clock
|
import java.time.Clock
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
@ -13,7 +13,7 @@ import net.corda.core.utilities.MAX_HASH_HEX_SIZE
|
|||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.contextLogger
|
||||||
import net.corda.core.utilities.debug
|
import net.corda.core.utilities.debug
|
||||||
import net.corda.node.utilities.AppendOnlyPersistentMap
|
import net.corda.node.utilities.AppendOnlyPersistentMap
|
||||||
import net.corda.node.utilities.NODE_DATABASE_PREFIX
|
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
|
||||||
import net.corda.nodeapi.internal.crypto.X509CertificateFactory
|
import net.corda.nodeapi.internal.crypto.X509CertificateFactory
|
||||||
import org.bouncycastle.cert.X509CertificateHolder
|
import org.bouncycastle.cert.X509CertificateHolder
|
||||||
import java.security.InvalidAlgorithmParameterException
|
import java.security.InvalidAlgorithmParameterException
|
||||||
|
@ -7,7 +7,7 @@ import net.corda.core.node.services.KeyManagementService
|
|||||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||||
import net.corda.core.utilities.MAX_HASH_HEX_SIZE
|
import net.corda.core.utilities.MAX_HASH_HEX_SIZE
|
||||||
import net.corda.node.utilities.AppendOnlyPersistentMap
|
import net.corda.node.utilities.AppendOnlyPersistentMap
|
||||||
import net.corda.node.utilities.NODE_DATABASE_PREFIX
|
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
|
||||||
import org.bouncycastle.operator.ContentSigner
|
import org.bouncycastle.operator.ContentSigner
|
||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
import java.security.PrivateKey
|
import java.security.PrivateKey
|
||||||
|
@ -190,11 +190,13 @@ fun <M : Any> MessagingService.onNext(topic: String, sessionId: Long): CordaFutu
|
|||||||
return messageFuture
|
return messageFuture
|
||||||
}
|
}
|
||||||
|
|
||||||
fun MessagingService.send(topic: String, sessionID: Long, payload: Any, to: MessageRecipients, uuid: UUID = UUID.randomUUID())
|
fun MessagingService.send(topic: String, sessionID: Long, payload: Any, to: MessageRecipients, uuid: UUID = UUID.randomUUID()) {
|
||||||
= send(TopicSession(topic, sessionID), payload, to, uuid)
|
send(TopicSession(topic, sessionID), payload, to, uuid)
|
||||||
|
}
|
||||||
|
|
||||||
fun MessagingService.send(topicSession: TopicSession, payload: Any, to: MessageRecipients, uuid: UUID = UUID.randomUUID(), retryId: Long? = null)
|
fun MessagingService.send(topicSession: TopicSession, payload: Any, to: MessageRecipients, uuid: UUID = UUID.randomUUID(), retryId: Long? = null) {
|
||||||
= send(createMessage(topicSession, payload.serialize().bytes, uuid), to, retryId)
|
send(createMessage(topicSession, payload.serialize().bytes, uuid), to, retryId)
|
||||||
|
}
|
||||||
|
|
||||||
interface MessageHandlerRegistration
|
interface MessageHandlerRegistration
|
||||||
|
|
||||||
|
@ -17,10 +17,13 @@ import net.corda.core.utilities.trace
|
|||||||
import net.corda.node.VersionInfo
|
import net.corda.node.VersionInfo
|
||||||
import net.corda.node.services.config.NodeConfiguration
|
import net.corda.node.services.config.NodeConfiguration
|
||||||
import net.corda.node.services.statemachine.StateMachineManagerImpl
|
import net.corda.node.services.statemachine.StateMachineManagerImpl
|
||||||
import net.corda.node.utilities.*
|
import net.corda.node.utilities.AffinityExecutor
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent
|
import net.corda.node.utilities.AppendOnlyPersistentMap
|
||||||
|
import net.corda.node.utilities.PersistentMap
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.*
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.*
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_QUEUE
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_QUEUE
|
||||||
|
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||||
|
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
|
||||||
import org.apache.activemq.artemis.api.core.ActiveMQObjectClosedException
|
import org.apache.activemq.artemis.api.core.ActiveMQObjectClosedException
|
||||||
import org.apache.activemq.artemis.api.core.Message.*
|
import org.apache.activemq.artemis.api.core.Message.*
|
||||||
import org.apache.activemq.artemis.api.core.RoutingType
|
import org.apache.activemq.artemis.api.core.RoutingType
|
||||||
|
@ -20,6 +20,7 @@ import net.corda.core.context.Trace.InvocationId
|
|||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.identity.CordaX500Name
|
||||||
import net.corda.core.internal.LazyStickyPool
|
import net.corda.core.internal.LazyStickyPool
|
||||||
import net.corda.core.internal.LifeCycle
|
import net.corda.core.internal.LifeCycle
|
||||||
|
import net.corda.core.internal.join
|
||||||
import net.corda.core.messaging.RPCOps
|
import net.corda.core.messaging.RPCOps
|
||||||
import net.corda.core.serialization.SerializationContext
|
import net.corda.core.serialization.SerializationContext
|
||||||
import net.corda.core.serialization.SerializationDefaults.RPC_SERVER_CONTEXT
|
import net.corda.core.serialization.SerializationDefaults.RPC_SERVER_CONTEXT
|
||||||
@ -207,6 +208,7 @@ class RPCServer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun close() {
|
fun close() {
|
||||||
|
observationSendExecutor?.join()
|
||||||
reaperScheduledFuture?.cancel(false)
|
reaperScheduledFuture?.cancel(false)
|
||||||
rpcExecutor?.shutdownNow()
|
rpcExecutor?.shutdownNow()
|
||||||
reaperExecutor?.shutdownNow()
|
reaperExecutor?.shutdownNow()
|
||||||
|
@ -21,9 +21,9 @@ import net.corda.core.utilities.NetworkHostAndPort
|
|||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.contextLogger
|
||||||
import net.corda.node.services.api.NetworkMapCacheBaseInternal
|
import net.corda.node.services.api.NetworkMapCacheBaseInternal
|
||||||
import net.corda.node.services.api.NetworkMapCacheInternal
|
import net.corda.node.services.api.NetworkMapCacheInternal
|
||||||
import net.corda.node.utilities.CordaPersistence
|
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||||
import net.corda.node.utilities.bufferUntilDatabaseCommit
|
import net.corda.nodeapi.internal.persistence.bufferUntilDatabaseCommit
|
||||||
import net.corda.node.utilities.wrapWithDatabaseTransaction
|
import net.corda.nodeapi.internal.persistence.wrapWithDatabaseTransaction
|
||||||
import net.corda.nodeapi.internal.NotaryInfo
|
import net.corda.nodeapi.internal.NotaryInfo
|
||||||
import org.hibernate.Session
|
import org.hibernate.Session
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
@ -162,13 +162,13 @@ open class PersistentNetworkMapCache(
|
|||||||
if (previousNode == null) {
|
if (previousNode == null) {
|
||||||
logger.info("No previous node found")
|
logger.info("No previous node found")
|
||||||
database.transaction {
|
database.transaction {
|
||||||
updateInfoDB(node)
|
updateInfoDB(node, session)
|
||||||
changePublisher.onNext(MapChange.Added(node))
|
changePublisher.onNext(MapChange.Added(node))
|
||||||
}
|
}
|
||||||
} else if (previousNode != node) {
|
} else if (previousNode != node) {
|
||||||
logger.info("Previous node was found as: $previousNode")
|
logger.info("Previous node was found as: $previousNode")
|
||||||
database.transaction {
|
database.transaction {
|
||||||
updateInfoDB(node)
|
updateInfoDB(node, session)
|
||||||
changePublisher.onNext(MapChange.Modified(node, previousNode))
|
changePublisher.onNext(MapChange.Modified(node, previousNode))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -223,25 +223,14 @@ open class PersistentNetworkMapCache(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateInfoDB(nodeInfo: NodeInfo) {
|
private fun updateInfoDB(nodeInfo: NodeInfo, session: Session) {
|
||||||
// TODO Temporary workaround to force isolated transaction (otherwise it causes race conditions when processing
|
// TODO For now the main legal identity is left in NodeInfo, this should be set comparision/come up with index for NodeInfo?
|
||||||
// network map registration on network map node)
|
val info = findByIdentityKey(session, nodeInfo.legalIdentitiesAndCerts.first().owningKey)
|
||||||
database.dataSource.connection.use {
|
val nodeInfoEntry = generateMappedObject(nodeInfo)
|
||||||
val session = database.entityManagerFactory.withOptions().connection(it.apply {
|
if (info.isNotEmpty()) {
|
||||||
transactionIsolation = 1
|
nodeInfoEntry.id = info.first().id
|
||||||
}).openSession()
|
|
||||||
session.use {
|
|
||||||
val tx = session.beginTransaction()
|
|
||||||
// TODO For now the main legal identity is left in NodeInfo, this should be set comparision/come up with index for NodeInfo?
|
|
||||||
val info = findByIdentityKey(session, nodeInfo.legalIdentitiesAndCerts.first().owningKey)
|
|
||||||
val nodeInfoEntry = generateMappedObject(nodeInfo)
|
|
||||||
if (info.isNotEmpty()) {
|
|
||||||
nodeInfoEntry.id = info[0].id
|
|
||||||
}
|
|
||||||
session.merge(nodeInfoEntry)
|
|
||||||
tx.commit()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
session.merge(nodeInfoEntry)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun removeInfoDB(session: Session, nodeInfo: NodeInfo) {
|
private fun removeInfoDB(session: Session, nodeInfo: NodeInfo) {
|
||||||
|
@ -3,8 +3,8 @@ package net.corda.node.services.persistence
|
|||||||
import net.corda.core.serialization.SerializedBytes
|
import net.corda.core.serialization.SerializedBytes
|
||||||
import net.corda.node.services.api.Checkpoint
|
import net.corda.node.services.api.Checkpoint
|
||||||
import net.corda.node.services.api.CheckpointStorage
|
import net.corda.node.services.api.CheckpointStorage
|
||||||
import net.corda.node.utilities.NODE_DATABASE_PREFIX
|
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
|
||||||
import net.corda.node.utilities.currentDBSession
|
import net.corda.nodeapi.internal.persistence.currentDBSession
|
||||||
import javax.persistence.Column
|
import javax.persistence.Column
|
||||||
import javax.persistence.Entity
|
import javax.persistence.Entity
|
||||||
import javax.persistence.Id
|
import javax.persistence.Id
|
||||||
|
@ -7,6 +7,9 @@ import net.corda.core.messaging.DataFeed
|
|||||||
import net.corda.core.messaging.StateMachineTransactionMapping
|
import net.corda.core.messaging.StateMachineTransactionMapping
|
||||||
import net.corda.node.services.api.StateMachineRecordedTransactionMappingStorage
|
import net.corda.node.services.api.StateMachineRecordedTransactionMappingStorage
|
||||||
import net.corda.node.utilities.*
|
import net.corda.node.utilities.*
|
||||||
|
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
|
||||||
|
import net.corda.nodeapi.internal.persistence.bufferUntilDatabaseCommit
|
||||||
|
import net.corda.nodeapi.internal.persistence.wrapWithDatabaseTransaction
|
||||||
import rx.subjects.PublishSubject
|
import rx.subjects.PublishSubject
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.annotation.concurrent.ThreadSafe
|
import javax.annotation.concurrent.ThreadSafe
|
||||||
|
@ -8,6 +8,9 @@ import net.corda.core.serialization.*
|
|||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.node.services.api.WritableTransactionStorage
|
import net.corda.node.services.api.WritableTransactionStorage
|
||||||
import net.corda.node.utilities.*
|
import net.corda.node.utilities.*
|
||||||
|
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
|
||||||
|
import net.corda.nodeapi.internal.persistence.bufferUntilDatabaseCommit
|
||||||
|
import net.corda.nodeapi.internal.persistence.wrapWithDatabaseTransaction
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.subjects.PublishSubject
|
import rx.subjects.PublishSubject
|
||||||
import javax.persistence.*
|
import javax.persistence.*
|
||||||
|
@ -17,9 +17,9 @@ import net.corda.core.node.services.vault.AttachmentSort
|
|||||||
import net.corda.core.serialization.*
|
import net.corda.core.serialization.*
|
||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.contextLogger
|
||||||
import net.corda.node.services.vault.HibernateAttachmentQueryCriteriaParser
|
import net.corda.node.services.vault.HibernateAttachmentQueryCriteriaParser
|
||||||
import net.corda.node.utilities.DatabaseTransactionManager
|
import net.corda.nodeapi.internal.persistence.DatabaseTransactionManager
|
||||||
import net.corda.node.utilities.NODE_DATABASE_PREFIX
|
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
|
||||||
import net.corda.node.utilities.currentDBSession
|
import net.corda.nodeapi.internal.persistence.currentDBSession
|
||||||
import java.io.*
|
import java.io.*
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
@ -9,8 +9,9 @@ import net.corda.core.schemas.MappedSchema
|
|||||||
import net.corda.core.schemas.PersistentStateRef
|
import net.corda.core.schemas.PersistentStateRef
|
||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.contextLogger
|
||||||
import net.corda.core.utilities.debug
|
import net.corda.core.utilities.debug
|
||||||
import net.corda.node.services.persistence.HibernateConfiguration
|
import net.corda.node.services.api.SchemaService
|
||||||
import net.corda.node.utilities.DatabaseTransactionManager
|
import net.corda.nodeapi.internal.persistence.HibernateConfiguration
|
||||||
|
import net.corda.nodeapi.internal.persistence.DatabaseTransactionManager
|
||||||
import org.hibernate.FlushMode
|
import org.hibernate.FlushMode
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
|
||||||
@ -18,12 +19,11 @@ import rx.Observable
|
|||||||
* A vault observer that extracts Object Relational Mappings for contract states that support it, and persists them with Hibernate.
|
* A vault observer that extracts Object Relational Mappings for contract states that support it, and persists them with Hibernate.
|
||||||
*/
|
*/
|
||||||
// TODO: Manage version evolution of the schemas via additional tooling.
|
// TODO: Manage version evolution of the schemas via additional tooling.
|
||||||
class HibernateObserver private constructor(private val config: HibernateConfiguration) {
|
class HibernateObserver private constructor(private val config: HibernateConfiguration, private val schemaService: SchemaService) {
|
||||||
companion object {
|
companion object {
|
||||||
private val log = contextLogger()
|
private val log = contextLogger()
|
||||||
@JvmStatic
|
fun install(vaultUpdates: Observable<Vault.Update<ContractState>>, config: HibernateConfiguration, schemaService: SchemaService): HibernateObserver {
|
||||||
fun install(vaultUpdates: Observable<Vault.Update<ContractState>>, config: HibernateConfiguration): HibernateObserver {
|
val observer = HibernateObserver(config, schemaService)
|
||||||
val observer = HibernateObserver(config)
|
|
||||||
vaultUpdates.subscribe { observer.persist(it.produced) }
|
vaultUpdates.subscribe { observer.persist(it.produced) }
|
||||||
return observer
|
return observer
|
||||||
}
|
}
|
||||||
@ -36,7 +36,7 @@ class HibernateObserver private constructor(private val config: HibernateConfigu
|
|||||||
private fun persistState(stateAndRef: StateAndRef<ContractState>) {
|
private fun persistState(stateAndRef: StateAndRef<ContractState>) {
|
||||||
val state = stateAndRef.state.data
|
val state = stateAndRef.state.data
|
||||||
log.debug { "Asked to persist state ${stateAndRef.ref}" }
|
log.debug { "Asked to persist state ${stateAndRef.ref}" }
|
||||||
config.schemaService.selectSchemas(state).forEach { persistStateWithSchema(state, stateAndRef.ref, it) }
|
schemaService.selectSchemas(state).forEach { persistStateWithSchema(state, stateAndRef.ref, it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
@ -47,7 +47,7 @@ class HibernateObserver private constructor(private val config: HibernateConfigu
|
|||||||
flushMode(FlushMode.MANUAL).
|
flushMode(FlushMode.MANUAL).
|
||||||
openSession()
|
openSession()
|
||||||
session.use {
|
session.use {
|
||||||
val mappedObject = config.schemaService.generateMappedObject(state, schema)
|
val mappedObject = schemaService.generateMappedObject(state, schema)
|
||||||
mappedObject.stateRef = PersistentStateRef(stateRef)
|
mappedObject.stateRef = PersistentStateRef(stateRef)
|
||||||
it.persist(mappedObject)
|
it.persist(mappedObject)
|
||||||
it.flush()
|
it.flush()
|
||||||
|
@ -9,8 +9,8 @@ import net.corda.core.schemas.MappedSchema
|
|||||||
import net.corda.core.schemas.PersistentState
|
import net.corda.core.schemas.PersistentState
|
||||||
import net.corda.core.schemas.QueryableState
|
import net.corda.core.schemas.QueryableState
|
||||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||||
import net.corda.node.internal.cordapp.CordappLoader
|
|
||||||
import net.corda.node.services.api.SchemaService
|
import net.corda.node.services.api.SchemaService
|
||||||
|
import net.corda.node.services.api.SchemaService.SchemaOptions
|
||||||
import net.corda.node.services.events.NodeSchedulerService
|
import net.corda.node.services.events.NodeSchedulerService
|
||||||
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
|
||||||
@ -27,14 +27,12 @@ import net.corda.node.services.vault.VaultSchemaV1
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Most basic implementation of [SchemaService].
|
* Most basic implementation of [SchemaService].
|
||||||
* @param cordappLoader if not null, custom schemas will be extracted from its cordapps.
|
|
||||||
* TODO: support loading schema options from node configuration.
|
* TODO: support loading schema options from node configuration.
|
||||||
* TODO: support configuring what schemas are to be selected for persistence.
|
* TODO: support configuring what schemas are to be selected for persistence.
|
||||||
* TODO: support plugins for schema version upgrading or custom mapping not supported by original [QueryableState].
|
* TODO: support plugins for schema version upgrading or custom mapping not supported by original [QueryableState].
|
||||||
* TODO: create whitelisted tables when a CorDapp is first installed
|
* TODO: create whitelisted tables when a CorDapp is first installed
|
||||||
*/
|
*/
|
||||||
class NodeSchemaService(cordappLoader: CordappLoader?) : SchemaService, SingletonSerializeAsToken() {
|
class NodeSchemaService(extraSchemas: Set<MappedSchema> = emptySet()) : SchemaService, SingletonSerializeAsToken() {
|
||||||
|
|
||||||
// Entities for compulsory services
|
// Entities for compulsory services
|
||||||
object NodeServices
|
object NodeServices
|
||||||
|
|
||||||
@ -60,17 +58,12 @@ class NodeSchemaService(cordappLoader: CordappLoader?) : SchemaService, Singleto
|
|||||||
// Required schemas are those used by internal Corda services
|
// Required schemas are those used by internal Corda services
|
||||||
// For example, cash is used by the vault for coin selection (but will be extracted as a standalone CorDapp in future)
|
// For example, cash is used by the vault for coin selection (but will be extracted as a standalone CorDapp in future)
|
||||||
private val requiredSchemas: Map<MappedSchema, SchemaService.SchemaOptions> =
|
private val requiredSchemas: Map<MappedSchema, SchemaService.SchemaOptions> =
|
||||||
mapOf(Pair(CommonSchemaV1, SchemaService.SchemaOptions()),
|
mapOf(Pair(CommonSchemaV1, SchemaOptions()),
|
||||||
Pair(VaultSchemaV1, SchemaService.SchemaOptions()),
|
Pair(VaultSchemaV1, SchemaOptions()),
|
||||||
Pair(NodeInfoSchemaV1, SchemaService.SchemaOptions()),
|
Pair(NodeInfoSchemaV1, SchemaOptions()),
|
||||||
Pair(NodeServicesV1, SchemaService.SchemaOptions()))
|
Pair(NodeServicesV1, SchemaOptions()))
|
||||||
|
|
||||||
override val schemaOptions: Map<MappedSchema, SchemaService.SchemaOptions> = if (cordappLoader == null) {
|
override val schemaOptions: Map<MappedSchema, SchemaService.SchemaOptions> = requiredSchemas + extraSchemas.associateBy({ it }, { SchemaOptions() })
|
||||||
requiredSchemas
|
|
||||||
} else {
|
|
||||||
val customSchemas = cordappLoader.cordapps.flatMap { it.customSchemas }.toSet()
|
|
||||||
requiredSchemas.plus(customSchemas.map { mappedSchema -> Pair(mappedSchema, SchemaService.SchemaOptions()) })
|
|
||||||
}
|
|
||||||
|
|
||||||
// Currently returns all schemas supported by the state, with no filtering or enrichment.
|
// Currently returns all schemas supported by the state, with no filtering or enrichment.
|
||||||
override fun selectSchemas(state: ContractState): Iterable<MappedSchema> {
|
override fun selectSchemas(state: ContractState): Iterable<MappedSchema> {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user