mirror of
https://github.com/corda/corda.git
synced 2025-06-06 01:11:45 +00:00
Merge remote-tracking branch 'open/master' into andrius/os-merge
This commit is contained in:
commit
855f89b61e
@ -1378,7 +1378,6 @@ public @interface net.corda.core.flows.InitiatingFlow
|
|||||||
public <init>(List, net.corda.core.crypto.SecureHash)
|
public <init>(List, net.corda.core.crypto.SecureHash)
|
||||||
@org.jetbrains.annotations.NotNull public final List getStatesToConsume()
|
@org.jetbrains.annotations.NotNull public final List getStatesToConsume()
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getTransactionId()
|
@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 net.corda.core.flows.NotarisationRequest$Companion Companion
|
||||||
##
|
##
|
||||||
public static final class net.corda.core.flows.NotarisationRequest$Companion extends java.lang.Object
|
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
|
@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 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 final class net.corda.core.flows.ReceiveStateAndRefFlow extends net.corda.core.flows.FlowLogic
|
||||||
public <init>(net.corda.core.flows.FlowSession)
|
public <init>(net.corda.core.flows.FlowSession)
|
||||||
@co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public List call()
|
@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)
|
||||||
public <init>(String, Throwable)
|
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
|
@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)
|
||||||
public <init>(String, Throwable)
|
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)
|
public abstract boolean isValidatingNotary(net.corda.core.identity.Party)
|
||||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.DataFeed track()
|
@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
|
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()
|
@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
|
@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)
|
@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
|
@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.services.UnknownAnonymousPartyException extends net.corda.core.CordaException
|
||||||
public <init>(String)
|
public <init>(String)
|
||||||
##
|
##
|
||||||
|
@ -1,35 +1,51 @@
|
|||||||
# List of Contributors
|
# List of Contributors
|
||||||
|
|
||||||
We'd like to thank the following people for contributing ideas to Corda,
|
We'd like to thank the following people for contributing to Corda, either by
|
||||||
either during architecture review sessions of the R3 Architecture Working Group,
|
contributing to the design of Corda during the architecture review sessions of the
|
||||||
or in design reviews since Corda has been open-sourced. Some people have moved to
|
R3 Architecture Working Group and during design reviews since Corda has been
|
||||||
a different organisation since their contribution. Please forgive any omissions, and
|
open-sourced, or by contributing code via pull requests. Some people have
|
||||||
create a pull request, or email <james@r3.com>, if you wish to see
|
moved to a different organisation since their contribution. Please forgive any
|
||||||
changes to this list.
|
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)
|
* Alberto Arri (R3)
|
||||||
|
* amiracam
|
||||||
* Andras Slemmer (R3)
|
* Andras Slemmer (R3)
|
||||||
* Andrius Dagys (R3)
|
* Andrius Dagys (R3)
|
||||||
* Andrzej Cichocki (R3)
|
* Andrzej Cichocki (R3)
|
||||||
* Andrzej Grzesik (R3)
|
* Andrzej Grzesik (R3)
|
||||||
* Anthony Coates (Deutsche Bank)
|
* Anthony Coates (Deutsche Bank)
|
||||||
|
* Anthony Keenan (R3)
|
||||||
|
* Anthony Woolley (Société Générale)
|
||||||
* Anton Semenov (Commerzbank)
|
* Anton Semenov (Commerzbank)
|
||||||
* Antonio Cerrato (SEB)
|
* Antonio Cerrato (SEB)
|
||||||
* Anthony Woolley (Société Générale)
|
* Antony Lewis (R3)
|
||||||
* Arnaud Stevens (Natixis)
|
* anttiai
|
||||||
* Arijit Das (Northern Trust)
|
* Arijit Das (Northern Trust)
|
||||||
|
* Arnaud Stevens (Natixis)
|
||||||
* Arun Battu (BNY Mellon)
|
* Arun Battu (BNY Mellon)
|
||||||
* Austin Moothart (R3)
|
* Austin Moothart (R3)
|
||||||
|
* balajimore
|
||||||
* Barry Childe (HSBC)
|
* Barry Childe (HSBC)
|
||||||
* Barry Flower (Westpac)
|
* Barry Flower (Westpac)
|
||||||
|
* Bart van den Bosch (KBC)
|
||||||
|
* Ben Wyeth (RBS)
|
||||||
* Benjamin Abineri (R3)
|
* Benjamin Abineri (R3)
|
||||||
* Benoit Lafontaine (OCTO)
|
* Benoit Lafontaine (OCTO)
|
||||||
* Berit Bourgonje (ING)
|
* Berit Bourgonje (ING)
|
||||||
|
* BitcoinErrorLog
|
||||||
* Bob Crozier (AIA)
|
* Bob Crozier (AIA)
|
||||||
* Bogdan Paunescu (R3)
|
* Bogdan Paunescu (R3)
|
||||||
|
* C-Otto
|
||||||
* Cais Manai (R3)
|
* Cais Manai (R3)
|
||||||
* Carl Worrall (BCS)
|
* Carl Worrall (BCS)
|
||||||
|
* Carlos Kuchovsky (BBVA)
|
||||||
|
* Cédric Wahl (Société Générale)
|
||||||
* Chaitanya Jadhav (HSBC)
|
* Chaitanya Jadhav (HSBC)
|
||||||
|
* chalkido
|
||||||
* Chris Akers (R3)
|
* Chris Akers (R3)
|
||||||
* Chris Burlinchon (R3)
|
* Chris Burlinchon (R3)
|
||||||
* Chris Rankin (R3)
|
* Chris Rankin (R3)
|
||||||
@ -41,14 +57,22 @@ changes to this list.
|
|||||||
* Clay Ratliff (Thoughtworks)
|
* Clay Ratliff (Thoughtworks)
|
||||||
* Clemens Wan (R3)
|
* Clemens Wan (R3)
|
||||||
* Clinton Alexander (R3)
|
* Clinton Alexander (R3)
|
||||||
|
* cncorda
|
||||||
|
* cyrsis
|
||||||
* Daniel Roig (SEB)
|
* Daniel Roig (SEB)
|
||||||
* Dave Hudson (R3)
|
* Dave Hudson (R3)
|
||||||
|
* David John Grundy (Dankse Bank)
|
||||||
* David Lee (BCS)
|
* David Lee (BCS)
|
||||||
|
* Dirk Hermans (KBC)
|
||||||
|
* Edward Greenwood (State Street)
|
||||||
* Farzad Pezeshkpour (RBS)
|
* Farzad Pezeshkpour (RBS)
|
||||||
|
* fracting
|
||||||
* Frederic Dalibard (Natixis)
|
* Frederic Dalibard (Natixis)
|
||||||
* Garrett Macey (Wells Fargo)
|
* Garrett Macey (Wells Fargo)
|
||||||
|
* gary-rowe
|
||||||
* Gavin Thomas (R3)
|
* Gavin Thomas (R3)
|
||||||
* George Marcel Smetana (Bradesco)
|
* George Marcel Smetana (Bradesco)
|
||||||
|
* George Smetana (Bradesco)
|
||||||
* Giulio Katis (Westpac)
|
* Giulio Katis (Westpac)
|
||||||
* Giuseppe Cardone (Intesa Sanpaolo)
|
* Giuseppe Cardone (Intesa Sanpaolo)
|
||||||
* Guy Hochstetler (IBM)
|
* Guy Hochstetler (IBM)
|
||||||
@ -60,9 +84,11 @@ changes to this list.
|
|||||||
* James Brown (R3)
|
* James Brown (R3)
|
||||||
* James Carlyle (R3)
|
* James Carlyle (R3)
|
||||||
* Jared Harwayne-Gidansky (BNY Mellon)
|
* Jared Harwayne-Gidansky (BNY Mellon)
|
||||||
|
* Jayavaradhan Sambedu (Société Générale)
|
||||||
* Joel Dudley (R3)
|
* Joel Dudley (R3)
|
||||||
* Johan Hörmark (SEB)
|
* Johan Hörmark (SEB)
|
||||||
* Johann Palychata (BNP Paribas)
|
* Johann Palychata (BNP Paribas)
|
||||||
|
* johnnyychiu
|
||||||
* Jonathan Sartin (R3)
|
* Jonathan Sartin (R3)
|
||||||
* Jose Coll (R3)
|
* Jose Coll (R3)
|
||||||
* Jose Luu (Natixis)
|
* Jose Luu (Natixis)
|
||||||
@ -70,6 +96,7 @@ changes to this list.
|
|||||||
* Justin Chapman (Northern Trust)
|
* Justin Chapman (Northern Trust)
|
||||||
* Kai-Michael Schramm (Credit Suisse)
|
* Kai-Michael Schramm (Credit Suisse)
|
||||||
* Karel Hajek (Barclays Capital)
|
* Karel Hajek (Barclays Capital)
|
||||||
|
* karnauskas
|
||||||
* Kasia Streich (R3)
|
* Kasia Streich (R3)
|
||||||
* Kat Baker (R3)
|
* Kat Baker (R3)
|
||||||
* Khaild Ahmed (Northern Trust)
|
* Khaild Ahmed (Northern Trust)
|
||||||
@ -81,6 +108,7 @@ changes to this list.
|
|||||||
* Lucas Salmen (Itau)
|
* Lucas Salmen (Itau)
|
||||||
* Maksymillian Pawlak (R3)
|
* Maksymillian Pawlak (R3)
|
||||||
* Marek Scocovsky (ABSA)
|
* Marek Scocovsky (ABSA)
|
||||||
|
* marekdapps
|
||||||
* Mark Lauer (Westpac)
|
* Mark Lauer (Westpac)
|
||||||
* Mark Oldfield (R3)
|
* Mark Oldfield (R3)
|
||||||
* Mark Raynes (Thomson Reuters)
|
* Mark Raynes (Thomson Reuters)
|
||||||
@ -103,18 +131,28 @@ changes to this list.
|
|||||||
* Oscar Zibordi de Paiva (Bradesco)
|
* Oscar Zibordi de Paiva (Bradesco)
|
||||||
* Patrick Kuo (R3)
|
* Patrick Kuo (R3)
|
||||||
* Pekka Kaipio (OP Financial)
|
* Pekka Kaipio (OP Financial)
|
||||||
|
* Phillip Griffin
|
||||||
* Piotr Piskorski (Nordea)
|
* Piotr Piskorski (Nordea)
|
||||||
* Przemyslaw Bak (R3)
|
* Przemyslaw Bak (R3)
|
||||||
|
* quiark
|
||||||
|
* RangerOfFire
|
||||||
|
* renlulu
|
||||||
* Rex Maudsley (Société Générale)
|
* Rex Maudsley (Société Générale)
|
||||||
|
* Rhett Brewer (Goldman Sachs)
|
||||||
|
* Richard Crook (RBS)
|
||||||
|
* Richard Gendal Brown (R3)
|
||||||
* Richard Green (R3)
|
* Richard Green (R3)
|
||||||
* Rick Parker (R3)
|
* Rick Parker (R3)
|
||||||
* Rhett Brewer (Goldman Sachs)
|
|
||||||
* Roberto Karpinski (Bradesco)
|
* Roberto Karpinski (Bradesco)
|
||||||
* Robin Green (CIBC)
|
* Robin Green (CIBC)
|
||||||
* Rodrigo Bueno (Itau)
|
* Rodrigo Bueno (Itau)
|
||||||
|
* Rodrigo Gonçalves (Itau Unibanco)
|
||||||
* Roger Willis (R3)
|
* Roger Willis (R3)
|
||||||
* Ross Burnett (Macquarie)
|
* Ross Burnett (Macquarie)
|
||||||
* Ross Nicoll (R3)
|
* Ross Nicoll (R3)
|
||||||
|
* Rui Hu (Nordea)
|
||||||
|
* s-matthew-english
|
||||||
|
* sadysnaat
|
||||||
* Sajindra Jayasena (Deutsche Bank)
|
* Sajindra Jayasena (Deutsche Bank)
|
||||||
* Saket Sharma (BNY Mellon)
|
* Saket Sharma (BNY Mellon)
|
||||||
* Sam Chadwick (Thomson Reuters)
|
* Sam Chadwick (Thomson Reuters)
|
||||||
@ -123,16 +161,25 @@ changes to this list.
|
|||||||
* Shams Asari (R3)
|
* Shams Asari (R3)
|
||||||
* Simon Taylor (Barclays)
|
* Simon Taylor (Barclays)
|
||||||
* Sofus Mortensen (Digital Asset Holdings)
|
* Sofus Mortensen (Digital Asset Holdings)
|
||||||
* Szymon Sztuka (R3)
|
|
||||||
* Stephen Lane-Smith (BMO)
|
* Stephen Lane-Smith (BMO)
|
||||||
|
* stevenroose
|
||||||
|
* Szymon Sztuka (R3)
|
||||||
|
* tb-pq
|
||||||
|
* Thiago Rafael Ferreira (Scorpius IT Solutions)
|
||||||
* Thomas O'Donnell (Macquarie)
|
* Thomas O'Donnell (Macquarie)
|
||||||
* Thomas Schroeter (R3)
|
* Thomas Schroeter (R3)
|
||||||
* Tom Menner (R3)
|
|
||||||
* Tudor Malene (R3)
|
|
||||||
* Tim Swanson (R3)
|
* Tim Swanson (R3)
|
||||||
* Timothy Smith (Credit Suisse)
|
* Timothy Smith (Credit Suisse)
|
||||||
|
* Tom Menner (R3)
|
||||||
|
* tomconte
|
||||||
* Tommy Lillehagen (R3)
|
* Tommy Lillehagen (R3)
|
||||||
|
* tomtau
|
||||||
|
* Tudor Malene (R3)
|
||||||
|
* varunkm
|
||||||
|
* verymahler
|
||||||
* Viktor Kolomeyko (R3)
|
* Viktor Kolomeyko (R3)
|
||||||
|
* Vipin Bharathan
|
||||||
* Wawrzek Niewodniczanski (R3)
|
* Wawrzek Niewodniczanski (R3)
|
||||||
* Wei Wu Zhang (Commonwealth Bank of Australia)
|
* Wei Wu Zhang (Commonwealth Bank of Australia)
|
||||||
* Zabrina Smith (Northern Trust)
|
* Zabrina Smith (Northern Trust)
|
||||||
|
* zorenmith (Northern Trust)
|
||||||
|
13
build.gradle
13
build.gradle
@ -265,6 +265,14 @@ allprojects {
|
|||||||
}
|
}
|
||||||
|
|
||||||
configurations {
|
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 {
|
compile {
|
||||||
// We want to use SLF4J's version of these bindings: jcl-over-slf4j
|
// We want to use SLF4J's version of these bindings: jcl-over-slf4j
|
||||||
// Remove any transitive dependency on Apache's version.
|
// 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.Node
|
||||||
import net.corda.node.internal.StartedNode
|
import net.corda.node.internal.StartedNode
|
||||||
import net.corda.node.services.Permissions.Companion.all
|
import net.corda.node.services.Permissions.Companion.all
|
||||||
import net.corda.nodeapi.exceptions.InternalNodeException
|
|
||||||
import net.corda.testing.core.*
|
import net.corda.testing.core.*
|
||||||
import net.corda.testing.node.User
|
import net.corda.testing.node.User
|
||||||
import net.corda.testing.internal.IntegrationTestSchemas
|
import net.corda.testing.internal.IntegrationTestSchemas
|
||||||
@ -173,15 +172,6 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C
|
|||||||
println("Result: ${flowHandle.returnValue.getOrThrow()}")
|
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
|
@Test
|
||||||
fun `check basic flow has no progress`() {
|
fun `check basic flow has no progress`() {
|
||||||
login(rpcUser.username, rpcUser.password)
|
login(rpcUser.username, rpcUser.password)
|
||||||
|
@ -10,8 +10,8 @@
|
|||||||
|
|
||||||
package net.corda.client.rpc
|
package net.corda.client.rpc
|
||||||
|
|
||||||
|
import KryoClientSerializationScheme
|
||||||
import net.corda.client.rpc.internal.CordaRPCClientConfigurationImpl
|
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.RPCClient
|
||||||
import net.corda.core.context.Actor
|
import net.corda.core.context.Actor
|
||||||
import net.corda.core.context.Trace
|
import net.corda.core.context.Trace
|
||||||
|
@ -24,6 +24,7 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder
|
|||||||
import net.corda.client.rpc.CordaRPCClientConfiguration
|
import net.corda.client.rpc.CordaRPCClientConfiguration
|
||||||
import net.corda.client.rpc.RPCException
|
import net.corda.client.rpc.RPCException
|
||||||
import net.corda.client.rpc.RPCSinceVersion
|
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.Actor
|
||||||
import net.corda.core.context.Trace
|
import net.corda.core.context.Trace
|
||||||
import net.corda.core.context.Trace.InvocationId
|
import net.corda.core.context.Trace.InvocationId
|
||||||
@ -557,62 +558,3 @@ data class ObservableContext(
|
|||||||
val hardReferenceStore: MutableSet<Observable<*>>
|
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.
|
* 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 com.esotericsoftware.kryo.pool.KryoPool
|
||||||
import net.corda.core.serialization.SerializationContext
|
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.
|
# Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||||
#
|
#
|
||||||
|
|
||||||
gradlePluginsVersion=4.0.14
|
gradlePluginsVersion=4.0.15
|
||||||
kotlinVersion=1.2.20
|
kotlinVersion=1.2.41
|
||||||
platformVersion=4
|
platformVersion=4
|
||||||
guavaVersion=21.0
|
guavaVersion=21.0
|
||||||
bouncycastleVersion=1.57
|
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.DoNotImplement
|
||||||
import net.corda.core.contracts.StateRef
|
import net.corda.core.contracts.StateRef
|
||||||
import net.corda.core.contracts.TimeWindow
|
import net.corda.core.contracts.TimeWindow
|
||||||
import net.corda.core.crypto.SecureHash
|
|
||||||
import net.corda.core.crypto.TransactionSignature
|
import net.corda.core.crypto.TransactionSignature
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.internal.FetchDataFlow
|
import net.corda.core.internal.FetchDataFlow
|
||||||
import net.corda.core.internal.generateSignature
|
import net.corda.core.internal.notary.generateSignature
|
||||||
import net.corda.core.internal.validateSignatures
|
import net.corda.core.internal.notary.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.transactions.ContractUpgradeWireTransaction
|
import net.corda.core.transactions.ContractUpgradeWireTransaction
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.transactions.WireTransaction
|
import net.corda.core.transactions.WireTransaction
|
||||||
import net.corda.core.utilities.ProgressTracker
|
import net.corda.core.utilities.ProgressTracker
|
||||||
import net.corda.core.utilities.UntrustworthyData
|
import net.corda.core.utilities.UntrustworthyData
|
||||||
import net.corda.core.utilities.unwrap
|
import net.corda.core.utilities.unwrap
|
||||||
import java.time.Instant
|
|
||||||
import java.util.function.Predicate
|
import java.util.function.Predicate
|
||||||
|
|
||||||
class NotaryFlow {
|
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
|
package net.corda.core.flows
|
||||||
|
|
||||||
import net.corda.core.contracts.StateRef
|
import net.corda.core.contracts.StateRef
|
||||||
import net.corda.core.crypto.*
|
import net.corda.core.crypto.DigitalSignature
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.crypto.SecureHash
|
||||||
|
import net.corda.core.crypto.TransactionSignature
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import net.corda.core.serialization.serialize
|
|
||||||
import net.corda.core.transactions.CoreTransaction
|
import net.corda.core.transactions.CoreTransaction
|
||||||
import net.corda.core.transactions.SignedTransaction
|
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
|
* 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. */
|
/** 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
|
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.CordappConfig
|
||||||
import net.corda.core.cordapp.CordappContext
|
import net.corda.core.cordapp.CordappContext
|
||||||
import net.corda.core.crypto.*
|
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.identity.CordaX500Name
|
||||||
import net.corda.core.node.ServiceHub
|
|
||||||
import net.corda.core.node.ServicesForResolution
|
import net.corda.core.node.ServicesForResolution
|
||||||
import net.corda.core.serialization.SerializationContext
|
import net.corda.core.serialization.SerializationContext
|
||||||
import net.corda.core.serialization.SerializedBytes
|
import net.corda.core.serialization.SerializedBytes
|
||||||
@ -422,22 +418,6 @@ fun createCordappContext(cordapp: Cordapp, attachmentId: SecureHash?, classLoade
|
|||||||
return CordappContext(cordapp, attachmentId, classLoader, config)
|
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()
|
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
|
internal fun getMigrationResource(): String? = migrationResource
|
||||||
|
|
||||||
override fun toString(): String = "${this.javaClass.simpleName}(name=$name, version=$version)"
|
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
|
//DOCEND MappedSchema
|
||||||
|
|
||||||
|
@ -7,6 +7,22 @@ release, see :doc:`upgrade-notes`.
|
|||||||
Unreleased
|
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.
|
* 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.
|
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.
|
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.
|
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:
|
.. _changelog_v1:
|
||||||
|
|
||||||
Corda 1.0
|
Corda 1.0
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
Contributing
|
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::
|
.. contents::
|
||||||
|
|
||||||
@ -9,19 +11,19 @@ Identifying an area to contribute
|
|||||||
---------------------------------
|
---------------------------------
|
||||||
There are several ways to identify an area where you can contribute to Corda:
|
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
|
* Browse issues labelled as ``HelpWanted`` on the
|
||||||
`Corda JIRA board <https://r3-cev.atlassian.net/issues/?jql=labels%20%3D%20HelpWanted>`_
|
`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
|
* 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
|
* 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
|
doesn't mean your contribution isn't welcome. Please reach out on the ``#design`` channel 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/>`_
|
|
||||||
|
|
||||||
Making the required changes
|
Making the required changes
|
||||||
---------------------------
|
---------------------------
|
||||||
@ -39,8 +41,17 @@ Your changes must pass the tests described :doc:`here </testing>`.
|
|||||||
|
|
||||||
Building against the master branch
|
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
|
You can test your changes against CorDapps defined in other repos by following the instructions :doc:`here </building-against-master>`.
|
||||||
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
|
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
|
* State that you are in agreement with the terms of
|
||||||
`CONTRIBUTING.md <https://github.com/corda/corda/blob/master/CONTRIBUTING.md>`_
|
`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)
|
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
|
* 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
|
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::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
|
node-structure
|
||||||
generating-a-node
|
generating-a-node
|
||||||
running-a-node
|
running-a-node
|
||||||
deploying-a-node
|
deploying-a-node
|
||||||
|
@ -3,92 +3,24 @@ Creating nodes locally
|
|||||||
|
|
||||||
.. contents::
|
.. contents::
|
||||||
|
|
||||||
Node structure
|
Handcrafting a node
|
||||||
--------------
|
-------------------
|
||||||
A Corda node has the following structure:
|
A node can be created manually by creating a folder that contains the following items:
|
||||||
|
|
||||||
.. sourcecode:: none
|
* The Corda JAR
|
||||||
|
|
||||||
.
|
* Can be downloaded from https://r3.bintray.com/corda/net/corda/corda/ (under /VERSION_NUMBER/corda-VERSION_NUMBER.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
|
|
||||||
|
|
||||||
The node is configured by editing its ``node.conf`` file. You install CorDapps on the node by dropping the CorDapp JARs
|
* A node configuration file entitled ``node.conf``, configured as per :doc:`corda-configuration-file`
|
||||||
into the ``cordapps`` folder.
|
|
||||||
|
|
||||||
In development mode (i.e. when ``devMode = true``, see :doc:`corda-configuration-file` for more information), the ``certificates``
|
* A folder entitled ``cordapps`` containing any CorDapp JARs you want the node to load
|
||||||
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:
|
* **Optional:** A webserver JAR entitled ``corda-webserver.jar`` that will connect to the node via RPC
|
||||||
|
|
||||||
Node naming
|
* 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
|
||||||
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)
|
The remaining files and folders described in :doc:`node-structure` will be generated at runtime.
|
||||||
* 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 Cordform task
|
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 task
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
The ```Dockerform``` is a sister task of ```Cordform```. It has nearly the same syntax and produces very
|
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```.
|
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>```
|
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:
|
included in the samples directory of main Corda GitHub repository:
|
||||||
|
|
||||||
.. sourcecode:: groovy
|
.. 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 {
|
node {
|
||||||
name "O=Notary Service,L=Zurich,C=CH"
|
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.
|
There is no need to specify the ports, as every node is a separated container, so no ports conflict will occur. Every
|
||||||
Running the task will create the same folders structure as described in :ref:`The Cordform task` with an additional
|
node by default will expose port 10003 which is the default port for RPC connections.
|
||||||
```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.
|
|
||||||
|
|
||||||
.. 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!
|
.. 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
|
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.
|
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>`.
|
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.
|
.. 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.
|
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
|
.. 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
|
* 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
|
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
|
.. topic:: Summary
|
||||||
|
|
||||||
* *Notaries prevent "double-spends"*
|
* *Notary clusters prevent "double-spends"*
|
||||||
* *Notaries may optionally also validate transactions*
|
* *Notary clusters may optionally also validate transactions*
|
||||||
* *A network can have several notaries, each running a different consensus algorithm*
|
* *A network can have several notary clusters, each running a different consensus algorithm*
|
||||||
|
|
||||||
Video
|
Video
|
||||||
-----
|
-----
|
||||||
@ -16,80 +16,83 @@ Video
|
|||||||
|
|
||||||
Overview
|
Overview
|
||||||
--------
|
--------
|
||||||
A *notary* is a network service that provides **uniqueness consensus** by attesting that, for a given transaction, it
|
A *notary cluster* is a network service that provides **uniqueness consensus** by attesting that, for a given
|
||||||
has not already signed other transactions that consumes any of the proposed transaction's input states.
|
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
|
* Sign the transaction if it has not already signed other transactions consuming any of the proposed transaction's
|
||||||
input states
|
input states
|
||||||
* Reject the transaction and flag that a double-spend attempt has occurred otherwise
|
* 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
|
In doing so, the notary cluster provides the point of finality in the system. Until the notary cluster's signature is
|
||||||
cannot be sure that an equally valid, but conflicting, transaction will not be regarded as the "valid" attempt to spend
|
obtained, parties cannot be sure that an equally valid, but conflicting, transaction will not be regarded as the
|
||||||
a given input state. However, after the notary's signature is obtained, we can be sure that the proposed
|
"valid" attempt to spend a given input state. However, after the notary cluster's signature is obtained, we can be sure
|
||||||
transaction's input states had not already been consumed by a prior transaction. Hence, notarisation is the point
|
that the proposed transaction's input states have not already been consumed by a prior transaction. Hence, notarisation
|
||||||
of finality in the system.
|
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
|
Every state has an appointed notary cluster, and a notary cluster will only notarise a transaction if it is the
|
||||||
of all the transaction's input states.
|
appointed notary cluster of all the transaction's input states.
|
||||||
|
|
||||||
Consensus algorithms
|
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.
|
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
|
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
|
low-speed, low-trust algorithm such as BFT, or any other consensus algorithm it chooses
|
||||||
|
|
||||||
Validation
|
Validation
|
||||||
^^^^^^^^^^
|
^^^^^^^^^^
|
||||||
A notary service must also decide whether or not to provide **validity consensus** by validating each transaction
|
A notary cluster 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:
|
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
|
* 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
|
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
|
* 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
|
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.
|
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
|
Additionally, Corda's permissioned network means that the notary cluster can store the identity of the party that
|
||||||
the "denial of state" transaction, allowing the attack to be resolved off-ledger.
|
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
|
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
|
Multiple notaries
|
||||||
-----------------
|
-----------------
|
||||||
Each Corda network can have multiple notaries, each potentially running a different consensus algorithm. This provides
|
Each Corda network can have multiple notary clusters, each potentially running a different consensus algorithm. This
|
||||||
several benefits:
|
provides several benefits:
|
||||||
|
|
||||||
* **Privacy** - we can have both validating and non-validating notary services on the same network, each running a
|
* **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 on a per-transaction basis
|
different algorithm. This allows nodes to choose the preferred notary cluster on a per-transaction basis
|
||||||
* **Load balancing** - spreading the transaction load over multiple notaries allows higher transaction throughput for
|
* **Load balancing** - spreading the transaction load over multiple notary clusters allows higher transaction
|
||||||
the platform overall
|
throughput for the platform overall
|
||||||
* **Low latency** - latency can be minimised by choosing a notary physically closer to the transacting parties
|
* **Low latency** - latency can be minimised by choosing a notary cluster physically closer to the transacting parties
|
||||||
|
|
||||||
Changing notaries
|
Changing notaries
|
||||||
^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^
|
||||||
Remember that a notary will only sign a transaction if it is the appointed notary of all of the transaction's input
|
Remember that a notary cluster will only sign a transaction if it is the appointed notary cluster of all of the
|
||||||
states. However, there are cases in which we may need to change a state's appointed notary. These include:
|
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 single transaction needs to consume several states that have different appointed notary clusters
|
||||||
* When a node would prefer to use a different notary for a given transaction due to privacy or efficiency concerns
|
* 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:
|
achieved using a special notary-change transaction that takes:
|
||||||
|
|
||||||
* A single input state
|
* 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
|
The input state's appointed notary cluster will sign the transaction if it doesn't constitute a double-spend, at which
|
||||||
a state will enter existence that has all the properties of the old state, but has a different appointed notary.
|
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
|
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.
|
resolvable name of a machine in a VPN.
|
||||||
* ``rpcAddress``: The address to which Artemis will bind for RPC calls.
|
* ``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
|
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
|
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.
|
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.
|
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.
|
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.
|
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
|
Testing and debugging
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
|
@ -81,7 +81,35 @@ Build
|
|||||||
|
|
||||||
.. sourcecode:: shell
|
.. 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:
|
* 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')
|
srcDir file('src/integration-test/kotlin')
|
||||||
}
|
}
|
||||||
resources {
|
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.USD
|
||||||
import net.corda.finance.flows.ConfigHolder.Companion.supportedCurrencies
|
import net.corda.finance.flows.ConfigHolder.Companion.supportedCurrencies
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.nio.file.Path
|
|
||||||
import java.util.*
|
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
|
// 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.core.utilities.ProgressTracker
|
||||||
import net.corda.finance.contracts.asset.Cash
|
import net.corda.finance.contracts.asset.Cash
|
||||||
import net.corda.finance.contracts.asset.cash.selection.AbstractCashSelection
|
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 net.corda.finance.issuedBy
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
@ -19,6 +19,9 @@ import net.corda.core.transactions.TransactionBuilder
|
|||||||
import net.corda.core.utilities.OpaqueBytes
|
import net.corda.core.utilities.OpaqueBytes
|
||||||
import net.corda.core.utilities.ProgressTracker
|
import net.corda.core.utilities.ProgressTracker
|
||||||
import net.corda.finance.contracts.asset.Cash
|
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 net.corda.finance.issuedBy
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
@ -21,6 +21,10 @@ import net.corda.core.serialization.CordaSerializable
|
|||||||
import net.corda.core.transactions.TransactionBuilder
|
import net.corda.core.transactions.TransactionBuilder
|
||||||
import net.corda.core.utilities.ProgressTracker
|
import net.corda.core.utilities.ProgressTracker
|
||||||
import net.corda.finance.contracts.asset.Cash
|
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.*
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -44,7 +44,7 @@ dependencies {
|
|||||||
compile "de.javakaffee:kryo-serializers:0.41"
|
compile "de.javakaffee:kryo-serializers:0.41"
|
||||||
|
|
||||||
// For AMQP serialisation.
|
// 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
|
// SQL connection pooling library
|
||||||
compile "com.zaxxer:HikariCP:$hikari_version"
|
compile "com.zaxxer:HikariCP:$hikari_version"
|
||||||
|
@ -2,10 +2,12 @@ package net.corda.nodeapi.exceptions
|
|||||||
|
|
||||||
import net.corda.core.CordaRuntimeException
|
import net.corda.core.CordaRuntimeException
|
||||||
import net.corda.core.contracts.TransactionVerificationException
|
import net.corda.core.contracts.TransactionVerificationException
|
||||||
|
import net.corda.core.flows.FlowException
|
||||||
import java.io.InvalidClassException
|
import java.io.InvalidClassException
|
||||||
|
|
||||||
// could change to use package name matching but trying to avoid reflection for now
|
// could change to use package name matching but trying to avoid reflection for now
|
||||||
private val whitelisted = setOf(
|
private val whitelisted = setOf(
|
||||||
|
FlowException::class,
|
||||||
InvalidClassException::class,
|
InvalidClassException::class,
|
||||||
RpcSerializableError::class,
|
RpcSerializableError::class,
|
||||||
TransactionVerificationException::class
|
TransactionVerificationException::class
|
||||||
@ -23,7 +25,6 @@ class InternalNodeException(message: String) : CordaRuntimeException(message) {
|
|||||||
fun defaultMessage(): String = DEFAULT_MESSAGE
|
fun defaultMessage(): String = DEFAULT_MESSAGE
|
||||||
|
|
||||||
fun obfuscateIfInternal(wrapped: Throwable): Throwable {
|
fun obfuscateIfInternal(wrapped: Throwable): Throwable {
|
||||||
|
|
||||||
(wrapped as? CordaRuntimeException)?.setCause(null)
|
(wrapped as? CordaRuntimeException)?.setCause(null)
|
||||||
return when {
|
return when {
|
||||||
whitelisted.any { it.isInstance(wrapped) } -> wrapped
|
whitelisted.any { it.isInstance(wrapped) } -> wrapped
|
||||||
|
@ -142,7 +142,7 @@ class AMQPBridgeManager(config: NodeSSLConfiguration, private val socksProxyConf
|
|||||||
|
|
||||||
private fun clientArtemisMessageHandler(artemisMessage: ClientMessage) {
|
private fun clientArtemisMessageHandler(artemisMessage: ClientMessage) {
|
||||||
val data = ByteArray(artemisMessage.bodySize).apply { artemisMessage.bodyBuffer.readBytes(this) }
|
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) {
|
for (key in P2PMessagingHeaders.whitelistedHeaders) {
|
||||||
if (artemisMessage.containsProperty(key)) {
|
if (artemisMessage.containsProperty(key)) {
|
||||||
var value = artemisMessage.getObjectProperty(key)
|
var value = artemisMessage.getObjectProperty(key)
|
||||||
|
@ -36,7 +36,8 @@ data class DatabaseConfig(
|
|||||||
val transactionIsolationLevel: TransactionIsolationLevel = TransactionIsolationLevel.REPEATABLE_READ,
|
val transactionIsolationLevel: TransactionIsolationLevel = TransactionIsolationLevel.REPEATABLE_READ,
|
||||||
val schema: String? = null,
|
val schema: String? = null,
|
||||||
val exportHibernateJMXStatistics: Boolean = false,
|
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
|
// 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
|
package net.corda.nodeapi.internal.persistence
|
||||||
|
|
||||||
|
import com.github.benmanes.caffeine.cache.Caffeine
|
||||||
import net.corda.core.internal.castIfPossible
|
import net.corda.core.internal.castIfPossible
|
||||||
import net.corda.core.schemas.MappedSchema
|
import net.corda.core.schemas.MappedSchema
|
||||||
import net.corda.core.utilities.contextLogger
|
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 org.hibernate.type.descriptor.sql.VarbinaryTypeDescriptor
|
||||||
import java.lang.management.ManagementFactory
|
import java.lang.management.ManagementFactory
|
||||||
import java.sql.Connection
|
import java.sql.Connection
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
|
||||||
import javax.management.ObjectName
|
import javax.management.ObjectName
|
||||||
import javax.persistence.AttributeConverter
|
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 = Caffeine.newBuilder().maximumSize(databaseConfig.mappedSchemaCacheSize).build<Set<MappedSchema>, SessionFactory>()
|
||||||
private val sessionFactories = ConcurrentHashMap<Set<MappedSchema>, SessionFactory>()
|
|
||||||
|
|
||||||
val sessionFactoryForRegisteredSchemas = schemas.let {
|
val sessionFactoryForRegisteredSchemas = schemas.let {
|
||||||
logger.info("Init HibernateConfiguration for schemas: $it")
|
logger.info("Init HibernateConfiguration for schemas: $it")
|
||||||
@ -70,7 +69,7 @@ class HibernateConfiguration(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @param key must be immutable, not just read-only. */
|
/** @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 {
|
private fun makeSessionFactoryForSchemas(schemas: Set<MappedSchema>): SessionFactory {
|
||||||
logger.info("Creating session factory for schemas: $schemas")
|
logger.info("Creating session factory for schemas: $schemas")
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
package net.corda.nodeapi.internal.protonwrapper.engine
|
package net.corda.nodeapi.internal.protonwrapper.engine
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf
|
import io.netty.buffer.ByteBuf
|
||||||
|
import org.apache.qpid.proton.codec.ReadableBuffer
|
||||||
import org.apache.qpid.proton.codec.WritableBuffer
|
import org.apache.qpid.proton.codec.WritableBuffer
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
@ -67,6 +68,10 @@ internal class NettyWritable(val nettyBuffer: ByteBuf) : WritableBuffer {
|
|||||||
nettyBuffer.writeBytes(payload)
|
nettyBuffer.writeBytes(payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun put(payload: ReadableBuffer) {
|
||||||
|
nettyBuffer.writeBytes(payload.byteBuffer())
|
||||||
|
}
|
||||||
|
|
||||||
override fun limit(): Int {
|
override fun limit(): Int {
|
||||||
return nettyBuffer.capacity()
|
return nettyBuffer.capacity()
|
||||||
}
|
}
|
||||||
|
@ -20,5 +20,5 @@ interface ApplicationMessage {
|
|||||||
val topic: String
|
val topic: String
|
||||||
val destinationLegalName: String
|
val destinationLegalName: String
|
||||||
val destinationLink: NetworkHostAndPort
|
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 sourceLink: NetworkHostAndPort,
|
||||||
override val destinationLegalName: String,
|
override val destinationLegalName: String,
|
||||||
override val destinationLink: NetworkHostAndPort,
|
override val destinationLink: NetworkHostAndPort,
|
||||||
override val applicationProperties: Map<Any?, Any?>,
|
override val applicationProperties: Map<String, Any?>,
|
||||||
private val channel: Channel,
|
private val channel: Channel,
|
||||||
private val delivery: Delivery) : ReceivedMessage {
|
private val delivery: Delivery) : ReceivedMessage {
|
||||||
data class MessageCompleter(val status: MessageStatus, val delivery: Delivery)
|
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 topic: String,
|
||||||
override val destinationLegalName: String,
|
override val destinationLegalName: String,
|
||||||
override val destinationLink: NetworkHostAndPort,
|
override val destinationLink: NetworkHostAndPort,
|
||||||
override val applicationProperties: Map<Any?, Any?>) : SendableMessage {
|
override val applicationProperties: Map<String, Any?>) : SendableMessage {
|
||||||
var buf: ByteBuf? = null
|
var buf: ByteBuf? = null
|
||||||
@Volatile
|
@Volatile
|
||||||
var status: MessageStatus = MessageStatus.Unsent
|
var status: MessageStatus = MessageStatus.Unsent
|
||||||
|
@ -216,7 +216,7 @@ class AMQPClient(val targets: List<NetworkHostAndPort>,
|
|||||||
fun createMessage(payload: ByteArray,
|
fun createMessage(payload: ByteArray,
|
||||||
topic: String,
|
topic: String,
|
||||||
destinationLegalName: String,
|
destinationLegalName: String,
|
||||||
properties: Map<Any?, Any?>): SendableMessage {
|
properties: Map<String, Any?>): SendableMessage {
|
||||||
return SendableMessageImpl(payload, topic, destinationLegalName, currentTarget, properties)
|
return SendableMessageImpl(payload, topic, destinationLegalName, currentTarget, properties)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,7 +165,7 @@ class AMQPServer(val hostName: String,
|
|||||||
topic: String,
|
topic: String,
|
||||||
destinationLegalName: String,
|
destinationLegalName: String,
|
||||||
destinationLink: NetworkHostAndPort,
|
destinationLink: NetworkHostAndPort,
|
||||||
properties: Map<Any?, Any?>): SendableMessage {
|
properties: Map<String, Any?>): SendableMessage {
|
||||||
val dest = InetSocketAddress(destinationLink.host, destinationLink.port)
|
val dest = InetSocketAddress(destinationLink.host, destinationLink.port)
|
||||||
require(dest in clientChannels.keys) {
|
require(dest in clientChannels.keys) {
|
||||||
"Destination not available"
|
"Destination not available"
|
||||||
|
@ -490,10 +490,10 @@ internal fun Type.asParameterizedType(): ParameterizedType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal fun Type.isSubClassOf(type: Type): Boolean {
|
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 {
|
internal fun suitableForObjectReference(type: Type): Boolean {
|
||||||
val clazz = type.asClass()
|
val clazz = type.asClass()
|
||||||
return type != ByteArray::class.java && (clazz != null && !clazz.isPrimitive && !Primitives.unwrap(clazz).isPrimitive)
|
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.SerializationContext
|
||||||
import net.corda.core.serialization.deserialize
|
import net.corda.core.serialization.deserialize
|
||||||
import net.corda.core.serialization.serialize
|
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.config.SSLConfiguration
|
||||||
import net.corda.nodeapi.internal.createDevKeyStores
|
import net.corda.nodeapi.internal.createDevKeyStores
|
||||||
import net.corda.nodeapi.internal.serialization.AllWhitelist
|
import net.corda.nodeapi.internal.serialization.AllWhitelist
|
||||||
|
@ -1320,5 +1320,30 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi
|
|||||||
C(12).serializeE()
|
C(12).serializeE()
|
||||||
}.withMessageContaining("has synthetic fields and is likely a nested inner class")
|
}.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.serialization.*
|
||||||
import net.corda.core.utilities.ProgressTracker
|
import net.corda.core.utilities.ProgressTracker
|
||||||
import net.corda.core.utilities.sequence
|
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.node.services.persistence.NodeAttachmentService
|
||||||
import net.corda.nodeapi.internal.serialization.*
|
import net.corda.nodeapi.internal.serialization.*
|
||||||
import net.corda.testing.core.ALICE_NAME
|
import net.corda.testing.core.ALICE_NAME
|
||||||
|
@ -59,13 +59,13 @@ class CordappScanningDriverTest : IntegrationTest() {
|
|||||||
|
|
||||||
@StartableByRPC
|
@StartableByRPC
|
||||||
@InitiatingFlow
|
@InitiatingFlow
|
||||||
class ReceiveFlow(val otherParty: Party) : FlowLogic<String>() {
|
class ReceiveFlow(private val otherParty: Party) : FlowLogic<String>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): String = initiateFlow(otherParty).receive<String>().unwrap { it }
|
override fun call(): String = initiateFlow(otherParty).receive<String>().unwrap { it }
|
||||||
}
|
}
|
||||||
|
|
||||||
@InitiatedBy(ReceiveFlow::class)
|
@InitiatedBy(ReceiveFlow::class)
|
||||||
open class SendClassFlow(val otherPartySession: FlowSession) : FlowLogic<Unit>() {
|
open class SendClassFlow(private val otherPartySession: FlowSession) : FlowLogic<Unit>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call() = otherPartySession.send(javaClass.name)
|
override fun call() = otherPartySession.send(javaClass.name)
|
||||||
}
|
}
|
||||||
|
@ -175,7 +175,7 @@ class ProtonWrapperTests {
|
|||||||
artemis.session.createQueue(sendAddress, RoutingType.ANYCAST, "queue", true)
|
artemis.session.createQueue(sendAddress, RoutingType.ANYCAST, "queue", true)
|
||||||
val consumer = artemis.session.createConsumer("queue")
|
val consumer = artemis.session.createConsumer("queue")
|
||||||
val testData = "Test".toByteArray()
|
val testData = "Test".toByteArray()
|
||||||
val testProperty = mutableMapOf<Any?, Any?>()
|
val testProperty = mutableMapOf<String, Any?>()
|
||||||
testProperty["TestProp"] = "1"
|
testProperty["TestProp"] = "1"
|
||||||
val message = amqpClient.createMessage(testData, sendAddress, CHARLIE_NAME.toString(), testProperty)
|
val message = amqpClient.createMessage(testData, sendAddress, CHARLIE_NAME.toString(), testProperty)
|
||||||
amqpClient.write(message)
|
amqpClient.write(message)
|
||||||
|
@ -41,61 +41,70 @@ class RpcExceptionHandlingTest : IntegrationTest() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `rpc client handles exceptions thrown on node side`() {
|
fun `rpc client handles exceptions thrown on node side`() {
|
||||||
|
driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) {
|
||||||
driver(DriverParameters(startNodesInProcess = true)) {
|
|
||||||
|
|
||||||
val node = startNode(NodeParameters(rpcUsers = users)).getOrThrow()
|
val node = startNode(NodeParameters(rpcUsers = users)).getOrThrow()
|
||||||
|
|
||||||
assertThatCode { node.rpc.startFlow(::Flow).returnValue.getOrThrow() }.isInstanceOfSatisfying(InternalNodeException::class.java) { exception ->
|
assertThatCode { node.rpc.startFlow(::Flow).returnValue.getOrThrow() }
|
||||||
|
.isInstanceOfSatisfying(InternalNodeException::class.java) { exception ->
|
||||||
assertThat(exception).hasNoCause()
|
assertThat(exception).hasNoCause()
|
||||||
assertThat(exception.stackTrace).isEmpty()
|
assertThat(exception.stackTrace).isEmpty()
|
||||||
assertThat(exception.message).isEqualTo(InternalNodeException.defaultMessage())
|
assertThat(exception.message).isEqualTo(InternalNodeException.defaultMessage())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `rpc client handles client-relevant exceptions thrown on node side`() {
|
fun `rpc client handles client-relevant exceptions thrown on node side`() {
|
||||||
|
driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) {
|
||||||
driver(DriverParameters(startNodesInProcess = true)) {
|
|
||||||
|
|
||||||
val node = startNode(NodeParameters(rpcUsers = users)).getOrThrow()
|
val node = startNode(NodeParameters(rpcUsers = users)).getOrThrow()
|
||||||
val clientRelevantMessage = "This is for the players!"
|
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()
|
@Test
|
||||||
assertThat(exception.stackTrace).isEmpty()
|
fun `FlowException is received by the RPC client`() {
|
||||||
assertThat(exception.message).isEqualTo(clientRelevantMessage)
|
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
|
@Test
|
||||||
fun `rpc client handles exceptions thrown on counter-party side`() {
|
fun `rpc client handles exceptions thrown on counter-party side`() {
|
||||||
|
driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) {
|
||||||
driver(DriverParameters(startNodesInProcess = true)) {
|
|
||||||
|
|
||||||
val nodeA = startNode(NodeParameters(providedName = ALICE_NAME, rpcUsers = users)).getOrThrow()
|
val nodeA = startNode(NodeParameters(providedName = ALICE_NAME, rpcUsers = users)).getOrThrow()
|
||||||
val nodeB = startNode(NodeParameters(providedName = BOB_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 ->
|
assertThatCode { nodeA.rpc.startFlow(::InitFlow, nodeB.nodeInfo.singleIdentity()).returnValue.getOrThrow() }
|
||||||
|
.isInstanceOfSatisfying(InternalNodeException::class.java) { exception ->
|
||||||
assertThat(exception).hasNoCause()
|
assertThat(exception).hasNoCause()
|
||||||
assertThat(exception.stackTrace).isEmpty()
|
assertThat(exception.stackTrace).isEmpty()
|
||||||
assertThat(exception.message).isEqualTo(InternalNodeException.defaultMessage())
|
assertThat(exception.message).isEqualTo(InternalNodeException.defaultMessage())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@StartableByRPC
|
@StartableByRPC
|
||||||
class Flow : FlowLogic<String>() {
|
class Flow : FlowLogic<String>() {
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): String {
|
override fun call(): String {
|
||||||
|
|
||||||
throw GenericJDBCException("Something went wrong!", SQLException("Oops!"))
|
throw GenericJDBCException("Something went wrong!", SQLException("Oops!"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -103,10 +112,8 @@ class Flow : FlowLogic<String>() {
|
|||||||
@StartableByRPC
|
@StartableByRPC
|
||||||
@InitiatingFlow
|
@InitiatingFlow
|
||||||
class InitFlow(private val party: Party) : FlowLogic<String>() {
|
class InitFlow(private val party: Party) : FlowLogic<String>() {
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): String {
|
override fun call(): String {
|
||||||
|
|
||||||
val session = initiateFlow(party)
|
val session = initiateFlow(party)
|
||||||
return session.sendAndReceive<String>("hey").unwrap { it }
|
return session.sendAndReceive<String>("hey").unwrap { it }
|
||||||
}
|
}
|
||||||
@ -114,10 +121,8 @@ class InitFlow(private val party: Party) : FlowLogic<String>() {
|
|||||||
|
|
||||||
@InitiatedBy(InitFlow::class)
|
@InitiatedBy(InitFlow::class)
|
||||||
class InitiatedFlow(private val initiatingSession: FlowSession) : FlowLogic<Unit>() {
|
class InitiatedFlow(private val initiatingSession: FlowSession) : FlowLogic<Unit>() {
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call() {
|
override fun call() {
|
||||||
|
|
||||||
initiatingSession.receive<String>().unwrap { it }
|
initiatingSession.receive<String>().unwrap { it }
|
||||||
throw GenericJDBCException("Something went wrong!", SQLException("Oops!"))
|
throw GenericJDBCException("Something went wrong!", SQLException("Oops!"))
|
||||||
}
|
}
|
||||||
@ -125,10 +130,12 @@ class InitiatedFlow(private val initiatingSession: FlowSession) : FlowLogic<Unit
|
|||||||
|
|
||||||
@StartableByRPC
|
@StartableByRPC
|
||||||
class ClientRelevantErrorFlow(private val message: String) : FlowLogic<String>() {
|
class ClientRelevantErrorFlow(private val message: String) : FlowLogic<String>() {
|
||||||
|
|
||||||
@Suspendable
|
@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
|
package net.corda.node
|
||||||
|
|
||||||
import com.typesafe.config.ConfigFactory
|
import com.typesafe.config.ConfigFactory
|
||||||
import joptsimple.OptionParser
|
import joptsimple.OptionSet
|
||||||
import joptsimple.util.EnumConverter
|
import joptsimple.util.EnumConverter
|
||||||
import joptsimple.util.PathConverter
|
import joptsimple.util.PathConverter
|
||||||
import net.corda.core.internal.div
|
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.ConfigHelper
|
||||||
import net.corda.node.services.config.NodeConfiguration
|
import net.corda.node.services.config.NodeConfiguration
|
||||||
import net.corda.node.services.config.parseAsNodeConfiguration
|
import net.corda.node.services.config.parseAsNodeConfiguration
|
||||||
|
import net.corda.node.utilities.AbstractArgsParser
|
||||||
import net.corda.nodeapi.internal.config.UnknownConfigKeysPolicy
|
import net.corda.nodeapi.internal.config.UnknownConfigKeysPolicy
|
||||||
import org.slf4j.event.Level
|
import org.slf4j.event.Level
|
||||||
import java.io.PrintStream
|
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
|
|
||||||
// NOTE: Do not use any logger in this class as args parsing is done before the logger is setup.
|
// NOTE: Do not use any logger in this class as args parsing is done before the logger is setup.
|
||||||
class ArgsParser {
|
class NodeArgsParser : AbstractArgsParser<CmdLineOptions>() {
|
||||||
private val optionParser = OptionParser()
|
|
||||||
// The intent of allowing a command line configurable directory and config path is to allow deployment flexibility.
|
// 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
|
// Other general configuration should live inside the config file unless we regularly need temporary overrides on the command line
|
||||||
private val baseDirectoryArg = optionParser
|
private val baseDirectoryArg = optionParser
|
||||||
.accepts("base-directory", "The node working directory where all the files are kept")
|
.accepts("base-directory", "The node working directory where all the files are kept")
|
||||||
.withRequiredArg()
|
.withRequiredArg()
|
||||||
.defaultsTo(".")
|
.withValuesConvertedBy(PathConverter())
|
||||||
|
.defaultsTo(Paths.get("."))
|
||||||
private val configFileArg = optionParser
|
private val configFileArg = optionParser
|
||||||
.accepts("config-file", "The path to the config file")
|
.accepts("config-file", "The path to the config file")
|
||||||
.withRequiredArg()
|
.withRequiredArg()
|
||||||
@ -53,7 +53,7 @@ class ArgsParser {
|
|||||||
.defaultsTo((Paths.get("certificates") / "network-root-truststore.jks"))
|
.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.")
|
private val networkRootTrustStorePasswordArg = optionParser.accepts("network-root-truststore-password", "Network root trust store password obtained from network operator.")
|
||||||
.withRequiredArg()
|
.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()
|
.withRequiredArg()
|
||||||
.withValuesConvertedBy(object : EnumConverter<UnknownConfigKeysPolicy>(UnknownConfigKeysPolicy::class.java) {})
|
.withValuesConvertedBy(object : EnumConverter<UnknownConfigKeysPolicy>(UnknownConfigKeysPolicy::class.java) {})
|
||||||
.defaultsTo(UnknownConfigKeysPolicy.FAIL)
|
.defaultsTo(UnknownConfigKeysPolicy.FAIL)
|
||||||
@ -62,16 +62,13 @@ class ArgsParser {
|
|||||||
private val justGenerateNodeInfoArg = optionParser.accepts("just-generate-node-info",
|
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")
|
"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 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 {
|
override fun doParse(optionSet: OptionSet): CmdLineOptions {
|
||||||
val optionSet = optionParser.parse(*args)
|
|
||||||
require(!optionSet.has(baseDirectoryArg) || !optionSet.has(configFileArg)) {
|
require(!optionSet.has(baseDirectoryArg) || !optionSet.has(configFileArg)) {
|
||||||
"${baseDirectoryArg.options()[0]} and ${configFileArg.options()[0]} cannot be specified together"
|
"${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 configFile = baseDirectory / optionSet.valueOf(configFileArg)
|
||||||
val help = optionSet.has(helpArg)
|
|
||||||
val loggingLevel = optionSet.valueOf(loggerLevel)
|
val loggingLevel = optionSet.valueOf(loggerLevel)
|
||||||
val logToConsole = optionSet.has(logToConsoleArg)
|
val logToConsole = optionSet.has(logToConsoleArg)
|
||||||
val isRegistration = optionSet.has(isRegistrationArg)
|
val isRegistration = optionSet.has(isRegistrationArg)
|
||||||
@ -94,7 +91,6 @@ class ArgsParser {
|
|||||||
|
|
||||||
return CmdLineOptions(baseDirectory,
|
return CmdLineOptions(baseDirectory,
|
||||||
configFile,
|
configFile,
|
||||||
help,
|
|
||||||
loggingLevel,
|
loggingLevel,
|
||||||
logToConsole,
|
logToConsole,
|
||||||
registrationConfig,
|
registrationConfig,
|
||||||
@ -105,15 +101,12 @@ class ArgsParser {
|
|||||||
bootstrapRaftCluster,
|
bootstrapRaftCluster,
|
||||||
unknownConfigKeysPolicy)
|
unknownConfigKeysPolicy)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun printHelp(sink: PrintStream) = optionParser.printHelpOn(sink)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class NodeRegistrationOption(val networkRootTrustStorePath: Path, val networkRootTrustStorePassword: String)
|
data class NodeRegistrationOption(val networkRootTrustStorePath: Path, val networkRootTrustStorePassword: String)
|
||||||
|
|
||||||
data class CmdLineOptions(val baseDirectory: Path,
|
data class CmdLineOptions(val baseDirectory: Path,
|
||||||
val configFile: Path,
|
val configFile: Path,
|
||||||
val help: Boolean,
|
|
||||||
val loggingLevel: Level,
|
val loggingLevel: Level,
|
||||||
val logToConsole: Boolean,
|
val logToConsole: Boolean,
|
||||||
val nodeRegistrationOption: NodeRegistrationOption?,
|
val nodeRegistrationOption: NodeRegistrationOption?,
|
@ -27,6 +27,7 @@ import net.corda.core.internal.FlowStateMachine
|
|||||||
import net.corda.core.internal.VisibleForTesting
|
import net.corda.core.internal.VisibleForTesting
|
||||||
import net.corda.core.internal.concurrent.map
|
import net.corda.core.internal.concurrent.map
|
||||||
import net.corda.core.internal.concurrent.openFuture
|
import net.corda.core.internal.concurrent.openFuture
|
||||||
|
import net.corda.core.internal.notary.NotaryService
|
||||||
import net.corda.core.internal.uncheckedCast
|
import net.corda.core.internal.uncheckedCast
|
||||||
import net.corda.core.messaging.*
|
import net.corda.core.messaging.*
|
||||||
import net.corda.core.node.*
|
import net.corda.core.node.*
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
package net.corda.node.internal
|
package net.corda.node.internal
|
||||||
|
|
||||||
import com.codahale.metrics.JmxReporter
|
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.concurrent.CordaFuture
|
||||||
import net.corda.core.internal.concurrent.openFuture
|
import net.corda.core.internal.concurrent.openFuture
|
||||||
import net.corda.core.internal.concurrent.thenMatch
|
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.cordapp.CordappLoader
|
||||||
import net.corda.node.internal.security.RPCSecurityManagerImpl
|
import net.corda.node.internal.security.RPCSecurityManagerImpl
|
||||||
import net.corda.node.internal.security.RPCSecurityManagerWithAdditionalUser
|
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.NodePropertiesStore
|
||||||
import net.corda.node.services.api.SchemaService
|
import net.corda.node.services.api.SchemaService
|
||||||
import net.corda.node.services.config.*
|
import net.corda.node.services.config.*
|
||||||
@ -96,10 +96,10 @@ open class Node(configuration: NodeConfiguration,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val sameVmNodeCounter = AtomicInteger()
|
private val sameVmNodeCounter = AtomicInteger()
|
||||||
val scanPackagesSystemProperty = "net.corda.node.cordapp.scan.packages"
|
const val scanPackagesSystemProperty = "net.corda.node.cordapp.scan.packages"
|
||||||
val scanPackagesSeparator = ","
|
const val scanPackagesSeparator = ","
|
||||||
@JvmStatic
|
|
||||||
protected fun makeCordappLoader(configuration: NodeConfiguration): CordappLoader {
|
private fun makeCordappLoader(configuration: NodeConfiguration): CordappLoader {
|
||||||
return System.getProperty(scanPackagesSystemProperty)?.let { scanPackages ->
|
return System.getProperty(scanPackagesSystemProperty)?.let { scanPackages ->
|
||||||
CordappLoader.createDefaultWithTestPackages(configuration, scanPackages.split(scanPackagesSeparator))
|
CordappLoader.createDefaultWithTestPackages(configuration, scanPackages.split(scanPackagesSeparator))
|
||||||
} ?: CordappLoader.createDefault(configuration.baseDirectory)
|
} ?: CordappLoader.createDefault(configuration.baseDirectory)
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
package net.corda.node.internal
|
package net.corda.node.internal
|
||||||
|
|
||||||
import com.jcabi.manifests.Manifests
|
import com.jcabi.manifests.Manifests
|
||||||
import joptsimple.OptionException
|
|
||||||
import net.corda.core.internal.Emoji
|
import net.corda.core.internal.Emoji
|
||||||
import net.corda.core.internal.concurrent.thenMatch
|
import net.corda.core.internal.concurrent.thenMatch
|
||||||
import net.corda.core.internal.createDirectories
|
import net.corda.core.internal.createDirectories
|
||||||
@ -40,14 +39,13 @@ import java.net.InetAddress
|
|||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.system.exitProcess
|
|
||||||
|
|
||||||
/** This class is responsible for starting a Node from command line arguments. */
|
/** This class is responsible for starting a Node from command line arguments. */
|
||||||
open class NodeStartup(val args: Array<String>) {
|
open class NodeStartup(val args: Array<String>) {
|
||||||
companion object {
|
companion object {
|
||||||
private val logger by lazy { loggerFor<Node>() } // I guess this is lazy to allow for logging init, but why Node?
|
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"
|
const val LOGS_DIRECTORY_NAME = "logs"
|
||||||
val LOGS_CAN_BE_FOUND_IN_STRING = "Logs can be found in"
|
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...")
|
println("Corda will now exit...")
|
||||||
return false
|
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
|
// 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.
|
// doesn't mess with the running node's logs.
|
||||||
@ -77,12 +75,6 @@ open class NodeStartup(val args: Array<String>) {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Maybe render command line help.
|
|
||||||
if (cmdlineOptions.help) {
|
|
||||||
argsParser.printHelp(System.out)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
drawBanner(versionInfo)
|
drawBanner(versionInfo)
|
||||||
Node.printBasicNodeInfo(LOGS_CAN_BE_FOUND_IN_STRING, System.getProperty("log-path"))
|
Node.printBasicNodeInfo(LOGS_CAN_BE_FOUND_IN_STRING, System.getProperty("log-path"))
|
||||||
val conf = try {
|
val conf = try {
|
||||||
@ -269,18 +261,6 @@ open class NodeStartup(val args: Array<String>) {
|
|||||||
pidFileRw.write(ourProcessID.toByteArray())
|
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) {
|
open protected fun initLogging(cmdlineOptions: CmdLineOptions) {
|
||||||
val loggingLevel = cmdlineOptions.loggingLevel.name.toLowerCase(Locale.ENGLISH)
|
val loggingLevel = cmdlineOptions.loggingLevel.name.toLowerCase(Locale.ENGLISH)
|
||||||
System.setProperty("defaultLogLevel", loggingLevel) // These properties are referenced from the XML config file.
|
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.FlowLogic
|
||||||
import net.corda.core.flows.InitiatedBy
|
import net.corda.core.flows.InitiatedBy
|
||||||
import net.corda.core.internal.VisibleForTesting
|
import net.corda.core.internal.VisibleForTesting
|
||||||
|
import net.corda.core.internal.notary.NotaryService
|
||||||
import net.corda.core.messaging.CordaRPCOps
|
import net.corda.core.messaging.CordaRPCOps
|
||||||
import net.corda.core.node.NodeInfo
|
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.CheckpointStorage
|
||||||
import net.corda.node.services.api.StartedNodeServices
|
import net.corda.node.services.api.StartedNodeServices
|
||||||
import net.corda.node.services.messaging.MessagingService
|
import net.corda.node.services.messaging.MessagingService
|
||||||
|
@ -10,11 +10,9 @@
|
|||||||
|
|
||||||
package net.corda.node.internal.cordapp
|
package net.corda.node.internal.cordapp
|
||||||
|
|
||||||
|
import com.github.benmanes.caffeine.cache.Caffeine
|
||||||
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner
|
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner
|
||||||
import io.github.lukehutch.fastclasspathscanner.scanner.ScanResult
|
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.cordapp.Cordapp
|
||||||
import net.corda.core.flows.*
|
import net.corda.core.flows.*
|
||||||
import net.corda.core.internal.*
|
import net.corda.core.internal.*
|
||||||
@ -32,7 +30,6 @@ import net.corda.nodeapi.internal.serialization.DefaultWhitelist
|
|||||||
import org.apache.commons.collections4.map.LRUMap
|
import org.apache.commons.collections4.map.LRUMap
|
||||||
import java.lang.reflect.Modifier
|
import java.lang.reflect.Modifier
|
||||||
import java.net.JarURLConnection
|
import java.net.JarURLConnection
|
||||||
import java.net.URI
|
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.net.URLClassLoader
|
import java.net.URLClassLoader
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
@ -40,6 +37,7 @@ import java.nio.file.Paths
|
|||||||
import java.nio.file.attribute.FileTime
|
import java.nio.file.attribute.FileTime
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import java.util.jar.JarOutputStream
|
import java.util.jar.JarOutputStream
|
||||||
import java.util.zip.ZipEntry
|
import java.util.zip.ZipEntry
|
||||||
import kotlin.reflect.KClass
|
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
|
* @param baseDir The directory that this node is running in. Will use this to resolve the cordapps directory
|
||||||
* for classpath scanning.
|
* 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
|
// 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
|
* 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) {
|
if (!configuration.devMode) {
|
||||||
logger.warn("Package scanning should only occur in dev mode!")
|
logger.warn("Package scanning should only occur in dev mode!")
|
||||||
}
|
}
|
||||||
val paths = getCordappsInDirectory(getCordappsPath(configuration.baseDirectory)) + testPackages.flatMap(this::createScanPackage)
|
val urls = getNodeCordappURLs(configuration.baseDirectory) + simplifyScanPackages(testPackages).flatMap(this::getPackageURLs)
|
||||||
return cordappLoadersCache.computeIfAbsent(paths, { CordappLoader(paths) })
|
return cordappLoadersCache.asMap().computeIfAbsent(urls, ::CordappLoader)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -106,7 +115,8 @@ class CordappLoader private constructor(private val cordappJarPaths: List<Restri
|
|||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
fun createWithTestPackages(testPackages: List<String>): CordappLoader {
|
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
|
@VisibleForTesting
|
||||||
fun createDevMode(scanJars: List<URL>) = CordappLoader(scanJars.map { RestrictedURL(it, null) })
|
fun createDevMode(scanJars: List<URL>) = CordappLoader(scanJars.map { RestrictedURL(it, null) })
|
||||||
|
|
||||||
private fun getCordappsPath(baseDir: Path): Path = baseDir / CORDAPPS_DIR_NAME
|
private fun getPackageURLs(scanPackage: String): List<RestrictedURL> {
|
||||||
|
|
||||||
private fun createScanPackage(scanPackage: String): List<RestrictedURL> {
|
|
||||||
val resource = scanPackage.replace('.', '/')
|
val resource = scanPackage.replace('.', '/')
|
||||||
return this::class.java.classLoader.getResources(resource)
|
return this::class.java.classLoader.getResources(resource)
|
||||||
.asSequence()
|
.asSequence()
|
||||||
.map { path ->
|
.map { url ->
|
||||||
if (path.protocol == "jar") {
|
if (url.protocol == "jar") {
|
||||||
// When running tests from gradle this may be a corda module jar, so restrict to scanPackage:
|
// 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 {
|
} else {
|
||||||
// No need to restrict as createDevCordappJar has already done that:
|
// 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()
|
.toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Takes a package of classes and creates a JAR from them - only use in tests. */
|
/** 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) {
|
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 cordappDir = (Paths.get("build") / "tmp" / "generated-test-cordapps").createDirectories()
|
||||||
val cordappJAR = cordappDir / "$scanPackage-${UUID.randomUUID()}.jar"
|
val cordappJar = cordappDir / "$scanPackage-${UUID.randomUUID()}.jar"
|
||||||
logger.info("Generating a test-only cordapp of classes discovered in $scanPackage at $cordappJAR")
|
logger.info("Generating a test-only CorDapp of classes discovered for package $scanPackage in $url: $cordappJar")
|
||||||
JarOutputStream(cordappJAR.outputStream()).use { jos ->
|
JarOutputStream(cordappJar.outputStream()).use { jos ->
|
||||||
val scanDir = url.toPath()
|
val scanDir = url.toPath()
|
||||||
scanDir.walk { it.forEach {
|
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 time = FileTime.from(Instant.EPOCH)
|
||||||
val entry = ZipEntry(entryPath).setCreationTime(time).setLastAccessTime(time).setLastModifiedTime(time)
|
val entry = ZipEntry(entryPath).setCreationTime(time).setLastAccessTime(time).setLastModifiedTime(time)
|
||||||
jos.putNextEntry(entry)
|
jos.putNextEntry(entry)
|
||||||
@ -154,22 +163,21 @@ class CordappLoader private constructor(private val cordappJarPaths: List<Restri
|
|||||||
jos.closeEntry()
|
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()) {
|
return if (!cordappsDir.exists()) {
|
||||||
emptyList()
|
emptyList()
|
||||||
} else {
|
} else {
|
||||||
cordappsDir.list {
|
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 */
|
/** A list of the core RPC flows present in Corda */
|
||||||
private val coreRPCFlows = listOf(
|
private val coreRPCFlows = listOf(
|
||||||
ContractUpgradeFlow.Initiate::class.java,
|
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 val cachedScanResult = LRUMap<RestrictedURL, RestrictedScanResult>(1000)
|
||||||
private fun scanCordapp(cordappJarPath: RestrictedURL): RestrictedScanResult {
|
private fun scanCordapp(cordappJarPath: RestrictedURL): RestrictedScanResult {
|
||||||
logger.info("Scanning CorDapp in $cordappJarPath")
|
logger.info("Scanning CorDapp in ${cordappJarPath.url}")
|
||||||
return cachedScanResult.computeIfAbsent(cordappJarPath, {
|
return cachedScanResult.computeIfAbsent(cordappJarPath, {
|
||||||
RestrictedScanResult(FastClasspathScanner().addClassLoader(appClassLoader).overrideClasspath(cordappJarPath.url).scan(), cordappJarPath.qualifiedNamePrefix)
|
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. */
|
/** @property 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?) {
|
private data class RestrictedURL(val url: URL, val rootPackageName: String?) {
|
||||||
val qualifiedNamePrefix = rootPackageName?.let { it + '.' } ?: ""
|
val qualifiedNamePrefix: String get() = rootPackageName?.let { it + '.' } ?: ""
|
||||||
override fun toString() = url.toString()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class RestrictedScanResult(private val scanResult: ScanResult, private val qualifiedNamePrefix: String) {
|
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.
|
* 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 com.esotericsoftware.kryo.pool.KryoPool
|
||||||
import net.corda.core.serialization.SerializationContext
|
import net.corda.core.serialization.SerializationContext
|
||||||
import net.corda.nodeapi.internal.serialization.CordaSerializationMagic
|
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.AbstractKryoSerializationScheme
|
||||||
import net.corda.nodeapi.internal.serialization.kryo.DefaultKryoCustomizer
|
import net.corda.nodeapi.internal.serialization.kryo.DefaultKryoCustomizer
|
||||||
import net.corda.nodeapi.internal.serialization.kryo.kryoMagic
|
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
|
package net.corda.node.services.messaging
|
||||||
|
|
||||||
import co.paralleluniverse.common.util.SameThreadExecutor
|
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.Cache
|
||||||
import com.github.benmanes.caffeine.cache.Caffeine
|
import com.github.benmanes.caffeine.cache.Caffeine
|
||||||
import com.github.benmanes.caffeine.cache.RemovalListener
|
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.core.utilities.*
|
||||||
import net.corda.node.internal.security.AuthorizingSubject
|
import net.corda.node.internal.security.AuthorizingSubject
|
||||||
import net.corda.node.internal.security.RPCSecurityManager
|
import net.corda.node.internal.security.RPCSecurityManager
|
||||||
|
import net.corda.node.serialization.kryo.RpcServerObservableSerializer
|
||||||
import net.corda.node.services.logging.pushToLoggingContext
|
import net.corda.node.services.logging.pushToLoggingContext
|
||||||
import net.corda.nodeapi.RPCApi
|
import net.corda.nodeapi.RPCApi
|
||||||
import net.corda.nodeapi.externalTrace
|
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.ActiveMQServerControl
|
||||||
import org.apache.activemq.artemis.api.core.management.CoreNotificationType
|
import org.apache.activemq.artemis.api.core.management.CoreNotificationType
|
||||||
import org.apache.activemq.artemis.api.core.management.ManagementHelper
|
import org.apache.activemq.artemis.api.core.management.ManagementHelper
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import org.slf4j.MDC
|
import org.slf4j.MDC
|
||||||
import rx.Notification
|
|
||||||
import rx.Observable
|
|
||||||
import rx.Subscriber
|
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
import java.lang.reflect.InvocationTargetException
|
import java.lang.reflect.InvocationTargetException
|
||||||
import java.lang.reflect.Method
|
import java.lang.reflect.Method
|
||||||
@ -496,74 +489,3 @@ class ObservableSubscription(
|
|||||||
)
|
)
|
||||||
|
|
||||||
typealias ObservableSubscriptionMap = Cache<InvocationId, 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.crypto.SignedData
|
||||||
import net.corda.core.flows.*
|
import net.corda.core.flows.*
|
||||||
import net.corda.core.identity.Party
|
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.schemas.PersistentStateRef
|
||||||
import net.corda.core.serialization.deserialize
|
import net.corda.core.serialization.deserialize
|
||||||
import net.corda.core.serialization.serialize
|
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.StateRef
|
||||||
import net.corda.core.contracts.TimeWindow
|
import net.corda.core.contracts.TimeWindow
|
||||||
import net.corda.core.crypto.*
|
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.CordaX500Name
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.internal.declaredField
|
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.toTypedArray
|
||||||
import net.corda.core.internal.validateTimeWindow
|
|
||||||
import net.corda.core.schemas.PersistentStateRef
|
import net.corda.core.schemas.PersistentStateRef
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||||
|
@ -12,15 +12,17 @@ package net.corda.node.services.transactions
|
|||||||
|
|
||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.contracts.ComponentGroupEnum
|
import net.corda.core.contracts.ComponentGroupEnum
|
||||||
import net.corda.core.flows.*
|
import net.corda.core.flows.FlowSession
|
||||||
import net.corda.core.internal.validateRequestSignature
|
import net.corda.core.flows.NotarisationPayload
|
||||||
import net.corda.core.node.services.TrustedAuthorityNotaryService
|
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.ContractUpgradeFilteredTransaction
|
||||||
import net.corda.core.transactions.CoreTransaction
|
import net.corda.core.transactions.CoreTransaction
|
||||||
import net.corda.core.transactions.FilteredTransaction
|
import net.corda.core.transactions.FilteredTransaction
|
||||||
import net.corda.core.transactions.NotaryChangeWireTransaction
|
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
|
* 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
|
* 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.crypto.sha256
|
||||||
import net.corda.core.flows.NotarisationRequestSignature
|
import net.corda.core.flows.NotarisationRequestSignature
|
||||||
import net.corda.core.flows.NotaryError
|
import net.corda.core.flows.NotaryError
|
||||||
import net.corda.core.flows.NotaryInternalException
|
|
||||||
import net.corda.core.flows.StateConsumptionDetails
|
import net.corda.core.flows.StateConsumptionDetails
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.internal.ThreadBox
|
import net.corda.core.internal.ThreadBox
|
||||||
import net.corda.core.internal.isConsumedByTheSameTx
|
import net.corda.core.internal.notary.NotaryInternalException
|
||||||
import net.corda.core.internal.validateTimeWindow
|
import net.corda.core.internal.notary.UniquenessProvider
|
||||||
import net.corda.core.node.services.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.schemas.PersistentStateRef
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||||
|
@ -11,9 +11,9 @@
|
|||||||
package net.corda.node.services.transactions
|
package net.corda.node.services.transactions
|
||||||
|
|
||||||
import net.corda.core.flows.FlowSession
|
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.ServiceHub
|
||||||
import net.corda.core.node.services.TrustedAuthorityNotaryService
|
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
|
|
||||||
/** A non-validating notary service operated by a group of mutually trusting parties, uses the Raft algorithm to achieve consensus. */
|
/** 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 notaryIdentityKey: PublicKey,
|
||||||
override val uniquenessProvider: RaftUniquenessProvider
|
override val uniquenessProvider: RaftUniquenessProvider
|
||||||
) : TrustedAuthorityNotaryService() {
|
) : TrustedAuthorityNotaryService() {
|
||||||
override fun createServiceFlow(otherPartySession: FlowSession): NotaryFlow.Service {
|
override fun createServiceFlow(otherPartySession: FlowSession): NotaryServiceFlow {
|
||||||
return NonValidatingNotaryFlow(otherPartySession, this)
|
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.NotaryError
|
||||||
import net.corda.core.flows.StateConsumptionDetails
|
import net.corda.core.flows.StateConsumptionDetails
|
||||||
import net.corda.core.internal.VisibleForTesting
|
import net.corda.core.internal.VisibleForTesting
|
||||||
import net.corda.core.internal.isConsumedByTheSameTx
|
import net.corda.core.internal.notary.isConsumedByTheSameTx
|
||||||
import net.corda.core.internal.validateTimeWindow
|
import net.corda.core.internal.notary.validateTimeWindow
|
||||||
import net.corda.core.serialization.SerializationDefaults
|
import net.corda.core.serialization.SerializationDefaults
|
||||||
import net.corda.core.serialization.SerializationFactory
|
import net.corda.core.serialization.SerializationFactory
|
||||||
import net.corda.core.serialization.deserialize
|
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.contracts.TimeWindow
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.flows.NotarisationRequestSignature
|
import net.corda.core.flows.NotarisationRequestSignature
|
||||||
import net.corda.core.flows.NotaryInternalException
|
|
||||||
import net.corda.core.identity.Party
|
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.schemas.PersistentStateRef
|
||||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||||
import net.corda.core.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
|
@ -11,9 +11,9 @@
|
|||||||
package net.corda.node.services.transactions
|
package net.corda.node.services.transactions
|
||||||
|
|
||||||
import net.corda.core.flows.FlowSession
|
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.ServiceHub
|
||||||
import net.corda.core.node.services.TrustedAuthorityNotaryService
|
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
|
|
||||||
/** A validating notary service operated by a group of mutually trusting parties, uses the Raft algorithm to achieve consensus. */
|
/** 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 notaryIdentityKey: PublicKey,
|
||||||
override val uniquenessProvider: RaftUniquenessProvider
|
override val uniquenessProvider: RaftUniquenessProvider
|
||||||
) : TrustedAuthorityNotaryService() {
|
) : TrustedAuthorityNotaryService() {
|
||||||
override fun createServiceFlow(otherPartySession: FlowSession): NotaryFlow.Service {
|
override fun createServiceFlow(otherPartySession: FlowSession): NotaryServiceFlow {
|
||||||
return ValidatingNotaryFlow(otherPartySession, this)
|
return ValidatingNotaryFlow(otherPartySession, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,8 +11,8 @@
|
|||||||
package net.corda.node.services.transactions
|
package net.corda.node.services.transactions
|
||||||
|
|
||||||
import net.corda.core.flows.FlowSession
|
import net.corda.core.flows.FlowSession
|
||||||
import net.corda.core.flows.NotaryFlow
|
import net.corda.core.internal.notary.NotaryServiceFlow
|
||||||
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.api.ServiceHubInternal
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ import java.security.PublicKey
|
|||||||
class SimpleNotaryService(override val services: ServiceHubInternal, override val notaryIdentityKey: PublicKey) : TrustedAuthorityNotaryService() {
|
class SimpleNotaryService(override val services: ServiceHubInternal, override val notaryIdentityKey: PublicKey) : TrustedAuthorityNotaryService() {
|
||||||
override val uniquenessProvider = PersistentUniquenessProvider(services.clock)
|
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 start() {}
|
||||||
override fun stop() {}
|
override fun stop() {}
|
||||||
|
@ -13,10 +13,14 @@ package net.corda.node.services.transactions
|
|||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.contracts.TimeWindow
|
import net.corda.core.contracts.TimeWindow
|
||||||
import net.corda.core.contracts.TransactionVerificationException
|
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.ResolveTransactionsFlow
|
||||||
import net.corda.core.internal.validateRequestSignature
|
import net.corda.core.internal.notary.NotaryInternalException
|
||||||
import net.corda.core.node.services.TrustedAuthorityNotaryService
|
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.SignedTransaction
|
||||||
import net.corda.core.transactions.TransactionWithSignatures
|
import net.corda.core.transactions.TransactionWithSignatures
|
||||||
import net.corda.core.transactions.WireTransaction
|
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
|
* has its input states "blocked" by a transaction from another party, and needs to establish whether that transaction was
|
||||||
* indeed valid.
|
* 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
|
* 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.
|
* the transaction in question has all required signatures apart from the notary's.
|
||||||
|
@ -11,8 +11,8 @@
|
|||||||
package net.corda.node.services.transactions
|
package net.corda.node.services.transactions
|
||||||
|
|
||||||
import net.corda.core.flows.FlowSession
|
import net.corda.core.flows.FlowSession
|
||||||
import net.corda.core.flows.NotaryFlow
|
import net.corda.core.internal.notary.NotaryServiceFlow
|
||||||
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.api.ServiceHubInternal
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ import java.security.PublicKey
|
|||||||
class ValidatingNotaryService(override val services: ServiceHubInternal, override val notaryIdentityKey: PublicKey) : TrustedAuthorityNotaryService() {
|
class ValidatingNotaryService(override val services: ServiceHubInternal, override val notaryIdentityKey: PublicKey) : TrustedAuthorityNotaryService() {
|
||||||
override val uniquenessProvider = PersistentUniquenessProvider(services.clock)
|
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 start() {}
|
||||||
override fun stop() {}
|
override fun stop() {}
|
||||||
|
@ -138,10 +138,9 @@ class NodeVaultService(
|
|||||||
|
|
||||||
private fun makeUpdates(batch: Iterable<CoreTransaction>, statesToRecord: StatesToRecord): List<Vault.Update<ContractState>> {
|
private fun makeUpdates(batch: Iterable<CoreTransaction>, statesToRecord: StatesToRecord): List<Vault.Update<ContractState>> {
|
||||||
fun makeUpdate(tx: WireTransaction): 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) {
|
val ourNewStates = when (statesToRecord) {
|
||||||
StatesToRecord.NONE -> throw AssertionError("Should not reach here")
|
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
|
StatesToRecord.ALL_VISIBLE -> tx.outputs
|
||||||
}.map { tx.outRef<ContractState>(it.data) }
|
}.map { tx.outRef<ContractState>(it.data) }
|
||||||
|
|
||||||
@ -166,12 +165,15 @@ class NodeVaultService(
|
|||||||
is ContractUpgradeWireTransaction -> tx.resolve(servicesForResolution, emptyList())
|
is ContractUpgradeWireTransaction -> tx.resolve(servicesForResolution, emptyList())
|
||||||
else -> throw IllegalArgumentException("Unsupported transaction type: ${tx.javaClass.name}")
|
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.
|
val (consumedStateAndRefs, producedStates) = ltx.inputs.
|
||||||
zip(ltx.outputs).
|
zip(ltx.outputs).
|
||||||
filter { (_, output) ->
|
filter { (_, output) ->
|
||||||
if (statesToRecord == StatesToRecord.ONLY_RELEVANT) isRelevant(output.data, myKeys.toSet())
|
if (statesToRecord == StatesToRecord.ONLY_RELEVANT) {
|
||||||
else true
|
isRelevant(output.data, myKeys.toSet())
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
}.
|
}.
|
||||||
unzip()
|
unzip()
|
||||||
|
|
||||||
@ -414,65 +416,60 @@ class NodeVaultService(
|
|||||||
// TODO: revisit (use single instance of parser for all queries)
|
// TODO: revisit (use single instance of parser for all queries)
|
||||||
val criteriaParser = HibernateQueryCriteriaParser(contractStateType, contractStateTypeMappings, criteriaBuilder, criteriaQuery, queryRootVaultStates)
|
val criteriaParser = HibernateQueryCriteriaParser(contractStateType, contractStateTypeMappings, criteriaBuilder, criteriaQuery, queryRootVaultStates)
|
||||||
|
|
||||||
try {
|
// parse criteria and build where predicates
|
||||||
// parse criteria and build where predicates
|
criteriaParser.parse(criteria, sorting)
|
||||||
criteriaParser.parse(criteria, sorting)
|
|
||||||
|
|
||||||
// prepare query for execution
|
// prepare query for execution
|
||||||
val query = session.createQuery(criteriaQuery)
|
val query = session.createQuery(criteriaQuery)
|
||||||
|
|
||||||
// pagination checks
|
// pagination checks
|
||||||
if (!paging.isDefault) {
|
if (!paging.isDefault) {
|
||||||
// pagination
|
// 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.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]")
|
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
@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
|
# Note: sadly, due to present limitation of IntelliJ-IDEA in processing resource files, these constants cannot be
|
||||||
# imported from top-level 'constants.properties' file
|
# 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.assertEquals
|
||||||
import kotlin.test.assertNotNull
|
import kotlin.test.assertNotNull
|
||||||
|
|
||||||
class ArgsParserTest {
|
class NodeArgsParserTest {
|
||||||
private val parser = ArgsParser()
|
private val parser = NodeArgsParser()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private lateinit var workingDirectory: Path
|
private lateinit var workingDirectory: Path
|
||||||
@ -45,7 +45,6 @@ class ArgsParserTest {
|
|||||||
assertThat(parser.parse()).isEqualTo(CmdLineOptions(
|
assertThat(parser.parse()).isEqualTo(CmdLineOptions(
|
||||||
baseDirectory = workingDirectory,
|
baseDirectory = workingDirectory,
|
||||||
configFile = workingDirectory / "node.conf",
|
configFile = workingDirectory / "node.conf",
|
||||||
help = false,
|
|
||||||
logToConsole = false,
|
logToConsole = false,
|
||||||
loggingLevel = Level.INFO,
|
loggingLevel = Level.INFO,
|
||||||
nodeRegistrationOption = null,
|
nodeRegistrationOption = null,
|
||||||
@ -176,7 +175,6 @@ class ArgsParserTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `on-unknown-config-keys options`() {
|
fun `on-unknown-config-keys options`() {
|
||||||
|
|
||||||
UnknownConfigKeysPolicy.values().forEach { onUnknownConfigKeyPolicy ->
|
UnknownConfigKeysPolicy.values().forEach { onUnknownConfigKeyPolicy ->
|
||||||
val cmdLineOptions = parser.parse("--on-unknown-config-keys", onUnknownConfigKeyPolicy.name)
|
val cmdLineOptions = parser.parse("--on-unknown-config-keys", onUnknownConfigKeyPolicy.name)
|
||||||
assertThat(cmdLineOptions.unknownConfigKeysPolicy).isEqualTo(onUnknownConfigKeyPolicy)
|
assertThat(cmdLineOptions.unknownConfigKeysPolicy).isEqualTo(onUnknownConfigKeyPolicy)
|
@ -19,45 +19,39 @@ import java.nio.file.Paths
|
|||||||
@InitiatingFlow
|
@InitiatingFlow
|
||||||
class DummyFlow : FlowLogic<Unit>() {
|
class DummyFlow : FlowLogic<Unit>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call() {
|
override fun call() = Unit
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@InitiatedBy(DummyFlow::class)
|
@InitiatedBy(DummyFlow::class)
|
||||||
class LoaderTestFlow(unusedSession: FlowSession) : FlowLogic<Unit>() {
|
class LoaderTestFlow(@Suppress("UNUSED_PARAMETER") unusedSession: FlowSession) : FlowLogic<Unit>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call() {
|
override fun call() = Unit
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SchedulableFlow
|
@SchedulableFlow
|
||||||
class DummySchedulableFlow : FlowLogic<Unit>() {
|
class DummySchedulableFlow : FlowLogic<Unit>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call() {
|
override fun call() = Unit
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@StartableByRPC
|
@StartableByRPC
|
||||||
class DummyRPCFlow : FlowLogic<Unit>() {
|
class DummyRPCFlow : FlowLogic<Unit>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call() {
|
override fun call() = Unit
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class CordappLoaderTest {
|
class CordappLoaderTest {
|
||||||
private companion object {
|
private companion object {
|
||||||
val testScanPackages = listOf("net.corda.node.internal.cordapp")
|
const val testScanPackage = "net.corda.node.internal.cordapp"
|
||||||
val isolatedContractId = "net.corda.finance.contracts.isolated.AnotherDummyContract"
|
const val isolatedContractId = "net.corda.finance.contracts.isolated.AnotherDummyContract"
|
||||||
val isolatedFlowName = "net.corda.finance.contracts.isolated.IsolatedDummyFlow\$Initiator"
|
const val isolatedFlowName = "net.corda.finance.contracts.isolated.IsolatedDummyFlow\$Initiator"
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test that classes that aren't in cordapps aren't loaded`() {
|
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
|
// 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("."))
|
val loader = CordappLoader.createDefault(Paths.get("."))
|
||||||
assertThat(loader.cordapps)
|
assertThat(loader.cordapps).containsOnly(CordappLoader.coreCordapp)
|
||||||
.hasSize(1)
|
|
||||||
.contains(CordappLoader.coreCordapp)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -81,7 +75,7 @@ class CordappLoaderTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `flows are loaded by loader`() {
|
fun `flows are loaded by loader`() {
|
||||||
val loader = CordappLoader.createWithTestPackages(testScanPackages)
|
val loader = CordappLoader.createWithTestPackages(listOf(testScanPackage))
|
||||||
|
|
||||||
val actual = loader.cordapps.toTypedArray()
|
val actual = loader.cordapps.toTypedArray()
|
||||||
// One core cordapp, one cordapp from this source tree, and two others due to identically named locations
|
// 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)
|
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
|
// 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.
|
// being used internally. Later iterations will use a classloader per cordapp and this test can be retired.
|
||||||
@Test
|
@Test
|
||||||
|
@ -16,8 +16,8 @@ import net.corda.core.crypto.SecureHash
|
|||||||
import net.corda.core.crypto.sha256
|
import net.corda.core.crypto.sha256
|
||||||
import net.corda.core.flows.NotarisationRequestSignature
|
import net.corda.core.flows.NotarisationRequestSignature
|
||||||
import net.corda.core.flows.NotaryError
|
import net.corda.core.flows.NotaryError
|
||||||
import net.corda.core.flows.NotaryInternalException
|
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.identity.CordaX500Name
|
||||||
|
import net.corda.core.internal.notary.NotaryInternalException
|
||||||
import net.corda.node.internal.configureDatabase
|
import net.corda.node.internal.configureDatabase
|
||||||
import net.corda.node.services.schema.NodeSchemaService
|
import net.corda.node.services.schema.NodeSchemaService
|
||||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
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.crypto.*
|
||||||
import net.corda.core.flows.*
|
import net.corda.core.flows.*
|
||||||
import net.corda.core.identity.Party
|
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.messaging.MessageRecipients
|
||||||
import net.corda.core.node.ServiceHub
|
import net.corda.core.node.ServiceHub
|
||||||
import net.corda.core.serialization.deserialize
|
import net.corda.core.serialization.deserialize
|
||||||
|
@ -108,17 +108,17 @@ open class VaultQueryTests {
|
|||||||
// register additional identities
|
// register additional identities
|
||||||
val databaseAndServices = makeTestDatabaseAndMockServices(
|
val databaseAndServices = makeTestDatabaseAndMockServices(
|
||||||
cordappPackages,
|
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,
|
Companion.megaCorp,
|
||||||
moreKeys = Companion.DUMMY_NOTARY_KEY)
|
moreKeys = DUMMY_NOTARY_KEY)
|
||||||
database = databaseAndServices.first
|
database = databaseAndServices.first
|
||||||
services = databaseAndServices.second
|
services = databaseAndServices.second
|
||||||
vaultFiller = VaultFiller(services, Companion.dummyNotary)
|
vaultFiller = VaultFiller(services, dummyNotary)
|
||||||
vaultFillerCashNotary = VaultFiller(services, Companion.dummyNotary, Companion.CASH_NOTARY)
|
vaultFillerCashNotary = VaultFiller(services, dummyNotary, CASH_NOTARY)
|
||||||
notaryServices = MockServices(cordappPackages, Companion.dummyNotary, rigorousMock(), Companion.dummyCashIssuer.keyPair, Companion.BOC_KEY, Companion.MEGA_CORP_KEY)
|
notaryServices = MockServices(cordappPackages, dummyNotary, rigorousMock(), dummyCashIssuer.keyPair, BOC_KEY, MEGA_CORP_KEY)
|
||||||
identitySvc = services.identityService
|
identitySvc = services.identityService
|
||||||
// Register all of the identities we're going to use
|
// 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)
|
services.identityService.verifyAndRegisterIdentity(identity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,10 +15,11 @@ import net.corda.core.contracts.TimeWindow
|
|||||||
import net.corda.core.contracts.TransactionVerificationException
|
import net.corda.core.contracts.TransactionVerificationException
|
||||||
import net.corda.core.flows.*
|
import net.corda.core.flows.*
|
||||||
import net.corda.core.internal.ResolveTransactionsFlow
|
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.AppServiceHub
|
||||||
import net.corda.core.node.services.CordaService
|
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.SignedTransaction
|
||||||
import net.corda.core.transactions.TransactionWithSignatures
|
import net.corda.core.transactions.TransactionWithSignatures
|
||||||
import net.corda.core.transactions.WireTransaction
|
import net.corda.core.transactions.WireTransaction
|
||||||
@ -46,7 +47,7 @@ class MyCustomValidatingNotaryService(override val services: AppServiceHub, over
|
|||||||
|
|
||||||
@Suppress("UNUSED_PARAMETER")
|
@Suppress("UNUSED_PARAMETER")
|
||||||
// START 2
|
// 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
|
* The received transaction is checked for contract-validity, for which the caller also has to to reveal the whole
|
||||||
* transaction dependency chain.
|
* transaction dependency chain.
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
package net.corda.testing.node.internal
|
package net.corda.testing.node.internal
|
||||||
|
|
||||||
import net.corda.client.mock.Generator
|
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.RPCClient
|
||||||
import net.corda.client.rpc.internal.CordaRPCClientConfigurationImpl
|
import net.corda.client.rpc.internal.CordaRPCClientConfigurationImpl
|
||||||
import net.corda.core.concurrent.CordaFuture
|
import net.corda.core.concurrent.CordaFuture
|
||||||
|
@ -12,7 +12,7 @@ package net.corda.smoketesting
|
|||||||
|
|
||||||
import net.corda.client.rpc.CordaRPCClient
|
import net.corda.client.rpc.CordaRPCClient
|
||||||
import net.corda.client.rpc.CordaRPCConnection
|
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.internal.*
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
import net.corda.core.utilities.contextLogger
|
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.any
|
||||||
import com.nhaarman.mockito_kotlin.doAnswer
|
import com.nhaarman.mockito_kotlin.doAnswer
|
||||||
import com.nhaarman.mockito_kotlin.doNothing
|
|
||||||
import com.nhaarman.mockito_kotlin.whenever
|
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.internal.staticField
|
||||||
import net.corda.core.serialization.internal.SerializationEnvironment
|
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.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.common.internal.asContextEnv
|
||||||
import net.corda.testing.internal.createTestSerializationEnv
|
import net.corda.testing.internal.createTestSerializationEnv
|
||||||
import net.corda.testing.internal.inVMExecutors
|
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.rules.TestRule
|
||||||
import org.junit.runner.Description
|
import org.junit.runner.Description
|
||||||
import org.junit.runners.model.Statement
|
import org.junit.runners.model.Statement
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
|
||||||
import java.util.concurrent.ExecutorService
|
import java.util.concurrent.ExecutorService
|
||||||
import java.util.concurrent.Executors
|
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.doNothing
|
||||||
import com.nhaarman.mockito_kotlin.whenever
|
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.DoNotImplement
|
||||||
import net.corda.core.serialization.internal.*
|
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.*
|
||||||
import net.corda.nodeapi.internal.serialization.amqp.AMQPClientSerializationScheme
|
import net.corda.nodeapi.internal.serialization.amqp.AMQPClientSerializationScheme
|
||||||
import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme
|
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 {
|
dependencies {
|
||||||
// TornadoFX: A lightweight Kotlin framework for working with JavaFX UI's.
|
// TornadoFX: A lightweight Kotlin framework for working with JavaFX UI's.
|
||||||
compile "no.tornado:tornadofx:$tornadofx_version"
|
compile "no.tornado:tornadofx:$tornadofx_version"
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
package net.corda.demobench
|
package net.corda.demobench
|
||||||
|
|
||||||
import javafx.scene.image.Image
|
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.SerializationEnvironmentImpl
|
||||||
import net.corda.core.serialization.internal.nodeSerializationEnv
|
import net.corda.core.serialization.internal.nodeSerializationEnv
|
||||||
import net.corda.demobench.views.DemoBenchView
|
import net.corda.demobench.views.DemoBenchView
|
||||||
|
Loading…
x
Reference in New Issue
Block a user