diff --git a/.ci/api-current.txt b/.ci/api-current.txt index da5e9001a0..7bb291f430 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -1378,7 +1378,6 @@ public @interface net.corda.core.flows.InitiatingFlow public (List, net.corda.core.crypto.SecureHash) @org.jetbrains.annotations.NotNull public final List getStatesToConsume() @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getTransactionId() - public final void verifySignature(net.corda.core.flows.NotarisationRequestSignature, net.corda.core.identity.Party) public static final net.corda.core.flows.NotarisationRequest$Companion Companion ## public static final class net.corda.core.flows.NotarisationRequest$Companion extends java.lang.Object @@ -1492,18 +1491,6 @@ public static final class net.corda.core.flows.NotaryFlow$Client$Companion exten @net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.NotaryFlow$Client$Companion$VALIDATING extends net.corda.core.utilities.ProgressTracker$Step public static final net.corda.core.flows.NotaryFlow$Client$Companion$VALIDATING INSTANCE ## -public abstract static class net.corda.core.flows.NotaryFlow$Service extends net.corda.core.flows.FlowLogic - public (net.corda.core.flows.FlowSession, net.corda.core.node.services.TrustedAuthorityNotaryService) - @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.Nullable public Void call() - @co.paralleluniverse.fibers.Suspendable protected final void checkNotary(net.corda.core.identity.Party) - @org.jetbrains.annotations.NotNull public final net.corda.core.flows.FlowSession getOtherSideSession() - @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.TrustedAuthorityNotaryService getService() - @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull protected abstract net.corda.core.flows.TransactionParts validateRequest(net.corda.core.flows.NotarisationPayload) -## -@net.corda.core.serialization.CordaSerializable public final class net.corda.core.flows.NotaryInternalException extends net.corda.core.flows.FlowException - public (net.corda.core.flows.NotaryError) - @org.jetbrains.annotations.NotNull public final net.corda.core.flows.NotaryError getError() -## public final class net.corda.core.flows.ReceiveStateAndRefFlow extends net.corda.core.flows.FlowLogic public (net.corda.core.flows.FlowSession) @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public List call() @@ -1583,21 +1570,6 @@ public static final class net.corda.core.flows.StateMachineRunId$Companion exten public (String) public (String, Throwable) ## -public final class net.corda.core.flows.TransactionParts extends java.lang.Object - public (net.corda.core.crypto.SecureHash, List, net.corda.core.contracts.TimeWindow, net.corda.core.identity.Party) - @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component1() - @org.jetbrains.annotations.NotNull public final List component2() - @org.jetbrains.annotations.Nullable public final net.corda.core.contracts.TimeWindow component3() - @org.jetbrains.annotations.Nullable public final net.corda.core.identity.Party component4() - @org.jetbrains.annotations.NotNull public final net.corda.core.flows.TransactionParts copy(net.corda.core.crypto.SecureHash, List, net.corda.core.contracts.TimeWindow, net.corda.core.identity.Party) - public boolean equals(Object) - @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getId() - @org.jetbrains.annotations.NotNull public final List getInputs() - @org.jetbrains.annotations.Nullable public final net.corda.core.identity.Party getNotary() - @org.jetbrains.annotations.Nullable public final net.corda.core.contracts.TimeWindow getTimestamp() - public int hashCode() - public String toString() -## @net.corda.core.serialization.CordaSerializable public final class net.corda.core.flows.UnexpectedFlowEndException extends net.corda.core.CordaRuntimeException implements net.corda.core.flows.IdentifiableException public (String) public (String, Throwable) @@ -2062,21 +2034,6 @@ public @interface net.corda.core.node.services.CordaService public abstract boolean isValidatingNotary(net.corda.core.identity.Party) @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.DataFeed track() ## -@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.node.services.NotaryService extends net.corda.core.serialization.SingletonSerializeAsToken - public () - @org.jetbrains.annotations.NotNull public abstract net.corda.core.flows.FlowLogic createServiceFlow(net.corda.core.flows.FlowSession) - @org.jetbrains.annotations.NotNull public abstract java.security.PublicKey getNotaryIdentityKey() - @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.ServiceHub getServices() - public abstract void start() - public abstract void stop() - public static final void validateTimeWindow(java.time.Clock, net.corda.core.contracts.TimeWindow) - public static final net.corda.core.node.services.NotaryService$Companion Companion - @org.jetbrains.annotations.NotNull public static final String ID_PREFIX = "corda.notary." -## -public static final class net.corda.core.node.services.NotaryService$Companion extends java.lang.Object - @kotlin.Deprecated @org.jetbrains.annotations.NotNull public final String constructId(boolean, boolean, boolean, boolean) - public final void validateTimeWindow(java.time.Clock, net.corda.core.contracts.TimeWindow) -## public abstract class net.corda.core.node.services.PartyInfo extends java.lang.Object @org.jetbrains.annotations.NotNull public abstract net.corda.core.identity.Party getParty() ## @@ -2121,48 +2078,6 @@ public final class net.corda.core.node.services.TimeWindowChecker extends java.l @net.corda.core.DoNotImplement public interface net.corda.core.node.services.TransactionVerifierService @org.jetbrains.annotations.NotNull public abstract net.corda.core.concurrent.CordaFuture verify(net.corda.core.transactions.LedgerTransaction) ## -@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.node.services.TrustedAuthorityNotaryService extends net.corda.core.node.services.NotaryService - public () - public final void commitInputStates(List, net.corda.core.crypto.SecureHash, net.corda.core.identity.Party, net.corda.core.flows.NotarisationRequestSignature, net.corda.core.contracts.TimeWindow) - @org.jetbrains.annotations.NotNull protected org.slf4j.Logger getLog() - @org.jetbrains.annotations.NotNull protected net.corda.core.node.services.TimeWindowChecker getTimeWindowChecker() - @org.jetbrains.annotations.NotNull protected abstract net.corda.core.node.services.UniquenessProvider getUniquenessProvider() - @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.TransactionSignature sign(net.corda.core.crypto.SecureHash) - @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.DigitalSignature$WithKey sign(byte[]) - public final void validateTimeWindow(net.corda.core.contracts.TimeWindow) - public static final net.corda.core.node.services.TrustedAuthorityNotaryService$Companion Companion -## -public static final class net.corda.core.node.services.TrustedAuthorityNotaryService$Companion extends java.lang.Object -## -@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.services.UniquenessException extends net.corda.core.CordaException - public (net.corda.core.node.services.UniquenessProvider$Conflict) - @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.UniquenessProvider$Conflict getError() -## -public interface net.corda.core.node.services.UniquenessProvider - public abstract void commit(List, net.corda.core.crypto.SecureHash, net.corda.core.identity.Party, net.corda.core.flows.NotarisationRequestSignature, net.corda.core.contracts.TimeWindow) -## -@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.UniquenessProvider$Conflict extends java.lang.Object - public (Map) - @org.jetbrains.annotations.NotNull public final Map component1() - @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.UniquenessProvider$Conflict copy(Map) - public boolean equals(Object) - @org.jetbrains.annotations.NotNull public final Map getStateHistory() - public int hashCode() - public String toString() -## -@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.UniquenessProvider$ConsumingTx extends java.lang.Object - public (net.corda.core.crypto.SecureHash, int, net.corda.core.identity.Party) - @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component1() - public final int component2() - @org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party component3() - @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.UniquenessProvider$ConsumingTx copy(net.corda.core.crypto.SecureHash, int, net.corda.core.identity.Party) - public boolean equals(Object) - @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getId() - public final int getInputIndex() - @org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party getRequestingParty() - public int hashCode() - public String toString() -## @net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.services.UnknownAnonymousPartyException extends net.corda.core.CordaException public (String) ## diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index f2195fa5c7..a715a3daf1 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,35 +1,51 @@ # List of Contributors -We'd like to thank the following people for contributing ideas to Corda, -either during architecture review sessions of the R3 Architecture Working Group, -or in design reviews since Corda has been open-sourced. Some people have moved to -a different organisation since their contribution. Please forgive any omissions, and -create a pull request, or email , if you wish to see -changes to this list. +We'd like to thank the following people for contributing to Corda, either by +contributing to the design of Corda during the architecture review sessions of the +R3 Architecture Working Group and during design reviews since Corda has been +open-sourced, or by contributing code via pull requests. Some people have +moved to a different organisation since their contribution. Please forgive any +omissions, and create a pull request, or email , if you wish to +see changes to this list. +* acetheultimate +* Adrian Flethcehr (TD) +* agoldvarg * Alberto Arri (R3) +* amiracam * Andras Slemmer (R3) * Andrius Dagys (R3) * Andrzej Cichocki (R3) * Andrzej Grzesik (R3) * Anthony Coates (Deutsche Bank) +* Anthony Keenan (R3) +* Anthony Woolley (Société Générale) * Anton Semenov (Commerzbank) * Antonio Cerrato (SEB) -* Anthony Woolley (Société Générale) -* Arnaud Stevens (Natixis) +* Antony Lewis (R3) +* anttiai * Arijit Das (Northern Trust) +* Arnaud Stevens (Natixis) * Arun Battu (BNY Mellon) * Austin Moothart (R3) +* balajimore * Barry Childe (HSBC) * Barry Flower (Westpac) +* Bart van den Bosch (KBC) +* Ben Wyeth (RBS) * Benjamin Abineri (R3) * Benoit Lafontaine (OCTO) * Berit Bourgonje (ING) +* BitcoinErrorLog * Bob Crozier (AIA) * Bogdan Paunescu (R3) +* C-Otto * Cais Manai (R3) * Carl Worrall (BCS) +* Carlos Kuchovsky (BBVA) +* Cédric Wahl (Société Générale) * Chaitanya Jadhav (HSBC) +* chalkido * Chris Akers (R3) * Chris Burlinchon (R3) * Chris Rankin (R3) @@ -41,14 +57,22 @@ changes to this list. * Clay Ratliff (Thoughtworks) * Clemens Wan (R3) * Clinton Alexander (R3) +* cncorda +* cyrsis * Daniel Roig (SEB) * Dave Hudson (R3) +* David John Grundy (Dankse Bank) * David Lee (BCS) +* Dirk Hermans (KBC) +* Edward Greenwood (State Street) * Farzad Pezeshkpour (RBS) +* fracting * Frederic Dalibard (Natixis) * Garrett Macey (Wells Fargo) +* gary-rowe * Gavin Thomas (R3) * George Marcel Smetana (Bradesco) +* George Smetana (Bradesco) * Giulio Katis (Westpac) * Giuseppe Cardone (Intesa Sanpaolo) * Guy Hochstetler (IBM) @@ -60,9 +84,11 @@ changes to this list. * James Brown (R3) * James Carlyle (R3) * Jared Harwayne-Gidansky (BNY Mellon) +* Jayavaradhan Sambedu (Société Générale) * Joel Dudley (R3) * Johan Hörmark (SEB) * Johann Palychata (BNP Paribas) +* johnnyychiu * Jonathan Sartin (R3) * Jose Coll (R3) * Jose Luu (Natixis) @@ -70,6 +96,7 @@ changes to this list. * Justin Chapman (Northern Trust) * Kai-Michael Schramm (Credit Suisse) * Karel Hajek (Barclays Capital) +* karnauskas * Kasia Streich (R3) * Kat Baker (R3) * Khaild Ahmed (Northern Trust) @@ -81,6 +108,7 @@ changes to this list. * Lucas Salmen (Itau) * Maksymillian Pawlak (R3) * Marek Scocovsky (ABSA) +* marekdapps * Mark Lauer (Westpac) * Mark Oldfield (R3) * Mark Raynes (Thomson Reuters) @@ -103,18 +131,28 @@ changes to this list. * Oscar Zibordi de Paiva (Bradesco) * Patrick Kuo (R3) * Pekka Kaipio (OP Financial) +* Phillip Griffin * Piotr Piskorski (Nordea) * Przemyslaw Bak (R3) +* quiark +* RangerOfFire +* renlulu * Rex Maudsley (Société Générale) +* Rhett Brewer (Goldman Sachs) +* Richard Crook (RBS) +* Richard Gendal Brown (R3) * Richard Green (R3) * Rick Parker (R3) -* Rhett Brewer (Goldman Sachs) * Roberto Karpinski (Bradesco) * Robin Green (CIBC) * Rodrigo Bueno (Itau) +* Rodrigo Gonçalves (Itau Unibanco) * Roger Willis (R3) * Ross Burnett (Macquarie) * Ross Nicoll (R3) +* Rui Hu (Nordea) +* s-matthew-english +* sadysnaat * Sajindra Jayasena (Deutsche Bank) * Saket Sharma (BNY Mellon) * Sam Chadwick (Thomson Reuters) @@ -123,16 +161,25 @@ changes to this list. * Shams Asari (R3) * Simon Taylor (Barclays) * Sofus Mortensen (Digital Asset Holdings) -* Szymon Sztuka (R3) * Stephen Lane-Smith (BMO) +* stevenroose +* Szymon Sztuka (R3) +* tb-pq +* Thiago Rafael Ferreira (Scorpius IT Solutions) * Thomas O'Donnell (Macquarie) * Thomas Schroeter (R3) -* Tom Menner (R3) -* Tudor Malene (R3) * Tim Swanson (R3) * Timothy Smith (Credit Suisse) +* Tom Menner (R3) +* tomconte * Tommy Lillehagen (R3) +* tomtau +* Tudor Malene (R3) +* varunkm +* verymahler * Viktor Kolomeyko (R3) +* Vipin Bharathan * Wawrzek Niewodniczanski (R3) * Wei Wu Zhang (Commonwealth Bank of Australia) -* Zabrina Smith (Northern Trust) \ No newline at end of file +* Zabrina Smith (Northern Trust) +* zorenmith (Northern Trust) diff --git a/build.gradle b/build.gradle index fd02b98d4d..d1aaa2a74a 100644 --- a/build.gradle +++ b/build.gradle @@ -265,6 +265,14 @@ allprojects { } configurations { + all { + resolutionStrategy { + // Force dependencies to use the same version of Kotlin as Corda. + force "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + force "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + force "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" + } + } compile { // We want to use SLF4J's version of these bindings: jcl-over-slf4j // Remove any transitive dependency on Apache's version. @@ -434,3 +442,8 @@ if(file('corda-docs-only-build').exists() || (System.getenv('CORDA_DOCS_ONLY_BUI } } } + +wrapper { + gradleVersion = "4.4.1" + distributionType = Wrapper.DistributionType.ALL +} diff --git a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt index 60beccd070..2f5c1f290e 100644 --- a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt +++ b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt @@ -28,7 +28,6 @@ import net.corda.finance.schemas.CashSchemaV1 import net.corda.node.internal.Node import net.corda.node.internal.StartedNode import net.corda.node.services.Permissions.Companion.all -import net.corda.nodeapi.exceptions.InternalNodeException import net.corda.testing.core.* import net.corda.testing.node.User import net.corda.testing.internal.IntegrationTestSchemas @@ -173,15 +172,6 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C println("Result: ${flowHandle.returnValue.getOrThrow()}") } - @Test - fun `sub-type of FlowException thrown by flow`() { - login(rpcUser.username, rpcUser.password) - val handle = connection!!.proxy.startFlow(::CashPaymentFlow, 100.DOLLARS, identity) - assertThatExceptionOfType(InternalNodeException::class.java).isThrownBy { - handle.returnValue.getOrThrow() - } - } - @Test fun `check basic flow has no progress`() { login(rpcUser.username, rpcUser.password) diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt index db45afe4fb..fb9cd9e887 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt @@ -10,8 +10,8 @@ package net.corda.client.rpc +import KryoClientSerializationScheme import net.corda.client.rpc.internal.CordaRPCClientConfigurationImpl -import net.corda.client.rpc.internal.KryoClientSerializationScheme import net.corda.client.rpc.internal.RPCClient import net.corda.core.context.Actor import net.corda.core.context.Trace diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClientProxyHandler.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClientProxyHandler.kt index 1e03203031..fb3aa356d3 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClientProxyHandler.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClientProxyHandler.kt @@ -24,6 +24,7 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder import net.corda.client.rpc.CordaRPCClientConfiguration import net.corda.client.rpc.RPCException import net.corda.client.rpc.RPCSinceVersion +import net.corda.client.rpc.internal.serialization.kryo.RpcClientObservableSerializer import net.corda.core.context.Actor import net.corda.core.context.Trace import net.corda.core.context.Trace.InvocationId @@ -557,62 +558,3 @@ data class ObservableContext( val hardReferenceStore: MutableSet> ) -/** - * A [Serializer] to deserialise Observables once the corresponding Kryo instance has been provided with an [ObservableContext]. - */ -object RpcClientObservableSerializer : Serializer>() { - private object RpcObservableContextKey - - fun createContext(serializationContext: SerializationContext, observableContext: ObservableContext): SerializationContext { - return serializationContext.withProperty(RpcObservableContextKey, observableContext) - } - - private fun pinInSubscriptions(observable: Observable, hardReferenceStore: MutableSet>): Observable { - val refCount = AtomicInteger(0) - return observable.doOnSubscribe { - if (refCount.getAndIncrement() == 0) { - require(hardReferenceStore.add(observable)) { "Reference store already contained reference $this on add" } - } - }.doOnUnsubscribe { - if (refCount.decrementAndGet() == 0) { - require(hardReferenceStore.remove(observable)) { "Reference store did not contain reference $this on remove" } - } - } - } - - override fun read(kryo: Kryo, input: Input, type: Class>): Observable { - val observableContext = kryo.context[RpcObservableContextKey] as ObservableContext - val observableId = input.readInvocationId() ?: throw IllegalStateException("Unable to read invocationId from Input.") - val observable = UnicastSubject.create>() - require(observableContext.observableMap.getIfPresent(observableId) == null) { - "Multiple Observables arrived with the same ID $observableId" - } - val rpcCallSite = getRpcCallSite(kryo, observableContext) - observableContext.observableMap.put(observableId, observable) - observableContext.callSiteMap?.put(observableId, rpcCallSite) - // We pin all Observables into a hard reference store (rooted in the RPC proxy) on subscription so that users - // don't need to store a reference to the Observables themselves. - return pinInSubscriptions(observable, observableContext.hardReferenceStore).doOnUnsubscribe { - // This causes Future completions to give warnings because the corresponding OnComplete sent from the server - // will arrive after the client unsubscribes from the observable and consequently invalidates the mapping. - // The unsubscribe is due to [ObservableToFuture]'s use of first(). - observableContext.observableMap.invalidate(observableId) - }.dematerialize() - } - - private fun Input.readInvocationId() : InvocationId? { - - val value = readString() ?: return null - val timestamp = readLong() - return InvocationId(value, Instant.ofEpochMilli(timestamp)) - } - - override fun write(kryo: Kryo, output: Output, observable: Observable<*>) { - throw UnsupportedOperationException("Cannot serialise Observables on the client side") - } - - private fun getRpcCallSite(kryo: Kryo, observableContext: ObservableContext): Throwable? { - val rpcRequestOrObservableId = kryo.context[RPCApi.RpcRequestOrObservableIdKey] as InvocationId - return observableContext.callSiteMap?.get(rpcRequestOrObservableId) - } -} diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/KryoClientSerializationScheme.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/serialization/kryo/KryoClientSerializationScheme.kt similarity index 98% rename from client/rpc/src/main/kotlin/net/corda/client/rpc/internal/KryoClientSerializationScheme.kt rename to client/rpc/src/main/kotlin/net/corda/client/rpc/internal/serialization/kryo/KryoClientSerializationScheme.kt index 6558f3429f..6a916cb28b 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/KryoClientSerializationScheme.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/serialization/kryo/KryoClientSerializationScheme.kt @@ -8,7 +8,7 @@ * Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited. */ -package net.corda.client.rpc.internal +package net.corda.client.rpc.internal.serialization.kryo import com.esotericsoftware.kryo.pool.KryoPool import net.corda.core.serialization.SerializationContext diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/serialization/kryo/RpcClientObservableSerializer.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/serialization/kryo/RpcClientObservableSerializer.kt new file mode 100644 index 0000000000..2a8f6f5283 --- /dev/null +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/serialization/kryo/RpcClientObservableSerializer.kt @@ -0,0 +1,75 @@ +package net.corda.client.rpc.internal.serialization.kryo + +import com.esotericsoftware.kryo.Kryo +import com.esotericsoftware.kryo.Serializer +import com.esotericsoftware.kryo.io.Input +import com.esotericsoftware.kryo.io.Output +import net.corda.client.rpc.internal.ObservableContext +import net.corda.core.context.Trace +import net.corda.core.serialization.SerializationContext +import net.corda.nodeapi.RPCApi +import rx.Notification +import rx.Observable +import rx.subjects.UnicastSubject +import java.time.Instant +import java.util.concurrent.atomic.AtomicInteger + +/** + * A [Serializer] to deserialise Observables once the corresponding Kryo instance has been provided with an [ObservableContext]. + */ +object RpcClientObservableSerializer : Serializer>() { + private object RpcObservableContextKey + + fun createContext(serializationContext: SerializationContext, observableContext: ObservableContext): SerializationContext { + return serializationContext.withProperty(RpcObservableContextKey, observableContext) + } + + private fun pinInSubscriptions(observable: Observable, hardReferenceStore: MutableSet>): Observable { + val refCount = AtomicInteger(0) + return observable.doOnSubscribe { + if (refCount.getAndIncrement() == 0) { + require(hardReferenceStore.add(observable)) { "Reference store already contained reference $this on add" } + } + }.doOnUnsubscribe { + if (refCount.decrementAndGet() == 0) { + require(hardReferenceStore.remove(observable)) { "Reference store did not contain reference $this on remove" } + } + } + } + + override fun read(kryo: Kryo, input: Input, type: Class>): Observable { + val observableContext = kryo.context[RpcObservableContextKey] as ObservableContext + val observableId = input.readInvocationId() ?: throw IllegalStateException("Unable to read invocationId from Input.") + val observable = UnicastSubject.create>() + require(observableContext.observableMap.getIfPresent(observableId) == null) { + "Multiple Observables arrived with the same ID $observableId" + } + val rpcCallSite = getRpcCallSite(kryo, observableContext) + observableContext.observableMap.put(observableId, observable) + observableContext.callSiteMap?.put(observableId, rpcCallSite) + // We pin all Observables into a hard reference store (rooted in the RPC proxy) on subscription so that users + // don't need to store a reference to the Observables themselves. + return pinInSubscriptions(observable, observableContext.hardReferenceStore).doOnUnsubscribe { + // This causes Future completions to give warnings because the corresponding OnComplete sent from the server + // will arrive after the client unsubscribes from the observable and consequently invalidates the mapping. + // The unsubscribe is due to [ObservableToFuture]'s use of first(). + observableContext.observableMap.invalidate(observableId) + }.dematerialize() + } + + private fun Input.readInvocationId() : Trace.InvocationId? { + + val value = readString() ?: return null + val timestamp = readLong() + return Trace.InvocationId(value, Instant.ofEpochMilli(timestamp)) + } + + override fun write(kryo: Kryo, output: Output, observable: Observable<*>) { + throw UnsupportedOperationException("Cannot serialise Observables on the client side") + } + + private fun getRpcCallSite(kryo: Kryo, observableContext: ObservableContext): Throwable? { + val rpcRequestOrObservableId = kryo.context[RPCApi.RpcRequestOrObservableIdKey] as Trace.InvocationId + return observableContext.callSiteMap?.get(rpcRequestOrObservableId) + } +} diff --git a/constants.properties b/constants.properties index 03c76d77b5..55d36f37b5 100644 --- a/constants.properties +++ b/constants.properties @@ -8,8 +8,8 @@ # Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited. # -gradlePluginsVersion=4.0.14 -kotlinVersion=1.2.20 +gradlePluginsVersion=4.0.15 +kotlinVersion=1.2.41 platformVersion=4 guavaVersion=21.0 bouncycastleVersion=1.57 diff --git a/core/src/main/kotlin/net/corda/core/flows/NotaryError.kt b/core/src/main/kotlin/net/corda/core/flows/NotaryError.kt new file mode 100644 index 0000000000..281f3dfff6 --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/flows/NotaryError.kt @@ -0,0 +1,73 @@ +package net.corda.core.flows + +import net.corda.core.contracts.StateRef +import net.corda.core.contracts.TimeWindow +import net.corda.core.crypto.SecureHash +import net.corda.core.serialization.CordaSerializable +import java.time.Instant + +/** + * Exception thrown by the notary service if any issues are encountered while trying to commit a transaction. The + * underlying [error] specifies the cause of failure. + */ +class NotaryException( + /** Cause of notarisation failure. */ + val error: NotaryError, + /** Id of the transaction to be notarised. Can be _null_ if an error occurred before the id could be resolved. */ + val txId: SecureHash? = null +) : FlowException("Unable to notarise transaction${txId ?: " "}: $error") + +/** Specifies the cause for notarisation request failure. */ +@CordaSerializable +sealed class NotaryError { + /** Occurs when one or more input states have already been consumed by another transaction. */ + data class Conflict( + /** Id of the transaction that was attempted to be notarised. */ + val txId: SecureHash, + /** Specifies which states have already been consumed in another transaction. */ + val consumedStates: Map + ) : NotaryError() { + override fun toString() = "One or more input states have been used in another transaction" + } + + /** Occurs when time specified in the [TimeWindow] command is outside the allowed tolerance. */ + data class TimeWindowInvalid(val currentTime: Instant, val txTimeWindow: TimeWindow) : NotaryError() { + override fun toString() = "Current time $currentTime is outside the time bounds specified by the transaction: $txTimeWindow" + + companion object { + @JvmField + @Deprecated("Here only for binary compatibility purposes, do not use.") + val INSTANCE = TimeWindowInvalid(Instant.EPOCH, TimeWindow.fromOnly(Instant.EPOCH)) + } + } + + /** Occurs when the provided transaction fails to verify. */ + data class TransactionInvalid(val cause: Throwable) : NotaryError() { + override fun toString() = cause.toString() + } + + /** Occurs when the transaction sent for notarisation is assigned to a different notary identity. */ + object WrongNotary : NotaryError() + + /** Occurs when the notarisation request signature does not verify for the provided transaction. */ + data class RequestSignatureInvalid(val cause: Throwable) : NotaryError() { + override fun toString() = "Request signature invalid: $cause" + } + + /** Occurs when the notary service encounters an unexpected issue or becomes temporarily unavailable. */ + data class General(val cause: Throwable) : NotaryError() { + override fun toString() = cause.toString() + } +} + +/** Contains information about the consuming transaction for a particular state. */ +// TODO: include notary timestamp? +@CordaSerializable +data class StateConsumptionDetails( + /** + * Hash of the consuming transaction id. + * + * Note that this is NOT the transaction id itself – revealing it could lead to privacy leaks. + */ + val hashOfTransactionId: SecureHash +) \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt b/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt index 61d256bc5e..e8078c8877 100644 --- a/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt @@ -14,22 +14,17 @@ import co.paralleluniverse.fibers.Suspendable import net.corda.core.DoNotImplement import net.corda.core.contracts.StateRef import net.corda.core.contracts.TimeWindow -import net.corda.core.crypto.SecureHash import net.corda.core.crypto.TransactionSignature import net.corda.core.identity.Party import net.corda.core.internal.FetchDataFlow -import net.corda.core.internal.generateSignature -import net.corda.core.internal.validateSignatures -import net.corda.core.node.services.NotaryService -import net.corda.core.node.services.TrustedAuthorityNotaryService -import net.corda.core.serialization.CordaSerializable +import net.corda.core.internal.notary.generateSignature +import net.corda.core.internal.notary.validateSignatures import net.corda.core.transactions.ContractUpgradeWireTransaction import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.WireTransaction import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.UntrustworthyData import net.corda.core.utilities.unwrap -import java.time.Instant import java.util.function.Predicate class NotaryFlow { @@ -130,144 +125,4 @@ class NotaryFlow { } } } - - /** - * A flow run by a notary service that handles notarisation requests. - * - * It checks that the time-window command is valid (if present) and commits the input state, or returns a conflict - * if any of the input states have been previously committed. - * - * Additional transaction validation logic can be added when implementing [validateRequest]. - */ - // See AbstractStateReplacementFlow.Acceptor for why it's Void? - abstract class Service(val otherSideSession: FlowSession, val service: TrustedAuthorityNotaryService) : FlowLogic() { - companion object { - // TODO: Determine an appropriate limit and also enforce in the network parameters and the transaction builder. - private const val maxAllowedInputs = 10_000 - } - - @Suspendable - override fun call(): Void? { - check(serviceHub.myInfo.legalIdentities.any { serviceHub.networkMapCache.isNotary(it) }) { - "We are not a notary on the network" - } - val requestPayload = otherSideSession.receive().unwrap { it } - var txId: SecureHash? = null - try { - val parts = validateRequest(requestPayload) - txId = parts.id - checkNotary(parts.notary) - service.commitInputStates(parts.inputs, txId, otherSideSession.counterparty, requestPayload.requestSignature, parts.timestamp) - signTransactionAndSendResponse(txId) - } catch (e: NotaryInternalException) { - throw NotaryException(e.error, txId) - } - return null - } - - /** Checks whether the number of input states is too large. */ - protected fun checkInputs(inputs: List) { - if (inputs.size > maxAllowedInputs) { - val error = NotaryError.TransactionInvalid( - IllegalArgumentException("A transaction cannot have more than $maxAllowedInputs inputs, received: ${inputs.size}") - ) - throw NotaryInternalException(error) - } - } - - /** - * Implement custom logic to perform transaction verification based on validity and privacy requirements. - */ - @Suspendable - protected abstract fun validateRequest(requestPayload: NotarisationPayload): TransactionParts - - /** Check if transaction is intended to be signed by this notary. */ - @Suspendable - protected fun checkNotary(notary: Party?) { - if (notary?.owningKey != service.notaryIdentityKey) { - throw NotaryInternalException(NotaryError.WrongNotary) - } - } - - @Suspendable - private fun signTransactionAndSendResponse(txId: SecureHash) { - val signature = service.sign(txId) - otherSideSession.send(NotarisationResponse(listOf(signature))) - } - } -} - -/** - * The minimum amount of information needed to notarise a transaction. Note that this does not include - * any sensitive transaction details. - */ -data class TransactionParts(val id: SecureHash, val inputs: List, val timestamp: TimeWindow?, val notary: Party?) - -/** - * Exception thrown by the notary service if any issues are encountered while trying to commit a transaction. The - * underlying [error] specifies the cause of failure. - */ -class NotaryException( - /** Cause of notarisation failure. */ - val error: NotaryError, - /** Id of the transaction to be notarised. Can be _null_ if an error occurred before the id could be resolved. */ - val txId: SecureHash? = null -) : FlowException("Unable to notarise transaction${txId ?: " "}: $error") - -/** Exception internal to the notary service. Does not get exposed to CorDapps and flows calling [NotaryFlow.Client]. */ -class NotaryInternalException(val error: NotaryError) : FlowException("Unable to notarise: $error") - -/** Specifies the cause for notarisation request failure. */ -@CordaSerializable -sealed class NotaryError { - /** Occurs when one or more input states have already been consumed by another transaction. */ - data class Conflict( - /** Id of the transaction that was attempted to be notarised. */ - val txId: SecureHash, - /** Specifies which states have already been consumed in another transaction. */ - val consumedStates: Map - ) : NotaryError() { - override fun toString() = "One or more input states have been used in another transaction" - } - - /** Occurs when time specified in the [TimeWindow] command is outside the allowed tolerance. */ - data class TimeWindowInvalid(val currentTime: Instant, val txTimeWindow: TimeWindow) : NotaryError() { - override fun toString() = "Current time $currentTime is outside the time bounds specified by the transaction: $txTimeWindow" - - companion object { - @JvmField - @Deprecated("Here only for binary compatibility purposes, do not use.") - val INSTANCE = TimeWindowInvalid(Instant.EPOCH, TimeWindow.fromOnly(Instant.EPOCH)) - } - } - - /** Occurs when the provided transaction fails to verify. */ - data class TransactionInvalid(val cause: Throwable) : NotaryError() { - override fun toString() = cause.toString() - } - - /** Occurs when the transaction sent for notarisation is assigned to a different notary identity. */ - object WrongNotary : NotaryError() - - /** Occurs when the notarisation request signature does not verify for the provided transaction. */ - data class RequestSignatureInvalid(val cause: Throwable) : NotaryError() { - override fun toString() = "Request signature invalid: $cause" - } - - /** Occurs when the notary service encounters an unexpected issue or becomes temporarily unavailable. */ - data class General(val cause: Throwable) : NotaryError() { - override fun toString() = cause.toString() - } -} - -/** Contains information about the consuming transaction for a particular state. */ -// TODO: include notary timestamp? -@CordaSerializable -data class StateConsumptionDetails( - /** - * Hash of the consuming transaction id. - * - * Note that this is NOT the transaction id itself – revealing it could lead to privacy leaks. - */ - val hashOfTransactionId: SecureHash -) \ No newline at end of file +} \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/flows/NotarisationRequest.kt b/core/src/main/kotlin/net/corda/core/flows/NotaryWireFormat.kt similarity index 70% rename from core/src/main/kotlin/net/corda/core/flows/NotarisationRequest.kt rename to core/src/main/kotlin/net/corda/core/flows/NotaryWireFormat.kt index 998849b7c9..eff2ec41ad 100644 --- a/core/src/main/kotlin/net/corda/core/flows/NotarisationRequest.kt +++ b/core/src/main/kotlin/net/corda/core/flows/NotaryWireFormat.kt @@ -11,14 +11,12 @@ package net.corda.core.flows import net.corda.core.contracts.StateRef -import net.corda.core.crypto.* -import net.corda.core.identity.Party +import net.corda.core.crypto.DigitalSignature +import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.TransactionSignature import net.corda.core.serialization.CordaSerializable -import net.corda.core.serialization.serialize import net.corda.core.transactions.CoreTransaction import net.corda.core.transactions.SignedTransaction -import java.security.InvalidKeyException -import java.security.SignatureException /** * A notarisation request specifies a list of states to consume and the id of the consuming transaction. Its primary @@ -46,34 +44,6 @@ class NotarisationRequest(statesToConsume: List, val transactionId: Se /** States this request specifies to be consumed. Sorted to ensure the serialized form does not get affected by the state order. */ val statesToConsume: List get() = _statesToConsumeSorted // Getter required for AMQP serialization - - /** Verifies the signature against this notarisation request. Checks that the signature is issued by the right party. */ - fun verifySignature(requestSignature: NotarisationRequestSignature, intendedSigner: Party) { - val signature = requestSignature.digitalSignature - if (intendedSigner.owningKey != signature.by) { - val errorMessage = "Expected a signature by ${intendedSigner.owningKey}, but received by ${signature.by}}" - throw NotaryInternalException(NotaryError.RequestSignatureInvalid(IllegalArgumentException(errorMessage))) - } - // TODO: if requestSignature was generated over an old version of NotarisationRequest, we need to be able to - // reserialize it in that version to get the exact same bytes. Modify the serialization logic once that's - // available. - val expectedSignedBytes = this.serialize().bytes - verifyCorrectBytesSigned(signature, expectedSignedBytes) - } - - private fun verifyCorrectBytesSigned(signature: DigitalSignature.WithKey, bytes: ByteArray) { - try { - signature.verify(bytes) - } catch (e: Exception) { - when (e) { - is InvalidKeyException, is SignatureException -> { - val error = NotaryError.RequestSignatureInvalid(e) - throw NotaryInternalException(error) - } - else -> throw e - } - } - } } /** 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 da43f22e01..d74cfa77da 100644 --- a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt @@ -18,11 +18,7 @@ import net.corda.core.cordapp.Cordapp import net.corda.core.cordapp.CordappConfig import net.corda.core.cordapp.CordappContext import net.corda.core.crypto.* -import net.corda.core.flows.NotarisationRequest -import net.corda.core.flows.NotarisationRequestSignature -import net.corda.core.flows.NotaryFlow import net.corda.core.identity.CordaX500Name -import net.corda.core.node.ServiceHub import net.corda.core.node.ServicesForResolution import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializedBytes @@ -422,22 +418,6 @@ fun createCordappContext(cordapp: Cordapp, attachmentId: SecureHash?, classLoade return CordappContext(cordapp, attachmentId, classLoader, config) } -/** Verifies that the correct notarisation request was signed by the counterparty. */ -fun NotaryFlow.Service.validateRequestSignature(request: NotarisationRequest, signature: NotarisationRequestSignature) { - val requestingParty = otherSideSession.counterparty - request.verifySignature(signature, requestingParty) -} - -/** Creates a signature over the notarisation request using the legal identity key. */ -fun NotarisationRequest.generateSignature(serviceHub: ServiceHub): NotarisationRequestSignature { - val serializedRequest = this.serialize().bytes - val signature = with(serviceHub) { - val myLegalIdentity = myInfo.legalIdentitiesAndCerts.first().owningKey - keyManagementService.sign(serializedRequest, myLegalIdentity) - } - return NotarisationRequestSignature(signature, serviceHub.myInfo.platformVersion) -} - val PublicKey.hash: SecureHash get() = encoded.sha256() /** diff --git a/core/src/main/kotlin/net/corda/core/internal/NotaryUtils.kt b/core/src/main/kotlin/net/corda/core/internal/NotaryUtils.kt deleted file mode 100644 index 21e639c2e1..0000000000 --- a/core/src/main/kotlin/net/corda/core/internal/NotaryUtils.kt +++ /dev/null @@ -1,36 +0,0 @@ -package net.corda.core.internal - -import net.corda.core.contracts.StateRef -import net.corda.core.contracts.TimeWindow -import net.corda.core.crypto.SecureHash -import net.corda.core.crypto.isFulfilledBy -import net.corda.core.flows.NotarisationResponse -import net.corda.core.flows.NotaryError -import net.corda.core.flows.StateConsumptionDetails -import net.corda.core.identity.Party -import java.time.Instant - -/** - * Checks that there are sufficient signatures to satisfy the notary signing requirement and validates the signatures - * against the given transaction id. - */ -fun NotarisationResponse.validateSignatures(txId: SecureHash, notary: Party) { - val signingKeys = signatures.map { it.by } - require(notary.owningKey.isFulfilledBy(signingKeys)) { "Insufficient signatures to fulfill the notary signing requirement for $notary" } - signatures.forEach { it.verify(txId) } -} - -/** Checks if the provided states were used as inputs in the specified transaction. */ -fun isConsumedByTheSameTx(txIdHash: SecureHash, consumedStates: Map): Boolean { - val conflicts = consumedStates.filter { (_, cause) -> - cause.hashOfTransactionId != txIdHash - } - return conflicts.isEmpty() -} - -/** Returns [NotaryError.TimeWindowInvalid] if [currentTime] is outside the [timeWindow], and *null* otherwise. */ -fun validateTimeWindow(currentTime: Instant, timeWindow: TimeWindow?): NotaryError.TimeWindowInvalid? { - return if (timeWindow != null && currentTime !in timeWindow) { - NotaryError.TimeWindowInvalid(currentTime, timeWindow) - } else null -} \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/internal/notary/NotaryService.kt b/core/src/main/kotlin/net/corda/core/internal/notary/NotaryService.kt new file mode 100644 index 0000000000..68041a760a --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/internal/notary/NotaryService.kt @@ -0,0 +1,23 @@ +package net.corda.core.internal.notary + +import net.corda.core.flows.FlowLogic +import net.corda.core.flows.FlowSession +import net.corda.core.flows.NotaryFlow +import net.corda.core.identity.Party +import net.corda.core.node.ServiceHub +import net.corda.core.serialization.SingletonSerializeAsToken +import java.security.PublicKey + +abstract class NotaryService : SingletonSerializeAsToken() { + abstract val services: ServiceHub + abstract val notaryIdentityKey: PublicKey + + abstract fun start() + abstract fun stop() + + /** + * Produces a notary service flow which has the corresponding sends and receives as [NotaryFlow.Client]. + * @param otherPartySession client [Party] making the request + */ + abstract fun createServiceFlow(otherPartySession: FlowSession): FlowLogic +} \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/internal/notary/NotaryServiceFlow.kt b/core/src/main/kotlin/net/corda/core/internal/notary/NotaryServiceFlow.kt new file mode 100644 index 0000000000..ad1ad7293e --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/internal/notary/NotaryServiceFlow.kt @@ -0,0 +1,89 @@ +package net.corda.core.internal.notary + +import co.paralleluniverse.fibers.Suspendable +import net.corda.core.contracts.StateRef +import net.corda.core.contracts.TimeWindow +import net.corda.core.crypto.SecureHash +import net.corda.core.flows.* +import net.corda.core.identity.Party +import net.corda.core.utilities.unwrap + +/** + * A flow run by a notary service that handles notarisation requests. + * + * It checks that the time-window command is valid (if present) and commits the input state, or returns a conflict + * if any of the input states have been previously committed. + * + * Additional transaction validation logic can be added when implementing [validateRequest]. + */ +// See AbstractStateReplacementFlow.Acceptor for why it's Void? +abstract class NotaryServiceFlow(val otherSideSession: FlowSession, val service: TrustedAuthorityNotaryService) : FlowLogic() { + companion object { + // TODO: Determine an appropriate limit and also enforce in the network parameters and the transaction builder. + private const val maxAllowedInputs = 10_000 + } + + @Suspendable + override fun call(): Void? { + check(serviceHub.myInfo.legalIdentities.any { serviceHub.networkMapCache.isNotary(it) }) { + "We are not a notary on the network" + } + val requestPayload = otherSideSession.receive().unwrap { it } + var txId: SecureHash? = null + try { + val parts = validateRequest(requestPayload) + txId = parts.id + checkNotary(parts.notary) + service.commitInputStates(parts.inputs, txId, otherSideSession.counterparty, requestPayload.requestSignature, parts.timestamp) + signTransactionAndSendResponse(txId) + } catch (e: NotaryInternalException) { + throw NotaryException(e.error, txId) + } + return null + } + + /** Checks whether the number of input states is too large. */ + protected fun checkInputs(inputs: List) { + if (inputs.size > maxAllowedInputs) { + val error = NotaryError.TransactionInvalid( + IllegalArgumentException("A transaction cannot have more than $maxAllowedInputs inputs, received: ${inputs.size}") + ) + throw NotaryInternalException(error) + } + } + + /** + * Implement custom logic to perform transaction verification based on validity and privacy requirements. + */ + @Suspendable + protected abstract fun validateRequest(requestPayload: NotarisationPayload): TransactionParts + + /** Verifies that the correct notarisation request was signed by the counterparty. */ + protected fun validateRequestSignature(request: NotarisationRequest, signature: NotarisationRequestSignature) { + val requestingParty = otherSideSession.counterparty + request.verifySignature(signature, requestingParty) + } + + /** Check if transaction is intended to be signed by this notary. */ + @Suspendable + protected fun checkNotary(notary: Party?) { + if (notary?.owningKey != service.notaryIdentityKey) { + throw NotaryInternalException(NotaryError.WrongNotary) + } + } + + @Suspendable + private fun signTransactionAndSendResponse(txId: SecureHash) { + val signature = service.sign(txId) + otherSideSession.send(NotarisationResponse(listOf(signature))) + } + + /** + * The minimum amount of information needed to notarise a transaction. Note that this does not include + * any sensitive transaction details. + */ + protected data class TransactionParts(val id: SecureHash, val inputs: List, val timestamp: TimeWindow?, val notary: Party?) +} + +/** Exception internal to the notary service. Does not get exposed to CorDapps and flows calling [NotaryFlow.Client]. */ +class NotaryInternalException(val error: NotaryError) : FlowException("Unable to notarise: $error") \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/internal/notary/NotaryUtils.kt b/core/src/main/kotlin/net/corda/core/internal/notary/NotaryUtils.kt new file mode 100644 index 0000000000..008e2efe60 --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/internal/notary/NotaryUtils.kt @@ -0,0 +1,77 @@ +package net.corda.core.internal.notary + +import net.corda.core.contracts.StateRef +import net.corda.core.contracts.TimeWindow +import net.corda.core.crypto.DigitalSignature +import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.isFulfilledBy +import net.corda.core.flows.* +import net.corda.core.identity.Party +import net.corda.core.node.ServiceHub +import net.corda.core.serialization.serialize +import java.security.InvalidKeyException +import java.security.SignatureException +import java.time.Instant + +/** Verifies the signature against this notarisation request. Checks that the signature is issued by the right party. */ +fun NotarisationRequest.verifySignature(requestSignature: NotarisationRequestSignature, intendedSigner: Party) { + val signature = requestSignature.digitalSignature + if (intendedSigner.owningKey != signature.by) { + val errorMessage = "Expected a signature by ${intendedSigner.owningKey}, but received by ${signature.by}}" + throw NotaryInternalException(NotaryError.RequestSignatureInvalid(IllegalArgumentException(errorMessage))) + } + // TODO: if requestSignature was generated over an old version of NotarisationRequest, we need to be able to + // reserialize it in that version to get the exact same bytes. Modify the serialization logic once that's + // available. + val expectedSignedBytes = this.serialize().bytes + verifyCorrectBytesSigned(signature, expectedSignedBytes) +} + +private fun verifyCorrectBytesSigned(signature: DigitalSignature.WithKey, bytes: ByteArray) { + try { + signature.verify(bytes) + } catch (e: Exception) { + when (e) { + is InvalidKeyException, is SignatureException -> { + val error = NotaryError.RequestSignatureInvalid(e) + throw NotaryInternalException(error) + } + else -> throw e + } + } +} + +/** + * Checks that there are sufficient signatures to satisfy the notary signing requirement and validates the signatures + * against the given transaction id. + */ +fun NotarisationResponse.validateSignatures(txId: SecureHash, notary: Party) { + val signingKeys = signatures.map { it.by } + require(notary.owningKey.isFulfilledBy(signingKeys)) { "Insufficient signatures to fulfill the notary signing requirement for $notary" } + signatures.forEach { it.verify(txId) } +} + +/** Creates a signature over the notarisation request using the legal identity key. */ +fun NotarisationRequest.generateSignature(serviceHub: ServiceHub): NotarisationRequestSignature { + val serializedRequest = this.serialize().bytes + val signature = with(serviceHub) { + val myLegalIdentity = myInfo.legalIdentitiesAndCerts.first().owningKey + keyManagementService.sign(serializedRequest, myLegalIdentity) + } + return NotarisationRequestSignature(signature, serviceHub.myInfo.platformVersion) +} + +/** Checks if the provided states were used as inputs in the specified transaction. */ +fun isConsumedByTheSameTx(txIdHash: SecureHash, consumedStates: Map): Boolean { + val conflicts = consumedStates.filter { (_, cause) -> + cause.hashOfTransactionId != txIdHash + } + return conflicts.isEmpty() +} + +/** Returns [NotaryError.TimeWindowInvalid] if [currentTime] is outside the [timeWindow], and *null* otherwise. */ +fun validateTimeWindow(currentTime: Instant, timeWindow: TimeWindow?): NotaryError.TimeWindowInvalid? { + return if (timeWindow != null && currentTime !in timeWindow) { + NotaryError.TimeWindowInvalid(currentTime, timeWindow) + } else null +} \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/internal/notary/TrustedAuthorityNotaryService.kt b/core/src/main/kotlin/net/corda/core/internal/notary/TrustedAuthorityNotaryService.kt new file mode 100644 index 0000000000..3d6bda0a7b --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/internal/notary/TrustedAuthorityNotaryService.kt @@ -0,0 +1,61 @@ +package net.corda.core.internal.notary + +import net.corda.core.contracts.StateRef +import net.corda.core.contracts.TimeWindow +import net.corda.core.crypto.* +import net.corda.core.flows.NotarisationRequestSignature +import net.corda.core.flows.NotaryError +import net.corda.core.identity.Party +import net.corda.core.utilities.contextLogger +import org.slf4j.Logger + +/** + * A base notary service implementation that provides functionality for cases where a signature by a single member + * of the cluster is sufficient for transaction notarisation. For example, a single-node or a Raft notary. + */ +abstract class TrustedAuthorityNotaryService : NotaryService() { + companion object { + private val staticLog = contextLogger() + } + + protected open val log: Logger get() = staticLog + protected abstract val uniquenessProvider: UniquenessProvider + + /** + * A NotaryException is thrown if any of the states have been consumed by a different transaction. Note that + * this method does not throw an exception when input states are present multiple times within the transaction. + */ + fun commitInputStates(inputs: List, txId: SecureHash, caller: Party, requestSignature: NotarisationRequestSignature, timeWindow: TimeWindow?) { + try { + uniquenessProvider.commit(inputs, txId, caller, requestSignature, timeWindow) + } catch (e: NotaryInternalException) { + if (e.error is NotaryError.Conflict) { + val conflicts = inputs.filterIndexed { _, stateRef -> + val cause = e.error.consumedStates[stateRef] + cause != null && cause.hashOfTransactionId != txId.sha256() + } + if (conflicts.isNotEmpty()) { + // TODO: Create a new UniquenessException that only contains the conflicts filtered above. + log.info("Notary conflicts for $txId: $conflicts") + throw e + } + } else throw e + } catch (e: Exception) { + log.error("Internal error", e) + throw NotaryInternalException(NotaryError.General(Exception("Service unavailable, please try again later"))) + } + } + + /** Sign a [ByteArray] input. */ + fun sign(bits: ByteArray): DigitalSignature.WithKey { + return services.keyManagementService.sign(bits, notaryIdentityKey) + } + + /** Sign a single transaction. */ + fun sign(txId: SecureHash): TransactionSignature { + val signableData = SignableData(txId, SignatureMetadata(services.myInfo.platformVersion, Crypto.findSignatureScheme(notaryIdentityKey).schemeNumberID)) + return services.keyManagementService.sign(signableData, notaryIdentityKey) + } + + // TODO: Sign multiple transactions at once by building their Merkle tree and then signing over its root. +} \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/internal/notary/UniquenessProvider.kt b/core/src/main/kotlin/net/corda/core/internal/notary/UniquenessProvider.kt new file mode 100644 index 0000000000..3e31c95978 --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/internal/notary/UniquenessProvider.kt @@ -0,0 +1,24 @@ +package net.corda.core.internal.notary + +import net.corda.core.contracts.StateRef +import net.corda.core.contracts.TimeWindow +import net.corda.core.crypto.SecureHash +import net.corda.core.flows.NotarisationRequestSignature +import net.corda.core.identity.Party + +/** + * A service that records input states of the given transaction and provides conflict information + * if any of the inputs have already been used in another transaction. + * + * A uniqueness provider is expected to be used from within the context of a flow. + */ +interface UniquenessProvider { + /** Commits all input states of the given transaction. */ + fun commit( + states: List, + txId: SecureHash, + callerIdentity: Party, + requestSignature: NotarisationRequestSignature, + timeWindow: TimeWindow? = null + ) +} \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/node/services/NotaryService.kt b/core/src/main/kotlin/net/corda/core/node/services/NotaryService.kt deleted file mode 100644 index 116f7db74b..0000000000 --- a/core/src/main/kotlin/net/corda/core/node/services/NotaryService.kt +++ /dev/null @@ -1,131 +0,0 @@ -/* - * R3 Proprietary and Confidential - * - * Copyright (c) 2018 R3 Limited. All rights reserved. - * - * The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law. - * - * Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited. - */ - -package net.corda.core.node.services - -import com.google.common.primitives.Booleans -import net.corda.core.contracts.StateRef -import net.corda.core.contracts.TimeWindow -import net.corda.core.crypto.* -import net.corda.core.flows.* -import net.corda.core.identity.Party -import net.corda.core.node.ServiceHub -import net.corda.core.serialization.SingletonSerializeAsToken -import net.corda.core.utilities.contextLogger -import org.slf4j.Logger -import java.security.PublicKey -import java.time.Clock - -abstract class NotaryService : SingletonSerializeAsToken() { - companion object { - @Deprecated("No longer used") - const val ID_PREFIX = "corda.notary." - - @Deprecated("No longer used") - fun constructId(validating: Boolean, raft: Boolean = false, bft: Boolean = false, custom: Boolean = false): String { - require(Booleans.countTrue(raft, bft, custom) <= 1) { "At most one of raft, bft or custom may be true" } - return StringBuffer(ID_PREFIX).apply { - append(if (validating) "validating" else "simple") - if (raft) append(".raft") - if (bft) append(".bft") - if (custom) append(".custom") - }.toString() - } - - /** - * Checks if the current instant provided by the clock falls within the specified time window. Should only be - * used by a notary service flow. - * - * @throws NotaryInternalException if current time is outside the specified time window. The exception contains - * the [NotaryError.TimeWindowInvalid] error. - */ - @JvmStatic - @Throws(NotaryInternalException::class) - fun validateTimeWindow(clock: Clock, timeWindow: TimeWindow?) { - if (timeWindow == null) return - val currentTime = clock.instant() - if (currentTime !in timeWindow) { - throw NotaryInternalException( - NotaryError.TimeWindowInvalid(currentTime, timeWindow) - ) - } - } - } - - abstract val services: ServiceHub - abstract val notaryIdentityKey: PublicKey - - abstract fun start() - abstract fun stop() - - /** - * Produces a notary service flow which has the corresponding sends and receives as [NotaryFlow.Client]. - * @param otherPartySession client [Party] making the request - */ - abstract fun createServiceFlow(otherPartySession: FlowSession): FlowLogic -} - -/** - * A base notary service implementation that provides functionality for cases where a signature by a single member - * of the cluster is sufficient for transaction notarisation. For example, a single-node or a Raft notary. - */ -abstract class TrustedAuthorityNotaryService : NotaryService() { - companion object { - private val staticLog = contextLogger() - } - - protected open val log: Logger get() = staticLog - protected abstract val uniquenessProvider: UniquenessProvider - - fun validateTimeWindow(t: TimeWindow?) = NotaryService.validateTimeWindow(services.clock, t) - - /** - * A NotaryException is thrown if any of the states have been consumed by a different transaction. Note that - * this method does not throw an exception when input states are present multiple times within the transaction. - */ - fun commitInputStates(inputs: List, txId: SecureHash, caller: Party, requestSignature: NotarisationRequestSignature, timeWindow: TimeWindow?) { - try { - uniquenessProvider.commit(inputs, txId, caller, requestSignature, timeWindow) - } catch (e: NotaryInternalException) { - if (e.error is NotaryError.Conflict) { - val conflicts = inputs.filterIndexed { _, stateRef -> - val cause = e.error.consumedStates[stateRef] - cause != null && cause.hashOfTransactionId != txId.sha256() - } - if (conflicts.isNotEmpty()) { - // TODO: Create a new UniquenessException that only contains the conflicts filtered above. - log.info("Notary conflicts for $txId: $conflicts") - throw e - } - } else throw e - } catch (e: Exception) { - log.error("Internal error", e) - throw NotaryInternalException(NotaryError.General(Exception("Service unavailable, please try again later"))) - } - } - - /** Sign a [ByteArray] input. */ - fun sign(bits: ByteArray): DigitalSignature.WithKey { - return services.keyManagementService.sign(bits, notaryIdentityKey) - } - - /** Sign a single transaction. */ - fun sign(txId: SecureHash): TransactionSignature { - val signableData = SignableData(txId, SignatureMetadata(services.myInfo.platformVersion, Crypto.findSignatureScheme(notaryIdentityKey).schemeNumberID)) - return services.keyManagementService.sign(signableData, notaryIdentityKey) - } - - // TODO: Sign multiple transactions at once by building their Merkle tree and then signing over its root. - - @Deprecated("This property is no longer used") - @Suppress("DEPRECATION") - protected open val timeWindowChecker: TimeWindowChecker - get() = throw UnsupportedOperationException("No default implementation, need to override") -} \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/node/services/UniquenessProvider.kt b/core/src/main/kotlin/net/corda/core/node/services/UniquenessProvider.kt deleted file mode 100644 index bbe132d134..0000000000 --- a/core/src/main/kotlin/net/corda/core/node/services/UniquenessProvider.kt +++ /dev/null @@ -1,54 +0,0 @@ -/* - * R3 Proprietary and Confidential - * - * Copyright (c) 2018 R3 Limited. All rights reserved. - * - * The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law. - * - * Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited. - */ - -package net.corda.core.node.services - -import net.corda.core.CordaException -import net.corda.core.contracts.StateRef -import net.corda.core.contracts.TimeWindow -import net.corda.core.crypto.SecureHash -import net.corda.core.flows.NotarisationRequestSignature -import net.corda.core.identity.Party -import net.corda.core.serialization.CordaSerializable - -/** - * A service that records input states of the given transaction and provides conflict information - * if any of the inputs have already been used in another transaction. - * - * A uniqueness provider is expected to be used from within the context of a flow. - */ -interface UniquenessProvider { - /** Commits all input states of the given transaction. */ - fun commit( - states: List, - txId: SecureHash, - callerIdentity: Party, - requestSignature: NotarisationRequestSignature, - timeWindow: TimeWindow? = null - ) - - /** Specifies the consuming transaction for every conflicting state. */ - @CordaSerializable - @Deprecated("No longer used due to potential privacy leak") - @Suppress("DEPRECATION") - data class Conflict(val stateHistory: Map) - - /** - * Specifies the transaction id, the position of the consumed state in the inputs, and - * the caller identity requesting the commit. - */ - @CordaSerializable - @Deprecated("No longer used") - data class ConsumingTx(val id: SecureHash, val inputIndex: Int, val requestingParty: Party) -} - -@Deprecated("No longer used due to potential privacy leak") -@Suppress("DEPRECATION") -class UniquenessException(val error: UniquenessProvider.Conflict) : CordaException(UniquenessException::class.java.name) \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/schemas/PersistentTypes.kt b/core/src/main/kotlin/net/corda/core/schemas/PersistentTypes.kt index 3b0eda9e09..1b08092484 100644 --- a/core/src/main/kotlin/net/corda/core/schemas/PersistentTypes.kt +++ b/core/src/main/kotlin/net/corda/core/schemas/PersistentTypes.kt @@ -61,6 +61,26 @@ open class MappedSchema(schemaFamily: Class<*>, internal fun getMigrationResource(): String? = migrationResource override fun toString(): String = "${this.javaClass.simpleName}(name=$name, version=$version)" + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as MappedSchema + + if (version != other.version) return false + if (mappedTypes != other.mappedTypes) return false + if (name != other.name) return false + + return true + } + + override fun hashCode(): Int { + var result = version + result = 31 * result + mappedTypes.hashCode() + result = 31 * result + name.hashCode() + return result + } } //DOCEND MappedSchema diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index e4c0208508..6eeb2aeb8b 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -7,6 +7,22 @@ release, see :doc:`upgrade-notes`. Unreleased ========== +* Refactor RPC Server Kryo observable serializer into it's own sub module + +* Refactor RPC Client Kryo observable serializer into it's own sub module + +* Fix CORDA-1403 where a property of a class that implemented a generic interface could not be deserialized in + a factory without a serializer as the subtype check for the class instance failed. Fix is to compare the raw + type. + +* Due to ongoing work the experimental interfaces for defining custom notary services have been moved to the internal package. + CorDapps implementing custom notary services will need to be updated, see ``samples/notary-demo`` for an example. + Further changes may be required in the future. + +* Fixed incorrect exception handling in ``NodeVaultService._query()``. + +* Avoided a memory leak deriving from incorrect MappedSchema caching strategy. + * Added program line argument ``on-unknown-config-keys`` to allow specifying behaviour on unknown node configuration property keys. Values are: [FAIL, WARN, IGNORE], default to FAIL if unspecified. @@ -330,6 +346,8 @@ Corda 2.0 described in the documentation for this method. This helps resolve a bug in ``Cash`` coin selection. A new static property `currentTopLevel` returns the top most `FlowLogic` instance, or null if not in a flow. +* Updating Jolokia dependency to latest version (includes security fixes) + .. _changelog_v1: Corda 1.0 diff --git a/docs/source/contributing.rst b/docs/source/contributing.rst index 17d894dddb..e1b5396534 100644 --- a/docs/source/contributing.rst +++ b/docs/source/contributing.rst @@ -1,7 +1,9 @@ Contributing ============ -Corda is an open-source project and we welcome contributions. This guide explains how to contribute back to Corda. +Corda is an open-source project and contributions are welcome. Our contributing philosophy is described in +`CONTRIBUTING.md `_. This guide explains the mechanics +of contributing to Corda. .. contents:: @@ -9,19 +11,19 @@ Identifying an area to contribute --------------------------------- There are several ways to identify an area where you can contribute to Corda: +* Ask in the ``#design`` channel of the `Corda Slack `_ + +* Browse the `Corda GitHub issues `_ + + * It's always worth checking in the ``#design`` channel whether a given issue is a good target for your + contribution. Someone else may already be working on it, or it may be blocked by an on-going piece of work + * Browse issues labelled as ``HelpWanted`` on the `Corda JIRA board `_ * Any issue with a ``HelpWanted`` label is considered ideal for open-source contributions * If there is a feature you would like to add and there isn't a corresponding issue labelled as ``HelpWanted``, that - doesn't mean your contribution isn't welcome. Please reach out on the Corda Slack channel (see below) to clarify - -* Check the `Corda GitHub issues `_ - - * It's always worth checking in the Corda Slack channel (see below) whether a given issue is a good target for your - contribution. Someone else may already be working on it, or it may be blocked by an on-going piece of work - -* Ask in the `Corda Slack channel `_ + doesn't mean your contribution isn't welcome. Please reach out on the ``#design`` channel to clarify Making the required changes --------------------------- @@ -39,8 +41,17 @@ Your changes must pass the tests described :doc:`here `. Building against the master branch ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -You may also want to test your changes against a CorDapp defined outside of the Corda repo. To do so, please follow the -instructions :doc:`here `. +You can test your changes against CorDapps defined in other repos by following the instructions :doc:`here `. + +Updating the docs +----------------- + +Any changes to Corda's public API must be documented as follows: + +1. Update the relevant `.rst file(s) `_ +2. Include the change in the :doc:`changelog ` and :doc:`release notes ` where applicable +3. :doc:`Build the docs locally ` +4. Open the built .html files for the modified pages to ensure they render correctly Merging the changes back into Corda ----------------------------------- @@ -55,10 +66,10 @@ Merging the changes back into Corda * State that you are in agreement with the terms of `CONTRIBUTING.md `_ -3. Request a review from a member of the Corda platform team via the `Corda Slack channel `_ +3. Request a review from a member of the Corda platform team via the `#design channel `_ 4. Wait for your PR to pass all four types of continuous integration tests (integration, API stability, build and unit) * Currently, external contributors cannot see the output of these tests. If your PR fails a test that passed locally, ask the reviewer for further details -5. Once a reviewer has approved the PR and the tests have passed, squash-and-merge the PR as a single commit \ No newline at end of file +5. Once a reviewer has approved the PR and the tests have passed, squash-and-merge the PR as a single commit diff --git a/docs/source/corda-nodes-index.rst b/docs/source/corda-nodes-index.rst index 7d904694c2..5ab6994036 100644 --- a/docs/source/corda-nodes-index.rst +++ b/docs/source/corda-nodes-index.rst @@ -4,6 +4,7 @@ Corda nodes .. toctree:: :maxdepth: 1 + node-structure generating-a-node running-a-node deploying-a-node diff --git a/docs/source/generating-a-node.rst b/docs/source/generating-a-node.rst index 6105ac4af5..c9719afb20 100644 --- a/docs/source/generating-a-node.rst +++ b/docs/source/generating-a-node.rst @@ -3,92 +3,24 @@ Creating nodes locally .. contents:: -Node structure --------------- -A Corda node has the following structure: +Handcrafting a node +------------------- +A node can be created manually by creating a folder that contains the following items: -.. sourcecode:: none +* The Corda JAR - . - ├── certificates // The node's certificates - ├── corda-webserver.jar // The built-in node webserver - ├── corda.jar // The core Corda libraries - ├── logs // The node logs - ├── node.conf // The node's configuration files - ├── persistence.mv.db // The node's database - └── cordapps // The CorDapps jars installed on the node + * Can be downloaded from https://r3.bintray.com/corda/net/corda/corda/ (under /VERSION_NUMBER/corda-VERSION_NUMBER.jar) -The node is configured by editing its ``node.conf`` file. You install CorDapps on the node by dropping the CorDapp JARs -into the ``cordapps`` folder. +* A node configuration file entitled ``node.conf``, configured as per :doc:`corda-configuration-file` -In development mode (i.e. when ``devMode = true``, see :doc:`corda-configuration-file` for more information), the ``certificates`` -directory is filled with pre-configured keystores if the required keystores do not exist. This ensures that developers -can get the nodes working as quickly as possible. However, these pre-configured keystores are not secure, to learn more see :doc:`permissioning`. +* A folder entitled ``cordapps`` containing any CorDapp JARs you want the node to load -.. _node_naming: +* **Optional:** A webserver JAR entitled ``corda-webserver.jar`` that will connect to the node via RPC -Node naming ------------ -A node's name must be a valid X.500 distinguished name. In order to be compatible with other implementations -(particularly TLS implementations), we constrain the allowed X.500 name attribute types to a subset of the minimum -supported set for X.509 certificates (specified in RFC 3280), plus the locality attribute: + * The (deprecated) default webserver can be downloaded from http://r3.bintray.com/corda/net/corda/corda-webserver/ (under /VERSION_NUMBER/corda-VERSION_NUMBER.jar) + * A Spring Boot alternative can be found here: https://github.com/corda/spring-webserver -* Organization (O) -* State (ST) -* Locality (L) -* Country (C) -* Organizational-unit (OU) -* Common name (CN) - -Note that the serial number is intentionally excluded in order to minimise scope for uncertainty in the distinguished name format. -The distinguished name qualifier has been removed due to technical issues; consideration was given to "Corda" as qualifier, -however the qualifier needs to reflect the compatibility zone, not the technology involved. There may be many Corda namespaces, -but only one R3 namespace on Corda. The ordering of attributes is important. - -``State`` should be avoided unless required to differentiate from other ``localities`` with the same or similar names at the -country level. For example, London (GB) would not need a ``state``, but St Ives would (there are two, one in Cornwall, one -in Cambridgeshire). As legal entities in Corda are likely to be located in major cities, this attribute is not expected to be -present in the majority of names, but is an option for the cases which require it. - -The name must also obey the following constraints: - -* The ``organisation``, ``locality`` and ``country`` attributes are present - - * The ``state``, ``organisational-unit`` and ``common name`` attributes are optional - -* The fields of the name have the following maximum character lengths: - - * Common name: 64 - * Organisation: 128 - * Organisation unit: 64 - * Locality: 64 - * State: 64 - -* The ``country`` attribute is a valid ISO 3166-1 two letter code in upper-case - -* All attributes must obey the following constraints: - - * Upper-case first letter - * Has at least two letters - * No leading or trailing whitespace - * Does not include the following characters: ``,`` , ``=`` , ``$`` , ``"`` , ``'`` , ``\`` - * Is in NFKC normalization form - * Does not contain the null character - * Only the latin, common and inherited unicode scripts are supported - -* The ``organisation`` field of the name also obeys the following constraints: - - * No double-spacing - - * This is to avoid right-to-left issues, debugging issues when we can't pronounce names over the phone, and - character confusability attacks - -External identifiers -^^^^^^^^^^^^^^^^^^^^ -Mappings to external identifiers such as Companies House nos., LEI, BIC, etc. should be stored in custom X.509 -certificate extensions. These values may change for operational reasons, without the identity they're associated with -necessarily changing, and their inclusion in the distinguished name would cause significant logistical complications. -The OID and format for these extensions will be described in a further specification. +The remaining files and folders described in :doc:`node-structure` will be generated at runtime. The Cordform task ----------------- @@ -205,9 +137,9 @@ The webserver JAR will be copied into the node's ``build`` folder with the name The Dockerform task ------------------- -The ```Dockerform``` is a sister task of ```Cordform```. It has nearly the same syntax and produces very -similar results - enhanced by an extra file to enable easy spin up of nodes using ```docker-compose```. -Below you can find the example task from the ```IRS Demo``` +The ``Dockerform`` is a sister task of ``Cordform``. It has nearly the same syntax and produces very +similar results - enhanced by an extra file to enable easy spin up of nodes using ``docker-compose``. +Below you can find the example task from the ``IRS Demo`` included in the samples directory of main Corda GitHub repository: .. sourcecode:: groovy @@ -228,7 +160,7 @@ included in the samples directory of main Corda GitHub repository: // (...) - task prepareDockerNodes(type: net.corda.plugins.Dockerform, dependsOn: ['jar']) { + task deployNodes(type: net.corda.plugins.Dockerform, dependsOn: ['jar']) { node { name "O=Notary Service,L=Zurich,C=CH" @@ -257,12 +189,10 @@ included in the samples directory of main Corda GitHub repository: } } -There is no need to specify the ports, as every node is a separated container, so no ports conflict will occur. -Running the task will create the same folders structure as described in :ref:`The Cordform task` with an additional -```Dockerfile`` in each node directory, and ```docker-compose.yml``` in ```build/nodes``` directory. Every node -by default exposes port 10003 which is the default one for RPC connections. +There is no need to specify the ports, as every node is a separated container, so no ports conflict will occur. Every +node by default will expose port 10003 which is the default port for RPC connections. -.. warning:: Webserver is not supported by this task! +.. warning:: The node webserver is not supported by this task! .. warning:: Nodes are run without the local shell enabled! @@ -279,4 +209,7 @@ in the ``deployNodes`` task, plus a ``runnodes`` shell script (or batch file on for testing and development purposes. If you make any changes to your CorDapp source or ``deployNodes`` task, you will need to re-run the task to see the changes take effect. +If the task is a ``Dockerform`` task, running the task will also create an additional ``Dockerfile`` in each node +directory, and a ``docker-compose.yml`` file in the ``build/nodes`` directory. + You can now run the nodes by following the instructions in :doc:`Running a node `. diff --git a/docs/source/hello-world-running.rst b/docs/source/hello-world-running.rst index 010ec70af8..a22def00e0 100644 --- a/docs/source/hello-world-running.rst +++ b/docs/source/hello-world-running.rst @@ -105,7 +105,7 @@ commands. .. note:: Local terminal shell is available only in a development mode. In production environment SSH server can be enabled. More about SSH and how to connect can be found on the :doc:`shell` page. -We want to create an IOU of 100 with PartyB. We start the ``IOUFlow`` by typing: +We want to create an IOU of 99 with PartyB. We start the ``IOUFlow`` by typing: .. container:: codeset @@ -188,4 +188,4 @@ There are a number of improvements we could make to this CorDapp: * We could add an API, to make it easier to interact with the CorDapp But for now, the biggest priority is to add an ``IOUContract`` imposing constraints on the evolution of each -``IOUState`` over time. This will be the focus of our next tutorial. \ No newline at end of file +``IOUState`` over time. This will be the focus of our next tutorial. diff --git a/docs/source/key-concepts-notaries.rst b/docs/source/key-concepts-notaries.rst index 5408fae4b6..e92af20ed4 100644 --- a/docs/source/key-concepts-notaries.rst +++ b/docs/source/key-concepts-notaries.rst @@ -3,9 +3,9 @@ Notaries .. topic:: Summary - * *Notaries prevent "double-spends"* - * *Notaries may optionally also validate transactions* - * *A network can have several notaries, each running a different consensus algorithm* + * *Notary clusters prevent "double-spends"* + * *Notary clusters may optionally also validate transactions* + * *A network can have several notary clusters, each running a different consensus algorithm* Video ----- @@ -16,80 +16,83 @@ Video Overview -------- -A *notary* is a network service that provides **uniqueness consensus** by attesting that, for a given transaction, it -has not already signed other transactions that consumes any of the proposed transaction's input states. +A *notary cluster* is a network service that provides **uniqueness consensus** by attesting that, for a given +transaction, it has not already signed other transactions that consumes any of the proposed transaction's input states. -Upon being sent asked to notarise a transaction, a notary will either: +Upon being sent asked to notarise a transaction, a notary cluster will either: * Sign the transaction if it has not already signed other transactions consuming any of the proposed transaction's input states * Reject the transaction and flag that a double-spend attempt has occurred otherwise -In doing so, the notary provides the point of finality in the system. Until the notary's signature is obtained, parties -cannot be sure that an equally valid, but conflicting, transaction will not be regarded as the "valid" attempt to spend -a given input state. However, after the notary's signature is obtained, we can be sure that the proposed -transaction's input states had not already been consumed by a prior transaction. Hence, notarisation is the point -of finality in the system. +In doing so, the notary cluster provides the point of finality in the system. Until the notary cluster's signature is +obtained, parties cannot be sure that an equally valid, but conflicting, transaction will not be regarded as the +"valid" attempt to spend a given input state. However, after the notary cluster's signature is obtained, we can be sure +that the proposed transaction's input states have not already been consumed by a prior transaction. Hence, notarisation +is the point of finality in the system. -Every state has an appointed notary, and a notary will only notarise a transaction if it is the appointed notary -of all the transaction's input states. +Every state has an appointed notary cluster, and a notary cluster will only notarise a transaction if it is the +appointed notary cluster of all the transaction's input states. Consensus algorithms -------------------- -Corda has "pluggable" consensus, allowing notaries to choose a consensus algorithm based on their requirements in +Corda has "pluggable" consensus, allowing notary clusters to choose a consensus algorithm based on their requirements in terms of privacy, scalability, legal-system compatibility and algorithmic agility. -In particular, notaries may differ in terms of: +In particular, notary clusters may differ in terms of: -* **Structure** - a notary may be a single network node, a cluster of mutually-trusting nodes, or a cluster of +* **Structure** - a notary cluster may be a single node, several mutually-trusting nodes, or several mutually-distrusting nodes -* **Consensus algorithm** - a notary service may choose to run a high-speed, high-trust algorithm such as RAFT, a +* **Consensus algorithm** - a notary cluster may choose to run a high-speed, high-trust algorithm such as RAFT, a low-speed, low-trust algorithm such as BFT, or any other consensus algorithm it chooses Validation ^^^^^^^^^^ -A notary service must also decide whether or not to provide **validity consensus** by validating each transaction -before committing it. In making this decision, they face the following trade-off: +A notary cluster must also decide whether or not to provide **validity consensus** by validating each transaction +before committing it. In making this decision, it faces the following trade-off: * If a transaction **is not** checked for validity, it creates the risk of "denial of state" attacks, where a node knowingly builds an invalid transaction consuming some set of existing states and sends it to the - notary, causing the states to be marked as consumed + notary cluster, causing the states to be marked as consumed * If the transaction **is** checked for validity, the notary will need to see the full contents of the transaction and - its dependencies. This leaks potentially private data to the notary + its dependencies. This leaks potentially private data to the notary cluster There are several further points to keep in mind when evaluating this trade-off. In the case of the non-validating model, Corda's controlled data distribution model means that information on unconsumed states is not widely shared. -Additionally, Corda's permissioned network means that the notary can store to the identity of the party that created -the "denial of state" transaction, allowing the attack to be resolved off-ledger. +Additionally, Corda's permissioned network means that the notary cluster can store the identity of the party that +created the "denial of state" transaction, allowing the attack to be resolved off-ledger. In the case of the validating model, the use of anonymous, freshly-generated public keys instead of legal identities to -identify parties in a transaction limit the information the notary sees. +identify parties in a transaction limit the information the notary cluster sees. Multiple notaries ----------------- -Each Corda network can have multiple notaries, each potentially running a different consensus algorithm. This provides -several benefits: +Each Corda network can have multiple notary clusters, each potentially running a different consensus algorithm. This +provides several benefits: -* **Privacy** - we can have both validating and non-validating notary services on the same network, each running a - different algorithm. This allows nodes to choose the preferred notary on a per-transaction basis -* **Load balancing** - spreading the transaction load over multiple notaries allows higher transaction throughput for - the platform overall -* **Low latency** - latency can be minimised by choosing a notary physically closer to the transacting parties +* **Privacy** - we can have both validating and non-validating notary clusters on the same network, each running a + different algorithm. This allows nodes to choose the preferred notary cluster on a per-transaction basis +* **Load balancing** - spreading the transaction load over multiple notary clusters allows higher transaction + throughput for the platform overall +* **Low latency** - latency can be minimised by choosing a notary cluster physically closer to the transacting parties Changing notaries ^^^^^^^^^^^^^^^^^ -Remember that a notary will only sign a transaction if it is the appointed notary of all of the transaction's input -states. However, there are cases in which we may need to change a state's appointed notary. These include: +Remember that a notary cluster will only sign a transaction if it is the appointed notary cluster of all of the +transaction's input states. However, there are cases in which we may need to change a state's appointed notary cluster. +These include: -* When a single transaction needs to consume several states that have different appointed notaries -* When a node would prefer to use a different notary for a given transaction due to privacy or efficiency concerns +* When a single transaction needs to consume several states that have different appointed notary clusters +* When a node would prefer to use a different notary cluster for a given transaction due to privacy or efficiency + concerns -Before these transactions can be created, the states must first be repointed to all have the same notary. This is +Before these transactions can be created, the states must first all be repointed to the same notary cluster. This is achieved using a special notary-change transaction that takes: * A single input state -* An output state identical to the input state, except that the appointed notary has been changed +* An output state identical to the input state, except that the appointed notary cluster has been changed -The input state's appointed notary will sign the transaction if it doesn't constitute a double-spend, at which point -a state will enter existence that has all the properties of the old state, but has a different appointed notary. \ No newline at end of file +The input state's appointed notary cluster will sign the transaction if it doesn't constitute a double-spend, at which +point a state will enter existence that has all the properties of the old state, but has a different appointed notary +cluster. \ No newline at end of file diff --git a/docs/source/node-structure.rst b/docs/source/node-structure.rst new file mode 100644 index 0000000000..a098bcdfec --- /dev/null +++ b/docs/source/node-structure.rst @@ -0,0 +1,96 @@ +Node structure +============== + +.. contents:: + +A Corda node has the following structure: + +.. sourcecode:: none + + . + ├── additional-node-infos // Additional node infos to load into the network map cache, beyond what the network map server provides + ├── artemis // Stores buffered P2P messages + ├── brokers // Stores buffered RPC messages + ├── certificates // The node's certificates + ├── corda-webserver.jar // The built-in node webserver + ├── corda.jar // The core Corda libraries + ├── cordapps // The CorDapp JARs installed on the node + ├── drivers // Contains a Jolokia driver used to export JMX metrics + ├── logs // The node logs + ├── network-parameters // The network parameters automatically downloaded from the network map server + ├── node.conf // The node's configuration files + ├── persistence.mv.db // The node's database + └── shell-commands // Custom shell commands defined by the node owner + +The node is configured by editing its ``node.conf`` file (see :doc:`corda-configuration-file`). You install CorDapps on +the node by dropping CorDapp JARs into the ``cordapps`` folder. + +In development mode (i.e. when ``devMode = true``, see :doc:`corda-configuration-file`), the ``certificates`` +directory is filled with pre-configured keystores if the required keystores do not exist. This ensures that developers +can get the nodes working as quickly as possible. However, these pre-configured keystores are not secure, to learn more +see :doc:`permissioning`. + +.. _node_naming: + +Node naming +----------- +A node's name must be a valid X.500 distinguished name. In order to be compatible with other implementations +(particularly TLS implementations), we constrain the allowed X.500 name attribute types to a subset of the minimum +supported set for X.509 certificates (specified in RFC 3280), plus the locality attribute: + +* Organization (O) +* State (ST) +* Locality (L) +* Country (C) +* Organizational-unit (OU) +* Common name (CN) + +Note that the serial number is intentionally excluded in order to minimise scope for uncertainty in the distinguished name format. +The distinguished name qualifier has been removed due to technical issues; consideration was given to "Corda" as qualifier, +however the qualifier needs to reflect the compatibility zone, not the technology involved. There may be many Corda namespaces, +but only one R3 namespace on Corda. The ordering of attributes is important. + +``State`` should be avoided unless required to differentiate from other ``localities`` with the same or similar names at the +country level. For example, London (GB) would not need a ``state``, but St Ives would (there are two, one in Cornwall, one +in Cambridgeshire). As legal entities in Corda are likely to be located in major cities, this attribute is not expected to be +present in the majority of names, but is an option for the cases which require it. + +The name must also obey the following constraints: + +* The ``organisation``, ``locality`` and ``country`` attributes are present + + * The ``state``, ``organisational-unit`` and ``common name`` attributes are optional + +* The fields of the name have the following maximum character lengths: + + * Common name: 64 + * Organisation: 128 + * Organisation unit: 64 + * Locality: 64 + * State: 64 + +* The ``country`` attribute is a valid ISO 3166-1 two letter code in upper-case + +* All attributes must obey the following constraints: + + * Upper-case first letter + * Has at least two letters + * No leading or trailing whitespace + * Does not include the following characters: ``,`` , ``=`` , ``$`` , ``"`` , ``'`` , ``\`` + * Is in NFKC normalization form + * Does not contain the null character + * Only the latin, common and inherited unicode scripts are supported + +* The ``organisation`` field of the name also obeys the following constraints: + + * No double-spacing + + * This is to avoid right-to-left issues, debugging issues when we can't pronounce names over the phone, and + character confusability attacks + +External identifiers +^^^^^^^^^^^^^^^^^^^^ +Mappings to external identifiers such as Companies House nos., LEI, BIC, etc. should be stored in custom X.509 +certificate extensions. These values may change for operational reasons, without the identity they're associated with +necessarily changing, and their inclusion in the distinguished name would cause significant logistical complications. +The OID and format for these extensions will be described in a further specification. diff --git a/docs/source/setting-up-a-corda-network.rst b/docs/source/setting-up-a-corda-network.rst index 7a227d45e3..492bc4e24a 100644 --- a/docs/source/setting-up-a-corda-network.rst +++ b/docs/source/setting-up-a-corda-network.rst @@ -42,7 +42,8 @@ The most important fields regarding network configuration are: is the hostname *that must be externally resolvable by other nodes in the network*. In the above configuration this is the resolvable name of a machine in a VPN. * ``rpcAddress``: The address to which Artemis will bind for RPC calls. -* ``webAddress``: The address the webserver should bind. Note that the port must be distinct from that of ``p2pAddress`` and ``rpcAddress`` if they are on the same machine. +* ``webAddress``: The address the webserver should bind. Note that the port must be distinct from that of ``p2pAddress`` + and ``rpcAddress`` if they are on the same machine. Bootstrapping the network ~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -61,7 +62,7 @@ be done with the network bootstrapper. This is a tool that scans all the node co generate the network parameters file which is copied to the nodes' directories. It also copies each node's node-info file to every other node so that they can all transact with each other. -The bootstrapper tool can be downloaded from http://downloads.corda.net/network-bootstrapper-corda-X.Y.jar, where ``X`` +The bootstrapper tool can be downloaded from https://downloads.corda.net/network-bootstrapper-corda-X.Y.jar, where ``X`` is the major Corda version and ``Y`` is the minor Corda version. To use it, create a directory containing a node config file, ending in "_node.conf", for each node you want to create. diff --git a/docs/source/tutorial-cordapp.rst b/docs/source/tutorial-cordapp.rst index aba7647a98..ec4da46586 100644 --- a/docs/source/tutorial-cordapp.rst +++ b/docs/source/tutorial-cordapp.rst @@ -449,6 +449,10 @@ For example, you may end up with the following layout: After starting each node, the nodes will be able to see one another and agree IOUs among themselves. +.. note:: If you are using H2 and wish to use the same ``h2port`` value for all the nodes, then only assign them that + value after the nodes have been moved to their machines. The initial bootstrapping process requires access to the nodes' + databases and if they share the same H2 port then the process will fail. + Testing and debugging --------------------- diff --git a/docs/source/upgrade-notes.rst b/docs/source/upgrade-notes.rst index 1681d9ecc1..a188e94b2b 100644 --- a/docs/source/upgrade-notes.rst +++ b/docs/source/upgrade-notes.rst @@ -81,7 +81,35 @@ Build .. sourcecode:: shell - ext.corda_release_distribution = 'net.corda' // Corda (Open Source) + ext.corda_release_distribution = 'net.corda' // Corda (Open Source) + +Network Map Service +^^^^^^^^^^^^^^^^^^^ + +With the re-designed network map service the following changes need to be made: + +* The network map is no longer provided by a node and thus the ``networkMapService`` config is ignored. Instead the + network map is either provided by the compatibility zone (CZ) operator (who operates the doorman) and available + using the ``compatibilityZoneURL`` config, or is provided using signed node info files which are copied locally. + See :doc:`network-map` for more details, and :doc:`setting-up-a-corda-network` on how to use the network + bootstrapper for deploying a local network. + +* Configuration for a notary has been simplified. ``extraAdvertisedServiceIds``, ``notaryNodeAddress``, ``notaryClusterAddresses`` + and ``bftSMaRt`` configs have been replaced by a single ``notary`` config object. See :doc:`corda-configuration-file` + for more details. + +* The advertisement of the notary to the rest of the network, and its validation type, is no longer determined by the + ``extraAdvertisedServiceIds`` config. Instead it has been moved to the control of the network operator via + the introduction of network parameters. The network bootstrapper automatically includes the configured notaries + when generating the network parameters file for a local deployment. + +* Any nodes defined in a ``deployNodes`` gradle task performing the function of the network map can be removed, or the + ``NetworkMap`` parameter can be removed for any "controller" node which is both the network map and a notary. + +* For registering a node with the doorman the ``certificateSigningService`` config has been replaced by ``compatibilityZoneURL``. + +Corda Plugins +^^^^^^^^^^^^^ * Corda plugins have been modularised further so the following additional gradle entries are necessary: diff --git a/finance/build.gradle b/finance/build.gradle index f7bef861b1..2eaba4c794 100644 --- a/finance/build.gradle +++ b/finance/build.gradle @@ -28,7 +28,7 @@ sourceSets { srcDir file('src/integration-test/kotlin') } resources { - srcDir file('../../testing/test-utils/src/main/resources') + srcDir file('src/integration-test/resources') } } } diff --git a/finance/src/integration-test/kotlin/net/corda/finance/compat/CompatibilityTest.kt b/finance/src/integration-test/kotlin/net/corda/finance/compat/CompatibilityTest.kt new file mode 100644 index 0000000000..bf1dc2cb88 --- /dev/null +++ b/finance/src/integration-test/kotlin/net/corda/finance/compat/CompatibilityTest.kt @@ -0,0 +1,38 @@ +package net.corda.finance.compat + +import net.corda.core.serialization.SerializationDefaults +import net.corda.core.serialization.deserialize +import net.corda.core.serialization.serialize +import net.corda.core.transactions.SignedTransaction +import net.corda.finance.contracts.asset.Cash +import net.corda.testing.core.SerializationEnvironmentRule +import org.junit.Rule +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +// TODO: If this type of testing gets momentum, we can create a mini-framework that rides through list of files +// and performs necessary validation on all of them. +class CompatibilityTest { + + @Rule + @JvmField + val testSerialization = SerializationEnvironmentRule() + + @Test + fun issueCashTansactionReadTest() { + val inputStream = javaClass.classLoader.getResourceAsStream("compatibilityData/v3/node_transaction.dat") + assertNotNull(inputStream) + val inByteArray: ByteArray = inputStream.readBytes() + val transaction = inByteArray.deserialize(context = SerializationDefaults.STORAGE_CONTEXT) + assertNotNull(transaction) + val commands = transaction.tx.commands + assertEquals(1, commands.size) + assertTrue(commands.first().value is Cash.Commands.Issue) + + // Serialize back and check that representation is byte-to-byte identical to what it was originally. + val serializedForm = transaction.serialize(context = SerializationDefaults.STORAGE_CONTEXT) + assertTrue(inByteArray.contentEquals(serializedForm.bytes)) + } +} \ No newline at end of file diff --git a/finance/src/integration-test/resources/compatibilityData/v3/node_transaction.dat b/finance/src/integration-test/resources/compatibilityData/v3/node_transaction.dat new file mode 100644 index 0000000000..21392230dd Binary files /dev/null and b/finance/src/integration-test/resources/compatibilityData/v3/node_transaction.dat differ diff --git a/finance/src/main/kotlin/net/corda/finance/flows/CashConfigDataFlow.kt b/finance/src/main/kotlin/net/corda/finance/flows/CashConfigDataFlow.kt index c3b18dddac..d232afc6d3 100644 --- a/finance/src/main/kotlin/net/corda/finance/flows/CashConfigDataFlow.kt +++ b/finance/src/main/kotlin/net/corda/finance/flows/CashConfigDataFlow.kt @@ -27,7 +27,6 @@ import net.corda.finance.GBP import net.corda.finance.USD import net.corda.finance.flows.ConfigHolder.Companion.supportedCurrencies import java.io.IOException -import java.nio.file.Path import java.util.* // TODO Until apps have access to their own config, we'll hack things by first getting the baseDirectory, read the node.conf diff --git a/finance/src/main/kotlin/net/corda/finance/flows/CashExitFlow.kt b/finance/src/main/kotlin/net/corda/finance/flows/CashExitFlow.kt index 6086ef45d3..c6e85be18a 100644 --- a/finance/src/main/kotlin/net/corda/finance/flows/CashExitFlow.kt +++ b/finance/src/main/kotlin/net/corda/finance/flows/CashExitFlow.kt @@ -25,6 +25,9 @@ import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.ProgressTracker import net.corda.finance.contracts.asset.Cash import net.corda.finance.contracts.asset.cash.selection.AbstractCashSelection +import net.corda.finance.flows.AbstractCashFlow.Companion.FINALISING_TX +import net.corda.finance.flows.AbstractCashFlow.Companion.GENERATING_TX +import net.corda.finance.flows.AbstractCashFlow.Companion.SIGNING_TX import net.corda.finance.issuedBy import java.util.* diff --git a/finance/src/main/kotlin/net/corda/finance/flows/CashIssueFlow.kt b/finance/src/main/kotlin/net/corda/finance/flows/CashIssueFlow.kt index 13018f69d0..8614ca1318 100644 --- a/finance/src/main/kotlin/net/corda/finance/flows/CashIssueFlow.kt +++ b/finance/src/main/kotlin/net/corda/finance/flows/CashIssueFlow.kt @@ -19,6 +19,9 @@ import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.ProgressTracker import net.corda.finance.contracts.asset.Cash +import net.corda.finance.flows.AbstractCashFlow.Companion.FINALISING_TX +import net.corda.finance.flows.AbstractCashFlow.Companion.GENERATING_TX +import net.corda.finance.flows.AbstractCashFlow.Companion.SIGNING_TX import net.corda.finance.issuedBy import java.util.* diff --git a/finance/src/main/kotlin/net/corda/finance/flows/CashPaymentFlow.kt b/finance/src/main/kotlin/net/corda/finance/flows/CashPaymentFlow.kt index 7c809bcc65..4f5b785e9a 100644 --- a/finance/src/main/kotlin/net/corda/finance/flows/CashPaymentFlow.kt +++ b/finance/src/main/kotlin/net/corda/finance/flows/CashPaymentFlow.kt @@ -21,6 +21,10 @@ import net.corda.core.serialization.CordaSerializable import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.ProgressTracker import net.corda.finance.contracts.asset.Cash +import net.corda.finance.flows.AbstractCashFlow.Companion.FINALISING_TX +import net.corda.finance.flows.AbstractCashFlow.Companion.GENERATING_ID +import net.corda.finance.flows.AbstractCashFlow.Companion.GENERATING_TX +import net.corda.finance.flows.AbstractCashFlow.Companion.SIGNING_TX import java.util.* /** diff --git a/node-api/build.gradle b/node-api/build.gradle index 3ab4537b34..e4f45c19ad 100644 --- a/node-api/build.gradle +++ b/node-api/build.gradle @@ -44,7 +44,7 @@ dependencies { compile "de.javakaffee:kryo-serializers:0.41" // For AMQP serialisation. - compile "org.apache.qpid:proton-j:0.21.0" + compile "org.apache.qpid:proton-j:0.27.1" // SQL connection pooling library compile "com.zaxxer:HikariCP:$hikari_version" diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/exceptions/InternalNodeException.kt b/node-api/src/main/kotlin/net/corda/nodeapi/exceptions/InternalNodeException.kt index e0ee6c3c9b..0751e2681a 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/exceptions/InternalNodeException.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/exceptions/InternalNodeException.kt @@ -2,10 +2,12 @@ package net.corda.nodeapi.exceptions import net.corda.core.CordaRuntimeException import net.corda.core.contracts.TransactionVerificationException +import net.corda.core.flows.FlowException import java.io.InvalidClassException // could change to use package name matching but trying to avoid reflection for now private val whitelisted = setOf( + FlowException::class, InvalidClassException::class, RpcSerializableError::class, TransactionVerificationException::class @@ -23,7 +25,6 @@ class InternalNodeException(message: String) : CordaRuntimeException(message) { fun defaultMessage(): String = DEFAULT_MESSAGE fun obfuscateIfInternal(wrapped: Throwable): Throwable { - (wrapped as? CordaRuntimeException)?.setCause(null) return when { whitelisted.any { it.isInstance(wrapped) } -> wrapped diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/bridging/AMQPBridgeManager.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/bridging/AMQPBridgeManager.kt index 18406247a8..398ff0f16e 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/bridging/AMQPBridgeManager.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/bridging/AMQPBridgeManager.kt @@ -142,7 +142,7 @@ class AMQPBridgeManager(config: NodeSSLConfiguration, private val socksProxyConf private fun clientArtemisMessageHandler(artemisMessage: ClientMessage) { val data = ByteArray(artemisMessage.bodySize).apply { artemisMessage.bodyBuffer.readBytes(this) } - val properties = HashMap() + val properties = HashMap() for (key in P2PMessagingHeaders.whitelistedHeaders) { if (artemisMessage.containsProperty(key)) { var value = artemisMessage.getObjectProperty(key) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt index 7308fad8fa..d5b83a8c41 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt @@ -36,7 +36,8 @@ data class DatabaseConfig( val transactionIsolationLevel: TransactionIsolationLevel = TransactionIsolationLevel.REPEATABLE_READ, val schema: String? = null, val exportHibernateJMXStatistics: Boolean = false, - val hibernateDialect: String? = null + val hibernateDialect: String? = null, + val mappedSchemaCacheSize: Long = 100 ) // This class forms part of the node config and so any changes to it must be handled with care diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/HibernateConfiguration.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/HibernateConfiguration.kt index cbdf87b3f5..b7c3601cc0 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/HibernateConfiguration.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/HibernateConfiguration.kt @@ -10,6 +10,7 @@ package net.corda.nodeapi.internal.persistence +import com.github.benmanes.caffeine.cache.Caffeine import net.corda.core.internal.castIfPossible import net.corda.core.schemas.MappedSchema import net.corda.core.utilities.contextLogger @@ -30,7 +31,6 @@ import org.hibernate.type.descriptor.sql.BlobTypeDescriptor import org.hibernate.type.descriptor.sql.VarbinaryTypeDescriptor import java.lang.management.ManagementFactory import java.sql.Connection -import java.util.concurrent.ConcurrentHashMap import javax.management.ObjectName import javax.persistence.AttributeConverter @@ -61,8 +61,7 @@ class HibernateConfiguration( } } - // TODO: make this a guava cache or similar to limit ability for this to grow forever. - private val sessionFactories = ConcurrentHashMap, SessionFactory>() + private val sessionFactories = Caffeine.newBuilder().maximumSize(databaseConfig.mappedSchemaCacheSize).build, SessionFactory>() val sessionFactoryForRegisteredSchemas = schemas.let { logger.info("Init HibernateConfiguration for schemas: $it") @@ -70,7 +69,7 @@ class HibernateConfiguration( } /** @param key must be immutable, not just read-only. */ - fun sessionFactoryForSchemas(key: Set) = sessionFactories.computeIfAbsent(key, { makeSessionFactoryForSchemas(key) }) + fun sessionFactoryForSchemas(key: Set): SessionFactory = sessionFactories.get(key, ::makeSessionFactoryForSchemas)!! private fun makeSessionFactoryForSchemas(schemas: Set): SessionFactory { logger.info("Creating session factory for schemas: $schemas") diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/engine/NettyWritable.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/engine/NettyWritable.kt index 22f65fc092..240bc22e6a 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/engine/NettyWritable.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/engine/NettyWritable.kt @@ -11,6 +11,7 @@ package net.corda.nodeapi.internal.protonwrapper.engine import io.netty.buffer.ByteBuf +import org.apache.qpid.proton.codec.ReadableBuffer import org.apache.qpid.proton.codec.WritableBuffer import java.nio.ByteBuffer @@ -67,6 +68,10 @@ internal class NettyWritable(val nettyBuffer: ByteBuf) : WritableBuffer { nettyBuffer.writeBytes(payload) } + override fun put(payload: ReadableBuffer) { + nettyBuffer.writeBytes(payload.byteBuffer()) + } + override fun limit(): Int { return nettyBuffer.capacity() } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/messages/ApplicationMessage.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/messages/ApplicationMessage.kt index e59d4e7919..94e13c1c86 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/messages/ApplicationMessage.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/messages/ApplicationMessage.kt @@ -20,5 +20,5 @@ interface ApplicationMessage { val topic: String val destinationLegalName: String val destinationLink: NetworkHostAndPort - val applicationProperties: Map + val applicationProperties: Map } \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/messages/impl/ReceivedMessageImpl.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/messages/impl/ReceivedMessageImpl.kt index 42b79422b5..a9da11cdf2 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/messages/impl/ReceivedMessageImpl.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/messages/impl/ReceivedMessageImpl.kt @@ -26,7 +26,7 @@ internal class ReceivedMessageImpl(override val payload: ByteArray, override val sourceLink: NetworkHostAndPort, override val destinationLegalName: String, override val destinationLink: NetworkHostAndPort, - override val applicationProperties: Map, + override val applicationProperties: Map, private val channel: Channel, private val delivery: Delivery) : ReceivedMessage { data class MessageCompleter(val status: MessageStatus, val delivery: Delivery) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/messages/impl/SendableMessageImpl.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/messages/impl/SendableMessageImpl.kt index 546e499fff..7740b6d75e 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/messages/impl/SendableMessageImpl.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/messages/impl/SendableMessageImpl.kt @@ -25,7 +25,7 @@ internal class SendableMessageImpl(override val payload: ByteArray, override val topic: String, override val destinationLegalName: String, override val destinationLink: NetworkHostAndPort, - override val applicationProperties: Map) : SendableMessage { + override val applicationProperties: Map) : SendableMessage { var buf: ByteBuf? = null @Volatile var status: MessageStatus = MessageStatus.Unsent diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPClient.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPClient.kt index d8944c9cec..85922c5a29 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPClient.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPClient.kt @@ -216,7 +216,7 @@ class AMQPClient(val targets: List, fun createMessage(payload: ByteArray, topic: String, destinationLegalName: String, - properties: Map): SendableMessage { + properties: Map): SendableMessage { return SendableMessageImpl(payload, topic, destinationLegalName, currentTarget, properties) } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPServer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPServer.kt index e51e0baab2..50dc3dc9cf 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPServer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPServer.kt @@ -165,7 +165,7 @@ class AMQPServer(val hostName: String, topic: String, destinationLegalName: String, destinationLink: NetworkHostAndPort, - properties: Map): SendableMessage { + properties: Map): SendableMessage { val dest = InetSocketAddress(destinationLink.host, destinationLink.port) require(dest in clientChannels.keys) { "Destination not available" diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt index 9f2168dda8..950099ffcc 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt @@ -490,10 +490,10 @@ internal fun Type.asParameterizedType(): ParameterizedType { } internal fun Type.isSubClassOf(type: Type): Boolean { - return TypeToken.of(this).isSubtypeOf(type) + return TypeToken.of(this).isSubtypeOf(TypeToken.of(type).rawType) } -// ByteArrays, primtives and boxed primitives are not stored in the object history +// ByteArrays, primitives and boxed primitives are not stored in the object history internal fun suitableForObjectReference(type: Type): Boolean { val clazz = type.asClass() return type != ByteArray::class.java && (clazz != null && !clazz.isPrimitive && !Primitives.unwrap(clazz).isPrimitive) diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt index 0ee08d43eb..1fb12d495c 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt @@ -18,7 +18,7 @@ import net.corda.core.internal.div import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize -import net.corda.node.serialization.KryoServerSerializationScheme +import net.corda.node.serialization.kryo.KryoServerSerializationScheme import net.corda.nodeapi.internal.config.SSLConfiguration import net.corda.nodeapi.internal.createDevKeyStores import net.corda.nodeapi.internal.serialization.AllWhitelist diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt index 3e537fbc9f..1ac7a84d37 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt @@ -1320,5 +1320,30 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi C(12).serializeE() }.withMessageContaining("has synthetic fields and is likely a nested inner class") } + + interface DataClassByInterface { + val v : V + } + + @Test + fun dataClassBy() { + data class C (val s: String) : DataClassByInterface { + override val v: String = "-- $s" + } + + data class Inner(val wrapped: DataClassByInterface) : DataClassByInterface by wrapped { + override val v = wrapped.v + } + + val i = Inner(C("hello")) + + val bytes = SerializationOutput(testDefaultFactory()).serialize(i) + + try { + val i2 = DeserializationInput(testDefaultFactory()).deserialize(bytes) + } catch (e : NotSerializableException) { + throw Error ("Deserializing serialized \$C should not throw") + } + } } diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoTests.kt index a294b15695..e3021df901 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoTests.kt @@ -24,7 +24,7 @@ import net.corda.core.internal.FetchDataFlow import net.corda.core.serialization.* import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.sequence -import net.corda.node.serialization.KryoServerSerializationScheme +import net.corda.node.serialization.kryo.KryoServerSerializationScheme import net.corda.node.services.persistence.NodeAttachmentService import net.corda.nodeapi.internal.serialization.* import net.corda.testing.core.ALICE_NAME diff --git a/node/src/integration-test/kotlin/net/corda/node/CordappScanningDriverTest.kt b/node/src/integration-test/kotlin/net/corda/node/CordappScanningDriverTest.kt index ee0bed08cc..96ee0b67df 100644 --- a/node/src/integration-test/kotlin/net/corda/node/CordappScanningDriverTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/CordappScanningDriverTest.kt @@ -59,13 +59,13 @@ class CordappScanningDriverTest : IntegrationTest() { @StartableByRPC @InitiatingFlow - class ReceiveFlow(val otherParty: Party) : FlowLogic() { + class ReceiveFlow(private val otherParty: Party) : FlowLogic() { @Suspendable override fun call(): String = initiateFlow(otherParty).receive().unwrap { it } } @InitiatedBy(ReceiveFlow::class) - open class SendClassFlow(val otherPartySession: FlowSession) : FlowLogic() { + open class SendClassFlow(private val otherPartySession: FlowSession) : FlowLogic() { @Suspendable override fun call() = otherPartySession.send(javaClass.name) } diff --git a/node/src/integration-test/kotlin/net/corda/node/amqp/ProtonWrapperTests.kt b/node/src/integration-test/kotlin/net/corda/node/amqp/ProtonWrapperTests.kt index 39dedea30a..a70132b3a8 100644 --- a/node/src/integration-test/kotlin/net/corda/node/amqp/ProtonWrapperTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/amqp/ProtonWrapperTests.kt @@ -175,7 +175,7 @@ class ProtonWrapperTests { artemis.session.createQueue(sendAddress, RoutingType.ANYCAST, "queue", true) val consumer = artemis.session.createConsumer("queue") val testData = "Test".toByteArray() - val testProperty = mutableMapOf() + val testProperty = mutableMapOf() testProperty["TestProp"] = "1" val message = amqpClient.createMessage(testData, sendAddress, CHARLIE_NAME.toString(), testProperty) amqpClient.write(message) diff --git a/node/src/integration-test/kotlin/net/corda/node/services/rpc/RpcExceptionHandlingTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/rpc/RpcExceptionHandlingTest.kt index 5dbc736c85..03e9585e2f 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/rpc/RpcExceptionHandlingTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/rpc/RpcExceptionHandlingTest.kt @@ -41,61 +41,70 @@ class RpcExceptionHandlingTest : IntegrationTest() { @Test fun `rpc client handles exceptions thrown on node side`() { - - driver(DriverParameters(startNodesInProcess = true)) { + driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) { val node = startNode(NodeParameters(rpcUsers = users)).getOrThrow() - assertThatCode { node.rpc.startFlow(::Flow).returnValue.getOrThrow() }.isInstanceOfSatisfying(InternalNodeException::class.java) { exception -> - - assertThat(exception).hasNoCause() - assertThat(exception.stackTrace).isEmpty() - assertThat(exception.message).isEqualTo(InternalNodeException.defaultMessage()) - } + assertThatCode { node.rpc.startFlow(::Flow).returnValue.getOrThrow() } + .isInstanceOfSatisfying(InternalNodeException::class.java) { exception -> + assertThat(exception).hasNoCause() + assertThat(exception.stackTrace).isEmpty() + assertThat(exception.message).isEqualTo(InternalNodeException.defaultMessage()) + } } } @Test fun `rpc client handles client-relevant exceptions thrown on node side`() { - - driver(DriverParameters(startNodesInProcess = true)) { + driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) { val node = startNode(NodeParameters(rpcUsers = users)).getOrThrow() val clientRelevantMessage = "This is for the players!" - assertThatCode { node.rpc.startFlow(::ClientRelevantErrorFlow, clientRelevantMessage).returnValue.getOrThrow() }.isInstanceOfSatisfying(ClientRelevantException::class.java) { exception -> + assertThatCode { node.rpc.startFlow(::ClientRelevantErrorFlow, clientRelevantMessage).returnValue.getOrThrow() } + .isInstanceOfSatisfying(ClientRelevantException::class.java) { exception -> + assertThat(exception).hasNoCause() + assertThat(exception.stackTrace).isEmpty() + assertThat(exception.message).isEqualTo(clientRelevantMessage) + } + } + } - assertThat(exception).hasNoCause() - assertThat(exception.stackTrace).isEmpty() - assertThat(exception.message).isEqualTo(clientRelevantMessage) - } + @Test + fun `FlowException is received by the RPC client`() { + driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) { + val node = startNode(NodeParameters(rpcUsers = users)).getOrThrow() + val exceptionMessage = "Flow error!" + assertThatCode { node.rpc.startFlow(::FlowExceptionFlow, exceptionMessage).returnValue.getOrThrow() } + .isInstanceOfSatisfying(FlowException::class.java) { exception -> + assertThat(exception).hasNoCause() + assertThat(exception.stackTrace).isEmpty() + assertThat(exception.message).isEqualTo(exceptionMessage) + } } } @Test fun `rpc client handles exceptions thrown on counter-party side`() { - - driver(DriverParameters(startNodesInProcess = true)) { + driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) { val nodeA = startNode(NodeParameters(providedName = ALICE_NAME, rpcUsers = users)).getOrThrow() val nodeB = startNode(NodeParameters(providedName = BOB_NAME, rpcUsers = users)).getOrThrow() - assertThatCode { nodeA.rpc.startFlow(::InitFlow, nodeB.nodeInfo.singleIdentity()).returnValue.getOrThrow() }.isInstanceOfSatisfying(InternalNodeException::class.java) { exception -> - - assertThat(exception).hasNoCause() - assertThat(exception.stackTrace).isEmpty() - assertThat(exception.message).isEqualTo(InternalNodeException.defaultMessage()) - } + assertThatCode { nodeA.rpc.startFlow(::InitFlow, nodeB.nodeInfo.singleIdentity()).returnValue.getOrThrow() } + .isInstanceOfSatisfying(InternalNodeException::class.java) { exception -> + assertThat(exception).hasNoCause() + assertThat(exception.stackTrace).isEmpty() + assertThat(exception.message).isEqualTo(InternalNodeException.defaultMessage()) + } } } } @StartableByRPC class Flow : FlowLogic() { - @Suspendable override fun call(): String { - throw GenericJDBCException("Something went wrong!", SQLException("Oops!")) } } @@ -103,10 +112,8 @@ class Flow : FlowLogic() { @StartableByRPC @InitiatingFlow class InitFlow(private val party: Party) : FlowLogic() { - @Suspendable override fun call(): String { - val session = initiateFlow(party) return session.sendAndReceive("hey").unwrap { it } } @@ -114,10 +121,8 @@ class InitFlow(private val party: Party) : FlowLogic() { @InitiatedBy(InitFlow::class) class InitiatedFlow(private val initiatingSession: FlowSession) : FlowLogic() { - @Suspendable override fun call() { - initiatingSession.receive().unwrap { it } throw GenericJDBCException("Something went wrong!", SQLException("Oops!")) } @@ -125,10 +130,12 @@ class InitiatedFlow(private val initiatingSession: FlowSession) : FlowLogic() { - @Suspendable - override fun call(): String { + override fun call(): String = throw ClientRelevantException(message, SQLException("Oops!")) +} - throw ClientRelevantException(message, SQLException("Oops!")) - } -} \ No newline at end of file +@StartableByRPC +class FlowExceptionFlow(private val message: String) : FlowLogic() { + @Suspendable + override fun call(): String = throw FlowException(message) +} diff --git a/node/src/main/kotlin/net/corda/node/ArgsParser.kt b/node/src/main/kotlin/net/corda/node/NodeArgsParser.kt similarity index 91% rename from node/src/main/kotlin/net/corda/node/ArgsParser.kt rename to node/src/main/kotlin/net/corda/node/NodeArgsParser.kt index 98b15d3148..038d2d26b4 100644 --- a/node/src/main/kotlin/net/corda/node/ArgsParser.kt +++ b/node/src/main/kotlin/net/corda/node/NodeArgsParser.kt @@ -11,7 +11,7 @@ package net.corda.node import com.typesafe.config.ConfigFactory -import joptsimple.OptionParser +import joptsimple.OptionSet import joptsimple.util.EnumConverter import joptsimple.util.PathConverter import net.corda.core.internal.div @@ -19,21 +19,21 @@ import net.corda.core.internal.exists import net.corda.node.services.config.ConfigHelper import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.parseAsNodeConfiguration +import net.corda.node.utilities.AbstractArgsParser import net.corda.nodeapi.internal.config.UnknownConfigKeysPolicy import org.slf4j.event.Level -import java.io.PrintStream import java.nio.file.Path import java.nio.file.Paths // NOTE: Do not use any logger in this class as args parsing is done before the logger is setup. -class ArgsParser { - private val optionParser = OptionParser() +class NodeArgsParser : AbstractArgsParser() { // The intent of allowing a command line configurable directory and config path is to allow deployment flexibility. // Other general configuration should live inside the config file unless we regularly need temporary overrides on the command line private val baseDirectoryArg = optionParser .accepts("base-directory", "The node working directory where all the files are kept") .withRequiredArg() - .defaultsTo(".") + .withValuesConvertedBy(PathConverter()) + .defaultsTo(Paths.get(".")) private val configFileArg = optionParser .accepts("config-file", "The path to the config file") .withRequiredArg() @@ -53,7 +53,7 @@ class ArgsParser { .defaultsTo((Paths.get("certificates") / "network-root-truststore.jks")) private val networkRootTrustStorePasswordArg = optionParser.accepts("network-root-truststore-password", "Network root trust store password obtained from network operator.") .withRequiredArg() - private val unknownConfigKeysPolicy = optionParser.accepts("on-unknown-config-keys", "How to behave on unknown node configuration property keys: [WARN, FAIL, IGNORE].") + private val unknownConfigKeysPolicy = optionParser.accepts("on-unknown-config-keys", "How to behave on unknown node configuration.") .withRequiredArg() .withValuesConvertedBy(object : EnumConverter(UnknownConfigKeysPolicy::class.java) {}) .defaultsTo(UnknownConfigKeysPolicy.FAIL) @@ -62,16 +62,13 @@ class ArgsParser { private val justGenerateNodeInfoArg = optionParser.accepts("just-generate-node-info", "Perform the node start-up task necessary to generate its nodeInfo, save it to disk, then quit") private val bootstrapRaftClusterArg = optionParser.accepts("bootstrap-raft-cluster", "Bootstraps Raft cluster. The node forms a single node cluster (ignoring otherwise configured peer addresses), acting as a seed for other nodes to join the cluster.") - private val helpArg = optionParser.accepts("help").forHelp() - fun parse(vararg args: String): CmdLineOptions { - val optionSet = optionParser.parse(*args) + override fun doParse(optionSet: OptionSet): CmdLineOptions { require(!optionSet.has(baseDirectoryArg) || !optionSet.has(configFileArg)) { "${baseDirectoryArg.options()[0]} and ${configFileArg.options()[0]} cannot be specified together" } - val baseDirectory = Paths.get(optionSet.valueOf(baseDirectoryArg)).normalize().toAbsolutePath() + val baseDirectory = optionSet.valueOf(baseDirectoryArg).normalize().toAbsolutePath() val configFile = baseDirectory / optionSet.valueOf(configFileArg) - val help = optionSet.has(helpArg) val loggingLevel = optionSet.valueOf(loggerLevel) val logToConsole = optionSet.has(logToConsoleArg) val isRegistration = optionSet.has(isRegistrationArg) @@ -94,7 +91,6 @@ class ArgsParser { return CmdLineOptions(baseDirectory, configFile, - help, loggingLevel, logToConsole, registrationConfig, @@ -105,15 +101,12 @@ class ArgsParser { bootstrapRaftCluster, unknownConfigKeysPolicy) } - - fun printHelp(sink: PrintStream) = optionParser.printHelpOn(sink) } data class NodeRegistrationOption(val networkRootTrustStorePath: Path, val networkRootTrustStorePassword: String) data class CmdLineOptions(val baseDirectory: Path, val configFile: Path, - val help: Boolean, val loggingLevel: Level, val logToConsole: Boolean, val nodeRegistrationOption: NodeRegistrationOption?, diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 5bf2c8924f..d7cbd0b716 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -27,6 +27,7 @@ import net.corda.core.internal.FlowStateMachine import net.corda.core.internal.VisibleForTesting import net.corda.core.internal.concurrent.map import net.corda.core.internal.concurrent.openFuture +import net.corda.core.internal.notary.NotaryService import net.corda.core.internal.uncheckedCast import net.corda.core.messaging.* import net.corda.core.node.* diff --git a/node/src/main/kotlin/net/corda/node/internal/Node.kt b/node/src/main/kotlin/net/corda/node/internal/Node.kt index d9874d42cd..b9fd22b67a 100644 --- a/node/src/main/kotlin/net/corda/node/internal/Node.kt +++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt @@ -11,7 +11,7 @@ package net.corda.node.internal import com.codahale.metrics.JmxReporter -import net.corda.client.rpc.internal.KryoClientSerializationScheme +import net.corda.client.rpc.internal.serialization.kryo.KryoClientSerializationScheme import net.corda.core.concurrent.CordaFuture import net.corda.core.internal.concurrent.openFuture import net.corda.core.internal.concurrent.thenMatch @@ -35,7 +35,7 @@ import net.corda.node.internal.artemis.BrokerAddresses import net.corda.node.internal.cordapp.CordappLoader import net.corda.node.internal.security.RPCSecurityManagerImpl import net.corda.node.internal.security.RPCSecurityManagerWithAdditionalUser -import net.corda.node.serialization.KryoServerSerializationScheme +import net.corda.node.serialization.kryo.KryoServerSerializationScheme import net.corda.node.services.api.NodePropertiesStore import net.corda.node.services.api.SchemaService import net.corda.node.services.config.* @@ -96,10 +96,10 @@ open class Node(configuration: NodeConfiguration, } private val sameVmNodeCounter = AtomicInteger() - val scanPackagesSystemProperty = "net.corda.node.cordapp.scan.packages" - val scanPackagesSeparator = "," - @JvmStatic - protected fun makeCordappLoader(configuration: NodeConfiguration): CordappLoader { + const val scanPackagesSystemProperty = "net.corda.node.cordapp.scan.packages" + const val scanPackagesSeparator = "," + + private fun makeCordappLoader(configuration: NodeConfiguration): CordappLoader { return System.getProperty(scanPackagesSystemProperty)?.let { scanPackages -> CordappLoader.createDefaultWithTestPackages(configuration, scanPackages.split(scanPackagesSeparator)) } ?: CordappLoader.createDefault(configuration.baseDirectory) diff --git a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt index e694e39f54..baa8f3bfc0 100644 --- a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt +++ b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt @@ -11,7 +11,6 @@ package net.corda.node.internal import com.jcabi.manifests.Manifests -import joptsimple.OptionException import net.corda.core.internal.Emoji import net.corda.core.internal.concurrent.thenMatch import net.corda.core.internal.createDirectories @@ -40,14 +39,13 @@ import java.net.InetAddress import java.nio.file.Path import java.nio.file.Paths import java.util.* -import kotlin.system.exitProcess /** This class is responsible for starting a Node from command line arguments. */ open class NodeStartup(val args: Array) { companion object { private val logger by lazy { loggerFor() } // I guess this is lazy to allow for logging init, but why Node? - val LOGS_DIRECTORY_NAME = "logs" - val LOGS_CAN_BE_FOUND_IN_STRING = "Logs can be found in" + const val LOGS_DIRECTORY_NAME = "logs" + const val LOGS_CAN_BE_FOUND_IN_STRING = "Logs can be found in" } /** @@ -60,7 +58,7 @@ open class NodeStartup(val args: Array) { println("Corda will now exit...") return false } - val (argsParser, cmdlineOptions) = parseArguments() + val cmdlineOptions = NodeArgsParser().parseOrExit(*args) // We do the single node check before we initialise logging so that in case of a double-node start it // doesn't mess with the running node's logs. @@ -77,12 +75,6 @@ open class NodeStartup(val args: Array) { return true } - // Maybe render command line help. - if (cmdlineOptions.help) { - argsParser.printHelp(System.out) - return true - } - drawBanner(versionInfo) Node.printBasicNodeInfo(LOGS_CAN_BE_FOUND_IN_STRING, System.getProperty("log-path")) val conf = try { @@ -269,18 +261,6 @@ open class NodeStartup(val args: Array) { pidFileRw.write(ourProcessID.toByteArray()) } - private fun parseArguments(): Pair { - val argsParser = ArgsParser() - val cmdlineOptions = try { - argsParser.parse(*args) - } catch (ex: OptionException) { - println("Invalid command line arguments: ${ex.message}") - argsParser.printHelp(System.out) - exitProcess(1) - } - return Pair(argsParser, cmdlineOptions) - } - open protected fun initLogging(cmdlineOptions: CmdLineOptions) { val loggingLevel = cmdlineOptions.loggingLevel.name.toLowerCase(Locale.ENGLISH) System.setProperty("defaultLogLevel", loggingLevel) // These properties are referenced from the XML config file. diff --git a/node/src/main/kotlin/net/corda/node/internal/StartedNode.kt b/node/src/main/kotlin/net/corda/node/internal/StartedNode.kt index eca6eb902f..248dfa6e1e 100644 --- a/node/src/main/kotlin/net/corda/node/internal/StartedNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/StartedNode.kt @@ -13,11 +13,9 @@ package net.corda.node.internal import net.corda.core.flows.FlowLogic import net.corda.core.flows.InitiatedBy import net.corda.core.internal.VisibleForTesting +import net.corda.core.internal.notary.NotaryService import net.corda.core.messaging.CordaRPCOps import net.corda.core.node.NodeInfo -import net.corda.core.node.services.NotaryService -import net.corda.core.node.services.TransactionStorage -import net.corda.core.serialization.SerializeAsToken import net.corda.node.services.api.CheckpointStorage import net.corda.node.services.api.StartedNodeServices import net.corda.node.services.messaging.MessagingService diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt index cad494e03a..f7c3ec89c6 100644 --- a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt @@ -10,11 +10,9 @@ package net.corda.node.internal.cordapp +import com.github.benmanes.caffeine.cache.Caffeine import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner import io.github.lukehutch.fastclasspathscanner.scanner.ScanResult -import net.corda.core.contracts.Contract -import net.corda.core.contracts.UpgradedContract -import net.corda.core.contracts.UpgradedContractWithLegacyConstraint import net.corda.core.cordapp.Cordapp import net.corda.core.flows.* import net.corda.core.internal.* @@ -32,7 +30,6 @@ import net.corda.nodeapi.internal.serialization.DefaultWhitelist import org.apache.commons.collections4.map.LRUMap import java.lang.reflect.Modifier import java.net.JarURLConnection -import java.net.URI import java.net.URL import java.net.URLClassLoader import java.nio.file.Path @@ -40,6 +37,7 @@ import java.nio.file.Paths import java.nio.file.attribute.FileTime import java.time.Instant import java.util.* +import java.util.concurrent.ConcurrentHashMap import java.util.jar.JarOutputStream import java.util.zip.ZipEntry import kotlin.reflect.KClass @@ -77,10 +75,21 @@ class CordappLoader private constructor(private val cordappJarPaths: List, CordappLoader>(1000) + private val cordappLoadersCache = Caffeine.newBuilder().softValues().build, CordappLoader>() + private val generatedCordapps = ConcurrentHashMap() + + private fun simplifyScanPackages(scanPackages: List): List { + return scanPackages.sorted().fold(emptyList()) { listSoFar, packageName -> + when { + listSoFar.isEmpty() -> listOf(packageName) + packageName.startsWith(listSoFar.last()) -> listSoFar // Squash ["com.foo", "com.foo.bar"] into just ["com.foo"] + else -> listSoFar + packageName + } + } + } /** * Create a dev mode CordappLoader for test environments that creates and loads cordapps from the classpath @@ -93,8 +102,8 @@ class CordappLoader private constructor(private val cordappJarPaths: List): CordappLoader { - return cordappLoadersCache.computeIfAbsent(testPackages, { CordappLoader(testPackages.flatMap(this::createScanPackage)) }) + val urls = simplifyScanPackages(testPackages).flatMap(this::getPackageURLs) + return cordappLoadersCache.asMap().computeIfAbsent(urls, ::CordappLoader) } /** @@ -117,34 +127,33 @@ class CordappLoader private constructor(private val cordappJarPaths: List) = CordappLoader(scanJars.map { RestrictedURL(it, null) }) - private fun getCordappsPath(baseDir: Path): Path = baseDir / CORDAPPS_DIR_NAME - - private fun createScanPackage(scanPackage: String): List { + private fun getPackageURLs(scanPackage: String): List { val resource = scanPackage.replace('.', '/') return this::class.java.classLoader.getResources(resource) .asSequence() - .map { path -> - if (path.protocol == "jar") { + .map { url -> + if (url.protocol == "jar") { // When running tests from gradle this may be a corda module jar, so restrict to scanPackage: - RestrictedURL((path.openConnection() as JarURLConnection).jarFileURL, scanPackage) + RestrictedURL((url.openConnection() as JarURLConnection).jarFileURL, scanPackage) } else { // No need to restrict as createDevCordappJar has already done that: - RestrictedURL(createDevCordappJar(scanPackage, path, resource).toURL(), null) + RestrictedURL(createDevCordappJar(scanPackage, url, resource).toUri().toURL(), null) } } .toList() } /** Takes a package of classes and creates a JAR from them - only use in tests. */ - private fun createDevCordappJar(scanPackage: String, url: URL, jarPackageName: String): URI { + private fun createDevCordappJar(scanPackage: String, url: URL, resource: String): Path { return generatedCordapps.computeIfAbsent(url) { + // TODO Using the driver in out-of-process mode causes each node to have their own copy of the same dev CorDapps val cordappDir = (Paths.get("build") / "tmp" / "generated-test-cordapps").createDirectories() - val cordappJAR = cordappDir / "$scanPackage-${UUID.randomUUID()}.jar" - logger.info("Generating a test-only cordapp of classes discovered in $scanPackage at $cordappJAR") - JarOutputStream(cordappJAR.outputStream()).use { jos -> + val cordappJar = cordappDir / "$scanPackage-${UUID.randomUUID()}.jar" + logger.info("Generating a test-only CorDapp of classes discovered for package $scanPackage in $url: $cordappJar") + JarOutputStream(cordappJar.outputStream()).use { jos -> val scanDir = url.toPath() scanDir.walk { it.forEach { - val entryPath = "$jarPackageName/${scanDir.relativize(it).toString().replace('\\', '/')}" + val entryPath = "$resource/${scanDir.relativize(it).toString().replace('\\', '/')}" val time = FileTime.from(Instant.EPOCH) val entry = ZipEntry(entryPath).setCreationTime(time).setLastAccessTime(time).setLastModifiedTime(time) jos.putNextEntry(entry) @@ -154,22 +163,21 @@ class CordappLoader private constructor(private val cordappJarPaths: List { + private fun getNodeCordappURLs(baseDir: Path): List { + val cordappsDir = baseDir / CORDAPPS_DIR_NAME return if (!cordappsDir.exists()) { emptyList() } else { cordappsDir.list { - it.filter { it.isRegularFile() && it.toString().endsWith(".jar") }.map { RestrictedURL(it.toUri().toURL(), null) }.toList() + it.filter { it.toString().endsWith(".jar") }.map { RestrictedURL(it.toUri().toURL(), null) }.toList() } } } - private val generatedCordapps = mutableMapOf() - /** A list of the core RPC flows present in Corda */ private val coreRPCFlows = listOf( ContractUpgradeFlow.Initiate::class.java, @@ -263,7 +271,7 @@ class CordappLoader private constructor(private val cordappJarPaths: List(1000) private fun scanCordapp(cordappJarPath: RestrictedURL): RestrictedScanResult { - logger.info("Scanning CorDapp in $cordappJarPath") + logger.info("Scanning CorDapp in ${cordappJarPath.url}") return cachedScanResult.computeIfAbsent(cordappJarPath, { RestrictedScanResult(FastClasspathScanner().addClassLoader(appClassLoader).overrideClasspath(cordappJarPath.url).scan(), cordappJarPath.qualifiedNamePrefix) }) @@ -293,10 +301,9 @@ class CordappLoader private constructor(private val cordappJarPaths: List>() { + private object RpcObservableContextKey + + private val log = LoggerFactory.getLogger(javaClass) + fun createContext(observableContext: RPCServer.ObservableContext): SerializationContext { + return SerializationDefaults.RPC_SERVER_CONTEXT.withProperty(RpcServerObservableSerializer.RpcObservableContextKey, observableContext) + } + + override fun read(kryo: Kryo?, input: Input?, type: Class>?): Observable { + throw UnsupportedOperationException() + } + + override fun write(kryo: Kryo, output: Output, observable: Observable<*>) { + val observableId = Trace.InvocationId.newInstance() + val observableContext = kryo.context[RpcObservableContextKey] as RPCServer.ObservableContext + output.writeInvocationId(observableId) + val observableWithSubscription = ObservableSubscription( + // We capture [observableContext] in the subscriber. Note that all synchronisation/kryo borrowing + // must be done again within the subscriber + subscription = observable.materialize().subscribe( + object : Subscriber>() { + override fun onNext(observation: Notification<*>) { + if (!isUnsubscribed) { + val message = RPCApi.ServerToClient.Observation( + id = observableId, + content = observation, + deduplicationIdentity = observableContext.deduplicationIdentity + ) + observableContext.sendMessage(message) + } + } + + override fun onError(exception: Throwable) { + log.error("onError called in materialize()d RPC Observable", exception) + } + + override fun onCompleted() { + observableContext.clientAddressToObservables.compute(observableContext.clientAddress) { _, observables -> + if (observables != null) { + observables.remove(observableId) + if (observables.isEmpty()) { + null + } else { + observables + } + } else { + null + } + } + } + } + ) + ) + observableContext.clientAddressToObservables.compute(observableContext.clientAddress) { _, observables -> + if (observables == null) { + hashSetOf(observableId) + } else { + observables.add(observableId) + observables + } + } + observableContext.observableMap.put(observableId, observableWithSubscription) + } + + private fun Output.writeInvocationId(id: Trace.InvocationId) { + + writeString(id.value) + writeLong(id.timestamp.toEpochMilli()) + } +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt b/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt index 320d639664..b94c962d9f 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt @@ -11,10 +11,6 @@ package net.corda.node.services.messaging import co.paralleluniverse.common.util.SameThreadExecutor -import com.esotericsoftware.kryo.Kryo -import com.esotericsoftware.kryo.Serializer -import com.esotericsoftware.kryo.io.Input -import com.esotericsoftware.kryo.io.Output import com.github.benmanes.caffeine.cache.Cache import com.github.benmanes.caffeine.cache.Caffeine import com.github.benmanes.caffeine.cache.RemovalListener @@ -34,6 +30,7 @@ import net.corda.core.serialization.deserialize import net.corda.core.utilities.* import net.corda.node.internal.security.AuthorizingSubject import net.corda.node.internal.security.RPCSecurityManager +import net.corda.node.serialization.kryo.RpcServerObservableSerializer import net.corda.node.services.logging.pushToLoggingContext import net.corda.nodeapi.RPCApi import net.corda.nodeapi.externalTrace @@ -49,11 +46,7 @@ import org.apache.activemq.artemis.api.core.client.ActiveMQClient.DEFAULT_ACK_BA import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl import org.apache.activemq.artemis.api.core.management.CoreNotificationType import org.apache.activemq.artemis.api.core.management.ManagementHelper -import org.slf4j.LoggerFactory import org.slf4j.MDC -import rx.Notification -import rx.Observable -import rx.Subscriber import rx.Subscription import java.lang.reflect.InvocationTargetException import java.lang.reflect.Method @@ -496,74 +489,3 @@ class ObservableSubscription( ) typealias ObservableSubscriptionMap = Cache - -object RpcServerObservableSerializer : Serializer>() { - private object RpcObservableContextKey - - private val log = LoggerFactory.getLogger(javaClass) - fun createContext(observableContext: RPCServer.ObservableContext): SerializationContext { - return RPC_SERVER_CONTEXT.withProperty(RpcServerObservableSerializer.RpcObservableContextKey, observableContext) - } - - override fun read(kryo: Kryo?, input: Input?, type: Class>?): Observable { - throw UnsupportedOperationException() - } - - override fun write(kryo: Kryo, output: Output, observable: Observable<*>) { - val observableId = InvocationId.newInstance() - val observableContext = kryo.context[RpcObservableContextKey] as RPCServer.ObservableContext - output.writeInvocationId(observableId) - val observableWithSubscription = ObservableSubscription( - // We capture [observableContext] in the subscriber. Note that all synchronisation/kryo borrowing - // must be done again within the subscriber - subscription = observable.materialize().subscribe( - object : Subscriber>() { - override fun onNext(observation: Notification<*>) { - if (!isUnsubscribed) { - val message = RPCApi.ServerToClient.Observation( - id = observableId, - content = observation, - deduplicationIdentity = observableContext.deduplicationIdentity - ) - observableContext.sendMessage(message) - } - } - - override fun onError(exception: Throwable) { - log.error("onError called in materialize()d RPC Observable", exception) - } - - override fun onCompleted() { - observableContext.clientAddressToObservables.compute(observableContext.clientAddress) { _, observables -> - if (observables != null) { - observables.remove(observableId) - if (observables.isEmpty()) { - null - } else { - observables - } - } else { - null - } - } - } - } - ) - ) - observableContext.clientAddressToObservables.compute(observableContext.clientAddress) { _, observables -> - if (observables == null) { - hashSetOf(observableId) - } else { - observables.add(observableId) - observables - } - } - observableContext.observableMap.put(observableId, observableWithSubscription) - } - - private fun Output.writeInvocationId(id: InvocationId) { - - writeString(id.value) - writeLong(id.timestamp.toEpochMilli()) - } -} diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt index c76545bb4d..3e5e486c17 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt @@ -17,7 +17,9 @@ import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SignedData import net.corda.core.flows.* import net.corda.core.identity.Party -import net.corda.core.node.services.NotaryService +import net.corda.core.internal.notary.NotaryInternalException +import net.corda.core.internal.notary.NotaryService +import net.corda.core.internal.notary.verifySignature import net.corda.core.schemas.PersistentStateRef import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRt.kt b/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRt.kt index 4a9ac11c66..70da6f8a67 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRt.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRt.kt @@ -25,13 +25,17 @@ import bftsmart.tom.util.Extractor import net.corda.core.contracts.StateRef import net.corda.core.contracts.TimeWindow import net.corda.core.crypto.* -import net.corda.core.flows.* +import net.corda.core.flows.NotarisationPayload +import net.corda.core.flows.NotarisationRequestSignature +import net.corda.core.flows.NotaryError +import net.corda.core.flows.StateConsumptionDetails import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.internal.declaredField -import net.corda.core.internal.isConsumedByTheSameTx +import net.corda.core.internal.notary.NotaryInternalException +import net.corda.core.internal.notary.isConsumedByTheSameTx +import net.corda.core.internal.notary.validateTimeWindow import net.corda.core.internal.toTypedArray -import net.corda.core.internal.validateTimeWindow import net.corda.core.schemas.PersistentStateRef import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.SingletonSerializeAsToken diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/NonValidatingNotaryFlow.kt b/node/src/main/kotlin/net/corda/node/services/transactions/NonValidatingNotaryFlow.kt index e1c8fb7f23..86a099b4c1 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/NonValidatingNotaryFlow.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/NonValidatingNotaryFlow.kt @@ -12,15 +12,17 @@ package net.corda.node.services.transactions import co.paralleluniverse.fibers.Suspendable import net.corda.core.contracts.ComponentGroupEnum -import net.corda.core.flows.* -import net.corda.core.internal.validateRequestSignature -import net.corda.core.node.services.TrustedAuthorityNotaryService +import net.corda.core.flows.FlowSession +import net.corda.core.flows.NotarisationPayload +import net.corda.core.flows.NotarisationRequest +import net.corda.core.internal.notary.NotaryServiceFlow +import net.corda.core.internal.notary.TrustedAuthorityNotaryService import net.corda.core.transactions.ContractUpgradeFilteredTransaction import net.corda.core.transactions.CoreTransaction import net.corda.core.transactions.FilteredTransaction import net.corda.core.transactions.NotaryChangeWireTransaction -class NonValidatingNotaryFlow(otherSideSession: FlowSession, service: TrustedAuthorityNotaryService) : NotaryFlow.Service(otherSideSession, service) { +class NonValidatingNotaryFlow(otherSideSession: FlowSession, service: TrustedAuthorityNotaryService) : NotaryServiceFlow(otherSideSession, service) { /** * The received transaction is not checked for contract-validity, as that would require fully * resolving it into a [TransactionForVerification], for which the caller would have to reveal the whole transaction diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt b/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt index 23d155bfc3..2e4c394f64 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt @@ -16,13 +16,13 @@ import net.corda.core.crypto.SecureHash import net.corda.core.crypto.sha256 import net.corda.core.flows.NotarisationRequestSignature import net.corda.core.flows.NotaryError -import net.corda.core.flows.NotaryInternalException import net.corda.core.flows.StateConsumptionDetails import net.corda.core.identity.Party import net.corda.core.internal.ThreadBox -import net.corda.core.internal.isConsumedByTheSameTx -import net.corda.core.internal.validateTimeWindow -import net.corda.core.node.services.UniquenessProvider +import net.corda.core.internal.notary.NotaryInternalException +import net.corda.core.internal.notary.UniquenessProvider +import net.corda.core.internal.notary.isConsumedByTheSameTx +import net.corda.core.internal.notary.validateTimeWindow import net.corda.core.schemas.PersistentStateRef import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.SingletonSerializeAsToken diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/RaftNonValidatingNotaryService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/RaftNonValidatingNotaryService.kt index 0dd741b440..1ed394e470 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/RaftNonValidatingNotaryService.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/RaftNonValidatingNotaryService.kt @@ -11,9 +11,9 @@ package net.corda.node.services.transactions import net.corda.core.flows.FlowSession -import net.corda.core.flows.NotaryFlow +import net.corda.core.internal.notary.NotaryServiceFlow +import net.corda.core.internal.notary.TrustedAuthorityNotaryService import net.corda.core.node.ServiceHub -import net.corda.core.node.services.TrustedAuthorityNotaryService import java.security.PublicKey /** A non-validating notary service operated by a group of mutually trusting parties, uses the Raft algorithm to achieve consensus. */ @@ -22,7 +22,7 @@ class RaftNonValidatingNotaryService( override val notaryIdentityKey: PublicKey, override val uniquenessProvider: RaftUniquenessProvider ) : TrustedAuthorityNotaryService() { - override fun createServiceFlow(otherPartySession: FlowSession): NotaryFlow.Service { + override fun createServiceFlow(otherPartySession: FlowSession): NotaryServiceFlow { return NonValidatingNotaryFlow(otherPartySession, this) } diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/RaftTransactionCommitLog.kt b/node/src/main/kotlin/net/corda/node/services/transactions/RaftTransactionCommitLog.kt index e2b837c486..75f03a8370 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/RaftTransactionCommitLog.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/RaftTransactionCommitLog.kt @@ -28,8 +28,8 @@ import net.corda.core.crypto.sha256 import net.corda.core.flows.NotaryError import net.corda.core.flows.StateConsumptionDetails import net.corda.core.internal.VisibleForTesting -import net.corda.core.internal.isConsumedByTheSameTx -import net.corda.core.internal.validateTimeWindow +import net.corda.core.internal.notary.isConsumedByTheSameTx +import net.corda.core.internal.notary.validateTimeWindow import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.SerializationFactory import net.corda.core.serialization.deserialize diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt b/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt index 6e3a50d4e1..c2f8720aff 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt @@ -27,9 +27,9 @@ import net.corda.core.contracts.StateRef import net.corda.core.contracts.TimeWindow import net.corda.core.crypto.SecureHash import net.corda.core.flows.NotarisationRequestSignature -import net.corda.core.flows.NotaryInternalException import net.corda.core.identity.Party -import net.corda.core.node.services.UniquenessProvider +import net.corda.core.internal.notary.NotaryInternalException +import net.corda.core.internal.notary.UniquenessProvider import net.corda.core.schemas.PersistentStateRef import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.serialize diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/RaftValidatingNotaryService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/RaftValidatingNotaryService.kt index 258baa850d..ad5384c3d0 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/RaftValidatingNotaryService.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/RaftValidatingNotaryService.kt @@ -11,9 +11,9 @@ package net.corda.node.services.transactions import net.corda.core.flows.FlowSession -import net.corda.core.flows.NotaryFlow +import net.corda.core.internal.notary.NotaryServiceFlow +import net.corda.core.internal.notary.TrustedAuthorityNotaryService import net.corda.core.node.ServiceHub -import net.corda.core.node.services.TrustedAuthorityNotaryService import java.security.PublicKey /** A validating notary service operated by a group of mutually trusting parties, uses the Raft algorithm to achieve consensus. */ @@ -22,7 +22,7 @@ class RaftValidatingNotaryService( override val notaryIdentityKey: PublicKey, override val uniquenessProvider: RaftUniquenessProvider ) : TrustedAuthorityNotaryService() { - override fun createServiceFlow(otherPartySession: FlowSession): NotaryFlow.Service { + override fun createServiceFlow(otherPartySession: FlowSession): NotaryServiceFlow { return ValidatingNotaryFlow(otherPartySession, this) } diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/SimpleNotaryService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/SimpleNotaryService.kt index 48c06e4e44..6a25431672 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/SimpleNotaryService.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/SimpleNotaryService.kt @@ -11,8 +11,8 @@ package net.corda.node.services.transactions import net.corda.core.flows.FlowSession -import net.corda.core.flows.NotaryFlow -import net.corda.core.node.services.TrustedAuthorityNotaryService +import net.corda.core.internal.notary.NotaryServiceFlow +import net.corda.core.internal.notary.TrustedAuthorityNotaryService import net.corda.node.services.api.ServiceHubInternal import java.security.PublicKey @@ -20,7 +20,7 @@ import java.security.PublicKey class SimpleNotaryService(override val services: ServiceHubInternal, override val notaryIdentityKey: PublicKey) : TrustedAuthorityNotaryService() { override val uniquenessProvider = PersistentUniquenessProvider(services.clock) - override fun createServiceFlow(otherPartySession: FlowSession): NotaryFlow.Service = NonValidatingNotaryFlow(otherPartySession, this) + override fun createServiceFlow(otherPartySession: FlowSession): NotaryServiceFlow = NonValidatingNotaryFlow(otherPartySession, this) override fun start() {} override fun stop() {} diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryFlow.kt b/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryFlow.kt index 45e75e315f..1611734add 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryFlow.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryFlow.kt @@ -13,10 +13,14 @@ package net.corda.node.services.transactions import co.paralleluniverse.fibers.Suspendable import net.corda.core.contracts.TimeWindow import net.corda.core.contracts.TransactionVerificationException -import net.corda.core.flows.* +import net.corda.core.flows.FlowSession +import net.corda.core.flows.NotarisationPayload +import net.corda.core.flows.NotarisationRequest +import net.corda.core.flows.NotaryError import net.corda.core.internal.ResolveTransactionsFlow -import net.corda.core.internal.validateRequestSignature -import net.corda.core.node.services.TrustedAuthorityNotaryService +import net.corda.core.internal.notary.NotaryInternalException +import net.corda.core.internal.notary.NotaryServiceFlow +import net.corda.core.internal.notary.TrustedAuthorityNotaryService import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionWithSignatures import net.corda.core.transactions.WireTransaction @@ -28,7 +32,7 @@ import java.security.SignatureException * has its input states "blocked" by a transaction from another party, and needs to establish whether that transaction was * indeed valid. */ -class ValidatingNotaryFlow(otherSideSession: FlowSession, service: TrustedAuthorityNotaryService) : NotaryFlow.Service(otherSideSession, service) { +class ValidatingNotaryFlow(otherSideSession: FlowSession, service: TrustedAuthorityNotaryService) : NotaryServiceFlow(otherSideSession, service) { /** * Fully resolves the received transaction and its dependencies, runs contract verification logic and checks that * the transaction in question has all required signatures apart from the notary's. diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryService.kt index a840b2b37d..196d004c1e 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryService.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryService.kt @@ -11,8 +11,8 @@ package net.corda.node.services.transactions import net.corda.core.flows.FlowSession -import net.corda.core.flows.NotaryFlow -import net.corda.core.node.services.TrustedAuthorityNotaryService +import net.corda.core.internal.notary.NotaryServiceFlow +import net.corda.core.internal.notary.TrustedAuthorityNotaryService import net.corda.node.services.api.ServiceHubInternal import java.security.PublicKey @@ -20,7 +20,7 @@ import java.security.PublicKey class ValidatingNotaryService(override val services: ServiceHubInternal, override val notaryIdentityKey: PublicKey) : TrustedAuthorityNotaryService() { override val uniquenessProvider = PersistentUniquenessProvider(services.clock) - override fun createServiceFlow(otherPartySession: FlowSession): NotaryFlow.Service = ValidatingNotaryFlow(otherPartySession, this) + override fun createServiceFlow(otherPartySession: FlowSession): NotaryServiceFlow = ValidatingNotaryFlow(otherPartySession, this) override fun start() {} override fun stop() {} diff --git a/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt b/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt index f55465ca2c..d92cea8b42 100644 --- a/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt +++ b/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt @@ -138,10 +138,9 @@ class NodeVaultService( private fun makeUpdates(batch: Iterable, statesToRecord: StatesToRecord): List> { fun makeUpdate(tx: WireTransaction): Vault.Update? { - val myKeys = keyManagementService.filterMyKeys(tx.outputs.flatMap { it.data.participants.map { it.owningKey } }) val ourNewStates = when (statesToRecord) { StatesToRecord.NONE -> throw AssertionError("Should not reach here") - StatesToRecord.ONLY_RELEVANT -> tx.outputs.filter { isRelevant(it.data, myKeys.toSet()) } + StatesToRecord.ONLY_RELEVANT -> tx.outputs.filter { isRelevant(it.data, keyManagementService.filterMyKeys(tx.outputs.flatMap { it.data.participants.map { it.owningKey } }).toSet()) } StatesToRecord.ALL_VISIBLE -> tx.outputs }.map { tx.outRef(it.data) } @@ -166,12 +165,15 @@ class NodeVaultService( is ContractUpgradeWireTransaction -> tx.resolve(servicesForResolution, emptyList()) else -> throw IllegalArgumentException("Unsupported transaction type: ${tx.javaClass.name}") } - val myKeys = keyManagementService.filterMyKeys(ltx.outputs.flatMap { it.data.participants.map { it.owningKey } }) + val myKeys by lazy { keyManagementService.filterMyKeys(ltx.outputs.flatMap { it.data.participants.map { it.owningKey } }) } val (consumedStateAndRefs, producedStates) = ltx.inputs. zip(ltx.outputs). filter { (_, output) -> - if (statesToRecord == StatesToRecord.ONLY_RELEVANT) isRelevant(output.data, myKeys.toSet()) - else true + if (statesToRecord == StatesToRecord.ONLY_RELEVANT) { + isRelevant(output.data, myKeys.toSet()) + } else { + true + } }. unzip() @@ -414,65 +416,60 @@ class NodeVaultService( // TODO: revisit (use single instance of parser for all queries) val criteriaParser = HibernateQueryCriteriaParser(contractStateType, contractStateTypeMappings, criteriaBuilder, criteriaQuery, queryRootVaultStates) - try { - // parse criteria and build where predicates - criteriaParser.parse(criteria, sorting) + // parse criteria and build where predicates + criteriaParser.parse(criteria, sorting) - // prepare query for execution - val query = session.createQuery(criteriaQuery) + // prepare query for execution + val query = session.createQuery(criteriaQuery) - // pagination checks - if (!paging.isDefault) { - // pagination - if (paging.pageNumber < DEFAULT_PAGE_NUM) throw VaultQueryException("Page specification: invalid page number ${paging.pageNumber} [page numbers start from $DEFAULT_PAGE_NUM]") - if (paging.pageSize < 1) throw VaultQueryException("Page specification: invalid page size ${paging.pageSize} [must be a value between 1 and $MAX_PAGE_SIZE]") - } - - query.firstResult = if (paging.pageNumber > 0) (paging.pageNumber - 1) * paging.pageSize else 0 //some DB don't allow a negative value in SELECT TOP query - query.maxResults = paging.pageSize + 1 // detection too many results - - // execution - val results = query.resultList - - // final pagination check (fail-fast on too many results when no pagination specified) - if (paging.isDefault && results.size > DEFAULT_PAGE_SIZE) - throw VaultQueryException("Please specify a `PageSpecification` as there are more results [${results.size}] than the default page size [$DEFAULT_PAGE_SIZE]") - - val statesAndRefs: MutableList> = mutableListOf() - val statesMeta: MutableList = mutableListOf() - val otherResults: MutableList = mutableListOf() - val stateRefs = mutableSetOf() - - results.asSequence() - .forEachIndexed { index, result -> - if (result[0] is VaultSchemaV1.VaultStates) { - if (!paging.isDefault && index == paging.pageSize) // skip last result if paged - return@forEachIndexed - val vaultState = result[0] as VaultSchemaV1.VaultStates - val stateRef = StateRef(SecureHash.parse(vaultState.stateRef!!.txId!!), vaultState.stateRef!!.index!!) - stateRefs.add(stateRef) - statesMeta.add(Vault.StateMetadata(stateRef, - vaultState.contractStateClassName, - vaultState.recordedTime, - vaultState.consumedTime, - vaultState.stateStatus, - vaultState.notary, - vaultState.lockId, - vaultState.lockUpdateTime)) - } else { - // TODO: improve typing of returned other results - log.debug { "OtherResults: ${Arrays.toString(result.toArray())}" } - otherResults.addAll(result.toArray().asList()) - } - } - if (stateRefs.isNotEmpty()) - statesAndRefs.addAll(servicesForResolution.loadStates(stateRefs) as Collection>) - - return Vault.Page(states = statesAndRefs, statesMetadata = statesMeta, stateTypes = criteriaParser.stateTypes, totalStatesAvailable = totalStates, otherResults = otherResults) - } catch (e: java.lang.Exception) { - log.error(e.message) - throw e.cause ?: e + // pagination checks + if (!paging.isDefault) { + // pagination + if (paging.pageNumber < DEFAULT_PAGE_NUM) throw VaultQueryException("Page specification: invalid page number ${paging.pageNumber} [page numbers start from $DEFAULT_PAGE_NUM]") + if (paging.pageSize < 1) throw VaultQueryException("Page specification: invalid page size ${paging.pageSize} [must be a value between 1 and $MAX_PAGE_SIZE]") } + + query.firstResult = if (paging.pageNumber > 0) (paging.pageNumber - 1) * paging.pageSize else 0 //some DB don't allow a negative value in SELECT TOP query + query.maxResults = paging.pageSize + 1 // detection too many results + + // execution + val results = query.resultList + + // final pagination check (fail-fast on too many results when no pagination specified) + if (paging.isDefault && results.size > DEFAULT_PAGE_SIZE) + throw VaultQueryException("Please specify a `PageSpecification` as there are more results [${results.size}] than the default page size [$DEFAULT_PAGE_SIZE]") + + val statesAndRefs: MutableList> = mutableListOf() + val statesMeta: MutableList = mutableListOf() + val otherResults: MutableList = mutableListOf() + val stateRefs = mutableSetOf() + + results.asSequence() + .forEachIndexed { index, result -> + if (result[0] is VaultSchemaV1.VaultStates) { + if (!paging.isDefault && index == paging.pageSize) // skip last result if paged + return@forEachIndexed + val vaultState = result[0] as VaultSchemaV1.VaultStates + val stateRef = StateRef(SecureHash.parse(vaultState.stateRef!!.txId!!), vaultState.stateRef!!.index!!) + stateRefs.add(stateRef) + statesMeta.add(Vault.StateMetadata(stateRef, + vaultState.contractStateClassName, + vaultState.recordedTime, + vaultState.consumedTime, + vaultState.stateStatus, + vaultState.notary, + vaultState.lockId, + vaultState.lockUpdateTime)) + } else { + // TODO: improve typing of returned other results + log.debug { "OtherResults: ${Arrays.toString(result.toArray())}" } + otherResults.addAll(result.toArray().asList()) + } + } + if (stateRefs.isNotEmpty()) + statesAndRefs.addAll(servicesForResolution.loadStates(stateRefs) as Collection>) + + return Vault.Page(states = statesAndRefs, statesMetadata = statesMeta, stateTypes = criteriaParser.stateTypes, totalStatesAvailable = totalStates, otherResults = otherResults) } @Throws(VaultQueryException::class) diff --git a/node/src/main/kotlin/net/corda/node/utilities/AbstractArgsParser.kt b/node/src/main/kotlin/net/corda/node/utilities/AbstractArgsParser.kt new file mode 100644 index 0000000000..c31fdc1814 --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/utilities/AbstractArgsParser.kt @@ -0,0 +1,34 @@ +package net.corda.node.utilities + +import joptsimple.OptionException +import joptsimple.OptionParser +import joptsimple.OptionSet +import kotlin.system.exitProcess + +abstract class AbstractArgsParser { + protected val optionParser = OptionParser() + private val helpOption = optionParser.acceptsAll(listOf("h", "help"), "show help").forHelp() + + /** + * Parses the given [args] or exits the process if unable to, printing the help output to stderr. + * If the help option is specified then the process is also shutdown after printing the help output to stdout. + */ + fun parseOrExit(vararg args: String): T { + val optionSet = try { + optionParser.parse(*args) + } catch (e: OptionException) { + System.err.println(e.message ?: "Unable to parse arguments.") + optionParser.printHelpOn(System.err) + exitProcess(1) + } + if (optionSet.has(helpOption)) { + optionParser.printHelpOn(System.out) + exitProcess(0) + } + return doParse(optionSet) + } + + fun parse(vararg args: String): T = doParse(optionParser.parse(*args)) + + protected abstract fun doParse(optionSet: OptionSet): T +} \ No newline at end of file diff --git a/node/src/main/resources/build.properties b/node/src/main/resources/build.properties index 4dd9f57021..f61271471e 100644 --- a/node/src/main/resources/build.properties +++ b/node/src/main/resources/build.properties @@ -12,4 +12,4 @@ # Note: sadly, due to present limitation of IntelliJ-IDEA in processing resource files, these constants cannot be # imported from top-level 'constants.properties' file -jolokiaAgentVersion=1.3.7 \ No newline at end of file +jolokiaAgentVersion=1.5.0 diff --git a/node/src/test/kotlin/net/corda/node/ArgsParserTest.kt b/node/src/test/kotlin/net/corda/node/NodeArgsParserTest.kt similarity index 98% rename from node/src/test/kotlin/net/corda/node/ArgsParserTest.kt rename to node/src/test/kotlin/net/corda/node/NodeArgsParserTest.kt index 83d6ceac64..3e8437ecba 100644 --- a/node/src/test/kotlin/net/corda/node/ArgsParserTest.kt +++ b/node/src/test/kotlin/net/corda/node/NodeArgsParserTest.kt @@ -25,8 +25,8 @@ import java.nio.file.Paths import kotlin.test.assertEquals import kotlin.test.assertNotNull -class ArgsParserTest { - private val parser = ArgsParser() +class NodeArgsParserTest { + private val parser = NodeArgsParser() companion object { private lateinit var workingDirectory: Path @@ -45,7 +45,6 @@ class ArgsParserTest { assertThat(parser.parse()).isEqualTo(CmdLineOptions( baseDirectory = workingDirectory, configFile = workingDirectory / "node.conf", - help = false, logToConsole = false, loggingLevel = Level.INFO, nodeRegistrationOption = null, @@ -176,7 +175,6 @@ class ArgsParserTest { @Test fun `on-unknown-config-keys options`() { - UnknownConfigKeysPolicy.values().forEach { onUnknownConfigKeyPolicy -> val cmdLineOptions = parser.parse("--on-unknown-config-keys", onUnknownConfigKeyPolicy.name) assertThat(cmdLineOptions.unknownConfigKeysPolicy).isEqualTo(onUnknownConfigKeyPolicy) diff --git a/node/src/test/kotlin/net/corda/node/internal/cordapp/CordappLoaderTest.kt b/node/src/test/kotlin/net/corda/node/internal/cordapp/CordappLoaderTest.kt index b4c1787bfa..b65bde311a 100644 --- a/node/src/test/kotlin/net/corda/node/internal/cordapp/CordappLoaderTest.kt +++ b/node/src/test/kotlin/net/corda/node/internal/cordapp/CordappLoaderTest.kt @@ -19,45 +19,39 @@ import java.nio.file.Paths @InitiatingFlow class DummyFlow : FlowLogic() { @Suspendable - override fun call() { - } + override fun call() = Unit } @InitiatedBy(DummyFlow::class) -class LoaderTestFlow(unusedSession: FlowSession) : FlowLogic() { +class LoaderTestFlow(@Suppress("UNUSED_PARAMETER") unusedSession: FlowSession) : FlowLogic() { @Suspendable - override fun call() { - } + override fun call() = Unit } @SchedulableFlow class DummySchedulableFlow : FlowLogic() { @Suspendable - override fun call() { - } + override fun call() = Unit } @StartableByRPC class DummyRPCFlow : FlowLogic() { @Suspendable - override fun call() { - } + override fun call() = Unit } class CordappLoaderTest { private companion object { - val testScanPackages = listOf("net.corda.node.internal.cordapp") - val isolatedContractId = "net.corda.finance.contracts.isolated.AnotherDummyContract" - val isolatedFlowName = "net.corda.finance.contracts.isolated.IsolatedDummyFlow\$Initiator" + const val testScanPackage = "net.corda.node.internal.cordapp" + const val isolatedContractId = "net.corda.finance.contracts.isolated.AnotherDummyContract" + const val isolatedFlowName = "net.corda.finance.contracts.isolated.IsolatedDummyFlow\$Initiator" } @Test fun `test that classes that aren't in cordapps aren't loaded`() { // Basedir will not be a corda node directory so the dummy flow shouldn't be recognised as a part of a cordapp val loader = CordappLoader.createDefault(Paths.get(".")) - assertThat(loader.cordapps) - .hasSize(1) - .contains(CordappLoader.coreCordapp) + assertThat(loader.cordapps).containsOnly(CordappLoader.coreCordapp) } @Test @@ -81,7 +75,7 @@ class CordappLoaderTest { @Test fun `flows are loaded by loader`() { - val loader = CordappLoader.createWithTestPackages(testScanPackages) + val loader = CordappLoader.createWithTestPackages(listOf(testScanPackage)) val actual = loader.cordapps.toTypedArray() // One core cordapp, one cordapp from this source tree, and two others due to identically named locations @@ -95,6 +89,20 @@ class CordappLoaderTest { assertThat(actualCordapp.schedulableFlows).first().hasSameClassAs(DummySchedulableFlow::class.java) } + @Test + fun `duplicate packages are ignored`() { + val loader = CordappLoader.createWithTestPackages(listOf(testScanPackage, testScanPackage)) + val cordapps = loader.cordapps.filter { LoaderTestFlow::class.java in it.initiatedFlows } + assertThat(cordapps).hasSize(1) + } + + @Test + fun `sub-packages are ignored`() { + val loader = CordappLoader.createWithTestPackages(listOf("net.corda", testScanPackage)) + val cordapps = loader.cordapps.filter { LoaderTestFlow::class.java in it.initiatedFlows } + assertThat(cordapps).hasSize(1) + } + // This test exists because the appClassLoader is used by serialisation and we need to ensure it is the classloader // being used internally. Later iterations will use a classloader per cordapp and this test can be retired. @Test diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/PersistentUniquenessProviderTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/PersistentUniquenessProviderTests.kt index 1ae68528f9..7e62930b50 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/PersistentUniquenessProviderTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/PersistentUniquenessProviderTests.kt @@ -16,8 +16,8 @@ import net.corda.core.crypto.SecureHash import net.corda.core.crypto.sha256 import net.corda.core.flows.NotarisationRequestSignature import net.corda.core.flows.NotaryError -import net.corda.core.flows.NotaryInternalException import net.corda.core.identity.CordaX500Name +import net.corda.core.internal.notary.NotaryInternalException import net.corda.node.internal.configureDatabase import net.corda.node.services.schema.NodeSchemaService import net.corda.nodeapi.internal.persistence.CordaPersistence diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt index a5799598fa..04b336e6d5 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt @@ -17,7 +17,7 @@ import net.corda.core.contracts.StateRef import net.corda.core.crypto.* import net.corda.core.flows.* import net.corda.core.identity.Party -import net.corda.core.internal.generateSignature +import net.corda.core.internal.notary.generateSignature import net.corda.core.messaging.MessageRecipients import net.corda.core.node.ServiceHub import net.corda.core.serialization.deserialize diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt index fdad7e332d..980189f503 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt @@ -108,17 +108,17 @@ open class VaultQueryTests { // register additional identities val databaseAndServices = makeTestDatabaseAndMockServices( cordappPackages, - makeTestIdentityService(Companion.MEGA_CORP_IDENTITY, Companion.MINI_CORP_IDENTITY, Companion.dummyCashIssuer.identity, Companion.dummyNotary.identity), + makeTestIdentityService(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, dummyCashIssuer.identity, dummyNotary.identity), Companion.megaCorp, - moreKeys = Companion.DUMMY_NOTARY_KEY) + moreKeys = DUMMY_NOTARY_KEY) database = databaseAndServices.first services = databaseAndServices.second - vaultFiller = VaultFiller(services, Companion.dummyNotary) - vaultFillerCashNotary = VaultFiller(services, Companion.dummyNotary, Companion.CASH_NOTARY) - notaryServices = MockServices(cordappPackages, Companion.dummyNotary, rigorousMock(), Companion.dummyCashIssuer.keyPair, Companion.BOC_KEY, Companion.MEGA_CORP_KEY) + vaultFiller = VaultFiller(services, dummyNotary) + vaultFillerCashNotary = VaultFiller(services, dummyNotary, CASH_NOTARY) + notaryServices = MockServices(cordappPackages, dummyNotary, rigorousMock(), dummyCashIssuer.keyPair, BOC_KEY, MEGA_CORP_KEY) identitySvc = services.identityService // Register all of the identities we're going to use - (notaryServices.myInfo.legalIdentitiesAndCerts + Companion.BOC_IDENTITY + Companion.CASH_NOTARY_IDENTITY + Companion.MINI_CORP_IDENTITY + Companion.MEGA_CORP_IDENTITY).forEach { identity -> + (notaryServices.myInfo.legalIdentitiesAndCerts + BOC_IDENTITY + CASH_NOTARY_IDENTITY + MINI_CORP_IDENTITY + MEGA_CORP_IDENTITY).forEach { identity -> services.identityService.verifyAndRegisterIdentity(identity) } } diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/MyCustomNotaryService.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/MyCustomNotaryService.kt index c0725e6476..606b5ebf2d 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/MyCustomNotaryService.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/MyCustomNotaryService.kt @@ -15,10 +15,11 @@ import net.corda.core.contracts.TimeWindow import net.corda.core.contracts.TransactionVerificationException import net.corda.core.flows.* import net.corda.core.internal.ResolveTransactionsFlow -import net.corda.core.internal.validateRequestSignature +import net.corda.core.internal.notary.NotaryInternalException +import net.corda.core.internal.notary.NotaryServiceFlow +import net.corda.core.internal.notary.TrustedAuthorityNotaryService import net.corda.core.node.AppServiceHub import net.corda.core.node.services.CordaService -import net.corda.core.node.services.TrustedAuthorityNotaryService import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionWithSignatures import net.corda.core.transactions.WireTransaction @@ -46,7 +47,7 @@ class MyCustomValidatingNotaryService(override val services: AppServiceHub, over @Suppress("UNUSED_PARAMETER") // START 2 -class MyValidatingNotaryFlow(otherSide: FlowSession, service: MyCustomValidatingNotaryService) : NotaryFlow.Service(otherSide, service) { +class MyValidatingNotaryFlow(otherSide: FlowSession, service: MyCustomValidatingNotaryService) : NotaryServiceFlow(otherSide, service) { /** * The received transaction is checked for contract-validity, for which the caller also has to to reveal the whole * transaction dependency chain. diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt index cccfd447b1..233e91a97a 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt @@ -11,7 +11,7 @@ package net.corda.testing.node.internal import net.corda.client.mock.Generator -import net.corda.client.rpc.internal.KryoClientSerializationScheme +import net.corda.client.rpc.internal.serialization.kryo.KryoClientSerializationScheme import net.corda.client.rpc.internal.RPCClient import net.corda.client.rpc.internal.CordaRPCClientConfigurationImpl import net.corda.core.concurrent.CordaFuture diff --git a/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt b/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt index 7cdc703b97..8504c7ecd5 100644 --- a/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt +++ b/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt @@ -12,7 +12,7 @@ package net.corda.smoketesting import net.corda.client.rpc.CordaRPCClient import net.corda.client.rpc.CordaRPCConnection -import net.corda.client.rpc.internal.KryoClientSerializationScheme +import net.corda.client.rpc.internal.serialization.kryo.KryoClientSerializationScheme import net.corda.core.internal.* import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.contextLogger diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/core/SerializationTestHelpers.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/core/SerializationTestHelpers.kt index 30f5594439..6aadfd9642 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/core/SerializationTestHelpers.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/core/SerializationTestHelpers.kt @@ -12,19 +12,10 @@ package net.corda.testing.core import com.nhaarman.mockito_kotlin.any import com.nhaarman.mockito_kotlin.doAnswer -import com.nhaarman.mockito_kotlin.doNothing import com.nhaarman.mockito_kotlin.whenever -import net.corda.client.rpc.internal.KryoClientSerializationScheme -import net.corda.core.DoNotImplement import net.corda.core.internal.staticField import net.corda.core.serialization.internal.SerializationEnvironment -import net.corda.core.serialization.internal.SerializationEnvironmentImpl -import net.corda.core.serialization.internal._globalSerializationEnv import net.corda.core.serialization.internal.effectiveSerializationEnv -import net.corda.node.serialization.KryoServerSerializationScheme -import net.corda.nodeapi.internal.serialization.* -import net.corda.nodeapi.internal.serialization.amqp.AMQPClientSerializationScheme -import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme import net.corda.testing.common.internal.asContextEnv import net.corda.testing.internal.createTestSerializationEnv import net.corda.testing.internal.inVMExecutors @@ -34,7 +25,6 @@ import org.apache.activemq.artemis.core.remoting.impl.invm.InVMConnector import org.junit.rules.TestRule import org.junit.runner.Description import org.junit.runners.model.Statement -import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ExecutorService import java.util.concurrent.Executors diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalSerializationTestHelpers.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalSerializationTestHelpers.kt index f2bf98947e..03f06bfd56 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalSerializationTestHelpers.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalSerializationTestHelpers.kt @@ -12,10 +12,10 @@ package net.corda.testing.internal import com.nhaarman.mockito_kotlin.doNothing import com.nhaarman.mockito_kotlin.whenever -import net.corda.client.rpc.internal.KryoClientSerializationScheme +import net.corda.client.rpc.internal.serialization.kryo.KryoClientSerializationScheme import net.corda.core.DoNotImplement import net.corda.core.serialization.internal.* -import net.corda.node.serialization.KryoServerSerializationScheme +import net.corda.node.serialization.kryo.KryoServerSerializationScheme import net.corda.nodeapi.internal.serialization.* import net.corda.nodeapi.internal.serialization.amqp.AMQPClientSerializationScheme import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme diff --git a/tools/demobench/build.gradle b/tools/demobench/build.gradle index dd36e0a4b5..b735c0b48c 100644 --- a/tools/demobench/build.gradle +++ b/tools/demobench/build.gradle @@ -49,15 +49,6 @@ repositories { } } -configurations.all { - resolutionStrategy { - // Force TornadoFX to use the same version of Kotlin as Corda. - force "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - force "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - force "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" - } -} - dependencies { // TornadoFX: A lightweight Kotlin framework for working with JavaFX UI's. compile "no.tornado:tornadofx:$tornadofx_version" diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/DemoBench.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/DemoBench.kt index 3cb7b25f4f..04a6de243b 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/DemoBench.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/DemoBench.kt @@ -11,7 +11,7 @@ package net.corda.demobench import javafx.scene.image.Image -import net.corda.client.rpc.internal.KryoClientSerializationScheme +import net.corda.client.rpc.internal.serialization.kryo.KryoClientSerializationScheme import net.corda.core.serialization.internal.SerializationEnvironmentImpl import net.corda.core.serialization.internal.nodeSerializationEnv import net.corda.demobench.views.DemoBenchView