mirror of
https://github.com/corda/corda.git
synced 2025-03-14 00:06:45 +00:00
commit
253de5dc75
@ -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)
|
||||
##
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
13
build.gradle
13
build.gradle
@ -265,6 +265,14 @@ allprojects {
|
||||
}
|
||||
|
||||
configurations {
|
||||
all {
|
||||
resolutionStrategy {
|
||||
// Force dependencies to use the same version of Kotlin as Corda.
|
||||
force "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
force "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
force "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||
}
|
||||
}
|
||||
compile {
|
||||
// We want to use SLF4J's version of these bindings: jcl-over-slf4j
|
||||
// Remove any transitive dependency on Apache's version.
|
||||
@ -434,3 +442,8 @@ if(file('corda-docs-only-build').exists() || (System.getenv('CORDA_DOCS_ONLY_BUI
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wrapper {
|
||||
gradleVersion = "4.4.1"
|
||||
distributionType = Wrapper.DistributionType.ALL
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
|
||||
|
73
core/src/main/kotlin/net/corda/core/flows/NotaryError.kt
Normal file
73
core/src/main/kotlin/net/corda/core/flows/NotaryError.kt
Normal 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
|
||||
)
|
@ -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
|
||||
)
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
@ -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()
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
}
|
@ -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?>
|
||||
}
|
@ -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")
|
@ -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
|
||||
}
|
@ -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.
|
||||
}
|
@ -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
|
||||
)
|
||||
}
|
@ -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")
|
||||
}
|
@ -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)
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -4,6 +4,7 @@ Corda nodes
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
node-structure
|
||||
generating-a-node
|
||||
running-a-node
|
||||
deploying-a-node
|
||||
|
@ -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>`.
|
||||
|
@ -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.
|
||||
|
@ -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.
|
96
docs/source/node-structure.rst
Normal file
96
docs/source/node-structure.rst
Normal 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.
|
@ -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.
|
||||
|
@ -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
|
||||
---------------------
|
||||
|
||||
|
@ -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:
|
||||
|
||||
|
@ -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')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
Binary file not shown.
@ -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
|
||||
|
@ -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.*
|
||||
|
||||
|
@ -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.*
|
||||
|
||||
|
@ -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.*
|
||||
|
||||
/**
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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?>
|
||||
}
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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?,
|
@ -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.*
|
||||
|
@ -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 ->
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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
|
@ -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())
|
||||
}
|
||||
}
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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() {}
|
||||
|
@ -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.
|
||||
|
@ -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() {}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
@ -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
|
||||
|
@ -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)
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user