Merge pull request #803 from corda/andrius/os-merge

OS merge
This commit is contained in:
Andrius Dagys 2018-05-04 08:50:55 +01:00 committed by GitHub
commit 253de5dc75
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
99 changed files with 1240 additions and 1110 deletions

View File

@ -1378,7 +1378,6 @@ public @interface net.corda.core.flows.InitiatingFlow
public <init>(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 <init>(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 <init>(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 <init>(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 <init>(String)
public <init>(String, Throwable)
##
public final class net.corda.core.flows.TransactionParts extends java.lang.Object
public <init>(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 <init>(String)
public <init>(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 <init>()
@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 <init>()
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 <init>(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 <init>(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 <init>(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 <init>(String)
##

View File

@ -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 <james@r3.com>, 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 <james@r3.com>, 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)
* Zabrina Smith (Northern Trust)
* zorenmith (Northern Trust)

View File

@ -186,7 +186,7 @@ class TunnelingBridgeReceiverService(val conf: BridgeConfiguration,
log.debug { "Received message from ${innerMessage.sourceLegalName}" }
val onwardMessage = object : ReceivedMessage {
override val topic: String = innerMessage.topic
override val applicationProperties: Map<Any?, Any?> = innerMessage.originalHeaders.toMap()
override val applicationProperties: Map<String, Any?> = innerMessage.originalHeaders.toMap()
override val payload: ByteArray = innerMessage.originalPayload
override val sourceLegalName: String = innerMessage.sourceLegalName.toString()
override val sourceLink: NetworkHostAndPort = receivedMessage.sourceLink

View File

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

View File

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

View File

@ -11,8 +11,8 @@
package net.corda.client.rpc
import net.corda.client.rpc.internal.CordaRPCClientConfigurationImpl
import net.corda.client.rpc.internal.KryoClientSerializationScheme
import net.corda.client.rpc.internal.RPCClient
import net.corda.client.rpc.internal.serialization.kryo.KryoClientSerializationScheme
import net.corda.core.context.Actor
import net.corda.core.context.Trace
import net.corda.core.messaging.CordaRPCOps

View File

@ -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<Observable<*>>
)
/**
* A [Serializer] to deserialise Observables once the corresponding Kryo instance has been provided with an [ObservableContext].
*/
object RpcClientObservableSerializer : Serializer<Observable<*>>() {
private object RpcObservableContextKey
fun createContext(serializationContext: SerializationContext, observableContext: ObservableContext): SerializationContext {
return serializationContext.withProperty(RpcObservableContextKey, observableContext)
}
private fun <T> pinInSubscriptions(observable: Observable<T>, hardReferenceStore: MutableSet<Observable<*>>): Observable<T> {
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<*>>): Observable<Any> {
val observableContext = kryo.context[RpcObservableContextKey] as ObservableContext
val observableId = input.readInvocationId() ?: throw IllegalStateException("Unable to read invocationId from Input.")
val observable = UnicastSubject.create<Notification<*>>()
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)
}
}

View File

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

View File

@ -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<Observable<*>>() {
private object RpcObservableContextKey
fun createContext(serializationContext: SerializationContext, observableContext: ObservableContext): SerializationContext {
return serializationContext.withProperty(RpcObservableContextKey, observableContext)
}
private fun <T> pinInSubscriptions(observable: Observable<T>, hardReferenceStore: MutableSet<Observable<*>>): Observable<T> {
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<*>>): Observable<Any> {
val observableContext = kryo.context[RpcObservableContextKey] as ObservableContext
val observableId = input.readInvocationId() ?: throw IllegalStateException("Unable to read invocationId from Input.")
val observable = UnicastSubject.create<Notification<*>>()
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)
}
}

View File

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

View File

@ -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<StateRef, StateConsumptionDetails>
) : 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
)

View File

@ -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<Void?>() {
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<NotarisationPayload>().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<StateRef>) {
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<StateRef>, 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<StateRef, StateConsumptionDetails>
) : 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
)
}

View File

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

View File

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

View File

@ -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<StateRef, StateConsumptionDetails>): 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
}

View File

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

View File

@ -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<Void?>() {
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<NotarisationPayload>().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<StateRef>) {
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<StateRef>, 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")

View File

@ -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<StateRef, StateConsumptionDetails>): 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
}

View File

@ -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<StateRef>, 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.
}

View File

@ -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<StateRef>,
txId: SecureHash,
callerIdentity: Party,
requestSignature: NotarisationRequestSignature,
timeWindow: TimeWindow? = null
)
}

View File

@ -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<Void?>
}
/**
* 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<StateRef>, 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")
}

View File

@ -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<StateRef>,
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<StateRef, ConsumingTx>)
/**
* 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)

View File

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

View File

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

View File

@ -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 <https://github.com/corda/corda/blob/master/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 <http://slack.corda.net/>`_
* Browse the `Corda GitHub issues <https://github.com/corda/corda/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 <https://r3-cev.atlassian.net/issues/?jql=labels%20%3D%20HelpWanted>`_
* 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 <https://github.com/corda/corda/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 <http://slack.corda.net/>`_
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 </testing>`.
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 </building-against-master>`.
You can test your changes against CorDapps defined in other repos by following the instructions :doc:`here </building-against-master>`.
Updating the docs
-----------------
Any changes to Corda's public API must be documented as follows:
1. Update the relevant `.rst file(s) <https://github.com/corda/corda/tree/master/docs/source>`_
2. Include the change in the :doc:`changelog </changelog>` and :doc:`release notes </release-notes>` where applicable
3. :doc:`Build the docs locally </building-the-docs>`
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 <https://github.com/corda/corda/blob/master/CONTRIBUTING.md>`_
3. Request a review from a member of the Corda platform team via the `Corda Slack channel <http://slack.corda.net/>`_
3. Request a review from a member of the Corda platform team via the `#design channel <http://slack.corda.net/>`_
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
5. Once a reviewer has approved the PR and the tests have passed, squash-and-merge the PR as a single commit

View File

@ -4,6 +4,7 @@ Corda nodes
.. toctree::
:maxdepth: 1
node-structure
generating-a-node
running-a-node
deploying-a-node

View File

@ -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<https://github.com/corda/corda/blob/release-V3.0/samples/irs-demo/cordapp/build.gradle#L111>```
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<https://github.com/corda/corda/blob/release-V3.0/samples/irs-demo/cordapp/build.gradle#L111>``
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 <running-a-node>`.

View File

@ -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.
``IOUState`` over time. This will be the focus of our next tutorial.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<Any?, Any?>()
val properties = HashMap<String, Any?>()
for (key in P2PMessagingHeaders.whitelistedHeaders) {
if (artemisMessage.containsProperty(key)) {
var value = artemisMessage.getObjectProperty(key)

View File

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

View File

@ -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<Set<MappedSchema>, SessionFactory>()
private val sessionFactories = Caffeine.newBuilder().maximumSize(databaseConfig.mappedSchemaCacheSize).build<Set<MappedSchema>, 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<MappedSchema>) = sessionFactories.computeIfAbsent(key, { makeSessionFactoryForSchemas(key) })
fun sessionFactoryForSchemas(key: Set<MappedSchema>): SessionFactory = sessionFactories.get(key, ::makeSessionFactoryForSchemas)!!
private fun makeSessionFactoryForSchemas(schemas: Set<MappedSchema>): SessionFactory {
logger.info("Creating session factory for schemas: $schemas")

View File

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

View File

@ -20,5 +20,5 @@ interface ApplicationMessage {
val topic: String
val destinationLegalName: String
val destinationLink: NetworkHostAndPort
val applicationProperties: Map<Any?, Any?>
val applicationProperties: Map<String, Any?>
}

View File

@ -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<Any?, Any?>,
override val applicationProperties: Map<String, Any?>,
private val channel: Channel,
private val delivery: Delivery) : ReceivedMessage {
data class MessageCompleter(val status: MessageStatus, val delivery: Delivery)

View File

@ -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<Any?, Any?>) : SendableMessage {
override val applicationProperties: Map<String, Any?>) : SendableMessage {
var buf: ByteBuf? = null
@Volatile
var status: MessageStatus = MessageStatus.Unsent

View File

@ -216,7 +216,7 @@ class AMQPClient(val targets: List<NetworkHostAndPort>,
fun createMessage(payload: ByteArray,
topic: String,
destinationLegalName: String,
properties: Map<Any?, Any?>): SendableMessage {
properties: Map<String, Any?>): SendableMessage {
return SendableMessageImpl(payload, topic, destinationLegalName, currentTarget, properties)
}

View File

@ -165,7 +165,7 @@ class AMQPServer(val hostName: String,
topic: String,
destinationLegalName: String,
destinationLink: NetworkHostAndPort,
properties: Map<Any?, Any?>): SendableMessage {
properties: Map<String, Any?>): SendableMessage {
val dest = InetSocketAddress(destinationLink.host, destinationLink.port)
require(dest in clientChannels.keys) {
"Destination not available"

View File

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

View File

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

View File

@ -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<V> {
val v : V
}
@Test
fun dataClassBy() {
data class C (val s: String) : DataClassByInterface<String> {
override val v: String = "-- $s"
}
data class Inner<T>(val wrapped: DataClassByInterface<T>) : DataClassByInterface<T> 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")
}
}
}

View File

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

View File

@ -59,13 +59,13 @@ class CordappScanningDriverTest : IntegrationTest() {
@StartableByRPC
@InitiatingFlow
class ReceiveFlow(val otherParty: Party) : FlowLogic<String>() {
class ReceiveFlow(private val otherParty: Party) : FlowLogic<String>() {
@Suspendable
override fun call(): String = initiateFlow(otherParty).receive<String>().unwrap { it }
}
@InitiatedBy(ReceiveFlow::class)
open class SendClassFlow(val otherPartySession: FlowSession) : FlowLogic<Unit>() {
open class SendClassFlow(private val otherPartySession: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() = otherPartySession.send(javaClass.name)
}

View File

@ -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<Any?, Any?>()
val testProperty = mutableMapOf<String, Any?>()
testProperty["TestProp"] = "1"
val message = amqpClient.createMessage(testData, sendAddress, CHARLIE_NAME.toString(), testProperty)
amqpClient.write(message)

View File

@ -210,7 +210,7 @@ class SocksTests {
artemis.session.createQueue(sendAddress, RoutingType.ANYCAST, "queue", true)
val consumer = artemis.session.createConsumer("queue")
val testData = "Test".toByteArray()
val testProperty = mutableMapOf<Any?, Any?>()
val testProperty = mutableMapOf<String, Any?>()
testProperty["TestProp"] = "1"
val message = amqpClient.createMessage(testData, sendAddress, CHARLIE_NAME.toString(), testProperty)
amqpClient.write(message)

View File

@ -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<String>() {
@Suspendable
override fun call(): String {
throw GenericJDBCException("Something went wrong!", SQLException("Oops!"))
}
}
@ -103,10 +112,8 @@ class Flow : FlowLogic<String>() {
@StartableByRPC
@InitiatingFlow
class InitFlow(private val party: Party) : FlowLogic<String>() {
@Suspendable
override fun call(): String {
val session = initiateFlow(party)
return session.sendAndReceive<String>("hey").unwrap { it }
}
@ -114,10 +121,8 @@ class InitFlow(private val party: Party) : FlowLogic<String>() {
@InitiatedBy(InitFlow::class)
class InitiatedFlow(private val initiatingSession: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
initiatingSession.receive<String>().unwrap { it }
throw GenericJDBCException("Something went wrong!", SQLException("Oops!"))
}
@ -125,10 +130,12 @@ class InitiatedFlow(private val initiatingSession: FlowSession) : FlowLogic<Unit
@StartableByRPC
class ClientRelevantErrorFlow(private val message: String) : FlowLogic<String>() {
@Suspendable
override fun call(): String {
override fun call(): String = throw ClientRelevantException(message, SQLException("Oops!"))
}
throw ClientRelevantException(message, SQLException("Oops!"))
}
}
@StartableByRPC
class FlowExceptionFlow(private val message: String) : FlowLogic<String>() {
@Suspendable
override fun call(): String = throw FlowException(message)
}

View File

@ -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<CmdLineOptions>() {
// 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>(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?,

View File

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

View File

@ -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,8 +96,9 @@ open class Node(configuration: NodeConfiguration,
}
private val sameVmNodeCounter = AtomicInteger()
val scanPackagesSystemProperty = "net.corda.node.cordapp.scan.packages"
val scanPackagesSeparator = ","
const val scanPackagesSystemProperty = "net.corda.node.cordapp.scan.packages"
const val scanPackagesSeparator = ","
@JvmStatic
protected fun makeCordappLoader(configuration: NodeConfiguration): CordappLoader {
return System.getProperty(scanPackagesSystemProperty)?.let { scanPackages ->

View File

@ -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<String>) {
companion object {
private val logger by lazy { loggerFor<Node>() } // 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<String>) {
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<String>) {
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<String>) {
pidFileRw.write(ourProcessID.toByteArray())
}
private fun parseArguments(): Pair<ArgsParser, CmdLineOptions> {
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.

View File

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

View File

@ -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<Restri
* @param baseDir The directory that this node is running in. Will use this to resolve the cordapps directory
* for classpath scanning.
*/
fun createDefault(baseDir: Path) = CordappLoader(getCordappsInDirectory(getCordappsPath(baseDir)))
fun createDefault(baseDir: Path) = CordappLoader(getNodeCordappURLs(baseDir))
// Cache for CordappLoaders to avoid costly classpath scanning
private val cordappLoadersCache = LRUMap<List<*>, CordappLoader>(1000)
private val cordappLoadersCache = Caffeine.newBuilder().softValues().build<List<RestrictedURL>, CordappLoader>()
private val generatedCordapps = ConcurrentHashMap<URL, Path>()
private fun simplifyScanPackages(scanPackages: List<String>): List<String> {
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<Restri
if (!configuration.devMode) {
logger.warn("Package scanning should only occur in dev mode!")
}
val paths = getCordappsInDirectory(getCordappsPath(configuration.baseDirectory)) + testPackages.flatMap(this::createScanPackage)
return cordappLoadersCache.computeIfAbsent(paths, { CordappLoader(paths) })
val urls = getNodeCordappURLs(configuration.baseDirectory) + simplifyScanPackages(testPackages).flatMap(this::getPackageURLs)
return cordappLoadersCache.asMap().computeIfAbsent(urls, ::CordappLoader)
}
/**
@ -106,7 +115,8 @@ class CordappLoader private constructor(private val cordappJarPaths: List<Restri
*/
@VisibleForTesting
fun createWithTestPackages(testPackages: List<String>): 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<Restri
@VisibleForTesting
fun createDevMode(scanJars: List<URL>) = CordappLoader(scanJars.map { RestrictedURL(it, null) })
private fun getCordappsPath(baseDir: Path): Path = baseDir / CORDAPPS_DIR_NAME
private fun createScanPackage(scanPackage: String): List<RestrictedURL> {
private fun getPackageURLs(scanPackage: String): List<RestrictedURL> {
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<Restri
jos.closeEntry()
} }
}
cordappJAR.toUri()
cordappJar
}
}
private fun getCordappsInDirectory(cordappsDir: Path): List<RestrictedURL> {
private fun getNodeCordappURLs(baseDir: Path): List<RestrictedURL> {
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<URL, URI>()
/** 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<Restri
private val cachedScanResult = LRUMap<RestrictedURL, RestrictedScanResult>(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<Restri
}
}
/** @param rootPackageName only this package and subpackages may be extracted from [url], or null to allow all packages. */
private class RestrictedURL(val url: URL, rootPackageName: String?) {
val qualifiedNamePrefix = rootPackageName?.let { it + '.' } ?: ""
override fun toString() = url.toString()
/** @property rootPackageName only this package and subpackages may be extracted from [url], or null to allow all packages. */
private data class RestrictedURL(val url: URL, val rootPackageName: String?) {
val qualifiedNamePrefix: String get() = rootPackageName?.let { it + '.' } ?: ""
}
private inner class RestrictedScanResult(private val scanResult: ScanResult, private val qualifiedNamePrefix: String) {

View File

@ -8,12 +8,11 @@
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package net.corda.node.serialization
package net.corda.node.serialization.kryo
import com.esotericsoftware.kryo.pool.KryoPool
import net.corda.core.serialization.SerializationContext
import net.corda.nodeapi.internal.serialization.CordaSerializationMagic
import net.corda.node.services.messaging.RpcServerObservableSerializer
import net.corda.nodeapi.internal.serialization.kryo.AbstractKryoSerializationScheme
import net.corda.nodeapi.internal.serialization.kryo.DefaultKryoCustomizer
import net.corda.nodeapi.internal.serialization.kryo.kryoMagic

View File

@ -0,0 +1,87 @@
package net.corda.node.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.core.context.Trace
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationDefaults
import net.corda.node.services.messaging.ObservableSubscription
import net.corda.node.services.messaging.RPCServer
import net.corda.nodeapi.RPCApi
import org.slf4j.LoggerFactory
import rx.Notification
import rx.Observable
import rx.Subscriber
object RpcServerObservableSerializer : Serializer<Observable<*>>() {
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<*>>?): Observable<Any> {
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<Notification<*>>() {
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())
}
}

View File

@ -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<InvocationId, ObservableSubscription>
object RpcServerObservableSerializer : Serializer<Observable<*>>() {
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<*>>?): Observable<Any> {
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<Notification<*>>() {
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())
}
}

View File

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

View File

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

View File

@ -12,12 +12,10 @@ package net.corda.node.services.transactions
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowSession
import net.corda.core.node.services.TimeWindowChecker
import net.corda.core.node.services.TrustedAuthorityNotaryService
import net.corda.core.internal.notary.TrustedAuthorityNotaryService
import net.corda.node.services.api.ServiceHubInternal
import net.corda.node.services.config.MySQLConfiguration
import java.security.PublicKey
import java.util.*
/** Notary service backed by a replicated MySQL database. */
abstract class MySQLNotaryService(

View File

@ -22,10 +22,10 @@ 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.node.services.UniquenessProvider
import net.corda.core.internal.notary.NotaryInternalException
import net.corda.core.internal.notary.UniquenessProvider
import net.corda.core.serialization.SerializationDefaults
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.serialization.serialize

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -138,10 +138,9 @@ class NodeVaultService(
private fun makeUpdates(batch: Iterable<CoreTransaction>, statesToRecord: StatesToRecord): List<Vault.Update<ContractState>> {
fun makeUpdate(tx: WireTransaction): Vault.Update<ContractState>? {
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<ContractState>(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<StateAndRef<T>> = mutableListOf()
val statesMeta: MutableList<Vault.StateMetadata> = mutableListOf()
val otherResults: MutableList<Any> = mutableListOf()
val stateRefs = mutableSetOf<StateRef>()
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<StateAndRef<T>>)
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<StateAndRef<T>> = mutableListOf()
val statesMeta: MutableList<Vault.StateMetadata> = mutableListOf()
val otherResults: MutableList<Any> = mutableListOf()
val stateRefs = mutableSetOf<StateRef>()
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<StateAndRef<T>>)
return Vault.Page(states = statesAndRefs, statesMetadata = statesMeta, stateTypes = criteriaParser.stateTypes, totalStatesAvailable = totalStates, otherResults = otherResults)
}
@Throws(VaultQueryException::class)

View File

@ -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<out T : Any> {
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
}

View File

@ -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
jolokiaAgentVersion=1.5.0

View File

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

View File

@ -19,45 +19,39 @@ import java.nio.file.Paths
@InitiatingFlow
class DummyFlow : FlowLogic<Unit>() {
@Suspendable
override fun call() {
}
override fun call() = Unit
}
@InitiatedBy(DummyFlow::class)
class LoaderTestFlow(unusedSession: FlowSession) : FlowLogic<Unit>() {
class LoaderTestFlow(@Suppress("UNUSED_PARAMETER") unusedSession: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
}
override fun call() = Unit
}
@SchedulableFlow
class DummySchedulableFlow : FlowLogic<Unit>() {
@Suspendable
override fun call() {
}
override fun call() = Unit
}
@StartableByRPC
class DummyRPCFlow : FlowLogic<Unit>() {
@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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
package net.corda.behave.service.proxy
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.contracts.ContractState
import net.corda.core.crypto.SecureHash

View File

@ -2,7 +2,7 @@ package net.corda.behave.service.proxy
import net.corda.behave.service.proxy.RPCProxyServer.Companion.initialiseSerialization
import net.corda.behave.service.proxy.RPCProxyServer.Companion.log
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.core.utilities.NetworkHostAndPort

View File

@ -3,7 +3,7 @@ package net.corda.behave.service.proxy
import net.corda.behave.service.proxy.RPCProxyWebService.Companion.RPC_PROXY_PATH
import net.corda.client.rpc.CordaRPCClient
import net.corda.client.rpc.CordaRPCClientConfiguration
import net.corda.client.rpc.internal.KryoClientSerializationScheme
import net.corda.client.rpc.internal.serialization.kryo.KryoClientSerializationScheme
import net.corda.core.contracts.ContractState
import net.corda.core.flows.FlowLogic
import net.corda.core.messaging.CordaRPCOps

View File

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

View File

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

View File

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

View File

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

View File

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