Merge pull request #1272 from corda/parkri-os-merge-20180713-1

OS -> ENT merge
This commit is contained in:
Rick Parker 2018-07-13 18:05:29 +01:00 committed by GitHub
commit 52eb5d76d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 539 additions and 295 deletions

View File

@ -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'

View File

@ -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.")
}
}

View File

@ -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

View File

@ -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"
}

View File

@ -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 {

View File

@ -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()

View File

@ -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.

View File

@ -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)"

View File

@ -40,7 +40,7 @@ class AttachmentTests {
@Before
fun setUp() {
mockNet = InternalMockNetwork(emptyList())
mockNet = InternalMockNetwork()
}
@After

View File

@ -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() {

View File

@ -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

View File

@ -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.

View File

@ -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**
::

View File

@ -12,7 +12,6 @@ CorDapps
cordapp-build-systems
building-against-master
corda-api
serialization-index
secure-coding-guidelines
flow-cookbook
vault

View 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>

View File

@ -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

View File

@ -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

View File

@ -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/>`_.

View File

@ -8,4 +8,5 @@ Serialization
serialization.rst
cordapp-custom-serializers
serialization-default-evolution.rst
serialization-enum-evolution.rst
serialization-enum-evolution.rst
blob-inspector

View File

@ -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

View File

@ -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)
}
}
}

View File

@ -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)
}
}

View File

@ -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
}
/**

View File

@ -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 {

View File

@ -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

View File

@ -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}"

View File

@ -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)
}

View File

@ -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)

View File

@ -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 {

View File

@ -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

View File

@ -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

View File

@ -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()
}
}
}

View File

@ -28,7 +28,7 @@ class InMemoryMessagingTests {
@Before
fun setUp() {
mockNet = InternalMockNetwork(emptyList())
mockNet = InternalMockNetwork()
}
@After

View File

@ -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,

View File

@ -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() {

View File

@ -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

View File

@ -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 {

View File

@ -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"

View File

@ -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.")
}
}

View File

@ -20,7 +20,7 @@ class InternalMockNetworkIntegrationTests {
companion object {
@JvmStatic
fun main(args: Array<String>) {
InternalMockNetwork(emptyList()).run {
InternalMockNetwork().run {
repeat(2) { createNode() }
runNetwork()
stopNodes()

View File

@ -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,

View File

@ -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)

View File

@ -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 {

View File

@ -50,5 +50,5 @@ class DummyContractV2 : UpgradedContractWithLegacyConstraint<DummyContract.State
override fun verify(tx: LedgerTransaction) {
// Other verifications.
}
// DOCEND 1
}
// DOCEND 1

View File

@ -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'
)
}
}

View File

@ -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 }

View File

@ -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." }
}
}

View File

@ -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