mirror of
https://github.com/corda/corda.git
synced 2025-02-11 21:26:23 +00:00
Merge remote-tracking branch 'remotes/open/master' into parkri-os-merge-20180713-1
This commit is contained in:
commit
4e81d26985
@ -34,7 +34,7 @@ buildscript {
|
|||||||
ext.capsule_version = '1.0.1'
|
ext.capsule_version = '1.0.1'
|
||||||
|
|
||||||
ext.asm_version = '5.0.4'
|
ext.asm_version = '5.0.4'
|
||||||
ext.artemis_version = '2.5.0'
|
ext.artemis_version = '2.6.2'
|
||||||
ext.jackson_version = '2.9.5'
|
ext.jackson_version = '2.9.5'
|
||||||
ext.jetty_version = '9.4.7.v20170914'
|
ext.jetty_version = '9.4.7.v20170914'
|
||||||
ext.jersey_version = '2.25'
|
ext.jersey_version = '2.25'
|
||||||
@ -79,8 +79,8 @@ buildscript {
|
|||||||
ext.curator_version = '4.0.1'
|
ext.curator_version = '4.0.1'
|
||||||
ext.proguard_version = constants.getProperty('proguardVersion')
|
ext.proguard_version = constants.getProperty('proguardVersion')
|
||||||
ext.jsch_version = '0.1.54'
|
ext.jsch_version = '0.1.54'
|
||||||
ext.protonj_version = '0.27.1'
|
|
||||||
ext.commons_cli_version = '1.4'
|
ext.commons_cli_version = '1.4'
|
||||||
|
ext.protonj_version = '0.27.1' // This is now aligned with the Artemis version, but retaining in case we ever need to diverge again for a bug fix.
|
||||||
ext.snappy_version = '0.4'
|
ext.snappy_version = '0.4'
|
||||||
ext.fast_classpath_scanner_version = '2.12.3'
|
ext.fast_classpath_scanner_version = '2.12.3'
|
||||||
ext.jcabi_manifests_version = '1.1'
|
ext.jcabi_manifests_version = '1.1'
|
||||||
@ -118,7 +118,6 @@ buildscript {
|
|||||||
classpath 'com.github.ben-manes:gradle-versions-plugin:0.15.0'
|
classpath 'com.github.ben-manes:gradle-versions-plugin:0.15.0'
|
||||||
classpath "org.jetbrains.kotlin:kotlin-noarg:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-noarg:$kotlin_version"
|
||||||
classpath "org.jetbrains.dokka:dokka-gradle-plugin:${dokka_version}"
|
classpath "org.jetbrains.dokka:dokka-gradle-plugin:${dokka_version}"
|
||||||
classpath "org.ajoberstar:grgit:1.1.0"
|
|
||||||
classpath "net.i2p.crypto:eddsa:$eddsa_version" // Needed for ServiceIdentityGenerator in the build environment.
|
classpath "net.i2p.crypto:eddsa:$eddsa_version" // Needed for ServiceIdentityGenerator in the build environment.
|
||||||
classpath "org.owasp:dependency-check-gradle:${dependency_checker_version}"
|
classpath "org.owasp:dependency-check-gradle:${dependency_checker_version}"
|
||||||
classpath "org.jfrog.buildinfo:build-info-extractor-gradle:$artifactory_plugin_version"
|
classpath "org.jfrog.buildinfo:build-info-extractor-gradle:$artifactory_plugin_version"
|
||||||
@ -133,7 +132,7 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
corda_revision = org.ajoberstar.grgit.Grgit.open(file('.')).head().id
|
corda_revision = "git rev-parse HEAD".execute().text.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
apply plugin: 'project-report'
|
apply plugin: 'project-report'
|
||||||
|
@ -13,7 +13,7 @@ package net.corda.client.rpc
|
|||||||
import net.corda.core.CordaRuntimeException
|
import net.corda.core.CordaRuntimeException
|
||||||
import net.corda.core.concurrent.CordaFuture
|
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.RPCOps
|
||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.core.utilities.getOrThrow
|
||||||
import net.corda.testing.core.SerializationEnvironmentRule
|
import net.corda.testing.core.SerializationEnvironmentRule
|
||||||
import net.corda.testing.node.internal.rpcDriver
|
import net.corda.testing.node.internal.rpcDriver
|
||||||
@ -70,10 +70,10 @@ class RPCFailureTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `unserializable`() = rpc {
|
fun unserializable() = rpc {
|
||||||
assertThatThrownBy { it.getUnserializable() }.isInstanceOf(CordaRuntimeException::class.java)
|
assertThatThrownBy { it.getUnserializable() }.isInstanceOf(CordaRuntimeException::class.java)
|
||||||
.hasMessageContaining("java.io.NotSerializableException:")
|
.hasMessageContaining("java.io.NotSerializableException:")
|
||||||
.hasMessageContaining("Unserializable is not on the whitelist or annotated with @CordaSerializable.")
|
.hasMessageContaining("Unserializable\" is not on the whitelist or annotated with @CordaSerializable.")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -81,6 +81,6 @@ class RPCFailureTests {
|
|||||||
val future = it.getUnserializableAsync()
|
val future = it.getUnserializableAsync()
|
||||||
assertThatThrownBy { future.getOrThrow() }.isInstanceOf(CordaRuntimeException::class.java)
|
assertThatThrownBy { future.getOrThrow() }.isInstanceOf(CordaRuntimeException::class.java)
|
||||||
.hasMessageContaining("java.io.NotSerializableException:")
|
.hasMessageContaining("java.io.NotSerializableException:")
|
||||||
.hasMessageContaining("Unserializable is not on the whitelist or annotated with @CordaSerializable.")
|
.hasMessageContaining("Unserializable\" is not on the whitelist or annotated with @CordaSerializable.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ class SwapIdentitiesFlowTests {
|
|||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
// We run this in parallel threads to help catch any race conditions that may exist.
|
// We run this in parallel threads to help catch any race conditions that may exist.
|
||||||
mockNet = InternalMockNetwork(emptyList(), networkSendManuallyPumped = false, threadPerNode = true)
|
mockNet = InternalMockNetwork(networkSendManuallyPumped = false, threadPerNode = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -26,8 +26,6 @@ configurations {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compileOnly project(':core')
|
compileOnly project(':core')
|
||||||
compileOnly "com.google.guava:guava:$guava_version"
|
|
||||||
compileOnly "$quasar_group:quasar-core:$quasar_version:jdk8"
|
|
||||||
|
|
||||||
// Configure these by hand. It should be a minimal subset of core's dependencies,
|
// Configure these by hand. It should be a minimal subset of core's dependencies,
|
||||||
// and without any obviously non-deterministic ones such as Hibernate.
|
// and without any obviously non-deterministic ones such as Hibernate.
|
||||||
@ -37,7 +35,6 @@ dependencies {
|
|||||||
runtimeLibraries "org.bouncycastle:bcprov-jdk15on:$bouncycastle_version"
|
runtimeLibraries "org.bouncycastle:bcprov-jdk15on:$bouncycastle_version"
|
||||||
runtimeLibraries "org.bouncycastle:bcpkix-jdk15on:$bouncycastle_version"
|
runtimeLibraries "org.bouncycastle:bcpkix-jdk15on:$bouncycastle_version"
|
||||||
runtimeLibraries "com.google.code.findbugs:jsr305:$jsr305_version"
|
runtimeLibraries "com.google.code.findbugs:jsr305:$jsr305_version"
|
||||||
runtimeLibraries "com.google.guava:guava:$guava_version"
|
|
||||||
runtimeLibraries "net.i2p.crypto:eddsa:$eddsa_version"
|
runtimeLibraries "net.i2p.crypto:eddsa:$eddsa_version"
|
||||||
runtimeLibraries "org.slf4j:slf4j-api:$slf4j_version"
|
runtimeLibraries "org.slf4j:slf4j-api:$slf4j_version"
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@
|
|||||||
|
|
||||||
package net.corda.core.identity
|
package net.corda.core.identity
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableSet
|
|
||||||
import net.corda.core.KeepForDJVM
|
import net.corda.core.KeepForDJVM
|
||||||
import net.corda.core.internal.LegalNameValidator
|
import net.corda.core.internal.LegalNameValidator
|
||||||
import net.corda.core.internal.toAttributesMap
|
import net.corda.core.internal.toAttributesMap
|
||||||
@ -89,7 +88,7 @@ data class CordaX500Name(val commonName: String?,
|
|||||||
const val MAX_LENGTH_COMMON_NAME = 64
|
const val MAX_LENGTH_COMMON_NAME = 64
|
||||||
|
|
||||||
private val supportedAttributes = setOf(BCStyle.O, BCStyle.C, BCStyle.L, BCStyle.CN, BCStyle.ST, BCStyle.OU)
|
private val supportedAttributes = setOf(BCStyle.O, BCStyle.C, BCStyle.L, BCStyle.CN, BCStyle.ST, BCStyle.OU)
|
||||||
private val countryCodes: Set<String> = ImmutableSet.copyOf(Locale.getISOCountries() + unspecifiedCountry)
|
private val countryCodes: Set<String> = setOf(*Locale.getISOCountries(), unspecifiedCountry)
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun build(principal: X500Principal): CordaX500Name {
|
fun build(principal: X500Principal): CordaX500Name {
|
||||||
|
@ -12,8 +12,6 @@
|
|||||||
@file:KeepForDJVM
|
@file:KeepForDJVM
|
||||||
package net.corda.core.internal
|
package net.corda.core.internal
|
||||||
|
|
||||||
import com.google.common.hash.Hashing
|
|
||||||
import com.google.common.hash.HashingInputStream
|
|
||||||
import net.corda.core.DeleteForDJVM
|
import net.corda.core.DeleteForDJVM
|
||||||
import net.corda.core.KeepForDJVM
|
import net.corda.core.KeepForDJVM
|
||||||
import net.corda.core.cordapp.Cordapp
|
import net.corda.core.cordapp.Cordapp
|
||||||
@ -53,6 +51,7 @@ 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.security.KeyPair
|
import java.security.KeyPair
|
||||||
|
import java.security.MessageDigest
|
||||||
import java.security.PrivateKey
|
import java.security.PrivateKey
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.security.cert.*
|
import java.security.cert.*
|
||||||
@ -142,9 +141,16 @@ fun InputStream.readFully(): ByteArray = use { it.readBytes() }
|
|||||||
/** Calculate the hash of the remaining bytes in this input stream. The stream is closed at the end. */
|
/** Calculate the hash of the remaining bytes in this input stream. The stream is closed at the end. */
|
||||||
fun InputStream.hash(): SecureHash {
|
fun InputStream.hash(): SecureHash {
|
||||||
return use {
|
return use {
|
||||||
val his = HashingInputStream(Hashing.sha256(), it)
|
val md = MessageDigest.getInstance("SHA-256")
|
||||||
his.copyTo(NullOutputStream) // To avoid reading in the entire stream into memory just write out the bytes to /dev/null
|
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
|
||||||
SecureHash.SHA256(his.hash().asBytes())
|
while (true) {
|
||||||
|
val count = it.read(buffer)
|
||||||
|
if (count == -1) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
md.update(buffer, 0, count)
|
||||||
|
}
|
||||||
|
SecureHash.SHA256(md.digest())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,13 +44,13 @@ import java.time.Instant
|
|||||||
*/
|
*/
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
data class StateMachineInfo @JvmOverloads constructor(
|
data class StateMachineInfo @JvmOverloads constructor(
|
||||||
/** A univerally unique ID ([java.util.UUID]) representing this particular instance of the named flow. */
|
/** A universally unique ID ([java.util.UUID]) representing this particular instance of the named flow. */
|
||||||
val id: StateMachineRunId,
|
val id: StateMachineRunId,
|
||||||
/** The JVM class name of the flow code. */
|
/** The JVM class name of the flow code. */
|
||||||
val flowLogicClassName: String,
|
val flowLogicClassName: String,
|
||||||
/**
|
/**
|
||||||
* An object representing information about the initiator of the flow. Note that this field is
|
* An object representing information about the initiator of the flow. Note that this field is
|
||||||
* superceded by the [invocationContext] property, which has more detail.
|
* superseded by the [invocationContext] property, which has more detail.
|
||||||
*/
|
*/
|
||||||
@Deprecated("There is more info available using 'invocationContext'") val initiator: FlowInitiator,
|
@Deprecated("There is more info available using 'invocationContext'") val initiator: FlowInitiator,
|
||||||
/** A [DataFeed] of the current progress step as a human readable string, and updates to that string. */
|
/** A [DataFeed] of the current progress step as a human readable string, and updates to that string. */
|
||||||
@ -378,9 +378,24 @@ interface CordaRPCOps : RPCOps {
|
|||||||
*/
|
*/
|
||||||
fun nodeInfoFromParty(party: AbstractParty): NodeInfo?
|
fun nodeInfoFromParty(party: AbstractParty): NodeInfo?
|
||||||
|
|
||||||
/** Clear all network map data from local node cache. */
|
/**
|
||||||
|
* Clear all network map data from local node cache. Notice that after invoking this method your node will lose
|
||||||
|
* network map data and effectively won't be able to start any flow with the peers until network map is downloaded
|
||||||
|
* again on next poll - from `additional-node-infos` directory or from network map server. It depends on the
|
||||||
|
* polling interval when it happens. You can also use [refreshNetworkMapCache] to force next fetch from network map server
|
||||||
|
* (not from directory - it will happen automatically).
|
||||||
|
* If you run local test deployment and want clear view of the network, you may want to clear also `additional-node-infos`
|
||||||
|
* directory, because cache can be repopulated from there.
|
||||||
|
*/
|
||||||
fun clearNetworkMapCache()
|
fun clearNetworkMapCache()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Poll network map server if available for the network map. Notice that you need to have `compatibilityZone`
|
||||||
|
* or `networkServices` configured. This is normally done automatically on the regular time interval, but you may wish to
|
||||||
|
* have the fresh view of network earlier.
|
||||||
|
*/
|
||||||
|
fun refreshNetworkMapCache()
|
||||||
|
|
||||||
/** Sets the value of the node's flows draining mode.
|
/** Sets the value of the node's flows draining mode.
|
||||||
* If this mode is [enabled], the node will reject new flows through RPC, ignore scheduled flows, and do not process
|
* If this mode is [enabled], the node will reject new flows through RPC, ignore scheduled flows, and do not process
|
||||||
* initial session messages, meaning that P2P counterparties will not be able to initiate new flows involving the node.
|
* initial session messages, meaning that P2P counterparties will not be able to initiate new flows involving the node.
|
||||||
|
@ -40,7 +40,7 @@ class AttachmentTests {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
mockNet = InternalMockNetwork(emptyList())
|
mockNet = InternalMockNetwork()
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
|
@ -23,7 +23,7 @@ import org.junit.After
|
|||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class ReceiveMultipleFlowTests {
|
class ReceiveMultipleFlowTests {
|
||||||
private val mockNet = InternalMockNetwork(emptyList())
|
private val mockNet = InternalMockNetwork()
|
||||||
private val nodes = (0..2).map { mockNet.createPartyNode() }
|
private val nodes = (0..2).map { mockNet.createPartyNode() }
|
||||||
@After
|
@After
|
||||||
fun stopNodes() {
|
fun stopNodes() {
|
||||||
|
@ -11,9 +11,11 @@
|
|||||||
package net.corda.core.internal
|
package net.corda.core.internal
|
||||||
|
|
||||||
import net.corda.core.contracts.TimeWindow
|
import net.corda.core.contracts.TimeWindow
|
||||||
|
import net.corda.core.crypto.SecureHash
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.Assert.assertArrayEquals
|
import org.junit.Assert.assertArrayEquals
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import java.util.*
|
||||||
import java.util.stream.IntStream
|
import java.util.stream.IntStream
|
||||||
import java.util.stream.Stream
|
import java.util.stream.Stream
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
@ -109,6 +111,19 @@ open class InternalUtilsTest {
|
|||||||
assertThat(PrivateClass::class.java.kotlinObjectInstance).isNull()
|
assertThat(PrivateClass::class.java.kotlinObjectInstance).isNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test SHA-256 hash for InputStream`() {
|
||||||
|
val contents = arrayOfJunk(DEFAULT_BUFFER_SIZE * 2 + DEFAULT_BUFFER_SIZE / 2)
|
||||||
|
assertThat(contents.inputStream().hash())
|
||||||
|
.isEqualTo(SecureHash.parse("A4759E7AA20338328866A2EA17EAF8C7FE4EC6BBE3BB71CEE7DF7C0461B3C22F"))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun arrayOfJunk(size: Int) = ByteArray(size).apply {
|
||||||
|
for (i in 0 until size) {
|
||||||
|
this[i] = (i and 0xFF).toByte()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
object PublicObject
|
object PublicObject
|
||||||
private object PrivateObject
|
private object PrivateObject
|
||||||
protected object ProtectedObject
|
protected object ProtectedObject
|
||||||
|
@ -82,7 +82,7 @@ class AttachmentSerializationTest {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
mockNet = InternalMockNetwork(emptyList())
|
mockNet = InternalMockNetwork()
|
||||||
server = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME))
|
server = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME))
|
||||||
client = mockNet.createNode(InternalMockNodeParameters(legalName = BOB_NAME))
|
client = mockNet.createNode(InternalMockNodeParameters(legalName = BOB_NAME))
|
||||||
client.internals.disableDBCloseOnStop() // Otherwise the in-memory database may disappear (taking the checkpoint with it) while we reboot the client.
|
client.internals.disableDBCloseOnStop() // Otherwise the in-memory database may disappear (taking the checkpoint with it) while we reboot the client.
|
||||||
|
@ -25,15 +25,38 @@ When inspecting your custom data structures, there is no need to include the jar
|
|||||||
in the classpath. The blob inspector (or rather the serialization framework) is able to synthesis any classes found in the
|
in the classpath. The blob inspector (or rather the serialization framework) is able to synthesis any classes found in the
|
||||||
blob that are not on the classpath.
|
blob that are not on the classpath.
|
||||||
|
|
||||||
SerializedBytes
|
Supported formats
|
||||||
~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
One thing to note is that the binary blob may contain embedded ``SerializedBytes`` objects. Rather than printing these
|
The inspector can read **input data** in three formats: raw binary, hex encoded text and base64 encoded text. For instance
|
||||||
out as a Base64 string, the blob inspector will first materialise them into Java objects and then output those. You will
|
if you have retrieved your binary data and it looks like this::
|
||||||
see this when dealing with classes such as ``SignedData`` or other structures that attach a signature, such as the
|
|
||||||
``nodeInfo-*`` files or the ``network-parameters`` file in the node's directory. For example, the output of a node-info
|
|
||||||
file may look like:
|
|
||||||
|
|
||||||
|
636f7264610100000080c562000000000001d0000030720000000300a3226e65742e636f7264613a38674f537471464b414a5055...
|
||||||
|
|
||||||
|
then you have hex encoded data. If it looks like this it's base64 encoded::
|
||||||
|
|
||||||
|
Y29yZGEBAAAAgMViAAAAAAAB0AAAMHIAAAADAKMibmV0LmNvcmRhOjhnT1N0cUZLQUpQVWVvY2Z2M1NlU1E9PdAAACc1AAAAAgCjIm5l...
|
||||||
|
|
||||||
|
And if it looks like something vomited over your screen it's raw binary. You don't normally need to care about these
|
||||||
|
differences because the tool will try every format until it works.
|
||||||
|
|
||||||
|
Something that's useful to know about Corda's format is that it always starts with the word "corda" in binary. Try
|
||||||
|
hex decoding 636f726461 using the `online hex decoder tool here <https://convertstring.com/EncodeDecode/HexDecode>`_
|
||||||
|
to see for yourself.
|
||||||
|
|
||||||
|
**Output data** can be in either a slightly extended form of YaML or JSON. YaML (Yet another markup language) is a bit
|
||||||
|
easier to read for humans and is the default. JSON can of course be parsed by any JSON library in any language.
|
||||||
|
|
||||||
|
.. note:: One thing to note is that the binary blob may contain embedded ``SerializedBytes`` objects. Rather than printing these
|
||||||
|
out as a Base64 string, the blob inspector will first materialise them into Java objects and then output those. You will
|
||||||
|
see this when dealing with classes such as ``SignedData`` or other structures that attach a signature, such as the
|
||||||
|
``nodeInfo-*`` files or the ``network-parameters`` file in the node's directory.
|
||||||
|
|
||||||
|
|
||||||
|
Example
|
||||||
|
~~~~~~~
|
||||||
|
|
||||||
|
Here's what a node-info file from the node's data directory may look like:
|
||||||
|
|
||||||
**-\\-format=YAML**
|
**-\\-format=YAML**
|
||||||
::
|
::
|
||||||
|
@ -12,7 +12,6 @@ CorDapps
|
|||||||
cordapp-build-systems
|
cordapp-build-systems
|
||||||
building-against-master
|
building-against-master
|
||||||
corda-api
|
corda-api
|
||||||
serialization-index
|
|
||||||
secure-coding-guidelines
|
secure-coding-guidelines
|
||||||
flow-cookbook
|
flow-cookbook
|
||||||
vault
|
vault
|
||||||
|
54
docs/source/building-container-images.rst
Normal file
54
docs/source/building-container-images.rst
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
=========================
|
||||||
|
Building Container Images
|
||||||
|
=========================
|
||||||
|
|
||||||
|
To build a container image of Corda you can use the Jib gradle tasks. See the `documentation of the Jib gradle plugin <https://github.com/GoogleContainerTools/jib/tree/master/jib-gradle-plugin>`_ for details.
|
||||||
|
|
||||||
|
Building the image
|
||||||
|
==================
|
||||||
|
|
||||||
|
To build an image locally you can use the following command. Note that you do not require Docker.
|
||||||
|
|
||||||
|
.. sourcecode:: shell
|
||||||
|
|
||||||
|
./gradlew node:jib --image <registry>/<image>:<tag>
|
||||||
|
|
||||||
|
If you prefer building to a Docker deamon you can use
|
||||||
|
|
||||||
|
.. sourcecode:: shell
|
||||||
|
|
||||||
|
./gradlew node:jibDockerBuild --image <registry>/<image>:<tag>
|
||||||
|
|
||||||
|
Running the image
|
||||||
|
=================
|
||||||
|
|
||||||
|
The Corda application expects its config file in ``/config/node.conf``, make
|
||||||
|
sure you mount the config file to that location. You might also want to mount
|
||||||
|
``/credentials`` and ``/persistence.mv.db`` (if you're using H2) in order to
|
||||||
|
preserve the credentials and node data between container restarts.
|
||||||
|
|
||||||
|
The JVM options are currently hardcoded in ``node/build.gradle`` in the
|
||||||
|
``jib.container`` section.
|
||||||
|
|
||||||
|
Below is an example directory layout and command to run your image with Docker.
|
||||||
|
Make sure to run ``touch persistence.mv.db`` befor starting the container,
|
||||||
|
otherwise a new directory will be created by Docker.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
.
|
||||||
|
├── additional-node-infos
|
||||||
|
├── certificates
|
||||||
|
├── config
|
||||||
|
│ └── node.conf
|
||||||
|
├── network-parameters
|
||||||
|
└── persistence.mv.db
|
||||||
|
|
||||||
|
.. sourcecode:: shell
|
||||||
|
|
||||||
|
docker run --rm -it -v ${PWD}/certificates:/certificates \
|
||||||
|
-v ${PWD}/config:/config \
|
||||||
|
-v ${PWD}/network-parameters:/network-parameters \
|
||||||
|
-v ${PWD}/persistence.mv.db:/persistence.mv.db \
|
||||||
|
-v ${PWD}/additional-node-infos:/additional-node-infos \
|
||||||
|
<registry>/<image>:<tag>
|
@ -178,6 +178,8 @@ Unreleased
|
|||||||
The log entry starts with `Cross-reference between MappedSchemas.`.
|
The log entry starts with `Cross-reference between MappedSchemas.`.
|
||||||
API: Persistence documentation no longer suggests mapping between different schemas.
|
API: Persistence documentation no longer suggests mapping between different schemas.
|
||||||
|
|
||||||
|
* Upgraded Artemis to v2.6.2.
|
||||||
|
|
||||||
.. _changelog_v3.1:
|
.. _changelog_v3.1:
|
||||||
|
|
||||||
Version 3.1
|
Version 3.1
|
||||||
|
@ -24,9 +24,7 @@ partially signed invalid transactions outside of the main network, and by doing
|
|||||||
traded asset are performed atomically by the same transaction. To perform such a trade involves a multi-step flow
|
traded asset are performed atomically by the same transaction. To perform such a trade involves a multi-step flow
|
||||||
in which messages are passed back and forth privately between parties, checked, signed and so on.
|
in which messages are passed back and forth privately between parties, checked, signed and so on.
|
||||||
|
|
||||||
Despite how useful these flows are, platforms such as Bitcoin and Ethereum do not assist the developer with the rather
|
There are many benefits of this flow based design and some development complexities as well. Some of the development challenges include:
|
||||||
tricky task of actually building them. That is unfortunate. There are many awkward problems in their implementation
|
|
||||||
that a good platform would take care of for you, problems like:
|
|
||||||
|
|
||||||
* Avoiding "callback hell" in which code that should ideally be sequential is turned into an unreadable mess due to the
|
* Avoiding "callback hell" in which code that should ideally be sequential is turned into an unreadable mess due to the
|
||||||
desire to avoid using up a thread for every flow instantiation.
|
desire to avoid using up a thread for every flow instantiation.
|
||||||
@ -517,4 +515,4 @@ the features we have planned:
|
|||||||
* Being able to interact with people, either via some sort of external ticketing system, or email, or a custom UI.
|
* Being able to interact with people, either via some sort of external ticketing system, or email, or a custom UI.
|
||||||
For example to implement human transaction authorisations
|
For example to implement human transaction authorisations
|
||||||
* A standard library of flows that can be easily sub-classed by local developers in order to integrate internal
|
* A standard library of flows that can be easily sub-classed by local developers in order to integrate internal
|
||||||
reporting logic, or anything else that might be required as part of a communications lifecycle
|
reporting logic, or anything else that might be required as part of a communications lifecycle
|
||||||
|
@ -24,9 +24,9 @@ Please note:
|
|||||||
`here <https://medium.com/@octskyward/why-kotlin-is-my-next-programming-language-c25c001e26e3>`_. If you're
|
`here <https://medium.com/@octskyward/why-kotlin-is-my-next-programming-language-c25c001e26e3>`_. If you're
|
||||||
unfamiliar with Kotlin, there is an official
|
unfamiliar with Kotlin, there is an official
|
||||||
`getting started guide <https://kotlinlang.org/docs/tutorials/>`_, and a series of
|
`getting started guide <https://kotlinlang.org/docs/tutorials/>`_, and a series of
|
||||||
`Kotlin Koans <https://kotlinlang.org/docs/tutorials/koans.html>`_.
|
`Kotlin Koans <https://kotlinlang.org/docs/tutorials/koans.html>`_
|
||||||
|
|
||||||
* IntelliJ IDEA is recommended due to the strength of its Kotlin integration.
|
* IntelliJ IDEA is recommended due to the strength of its Kotlin integration
|
||||||
|
|
||||||
* If an HA Bridge/Float deployment is required then a ``Zookeeper 3.5.3-Beta`` cluster will be required.
|
* If an HA Bridge/Float deployment is required then a ``Zookeeper 3.5.3-Beta`` cluster will be required.
|
||||||
Refer to :doc:`Hot-cold deployment <hot-cold-deployment>` and :doc:`Bridge configuration <bridge-configuration-file>`
|
Refer to :doc:`Hot-cold deployment <hot-cold-deployment>` and :doc:`Bridge configuration <bridge-configuration-file>`
|
||||||
@ -37,9 +37,9 @@ others to provide support. However, if you do use other tools, we'd be intereste
|
|||||||
|
|
||||||
Set-up instructions
|
Set-up instructions
|
||||||
-------------------
|
-------------------
|
||||||
The instructions below will allow you to set up a Corda development environment and run a basic CorDapp. If you have
|
The instructions below will allow you to set up your development environment for running Corda and writing CorDapps. If
|
||||||
any issues, please consult the :doc:`troubleshooting` page, or reach out on `Slack <http://slack.corda.net/>`_,
|
you have any issues, please reach out on `Stack Overflow <https://stackoverflow.com/questions/tagged/corda>`_ or via
|
||||||
`Stack Overflow <https://stackoverflow.com/questions/tagged/corda>`_ or the `forums <https://discourse.corda.net/>`_.
|
`our Slack channels <http://slack.corda.net/>`_.
|
||||||
|
|
||||||
The set-up instructions are available for the following platforms:
|
The set-up instructions are available for the following platforms:
|
||||||
|
|
||||||
@ -250,8 +250,9 @@ The best way to check that everything is working fine is by taking a deeper look
|
|||||||
Next, you should read through :doc:`Corda Key Concepts <key-concepts>` to understand how Corda works.
|
Next, you should read through :doc:`Corda Key Concepts <key-concepts>` to understand how Corda works.
|
||||||
|
|
||||||
By then, you'll be ready to start writing your own CorDapps. Learn how to do this in the
|
By then, you'll be ready to start writing your own CorDapps. Learn how to do this in the
|
||||||
:doc:`Hello, World tutorial <hello-world-introduction>`. You may want to refer to the API documentation, the
|
:doc:`Hello, World tutorial <hello-world-introduction>`. You may want to refer to the
|
||||||
:doc:`flow cookbook <flow-cookbook>` and the `samples <https://www.corda.net/samples/>`_ along the way.
|
:doc:`API documentation <corda-api>`, the :doc:`flow cookbook <flow-cookbook>` and the
|
||||||
|
`samples <https://www.corda.net/samples/>`_ along the way.
|
||||||
|
|
||||||
If you encounter any issues, please see the :doc:`troubleshooting` page, or ask on
|
If you encounter any issues, please ask on `Stack Overflow <https://stackoverflow.com/questions/tagged/corda>`_ or via
|
||||||
`Stack Overflow <https://stackoverflow.com/questions/tagged/corda>`_ or via `our Slack channels <http://slack.corda.net/>`_.
|
`our Slack channels <http://slack.corda.net/>`_.
|
||||||
|
@ -8,4 +8,5 @@ Serialization
|
|||||||
serialization.rst
|
serialization.rst
|
||||||
cordapp-custom-serializers
|
cordapp-custom-serializers
|
||||||
serialization-default-evolution.rst
|
serialization-default-evolution.rst
|
||||||
serialization-enum-evolution.rst
|
serialization-enum-evolution.rst
|
||||||
|
blob-inspector
|
@ -1,10 +1,12 @@
|
|||||||
Tools
|
Tools
|
||||||
=====
|
=====
|
||||||
|
|
||||||
|
Corda provides various command line and GUI tools to help you as you work. Along with the three below, you may also
|
||||||
|
wish to try the :doc:`blob-inspector`.
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
network-bootstrapper
|
network-bootstrapper
|
||||||
blob-inspector
|
|
||||||
demobench
|
demobench
|
||||||
node-explorer
|
node-explorer
|
||||||
|
@ -1,158 +0,0 @@
|
|||||||
/*
|
|
||||||
* R3 Proprietary and Confidential
|
|
||||||
*
|
|
||||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
|
||||||
*
|
|
||||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
|
||||||
*
|
|
||||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.corda.finance.flows
|
|
||||||
|
|
||||||
import net.corda.core.contracts.TransactionState
|
|
||||||
import net.corda.core.contracts.withoutIssuer
|
|
||||||
import net.corda.core.identity.Party
|
|
||||||
import net.corda.core.messaging.startFlow
|
|
||||||
import net.corda.core.transactions.TransactionBuilder
|
|
||||||
import net.corda.core.utilities.OpaqueBytes
|
|
||||||
import net.corda.core.utilities.getOrThrow
|
|
||||||
import net.corda.finance.DOLLARS
|
|
||||||
import net.corda.finance.contracts.asset.Cash
|
|
||||||
import net.corda.finance.contracts.asset.cash.selection.AbstractCashSelection
|
|
||||||
import net.corda.finance.contracts.getCashBalance
|
|
||||||
import net.corda.finance.issuedBy
|
|
||||||
import net.corda.testing.core.*
|
|
||||||
import net.corda.testing.driver.DriverParameters
|
|
||||||
import net.corda.testing.driver.driver
|
|
||||||
import net.corda.testing.driver.internal.InProcessImpl
|
|
||||||
import net.corda.testing.internal.IntegrationTest
|
|
||||||
import net.corda.testing.internal.IntegrationTestSchemas
|
|
||||||
import net.corda.testing.internal.toDatabaseSchemaName
|
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
|
||||||
import org.junit.ClassRule
|
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
class CashSelectionTest : IntegrationTest() {
|
|
||||||
companion object {
|
|
||||||
@ClassRule
|
|
||||||
@JvmField
|
|
||||||
val databaseSchemas = IntegrationTestSchemas(*listOf(ALICE_NAME, BOB_NAME, DUMMY_BANK_A_NAME, DUMMY_NOTARY_NAME)
|
|
||||||
.map { it.toDatabaseSchemaName() }.toTypedArray())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `unconsumed cash states`() {
|
|
||||||
driver(DriverParameters(startNodesInProcess = true, extraCordappPackagesToScan = listOf("net.corda.finance"))) {
|
|
||||||
val node = startNode().getOrThrow() as InProcessImpl
|
|
||||||
val issuerRef = OpaqueBytes.of(0)
|
|
||||||
val issuedAmount = 1000.DOLLARS
|
|
||||||
|
|
||||||
node.rpc.startFlow(::CashIssueFlow, issuedAmount, issuerRef, defaultNotaryIdentity).returnValue.getOrThrow()
|
|
||||||
|
|
||||||
val availableBalance = node.rpc.getCashBalance(issuedAmount.token)
|
|
||||||
|
|
||||||
assertThat(availableBalance).isEqualTo(issuedAmount)
|
|
||||||
|
|
||||||
val exitedAmount = 300.DOLLARS
|
|
||||||
node.rpc.startFlow(::CashExitFlow, exitedAmount, issuerRef).returnValue.getOrThrow()
|
|
||||||
|
|
||||||
val availableBalanceAfterExit = node.rpc.getCashBalance(issuedAmount.token)
|
|
||||||
|
|
||||||
assertThat(availableBalanceAfterExit).isEqualTo(issuedAmount - exitedAmount)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `cash selection sees states added in the same transaction`() {
|
|
||||||
driver(DriverParameters(startNodesInProcess = true, extraCordappPackagesToScan = listOf("net.corda.finance"))) {
|
|
||||||
val node = startNode().getOrThrow() as InProcessImpl
|
|
||||||
val nodeIdentity = node.services.myInfo.singleIdentity()
|
|
||||||
val issuer = nodeIdentity.ref(1)
|
|
||||||
val coin = 1.DOLLARS.issuedBy(issuer)
|
|
||||||
val exitedAmount = 1.DOLLARS
|
|
||||||
val issuance = TransactionBuilder(null as Party?)
|
|
||||||
issuance.addOutputState(TransactionState(Cash.State(coin, nodeIdentity), Cash.PROGRAM_ID, defaultNotaryIdentity))
|
|
||||||
issuance.addCommand(Cash.Commands.Issue(), nodeIdentity.owningKey)
|
|
||||||
|
|
||||||
//insert ans select in the same transaction
|
|
||||||
val exitStates = node.database.transaction {
|
|
||||||
|
|
||||||
val transaction = node.services.signInitialTransaction(issuance, nodeIdentity.owningKey)
|
|
||||||
node.services.recordTransactions(transaction)
|
|
||||||
|
|
||||||
val builder = TransactionBuilder(notary = null)
|
|
||||||
AbstractCashSelection
|
|
||||||
.getInstance { node.services.jdbcSession().metaData }
|
|
||||||
.unconsumedCashStatesForSpending(node.services, exitedAmount, setOf(issuer.party), builder.notary, builder.lockId, setOf(issuer.reference))
|
|
||||||
}
|
|
||||||
val returnedCoinsNumber = 1
|
|
||||||
assertThat(exitStates.size).isEqualTo(returnedCoinsNumber)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `dont return extra coins if the selected amount has been reached`() {
|
|
||||||
driver(DriverParameters(startNodesInProcess = true, extraCordappPackagesToScan = listOf("net.corda.finance"))) {
|
|
||||||
val node = startNode().getOrThrow() as InProcessImpl
|
|
||||||
val nodeIdentity = node.services.myInfo.singleIdentity()
|
|
||||||
|
|
||||||
val issuer = nodeIdentity.ref(1)
|
|
||||||
|
|
||||||
val exitStates = node.database.transaction {
|
|
||||||
//issue $1 coin twice
|
|
||||||
repeat(2, {
|
|
||||||
val coin = 1.DOLLARS.issuedBy(issuer)
|
|
||||||
val issuance = TransactionBuilder(null as Party?)
|
|
||||||
issuance.addOutputState(TransactionState(Cash.State(coin, nodeIdentity), Cash.PROGRAM_ID, defaultNotaryIdentity))
|
|
||||||
issuance.addCommand(Cash.Commands.Issue(), nodeIdentity.owningKey)
|
|
||||||
|
|
||||||
val transaction = node.services.signInitialTransaction(issuance, nodeIdentity.owningKey)
|
|
||||||
|
|
||||||
node.services.recordTransactions(transaction)
|
|
||||||
})
|
|
||||||
|
|
||||||
val exitedAmount = 1.DOLLARS
|
|
||||||
val builder = TransactionBuilder(notary = null)
|
|
||||||
AbstractCashSelection
|
|
||||||
.getInstance { node.services.jdbcSession().metaData }
|
|
||||||
.unconsumedCashStatesForSpending(node.services, exitedAmount, setOf(issuer.party), builder.notary, builder.lockId, setOf(issuer.reference))
|
|
||||||
}
|
|
||||||
val returnedCoinsNumber = 1
|
|
||||||
assertThat(exitStates.size).isEqualTo(returnedCoinsNumber)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `select cash states issued by single transaction and give change`() {
|
|
||||||
driver(DriverParameters(startNodesInProcess = true, extraCordappPackagesToScan = listOf("net.corda.finance"))) {
|
|
||||||
val node = startNode().getOrThrow() as InProcessImpl
|
|
||||||
val nodeIdentity = node.services.myInfo.singleIdentity()
|
|
||||||
|
|
||||||
val coins = listOf(3.DOLLARS, 2.DOLLARS, 1.DOLLARS).map { it.issuedBy(nodeIdentity.ref(1)) }
|
|
||||||
|
|
||||||
//create single transaction with 3 cash outputs
|
|
||||||
val issuance = TransactionBuilder(null as Party?)
|
|
||||||
coins.map { issuance.addOutputState(TransactionState(Cash.State(it, nodeIdentity), "net.corda.finance.contracts.asset.Cash", defaultNotaryIdentity)) }
|
|
||||||
issuance.addCommand(Cash.Commands.Issue(), nodeIdentity.owningKey)
|
|
||||||
|
|
||||||
val transaction = node.services.signInitialTransaction(issuance, nodeIdentity.owningKey)
|
|
||||||
node.database.transaction {
|
|
||||||
node.services.recordTransactions(transaction)
|
|
||||||
}
|
|
||||||
|
|
||||||
val issuedAmount = coins.reduce { sum, element -> sum + element }.withoutIssuer()
|
|
||||||
|
|
||||||
val availableBalance = node.rpc.getCashBalance(issuedAmount.token)
|
|
||||||
|
|
||||||
assertThat(availableBalance).isEqualTo(issuedAmount)
|
|
||||||
|
|
||||||
val exitedAmount = 3.01.DOLLARS
|
|
||||||
node.rpc.startFlow(::CashExitFlow, exitedAmount, OpaqueBytes.of(1)).returnValue.getOrThrow()
|
|
||||||
|
|
||||||
val availableBalanceAfterExit = node.rpc.getCashBalance(issuedAmount.token)
|
|
||||||
|
|
||||||
assertThat(availableBalanceAfterExit).isEqualTo(issuedAmount - exitedAmount)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,136 @@
|
|||||||
|
package net.corda.finance.flows
|
||||||
|
|
||||||
|
import net.corda.core.contracts.TransactionState
|
||||||
|
import net.corda.core.contracts.withoutIssuer
|
||||||
|
import net.corda.core.identity.Party
|
||||||
|
import net.corda.core.transactions.TransactionBuilder
|
||||||
|
import net.corda.core.utilities.OpaqueBytes
|
||||||
|
import net.corda.core.utilities.getOrThrow
|
||||||
|
import net.corda.finance.DOLLARS
|
||||||
|
import net.corda.finance.contracts.asset.Cash
|
||||||
|
import net.corda.finance.contracts.asset.cash.selection.AbstractCashSelection
|
||||||
|
import net.corda.finance.contracts.getCashBalance
|
||||||
|
import net.corda.finance.issuedBy
|
||||||
|
import net.corda.testing.core.singleIdentity
|
||||||
|
import net.corda.testing.node.internal.InternalMockNetwork
|
||||||
|
import net.corda.testing.node.internal.startFlow
|
||||||
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class CashSelectionTest {
|
||||||
|
private val mockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.finance"), threadPerNode = true)
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun cleanUp() {
|
||||||
|
mockNet.stopNodes()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `unconsumed cash states`() {
|
||||||
|
val issuerRef = OpaqueBytes.of(0)
|
||||||
|
val issuedAmount = 1000.DOLLARS
|
||||||
|
|
||||||
|
val node = mockNet.createNode()
|
||||||
|
node.services.startFlow(CashIssueFlow(issuedAmount, issuerRef, mockNet.defaultNotaryIdentity)).resultFuture.getOrThrow()
|
||||||
|
|
||||||
|
val availableBalance = node.services.getCashBalance(issuedAmount.token)
|
||||||
|
|
||||||
|
assertThat(availableBalance).isEqualTo(issuedAmount)
|
||||||
|
|
||||||
|
val exitedAmount = 300.DOLLARS
|
||||||
|
node.services.startFlow(CashExitFlow(exitedAmount, issuerRef)).resultFuture.getOrThrow()
|
||||||
|
|
||||||
|
val availableBalanceAfterExit = node.services.getCashBalance(issuedAmount.token)
|
||||||
|
|
||||||
|
assertThat(availableBalanceAfterExit).isEqualTo(issuedAmount - exitedAmount)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `cash selection sees states added in the same transaction`() {
|
||||||
|
val node = mockNet.createNode()
|
||||||
|
val nodeIdentity = node.services.myInfo.singleIdentity()
|
||||||
|
val issuer = nodeIdentity.ref(1)
|
||||||
|
val coin = 1.DOLLARS.issuedBy(issuer)
|
||||||
|
val exitedAmount = 1.DOLLARS
|
||||||
|
val issuance = TransactionBuilder(null as Party?)
|
||||||
|
issuance.addOutputState(TransactionState(Cash.State(coin, nodeIdentity), Cash.PROGRAM_ID, mockNet.defaultNotaryIdentity))
|
||||||
|
issuance.addCommand(Cash.Commands.Issue(), nodeIdentity.owningKey)
|
||||||
|
|
||||||
|
// Insert and select in the same transaction
|
||||||
|
val exitStates = node.database.transaction {
|
||||||
|
val transaction = node.services.signInitialTransaction(issuance, nodeIdentity.owningKey)
|
||||||
|
node.services.recordTransactions(transaction)
|
||||||
|
|
||||||
|
val builder = TransactionBuilder(notary = null)
|
||||||
|
AbstractCashSelection
|
||||||
|
.getInstance { node.services.jdbcSession().metaData }
|
||||||
|
.unconsumedCashStatesForSpending(node.services, exitedAmount, setOf(issuer.party), builder.notary, builder.lockId, setOf(issuer.reference))
|
||||||
|
}
|
||||||
|
val returnedCoinsNumber = 1
|
||||||
|
assertThat(exitStates.size).isEqualTo(returnedCoinsNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `don't return extra coins if the selected amount has been reached`() {
|
||||||
|
val node = mockNet.createNode()
|
||||||
|
val nodeIdentity = node.services.myInfo.singleIdentity()
|
||||||
|
|
||||||
|
val issuer = nodeIdentity.ref(1)
|
||||||
|
|
||||||
|
val exitStates = node.database.transaction {
|
||||||
|
//issue $1 coin twice
|
||||||
|
repeat(2) {
|
||||||
|
val coin = 1.DOLLARS.issuedBy(issuer)
|
||||||
|
val issuance = TransactionBuilder(null as Party?)
|
||||||
|
issuance.addOutputState(TransactionState(Cash.State(coin, nodeIdentity), Cash.PROGRAM_ID, mockNet.defaultNotaryIdentity))
|
||||||
|
issuance.addCommand(Cash.Commands.Issue(), nodeIdentity.owningKey)
|
||||||
|
|
||||||
|
val transaction = node.services.signInitialTransaction(issuance, nodeIdentity.owningKey)
|
||||||
|
|
||||||
|
node.services.recordTransactions(transaction)
|
||||||
|
}
|
||||||
|
|
||||||
|
val exitedAmount = 1.DOLLARS
|
||||||
|
val builder = TransactionBuilder(notary = null)
|
||||||
|
AbstractCashSelection
|
||||||
|
.getInstance { node.services.jdbcSession().metaData }
|
||||||
|
.unconsumedCashStatesForSpending(node.services, exitedAmount, setOf(issuer.party), builder.notary, builder.lockId, setOf(issuer.reference))
|
||||||
|
}
|
||||||
|
val returnedCoinsNumber = 1
|
||||||
|
assertThat(exitStates.size).isEqualTo(returnedCoinsNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `select cash states issued by single transaction and give change`() {
|
||||||
|
val node = mockNet.createNode()
|
||||||
|
val nodeIdentity = node.services.myInfo.singleIdentity()
|
||||||
|
|
||||||
|
val coins = listOf(3.DOLLARS, 2.DOLLARS, 1.DOLLARS).map { it.issuedBy(nodeIdentity.ref(1)) }
|
||||||
|
|
||||||
|
//create single transaction with 3 cash outputs
|
||||||
|
val issuance = TransactionBuilder(null as Party?)
|
||||||
|
coins.forEach {
|
||||||
|
issuance.addOutputState(TransactionState(Cash.State(it, nodeIdentity), "net.corda.finance.contracts.asset.Cash", mockNet.defaultNotaryIdentity))
|
||||||
|
}
|
||||||
|
issuance.addCommand(Cash.Commands.Issue(), nodeIdentity.owningKey)
|
||||||
|
|
||||||
|
val transaction = node.services.signInitialTransaction(issuance, nodeIdentity.owningKey)
|
||||||
|
node.database.transaction {
|
||||||
|
node.services.recordTransactions(transaction)
|
||||||
|
}
|
||||||
|
|
||||||
|
val issuedAmount = coins.reduce { sum, element -> sum + element }.withoutIssuer()
|
||||||
|
|
||||||
|
val availableBalance = node.services.getCashBalance(issuedAmount.token)
|
||||||
|
|
||||||
|
assertThat(availableBalance).isEqualTo(issuedAmount)
|
||||||
|
|
||||||
|
val exitedAmount = 3.01.DOLLARS
|
||||||
|
node.services.startFlow(CashExitFlow(exitedAmount, OpaqueBytes.of(1))).resultFuture.getOrThrow()
|
||||||
|
|
||||||
|
val availableBalanceAfterExit = node.services.getCashBalance(issuedAmount.token)
|
||||||
|
|
||||||
|
assertThat(availableBalanceAfterExit).isEqualTo(issuedAmount - exitedAmount)
|
||||||
|
}
|
||||||
|
}
|
@ -22,7 +22,7 @@ class ArtemisMessageSizeChecksInterceptor(maxMessageSize: Int) : MessageSizeChec
|
|||||||
}
|
}
|
||||||
|
|
||||||
class AmqpMessageSizeChecksInterceptor(maxMessageSize: Int) : MessageSizeChecksInterceptor<AMQPMessage>(maxMessageSize), AmqpInterceptor {
|
class AmqpMessageSizeChecksInterceptor(maxMessageSize: Int) : MessageSizeChecksInterceptor<AMQPMessage>(maxMessageSize), AmqpInterceptor {
|
||||||
override fun getMessageSize(packet: AMQPMessage?): Int? = packet?.length
|
override fun getMessageSize(packet: AMQPMessage?): Int? = packet?.encodeSize
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -8,6 +8,18 @@
|
|||||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Import private compile time constants
|
||||||
|
buildscript {
|
||||||
|
def properties = new Properties()
|
||||||
|
file("$projectDir/src/main/resources/build.properties").withInputStream { properties.load(it) }
|
||||||
|
|
||||||
|
ext.jolokia_version = properties.getProperty('jolokiaAgentVersion')
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id 'com.google.cloud.tools.jib' version '0.9.4'
|
||||||
|
}
|
||||||
|
|
||||||
apply plugin: 'kotlin'
|
apply plugin: 'kotlin'
|
||||||
// Java Persistence API support: create no-arg constructor
|
// Java Persistence API support: create no-arg constructor
|
||||||
// see: http://stackoverflow.com/questions/32038177/kotlin-with-jpa-default-constructor-hell
|
// see: http://stackoverflow.com/questions/32038177/kotlin-with-jpa-default-constructor-hell
|
||||||
@ -19,14 +31,6 @@ apply plugin: 'com.jfrog.artifactory'
|
|||||||
|
|
||||||
description 'Corda node modules'
|
description 'Corda node modules'
|
||||||
|
|
||||||
// Import private compile time constants
|
|
||||||
buildscript {
|
|
||||||
def properties = new Properties()
|
|
||||||
file("$projectDir/src/main/resources/build.properties").withInputStream { properties.load(it) }
|
|
||||||
|
|
||||||
ext.jolokia_version = properties.getProperty('jolokiaAgentVersion')
|
|
||||||
}
|
|
||||||
|
|
||||||
//noinspection GroovyAssignabilityCheck
|
//noinspection GroovyAssignabilityCheck
|
||||||
configurations {
|
configurations {
|
||||||
integrationTestCompile.extendsFrom testCompile
|
integrationTestCompile.extendsFrom testCompile
|
||||||
@ -51,6 +55,12 @@ sourceSets {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jib.container {
|
||||||
|
mainClass = "net.corda.node.Corda"
|
||||||
|
args = ['--log-to-console', '--no-local-shell', '--config-file=/config/node.conf']
|
||||||
|
jvmFlags = ['-Xmx1g', '-javaagent:/app/libs/quasar-core-0.7.10.jar']
|
||||||
|
}
|
||||||
|
|
||||||
// Use manual resource copying of log4j2.xml rather than source sets.
|
// Use manual resource copying of log4j2.xml rather than source sets.
|
||||||
// This prevents problems in IntelliJ with regard to duplicate source roots.
|
// This prevents problems in IntelliJ with regard to duplicate source roots.
|
||||||
processResources {
|
processResources {
|
||||||
@ -73,6 +83,8 @@ dependencies {
|
|||||||
|
|
||||||
compile "net.corda.plugins:cordform-common:$gradle_plugins_version"
|
compile "net.corda.plugins:cordform-common:$gradle_plugins_version"
|
||||||
|
|
||||||
|
compile group: 'co.paralleluniverse', name: 'quasar-core', version: '0.7.10:jdk8@jar'
|
||||||
|
|
||||||
// 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}"
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
package net.corda.node.internal
|
package net.corda.node.internal
|
||||||
|
|
||||||
import net.corda.client.rpc.notUsed
|
import net.corda.client.rpc.notUsed
|
||||||
|
import net.corda.core.CordaRuntimeException
|
||||||
import net.corda.core.concurrent.CordaFuture
|
import net.corda.core.concurrent.CordaFuture
|
||||||
import net.corda.core.context.InvocationContext
|
import net.corda.core.context.InvocationContext
|
||||||
import net.corda.core.context.InvocationOrigin
|
import net.corda.core.context.InvocationOrigin
|
||||||
@ -59,6 +60,7 @@ import net.corda.nodeapi.exceptions.NonRpcFlowException
|
|||||||
import net.corda.nodeapi.exceptions.RejectedCommandException
|
import net.corda.nodeapi.exceptions.RejectedCommandException
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
import java.net.ConnectException
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
@ -247,6 +249,17 @@ internal class CordaRPCOpsImpl(
|
|||||||
services.networkMapCache.clearNetworkMapCache()
|
services.networkMapCache.clearNetworkMapCache()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun refreshNetworkMapCache() {
|
||||||
|
try {
|
||||||
|
services.networkMapUpdater.updateNetworkMapCache()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
when (e) {
|
||||||
|
is ConnectException -> throw CordaRuntimeException("There is connection problem to network map. The possible causes are incorrect configuration or network map service being down")
|
||||||
|
else -> throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun <T : ContractState> vaultQuery(contractStateType: Class<out T>): Vault.Page<T> {
|
override fun <T : ContractState> vaultQuery(contractStateType: Class<out T>): Vault.Page<T> {
|
||||||
return vaultQueryBy(QueryCriteria.VaultQueryCriteria(), PageSpecification(), Sort(emptySet()), contractStateType)
|
return vaultQueryBy(QueryCriteria.VaultQueryCriteria(), PageSpecification(), Sort(emptySet()), contractStateType)
|
||||||
}
|
}
|
||||||
|
@ -46,8 +46,6 @@ internal class ExceptionSerialisingRpcOpsProxy(private val delegate: CordaRPCOps
|
|||||||
val result = super.invoke(proxy, method, arguments)
|
val result = super.invoke(proxy, method, arguments)
|
||||||
return result?.let { ensureSerialisable(it) }
|
return result?.let { ensureSerialisable(it) }
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
// In this special case logging and re-throwing is the right approach.
|
|
||||||
log(exception)
|
|
||||||
throw ensureSerialisable(exception)
|
throw ensureSerialisable(exception)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -90,7 +88,13 @@ internal class ExceptionSerialisingRpcOpsProxy(private val delegate: CordaRPCOps
|
|||||||
|
|
||||||
private fun ensureSerialisable(error: Throwable): Throwable {
|
private fun ensureSerialisable(error: Throwable): Throwable {
|
||||||
val serialisable = (superclasses(error::class.java) + error::class.java).any { it.isAnnotationPresent(CordaSerializable::class.java) || it.interfaces.any { it.isAnnotationPresent(CordaSerializable::class.java) } }
|
val serialisable = (superclasses(error::class.java) + error::class.java).any { it.isAnnotationPresent(CordaSerializable::class.java) || it.interfaces.any { it.isAnnotationPresent(CordaSerializable::class.java) } }
|
||||||
val result = if (serialisable) error else CordaRuntimeException(error.message, error)
|
val result = if (serialisable) {
|
||||||
|
error
|
||||||
|
} else {
|
||||||
|
log(error)
|
||||||
|
CordaRuntimeException(error.message, error)
|
||||||
|
}
|
||||||
|
|
||||||
if (result is CordaThrowable) {
|
if (result is CordaThrowable) {
|
||||||
result.stackTrace = arrayOf<StackTraceElement>()
|
result.stackTrace = arrayOf<StackTraceElement>()
|
||||||
result.setCause(null)
|
result.setCause(null)
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
package net.corda.node.services.network
|
package net.corda.node.services.network
|
||||||
|
|
||||||
import com.google.common.util.concurrent.MoreExecutors
|
import com.google.common.util.concurrent.MoreExecutors
|
||||||
|
import net.corda.core.CordaRuntimeException
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.crypto.SignedData
|
import net.corda.core.crypto.SignedData
|
||||||
import net.corda.core.internal.copyTo
|
import net.corda.core.internal.copyTo
|
||||||
@ -38,6 +39,7 @@ import java.nio.file.StandardCopyOption
|
|||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
import java.util.concurrent.ScheduledThreadPoolExecutor
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
@ -55,7 +57,7 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val parametersUpdatesTrack: PublishSubject<ParametersUpdateInfo> = PublishSubject.create<ParametersUpdateInfo>()
|
private val parametersUpdatesTrack: PublishSubject<ParametersUpdateInfo> = PublishSubject.create<ParametersUpdateInfo>()
|
||||||
private val executor = Executors.newSingleThreadScheduledExecutor(NamedThreadFactory("Network Map Updater Thread", Executors.defaultThreadFactory()))
|
private val executor = ScheduledThreadPoolExecutor(1, NamedThreadFactory("Network Map Updater Thread", Executors.defaultThreadFactory()))
|
||||||
private var newNetworkParameters: Pair<ParametersUpdate, SignedNetworkParameters>? = null
|
private var newNetworkParameters: Pair<ParametersUpdate, SignedNetworkParameters>? = null
|
||||||
private var fileWatcherSubscription: Subscription? = null
|
private var fileWatcherSubscription: Subscription? = null
|
||||||
|
|
||||||
@ -91,10 +93,11 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
|
|||||||
if (networkMapClient == null) return
|
if (networkMapClient == null) return
|
||||||
|
|
||||||
// Subscribe to remote network map if configured.
|
// Subscribe to remote network map if configured.
|
||||||
|
executor.executeExistingDelayedTasksAfterShutdownPolicy = false
|
||||||
executor.submit(object : Runnable {
|
executor.submit(object : Runnable {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
val nextScheduleDelay = try {
|
val nextScheduleDelay = try {
|
||||||
updateNetworkMapCache(networkMapClient)
|
updateNetworkMapCache()
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
logger.warn("Error encountered while updating network map, will retry in $defaultRetryInterval", t)
|
logger.warn("Error encountered while updating network map, will retry in $defaultRetryInterval", t)
|
||||||
defaultRetryInterval
|
defaultRetryInterval
|
||||||
@ -105,7 +108,8 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
|
|||||||
}) // The check may be expensive, so always run it in the background even the first time.
|
}) // The check may be expensive, so always run it in the background even the first time.
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateNetworkMapCache(networkMapClient: NetworkMapClient): Duration {
|
fun updateNetworkMapCache(): Duration {
|
||||||
|
if (networkMapClient == null) throw CordaRuntimeException("Network map cache can be updated only if network map/compatibility zone URL is specified")
|
||||||
val (globalNetworkMap, cacheTimeout) = networkMapClient.getNetworkMap()
|
val (globalNetworkMap, cacheTimeout) = networkMapClient.getNetworkMap()
|
||||||
globalNetworkMap.parametersUpdate?.let { handleUpdateNetworkParameters(networkMapClient, it) }
|
globalNetworkMap.parametersUpdate?.let { handleUpdateNetworkParameters(networkMapClient, it) }
|
||||||
val additionalHashes = extraNetworkMapKeys.flatMap {
|
val additionalHashes = extraNetworkMapKeys.flatMap {
|
||||||
|
@ -39,8 +39,7 @@ import kotlin.test.assertFails
|
|||||||
|
|
||||||
class NetworkParametersTest {
|
class NetworkParametersTest {
|
||||||
private val mockNet = InternalMockNetwork(
|
private val mockNet = InternalMockNetwork(
|
||||||
emptyList(),
|
defaultParameters = MockNetworkParameters(networkSendManuallyPumped = true),
|
||||||
MockNetworkParameters(networkSendManuallyPumped = true),
|
|
||||||
notarySpecs = listOf(MockNetworkNotarySpec(DUMMY_NOTARY_NAME)))
|
notarySpecs = listOf(MockNetworkNotarySpec(DUMMY_NOTARY_NAME)))
|
||||||
|
|
||||||
@After
|
@After
|
||||||
|
@ -7,43 +7,39 @@
|
|||||||
*
|
*
|
||||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||||
*/
|
*/
|
||||||
|
package net.corda.node.internal
|
||||||
|
|
||||||
package net.corda.node
|
import net.corda.core.internal.packageName
|
||||||
|
|
||||||
import net.corda.core.node.ServiceHub
|
import net.corda.core.node.ServiceHub
|
||||||
import net.corda.core.node.services.CordaService
|
import net.corda.core.node.services.CordaService
|
||||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.contextLogger
|
||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.testing.node.internal.InternalMockNetwork
|
||||||
import net.corda.testing.core.DUMMY_BANK_A_NAME
|
import org.junit.After
|
||||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
|
||||||
import net.corda.testing.driver.DriverParameters
|
|
||||||
import net.corda.testing.driver.driver
|
|
||||||
import net.corda.testing.internal.IntegrationTest
|
|
||||||
import net.corda.testing.internal.IntegrationTestSchemas
|
|
||||||
import net.corda.testing.internal.toDatabaseSchemaName
|
|
||||||
import org.junit.Assert
|
|
||||||
import org.junit.ClassRule
|
|
||||||
import org.junit.Assert.assertTrue
|
import org.junit.Assert.assertTrue
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class NodeUnloadHandlerTests : IntegrationTest() {
|
class NodeUnloadHandlerTests {
|
||||||
companion object {
|
companion object {
|
||||||
@ClassRule
|
val registerLatch = CountDownLatch(1)
|
||||||
@JvmField
|
val shutdownLatch = CountDownLatch(1)
|
||||||
val databaseSchemas = IntegrationTestSchemas(DUMMY_BANK_A_NAME.toDatabaseSchemaName(), DUMMY_NOTARY_NAME.toDatabaseSchemaName() )
|
}
|
||||||
val latch = CountDownLatch(1)
|
|
||||||
|
private val mockNet = InternalMockNetwork(cordappPackages = listOf(javaClass.packageName), notarySpecs = emptyList())
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun cleanUp() {
|
||||||
|
mockNet.stopNodes()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `should be able to register run on stop lambda`() {
|
fun `should be able to register run on stop lambda`() {
|
||||||
driver(DriverParameters(startNodesInProcess = true, extraCordappPackagesToScan = listOf("net.corda.node"), notarySpecs = emptyList())) {
|
val node = mockNet.createNode()
|
||||||
startNode(providedName = DUMMY_BANK_A_NAME).getOrThrow()
|
registerLatch.await() // Make sure the handler is registered on node start up
|
||||||
// just want to fall off the end of this for the mo...
|
node.dispose()
|
||||||
}
|
assertTrue("Timed out waiting for AbstractNode to invoke the test service shutdown callback", shutdownLatch.await(30, TimeUnit.SECONDS))
|
||||||
assertTrue("Timed out waiting for AbstractNode to invoke the test service shutdown callback", latch.await(30, TimeUnit.SECONDS))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
@ -55,11 +51,12 @@ class NodeUnloadHandlerTests : IntegrationTest() {
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
serviceHub.registerUnloadHandler(this::shutdown)
|
serviceHub.registerUnloadHandler(this::shutdown)
|
||||||
|
registerLatch.countDown()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun shutdown() {
|
private fun shutdown() {
|
||||||
log.info("shutting down")
|
log.info("shutting down")
|
||||||
latch.countDown()
|
shutdownLatch.countDown()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -28,7 +28,7 @@ class InMemoryMessagingTests {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
mockNet = InternalMockNetwork(emptyList())
|
mockNet = InternalMockNetwork()
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
|
@ -35,7 +35,7 @@ class FinalityHandlerTest {
|
|||||||
fun `sent to flow hospital on error and attempted retry on node restart`() {
|
fun `sent to flow hospital on error and attempted retry on node restart`() {
|
||||||
// Setup a network where only Alice has the finance CorDapp and it sends a cash tx to Bob who doesn't have the
|
// Setup a network where only Alice has the finance CorDapp and it sends a cash tx to Bob who doesn't have the
|
||||||
// CorDapp. Bob's FinalityHandler will error when validating the tx.
|
// CorDapp. Bob's FinalityHandler will error when validating the tx.
|
||||||
mockNet = InternalMockNetwork(cordappPackages = emptyList())
|
mockNet = InternalMockNetwork()
|
||||||
|
|
||||||
val alice = mockNet.createNode(InternalMockNodeParameters(
|
val alice = mockNet.createNode(InternalMockNodeParameters(
|
||||||
legalName = ALICE_NAME,
|
legalName = ALICE_NAME,
|
||||||
|
@ -29,7 +29,7 @@ import kotlin.test.assertNotNull
|
|||||||
import kotlin.test.assertNull
|
import kotlin.test.assertNull
|
||||||
|
|
||||||
class NetworkMapCacheTest {
|
class NetworkMapCacheTest {
|
||||||
private val mockNet = InternalMockNetwork(emptyList())
|
private val mockNet = InternalMockNetwork()
|
||||||
|
|
||||||
@After
|
@After
|
||||||
fun teardown() {
|
fun teardown() {
|
||||||
|
@ -50,7 +50,7 @@ class NodeSchemaServiceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `check node runs with minimal core schema set`() {
|
fun `check node runs with minimal core schema set`() {
|
||||||
val mockNet = InternalMockNetwork(cordappPackages = emptyList())
|
val mockNet = InternalMockNetwork()
|
||||||
val mockNode = mockNet.createNode()
|
val mockNode = mockNet.createNode()
|
||||||
val schemaService = mockNode.services.schemaService
|
val schemaService = mockNode.services.schemaService
|
||||||
|
|
||||||
@ -62,7 +62,7 @@ class NodeSchemaServiceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `check node runs inclusive of notary node schema set`() {
|
fun `check node runs inclusive of notary node schema set`() {
|
||||||
val mockNet = InternalMockNetwork(cordappPackages = emptyList())
|
val mockNet = InternalMockNetwork()
|
||||||
val mockNotaryNode = mockNet.notaryNodes.first()
|
val mockNotaryNode = mockNet.notaryNodes.first()
|
||||||
val schemaService = mockNotaryNode.services.schemaService
|
val schemaService = mockNotaryNode.services.schemaService
|
||||||
|
|
||||||
|
@ -26,13 +26,13 @@ configurations {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compileOnly project(':serialization')
|
compileOnly project(':serialization')
|
||||||
compileOnly "$quasar_group:quasar-core:$quasar_version:jdk8"
|
|
||||||
|
|
||||||
// Configure these by hand. It should be a minimal subset of dependencies,
|
// Configure these by hand. It should be a minimal subset of dependencies,
|
||||||
// and without any obviously non-deterministic ones such as Hibernate.
|
// and without any obviously non-deterministic ones such as Hibernate.
|
||||||
runtimeLibraries project(path: ':core-deterministic', configuration: 'runtimeArtifacts')
|
runtimeLibraries project(path: ':core-deterministic', configuration: 'runtimeArtifacts')
|
||||||
runtimeLibraries "org.apache.qpid:proton-j:$protonj_version"
|
runtimeLibraries "org.apache.qpid:proton-j:$protonj_version"
|
||||||
runtimeLibraries "org.iq80.snappy:snappy:$snappy_version"
|
runtimeLibraries "org.iq80.snappy:snappy:$snappy_version"
|
||||||
|
runtimeLibraries "com.google.guava:guava:$guava_version"
|
||||||
}
|
}
|
||||||
|
|
||||||
jar {
|
jar {
|
||||||
|
@ -14,6 +14,8 @@ dependencies {
|
|||||||
|
|
||||||
compile "org.ow2.asm:asm:$asm_version"
|
compile "org.ow2.asm:asm:$asm_version"
|
||||||
|
|
||||||
|
compile "com.google.guava:guava:$guava_version"
|
||||||
|
|
||||||
// For AMQP serialisation.
|
// For AMQP serialisation.
|
||||||
compile "org.apache.qpid:proton-j:$protonj_version"
|
compile "org.apache.qpid:proton-j:$protonj_version"
|
||||||
|
|
||||||
|
@ -534,7 +534,7 @@ private fun Throwable.setMessage(newMsg: String) {
|
|||||||
|
|
||||||
fun ClassWhitelist.requireWhitelisted(type: Type) {
|
fun ClassWhitelist.requireWhitelisted(type: Type) {
|
||||||
if (!this.isWhitelisted(type.asClass()!!)) {
|
if (!this.isWhitelisted(type.asClass()!!)) {
|
||||||
throw NotSerializableException("Class $type is not on the whitelist or annotated with @CordaSerializable.")
|
throw NotSerializableException("Class \"$type\" is not on the whitelist or annotated with @CordaSerializable.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ class InternalMockNetworkIntegrationTests {
|
|||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
InternalMockNetwork(emptyList()).run {
|
InternalMockNetwork().run {
|
||||||
repeat(2) { createNode() }
|
repeat(2) { createNode() }
|
||||||
runNetwork()
|
runNetwork()
|
||||||
stopNodes()
|
stopNodes()
|
||||||
|
@ -106,7 +106,7 @@ data class InternalMockNodeParameters(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
open class InternalMockNetwork(private val cordappPackages: List<String>,
|
open class InternalMockNetwork(private val cordappPackages: List<String> = emptyList(),
|
||||||
defaultParameters: MockNetworkParameters = MockNetworkParameters(),
|
defaultParameters: MockNetworkParameters = MockNetworkParameters(),
|
||||||
val networkSendManuallyPumped: Boolean = defaultParameters.networkSendManuallyPumped,
|
val networkSendManuallyPumped: Boolean = defaultParameters.networkSendManuallyPumped,
|
||||||
val threadPerNode: Boolean = defaultParameters.threadPerNode,
|
val threadPerNode: Boolean = defaultParameters.threadPerNode,
|
||||||
|
@ -19,7 +19,7 @@ class InternalMockNetworkTests {
|
|||||||
fun `does not leak serialization env if init fails`() {
|
fun `does not leak serialization env if init fails`() {
|
||||||
val e = Exception("didn't work")
|
val e = Exception("didn't work")
|
||||||
assertThatThrownBy {
|
assertThatThrownBy {
|
||||||
object : InternalMockNetwork(emptyList()) {
|
object : InternalMockNetwork() {
|
||||||
override fun createNotaries() = throw e
|
override fun createNotaries() = throw e
|
||||||
}
|
}
|
||||||
}.isSameAs(e)
|
}.isSameAs(e)
|
||||||
|
@ -50,5 +50,5 @@ class DummyContractV2 : UpgradedContractWithLegacyConstraint<DummyContract.State
|
|||||||
override fun verify(tx: LedgerTransaction) {
|
override fun verify(tx: LedgerTransaction) {
|
||||||
// Other verifications.
|
// Other verifications.
|
||||||
}
|
}
|
||||||
// DOCEND 1
|
|
||||||
}
|
}
|
||||||
|
// DOCEND 1
|
||||||
|
@ -10,8 +10,11 @@ dependencies {
|
|||||||
compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
|
compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
|
||||||
compile "com.jcabi:jcabi-manifests:$jcabi_manifests_version"
|
compile "com.jcabi:jcabi-manifests:$jcabi_manifests_version"
|
||||||
|
|
||||||
|
testCompile(project(':test-utils')) {
|
||||||
|
exclude module: 'node-api'
|
||||||
|
exclude module: 'finance'
|
||||||
|
}
|
||||||
testCompile project(':test-utils')
|
testCompile project(':test-utils')
|
||||||
testCompile "junit:junit:$junit_version"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
jar {
|
jar {
|
||||||
@ -24,7 +27,7 @@ jar {
|
|||||||
manifest {
|
manifest {
|
||||||
attributes(
|
attributes(
|
||||||
'Automatic-Module-Name': 'net.corda.blobinspector',
|
'Automatic-Module-Name': 'net.corda.blobinspector',
|
||||||
'Main-Class': 'net.corda.blobinspector.MainKt'
|
'Main-Class': 'net.corda.blobinspector.BlobInspectorKt'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,12 +7,15 @@ import net.corda.client.jackson.JacksonSupport
|
|||||||
import net.corda.core.internal.isRegularFile
|
import net.corda.core.internal.isRegularFile
|
||||||
import net.corda.core.internal.rootMessage
|
import net.corda.core.internal.rootMessage
|
||||||
import net.corda.core.serialization.SerializationContext
|
import net.corda.core.serialization.SerializationContext
|
||||||
import net.corda.core.serialization.SerializationFactory
|
import net.corda.core.serialization.SerializationDefaults
|
||||||
import net.corda.core.serialization.deserialize
|
import net.corda.core.serialization.deserialize
|
||||||
import net.corda.core.serialization.internal.SerializationEnvironmentImpl
|
import net.corda.core.serialization.internal.SerializationEnvironmentImpl
|
||||||
import net.corda.core.serialization.internal._contextSerializationEnv
|
import net.corda.core.serialization.internal._contextSerializationEnv
|
||||||
|
import net.corda.core.utilities.base64ToByteArray
|
||||||
|
import net.corda.core.utilities.hexToByteArray
|
||||||
import net.corda.core.utilities.sequence
|
import net.corda.core.utilities.sequence
|
||||||
import net.corda.serialization.internal.AMQP_P2P_CONTEXT
|
import net.corda.serialization.internal.AMQP_P2P_CONTEXT
|
||||||
|
import net.corda.serialization.internal.AMQP_STORAGE_CONTEXT
|
||||||
import net.corda.serialization.internal.CordaSerializationMagic
|
import net.corda.serialization.internal.CordaSerializationMagic
|
||||||
import net.corda.serialization.internal.SerializationFactoryImpl
|
import net.corda.serialization.internal.SerializationFactoryImpl
|
||||||
import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme
|
import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme
|
||||||
@ -20,13 +23,14 @@ import net.corda.serialization.internal.amqp.DeserializationInput
|
|||||||
import net.corda.serialization.internal.amqp.amqpMagic
|
import net.corda.serialization.internal.amqp.amqpMagic
|
||||||
import picocli.CommandLine
|
import picocli.CommandLine
|
||||||
import picocli.CommandLine.*
|
import picocli.CommandLine.*
|
||||||
|
import java.io.PrintStream
|
||||||
import java.net.MalformedURLException
|
import java.net.MalformedURLException
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
val main = Main()
|
val main = BlobInspector()
|
||||||
try {
|
try {
|
||||||
CommandLine.run(main, *args)
|
CommandLine.run(main, *args)
|
||||||
} catch (e: ExecutionException) {
|
} catch (e: ExecutionException) {
|
||||||
@ -41,18 +45,21 @@ fun main(args: Array<String>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Command(
|
@Command(
|
||||||
name = "Blob Inspector",
|
name = "blob-inspector",
|
||||||
versionProvider = CordaVersionProvider::class,
|
versionProvider = CordaVersionProvider::class,
|
||||||
mixinStandardHelpOptions = true, // add --help and --version options,
|
mixinStandardHelpOptions = true, // add --help and --version options,
|
||||||
showDefaultValues = true,
|
showDefaultValues = true,
|
||||||
description = ["Inspect AMQP serialised binary blobs"]
|
description = ["Convert AMQP serialised binary blobs to text"]
|
||||||
)
|
)
|
||||||
class Main : Runnable {
|
class BlobInspector : Runnable {
|
||||||
@Parameters(index = "0", paramLabel = "SOURCE", description = ["URL or file path to the blob"], converter = [SourceConverter::class])
|
@Parameters(index = "0", paramLabel = "SOURCE", description = ["URL or file path to the blob"], converter = [SourceConverter::class])
|
||||||
private var source: URL? = null
|
var source: URL? = null
|
||||||
|
|
||||||
@Option(names = ["--format"], paramLabel = "type", description = ["Output format. Possible values: [YAML, JSON]"])
|
@Option(names = ["--format"], paramLabel = "type", description = ["Output format. Possible values: [YAML, JSON]"])
|
||||||
private var formatType: FormatType = FormatType.YAML
|
private var formatType: OutputFormatType = OutputFormatType.YAML
|
||||||
|
|
||||||
|
@Option(names = ["--input-format"], paramLabel = "type", description = ["Input format. If the file can't be decoded with the given value it's auto-detected, so you should never normally need to specify this. Possible values: [BINARY, HEX, BASE64]"])
|
||||||
|
private var inputFormatType: InputFormatType = InputFormatType.BINARY
|
||||||
|
|
||||||
@Option(names = ["--full-parties"],
|
@Option(names = ["--full-parties"],
|
||||||
description = ["Display the owningKey and certPath properties of Party and PartyAndReference objects respectively"])
|
description = ["Display the owningKey and certPath properties of Party and PartyAndReference objects respectively"])
|
||||||
@ -64,54 +71,89 @@ class Main : Runnable {
|
|||||||
@Option(names = ["--verbose"], description = ["Enable verbose output"])
|
@Option(names = ["--verbose"], description = ["Enable verbose output"])
|
||||||
var verbose: Boolean = false
|
var verbose: Boolean = false
|
||||||
|
|
||||||
override fun run() {
|
override fun run() = run(System.out)
|
||||||
|
|
||||||
|
fun run(out: PrintStream) {
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
System.setProperty("logLevel", "trace")
|
System.setProperty("logLevel", "trace")
|
||||||
}
|
}
|
||||||
|
|
||||||
val bytes = source!!.readBytes().run {
|
val inputBytes = source!!.readBytes()
|
||||||
require(size > amqpMagic.size) { "Insufficient bytes for AMQP blob" }
|
val bytes = parseToBinaryRelaxed(inputFormatType, inputBytes)
|
||||||
sequence()
|
?: throw IllegalArgumentException("Error: this input does not appear to be encoded in Corda's AMQP extended format, sorry.")
|
||||||
}
|
|
||||||
|
|
||||||
require(bytes.take(amqpMagic.size) == amqpMagic) { "Not an AMQP blob" }
|
|
||||||
|
|
||||||
if (schema) {
|
if (schema) {
|
||||||
val envelope = DeserializationInput.getEnvelope(bytes)
|
val envelope = DeserializationInput.getEnvelope(bytes.sequence())
|
||||||
println(envelope.schema)
|
out.println(envelope.schema)
|
||||||
println()
|
out.println()
|
||||||
println(envelope.transformsSchema)
|
out.println(envelope.transformsSchema)
|
||||||
println()
|
out.println()
|
||||||
}
|
}
|
||||||
|
|
||||||
initialiseSerialization()
|
|
||||||
|
|
||||||
val factory = when (formatType) {
|
val factory = when (formatType) {
|
||||||
FormatType.YAML -> YAMLFactory()
|
OutputFormatType.YAML -> YAMLFactory()
|
||||||
FormatType.JSON -> JsonFactory()
|
OutputFormatType.JSON -> JsonFactory()
|
||||||
}
|
}
|
||||||
|
|
||||||
val mapper = JacksonSupport.createNonRpcMapper(factory, fullParties)
|
val mapper = JacksonSupport.createNonRpcMapper(factory, fullParties)
|
||||||
|
|
||||||
// Deserialise with the lenient carpenter as we only care for the AMQP field getters
|
initialiseSerialization()
|
||||||
val deserialized = bytes.deserialize<Any>(context = SerializationFactory.defaultFactory.defaultContext.withLenientCarpenter())
|
try {
|
||||||
println(deserialized.javaClass.name)
|
val deserialized = bytes.deserialize<Any>(context = SerializationDefaults.STORAGE_CONTEXT)
|
||||||
mapper.writeValue(System.out, deserialized)
|
out.println(deserialized.javaClass.name)
|
||||||
|
mapper.writeValue(out, deserialized)
|
||||||
|
} finally {
|
||||||
|
_contextSerializationEnv.set(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseToBinaryRelaxed(format: InputFormatType, inputBytes: ByteArray): ByteArray? {
|
||||||
|
// Try the format the user gave us first, then try the others.
|
||||||
|
//@formatter:off
|
||||||
|
return parseToBinary(format, inputBytes) ?:
|
||||||
|
parseToBinary(InputFormatType.HEX, inputBytes) ?:
|
||||||
|
parseToBinary(InputFormatType.BASE64, inputBytes) ?:
|
||||||
|
parseToBinary(InputFormatType.BINARY, inputBytes)
|
||||||
|
//@formatter:on
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseToBinary(format: InputFormatType, inputBytes: ByteArray): ByteArray? {
|
||||||
|
try {
|
||||||
|
val bytes = when (format) {
|
||||||
|
InputFormatType.BINARY -> inputBytes
|
||||||
|
InputFormatType.HEX -> String(inputBytes).trim().hexToByteArray()
|
||||||
|
InputFormatType.BASE64 -> String(inputBytes).trim().base64ToByteArray()
|
||||||
|
}
|
||||||
|
require(bytes.size > amqpMagic.size) { "Insufficient bytes for AMQP blob" }
|
||||||
|
return if (bytes.copyOf(amqpMagic.size).contentEquals(amqpMagic.bytes)) {
|
||||||
|
if (verbose)
|
||||||
|
println("Parsing input as $format")
|
||||||
|
bytes
|
||||||
|
} else {
|
||||||
|
null // Not an AMQP blob.
|
||||||
|
}
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
return null // Failed to parse in some other way.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initialiseSerialization() {
|
private fun initialiseSerialization() {
|
||||||
|
// Deserialise with the lenient carpenter as we only care for the AMQP field getters
|
||||||
_contextSerializationEnv.set(SerializationEnvironmentImpl(
|
_contextSerializationEnv.set(SerializationEnvironmentImpl(
|
||||||
SerializationFactoryImpl().apply {
|
SerializationFactoryImpl().apply {
|
||||||
registerScheme(AMQPInspectorSerializationScheme)
|
registerScheme(AMQPInspectorSerializationScheme)
|
||||||
},
|
},
|
||||||
AMQP_P2P_CONTEXT
|
p2pContext = AMQP_P2P_CONTEXT.withLenientCarpenter(),
|
||||||
|
storageContext = AMQP_STORAGE_CONTEXT.withLenientCarpenter()
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private object AMQPInspectorSerializationScheme : AbstractAMQPSerializationScheme(emptyList()) {
|
private object AMQPInspectorSerializationScheme : AbstractAMQPSerializationScheme(emptyList()) {
|
||||||
override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean {
|
override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean {
|
||||||
return magic == amqpMagic && target == SerializationContext.UseCase.P2P
|
return magic == amqpMagic
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun rpcClientSerializerFactory(context: SerializationContext) = throw UnsupportedOperationException()
|
override fun rpcClientSerializerFactory(context: SerializationContext) = throw UnsupportedOperationException()
|
||||||
override fun rpcServerSerializerFactory(context: SerializationContext) = throw UnsupportedOperationException()
|
override fun rpcServerSerializerFactory(context: SerializationContext) = throw UnsupportedOperationException()
|
||||||
}
|
}
|
||||||
@ -137,5 +179,6 @@ private class CordaVersionProvider : IVersionProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum class FormatType { YAML, JSON }
|
private enum class OutputFormatType { YAML, JSON }
|
||||||
|
private enum class InputFormatType { BINARY, HEX, BASE64 }
|
||||||
|
|
@ -0,0 +1,67 @@
|
|||||||
|
package net.corda.blobinspector
|
||||||
|
|
||||||
|
import net.corda.core.identity.CordaX500Name
|
||||||
|
import net.corda.core.internal.SignedDataWithCert
|
||||||
|
import net.corda.core.node.NetworkParameters
|
||||||
|
import net.corda.core.transactions.SignedTransaction
|
||||||
|
import net.corda.core.transactions.WireTransaction
|
||||||
|
import net.corda.testing.common.internal.checkNotOnClasspath
|
||||||
|
import org.apache.commons.io.output.WriterOutputStream
|
||||||
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.junit.Test
|
||||||
|
import java.io.PrintStream
|
||||||
|
import java.io.StringWriter
|
||||||
|
import java.nio.charset.StandardCharsets.UTF_8
|
||||||
|
|
||||||
|
class BlobInspectorTest {
|
||||||
|
private val blobInspector = BlobInspector()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `network-parameters file`() {
|
||||||
|
val output = run("network-parameters")
|
||||||
|
assertThat(output)
|
||||||
|
.startsWith(SignedDataWithCert::class.java.name)
|
||||||
|
.contains(NetworkParameters::class.java.name)
|
||||||
|
.contains(CordaX500Name("Notary Service", "Zurich", "CH").toString()) // Name of the notary in the network parameters
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `node-info file`() {
|
||||||
|
checkNotOnClassPath("net.corda.nodeapi.internal.SignedNodeInfo")
|
||||||
|
val output = run("node-info")
|
||||||
|
assertThat(output)
|
||||||
|
.startsWith("net.corda.nodeapi.internal.SignedNodeInfo")
|
||||||
|
.contains(CordaX500Name("BankOfCorda", "New York", "US").toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `WireTransaction with Cash state`() {
|
||||||
|
checkNotOnClassPath("net.corda.finance.contracts.asset.Cash\$State")
|
||||||
|
val output = run("cash-wtx.blob")
|
||||||
|
assertThat(output)
|
||||||
|
.startsWith(WireTransaction::class.java.name)
|
||||||
|
.contains("net.corda.finance.contracts.asset.Cash\$State")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `SignedTransaction with Cash state taken from node db`() {
|
||||||
|
checkNotOnClassPath("net.corda.finance.contracts.asset.Cash\$State")
|
||||||
|
val output = run("cash-stx-db.blob")
|
||||||
|
assertThat(output)
|
||||||
|
.startsWith(SignedTransaction::class.java.name)
|
||||||
|
.contains("net.corda.finance.contracts.asset.Cash\$State")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun run(resourceName: String): String {
|
||||||
|
blobInspector.source = javaClass.getResource(resourceName)
|
||||||
|
val writer = StringWriter()
|
||||||
|
blobInspector.run(PrintStream(WriterOutputStream(writer, UTF_8)))
|
||||||
|
val output = writer.toString()
|
||||||
|
println(output)
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkNotOnClassPath(className: String) {
|
||||||
|
checkNotOnClasspath(className) { "The Blob Inspector does not have this as a dependency." }
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user