mirror of
https://github.com/corda/corda.git
synced 2025-01-30 16:14:39 +00:00
Merge pull request #1272 from corda/parkri-os-merge-20180713-1
OS -> ENT merge
This commit is contained in:
commit
52eb5d76d3
@ -34,7 +34,7 @@ buildscript {
|
||||
ext.capsule_version = '1.0.1'
|
||||
|
||||
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.jetty_version = '9.4.7.v20170914'
|
||||
ext.jersey_version = '2.25'
|
||||
@ -79,8 +79,8 @@ buildscript {
|
||||
ext.curator_version = '4.0.1'
|
||||
ext.proguard_version = constants.getProperty('proguardVersion')
|
||||
ext.jsch_version = '0.1.54'
|
||||
ext.protonj_version = '0.27.1'
|
||||
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.fast_classpath_scanner_version = '2.12.3'
|
||||
ext.jcabi_manifests_version = '1.1'
|
||||
@ -118,7 +118,6 @@ buildscript {
|
||||
classpath 'com.github.ben-manes:gradle-versions-plugin:0.15.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-noarg:$kotlin_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 "org.owasp:dependency-check-gradle:${dependency_checker_version}"
|
||||
classpath "org.jfrog.buildinfo:build-info-extractor-gradle:$artifactory_plugin_version"
|
||||
@ -133,7 +132,7 @@ plugins {
|
||||
}
|
||||
|
||||
ext {
|
||||
corda_revision = org.ajoberstar.grgit.Grgit.open(file('.')).head().id
|
||||
corda_revision = "git rev-parse HEAD".execute().text.trim()
|
||||
}
|
||||
|
||||
apply plugin: 'project-report'
|
||||
|
@ -13,7 +13,7 @@ package net.corda.client.rpc
|
||||
import net.corda.core.CordaRuntimeException
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
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.testing.core.SerializationEnvironmentRule
|
||||
import net.corda.testing.node.internal.rpcDriver
|
||||
@ -70,10 +70,10 @@ class RPCFailureTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unserializable`() = rpc {
|
||||
fun unserializable() = rpc {
|
||||
assertThatThrownBy { it.getUnserializable() }.isInstanceOf(CordaRuntimeException::class.java)
|
||||
.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
|
||||
@ -81,6 +81,6 @@ class RPCFailureTests {
|
||||
val future = it.getUnserializableAsync()
|
||||
assertThatThrownBy { future.getOrThrow() }.isInstanceOf(CordaRuntimeException::class.java)
|
||||
.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
|
||||
fun setup() {
|
||||
// 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
|
||||
|
@ -26,8 +26,6 @@ configurations {
|
||||
|
||||
dependencies {
|
||||
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,
|
||||
// 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:bcpkix-jdk15on:$bouncycastle_version"
|
||||
runtimeLibraries "com.google.code.findbugs:jsr305:$jsr305_version"
|
||||
runtimeLibraries "com.google.guava:guava:$guava_version"
|
||||
runtimeLibraries "net.i2p.crypto:eddsa:$eddsa_version"
|
||||
runtimeLibraries "org.slf4j:slf4j-api:$slf4j_version"
|
||||
}
|
||||
|
@ -10,7 +10,6 @@
|
||||
|
||||
package net.corda.core.identity
|
||||
|
||||
import com.google.common.collect.ImmutableSet
|
||||
import net.corda.core.KeepForDJVM
|
||||
import net.corda.core.internal.LegalNameValidator
|
||||
import net.corda.core.internal.toAttributesMap
|
||||
@ -89,7 +88,7 @@ data class CordaX500Name(val commonName: String?,
|
||||
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 countryCodes: Set<String> = ImmutableSet.copyOf(Locale.getISOCountries() + unspecifiedCountry)
|
||||
private val countryCodes: Set<String> = setOf(*Locale.getISOCountries(), unspecifiedCountry)
|
||||
|
||||
@JvmStatic
|
||||
fun build(principal: X500Principal): CordaX500Name {
|
||||
|
@ -12,8 +12,6 @@
|
||||
@file:KeepForDJVM
|
||||
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.KeepForDJVM
|
||||
import net.corda.core.cordapp.Cordapp
|
||||
@ -22,6 +20,7 @@ import net.corda.core.cordapp.CordappContext
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.schemas.MappedSchema
|
||||
import net.corda.core.serialization.*
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
@ -53,6 +52,7 @@ import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.security.KeyPair
|
||||
import java.security.MessageDigest
|
||||
import java.security.PrivateKey
|
||||
import java.security.PublicKey
|
||||
import java.security.cert.*
|
||||
@ -142,9 +142,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. */
|
||||
fun InputStream.hash(): SecureHash {
|
||||
return use {
|
||||
val his = HashingInputStream(Hashing.sha256(), it)
|
||||
his.copyTo(NullOutputStream) // To avoid reading in the entire stream into memory just write out the bytes to /dev/null
|
||||
SecureHash.SHA256(his.hash().asBytes())
|
||||
val md = MessageDigest.getInstance("SHA-256")
|
||||
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
|
||||
while (true) {
|
||||
val count = it.read(buffer)
|
||||
if (count == -1) {
|
||||
break
|
||||
}
|
||||
md.update(buffer, 0, count)
|
||||
}
|
||||
SecureHash.SHA256(md.digest())
|
||||
}
|
||||
}
|
||||
|
||||
@ -536,3 +543,8 @@ fun <T : Any> SerializedBytes<Any>.checkPayloadIs(type: Class<T>): Untrustworthy
|
||||
return type.castIfPossible(payloadData)?.let { UntrustworthyData(it) }
|
||||
?: throw IllegalArgumentException("We were expecting a ${type.name} but we instead got a ${payloadData.javaClass.name} ($payloadData)")
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension method to make this method visible to nodeapi module.
|
||||
*/
|
||||
fun MappedSchema.getMigrationResource(): String? = this.internalGetMigrationResource()
|
@ -44,13 +44,13 @@ import java.time.Instant
|
||||
*/
|
||||
@CordaSerializable
|
||||
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,
|
||||
/** The JVM class name of the flow code. */
|
||||
val flowLogicClassName: String,
|
||||
/**
|
||||
* 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,
|
||||
/** 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?
|
||||
|
||||
/** 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()
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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.
|
||||
|
@ -61,7 +61,7 @@ open class MappedSchema(schemaFamily: Class<*>,
|
||||
*/
|
||||
protected open val migrationResource: String? = null
|
||||
|
||||
internal fun getMigrationResource(): String? = migrationResource
|
||||
internal fun internalGetMigrationResource(): String? = migrationResource
|
||||
|
||||
override fun toString(): String = "${this.javaClass.simpleName}(name=$name, version=$version)"
|
||||
|
||||
|
@ -40,7 +40,7 @@ class AttachmentTests {
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
mockNet = InternalMockNetwork(emptyList())
|
||||
mockNet = InternalMockNetwork()
|
||||
}
|
||||
|
||||
@After
|
||||
|
@ -23,7 +23,7 @@ import org.junit.After
|
||||
import org.junit.Test
|
||||
|
||||
class ReceiveMultipleFlowTests {
|
||||
private val mockNet = InternalMockNetwork(emptyList())
|
||||
private val mockNet = InternalMockNetwork()
|
||||
private val nodes = (0..2).map { mockNet.createPartyNode() }
|
||||
@After
|
||||
fun stopNodes() {
|
||||
|
@ -11,9 +11,11 @@
|
||||
package net.corda.core.internal
|
||||
|
||||
import net.corda.core.contracts.TimeWindow
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Assert.assertArrayEquals
|
||||
import org.junit.Test
|
||||
import java.util.*
|
||||
import java.util.stream.IntStream
|
||||
import java.util.stream.Stream
|
||||
import kotlin.test.assertEquals
|
||||
@ -109,6 +111,19 @@ open class InternalUtilsTest {
|
||||
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
|
||||
private object PrivateObject
|
||||
protected object ProtectedObject
|
||||
|
@ -82,7 +82,7 @@ class AttachmentSerializationTest {
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
mockNet = InternalMockNetwork(emptyList())
|
||||
mockNet = InternalMockNetwork()
|
||||
server = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_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.
|
||||
|
@ -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
|
||||
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
|
||||
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. For example, the output of a node-info
|
||||
file may look like:
|
||||
The inspector can read **input data** in three formats: raw binary, hex encoded text and base64 encoded text. For instance
|
||||
if you have retrieved your binary data and it looks like this::
|
||||
|
||||
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**
|
||||
::
|
||||
|
@ -12,7 +12,6 @@ CorDapps
|
||||
cordapp-build-systems
|
||||
building-against-master
|
||||
corda-api
|
||||
serialization-index
|
||||
secure-coding-guidelines
|
||||
flow-cookbook
|
||||
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.`.
|
||||
API: Persistence documentation no longer suggests mapping between different schemas.
|
||||
|
||||
* Upgraded Artemis to v2.6.2.
|
||||
|
||||
.. _changelog_v3.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
|
||||
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
|
||||
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:
|
||||
There are many benefits of this flow based design and some development complexities as well. Some of the development challenges include:
|
||||
|
||||
* 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.
|
||||
@ -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.
|
||||
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
|
||||
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
|
||||
unfamiliar with Kotlin, there is an official
|
||||
`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.
|
||||
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
|
||||
-------------------
|
||||
The instructions below will allow you to set up a Corda development environment and run a basic CorDapp. If you have
|
||||
any issues, please consult the :doc:`troubleshooting` page, or reach out on `Slack <http://slack.corda.net/>`_,
|
||||
`Stack Overflow <https://stackoverflow.com/questions/tagged/corda>`_ or the `forums <https://discourse.corda.net/>`_.
|
||||
The instructions below will allow you to set up your development environment for running Corda and writing CorDapps. If
|
||||
you have any issues, please reach out on `Stack Overflow <https://stackoverflow.com/questions/tagged/corda>`_ or via
|
||||
`our Slack channels <http://slack.corda.net/>`_.
|
||||
|
||||
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.
|
||||
|
||||
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:`flow cookbook <flow-cookbook>` and the `samples <https://www.corda.net/samples/>`_ along the way.
|
||||
:doc:`Hello, World tutorial <hello-world-introduction>`. You may want to refer to the
|
||||
: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
|
||||
`Stack Overflow <https://stackoverflow.com/questions/tagged/corda>`_ or via `our Slack channels <http://slack.corda.net/>`_.
|
||||
If you encounter any issues, please ask on `Stack Overflow <https://stackoverflow.com/questions/tagged/corda>`_ or via
|
||||
`our Slack channels <http://slack.corda.net/>`_.
|
||||
|
@ -8,4 +8,5 @@ Serialization
|
||||
serialization.rst
|
||||
cordapp-custom-serializers
|
||||
serialization-default-evolution.rst
|
||||
serialization-enum-evolution.rst
|
||||
serialization-enum-evolution.rst
|
||||
blob-inspector
|
@ -1,10 +1,12 @@
|
||||
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::
|
||||
:maxdepth: 1
|
||||
|
||||
network-bootstrapper
|
||||
blob-inspector
|
||||
demobench
|
||||
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 {
|
||||
override fun getMessageSize(packet: AMQPMessage?): Int? = packet?.length
|
||||
override fun getMessageSize(packet: AMQPMessage?): Int? = packet?.encodeSize
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -8,9 +8,10 @@
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.core.internal
|
||||
package net.corda.nodeapi.internal
|
||||
|
||||
import com.google.common.base.CaseFormat
|
||||
import net.corda.core.internal.getMigrationResource
|
||||
import net.corda.core.schemas.MappedSchema
|
||||
|
||||
object MigrationHelpers {
|
@ -20,7 +20,7 @@ import liquibase.database.core.MSSQLDatabase
|
||||
import liquibase.database.jvm.JdbcConnection
|
||||
import liquibase.lockservice.LockServiceFactory
|
||||
import liquibase.resource.ClassLoaderResourceAccessor
|
||||
import net.corda.core.internal.MigrationHelpers.getMigrationResource
|
||||
import net.corda.nodeapi.internal.MigrationHelpers.getMigrationResource
|
||||
import net.corda.core.schemas.MappedSchema
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import java.io.ByteArrayInputStream
|
||||
|
@ -8,6 +8,18 @@
|
||||
* 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'
|
||||
// Java Persistence API support: create no-arg constructor
|
||||
// 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'
|
||||
|
||||
// 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
|
||||
configurations {
|
||||
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.
|
||||
// This prevents problems in IntelliJ with regard to duplicate source roots.
|
||||
processResources {
|
||||
@ -73,6 +83,8 @@ dependencies {
|
||||
|
||||
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)
|
||||
compile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}"
|
||||
compile "org.apache.logging.log4j:log4j-web:${log4j_version}"
|
||||
|
@ -11,6 +11,7 @@
|
||||
package net.corda.node.internal
|
||||
|
||||
import net.corda.client.rpc.notUsed
|
||||
import net.corda.core.CordaRuntimeException
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.context.InvocationContext
|
||||
import net.corda.core.context.InvocationOrigin
|
||||
@ -59,6 +60,7 @@ import net.corda.nodeapi.exceptions.NonRpcFlowException
|
||||
import net.corda.nodeapi.exceptions.RejectedCommandException
|
||||
import rx.Observable
|
||||
import java.io.InputStream
|
||||
import java.net.ConnectException
|
||||
import java.security.PublicKey
|
||||
import java.time.Instant
|
||||
|
||||
@ -247,6 +249,17 @@ internal class CordaRPCOpsImpl(
|
||||
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> {
|
||||
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)
|
||||
return result?.let { ensureSerialisable(it) }
|
||||
} catch (exception: Exception) {
|
||||
// In this special case logging and re-throwing is the right approach.
|
||||
log(exception)
|
||||
throw ensureSerialisable(exception)
|
||||
}
|
||||
}
|
||||
@ -90,7 +88,13 @@ internal class ExceptionSerialisingRpcOpsProxy(private val delegate: CordaRPCOps
|
||||
|
||||
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 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) {
|
||||
result.stackTrace = arrayOf<StackTraceElement>()
|
||||
result.setCause(null)
|
||||
|
@ -11,6 +11,7 @@
|
||||
package net.corda.node.services.network
|
||||
|
||||
import com.google.common.util.concurrent.MoreExecutors
|
||||
import net.corda.core.CordaRuntimeException
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.SignedData
|
||||
import net.corda.core.internal.copyTo
|
||||
@ -38,6 +39,7 @@ import java.nio.file.StandardCopyOption
|
||||
import java.time.Duration
|
||||
import java.util.*
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
@ -55,7 +57,7 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
|
||||
}
|
||||
|
||||
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 fileWatcherSubscription: Subscription? = null
|
||||
|
||||
@ -91,10 +93,11 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
|
||||
if (networkMapClient == null) return
|
||||
|
||||
// Subscribe to remote network map if configured.
|
||||
executor.executeExistingDelayedTasksAfterShutdownPolicy = false
|
||||
executor.submit(object : Runnable {
|
||||
override fun run() {
|
||||
val nextScheduleDelay = try {
|
||||
updateNetworkMapCache(networkMapClient)
|
||||
updateNetworkMapCache()
|
||||
} catch (t: Throwable) {
|
||||
logger.warn("Error encountered while updating network map, will retry in $defaultRetryInterval", t)
|
||||
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.
|
||||
}
|
||||
|
||||
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()
|
||||
globalNetworkMap.parametersUpdate?.let { handleUpdateNetworkParameters(networkMapClient, it) }
|
||||
val additionalHashes = extraNetworkMapKeys.flatMap {
|
||||
|
@ -11,7 +11,7 @@
|
||||
package net.corda.node.services.persistence
|
||||
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.internal.MigrationHelpers.migrationResourceNameForSchema
|
||||
import net.corda.nodeapi.internal.MigrationHelpers.migrationResourceNameForSchema
|
||||
import net.corda.core.internal.objectOrNewInstance
|
||||
import net.corda.core.schemas.MappedSchema
|
||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
|
@ -39,8 +39,7 @@ import kotlin.test.assertFails
|
||||
|
||||
class NetworkParametersTest {
|
||||
private val mockNet = InternalMockNetwork(
|
||||
emptyList(),
|
||||
MockNetworkParameters(networkSendManuallyPumped = true),
|
||||
defaultParameters = MockNetworkParameters(networkSendManuallyPumped = true),
|
||||
notarySpecs = listOf(MockNetworkNotarySpec(DUMMY_NOTARY_NAME)))
|
||||
|
||||
@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.
|
||||
*/
|
||||
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.services.CordaService
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.testing.core.DUMMY_BANK_A_NAME
|
||||
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 net.corda.testing.node.internal.InternalMockNetwork
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class NodeUnloadHandlerTests : IntegrationTest() {
|
||||
class NodeUnloadHandlerTests {
|
||||
companion object {
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val databaseSchemas = IntegrationTestSchemas(DUMMY_BANK_A_NAME.toDatabaseSchemaName(), DUMMY_NOTARY_NAME.toDatabaseSchemaName() )
|
||||
val latch = CountDownLatch(1)
|
||||
val registerLatch = CountDownLatch(1)
|
||||
val shutdownLatch = CountDownLatch(1)
|
||||
}
|
||||
|
||||
private val mockNet = InternalMockNetwork(cordappPackages = listOf(javaClass.packageName), notarySpecs = emptyList())
|
||||
|
||||
@After
|
||||
fun cleanUp() {
|
||||
mockNet.stopNodes()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should be able to register run on stop lambda`() {
|
||||
driver(DriverParameters(startNodesInProcess = true, extraCordappPackagesToScan = listOf("net.corda.node"), notarySpecs = emptyList())) {
|
||||
startNode(providedName = DUMMY_BANK_A_NAME).getOrThrow()
|
||||
// just want to fall off the end of this for the mo...
|
||||
}
|
||||
assertTrue("Timed out waiting for AbstractNode to invoke the test service shutdown callback", latch.await(30, TimeUnit.SECONDS))
|
||||
val node = mockNet.createNode()
|
||||
registerLatch.await() // Make sure the handler is registered on node start up
|
||||
node.dispose()
|
||||
assertTrue("Timed out waiting for AbstractNode to invoke the test service shutdown callback", shutdownLatch.await(30, TimeUnit.SECONDS))
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
@ -55,11 +51,12 @@ class NodeUnloadHandlerTests : IntegrationTest() {
|
||||
|
||||
init {
|
||||
serviceHub.registerUnloadHandler(this::shutdown)
|
||||
registerLatch.countDown()
|
||||
}
|
||||
|
||||
private fun shutdown() {
|
||||
log.info("shutting down")
|
||||
latch.countDown()
|
||||
shutdownLatch.countDown()
|
||||
}
|
||||
}
|
||||
}
|
@ -28,7 +28,7 @@ class InMemoryMessagingTests {
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
mockNet = InternalMockNetwork(emptyList())
|
||||
mockNet = InternalMockNetwork()
|
||||
}
|
||||
|
||||
@After
|
||||
|
@ -35,7 +35,7 @@ class FinalityHandlerTest {
|
||||
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
|
||||
// CorDapp. Bob's FinalityHandler will error when validating the tx.
|
||||
mockNet = InternalMockNetwork(cordappPackages = emptyList())
|
||||
mockNet = InternalMockNetwork()
|
||||
|
||||
val alice = mockNet.createNode(InternalMockNodeParameters(
|
||||
legalName = ALICE_NAME,
|
||||
|
@ -29,7 +29,7 @@ import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertNull
|
||||
|
||||
class NetworkMapCacheTest {
|
||||
private val mockNet = InternalMockNetwork(emptyList())
|
||||
private val mockNet = InternalMockNetwork()
|
||||
|
||||
@After
|
||||
fun teardown() {
|
||||
|
@ -50,7 +50,7 @@ class NodeSchemaServiceTest {
|
||||
|
||||
@Test
|
||||
fun `check node runs with minimal core schema set`() {
|
||||
val mockNet = InternalMockNetwork(cordappPackages = emptyList())
|
||||
val mockNet = InternalMockNetwork()
|
||||
val mockNode = mockNet.createNode()
|
||||
val schemaService = mockNode.services.schemaService
|
||||
|
||||
@ -62,7 +62,7 @@ class NodeSchemaServiceTest {
|
||||
|
||||
@Test
|
||||
fun `check node runs inclusive of notary node schema set`() {
|
||||
val mockNet = InternalMockNetwork(cordappPackages = emptyList())
|
||||
val mockNet = InternalMockNetwork()
|
||||
val mockNotaryNode = mockNet.notaryNodes.first()
|
||||
val schemaService = mockNotaryNode.services.schemaService
|
||||
|
||||
|
@ -26,13 +26,13 @@ configurations {
|
||||
|
||||
dependencies {
|
||||
compileOnly project(':serialization')
|
||||
compileOnly "$quasar_group:quasar-core:$quasar_version:jdk8"
|
||||
|
||||
// Configure these by hand. It should be a minimal subset of dependencies,
|
||||
// and without any obviously non-deterministic ones such as Hibernate.
|
||||
runtimeLibraries project(path: ':core-deterministic', configuration: 'runtimeArtifacts')
|
||||
runtimeLibraries "org.apache.qpid:proton-j:$protonj_version"
|
||||
runtimeLibraries "org.iq80.snappy:snappy:$snappy_version"
|
||||
runtimeLibraries "com.google.guava:guava:$guava_version"
|
||||
}
|
||||
|
||||
jar {
|
||||
|
@ -14,6 +14,8 @@ dependencies {
|
||||
|
||||
compile "org.ow2.asm:asm:$asm_version"
|
||||
|
||||
compile "com.google.guava:guava:$guava_version"
|
||||
|
||||
// For AMQP serialisation.
|
||||
compile "org.apache.qpid:proton-j:$protonj_version"
|
||||
|
||||
|
@ -534,7 +534,7 @@ private fun Throwable.setMessage(newMsg: String) {
|
||||
|
||||
fun ClassWhitelist.requireWhitelisted(type: Type) {
|
||||
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 {
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
InternalMockNetwork(emptyList()).run {
|
||||
InternalMockNetwork().run {
|
||||
repeat(2) { createNode() }
|
||||
runNetwork()
|
||||
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(),
|
||||
val networkSendManuallyPumped: Boolean = defaultParameters.networkSendManuallyPumped,
|
||||
val threadPerNode: Boolean = defaultParameters.threadPerNode,
|
||||
|
@ -19,7 +19,7 @@ class InternalMockNetworkTests {
|
||||
fun `does not leak serialization env if init fails`() {
|
||||
val e = Exception("didn't work")
|
||||
assertThatThrownBy {
|
||||
object : InternalMockNetwork(emptyList()) {
|
||||
object : InternalMockNetwork() {
|
||||
override fun createNotaries() = throw e
|
||||
}
|
||||
}.isSameAs(e)
|
||||
|
@ -230,6 +230,10 @@ class CordaRPCProxyClient(private val targetHostAndPort: NetworkHostAndPort) : C
|
||||
TODO("not implemented")
|
||||
}
|
||||
|
||||
override fun refreshNetworkMapCache() {
|
||||
TODO("not implemented")
|
||||
}
|
||||
|
||||
private inline fun <reified T : Any> doPost(hostAndPort: NetworkHostAndPort, path: String, payload: ByteArray) : T {
|
||||
val url = URL("http://$hostAndPort/rpc/$path")
|
||||
val connection = url.openHttpConnection().apply {
|
||||
|
@ -50,5 +50,5 @@ class DummyContractV2 : UpgradedContractWithLegacyConstraint<DummyContract.State
|
||||
override fun verify(tx: LedgerTransaction) {
|
||||
// Other verifications.
|
||||
}
|
||||
// DOCEND 1
|
||||
}
|
||||
// DOCEND 1
|
||||
|
@ -10,8 +10,11 @@ dependencies {
|
||||
compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_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 "junit:junit:$junit_version"
|
||||
}
|
||||
|
||||
jar {
|
||||
@ -24,7 +27,7 @@ jar {
|
||||
manifest {
|
||||
attributes(
|
||||
'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.rootMessage
|
||||
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.internal.SerializationEnvironmentImpl
|
||||
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.serialization.internal.AMQP_P2P_CONTEXT
|
||||
import net.corda.serialization.internal.AMQP_STORAGE_CONTEXT
|
||||
import net.corda.serialization.internal.CordaSerializationMagic
|
||||
import net.corda.serialization.internal.SerializationFactoryImpl
|
||||
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 picocli.CommandLine
|
||||
import picocli.CommandLine.*
|
||||
import java.io.PrintStream
|
||||
import java.net.MalformedURLException
|
||||
import java.net.URL
|
||||
import java.nio.file.Paths
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val main = Main()
|
||||
val main = BlobInspector()
|
||||
try {
|
||||
CommandLine.run(main, *args)
|
||||
} catch (e: ExecutionException) {
|
||||
@ -41,18 +45,21 @@ fun main(args: Array<String>) {
|
||||
}
|
||||
|
||||
@Command(
|
||||
name = "Blob Inspector",
|
||||
name = "blob-inspector",
|
||||
versionProvider = CordaVersionProvider::class,
|
||||
mixinStandardHelpOptions = true, // add --help and --version options,
|
||||
mixinStandardHelpOptions = true, // add --help and --version options,
|
||||
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])
|
||||
private var source: URL? = null
|
||||
var source: URL? = null
|
||||
|
||||
@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"],
|
||||
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"])
|
||||
var verbose: Boolean = false
|
||||
|
||||
override fun run() {
|
||||
override fun run() = run(System.out)
|
||||
|
||||
fun run(out: PrintStream) {
|
||||
if (verbose) {
|
||||
System.setProperty("logLevel", "trace")
|
||||
}
|
||||
|
||||
val bytes = source!!.readBytes().run {
|
||||
require(size > amqpMagic.size) { "Insufficient bytes for AMQP blob" }
|
||||
sequence()
|
||||
}
|
||||
|
||||
require(bytes.take(amqpMagic.size) == amqpMagic) { "Not an AMQP blob" }
|
||||
val inputBytes = source!!.readBytes()
|
||||
val bytes = parseToBinaryRelaxed(inputFormatType, inputBytes)
|
||||
?: throw IllegalArgumentException("Error: this input does not appear to be encoded in Corda's AMQP extended format, sorry.")
|
||||
|
||||
if (schema) {
|
||||
val envelope = DeserializationInput.getEnvelope(bytes)
|
||||
println(envelope.schema)
|
||||
println()
|
||||
println(envelope.transformsSchema)
|
||||
println()
|
||||
val envelope = DeserializationInput.getEnvelope(bytes.sequence())
|
||||
out.println(envelope.schema)
|
||||
out.println()
|
||||
out.println(envelope.transformsSchema)
|
||||
out.println()
|
||||
}
|
||||
|
||||
initialiseSerialization()
|
||||
|
||||
val factory = when (formatType) {
|
||||
FormatType.YAML -> YAMLFactory()
|
||||
FormatType.JSON -> JsonFactory()
|
||||
OutputFormatType.YAML -> YAMLFactory()
|
||||
OutputFormatType.JSON -> JsonFactory()
|
||||
}
|
||||
|
||||
val mapper = JacksonSupport.createNonRpcMapper(factory, fullParties)
|
||||
|
||||
// Deserialise with the lenient carpenter as we only care for the AMQP field getters
|
||||
val deserialized = bytes.deserialize<Any>(context = SerializationFactory.defaultFactory.defaultContext.withLenientCarpenter())
|
||||
println(deserialized.javaClass.name)
|
||||
mapper.writeValue(System.out, deserialized)
|
||||
initialiseSerialization()
|
||||
try {
|
||||
val deserialized = bytes.deserialize<Any>(context = SerializationDefaults.STORAGE_CONTEXT)
|
||||
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() {
|
||||
// Deserialise with the lenient carpenter as we only care for the AMQP field getters
|
||||
_contextSerializationEnv.set(SerializationEnvironmentImpl(
|
||||
SerializationFactoryImpl().apply {
|
||||
registerScheme(AMQPInspectorSerializationScheme)
|
||||
},
|
||||
AMQP_P2P_CONTEXT
|
||||
p2pContext = AMQP_P2P_CONTEXT.withLenientCarpenter(),
|
||||
storageContext = AMQP_STORAGE_CONTEXT.withLenientCarpenter()
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
private object AMQPInspectorSerializationScheme : AbstractAMQPSerializationScheme(emptyList()) {
|
||||
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 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.
@ -18,7 +18,7 @@ import joptsimple.OptionException
|
||||
import joptsimple.OptionParser
|
||||
import joptsimple.OptionSet
|
||||
import joptsimple.util.EnumConverter
|
||||
import net.corda.core.internal.MigrationHelpers
|
||||
import net.corda.nodeapi.internal.MigrationHelpers
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.exists
|
||||
import net.corda.core.schemas.MappedSchema
|
||||
|
Loading…
x
Reference in New Issue
Block a user