From ac179aa9ab7979faed0c355a014c0414de94c551 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Wed, 11 Jul 2018 14:50:51 +0100 Subject: [PATCH 01/21] CORDA-1664: Blob inspector able to display SignedTransaction blobs dumped from a node's db. (#3559) --- tools/blobinspector/build.gradle | 7 +- .../{Main.kt => BlobInspector.kt} | 43 ++++++----- .../corda/blobinspector/BlobInspectorTest.kt | 67 ++++++++++++++++++ .../net/corda/blobinspector/cash-stx-db.blob | Bin 0 -> 11244 bytes .../net/corda/blobinspector/cash-wtx.blob | Bin 0 -> 14875 bytes .../corda/blobinspector/network-parameters | Bin 0 -> 4559 bytes .../net/corda/blobinspector/node-info | Bin 0 -> 4718 bytes 7 files changed, 98 insertions(+), 19 deletions(-) rename tools/blobinspector/src/main/kotlin/net/corda/blobinspector/{Main.kt => BlobInspector.kt} (83%) create mode 100644 tools/blobinspector/src/test/kotlin/net/corda/blobinspector/BlobInspectorTest.kt create mode 100644 tools/blobinspector/src/test/resources/net/corda/blobinspector/cash-stx-db.blob create mode 100644 tools/blobinspector/src/test/resources/net/corda/blobinspector/cash-wtx.blob create mode 100644 tools/blobinspector/src/test/resources/net/corda/blobinspector/network-parameters create mode 100644 tools/blobinspector/src/test/resources/net/corda/blobinspector/node-info diff --git a/tools/blobinspector/build.gradle b/tools/blobinspector/build.gradle index 26a15d84dc..4cd7ef4386 100644 --- a/tools/blobinspector/build.gradle +++ b/tools/blobinspector/build.gradle @@ -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' ) } } diff --git a/tools/blobinspector/src/main/kotlin/net/corda/blobinspector/Main.kt b/tools/blobinspector/src/main/kotlin/net/corda/blobinspector/BlobInspector.kt similarity index 83% rename from tools/blobinspector/src/main/kotlin/net/corda/blobinspector/Main.kt rename to tools/blobinspector/src/main/kotlin/net/corda/blobinspector/BlobInspector.kt index 9590f39d8e..389688f766 100644 --- a/tools/blobinspector/src/main/kotlin/net/corda/blobinspector/Main.kt +++ b/tools/blobinspector/src/main/kotlin/net/corda/blobinspector/BlobInspector.kt @@ -7,12 +7,13 @@ 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.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 +21,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) { - val main = Main() + val main = BlobInspector() try { CommandLine.run(main, *args) } catch (e: ExecutionException) { @@ -47,9 +49,9 @@ fun main(args: Array) { showDefaultValues = true, description = ["Inspect AMQP serialised binary blobs"] ) -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 @@ -64,7 +66,9 @@ 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") } @@ -78,39 +82,44 @@ class Main : Runnable { if (schema) { val envelope = DeserializationInput.getEnvelope(bytes) - println(envelope.schema) - println() - println(envelope.transformsSchema) - println() + out.println(envelope.schema) + out.println() + out.println(envelope.transformsSchema) + out.println() } - initialiseSerialization() - val factory = when (formatType) { FormatType.YAML -> YAMLFactory() FormatType.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(context = SerializationFactory.defaultFactory.defaultContext.withLenientCarpenter()) - println(deserialized.javaClass.name) - mapper.writeValue(System.out, deserialized) + initialiseSerialization() + try { + val deserialized = bytes.deserialize(context = SerializationDefaults.STORAGE_CONTEXT) + out.println(deserialized.javaClass.name) + mapper.writeValue(out, deserialized) + } finally { + _contextSerializationEnv.set(null) + } } 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() diff --git a/tools/blobinspector/src/test/kotlin/net/corda/blobinspector/BlobInspectorTest.kt b/tools/blobinspector/src/test/kotlin/net/corda/blobinspector/BlobInspectorTest.kt new file mode 100644 index 0000000000..65e1223c4f --- /dev/null +++ b/tools/blobinspector/src/test/kotlin/net/corda/blobinspector/BlobInspectorTest.kt @@ -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." } + } +} diff --git a/tools/blobinspector/src/test/resources/net/corda/blobinspector/cash-stx-db.blob b/tools/blobinspector/src/test/resources/net/corda/blobinspector/cash-stx-db.blob new file mode 100644 index 0000000000000000000000000000000000000000..21f93d8367c021c98c805da630920af9b0556b14 GIT binary patch literal 11244 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 literal 0 HcmV?d00001 diff --git a/tools/blobinspector/src/test/resources/net/corda/blobinspector/network-parameters b/tools/blobinspector/src/test/resources/net/corda/blobinspector/network-parameters new file mode 100644 index 0000000000000000000000000000000000000000..d6494a03d9db6f312053a0265541a9d7b5f19c33 GIT binary patch literal 4559 zcmb_gZEPF$8NWOG&d&Rqgr;p8aND&-N#~y9yjDr)mpE~3H+2#_%?o2XpU=)UzB}jc z9LFINZGh3j*nnzmurIk~6WY45PE)5&)!1zv9iR!dUnWgO@By@HnpD2PG$f#V&S~wl z6FUSgAEJBt{h#;e_dI7>5Tg`|Ajp|FBJdwU&>IM{;|g3DkhT3hBU@<=acm-!UYd}O zhQ@l9fTTfF9lvQRq#yXG^ zDVY^`wHgZ{X90>Jho3{{kyQklfBf#}sS!v7Zx5ikW;%Np7jF&}4UG%7xwh$rG&t=q40iX_^msUIm)%`c3-YAh?oSB4J}rm`&{az;X8S1GalQ3h zS7+!p;_MfflBY*6F2^#12YpxSBSu*{TFyKl)YVrqu?EexmwC*r94e=Tk|bz4Hzmz} zIrocXAK6;=Nl0`&+ZJC%L(UTnTHkyOlBzL=*#;LCxpz1`!u;?ER}SV4f_`VLybxuq zBaECD#JM0PQXC^QqU7-8&KlH1n7(^Tb-x32ptrT6${jVTjV1+Wk^_m~tj zuTcQYNDA7j+EB%sJ(QB!gf+lQa^I%e$+E^U9}|ujg<{d9>`%|KGLwL_Ow@g^bBDQP zg5e;l*{aH_@}bI5UeyUI*(b>&%g2vc@=rH2fEF`JS+I_4w8VI!3yv4sK&S~~+fH~C zqh@ff4vI^x6p`zy5?57Rd9Zx6bnp!tN{j%9%3?XQjOtNH7#oE^0wXIeMcVGautM9& znc`eS@l`HR2{sBj;NyxZA_xhF;ynr;qY@IMFKJx8iEkmjj4P$4ltXuhAlbs|v0^4Bx$v~2UVJ~rL%Ej6%?sPJ?Oxu)U&zfGOm^y zv<~$HLYK8Y5PBL$m%wYDD8zcDgGVks^)#6j6D_1*)@5{%0(RO3=}!nWWD(Mz7gK=Q z3p5~zaf)Z9Y`2xdSxm~;^tdq3$~tU;X%7Hv!oDMyu$@q!B>sQh>`@FHmCS{&xKm24 z5V|xGDo-fnc6r3H6$_NwFm#%6^(wAdiu!T`W$6wSdl%{M4KgQO0m-|VYInBVGXqm& z6s&FzHDbtx#HeZsiDYD8Z6X3&lDMJU0YC_`7_159gt_%Q z-go+q2JGII0X316wq|lSVF()y)%97PvW&pqVi^+z+2VHEs>llMWo0!sX1CRn)!I!} zHJ<&TvDJ|^+P$fo2nc*s;P0b}1}y9+A8e|z^^n~*8=3u^ZmMbTCOg~L@5pJ=qk|8* za>5$!3%e)1vVj=0TLN;_)=1W84Q;3zS>LKGFm3fKeBTql`v>25=<-jVpLo1!@Po={ zUw!}Gt51tB9qG8ZcwzV7xwW$|k!LTEEg+?(*0|Hy_@;cve}OVCzr1+a^{}OP+k&R^ZMZ1=a9LT^yv5vOMc#I%P^LL+SHa|ECCn!cE;6D zaHX}V+h99oM4n1m$Jsc~LpMfTGf_mxKdx?4)w*nEk?D^q!>ght^blpP$fW3AsYQI|$FecwD@JcRo}j>N_lvXt=yY@C&;gysV!!@_pog+f8N`V78p uTgpVC)UONMqa9p*1K+-9Wy~r?TMqRReR8ZO1&d^q{af=^VHCdL_tzC&V@=9JUHZmcO zInB`~#X|@U!+0^Fg(=1)CL!VIrX+-hV?t;PlRzMZYbG2`CKLim>08;@l59CZNIfHI zci;Ek_ul{ey>Gu~i-RQzL?V&A^kEJBmPn9KB$DA3@B))`#4{A9wulx}X{ubi&_>pj zEHoCE7Nr|CjRu0y&zJ$N6$)sL`dBw*Y3h^N6q7AqJF}!Qx7J!iYRo;>5k;btE{P;! zre|b--w{&=_+~6+mg=U4S=uTS19CaiQBjdnQsVI)d=pyZKEAaJi)THnciZe5r)ieH z((E*qb2RiVXb~T~Tzyh1nVwYEP?YB8TxpHQCN?7tjw%d8`Cz-lLfGdyTwJ|GG9lWh zA5lSN)Fu;pl9`g9tRa&1DTe&Ip0x#+iEHz*HW9_n`YCLIHlMT3Dk?D+YO4)3z19wTrSWU}?^+woojmHo&i9f}0104*C!5fI{hlu7D*$7r_!V7%V}$+A$PC z(I96<_x7W;c~?P*ENJqIoD(x}3gC%5W@fxIql0s?b4FDzn4EsSJ8eCp= ztMcNlyW1`o=TQ@`KG={}(55z$-`xm4vf}(|TgH#EtsgZffDRJWfEc*wm7X{&z+1N z4o39D(67}m7#tCc43MsQ$bml=7H^75HN!mW;BsY9A=Rj=a8plZ8OeaAfJ~ z8Uzt(L6>gJFVDfE#tBC``W&?EFV6X}+IiY6 zjJwrpc(ZD0=7s%d9++iq&#$bvrt0zasVxW1C#u>lt1G(?erMXguCSx!DrmXn#ZTp! z8}_WrqYpK&t$5~C?ad8)7q`9hwD}p7(ECRo=`$wGB_7l zjs#?*s>H4z;=V5pJ@oq&Hg96>sj^GE)-GdD9^3Fr>NV3B+pcdx$4L%sK$pM%n~|G# zo~=@ZYcnDfCa%o?%h|6NUHevB*BYbL!DM#N>mlrpSU`yvVihRuYm>r9NTc|OAKO=3iFGWc zW?=QB7E7R9#_G^z_jSR9MAW<74ODXK#G%K+q4B=1xKJwRg9(ykDVK|K@yKK$6XBP*mFsW z;XJ*drC1j}qBFXupIco_u$;TMFKwkQ1V{NhiVr=8%e@MT&~u`b%d@EFZ-**pS# zr!`8>%Ul#o6LucAI~be4g-meEex3~3aA2ripA`N<3RLNP#Z@Y7mGViCA--2%e|Lcn z3_3xF_RSF&M#yuyoR?V9G{ldS*vSheQNvq|B7 z>k^X``rqCph2Hi_8=fTfYfqAz)X%4wp9m+dhC7a88N#kErELsFK0?L{bU@b$fAyxb zLe66>(a7iZ`}8Eno}ElL5ggp{>Qc|Xrkf}-%gs?P|Lohff zN)+><7U9!|&W9G(?c^M4BWoQk7&;-|8{3g`URon6 z#LH@EhG5;kTF3-W_QWgysf`>6b+1)Pg%&BF=oje#QhRmt-@}16%j1b5?$!SRlc{K5 literal 0 HcmV?d00001 From fa92c082badf0e6e50e1ed78232d36d313c4582a Mon Sep 17 00:00:00 2001 From: Lamar Thomas <38670842+r3ltsupport@users.noreply.github.com> Date: Wed, 11 Jul 2018 11:05:14 -0400 Subject: [PATCH 02/21] Removed the negative reference to ethereum/bitcoin in reading this document from docs.corda.net, trying to understand the flow state machines (support /development perspective), I found that the negative outlook of limitations in ethereum and bitcoin may be a bit out-dated and interrupted learning thought/understanding - on this specific page. adjusted the verbage to keep the point that there are development challenges that we've addressed, but removed the excess language around ethereum and bitcoin --- docs/source/flow-state-machines.rst | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/source/flow-state-machines.rst b/docs/source/flow-state-machines.rst index 5f7f2a8caa..c48481df3c 100644 --- a/docs/source/flow-state-machines.rst +++ b/docs/source/flow-state-machines.rst @@ -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 \ No newline at end of file + reporting logic, or anything else that might be required as part of a communications lifecycle From a4355ce1981b0406ffd1654ba0cc8062847f7a74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Pl=C3=A1=C5=A1il?= Date: Thu, 12 Jul 2018 01:10:43 +0800 Subject: [PATCH 03/21] Configure NetworkMapUpdater executor so that it doesn't block node shutdown (#3530) --- .../net/corda/node/services/network/NetworkMapUpdater.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapUpdater.kt b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapUpdater.kt index b30e3472e8..d06abce27a 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapUpdater.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapUpdater.kt @@ -28,6 +28,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 @@ -45,7 +46,7 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal, } private val parametersUpdatesTrack: PublishSubject = PublishSubject.create() - 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? = null private var fileWatcherSubscription: Subscription? = null @@ -81,6 +82,7 @@ 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 { From f4a248f81f58355efc3e3c5fd3df757cec3ddb97 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Thu, 12 Jul 2018 11:55:51 +0100 Subject: [PATCH 04/21] Made CashSelectionTest a unit test by using mock network. (#3570) Also includes adding a default value of emptyList() to InternalMockNetwork.cordappPackages --- .../confidential/SwapIdentitiesFlowTests.kt | 2 +- .../net/corda/core/flows/AttachmentTests.kt | 2 +- .../corda/core/flows/ReceiveAllFlowTests.kt | 2 +- .../AttachmentSerializationTest.kt | 2 +- .../corda/finance/flows/CashSelectionTest.kt | 138 ------------------ .../corda/finance/flows/CashSelectionTest.kt | 136 +++++++++++++++++ .../node/internal/NetworkParametersTest.kt | 3 +- .../node/messaging/InMemoryMessagingTests.kt | 2 +- .../node/services/FinalityHandlerTest.kt | 2 +- .../services/network/NetworkMapCacheTest.kt | 2 +- .../services/schema/NodeSchemaServiceTest.kt | 4 +- .../InternalMockNetworkIntegrationTests.kt | 2 +- .../node/internal/InternalMockNetwork.kt | 2 +- .../node/internal/InternalMockNetworkTests.kt | 2 +- 14 files changed, 149 insertions(+), 152 deletions(-) delete mode 100644 finance/src/integration-test/kotlin/net/corda/finance/flows/CashSelectionTest.kt create mode 100644 finance/src/test/kotlin/net/corda/finance/flows/CashSelectionTest.kt diff --git a/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt b/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt index 91ab305ab6..5b1541b597 100644 --- a/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt +++ b/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt @@ -15,7 +15,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 diff --git a/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt b/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt index 6029897303..1b0974559d 100644 --- a/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt @@ -30,7 +30,7 @@ class AttachmentTests { @Before fun setUp() { - mockNet = InternalMockNetwork(emptyList()) + mockNet = InternalMockNetwork() } @After diff --git a/core/src/test/kotlin/net/corda/core/flows/ReceiveAllFlowTests.kt b/core/src/test/kotlin/net/corda/core/flows/ReceiveAllFlowTests.kt index 93f2f0f471..8970f5e799 100644 --- a/core/src/test/kotlin/net/corda/core/flows/ReceiveAllFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/ReceiveAllFlowTests.kt @@ -13,7 +13,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() { diff --git a/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt b/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt index eea014f9c6..ac5116f938 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt @@ -72,7 +72,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. diff --git a/finance/src/integration-test/kotlin/net/corda/finance/flows/CashSelectionTest.kt b/finance/src/integration-test/kotlin/net/corda/finance/flows/CashSelectionTest.kt deleted file mode 100644 index f16e4122c5..0000000000 --- a/finance/src/integration-test/kotlin/net/corda/finance/flows/CashSelectionTest.kt +++ /dev/null @@ -1,138 +0,0 @@ -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.singleIdentity -import net.corda.testing.driver.DriverParameters -import net.corda.testing.driver.driver -import net.corda.testing.driver.internal.InProcessImpl -import org.assertj.core.api.Assertions.assertThat -import org.junit.Test - -class CashSelectionTest { - - @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) - } - } -} \ No newline at end of file diff --git a/finance/src/test/kotlin/net/corda/finance/flows/CashSelectionTest.kt b/finance/src/test/kotlin/net/corda/finance/flows/CashSelectionTest.kt new file mode 100644 index 0000000000..0f829fbee7 --- /dev/null +++ b/finance/src/test/kotlin/net/corda/finance/flows/CashSelectionTest.kt @@ -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) + } +} diff --git a/node/src/test/kotlin/net/corda/node/internal/NetworkParametersTest.kt b/node/src/test/kotlin/net/corda/node/internal/NetworkParametersTest.kt index 1042a6d49e..e224f32b7f 100644 --- a/node/src/test/kotlin/net/corda/node/internal/NetworkParametersTest.kt +++ b/node/src/test/kotlin/net/corda/node/internal/NetworkParametersTest.kt @@ -29,8 +29,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 diff --git a/node/src/test/kotlin/net/corda/node/messaging/InMemoryMessagingTests.kt b/node/src/test/kotlin/net/corda/node/messaging/InMemoryMessagingTests.kt index d250ddf7b1..4add5f243c 100644 --- a/node/src/test/kotlin/net/corda/node/messaging/InMemoryMessagingTests.kt +++ b/node/src/test/kotlin/net/corda/node/messaging/InMemoryMessagingTests.kt @@ -18,7 +18,7 @@ class InMemoryMessagingTests { @Before fun setUp() { - mockNet = InternalMockNetwork(emptyList()) + mockNet = InternalMockNetwork() } @After diff --git a/node/src/test/kotlin/net/corda/node/services/FinalityHandlerTest.kt b/node/src/test/kotlin/net/corda/node/services/FinalityHandlerTest.kt index 8f4dfc408e..fc07181b00 100644 --- a/node/src/test/kotlin/net/corda/node/services/FinalityHandlerTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/FinalityHandlerTest.kt @@ -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, diff --git a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt index 7ca6477ebc..de6c357ff2 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt @@ -18,7 +18,7 @@ import kotlin.test.assertNotNull import kotlin.test.assertNull class NetworkMapCacheTest { - private val mockNet = InternalMockNetwork(emptyList()) + private val mockNet = InternalMockNetwork() @After fun teardown() { diff --git a/node/src/test/kotlin/net/corda/node/services/schema/NodeSchemaServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/schema/NodeSchemaServiceTest.kt index 696353ba3d..0a86aad1da 100644 --- a/node/src/test/kotlin/net/corda/node/services/schema/NodeSchemaServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/schema/NodeSchemaServiceTest.kt @@ -40,7 +40,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 @@ -52,7 +52,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 diff --git a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/node/internal/InternalMockNetworkIntegrationTests.kt b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/node/internal/InternalMockNetworkIntegrationTests.kt index b630143a71..2ae8748215 100644 --- a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/node/internal/InternalMockNetworkIntegrationTests.kt +++ b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/node/internal/InternalMockNetworkIntegrationTests.kt @@ -10,7 +10,7 @@ class InternalMockNetworkIntegrationTests { companion object { @JvmStatic fun main(args: Array) { - InternalMockNetwork(emptyList()).run { + InternalMockNetwork().run { repeat(2) { createNode() } runNetwork() stopNodes() diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt index 2db5bd39cd..5251a29c57 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt @@ -97,7 +97,7 @@ data class InternalMockNodeParameters( ) } -open class InternalMockNetwork(private val cordappPackages: List, +open class InternalMockNetwork(private val cordappPackages: List = emptyList(), defaultParameters: MockNetworkParameters = MockNetworkParameters(), val networkSendManuallyPumped: Boolean = defaultParameters.networkSendManuallyPumped, val threadPerNode: Boolean = defaultParameters.threadPerNode, diff --git a/testing/node-driver/src/test/kotlin/net/corda/testing/node/internal/InternalMockNetworkTests.kt b/testing/node-driver/src/test/kotlin/net/corda/testing/node/internal/InternalMockNetworkTests.kt index fe2780977a..4ca0c97154 100644 --- a/testing/node-driver/src/test/kotlin/net/corda/testing/node/internal/InternalMockNetworkTests.kt +++ b/testing/node-driver/src/test/kotlin/net/corda/testing/node/internal/InternalMockNetworkTests.kt @@ -9,7 +9,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) From b275f4349a8909e80cfe438ab78a83e7742c738d Mon Sep 17 00:00:00 2001 From: Katarzyna Streich Date: Thu, 12 Jul 2018 13:40:33 +0100 Subject: [PATCH 05/21] ENT-1968: Add RPC for refreshing network map cache (#3552) ENT-1968: Add RPC for refreshing network map cache --- .../net/corda/core/messaging/CordaRPCOps.kt | 21 ++++++++++++++++--- .../corda/node/internal/CordaRPCOpsImpl.kt | 13 ++++++++++++ .../ExceptionSerialisingRpcOpsProxy.kt | 10 ++++++--- .../services/network/NetworkMapUpdater.kt | 6 ++++-- 4 files changed, 42 insertions(+), 8 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt b/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt index 6844b95ca6..f59aa49a40 100644 --- a/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt +++ b/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt @@ -34,13 +34,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 'context'") val initiator: FlowInitiator, /** A [DataFeed] of the current progress step as a human readable string, and updates to that string. */ @@ -368,9 +368,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. diff --git a/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt b/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt index e1c9b2dd33..fcc6c7eafe 100644 --- a/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt +++ b/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt @@ -1,6 +1,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 @@ -48,6 +49,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 @@ -232,6 +234,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 vaultQuery(contractStateType: Class): Vault.Page { return vaultQueryBy(QueryCriteria.VaultQueryCriteria(), PageSpecification(), Sort(emptySet()), contractStateType) } diff --git a/node/src/main/kotlin/net/corda/node/internal/rpc/proxies/ExceptionSerialisingRpcOpsProxy.kt b/node/src/main/kotlin/net/corda/node/internal/rpc/proxies/ExceptionSerialisingRpcOpsProxy.kt index 7a35fa56c7..0b1e361db2 100644 --- a/node/src/main/kotlin/net/corda/node/internal/rpc/proxies/ExceptionSerialisingRpcOpsProxy.kt +++ b/node/src/main/kotlin/net/corda/node/internal/rpc/proxies/ExceptionSerialisingRpcOpsProxy.kt @@ -36,8 +36,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) } } @@ -80,7 +78,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() result.setCause(null) diff --git a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapUpdater.kt b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapUpdater.kt index d06abce27a..d4f1a12fc7 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapUpdater.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapUpdater.kt @@ -1,6 +1,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 @@ -86,7 +87,7 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal, 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 @@ -97,7 +98,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 { From 12174fdaae9fa66fdd78ce4a3e31050a05268fce Mon Sep 17 00:00:00 2001 From: Thomas Schroeter Date: Thu, 12 Jul 2018 14:39:17 +0100 Subject: [PATCH 06/21] Add jib gradle plugin to node (#3571) * Add jib gradle plugin to node Available tasks: ./gradlew node:jibExportDockerContext ./gradlew node:jib --image --- docs/source/building-container-images.rst | 51 +++++++++++++++++++++++ docs/source/index.rst | 3 +- node/build.gradle | 28 +++++++++---- 3 files changed, 73 insertions(+), 9 deletions(-) create mode 100644 docs/source/building-container-images.rst diff --git a/docs/source/building-container-images.rst b/docs/source/building-container-images.rst new file mode 100644 index 0000000000..0257c18592 --- /dev/null +++ b/docs/source/building-container-images.rst @@ -0,0 +1,51 @@ +========================= +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 `_ 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 /: + +If you prefer building to a Docker deamon you can use + +.. sourcecode:: shell + + ./gradlew node:jibDockerBuild --image /: + +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. + +:: + + . + ├── 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 diff --git a/docs/source/index.rst b/docs/source/index.rst index 1f82a81a6e..1ba5e5757b 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -87,4 +87,5 @@ We look forward to seeing what you can do with Corda! release-process-index.rst corda-repo-layout.rst deterministic-modules.rst - building-the-docs.rst \ No newline at end of file + building-the-docs.rst + building-container-images.rst diff --git a/node/build.gradle b/node/build.gradle index 2a0aabcd4d..4a255e1e27 100644 --- a/node/build.gradle +++ b/node/build.gradle @@ -1,3 +1,15 @@ +// 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 @@ -9,14 +21,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 @@ -41,6 +45,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 { @@ -62,6 +72,8 @@ dependencies { compile project(':tools:shell') 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}" From 7f42a9b48c17792ce84aac09275ca52208b042bf Mon Sep 17 00:00:00 2001 From: Chris Rankin Date: Thu, 12 Jul 2018 14:52:44 +0100 Subject: [PATCH 07/21] ENT-1565: Remove Guava code from core. (#3569) Remove Guava code from core. Note that forcing this project to use Guava `$guava_version` throughout breaks SimmValuationTest. --- core-deterministic/build.gradle | 3 --- .../net/corda/core/identity/CordaX500Name.kt | 3 +-- .../net/corda/core/internal/InternalUtils.kt | 16 +++++++++++----- .../net/corda/core/internal/InternalUtilsTest.kt | 15 +++++++++++++++ serialization-deterministic/build.gradle | 2 +- 5 files changed, 28 insertions(+), 11 deletions(-) diff --git a/core-deterministic/build.gradle b/core-deterministic/build.gradle index b3f4718c50..508ecda18b 100644 --- a/core-deterministic/build.gradle +++ b/core-deterministic/build.gradle @@ -17,8 +17,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. @@ -28,7 +26,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" } diff --git a/core/src/main/kotlin/net/corda/core/identity/CordaX500Name.kt b/core/src/main/kotlin/net/corda/core/identity/CordaX500Name.kt index 24a3f97b7c..213e5670aa 100644 --- a/core/src/main/kotlin/net/corda/core/identity/CordaX500Name.kt +++ b/core/src/main/kotlin/net/corda/core/identity/CordaX500Name.kt @@ -1,6 +1,5 @@ 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 @@ -79,7 +78,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 = ImmutableSet.copyOf(Locale.getISOCountries() + unspecifiedCountry) + private val countryCodes: Set = setOf(*Locale.getISOCountries(), unspecifiedCountry) @JvmStatic fun build(principal: X500Principal): CordaX500Name { diff --git a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt index 778b4dfc11..6a53f25755 100644 --- a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt @@ -2,8 +2,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 @@ -43,6 +41,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.* @@ -132,9 +131,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()) } } diff --git a/core/src/test/kotlin/net/corda/core/internal/InternalUtilsTest.kt b/core/src/test/kotlin/net/corda/core/internal/InternalUtilsTest.kt index 21f89c34af..8ad6af291e 100644 --- a/core/src/test/kotlin/net/corda/core/internal/InternalUtilsTest.kt +++ b/core/src/test/kotlin/net/corda/core/internal/InternalUtilsTest.kt @@ -1,9 +1,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 @@ -99,6 +101,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 diff --git a/serialization-deterministic/build.gradle b/serialization-deterministic/build.gradle index c0c8ccb600..c19e128a46 100644 --- a/serialization-deterministic/build.gradle +++ b/serialization-deterministic/build.gradle @@ -17,13 +17,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 { From ab08ce21f471695ad4d81d42e1ebe4a870f6e3be Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Thu, 12 Jul 2018 15:22:31 +0100 Subject: [PATCH 08/21] Made NodeUnloadHandlerTests a unit test by using mock node (#3572) --- .../node/internal}/NodeUnloadHandlerTests.kt | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) rename node/src/{integration-test/kotlin/net/corda/node => test/kotlin/net/corda/node/internal}/NodeUnloadHandlerTests.kt (55%) diff --git a/node/src/integration-test/kotlin/net/corda/node/NodeUnloadHandlerTests.kt b/node/src/test/kotlin/net/corda/node/internal/NodeUnloadHandlerTests.kt similarity index 55% rename from node/src/integration-test/kotlin/net/corda/node/NodeUnloadHandlerTests.kt rename to node/src/test/kotlin/net/corda/node/internal/NodeUnloadHandlerTests.kt index 0efbcf12a1..295c6ede59 100644 --- a/node/src/integration-test/kotlin/net/corda/node/NodeUnloadHandlerTests.kt +++ b/node/src/test/kotlin/net/corda/node/internal/NodeUnloadHandlerTests.kt @@ -1,13 +1,12 @@ -package net.corda.node +package net.corda.node.internal +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.driver.DriverParameters -import net.corda.testing.driver.driver +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 @@ -15,16 +14,23 @@ import java.util.concurrent.TimeUnit class NodeUnloadHandlerTests { companion object { - 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") @@ -36,11 +42,12 @@ class NodeUnloadHandlerTests { init { serviceHub.registerUnloadHandler(this::shutdown) + registerLatch.countDown() } private fun shutdown() { log.info("shutting down") - latch.countDown() + shutdownLatch.countDown() } } } From ae7e8a29c7d9cf2ed5f0287723db87a7d1a0bb00 Mon Sep 17 00:00:00 2001 From: Thomas Schroeter Date: Thu, 12 Jul 2018 16:20:37 +0100 Subject: [PATCH 09/21] Don't use grgit to enable git worktree (#3573) We might also include a check if the current repo is clean, e.g. by running `git diff --exit-code` and checking the return value. --- build.gradle | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 1c69429c03..25bbc7f46b 100644 --- a/build.gradle +++ b/build.gradle @@ -102,7 +102,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" @@ -117,7 +116,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' From d8cd83bf4fe3e8d5963f4e38ef5228a95e3f1067 Mon Sep 17 00:00:00 2001 From: Joel Dudley Date: Thu, 12 Jul 2018 17:34:13 +0100 Subject: [PATCH 10/21] Removes duplicated instructions for running example CorDapp. (#3579) --- docs/source/getting-set-up.rst | 81 ++++------------------------------ 1 file changed, 8 insertions(+), 73 deletions(-) diff --git a/docs/source/getting-set-up.rst b/docs/source/getting-set-up.rst index 485b15c286..615849d5b6 100644 --- a/docs/source/getting-set-up.rst +++ b/docs/source/getting-set-up.rst @@ -26,7 +26,7 @@ Please note: `getting started guide `_, and a series of `Kotlin Koans `_. -* IntelliJ IDEA is recommended due to the strength of its Kotlin integration. +* IntelliJ IDEA is recommended due to the strength of its Kotlin integration Following these software recommendations will minimize the number of errors you encounter, and make it easier for others to provide support. However, if you do use other tools, we'd be interested to hear about any issues that arise. @@ -73,39 +73,6 @@ IntelliJ 2. Download and run the executable to install IntelliJ Community Edition (use the default settings) 3. Ensure the Kotlin plugin in Intellij is updated to version |kotlin_version| -Download a sample project -^^^^^^^^^^^^^^^^^^^^^^^^^ -1. Open a command prompt -2. Clone the ``cordapp-example`` repo by running ``git clone https://github.com/corda/cordapp-example`` -3. Move into the ``cordapp-example`` folder by running ``cd cordapp-example`` - -Run from the command prompt -^^^^^^^^^^^^^^^^^^^^^^^^^^^ -1. From the ``cordapp-example`` folder, deploy the nodes by running ``gradlew deployNodes`` -2. Start the nodes by running ``call kotlin-source/build/nodes/runnodes.bat`` -3. Wait until all the terminal windows display either "Webserver started up in XX.X sec" or "Node for "NodeC" started up and registered in XX.XX sec" -4. Test the CorDapp is running correctly by visiting the front end at http://localhost:10009/web/example/ - -Run from IntelliJ -^^^^^^^^^^^^^^^^^ -1. Open IntelliJ Community Edition -2. On the splash screen, click ``Open`` (do **not** click ``Import Project``) and select the ``cordapp-example`` folder - -.. warning:: If you click ``Import Project`` instead of ``Open``, the project's run configurations will be erased! - -3. Once the project is open, click ``File``, then ``Project Structure``. Under ``Project SDK:``, set the project SDK by - clicking ``New...``, clicking ``JDK``, and navigating to ``C:\\Program Files\\Java\\jdk1.8.0_XXX`` (where ``XXX`` is - the latest minor version number). Click ``OK`` -4. Again under ``File`` then ``Project Structure``, select ``Modules``. Click ``+``, then ``Import Module``, then select - the ``cordapp-example`` folder and click ``Open``. Choose to ``Import module from external model``, select - ``Gradle``, click ``Next`` then ``Finish`` (leaving the defaults) and ``OK`` -5. Wait for the indexing to finish (a progress bar will display at the bottom-right of the IntelliJ window until indexing - is complete) -6. At the top-right of the screen, to the left of the green ``play`` arrow, you should see a dropdown. In that - dropdown, select ``Run Example Cordapp - Kotlin`` and click the green ``play`` arrow. -7. Wait until the run windows displays the message ``Webserver started up in XX.X sec`` -8. Test the CorDapp is running correctly by visiting the front end at `http://localhost:10009/web/example/ - .. _mac-label: Mac @@ -128,48 +95,16 @@ IntelliJ 2. Download and run the executable to install IntelliJ Community Edition (use the default settings) 3. Ensure the Kotlin plugin in Intellij is updated to version |kotlin_version| -Download a sample project -^^^^^^^^^^^^^^^^^^^^^^^^^ -1. Open a terminal -2. Clone the ``cordapp-example`` repo by running ``git clone https://github.com/corda/cordapp-example`` -3. Move into the ``cordapp-example`` folder by running ``cd cordapp-example`` - -Run from the terminal -^^^^^^^^^^^^^^^^^^^^^ -1. From the ``cordapp-example`` folder, deploy the nodes by running ``./gradlew deployNodes`` -2. Start the nodes by running ``kotlin-source/build/nodes/runnodes``. Do not click while 7 additional terminal windows start up. -3. Wait until all the terminal windows display either "Webserver started up in XX.X sec" or "Node for "NodeC" started up and registered in XX.XX sec" -4. Test the CorDapp is running correctly by visiting the front end at http://localhost:10009/web/example/ - -Run from IntelliJ -^^^^^^^^^^^^^^^^^ -1. Open IntelliJ Community Edition -2. On the splash screen, click ``Open`` (do **not** click ``Import Project``) and select the ``cordapp-example`` folder - -.. warning:: If you click ``Import Project`` instead of ``Open``, the project's run configurations will be erased! - -3. Once the project is open, click ``File``, then ``Project Structure``. Under ``Project SDK:``, set the project SDK by - clicking ``New...``, clicking ``JDK``, and navigating to ``C:\\Program Files\\Java\\jdk1.8.0_XXX`` (where ``XXX`` is - the latest minor version number). Click ``OK`` -4. Again under ``File`` then ``Project Structure``, select ``Modules``. Click ``+``, then ``Import Module``, then select - the ``cordapp-example`` folder and click ``Open``. Choose to ``Import module from external model``, select - ``Gradle``, click ``Next`` then ``Finish`` (leaving the defaults) and ``OK`` -5. Wait for the indexing to finish (a progress bar will display at the bottom-right of the IntelliJ window until indexing - is complete) -6. At the top-right of the screen, to the left of the green ``play`` arrow, you should see a dropdown. In that - dropdown, select ``Run Example Cordapp - Kotlin`` and click the green ``play`` arrow. -7. Wait until the run windows displays the message ``Webserver started up in XX.X sec`` -8. Test the CorDapp is running correctly by visiting the front end at http://localhost:10009/web/example/ - Next steps ---------- -First, explore the example CorDapp you just ran :doc:`here `. +First, run the :doc:`example CorDapp `. -Next, read through :doc:`Corda Key Concepts ` to understand how Corda works. +Next, read through the :doc:`Corda 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 `. You may want to refer to the API documentation, the -:doc:`flow cookbook ` and the `samples `_ along the way. +:doc:`Hello, World tutorial `. You may want to refer to the +:doc:`API documentation `, the :doc:`flow cookbook ` and the +`samples `_ along the way. -If you encounter any issues, please see the :doc:`troubleshooting` page, or ask on -`Stack Overflow `_ or via `our Slack channels `_. +If you encounter any issues, please ask on `Stack Overflow `_ or via +`our Slack channels `_. From 21754e4323fde6cd755e248e43853b7f842506d7 Mon Sep 17 00:00:00 2001 From: Joel Dudley Date: Thu, 12 Jul 2018 17:40:10 +0100 Subject: [PATCH 11/21] Removes reference to Discourse. (#3581) --- docs/source/getting-set-up.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/getting-set-up.rst b/docs/source/getting-set-up.rst index 615849d5b6..069dbce848 100644 --- a/docs/source/getting-set-up.rst +++ b/docs/source/getting-set-up.rst @@ -33,9 +33,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 `_, -`Stack Overflow `_ or the `forums `_. +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 `_ or via +`our Slack channels `_. The set-up instructions are available for the following platforms: From 20c03d219692e0bf425ad3e633833aed2dc3b756 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Wed, 4 Jul 2018 20:32:58 +0200 Subject: [PATCH 12/21] Blob inspector: add support for hex and base64 inputs. Also rename the file to make it easier to navigate to it in IntelliJ by keyboard. --- docs/source/blob-inspector.rst | 43 ++++++++++---- tools/blobinspector/build.gradle | 1 + .../net/corda/blobinspector/BlobInspector.kt | 59 ++++++++++++++----- 3 files changed, 78 insertions(+), 25 deletions(-) diff --git a/docs/source/blob-inspector.rst b/docs/source/blob-inspector.rst index 1742ee8771..02d8506849 100644 --- a/docs/source/blob-inspector.rst +++ b/docs/source/blob-inspector.rst @@ -8,25 +8,48 @@ by allowing the contents of a binary blob file (or URL end-point) to be output i The latest version of the tool can be downloaded from `here `_. -To run simply pass in the file or URL as the first parameter: +To run simply pass in the file or URL as the first parameter:: -``java -jar blob-inspector.jar `` + java -jar blob-inspector.jar Use the ``--help`` flag for a full list of command line options. When inspecting your custom data structures, there's no need to include the jars containing the class definitions for them -in the classpath. The blob inspector (or rather the serialization framework) is able to synthesis any classes found in the +in the classpath. The blob inspector (or rather the serialization framework) is able to synthesize any classes found in the blob that aren't 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 `_ +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** :: diff --git a/tools/blobinspector/build.gradle b/tools/blobinspector/build.gradle index 4cd7ef4386..350ba56760 100644 --- a/tools/blobinspector/build.gradle +++ b/tools/blobinspector/build.gradle @@ -6,6 +6,7 @@ apply plugin: 'com.jfrog.artifactory' dependencies { compile project(':client:jackson') compile "info.picocli:picocli:$picocli_version" + compile "com.google.guava:guava:$guava_version" compile "org.slf4j:jul-to-slf4j:$slf4j_version" compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version" compile "com.jcabi:jcabi-manifests:$jcabi_manifests_version" diff --git a/tools/blobinspector/src/main/kotlin/net/corda/blobinspector/BlobInspector.kt b/tools/blobinspector/src/main/kotlin/net/corda/blobinspector/BlobInspector.kt index 389688f766..a3709a2ec6 100644 --- a/tools/blobinspector/src/main/kotlin/net/corda/blobinspector/BlobInspector.kt +++ b/tools/blobinspector/src/main/kotlin/net/corda/blobinspector/BlobInspector.kt @@ -2,6 +2,7 @@ package net.corda.blobinspector import com.fasterxml.jackson.core.JsonFactory import com.fasterxml.jackson.dataformat.yaml.YAMLFactory +import com.google.common.io.BaseEncoding import com.jcabi.manifests.Manifests import net.corda.client.jackson.JacksonSupport import net.corda.core.internal.isRegularFile @@ -43,18 +44,21 @@ fun main(args: Array) { } @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 BlobInspector : Runnable { @Parameters(index = "0", paramLabel = "SOURCE", description = ["URL or file path to the blob"], converter = [SourceConverter::class]) - var source: URL? = null + private 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"]) @@ -73,15 +77,12 @@ class BlobInspector : Runnable { 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) + val envelope = DeserializationInput.getEnvelope(bytes.sequence()) out.println(envelope.schema) out.println() out.println(envelope.transformsSchema) @@ -89,8 +90,8 @@ class BlobInspector : Runnable { } val factory = when (formatType) { - FormatType.YAML -> YAMLFactory() - FormatType.JSON -> JsonFactory() + OutputFormatType.YAML -> YAMLFactory() + OutputFormatType.JSON -> JsonFactory() } val mapper = JacksonSupport.createNonRpcMapper(factory, fullParties) @@ -105,6 +106,32 @@ class BlobInspector : Runnable { } } + private fun parseToBinaryRelaxed(format: InputFormatType, inputBytes: ByteArray): ByteArray? { + // Try the format the user gave us first, then try the others. + return parseToBinary(format, inputBytes) ?: parseToBinary(InputFormatType.HEX, inputBytes) + ?: parseToBinary(InputFormatType.BASE64, inputBytes) ?: parseToBinary(InputFormatType.BINARY, inputBytes) + } + + private fun parseToBinary(format: InputFormatType, inputBytes: ByteArray): ByteArray? { + try { + val bytes = when (format) { + InputFormatType.BINARY -> inputBytes + InputFormatType.HEX -> BaseEncoding.base16().decode(String(inputBytes).trim().toUpperCase()) + InputFormatType.BASE64 -> BaseEncoding.base64().decode(String(inputBytes).trim()) + } + 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( @@ -121,6 +148,7 @@ private object AMQPInspectorSerializationScheme : AbstractAMQPSerializationSchem override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean { return magic == amqpMagic } + override fun rpcClientSerializerFactory(context: SerializationContext) = throw UnsupportedOperationException() override fun rpcServerSerializerFactory(context: SerializationContext) = throw UnsupportedOperationException() } @@ -146,5 +174,6 @@ private class CordaVersionProvider : IVersionProvider { } } -private enum class FormatType { YAML, JSON } +private enum class OutputFormatType { YAML, JSON } +private enum class InputFormatType { BINARY, HEX, BASE64 } From 493d4d5890615a7a28e60ba5cc4bb91a06208510 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Wed, 4 Jul 2018 20:33:13 +0200 Subject: [PATCH 13/21] Minor: slightly improve an exception message with clarifying quotes --- .../test/kotlin/net/corda/client/rpc/RPCFailureTests.kt | 8 ++++---- .../serialization/internal/amqp/SerializationHelper.kt | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCFailureTests.kt b/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCFailureTests.kt index 2ab308af1d..7806bc9b40 100644 --- a/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCFailureTests.kt +++ b/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCFailureTests.kt @@ -3,7 +3,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 @@ -60,10 +60,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 @@ -71,6 +71,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.") } } diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializationHelper.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializationHelper.kt index 4a8605d295..f9f5880053 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializationHelper.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializationHelper.kt @@ -524,7 +524,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.") } } From db1e33c820eb45bed57a6586f504a570eaa25dc6 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Wed, 4 Jul 2018 20:33:41 +0200 Subject: [PATCH 14/21] Docs: clean up the toctree a little bit w.r.t. serialisation and tools --- docs/source/building-a-cordapp-index.rst | 1 - docs/source/serialization-index.rst | 3 ++- docs/source/tools-index.rst | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/source/building-a-cordapp-index.rst b/docs/source/building-a-cordapp-index.rst index 925d1cc535..ad7b5457e5 100644 --- a/docs/source/building-a-cordapp-index.rst +++ b/docs/source/building-a-cordapp-index.rst @@ -12,7 +12,6 @@ CorDapps cordapp-build-systems building-against-master corda-api - serialization-index secure-coding-guidelines flow-cookbook vault diff --git a/docs/source/serialization-index.rst b/docs/source/serialization-index.rst index f59f687048..a01f6b15db 100644 --- a/docs/source/serialization-index.rst +++ b/docs/source/serialization-index.rst @@ -8,4 +8,5 @@ Serialization serialization.rst cordapp-custom-serializers serialization-default-evolution.rst - serialization-enum-evolution.rst \ No newline at end of file + serialization-enum-evolution.rst + blob-inspector \ No newline at end of file diff --git a/docs/source/tools-index.rst b/docs/source/tools-index.rst index 2285b38d9d..02d2c9c5fb 100644 --- a/docs/source/tools-index.rst +++ b/docs/source/tools-index.rst @@ -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 From 309a5fd9eca21abd726ec2ed3917a5faa88431c4 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Wed, 11 Jul 2018 18:46:02 +0200 Subject: [PATCH 15/21] Address review comments. --- tools/blobinspector/build.gradle | 1 - .../main/kotlin/net/corda/blobinspector/BlobInspector.kt | 7 ++++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/blobinspector/build.gradle b/tools/blobinspector/build.gradle index 350ba56760..4cd7ef4386 100644 --- a/tools/blobinspector/build.gradle +++ b/tools/blobinspector/build.gradle @@ -6,7 +6,6 @@ apply plugin: 'com.jfrog.artifactory' dependencies { compile project(':client:jackson') compile "info.picocli:picocli:$picocli_version" - compile "com.google.guava:guava:$guava_version" compile "org.slf4j:jul-to-slf4j:$slf4j_version" compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version" compile "com.jcabi:jcabi-manifests:$jcabi_manifests_version" diff --git a/tools/blobinspector/src/main/kotlin/net/corda/blobinspector/BlobInspector.kt b/tools/blobinspector/src/main/kotlin/net/corda/blobinspector/BlobInspector.kt index a3709a2ec6..0981fd3200 100644 --- a/tools/blobinspector/src/main/kotlin/net/corda/blobinspector/BlobInspector.kt +++ b/tools/blobinspector/src/main/kotlin/net/corda/blobinspector/BlobInspector.kt @@ -2,7 +2,6 @@ package net.corda.blobinspector import com.fasterxml.jackson.core.JsonFactory import com.fasterxml.jackson.dataformat.yaml.YAMLFactory -import com.google.common.io.BaseEncoding import com.jcabi.manifests.Manifests import net.corda.client.jackson.JacksonSupport import net.corda.core.internal.isRegularFile @@ -12,6 +11,8 @@ 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 @@ -116,8 +117,8 @@ class BlobInspector : Runnable { try { val bytes = when (format) { InputFormatType.BINARY -> inputBytes - InputFormatType.HEX -> BaseEncoding.base16().decode(String(inputBytes).trim().toUpperCase()) - InputFormatType.BASE64 -> BaseEncoding.base64().decode(String(inputBytes).trim()) + InputFormatType.HEX -> String(inputBytes).trim().toUpperCase().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)) { From df84dd5a33f4394adfe6f6340e1dc56f41146f59 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Thu, 12 Jul 2018 16:20:25 +0200 Subject: [PATCH 16/21] Address review comments. --- .../kotlin/net/corda/blobinspector/BlobInspector.kt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tools/blobinspector/src/main/kotlin/net/corda/blobinspector/BlobInspector.kt b/tools/blobinspector/src/main/kotlin/net/corda/blobinspector/BlobInspector.kt index 0981fd3200..94e1a45747 100644 --- a/tools/blobinspector/src/main/kotlin/net/corda/blobinspector/BlobInspector.kt +++ b/tools/blobinspector/src/main/kotlin/net/corda/blobinspector/BlobInspector.kt @@ -53,7 +53,7 @@ fun main(args: Array) { ) 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: OutputFormatType = OutputFormatType.YAML @@ -109,15 +109,19 @@ class BlobInspector : Runnable { private fun parseToBinaryRelaxed(format: InputFormatType, inputBytes: ByteArray): ByteArray? { // Try the format the user gave us first, then try the others. - return parseToBinary(format, inputBytes) ?: parseToBinary(InputFormatType.HEX, inputBytes) - ?: parseToBinary(InputFormatType.BASE64, inputBytes) ?: parseToBinary(InputFormatType.BINARY, inputBytes) + //@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().toUpperCase().hexToByteArray() + InputFormatType.HEX -> String(inputBytes).trim().hexToByteArray() InputFormatType.BASE64 -> String(inputBytes).trim().base64ToByteArray() } require(bytes.size > amqpMagic.size) { "Insufficient bytes for AMQP blob" } From 2db5e0e158dc1b03859f8effbd1e21e798b02d5b Mon Sep 17 00:00:00 2001 From: Joel Dudley Date: Thu, 12 Jul 2018 17:48:09 +0100 Subject: [PATCH 17/21] Docs were missing final brace (#3551) --- .../main/kotlin/net/corda/testing/contracts/DummyContractV2.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyContractV2.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyContractV2.kt index fa079f0e43..98254cbcc7 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyContractV2.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyContractV2.kt @@ -40,5 +40,5 @@ class DummyContractV2 : UpgradedContractWithLegacyConstraint Date: Thu, 12 Jul 2018 17:52:16 +0100 Subject: [PATCH 18/21] Stray full-stop --- docs/source/getting-set-up.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/getting-set-up.rst b/docs/source/getting-set-up.rst index 069dbce848..8fa4f8060a 100644 --- a/docs/source/getting-set-up.rst +++ b/docs/source/getting-set-up.rst @@ -24,7 +24,7 @@ Please note: `here `_. If you're unfamiliar with Kotlin, there is an official `getting started guide `_, and a series of - `Kotlin Koans `_. + `Kotlin Koans `_ * IntelliJ IDEA is recommended due to the strength of its Kotlin integration From f7a4c196560a26a2badaf67159a21dc185373151 Mon Sep 17 00:00:00 2001 From: Tommy Lillehagen Date: Fri, 13 Jul 2018 09:40:02 +0100 Subject: [PATCH 19/21] CORDA-1803 - Fix variable replacement (#3585) --- docs/source/conf.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/source/conf.py b/docs/source/conf.py index 3f185be179..e604d66e91 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -66,6 +66,12 @@ html_context = {'version': 'Master'} # Properties. kotlin_version = '1.2.51' +rst_epilog = """ +.. |kotlin_version| replace:: {kotlin_version} +""".format( + kotlin_version = kotlin_version, +) + # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # From e90d7c69c56e8df1b6c1e6c004e104195335535d Mon Sep 17 00:00:00 2001 From: Thomas Schroeter Date: Fri, 13 Jul 2018 09:58:05 +0100 Subject: [PATCH 20/21] Minor: doc fixes (#3584) --- docs/source/building-container-images.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/source/building-container-images.rst b/docs/source/building-container-images.rst index 0257c18592..58e02b3580 100644 --- a/docs/source/building-container-images.rst +++ b/docs/source/building-container-images.rst @@ -37,6 +37,7 @@ otherwise a new directory will be created by Docker. :: . + ├── additional-node-infos ├── certificates ├── config │   └── node.conf @@ -48,4 +49,6 @@ otherwise a new directory will be created by Docker. 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}/persistence.mv.db:/persistence.mv.db \ + -v ${PWD}/additional-node-infos:/additional-node-infos \ + /: From ed25d8f1be70e766d8a3c572fdcb064cc0ee505d Mon Sep 17 00:00:00 2001 From: Rick Parker Date: Fri, 13 Jul 2018 10:10:26 +0100 Subject: [PATCH 21/21] ENT-1565 Upgrade Artemis version to latest. (#3557) * ENT-1565 Upgrade Artemis version to latest. * Fix compiler errors. * Add to changelog * Additional commentary on Proton-J version --- build.gradle | 4 ++-- docs/source/changelog.rst | 2 ++ .../corda/nodeapi/internal/MessageSizeChecksInterceptor.kt | 2 +- serialization/build.gradle | 2 ++ 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 25bbc7f46b..d738a835b4 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,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' @@ -64,7 +64,7 @@ buildscript { ext.proguard_version = constants.getProperty('proguardVersion') ext.jsch_version = '0.1.54' ext.commons_cli_version = '1.4' - ext.protonj_version = '0.27.1' + 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' diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 1048335bc6..aa92974fb7 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -170,6 +170,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 diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/MessageSizeChecksInterceptor.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/MessageSizeChecksInterceptor.kt index 854ae0109f..5b813bff0a 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/MessageSizeChecksInterceptor.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/MessageSizeChecksInterceptor.kt @@ -22,7 +22,7 @@ class ArtemisMessageSizeChecksInterceptor(maxMessageSize: Int) : MessageSizeChec } class AmqpMessageSizeChecksInterceptor(maxMessageSize: Int) : MessageSizeChecksInterceptor(maxMessageSize), AmqpInterceptor { - override fun getMessageSize(packet: AMQPMessage?): Int? = packet?.length + override fun getMessageSize(packet: AMQPMessage?): Int? = packet?.encodeSize } /** diff --git a/serialization/build.gradle b/serialization/build.gradle index 48fa7dc6c0..eafc8cd155 100644 --- a/serialization/build.gradle +++ b/serialization/build.gradle @@ -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"