mirror of
https://github.com/corda/corda.git
synced 2024-12-27 00:21:12 +00:00
Merge remote-tracking branch 'open/master' into aslemmer-merge-19-Feb
This commit is contained in:
commit
25263c20c7
@ -634,10 +634,25 @@ public static final class net.corda.core.contracts.UniqueIdentifier$Companion ex
|
|||||||
@org.jetbrains.annotations.NotNull public abstract List getServiceFlows()
|
@org.jetbrains.annotations.NotNull public abstract List getServiceFlows()
|
||||||
@org.jetbrains.annotations.NotNull public abstract List getServices()
|
@org.jetbrains.annotations.NotNull public abstract List getServices()
|
||||||
##
|
##
|
||||||
|
@net.corda.core.DoNotImplement public interface net.corda.core.cordapp.CordappConfig
|
||||||
|
public abstract boolean exists(String)
|
||||||
|
@org.jetbrains.annotations.NotNull public abstract Object get(String)
|
||||||
|
public abstract boolean getBoolean(String)
|
||||||
|
public abstract double getDouble(String)
|
||||||
|
public abstract float getFloat(String)
|
||||||
|
public abstract int getInt(String)
|
||||||
|
public abstract long getLong(String)
|
||||||
|
@org.jetbrains.annotations.NotNull public abstract Number getNumber(String)
|
||||||
|
@org.jetbrains.annotations.NotNull public abstract String getString(String)
|
||||||
|
##
|
||||||
|
public final class net.corda.core.cordapp.CordappConfigException extends java.lang.Exception
|
||||||
|
public <init>(String, Throwable)
|
||||||
|
##
|
||||||
public final class net.corda.core.cordapp.CordappContext extends java.lang.Object
|
public final class net.corda.core.cordapp.CordappContext extends java.lang.Object
|
||||||
public <init>(net.corda.core.cordapp.Cordapp, net.corda.core.crypto.SecureHash, ClassLoader)
|
public <init>(net.corda.core.cordapp.Cordapp, net.corda.core.crypto.SecureHash, ClassLoader, net.corda.core.cordapp.CordappConfig)
|
||||||
@org.jetbrains.annotations.Nullable public final net.corda.core.crypto.SecureHash getAttachmentId()
|
@org.jetbrains.annotations.Nullable public final net.corda.core.crypto.SecureHash getAttachmentId()
|
||||||
@org.jetbrains.annotations.NotNull public final ClassLoader getClassLoader()
|
@org.jetbrains.annotations.NotNull public final ClassLoader getClassLoader()
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.core.cordapp.CordappConfig getConfig()
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.core.cordapp.Cordapp getCordapp()
|
@org.jetbrains.annotations.NotNull public final net.corda.core.cordapp.Cordapp getCordapp()
|
||||||
##
|
##
|
||||||
@net.corda.core.DoNotImplement public interface net.corda.core.cordapp.CordappProvider
|
@net.corda.core.DoNotImplement public interface net.corda.core.cordapp.CordappProvider
|
||||||
@ -1131,7 +1146,8 @@ public final class net.corda.core.flows.ContractUpgradeFlow extends java.lang.Ob
|
|||||||
public <init>(net.corda.core.contracts.StateAndRef, Class)
|
public <init>(net.corda.core.contracts.StateAndRef, Class)
|
||||||
@co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull protected net.corda.core.flows.AbstractStateReplacementFlow$UpgradeTx assembleTx()
|
@co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull protected net.corda.core.flows.AbstractStateReplacementFlow$UpgradeTx assembleTx()
|
||||||
##
|
##
|
||||||
public abstract class net.corda.core.flows.DataVendingFlow extends net.corda.core.flows.FlowLogic
|
public class net.corda.core.flows.DataVendingFlow extends net.corda.core.flows.FlowLogic
|
||||||
|
public <init>(net.corda.core.flows.FlowSession, Object)
|
||||||
@co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.Nullable public Void call()
|
@co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.Nullable public Void call()
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.core.flows.FlowSession getOtherSideSession()
|
@org.jetbrains.annotations.NotNull public final net.corda.core.flows.FlowSession getOtherSideSession()
|
||||||
@org.jetbrains.annotations.NotNull public final Object getPayload()
|
@org.jetbrains.annotations.NotNull public final Object getPayload()
|
||||||
@ -1238,7 +1254,6 @@ public abstract class net.corda.core.flows.FlowLogic extends java.lang.Object
|
|||||||
@org.jetbrains.annotations.Nullable public net.corda.core.utilities.ProgressTracker getProgressTracker()
|
@org.jetbrains.annotations.Nullable public net.corda.core.utilities.ProgressTracker getProgressTracker()
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.core.flows.StateMachineRunId getRunId()
|
@org.jetbrains.annotations.NotNull public final net.corda.core.flows.StateMachineRunId getRunId()
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.core.node.ServiceHub getServiceHub()
|
@org.jetbrains.annotations.NotNull public final net.corda.core.node.ServiceHub getServiceHub()
|
||||||
@net.corda.core.CordaInternal @org.jetbrains.annotations.NotNull public final net.corda.core.internal.FlowStateMachine getStateMachine()
|
|
||||||
@co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public final net.corda.core.flows.FlowSession initiateFlow(net.corda.core.identity.Party)
|
@co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public final net.corda.core.flows.FlowSession initiateFlow(net.corda.core.identity.Party)
|
||||||
@co.paralleluniverse.fibers.Suspendable public final void persistFlowStackSnapshot()
|
@co.paralleluniverse.fibers.Suspendable public final void persistFlowStackSnapshot()
|
||||||
@co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public List receiveAll(Class, List)
|
@co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public List receiveAll(Class, List)
|
||||||
@ -1246,7 +1261,6 @@ public abstract class net.corda.core.flows.FlowLogic extends java.lang.Object
|
|||||||
@co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public Map receiveAllMap(Map)
|
@co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public Map receiveAllMap(Map)
|
||||||
@co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public Map receiveAllMap(Map, boolean)
|
@co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public Map receiveAllMap(Map, boolean)
|
||||||
public final void recordAuditEvent(String, String, Map)
|
public final void recordAuditEvent(String, String, Map)
|
||||||
@net.corda.core.CordaInternal public final void setStateMachine(net.corda.core.internal.FlowStateMachine)
|
|
||||||
@co.paralleluniverse.fibers.Suspendable @kotlin.jvm.JvmStatic public static final void sleep(java.time.Duration)
|
@co.paralleluniverse.fibers.Suspendable @kotlin.jvm.JvmStatic public static final void sleep(java.time.Duration)
|
||||||
@co.paralleluniverse.fibers.Suspendable @kotlin.jvm.JvmStatic public static final void sleep(java.time.Duration, boolean)
|
@co.paralleluniverse.fibers.Suspendable @kotlin.jvm.JvmStatic public static final void sleep(java.time.Duration, boolean)
|
||||||
@co.paralleluniverse.fibers.Suspendable public Object subFlow(net.corda.core.flows.FlowLogic)
|
@co.paralleluniverse.fibers.Suspendable public Object subFlow(net.corda.core.flows.FlowLogic)
|
||||||
@ -1315,6 +1329,39 @@ public @interface net.corda.core.flows.InitiatedBy
|
|||||||
public @interface net.corda.core.flows.InitiatingFlow
|
public @interface net.corda.core.flows.InitiatingFlow
|
||||||
public abstract int version()
|
public abstract int version()
|
||||||
##
|
##
|
||||||
|
@net.corda.core.serialization.CordaSerializable public final class net.corda.core.flows.NotarisationPayload extends java.lang.Object
|
||||||
|
public <init>(Object, net.corda.core.flows.NotarisationRequestSignature)
|
||||||
|
@org.jetbrains.annotations.NotNull public final Object component1()
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.core.flows.NotarisationRequestSignature component2()
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.core.flows.NotarisationPayload copy(Object, net.corda.core.flows.NotarisationRequestSignature)
|
||||||
|
public boolean equals(Object)
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.CoreTransaction getCoreTransaction()
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.core.flows.NotarisationRequestSignature getRequestSignature()
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.SignedTransaction getSignedTransaction()
|
||||||
|
@org.jetbrains.annotations.NotNull public final Object getTransaction()
|
||||||
|
public int hashCode()
|
||||||
|
public String toString()
|
||||||
|
##
|
||||||
|
@net.corda.core.serialization.CordaSerializable public final class net.corda.core.flows.NotarisationRequest extends java.lang.Object
|
||||||
|
public <init>(List, net.corda.core.crypto.SecureHash)
|
||||||
|
@org.jetbrains.annotations.NotNull public final List getStatesToConsume()
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getTransactionId()
|
||||||
|
public final void verifySignature(net.corda.core.flows.NotarisationRequestSignature, net.corda.core.identity.Party)
|
||||||
|
public static final net.corda.core.flows.NotarisationRequest$Companion Companion
|
||||||
|
##
|
||||||
|
public static final class net.corda.core.flows.NotarisationRequest$Companion extends java.lang.Object
|
||||||
|
##
|
||||||
|
@net.corda.core.serialization.CordaSerializable public final class net.corda.core.flows.NotarisationRequestSignature extends java.lang.Object
|
||||||
|
public <init>(net.corda.core.crypto.DigitalSignature$WithKey, int)
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.core.crypto.DigitalSignature$WithKey component1()
|
||||||
|
public final int component2()
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.core.flows.NotarisationRequestSignature copy(net.corda.core.crypto.DigitalSignature$WithKey, int)
|
||||||
|
public boolean equals(Object)
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.core.crypto.DigitalSignature$WithKey getDigitalSignature()
|
||||||
|
public final int getPlatformVersion()
|
||||||
|
public int hashCode()
|
||||||
|
public String toString()
|
||||||
|
##
|
||||||
@net.corda.core.flows.InitiatingFlow public final class net.corda.core.flows.NotaryChangeFlow extends net.corda.core.flows.AbstractStateReplacementFlow$Instigator
|
@net.corda.core.flows.InitiatingFlow public final class net.corda.core.flows.NotaryChangeFlow extends net.corda.core.flows.AbstractStateReplacementFlow$Instigator
|
||||||
public <init>(net.corda.core.contracts.StateAndRef, net.corda.core.identity.Party, net.corda.core.utilities.ProgressTracker)
|
public <init>(net.corda.core.contracts.StateAndRef, net.corda.core.identity.Party, net.corda.core.utilities.ProgressTracker)
|
||||||
@org.jetbrains.annotations.NotNull protected net.corda.core.flows.AbstractStateReplacementFlow$UpgradeTx assembleTx()
|
@org.jetbrains.annotations.NotNull protected net.corda.core.flows.AbstractStateReplacementFlow$UpgradeTx assembleTx()
|
||||||
@ -1333,11 +1380,20 @@ public @interface net.corda.core.flows.InitiatingFlow
|
|||||||
@org.jetbrains.annotations.NotNull public String toString()
|
@org.jetbrains.annotations.NotNull public String toString()
|
||||||
##
|
##
|
||||||
@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.NotaryError$General extends net.corda.core.flows.NotaryError
|
@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.NotaryError$General extends net.corda.core.flows.NotaryError
|
||||||
public <init>(String)
|
public <init>(Throwable)
|
||||||
@org.jetbrains.annotations.NotNull public final String component1()
|
@org.jetbrains.annotations.NotNull public final Throwable component1()
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.core.flows.NotaryError$General copy(String)
|
@org.jetbrains.annotations.NotNull public final net.corda.core.flows.NotaryError$General copy(Throwable)
|
||||||
public boolean equals(Object)
|
public boolean equals(Object)
|
||||||
@org.jetbrains.annotations.NotNull public final String getCause()
|
@org.jetbrains.annotations.NotNull public final Throwable getCause()
|
||||||
|
public int hashCode()
|
||||||
|
@org.jetbrains.annotations.NotNull public String toString()
|
||||||
|
##
|
||||||
|
@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.NotaryError$RequestSignatureInvalid extends net.corda.core.flows.NotaryError
|
||||||
|
public <init>(Throwable)
|
||||||
|
@org.jetbrains.annotations.NotNull public final Throwable component1()
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.core.flows.NotaryError$RequestSignatureInvalid copy(Throwable)
|
||||||
|
public boolean equals(Object)
|
||||||
|
@org.jetbrains.annotations.NotNull public final Throwable getCause()
|
||||||
public int hashCode()
|
public int hashCode()
|
||||||
@org.jetbrains.annotations.NotNull public String toString()
|
@org.jetbrains.annotations.NotNull public String toString()
|
||||||
##
|
##
|
||||||
@ -1382,8 +1438,6 @@ public final class net.corda.core.flows.NotaryFlow extends java.lang.Object
|
|||||||
@org.jetbrains.annotations.NotNull protected final net.corda.core.identity.Party checkTransaction()
|
@org.jetbrains.annotations.NotNull protected final net.corda.core.identity.Party checkTransaction()
|
||||||
@org.jetbrains.annotations.NotNull public net.corda.core.utilities.ProgressTracker getProgressTracker()
|
@org.jetbrains.annotations.NotNull public net.corda.core.utilities.ProgressTracker getProgressTracker()
|
||||||
@co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull protected final net.corda.core.utilities.UntrustworthyData notarise(net.corda.core.identity.Party)
|
@co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull protected final net.corda.core.utilities.UntrustworthyData notarise(net.corda.core.identity.Party)
|
||||||
@co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull protected net.corda.core.utilities.UntrustworthyData sendAndReceiveNonValidating(net.corda.core.identity.Party, net.corda.core.flows.FlowSession)
|
|
||||||
@co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull protected net.corda.core.utilities.UntrustworthyData sendAndReceiveValidating(net.corda.core.flows.FlowSession)
|
|
||||||
@org.jetbrains.annotations.NotNull protected final List validateResponse(net.corda.core.utilities.UntrustworthyData, net.corda.core.identity.Party)
|
@org.jetbrains.annotations.NotNull protected final List validateResponse(net.corda.core.utilities.UntrustworthyData, net.corda.core.identity.Party)
|
||||||
public static final net.corda.core.flows.NotaryFlow$Client$Companion Companion
|
public static final net.corda.core.flows.NotaryFlow$Client$Companion Companion
|
||||||
##
|
##
|
||||||
@ -1819,6 +1873,7 @@ public @interface net.corda.core.messaging.RPCReturnsObservables
|
|||||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.FilteredTransaction, java.security.PublicKey)
|
@org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.FilteredTransaction, java.security.PublicKey)
|
||||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.SignedTransaction)
|
@org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.SignedTransaction)
|
||||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.SignedTransaction, java.security.PublicKey)
|
@org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.SignedTransaction, java.security.PublicKey)
|
||||||
|
@org.jetbrains.annotations.NotNull public abstract net.corda.core.cordapp.CordappContext getAppContext()
|
||||||
@org.jetbrains.annotations.NotNull public abstract java.time.Clock getClock()
|
@org.jetbrains.annotations.NotNull public abstract java.time.Clock getClock()
|
||||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.ContractUpgradeService getContractUpgradeService()
|
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.ContractUpgradeService getContractUpgradeService()
|
||||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.KeyManagementService getKeyManagementService()
|
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.KeyManagementService getKeyManagementService()
|
||||||
@ -1841,6 +1896,7 @@ public @interface net.corda.core.messaging.RPCReturnsObservables
|
|||||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.AttachmentStorage getAttachments()
|
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.AttachmentStorage getAttachments()
|
||||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.cordapp.CordappProvider getCordappProvider()
|
@org.jetbrains.annotations.NotNull public abstract net.corda.core.cordapp.CordappProvider getCordappProvider()
|
||||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.IdentityService getIdentityService()
|
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.IdentityService getIdentityService()
|
||||||
|
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.NetworkParameters getNetworkParameters()
|
||||||
##
|
##
|
||||||
@net.corda.core.DoNotImplement public interface net.corda.core.node.StateLoader
|
@net.corda.core.DoNotImplement public interface net.corda.core.node.StateLoader
|
||||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.TransactionState loadState(net.corda.core.contracts.StateRef)
|
@org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.TransactionState loadState(net.corda.core.contracts.StateRef)
|
||||||
@ -2940,7 +2996,7 @@ public static final class net.corda.core.serialization.SingletonSerializationTok
|
|||||||
@org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getId()
|
@org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getId()
|
||||||
@org.jetbrains.annotations.NotNull public final String getReason()
|
@org.jetbrains.annotations.NotNull public final String getReason()
|
||||||
##
|
##
|
||||||
@net.corda.core.DoNotImplement public abstract class net.corda.core.transactions.CoreTransaction extends net.corda.core.transactions.BaseTransaction
|
@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public abstract class net.corda.core.transactions.CoreTransaction extends net.corda.core.transactions.BaseTransaction
|
||||||
public <init>()
|
public <init>()
|
||||||
@org.jetbrains.annotations.NotNull public abstract List getInputs()
|
@org.jetbrains.annotations.NotNull public abstract List getInputs()
|
||||||
##
|
##
|
||||||
@ -3182,7 +3238,7 @@ public class net.corda.core.transactions.TransactionBuilder extends java.lang.Ob
|
|||||||
public abstract void verifyRequiredSignatures()
|
public abstract void verifyRequiredSignatures()
|
||||||
public abstract void verifySignaturesExcept(Collection)
|
public abstract void verifySignaturesExcept(Collection)
|
||||||
##
|
##
|
||||||
@net.corda.core.DoNotImplement public abstract class net.corda.core.transactions.TraversableTransaction extends net.corda.core.transactions.CoreTransaction
|
@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public abstract class net.corda.core.transactions.TraversableTransaction extends net.corda.core.transactions.CoreTransaction
|
||||||
public <init>(List)
|
public <init>(List)
|
||||||
@org.jetbrains.annotations.NotNull public final List getAttachments()
|
@org.jetbrains.annotations.NotNull public final List getAttachments()
|
||||||
@org.jetbrains.annotations.NotNull public final List getAvailableComponentGroups()
|
@org.jetbrains.annotations.NotNull public final List getAvailableComponentGroups()
|
||||||
@ -3204,7 +3260,7 @@ public class net.corda.core.transactions.TransactionBuilder extends java.lang.Ob
|
|||||||
@org.jetbrains.annotations.NotNull public final net.corda.core.contracts.PrivacySalt getPrivacySalt()
|
@org.jetbrains.annotations.NotNull public final net.corda.core.contracts.PrivacySalt getPrivacySalt()
|
||||||
@org.jetbrains.annotations.NotNull public final Set getRequiredSigningKeys()
|
@org.jetbrains.annotations.NotNull public final Set getRequiredSigningKeys()
|
||||||
public int hashCode()
|
public int hashCode()
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.LedgerTransaction toLedgerTransaction(kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function1)
|
@kotlin.Deprecated @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.LedgerTransaction toLedgerTransaction(kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function1)
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.LedgerTransaction toLedgerTransaction(net.corda.core.node.ServicesForResolution)
|
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.LedgerTransaction toLedgerTransaction(net.corda.core.node.ServicesForResolution)
|
||||||
@org.jetbrains.annotations.NotNull public String toString()
|
@org.jetbrains.annotations.NotNull public String toString()
|
||||||
public static final net.corda.core.transactions.WireTransaction$Companion Companion
|
public static final net.corda.core.transactions.WireTransaction$Companion Companion
|
||||||
@ -3709,20 +3765,20 @@ public final class net.corda.testing.driver.DriverParameters extends java.lang.O
|
|||||||
public final boolean getWaitForAllNodesToFinish()
|
public final boolean getWaitForAllNodesToFinish()
|
||||||
public int hashCode()
|
public int hashCode()
|
||||||
public final boolean isDebug()
|
public final boolean isDebug()
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters setDebugPortAllocation(net.corda.testing.driver.PortAllocation)
|
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters setDriverDirectory(java.nio.file.Path)
|
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters setExtraCordappPackagesToScan(List)
|
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters setInitialiseSerialization(boolean)
|
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters setIsDebug(boolean)
|
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters setJmxPolicy(net.corda.testing.driver.JmxPolicy)
|
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters setNetworkParameters(net.corda.core.node.NetworkParameters)
|
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters setNotarySpecs(List)
|
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters setPortAllocation(net.corda.testing.driver.PortAllocation)
|
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters setStartNodesInProcess(boolean)
|
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters setSystemProperties(Map)
|
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters setUseTestClock(boolean)
|
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters setWaitForAllNodesToFinish(boolean)
|
|
||||||
public String toString()
|
public String toString()
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters withDebugPortAllocation(net.corda.testing.driver.PortAllocation)
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters withDriverDirectory(java.nio.file.Path)
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters withExtraCordappPackagesToScan(List)
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters withInitialiseSerialization(boolean)
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters withIsDebug(boolean)
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters withJmxPolicy(net.corda.testing.driver.JmxPolicy)
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters withNetworkParameters(net.corda.core.node.NetworkParameters)
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters withNotarySpecs(List)
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters withPortAllocation(net.corda.testing.driver.PortAllocation)
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters withStartNodesInProcess(boolean)
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters withSystemProperties(Map)
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters withUseTestClock(boolean)
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters withWaitForAllNodesToFinish(boolean)
|
||||||
##
|
##
|
||||||
@net.corda.core.DoNotImplement public interface net.corda.testing.driver.InProcess extends net.corda.testing.driver.NodeHandle
|
@net.corda.core.DoNotImplement public interface net.corda.testing.driver.InProcess extends net.corda.testing.driver.NodeHandle
|
||||||
@org.jetbrains.annotations.NotNull public abstract net.corda.node.services.api.StartedNodeServices getServices()
|
@org.jetbrains.annotations.NotNull public abstract net.corda.node.services.api.StartedNodeServices getServices()
|
||||||
@ -3769,14 +3825,14 @@ public final class net.corda.testing.driver.NodeParameters extends java.lang.Obj
|
|||||||
@org.jetbrains.annotations.Nullable public final Boolean getStartInSameProcess()
|
@org.jetbrains.annotations.Nullable public final Boolean getStartInSameProcess()
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.node.services.config.VerifierType getVerifierType()
|
@org.jetbrains.annotations.NotNull public final net.corda.node.services.config.VerifierType getVerifierType()
|
||||||
public int hashCode()
|
public int hashCode()
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.NodeParameters setCustomerOverrides(Map)
|
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.NodeParameters setLogLevel(String)
|
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.NodeParameters setMaximumHeapSize(String)
|
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.NodeParameters setProvidedName(net.corda.core.identity.CordaX500Name)
|
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.NodeParameters setRpcUsers(List)
|
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.NodeParameters setStartInSameProcess(Boolean)
|
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.NodeParameters setVerifierType(net.corda.node.services.config.VerifierType)
|
|
||||||
public String toString()
|
public String toString()
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.NodeParameters withCustomerOverrides(Map)
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.NodeParameters withLogLevel(String)
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.NodeParameters withMaximumHeapSize(String)
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.NodeParameters withProvidedName(net.corda.core.identity.CordaX500Name)
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.NodeParameters withRpcUsers(List)
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.NodeParameters withStartInSameProcess(Boolean)
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.NodeParameters withVerifierType(net.corda.node.services.config.VerifierType)
|
||||||
##
|
##
|
||||||
public final class net.corda.testing.driver.NotaryHandle extends java.lang.Object
|
public final class net.corda.testing.driver.NotaryHandle extends java.lang.Object
|
||||||
public <init>(net.corda.core.identity.Party, boolean, net.corda.core.concurrent.CordaFuture)
|
public <init>(net.corda.core.identity.Party, boolean, net.corda.core.concurrent.CordaFuture)
|
||||||
@ -3832,7 +3888,6 @@ public static final class net.corda.testing.node.ClusterSpec$Raft extends net.co
|
|||||||
public String toString()
|
public String toString()
|
||||||
##
|
##
|
||||||
@javax.annotation.concurrent.ThreadSafe public final class net.corda.testing.node.InMemoryMessagingNetwork extends net.corda.core.serialization.SingletonSerializeAsToken
|
@javax.annotation.concurrent.ThreadSafe public final class net.corda.testing.node.InMemoryMessagingNetwork extends net.corda.core.serialization.SingletonSerializeAsToken
|
||||||
public <init>(boolean, net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy, org.apache.activemq.artemis.utils.ReusableLatch)
|
|
||||||
@org.jetbrains.annotations.NotNull public synchronized final List getEndpoints()
|
@org.jetbrains.annotations.NotNull public synchronized final List getEndpoints()
|
||||||
@org.jetbrains.annotations.NotNull public final rx.Observable getReceivedMessages()
|
@org.jetbrains.annotations.NotNull public final rx.Observable getReceivedMessages()
|
||||||
@org.jetbrains.annotations.NotNull public final rx.Observable getSentMessages()
|
@org.jetbrains.annotations.NotNull public final rx.Observable getSentMessages()
|
||||||
@ -3921,10 +3976,6 @@ public static final class net.corda.testing.node.InMemoryMessagingNetwork$Servic
|
|||||||
@org.jetbrains.annotations.Nullable public abstract net.corda.testing.node.InMemoryMessagingNetwork$MessageTransfer pumpReceive(boolean)
|
@org.jetbrains.annotations.Nullable public abstract net.corda.testing.node.InMemoryMessagingNetwork$MessageTransfer pumpReceive(boolean)
|
||||||
public abstract void stop()
|
public abstract void stop()
|
||||||
##
|
##
|
||||||
public static final class net.corda.testing.node.InMemoryMessagingNetwork$pumpSend$$inlined$schedule$1 extends java.util.TimerTask
|
|
||||||
public <init>(net.corda.testing.node.InMemoryMessagingNetwork, net.corda.testing.node.InMemoryMessagingNetwork$MessageTransfer, net.corda.core.internal.concurrent.OpenFuture)
|
|
||||||
public void run()
|
|
||||||
##
|
|
||||||
public class net.corda.testing.node.MessagingServiceSpy extends java.lang.Object implements net.corda.node.services.messaging.MessagingService
|
public class net.corda.testing.node.MessagingServiceSpy extends java.lang.Object implements net.corda.node.services.messaging.MessagingService
|
||||||
public <init>(net.corda.node.services.messaging.MessagingService)
|
public <init>(net.corda.node.services.messaging.MessagingService)
|
||||||
@org.jetbrains.annotations.NotNull public net.corda.node.services.messaging.MessageHandlerRegistration addMessageHandler(String, kotlin.jvm.functions.Function3)
|
@org.jetbrains.annotations.NotNull public net.corda.node.services.messaging.MessageHandlerRegistration addMessageHandler(String, kotlin.jvm.functions.Function3)
|
||||||
@ -3949,17 +4000,28 @@ public final class net.corda.testing.node.MockKeyManagementService extends net.c
|
|||||||
public class net.corda.testing.node.MockNetwork extends java.lang.Object
|
public class net.corda.testing.node.MockNetwork extends java.lang.Object
|
||||||
public <init>(List)
|
public <init>(List)
|
||||||
public <init>(List, net.corda.testing.node.MockNetworkParameters)
|
public <init>(List, net.corda.testing.node.MockNetworkParameters)
|
||||||
public <init>(List, net.corda.testing.node.MockNetworkParameters, boolean, boolean, net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy, boolean, List, int)
|
public <init>(List, net.corda.testing.node.MockNetworkParameters, boolean, boolean, net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy, List, net.corda.core.node.NetworkParameters)
|
||||||
@org.jetbrains.annotations.NotNull public final java.nio.file.Path baseDirectory(int)
|
@org.jetbrains.annotations.NotNull public final java.nio.file.Path baseDirectory(int)
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.StartedMockNode createNode()
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.StartedMockNode createNode(net.corda.core.identity.CordaX500Name)
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.StartedMockNode createNode(net.corda.core.identity.CordaX500Name, Integer)
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.StartedMockNode createNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger)
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.StartedMockNode createNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, kotlin.jvm.functions.Function1)
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.StartedMockNode createNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, kotlin.jvm.functions.Function1, net.corda.node.VersionInfo)
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.StartedMockNode createNode(net.corda.testing.node.MockNodeParameters)
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.StartedMockNode createNode(net.corda.testing.node.MockNodeParameters)
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.StartedMockNode createPartyNode(net.corda.core.identity.CordaX500Name)
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.StartedMockNode createPartyNode(net.corda.core.identity.CordaX500Name)
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.UnstartedMockNode createUnstartedNode()
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.UnstartedMockNode createUnstartedNode(net.corda.core.identity.CordaX500Name)
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.UnstartedMockNode createUnstartedNode(net.corda.core.identity.CordaX500Name, Integer)
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.UnstartedMockNode createUnstartedNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger)
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.UnstartedMockNode createUnstartedNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, kotlin.jvm.functions.Function1)
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.UnstartedMockNode createUnstartedNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, kotlin.jvm.functions.Function1, net.corda.node.VersionInfo)
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.UnstartedMockNode createUnstartedNode(net.corda.testing.node.MockNodeParameters)
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.UnstartedMockNode createUnstartedNode(net.corda.testing.node.MockNodeParameters)
|
||||||
@org.jetbrains.annotations.NotNull public final List getCordappPackages()
|
@org.jetbrains.annotations.NotNull public final List getCordappPackages()
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party getDefaultNotaryIdentity()
|
@org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party getDefaultNotaryIdentity()
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.StartedMockNode getDefaultNotaryNode()
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.StartedMockNode getDefaultNotaryNode()
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetworkParameters getDefaultParameters()
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetworkParameters getDefaultParameters()
|
||||||
public final boolean getInitialiseSerialization()
|
@org.jetbrains.annotations.NotNull public final net.corda.core.node.NetworkParameters getNetworkParameters()
|
||||||
public final int getMaxTransactionSize()
|
|
||||||
public final boolean getNetworkSendManuallyPumped()
|
public final boolean getNetworkSendManuallyPumped()
|
||||||
public final int getNextNodeId()
|
public final int getNextNodeId()
|
||||||
@org.jetbrains.annotations.NotNull public final List getNotaryNodes()
|
@org.jetbrains.annotations.NotNull public final List getNotaryNodes()
|
||||||
@ -3986,29 +4048,26 @@ public final class net.corda.testing.node.MockNetworkNotarySpec extends java.lan
|
|||||||
##
|
##
|
||||||
public final class net.corda.testing.node.MockNetworkParameters extends java.lang.Object
|
public final class net.corda.testing.node.MockNetworkParameters extends java.lang.Object
|
||||||
public <init>()
|
public <init>()
|
||||||
public <init>(boolean, boolean, net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy, boolean, List, int)
|
public <init>(boolean, boolean, net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy, List, net.corda.core.node.NetworkParameters)
|
||||||
public final boolean component1()
|
public final boolean component1()
|
||||||
public final boolean component2()
|
public final boolean component2()
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy component3()
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy component3()
|
||||||
public final boolean component4()
|
@org.jetbrains.annotations.NotNull public final List component4()
|
||||||
@org.jetbrains.annotations.NotNull public final List component5()
|
@org.jetbrains.annotations.NotNull public final net.corda.core.node.NetworkParameters component5()
|
||||||
public final int component6()
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetworkParameters copy(boolean, boolean, net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy, List, net.corda.core.node.NetworkParameters)
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetworkParameters copy(boolean, boolean, net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy, boolean, List, int)
|
|
||||||
public boolean equals(Object)
|
public boolean equals(Object)
|
||||||
public final boolean getInitialiseSerialization()
|
@org.jetbrains.annotations.NotNull public final net.corda.core.node.NetworkParameters getNetworkParameters()
|
||||||
public final int getMaxTransactionSize()
|
|
||||||
public final boolean getNetworkSendManuallyPumped()
|
public final boolean getNetworkSendManuallyPumped()
|
||||||
@org.jetbrains.annotations.NotNull public final List getNotarySpecs()
|
@org.jetbrains.annotations.NotNull public final List getNotarySpecs()
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy getServicePeerAllocationStrategy()
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy getServicePeerAllocationStrategy()
|
||||||
public final boolean getThreadPerNode()
|
public final boolean getThreadPerNode()
|
||||||
public int hashCode()
|
public int hashCode()
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetworkParameters setInitialiseSerialization(boolean)
|
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetworkParameters setMaxTransactionSize(int)
|
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetworkParameters setNetworkSendManuallyPumped(boolean)
|
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetworkParameters setNotarySpecs(List)
|
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetworkParameters setServicePeerAllocationStrategy(net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy)
|
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetworkParameters setThreadPerNode(boolean)
|
|
||||||
public String toString()
|
public String toString()
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetworkParameters withNetworkParameters(net.corda.core.node.NetworkParameters)
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetworkParameters withNetworkSendManuallyPumped(boolean)
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetworkParameters withNotarySpecs(List)
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetworkParameters withServicePeerAllocationStrategy(net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy)
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetworkParameters withThreadPerNode(boolean)
|
||||||
##
|
##
|
||||||
public final class net.corda.testing.node.MockNodeParameters extends java.lang.Object
|
public final class net.corda.testing.node.MockNodeParameters extends java.lang.Object
|
||||||
public <init>()
|
public <init>()
|
||||||
@ -4026,17 +4085,20 @@ public final class net.corda.testing.node.MockNodeParameters extends java.lang.O
|
|||||||
@org.jetbrains.annotations.Nullable public final net.corda.core.identity.CordaX500Name getLegalName()
|
@org.jetbrains.annotations.Nullable public final net.corda.core.identity.CordaX500Name getLegalName()
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.node.VersionInfo getVersion()
|
@org.jetbrains.annotations.NotNull public final net.corda.node.VersionInfo getVersion()
|
||||||
public int hashCode()
|
public int hashCode()
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNodeParameters setConfigOverrides(kotlin.jvm.functions.Function1)
|
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNodeParameters setEntropyRoot(java.math.BigInteger)
|
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNodeParameters setForcedID(Integer)
|
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNodeParameters setLegalName(net.corda.core.identity.CordaX500Name)
|
|
||||||
public String toString()
|
public String toString()
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNodeParameters withConfigOverrides(kotlin.jvm.functions.Function1)
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNodeParameters withEntropyRoot(java.math.BigInteger)
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNodeParameters withForcedID(Integer)
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNodeParameters withLegalName(net.corda.core.identity.CordaX500Name)
|
||||||
##
|
##
|
||||||
public class net.corda.testing.node.MockServices extends java.lang.Object implements net.corda.core.node.StateLoader, net.corda.core.node.ServiceHub
|
public class net.corda.testing.node.MockServices extends java.lang.Object implements net.corda.core.node.StateLoader, net.corda.core.node.ServiceHub
|
||||||
|
public <init>()
|
||||||
|
public <init>(List)
|
||||||
public <init>(List, net.corda.core.identity.CordaX500Name)
|
public <init>(List, net.corda.core.identity.CordaX500Name)
|
||||||
public <init>(List, net.corda.core.node.services.IdentityService, net.corda.core.identity.CordaX500Name)
|
public <init>(List, net.corda.core.identity.CordaX500Name, net.corda.core.node.services.IdentityService)
|
||||||
public <init>(net.corda.core.identity.CordaX500Name)
|
public <init>(net.corda.core.identity.CordaX500Name)
|
||||||
public <init>(net.corda.core.node.services.IdentityService, net.corda.core.identity.CordaX500Name)
|
public <init>(net.corda.core.identity.CordaX500Name, net.corda.core.node.services.IdentityService)
|
||||||
|
public final void addMockCordapp(String)
|
||||||
@org.jetbrains.annotations.NotNull public net.corda.core.transactions.SignedTransaction addSignature(net.corda.core.transactions.SignedTransaction)
|
@org.jetbrains.annotations.NotNull public net.corda.core.transactions.SignedTransaction addSignature(net.corda.core.transactions.SignedTransaction)
|
||||||
@org.jetbrains.annotations.NotNull public net.corda.core.transactions.SignedTransaction addSignature(net.corda.core.transactions.SignedTransaction, java.security.PublicKey)
|
@org.jetbrains.annotations.NotNull public net.corda.core.transactions.SignedTransaction addSignature(net.corda.core.transactions.SignedTransaction, java.security.PublicKey)
|
||||||
@org.jetbrains.annotations.NotNull public net.corda.core.serialization.SerializeAsToken cordaService(Class)
|
@org.jetbrains.annotations.NotNull public net.corda.core.serialization.SerializeAsToken cordaService(Class)
|
||||||
@ -4044,6 +4106,7 @@ public class net.corda.testing.node.MockServices extends java.lang.Object implem
|
|||||||
@org.jetbrains.annotations.NotNull public net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.FilteredTransaction, java.security.PublicKey)
|
@org.jetbrains.annotations.NotNull public net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.FilteredTransaction, java.security.PublicKey)
|
||||||
@org.jetbrains.annotations.NotNull public net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.SignedTransaction)
|
@org.jetbrains.annotations.NotNull public net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.SignedTransaction)
|
||||||
@org.jetbrains.annotations.NotNull public net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.SignedTransaction, java.security.PublicKey)
|
@org.jetbrains.annotations.NotNull public net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.SignedTransaction, java.security.PublicKey)
|
||||||
|
@org.jetbrains.annotations.NotNull public net.corda.core.cordapp.CordappContext getAppContext()
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.services.MockAttachmentStorage getAttachments()
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.services.MockAttachmentStorage getAttachments()
|
||||||
@org.jetbrains.annotations.NotNull public java.time.Clock getClock()
|
@org.jetbrains.annotations.NotNull public java.time.Clock getClock()
|
||||||
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.ContractUpgradeService getContractUpgradeService()
|
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.ContractUpgradeService getContractUpgradeService()
|
||||||
@ -4052,9 +4115,9 @@ public class net.corda.testing.node.MockServices extends java.lang.Object implem
|
|||||||
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.IdentityService getIdentityService()
|
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.IdentityService getIdentityService()
|
||||||
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.KeyManagementService getKeyManagementService()
|
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.KeyManagementService getKeyManagementService()
|
||||||
@org.jetbrains.annotations.NotNull public static final net.corda.node.VersionInfo getMOCK_VERSION_INFO()
|
@org.jetbrains.annotations.NotNull public static final net.corda.node.VersionInfo getMOCK_VERSION_INFO()
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.services.MockCordappProvider getMockCordappProvider()
|
|
||||||
@org.jetbrains.annotations.NotNull public net.corda.core.node.NodeInfo getMyInfo()
|
@org.jetbrains.annotations.NotNull public net.corda.core.node.NodeInfo getMyInfo()
|
||||||
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.NetworkMapCache getNetworkMapCache()
|
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.NetworkMapCache getNetworkMapCache()
|
||||||
|
@org.jetbrains.annotations.NotNull public net.corda.core.node.NetworkParameters getNetworkParameters()
|
||||||
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.TransactionVerifierService getTransactionVerifierService()
|
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.TransactionVerifierService getTransactionVerifierService()
|
||||||
@org.jetbrains.annotations.NotNull public net.corda.node.services.api.WritableTransactionStorage getValidatedTransactions()
|
@org.jetbrains.annotations.NotNull public net.corda.node.services.api.WritableTransactionStorage getValidatedTransactions()
|
||||||
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.VaultService getVaultService()
|
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.VaultService getVaultService()
|
||||||
@ -4097,6 +4160,7 @@ public static final class net.corda.testing.node.MockServicesKt$createMockCordaS
|
|||||||
@org.jetbrains.annotations.NotNull public net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.FilteredTransaction, java.security.PublicKey)
|
@org.jetbrains.annotations.NotNull public net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.FilteredTransaction, java.security.PublicKey)
|
||||||
@org.jetbrains.annotations.NotNull public net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.SignedTransaction)
|
@org.jetbrains.annotations.NotNull public net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.SignedTransaction)
|
||||||
@org.jetbrains.annotations.NotNull public net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.SignedTransaction, java.security.PublicKey)
|
@org.jetbrains.annotations.NotNull public net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.SignedTransaction, java.security.PublicKey)
|
||||||
|
@org.jetbrains.annotations.NotNull public net.corda.core.cordapp.CordappContext getAppContext()
|
||||||
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.AttachmentStorage getAttachments()
|
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.AttachmentStorage getAttachments()
|
||||||
@org.jetbrains.annotations.NotNull public java.time.Clock getClock()
|
@org.jetbrains.annotations.NotNull public java.time.Clock getClock()
|
||||||
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.ContractUpgradeService getContractUpgradeService()
|
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.ContractUpgradeService getContractUpgradeService()
|
||||||
@ -4105,6 +4169,7 @@ public static final class net.corda.testing.node.MockServicesKt$createMockCordaS
|
|||||||
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.KeyManagementService getKeyManagementService()
|
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.KeyManagementService getKeyManagementService()
|
||||||
@org.jetbrains.annotations.NotNull public net.corda.core.node.NodeInfo getMyInfo()
|
@org.jetbrains.annotations.NotNull public net.corda.core.node.NodeInfo getMyInfo()
|
||||||
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.NetworkMapCache getNetworkMapCache()
|
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.NetworkMapCache getNetworkMapCache()
|
||||||
|
@org.jetbrains.annotations.NotNull public net.corda.core.node.NetworkParameters getNetworkParameters()
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockServices getServiceHub()
|
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockServices getServiceHub()
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializeAsToken getServiceInstance()
|
@org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializeAsToken getServiceInstance()
|
||||||
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.TransactionVerifierService getTransactionVerifierService()
|
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.TransactionVerifierService getTransactionVerifierService()
|
||||||
@ -4163,7 +4228,6 @@ public final class net.corda.testing.node.NotarySpec extends java.lang.Object
|
|||||||
##
|
##
|
||||||
public final class net.corda.testing.node.StartedMockNode extends java.lang.Object
|
public final class net.corda.testing.node.StartedMockNode extends java.lang.Object
|
||||||
@org.jetbrains.annotations.NotNull public final List findStateMachines(Class)
|
@org.jetbrains.annotations.NotNull public final List findStateMachines(Class)
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.nodeapi.internal.persistence.CordaPersistence getDatabase()
|
|
||||||
public final int getId()
|
public final int getId()
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.core.node.NodeInfo getInfo()
|
@org.jetbrains.annotations.NotNull public final net.corda.core.node.NodeInfo getInfo()
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.node.services.messaging.MessagingService getNetwork()
|
@org.jetbrains.annotations.NotNull public final net.corda.node.services.messaging.MessagingService getNetwork()
|
||||||
@ -4172,11 +4236,12 @@ public final class net.corda.testing.node.StartedMockNode extends java.lang.Obje
|
|||||||
@org.jetbrains.annotations.NotNull public final rx.Observable registerInitiatedFlow(Class)
|
@org.jetbrains.annotations.NotNull public final rx.Observable registerInitiatedFlow(Class)
|
||||||
public final void setMessagingServiceSpy(net.corda.testing.node.MessagingServiceSpy)
|
public final void setMessagingServiceSpy(net.corda.testing.node.MessagingServiceSpy)
|
||||||
public final void stop()
|
public final void stop()
|
||||||
|
public final Object transaction(kotlin.jvm.functions.Function0)
|
||||||
public static final net.corda.testing.node.StartedMockNode$Companion Companion
|
public static final net.corda.testing.node.StartedMockNode$Companion Companion
|
||||||
##
|
##
|
||||||
public static final class net.corda.testing.node.StartedMockNode$Companion extends java.lang.Object
|
public static final class net.corda.testing.node.StartedMockNode$Companion extends java.lang.Object
|
||||||
##
|
##
|
||||||
@javax.annotation.concurrent.ThreadSafe public final class net.corda.testing.node.TestClock extends net.corda.node.internal.MutableClock
|
@javax.annotation.concurrent.ThreadSafe public final class net.corda.testing.node.TestClock extends net.corda.node.MutableClock
|
||||||
public <init>(java.time.Clock)
|
public <init>(java.time.Clock)
|
||||||
public synchronized final void advanceBy(java.time.Duration)
|
public synchronized final void advanceBy(java.time.Duration)
|
||||||
public synchronized final void setTo(java.time.Instant)
|
public synchronized final void setTo(java.time.Instant)
|
||||||
@ -4204,10 +4269,12 @@ public final class net.corda.testing.node.User extends java.lang.Object
|
|||||||
public final class net.corda.client.rpc.CordaRPCClient extends java.lang.Object
|
public final class net.corda.client.rpc.CordaRPCClient extends java.lang.Object
|
||||||
public <init>(net.corda.core.utilities.NetworkHostAndPort)
|
public <init>(net.corda.core.utilities.NetworkHostAndPort)
|
||||||
public <init>(net.corda.core.utilities.NetworkHostAndPort, net.corda.client.rpc.CordaRPCClientConfiguration)
|
public <init>(net.corda.core.utilities.NetworkHostAndPort, net.corda.client.rpc.CordaRPCClientConfiguration)
|
||||||
public <init>(net.corda.core.utilities.NetworkHostAndPort, net.corda.client.rpc.CordaRPCClientConfiguration, net.corda.nodeapi.internal.config.SSLConfiguration)
|
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.client.rpc.CordaRPCConnection start(String, String)
|
@org.jetbrains.annotations.NotNull public final net.corda.client.rpc.CordaRPCConnection start(String, String)
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.client.rpc.CordaRPCConnection start(String, String, net.corda.core.context.Trace, net.corda.core.context.Actor)
|
@org.jetbrains.annotations.NotNull public final net.corda.client.rpc.CordaRPCConnection start(String, String, net.corda.core.context.Trace, net.corda.core.context.Actor)
|
||||||
public final Object use(String, String, kotlin.jvm.functions.Function1)
|
public final Object use(String, String, kotlin.jvm.functions.Function1)
|
||||||
|
public static final net.corda.client.rpc.CordaRPCClient$Companion Companion
|
||||||
|
##
|
||||||
|
public static final class net.corda.client.rpc.CordaRPCClient$Companion extends java.lang.Object
|
||||||
##
|
##
|
||||||
public final class net.corda.client.rpc.CordaRPCClientConfiguration extends java.lang.Object
|
public final class net.corda.client.rpc.CordaRPCClientConfiguration extends java.lang.Object
|
||||||
public <init>(java.time.Duration)
|
public <init>(java.time.Duration)
|
||||||
@ -4433,24 +4500,7 @@ public static final class net.corda.testing.core.SerializationEnvironmentRule$Co
|
|||||||
public static final class net.corda.testing.core.SerializationEnvironmentRule$apply$1 extends org.junit.runners.model.Statement
|
public static final class net.corda.testing.core.SerializationEnvironmentRule$apply$1 extends org.junit.runners.model.Statement
|
||||||
public void evaluate()
|
public void evaluate()
|
||||||
##
|
##
|
||||||
public final class net.corda.testing.core.SerializationTestHelpersKt extends java.lang.Object
|
|
||||||
@org.jetbrains.annotations.NotNull public static final net.corda.testing.core.GlobalSerializationEnvironment setGlobalSerialization(boolean)
|
|
||||||
##
|
|
||||||
public static final class net.corda.testing.core.SerializationTestHelpersKt$createTestSerializationEnv$1 extends net.corda.core.serialization.internal.SerializationEnvironmentImpl
|
|
||||||
@org.jetbrains.annotations.NotNull public String toString()
|
|
||||||
##
|
|
||||||
public static final class net.corda.testing.core.SerializationTestHelpersKt$setGlobalSerialization$1 extends java.lang.Object implements net.corda.testing.core.GlobalSerializationEnvironment, net.corda.core.serialization.internal.SerializationEnvironment
|
|
||||||
@org.jetbrains.annotations.NotNull public net.corda.core.serialization.SerializationContext getCheckpointContext()
|
|
||||||
@org.jetbrains.annotations.NotNull public net.corda.core.serialization.SerializationContext getP2pContext()
|
|
||||||
@org.jetbrains.annotations.NotNull public net.corda.core.serialization.SerializationContext getRpcClientContext()
|
|
||||||
@org.jetbrains.annotations.NotNull public net.corda.core.serialization.SerializationContext getRpcServerContext()
|
|
||||||
@org.jetbrains.annotations.NotNull public net.corda.core.serialization.SerializationFactory getSerializationFactory()
|
|
||||||
@org.jetbrains.annotations.NotNull public net.corda.core.serialization.SerializationContext getStorageContext()
|
|
||||||
public void unset()
|
|
||||||
##
|
|
||||||
public final class net.corda.testing.core.TestConstants extends java.lang.Object
|
public final class net.corda.testing.core.TestConstants extends java.lang.Object
|
||||||
@org.jetbrains.annotations.NotNull public static final net.corda.nodeapi.internal.crypto.CertificateAndKeyPair getDEV_INTERMEDIATE_CA()
|
|
||||||
@org.jetbrains.annotations.NotNull public static final net.corda.nodeapi.internal.crypto.CertificateAndKeyPair getDEV_ROOT_CA()
|
|
||||||
@kotlin.jvm.JvmField @org.jetbrains.annotations.NotNull public static final net.corda.core.identity.CordaX500Name ALICE_NAME
|
@kotlin.jvm.JvmField @org.jetbrains.annotations.NotNull public static final net.corda.core.identity.CordaX500Name ALICE_NAME
|
||||||
@kotlin.jvm.JvmField @org.jetbrains.annotations.NotNull public static final net.corda.core.identity.CordaX500Name BOB_NAME
|
@kotlin.jvm.JvmField @org.jetbrains.annotations.NotNull public static final net.corda.core.identity.CordaX500Name BOB_NAME
|
||||||
@kotlin.jvm.JvmField @org.jetbrains.annotations.NotNull public static final net.corda.core.identity.CordaX500Name BOC_NAME
|
@kotlin.jvm.JvmField @org.jetbrains.annotations.NotNull public static final net.corda.core.identity.CordaX500Name BOC_NAME
|
||||||
@ -4624,6 +4674,7 @@ public static final class net.corda.testing.dsl.TestTransactionDSLInterpreter$se
|
|||||||
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.AttachmentStorage getAttachments()
|
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.AttachmentStorage getAttachments()
|
||||||
@org.jetbrains.annotations.NotNull public net.corda.core.cordapp.CordappProvider getCordappProvider()
|
@org.jetbrains.annotations.NotNull public net.corda.core.cordapp.CordappProvider getCordappProvider()
|
||||||
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.IdentityService getIdentityService()
|
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.IdentityService getIdentityService()
|
||||||
|
@org.jetbrains.annotations.NotNull public net.corda.core.node.NetworkParameters getNetworkParameters()
|
||||||
@org.jetbrains.annotations.NotNull public net.corda.core.contracts.TransactionState loadState(net.corda.core.contracts.StateRef)
|
@org.jetbrains.annotations.NotNull public net.corda.core.contracts.TransactionState loadState(net.corda.core.contracts.StateRef)
|
||||||
@org.jetbrains.annotations.NotNull public Set loadStates(Set)
|
@org.jetbrains.annotations.NotNull public Set loadStates(Set)
|
||||||
##
|
##
|
||||||
@ -4720,12 +4771,3 @@ public final class net.corda.testing.services.MockAttachmentStorage extends net.
|
|||||||
public static final class net.corda.testing.services.MockAttachmentStorage$Companion extends java.lang.Object
|
public static final class net.corda.testing.services.MockAttachmentStorage$Companion extends java.lang.Object
|
||||||
public final byte[] getBytes(java.io.InputStream)
|
public final byte[] getBytes(java.io.InputStream)
|
||||||
##
|
##
|
||||||
public static final class net.corda.testing.services.MockAttachmentStorage$openAttachment$1 extends net.corda.core.internal.AbstractAttachment
|
|
||||||
@org.jetbrains.annotations.NotNull public net.corda.core.crypto.SecureHash getId()
|
|
||||||
##
|
|
||||||
public final class net.corda.testing.services.MockCordappProvider extends net.corda.node.internal.cordapp.CordappProviderImpl
|
|
||||||
public <init>(net.corda.node.internal.cordapp.CordappLoader, net.corda.core.node.services.AttachmentStorage)
|
|
||||||
public final void addMockCordapp(String, net.corda.testing.services.MockAttachmentStorage)
|
|
||||||
@org.jetbrains.annotations.Nullable public net.corda.core.crypto.SecureHash getContractAttachmentID(String)
|
|
||||||
@org.jetbrains.annotations.NotNull public final List getCordappRegistry()
|
|
||||||
##
|
|
||||||
|
@ -31,8 +31,7 @@ if [ $removalCount -gt 0 ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Adding new abstract methods could also break the API.
|
# Adding new abstract methods could also break the API.
|
||||||
# However, first exclude anything with the @DoNotImplement annotation.
|
# However, first exclude classes marked with the @DoNotImplement annotation
|
||||||
|
|
||||||
function forUserImpl() {
|
function forUserImpl() {
|
||||||
awk '/DoNotImplement/,/^##/{ next }{ print }' $1
|
awk '/DoNotImplement/,/^##/{ next }{ print }' $1
|
||||||
}
|
}
|
||||||
@ -45,13 +44,28 @@ $newAbstracts
|
|||||||
EOF
|
EOF
|
||||||
`
|
`
|
||||||
|
|
||||||
|
#Get a list of any methods that expose classes in .internal. namespaces, and any classes which extend/implement
|
||||||
|
#an internal class
|
||||||
|
newInternalExposures=$(echo "$userDiffContents" | grep "^+" | grep "\.internal\." )
|
||||||
|
|
||||||
|
internalCount=`grep -v "^$" <<EOF | wc -l
|
||||||
|
$newInternalExposures
|
||||||
|
EOF
|
||||||
|
`
|
||||||
|
|
||||||
|
echo "Number of new internal class exposures: "$internalCount
|
||||||
|
if [ $internalCount -gt 0 ]; then
|
||||||
|
echo "$newInternalExposures"
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
|
||||||
echo "Number of new abstract APIs: "$abstractCount
|
echo "Number of new abstract APIs: "$abstractCount
|
||||||
if [ $abstractCount -gt 0 ]; then
|
if [ $abstractCount -gt 0 ]; then
|
||||||
echo "$newAbstracts"
|
echo "$newAbstracts"
|
||||||
echo
|
echo
|
||||||
fi
|
fi
|
||||||
|
|
||||||
badChanges=$(($removalCount + $abstractCount))
|
badChanges=$(($removalCount + $abstractCount + $internalCount))
|
||||||
if [ $badChanges -gt 255 ]; then
|
if [ $badChanges -gt 255 ]; then
|
||||||
echo "OVERFLOW! Number of bad API changes: $badChanges"
|
echo "OVERFLOW! Number of bad API changes: $badChanges"
|
||||||
badChanges=255
|
badChanges=255
|
||||||
|
56
.idea/compiler.xml
generated
56
.idea/compiler.xml
generated
@ -1,15 +1,16 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="CompilerConfiguration">
|
<component name="CompilerConfiguration">
|
||||||
<bytecodeTargetLevel target="1.8">
|
<bytecodeTargetLevel>
|
||||||
<module name="api-scanner_main" target="1.8" />
|
|
||||||
<module name="api-scanner_test" target="1.8" />
|
|
||||||
<module name="attachment-demo_integrationTest" target="1.8" />
|
<module name="attachment-demo_integrationTest" target="1.8" />
|
||||||
<module name="attachment-demo_main" target="1.8" />
|
<module name="attachment-demo_main" target="1.8" />
|
||||||
<module name="attachment-demo_test" target="1.8" />
|
<module name="attachment-demo_test" target="1.8" />
|
||||||
<module name="bank-of-corda-demo_integrationTest" target="1.8" />
|
<module name="bank-of-corda-demo_integrationTest" target="1.8" />
|
||||||
<module name="bank-of-corda-demo_main" target="1.8" />
|
<module name="bank-of-corda-demo_main" target="1.8" />
|
||||||
<module name="bank-of-corda-demo_test" target="1.8" />
|
<module name="bank-of-corda-demo_test" target="1.8" />
|
||||||
|
<module name="behave_main" target="1.8" />
|
||||||
|
<module name="behave_scenario" target="1.8" />
|
||||||
|
<module name="behave_test" target="1.8" />
|
||||||
<module name="bootstrapper_main" target="1.8" />
|
<module name="bootstrapper_main" target="1.8" />
|
||||||
<module name="bootstrapper_test" target="1.8" />
|
<module name="bootstrapper_test" target="1.8" />
|
||||||
<module name="buildSrc_main" target="1.8" />
|
<module name="buildSrc_main" target="1.8" />
|
||||||
@ -25,40 +26,28 @@
|
|||||||
<module name="client_test" target="1.8" />
|
<module name="client_test" target="1.8" />
|
||||||
<module name="confidential-identities_main" target="1.8" />
|
<module name="confidential-identities_main" target="1.8" />
|
||||||
<module name="confidential-identities_test" target="1.8" />
|
<module name="confidential-identities_test" target="1.8" />
|
||||||
<module name="corda-cordform-common_main" target="1.8" />
|
|
||||||
<module name="corda-cordform-common_test" target="1.8" />
|
|
||||||
<module name="corda-core_integrationTest" target="1.8" />
|
|
||||||
<module name="corda-core_smokeTest" target="1.8" />
|
|
||||||
<module name="corda-finance_integrationTest" target="1.8" />
|
|
||||||
<module name="corda-project_main" target="1.8" />
|
<module name="corda-project_main" target="1.8" />
|
||||||
<module name="corda-project_test" target="1.8" />
|
<module name="corda-project_test" target="1.8" />
|
||||||
|
<module name="cordapp-configuration_main" target="1.8" />
|
||||||
|
<module name="cordapp-configuration_test" target="1.8" />
|
||||||
<module name="cordapp_integrationTest" target="1.8" />
|
<module name="cordapp_integrationTest" target="1.8" />
|
||||||
<module name="cordapp_main" target="1.8" />
|
<module name="cordapp_main" target="1.8" />
|
||||||
<module name="cordapp_test" target="1.8" />
|
<module name="cordapp_test" target="1.8" />
|
||||||
<module name="cordform-common_main" target="1.8" />
|
<module name="cordform-common_main" target="1.8" />
|
||||||
<module name="cordform-common_test" target="1.8" />
|
<module name="cordform-common_test" target="1.8" />
|
||||||
<module name="cordformation_main" target="1.8" />
|
|
||||||
<module name="cordformation_runnodes" target="1.8" />
|
|
||||||
<module name="cordformation_test" target="1.8" />
|
|
||||||
<module name="core_integrationTest" target="1.8" />
|
<module name="core_integrationTest" target="1.8" />
|
||||||
<module name="core_main" target="1.8" />
|
<module name="core_main" target="1.8" />
|
||||||
<module name="core_smokeTest" target="1.8" />
|
<module name="core_smokeTest" target="1.8" />
|
||||||
<module name="core_smokeTestPlugins" target="1.8" />
|
|
||||||
<module name="core_test" target="1.8" />
|
<module name="core_test" target="1.8" />
|
||||||
<module name="dbmigration_main" target="1.8" />
|
<module name="dbmigration_main" target="1.8" />
|
||||||
<module name="dbmigration_test" target="1.8" />
|
<module name="dbmigration_test" target="1.8" />
|
||||||
<module name="demobench_main" target="1.8" />
|
<module name="demobench_main" target="1.8" />
|
||||||
<module name="demobench_test" target="1.8" />
|
<module name="demobench_test" target="1.8" />
|
||||||
<module name="docs_main" target="1.8" />
|
<module name="docs_main" target="1.8" />
|
||||||
<module name="docs_source_example-code_integrationTest" target="1.8" />
|
|
||||||
<module name="docs_source_example-code_main" target="1.8" />
|
|
||||||
<module name="docs_source_example-code_test" target="1.8" />
|
|
||||||
<module name="docs_test" target="1.8" />
|
<module name="docs_test" target="1.8" />
|
||||||
<module name="example-code_integrationTest" target="1.8" />
|
<module name="example-code_integrationTest" target="1.8" />
|
||||||
<module name="example-code_main" target="1.8" />
|
<module name="example-code_main" target="1.8" />
|
||||||
<module name="example-code_test" target="1.8" />
|
<module name="example-code_test" target="1.8" />
|
||||||
<module name="experimental-kryo-hook_main" target="1.8" />
|
|
||||||
<module name="experimental-kryo-hook_test" target="1.8" />
|
|
||||||
<module name="experimental_main" target="1.8" />
|
<module name="experimental_main" target="1.8" />
|
||||||
<module name="experimental_test" target="1.8" />
|
<module name="experimental_test" target="1.8" />
|
||||||
<module name="explorer-capsule_main" target="1.6" />
|
<module name="explorer-capsule_main" target="1.6" />
|
||||||
@ -70,21 +59,10 @@
|
|||||||
<module name="finance_test" target="1.8" />
|
<module name="finance_test" target="1.8" />
|
||||||
<module name="flow-hook_main" target="1.8" />
|
<module name="flow-hook_main" target="1.8" />
|
||||||
<module name="flow-hook_test" target="1.8" />
|
<module name="flow-hook_test" target="1.8" />
|
||||||
<module name="gradle-plugins-cordapp_main" target="1.8" />
|
|
||||||
<module name="gradle-plugins-cordapp_test" target="1.8" />
|
|
||||||
<module name="gradle-plugins-cordform-common_main" target="1.8" />
|
|
||||||
<module name="gradle-plugins-cordform-common_test" target="1.8" />
|
|
||||||
<module name="graphs_main" target="1.8" />
|
<module name="graphs_main" target="1.8" />
|
||||||
<module name="graphs_test" target="1.8" />
|
<module name="graphs_test" target="1.8" />
|
||||||
<module name="intellij-plugin_main" target="1.8" />
|
<module name="intellij-plugin_main" target="1.8" />
|
||||||
<module name="intellij-plugin_test" target="1.8" />
|
<module name="intellij-plugin_test" target="1.8" />
|
||||||
<module name="irs-demo-cordapp_integrationTest" target="1.8" />
|
|
||||||
<module name="irs-demo-cordapp_main" target="1.8" />
|
|
||||||
<module name="irs-demo-cordapp_main~1" target="1.8" />
|
|
||||||
<module name="irs-demo-cordapp_test" target="1.8" />
|
|
||||||
<module name="irs-demo-cordapp_test~1" target="1.8" />
|
|
||||||
<module name="irs-demo-web_main" target="1.8" />
|
|
||||||
<module name="irs-demo-web_test" target="1.8" />
|
|
||||||
<module name="irs-demo_integrationTest" target="1.8" />
|
<module name="irs-demo_integrationTest" target="1.8" />
|
||||||
<module name="irs-demo_main" target="1.8" />
|
<module name="irs-demo_main" target="1.8" />
|
||||||
<module name="irs-demo_systemTest" target="1.8" />
|
<module name="irs-demo_systemTest" target="1.8" />
|
||||||
@ -120,7 +98,6 @@
|
|||||||
<module name="node-driver_test" target="1.8" />
|
<module name="node-driver_test" target="1.8" />
|
||||||
<module name="node_integrationTest" target="1.8" />
|
<module name="node_integrationTest" target="1.8" />
|
||||||
<module name="node_main" target="1.8" />
|
<module name="node_main" target="1.8" />
|
||||||
<module name="node_smokeTest" target="1.8" />
|
|
||||||
<module name="node_test" target="1.8" />
|
<module name="node_test" target="1.8" />
|
||||||
<module name="notary-demo_main" target="1.8" />
|
<module name="notary-demo_main" target="1.8" />
|
||||||
<module name="notary-demo_test" target="1.8" />
|
<module name="notary-demo_test" target="1.8" />
|
||||||
@ -129,39 +106,21 @@
|
|||||||
<module name="perftestcordapp_integrationTest" target="1.8" />
|
<module name="perftestcordapp_integrationTest" target="1.8" />
|
||||||
<module name="perftestcordapp_main" target="1.8" />
|
<module name="perftestcordapp_main" target="1.8" />
|
||||||
<module name="perftestcordapp_test" target="1.8" />
|
<module name="perftestcordapp_test" target="1.8" />
|
||||||
<module name="publish-utils_main" target="1.8" />
|
|
||||||
<module name="publish-utils_test" target="1.8" />
|
|
||||||
<module name="quasar-hook_main" target="1.8" />
|
<module name="quasar-hook_main" target="1.8" />
|
||||||
<module name="quasar-hook_test" target="1.8" />
|
<module name="quasar-hook_test" target="1.8" />
|
||||||
<module name="quasar-utils_main" target="1.8" />
|
|
||||||
<module name="quasar-utils_test" target="1.8" />
|
|
||||||
<module name="rpc_integrationTest" target="1.8" />
|
<module name="rpc_integrationTest" target="1.8" />
|
||||||
<module name="rpc_main" target="1.8" />
|
<module name="rpc_main" target="1.8" />
|
||||||
<module name="rpc_smokeTest" target="1.8" />
|
<module name="rpc_smokeTest" target="1.8" />
|
||||||
<module name="rpc_test" target="1.8" />
|
<module name="rpc_test" target="1.8" />
|
||||||
<module name="samples-business-network-demo_main" target="1.8" />
|
|
||||||
<module name="samples-business-network-demo_test" target="1.8" />
|
|
||||||
<module name="samples_main" target="1.8" />
|
<module name="samples_main" target="1.8" />
|
||||||
<module name="samples_test" target="1.8" />
|
<module name="samples_test" target="1.8" />
|
||||||
<module name="sandbox_main" target="1.8" />
|
<module name="sandbox_main" target="1.8" />
|
||||||
<module name="sandbox_test" target="1.8" />
|
<module name="sandbox_test" target="1.8" />
|
||||||
<module name="sgx-hsm-tool_main" target="1.8" />
|
<module name="sgx-hsm-tool_main" target="1.8" />
|
||||||
<module name="sgx-hsm-tool_test" target="1.8" />
|
<module name="sgx-hsm-tool_test" target="1.8" />
|
||||||
<module name="sgx-jvm_hsm-tool_main" target="1.8" />
|
|
||||||
<module name="sgx-jvm_hsm-tool_test" target="1.8" />
|
|
||||||
<module name="simm-valuation-demo_integrationTest" target="1.8" />
|
<module name="simm-valuation-demo_integrationTest" target="1.8" />
|
||||||
<module name="simm-valuation-demo_main" target="1.8" />
|
<module name="simm-valuation-demo_main" target="1.8" />
|
||||||
<module name="simm-valuation-demo_test" target="1.8" />
|
<module name="simm-valuation-demo_test" target="1.8" />
|
||||||
<module name="smoke-test-utils_main" target="1.8" />
|
|
||||||
<module name="smoke-test-utils_test" target="1.8" />
|
|
||||||
<module name="smoke-test-utils_testDriver" target="1.8" />
|
|
||||||
<module name="source-example-code_integrationTest" target="1.8" />
|
|
||||||
<module name="source-example-code_main" target="1.8" />
|
|
||||||
<module name="source-example-code_test" target="1.8" />
|
|
||||||
<module name="test-common_main" target="1.8" />
|
|
||||||
<module name="test-common_test" target="1.8" />
|
|
||||||
<module name="test-utils_main" target="1.8" />
|
|
||||||
<module name="test-utils_test" target="1.8" />
|
|
||||||
<module name="testing-smoke-test-utils_main" target="1.8" />
|
<module name="testing-smoke-test-utils_main" target="1.8" />
|
||||||
<module name="testing-smoke-test-utils_test" target="1.8" />
|
<module name="testing-smoke-test-utils_test" target="1.8" />
|
||||||
<module name="testing-test-common_main" target="1.8" />
|
<module name="testing-test-common_main" target="1.8" />
|
||||||
@ -188,7 +147,4 @@
|
|||||||
<module name="webserver_test" target="1.8" />
|
<module name="webserver_test" target="1.8" />
|
||||||
</bytecodeTargetLevel>
|
</bytecodeTargetLevel>
|
||||||
</component>
|
</component>
|
||||||
<component name="JavacSettings">
|
|
||||||
<option name="ADDITIONAL_OPTIONS_STRING" value="-parameters" />
|
|
||||||
</component>
|
|
||||||
</project>
|
</project>
|
@ -15,7 +15,9 @@ buildscript {
|
|||||||
//
|
//
|
||||||
// TODO: Sort this alphabetically.
|
// TODO: Sort this alphabetically.
|
||||||
ext.kotlin_version = constants.getProperty("kotlinVersion")
|
ext.kotlin_version = constants.getProperty("kotlinVersion")
|
||||||
ext.quasar_version = '0.7.9'
|
// use our fork of quasar
|
||||||
|
ext.quasar_group = 'com.github.corda.quasar'
|
||||||
|
ext.quasar_version = '7629695563deae6cc95adcfbebcbc8322fd0241a'
|
||||||
|
|
||||||
// gradle-capsule-plugin:1.0.2 contains capsule:1.0.1
|
// gradle-capsule-plugin:1.0.2 contains capsule:1.0.1
|
||||||
// TODO: Upgrade gradle-capsule-plugin to a version with capsule:1.0.3
|
// TODO: Upgrade gradle-capsule-plugin to a version with capsule:1.0.3
|
||||||
@ -40,7 +42,6 @@ buildscript {
|
|||||||
ext.jackson_version = '2.9.3'
|
ext.jackson_version = '2.9.3'
|
||||||
ext.jetty_version = '9.4.7.v20170914'
|
ext.jetty_version = '9.4.7.v20170914'
|
||||||
ext.jersey_version = '2.25'
|
ext.jersey_version = '2.25'
|
||||||
ext.jolokia_version = '1.3.7'
|
|
||||||
ext.assertj_version = '3.8.0'
|
ext.assertj_version = '3.8.0'
|
||||||
ext.slf4j_version = '1.7.25'
|
ext.slf4j_version = '1.7.25'
|
||||||
ext.log4j_version = '2.9.1'
|
ext.log4j_version = '2.9.1'
|
||||||
@ -75,6 +76,7 @@ buildscript {
|
|||||||
ext.docker_compose_rule_version = '0.33.0'
|
ext.docker_compose_rule_version = '0.33.0'
|
||||||
ext.selenium_version = '3.8.1'
|
ext.selenium_version = '3.8.1'
|
||||||
ext.ghostdriver_version = '2.1.0'
|
ext.ghostdriver_version = '2.1.0'
|
||||||
|
ext.eaagentloader_version = '1.0.3'
|
||||||
|
|
||||||
// Update 121 is required for ObjectInputFilter and at time of writing 131 was latest:
|
// Update 121 is required for ObjectInputFilter and at time of writing 131 was latest:
|
||||||
ext.java8_minUpdateVersion = '131'
|
ext.java8_minUpdateVersion = '131'
|
||||||
|
@ -30,6 +30,7 @@ import java.util.concurrent.Executors
|
|||||||
import java.util.concurrent.ScheduledExecutorService
|
import java.util.concurrent.ScheduledExecutorService
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
import java.util.concurrent.atomic.AtomicLong
|
||||||
|
|
||||||
class RPCStabilityTests {
|
class RPCStabilityTests {
|
||||||
@Rule
|
@Rule
|
||||||
@ -127,7 +128,7 @@ class RPCStabilityTests {
|
|||||||
rpcDriver {
|
rpcDriver {
|
||||||
fun startAndCloseServer(broker: RpcBrokerHandle) {
|
fun startAndCloseServer(broker: RpcBrokerHandle) {
|
||||||
startRpcServerWithBrokerRunning(
|
startRpcServerWithBrokerRunning(
|
||||||
configuration = RPCServerConfiguration.default.copy(consumerPoolSize = 1, producerPoolBound = 1),
|
configuration = RPCServerConfiguration.default,
|
||||||
ops = DummyOps,
|
ops = DummyOps,
|
||||||
brokerHandle = broker
|
brokerHandle = broker
|
||||||
).rpcServer.close()
|
).rpcServer.close()
|
||||||
@ -148,7 +149,7 @@ class RPCStabilityTests {
|
|||||||
@Test
|
@Test
|
||||||
fun `rpc client close doesnt leak broker resources`() {
|
fun `rpc client close doesnt leak broker resources`() {
|
||||||
rpcDriver {
|
rpcDriver {
|
||||||
val server = startRpcServer(configuration = RPCServerConfiguration.default.copy(consumerPoolSize = 1, producerPoolBound = 1), ops = DummyOps).get()
|
val server = startRpcServer(configuration = RPCServerConfiguration.default, ops = DummyOps).get()
|
||||||
RPCClient<RPCOps>(server.broker.hostAndPort!!).start(RPCOps::class.java, rpcTestUser.username, rpcTestUser.password).close()
|
RPCClient<RPCOps>(server.broker.hostAndPort!!).start(RPCOps::class.java, rpcTestUser.username, rpcTestUser.password).close()
|
||||||
val initial = server.broker.getStats()
|
val initial = server.broker.getStats()
|
||||||
repeat(100) {
|
repeat(100) {
|
||||||
@ -337,11 +338,12 @@ class RPCStabilityTests {
|
|||||||
val request = RPCApi.ClientToServer.RpcRequest(
|
val request = RPCApi.ClientToServer.RpcRequest(
|
||||||
clientAddress = SimpleString(myQueue),
|
clientAddress = SimpleString(myQueue),
|
||||||
methodName = SlowConsumerRPCOps::streamAtInterval.name,
|
methodName = SlowConsumerRPCOps::streamAtInterval.name,
|
||||||
serialisedArguments = listOf(10.millis, 123456).serialize(context = SerializationDefaults.RPC_SERVER_CONTEXT).bytes,
|
serialisedArguments = listOf(10.millis, 123456).serialize(context = SerializationDefaults.RPC_SERVER_CONTEXT),
|
||||||
replyId = Trace.InvocationId.newInstance(),
|
replyId = Trace.InvocationId.newInstance(),
|
||||||
sessionId = Trace.SessionId.newInstance()
|
sessionId = Trace.SessionId.newInstance()
|
||||||
)
|
)
|
||||||
request.writeToClientMessage(message)
|
request.writeToClientMessage(message)
|
||||||
|
message.putLongProperty(RPCApi.DEDUPLICATION_SEQUENCE_NUMBER_FIELD_NAME, 0)
|
||||||
producer.send(message)
|
producer.send(message)
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
@ -350,6 +352,79 @@ class RPCStabilityTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `deduplication in the server`() {
|
||||||
|
rpcDriver {
|
||||||
|
val server = startRpcServer(ops = SlowConsumerRPCOpsImpl()).getOrThrow()
|
||||||
|
|
||||||
|
// Construct an RPC client session manually
|
||||||
|
val myQueue = "${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.test.${random63BitValue()}"
|
||||||
|
val session = startArtemisSession(server.broker.hostAndPort!!)
|
||||||
|
session.createTemporaryQueue(myQueue, myQueue)
|
||||||
|
val consumer = session.createConsumer(myQueue, null, -1, -1, false)
|
||||||
|
val replies = ArrayList<Any>()
|
||||||
|
consumer.setMessageHandler {
|
||||||
|
replies.add(it)
|
||||||
|
it.acknowledge()
|
||||||
|
}
|
||||||
|
|
||||||
|
val producer = session.createProducer(RPCApi.RPC_SERVER_QUEUE_NAME)
|
||||||
|
session.start()
|
||||||
|
|
||||||
|
pollUntilClientNumber(server, 1)
|
||||||
|
|
||||||
|
val message = session.createMessage(false)
|
||||||
|
val request = RPCApi.ClientToServer.RpcRequest(
|
||||||
|
clientAddress = SimpleString(myQueue),
|
||||||
|
methodName = DummyOps::protocolVersion.name,
|
||||||
|
serialisedArguments = emptyList<Any>().serialize(context = SerializationDefaults.RPC_SERVER_CONTEXT),
|
||||||
|
replyId = Trace.InvocationId.newInstance(),
|
||||||
|
sessionId = Trace.SessionId.newInstance()
|
||||||
|
)
|
||||||
|
request.writeToClientMessage(message)
|
||||||
|
message.putLongProperty(RPCApi.DEDUPLICATION_SEQUENCE_NUMBER_FIELD_NAME, 0)
|
||||||
|
producer.send(message)
|
||||||
|
// duplicate the message
|
||||||
|
producer.send(message)
|
||||||
|
|
||||||
|
pollUntilTrue("Number of replies is 1") {
|
||||||
|
replies.size == 1
|
||||||
|
}.getOrThrow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `deduplication in the client`() {
|
||||||
|
rpcDriver {
|
||||||
|
val broker = startRpcBroker().getOrThrow()
|
||||||
|
|
||||||
|
// Construct an RPC server session manually
|
||||||
|
val session = startArtemisSession(broker.hostAndPort!!)
|
||||||
|
val consumer = session.createConsumer(RPCApi.RPC_SERVER_QUEUE_NAME)
|
||||||
|
val producer = session.createProducer()
|
||||||
|
val dedupeId = AtomicLong(0)
|
||||||
|
consumer.setMessageHandler {
|
||||||
|
it.acknowledge()
|
||||||
|
val request = RPCApi.ClientToServer.fromClientMessage(it)
|
||||||
|
when (request) {
|
||||||
|
is RPCApi.ClientToServer.RpcRequest -> {
|
||||||
|
val reply = RPCApi.ServerToClient.RpcReply(request.replyId, Try.Success(0), "server")
|
||||||
|
val message = session.createMessage(false)
|
||||||
|
reply.writeToClientMessage(SerializationDefaults.RPC_SERVER_CONTEXT, message)
|
||||||
|
message.putLongProperty(RPCApi.DEDUPLICATION_SEQUENCE_NUMBER_FIELD_NAME, dedupeId.getAndIncrement())
|
||||||
|
producer.send(request.clientAddress, message)
|
||||||
|
// duplicate the reply
|
||||||
|
producer.send(request.clientAddress, message)
|
||||||
|
}
|
||||||
|
is RPCApi.ClientToServer.ObservablesClosed -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
session.start()
|
||||||
|
|
||||||
|
startRpcClient<RPCOps>(broker.hostAndPort!!).getOrThrow()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun RPCDriverDSL.pollUntilClientNumber(server: RpcServerHandle, expected: Int) {
|
fun RPCDriverDSL.pollUntilClientNumber(server: RpcServerHandle, expected: Int) {
|
||||||
|
@ -70,11 +70,24 @@ data class CordaRPCClientConfiguration(val connectionMaxRetryInterval: Duration)
|
|||||||
* @param configuration An optional configuration used to tweak client behaviour.
|
* @param configuration An optional configuration used to tweak client behaviour.
|
||||||
* @param sslConfiguration An optional [SSLConfiguration] used to enable secure communication with the server.
|
* @param sslConfiguration An optional [SSLConfiguration] used to enable secure communication with the server.
|
||||||
*/
|
*/
|
||||||
class CordaRPCClient @JvmOverloads constructor(
|
class CordaRPCClient private constructor(
|
||||||
hostAndPort: NetworkHostAndPort,
|
hostAndPort: NetworkHostAndPort,
|
||||||
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT,
|
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT,
|
||||||
sslConfiguration: SSLConfiguration? = null
|
sslConfiguration: SSLConfiguration? = null
|
||||||
) {
|
) {
|
||||||
|
@JvmOverloads
|
||||||
|
constructor(hostAndPort: NetworkHostAndPort, configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT) : this(hostAndPort, configuration, null)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
internal fun createWithSsl(
|
||||||
|
hostAndPort: NetworkHostAndPort,
|
||||||
|
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT,
|
||||||
|
sslConfiguration: SSLConfiguration? = null
|
||||||
|
): CordaRPCClient {
|
||||||
|
return CordaRPCClient(hostAndPort, configuration, sslConfiguration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
try {
|
try {
|
||||||
effectiveSerializationEnv
|
effectiveSerializationEnv
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
package net.corda.client.rpc.internal
|
||||||
|
|
||||||
|
import net.corda.client.rpc.CordaRPCClient
|
||||||
|
import net.corda.client.rpc.CordaRPCClientConfiguration
|
||||||
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
|
import net.corda.nodeapi.internal.config.SSLConfiguration
|
||||||
|
|
||||||
|
/** Utility which exposes the internal Corda RPC constructor to other internal Corda components */
|
||||||
|
fun createCordaRPCClientWithSsl(
|
||||||
|
hostAndPort: NetworkHostAndPort,
|
||||||
|
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT,
|
||||||
|
sslConfiguration: SSLConfiguration? = null
|
||||||
|
) = CordaRPCClient.createWithSsl(hostAndPort, configuration, sslConfiguration)
|
@ -1,5 +1,7 @@
|
|||||||
package net.corda.client.rpc.internal
|
package net.corda.client.rpc.internal
|
||||||
|
|
||||||
|
import net.corda.client.rpc.CordaRPCClient
|
||||||
|
import net.corda.client.rpc.CordaRPCClientConfiguration
|
||||||
import net.corda.client.rpc.RPCConnection
|
import net.corda.client.rpc.RPCConnection
|
||||||
import net.corda.client.rpc.RPCException
|
import net.corda.client.rpc.RPCException
|
||||||
import net.corda.core.context.Actor
|
import net.corda.core.context.Actor
|
||||||
@ -42,8 +44,6 @@ data class RPCClientConfiguration(
|
|||||||
val reapInterval: Duration,
|
val reapInterval: Duration,
|
||||||
/** The number of threads to use for observations (for executing [Observable.onNext]) */
|
/** The number of threads to use for observations (for executing [Observable.onNext]) */
|
||||||
val observationExecutorPoolSize: Int,
|
val observationExecutorPoolSize: Int,
|
||||||
/** The maximum number of producers to create to handle outgoing messages */
|
|
||||||
val producerPoolBound: Int,
|
|
||||||
/**
|
/**
|
||||||
* Determines the concurrency level of the Observable Cache. This is exposed because it implicitly determines
|
* Determines the concurrency level of the Observable Cache. This is exposed because it implicitly determines
|
||||||
* the limit on the number of leaked observables reaped because of garbage collection per reaping.
|
* the limit on the number of leaked observables reaped because of garbage collection per reaping.
|
||||||
@ -56,9 +56,12 @@ data class RPCClientConfiguration(
|
|||||||
val connectionRetryIntervalMultiplier: Double,
|
val connectionRetryIntervalMultiplier: Double,
|
||||||
/** Maximum retry interval */
|
/** Maximum retry interval */
|
||||||
val connectionMaxRetryInterval: Duration,
|
val connectionMaxRetryInterval: Duration,
|
||||||
|
/** Maximum reconnect attempts on failover */
|
||||||
val maxReconnectAttempts: Int,
|
val maxReconnectAttempts: Int,
|
||||||
/** Maximum file size */
|
/** Maximum file size */
|
||||||
val maxFileSize: Int
|
val maxFileSize: Int,
|
||||||
|
/** The cache expiry of a deduplication watermark per client. */
|
||||||
|
val deduplicationCacheExpiry: Duration
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
val unlimitedReconnectAttempts = -1
|
val unlimitedReconnectAttempts = -1
|
||||||
@ -68,14 +71,14 @@ data class RPCClientConfiguration(
|
|||||||
trackRpcCallSites = false,
|
trackRpcCallSites = false,
|
||||||
reapInterval = 1.seconds,
|
reapInterval = 1.seconds,
|
||||||
observationExecutorPoolSize = 4,
|
observationExecutorPoolSize = 4,
|
||||||
producerPoolBound = 1,
|
|
||||||
cacheConcurrencyLevel = 8,
|
cacheConcurrencyLevel = 8,
|
||||||
connectionRetryInterval = 5.seconds,
|
connectionRetryInterval = 5.seconds,
|
||||||
connectionRetryIntervalMultiplier = 1.5,
|
connectionRetryIntervalMultiplier = 1.5,
|
||||||
connectionMaxRetryInterval = 3.minutes,
|
connectionMaxRetryInterval = 3.minutes,
|
||||||
maxReconnectAttempts = unlimitedReconnectAttempts,
|
maxReconnectAttempts = unlimitedReconnectAttempts,
|
||||||
/** 10 MiB maximum allowed file size for attachments, including message headers. TODO: acquire this value from Network Map when supported. */
|
/** 10 MiB maximum allowed file size for attachments, including message headers. TODO: acquire this value from Network Map when supported. */
|
||||||
maxFileSize = 10485760
|
maxFileSize = 10485760,
|
||||||
|
deduplicationCacheExpiry = 1.days
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,6 @@ import net.corda.client.rpc.RPCSinceVersion
|
|||||||
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
|
||||||
import net.corda.core.internal.LazyPool
|
|
||||||
import net.corda.core.internal.LazyStickyPool
|
import net.corda.core.internal.LazyStickyPool
|
||||||
import net.corda.core.internal.LifeCycle
|
import net.corda.core.internal.LifeCycle
|
||||||
import net.corda.core.internal.ThreadBox
|
import net.corda.core.internal.ThreadBox
|
||||||
@ -26,14 +25,12 @@ import net.corda.core.utilities.Try
|
|||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.contextLogger
|
||||||
import net.corda.core.utilities.debug
|
import net.corda.core.utilities.debug
|
||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.core.utilities.getOrThrow
|
||||||
import net.corda.nodeapi.ArtemisConsumer
|
|
||||||
import net.corda.nodeapi.ArtemisProducer
|
|
||||||
import net.corda.nodeapi.RPCApi
|
import net.corda.nodeapi.RPCApi
|
||||||
|
import net.corda.nodeapi.internal.DeduplicationChecker
|
||||||
import org.apache.activemq.artemis.api.core.RoutingType
|
import org.apache.activemq.artemis.api.core.RoutingType
|
||||||
import org.apache.activemq.artemis.api.core.SimpleString
|
import org.apache.activemq.artemis.api.core.SimpleString
|
||||||
|
import org.apache.activemq.artemis.api.core.client.*
|
||||||
import org.apache.activemq.artemis.api.core.client.ActiveMQClient.DEFAULT_ACK_BATCH_SIZE
|
import org.apache.activemq.artemis.api.core.client.ActiveMQClient.DEFAULT_ACK_BATCH_SIZE
|
||||||
import org.apache.activemq.artemis.api.core.client.ClientMessage
|
|
||||||
import org.apache.activemq.artemis.api.core.client.ServerLocator
|
|
||||||
import rx.Notification
|
import rx.Notification
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.subjects.UnicastSubject
|
import rx.subjects.UnicastSubject
|
||||||
@ -43,6 +40,7 @@ import java.time.Instant
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.*
|
import java.util.concurrent.*
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
import java.util.concurrent.atomic.AtomicLong
|
||||||
import kotlin.reflect.jvm.javaMethod
|
import kotlin.reflect.jvm.javaMethod
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -111,6 +109,8 @@ class RPCClientProxyHandler(
|
|||||||
|
|
||||||
// Used for reaping
|
// Used for reaping
|
||||||
private var reaperExecutor: ScheduledExecutorService? = null
|
private var reaperExecutor: ScheduledExecutorService? = null
|
||||||
|
// Used for sending
|
||||||
|
private var sendExecutor: ExecutorService? = null
|
||||||
|
|
||||||
// A sticky pool for running Observable.onNext()s. We need the stickiness to preserve the observation ordering.
|
// A sticky pool for running Observable.onNext()s. We need the stickiness to preserve the observation ordering.
|
||||||
private val observationExecutorThreadFactory = ThreadFactoryBuilder().setNameFormat("rpc-client-observation-pool-%d").setDaemon(true).build()
|
private val observationExecutorThreadFactory = ThreadFactoryBuilder().setNameFormat("rpc-client-observation-pool-%d").setDaemon(true).build()
|
||||||
@ -161,22 +161,14 @@ class RPCClientProxyHandler(
|
|||||||
build()
|
build()
|
||||||
}
|
}
|
||||||
|
|
||||||
// We cannot pool consumers as we need to preserve the original muxed message order.
|
private var sessionFactory: ClientSessionFactory? = null
|
||||||
// TODO We may need to pool these somehow anyway, otherwise if the server sends many big messages in parallel a
|
private var producerSession: ClientSession? = null
|
||||||
// single consumer may be starved for flow control credits. Recheck this once Artemis's large message streaming is
|
private var consumerSession: ClientSession? = null
|
||||||
// integrated properly.
|
private var rpcProducer: ClientProducer? = null
|
||||||
private var sessionAndConsumer: ArtemisConsumer? = null
|
private var rpcConsumer: ClientConsumer? = null
|
||||||
// Pool producers to reduce contention on the client side.
|
|
||||||
private val sessionAndProducerPool = LazyPool(bound = rpcConfiguration.producerPoolBound) {
|
private val deduplicationChecker = DeduplicationChecker(rpcConfiguration.deduplicationCacheExpiry)
|
||||||
// Note how we create new sessions *and* session factories per producer.
|
private val deduplicationSequenceNumber = AtomicLong(0)
|
||||||
// We cannot simply pool producers on one session because sessions are single threaded.
|
|
||||||
// We cannot simply pool sessions on one session factory because flow control credits are tied to factories, so
|
|
||||||
// sessions tend to starve each other when used concurrently.
|
|
||||||
val sessionFactory = serverLocator.createSessionFactory()
|
|
||||||
val session = sessionFactory.createSession(rpcUsername, rpcPassword, false, true, true, false, DEFAULT_ACK_BATCH_SIZE)
|
|
||||||
session.start()
|
|
||||||
ArtemisProducer(sessionFactory, session, session.createProducer(RPCApi.RPC_SERVER_QUEUE_NAME))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start the client. This creates the per-client queue, starts the consumer session and the reaper.
|
* Start the client. This creates the per-client queue, starts the consumer session and the reaper.
|
||||||
@ -187,22 +179,25 @@ class RPCClientProxyHandler(
|
|||||||
1,
|
1,
|
||||||
ThreadFactoryBuilder().setNameFormat("rpc-client-reaper-%d").setDaemon(true).build()
|
ThreadFactoryBuilder().setNameFormat("rpc-client-reaper-%d").setDaemon(true).build()
|
||||||
)
|
)
|
||||||
|
sendExecutor = Executors.newSingleThreadExecutor(
|
||||||
|
ThreadFactoryBuilder().setNameFormat("rpc-client-sender-%d").build()
|
||||||
|
)
|
||||||
reaperScheduledFuture = reaperExecutor!!.scheduleAtFixedRate(
|
reaperScheduledFuture = reaperExecutor!!.scheduleAtFixedRate(
|
||||||
this::reapObservablesAndNotify,
|
this::reapObservablesAndNotify,
|
||||||
rpcConfiguration.reapInterval.toMillis(),
|
rpcConfiguration.reapInterval.toMillis(),
|
||||||
rpcConfiguration.reapInterval.toMillis(),
|
rpcConfiguration.reapInterval.toMillis(),
|
||||||
TimeUnit.MILLISECONDS
|
TimeUnit.MILLISECONDS
|
||||||
)
|
)
|
||||||
sessionAndProducerPool.run {
|
sessionFactory = serverLocator.createSessionFactory()
|
||||||
it.session.createTemporaryQueue(clientAddress, RoutingType.ANYCAST, clientAddress)
|
producerSession = sessionFactory!!.createSession(rpcUsername, rpcPassword, false, true, true, false, DEFAULT_ACK_BATCH_SIZE)
|
||||||
}
|
rpcProducer = producerSession!!.createProducer(RPCApi.RPC_SERVER_QUEUE_NAME)
|
||||||
val sessionFactory = serverLocator.createSessionFactory()
|
consumerSession = sessionFactory!!.createSession(rpcUsername, rpcPassword, false, true, true, false, DEFAULT_ACK_BATCH_SIZE)
|
||||||
val session = sessionFactory.createSession(rpcUsername, rpcPassword, false, true, true, false, DEFAULT_ACK_BATCH_SIZE)
|
consumerSession!!.createTemporaryQueue(clientAddress, RoutingType.ANYCAST, clientAddress)
|
||||||
val consumer = session.createConsumer(clientAddress)
|
rpcConsumer = consumerSession!!.createConsumer(clientAddress)
|
||||||
consumer.setMessageHandler(this@RPCClientProxyHandler::artemisMessageHandler)
|
rpcConsumer!!.setMessageHandler(this::artemisMessageHandler)
|
||||||
sessionAndConsumer = ArtemisConsumer(sessionFactory, session, consumer)
|
|
||||||
lifeCycle.transition(State.UNSTARTED, State.SERVER_VERSION_NOT_SET)
|
lifeCycle.transition(State.UNSTARTED, State.SERVER_VERSION_NOT_SET)
|
||||||
session.start()
|
consumerSession!!.start()
|
||||||
|
producerSession!!.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is the general function that transforms a client side RPC to internal Artemis messages.
|
// This is the general function that transforms a client side RPC to internal Artemis messages.
|
||||||
@ -212,7 +207,7 @@ class RPCClientProxyHandler(
|
|||||||
if (method == toStringMethod) {
|
if (method == toStringMethod) {
|
||||||
return "Client RPC proxy for $rpcOpsClass"
|
return "Client RPC proxy for $rpcOpsClass"
|
||||||
}
|
}
|
||||||
if (sessionAndConsumer!!.session.isClosed) {
|
if (consumerSession!!.isClosed) {
|
||||||
throw RPCException("RPC Proxy is closed")
|
throw RPCException("RPC Proxy is closed")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,23 +215,20 @@ class RPCClientProxyHandler(
|
|||||||
callSiteMap?.set(replyId, Throwable("<Call site of root RPC '${method.name}'>"))
|
callSiteMap?.set(replyId, Throwable("<Call site of root RPC '${method.name}'>"))
|
||||||
try {
|
try {
|
||||||
val serialisedArguments = (arguments?.toList() ?: emptyList()).serialize(context = serializationContextWithObservableContext)
|
val serialisedArguments = (arguments?.toList() ?: emptyList()).serialize(context = serializationContextWithObservableContext)
|
||||||
val request = RPCApi.ClientToServer.RpcRequest(clientAddress, method.name, serialisedArguments.bytes, replyId, sessionId, externalTrace, impersonatedActor)
|
val request = RPCApi.ClientToServer.RpcRequest(
|
||||||
|
clientAddress,
|
||||||
|
method.name,
|
||||||
|
serialisedArguments,
|
||||||
|
replyId,
|
||||||
|
sessionId,
|
||||||
|
externalTrace,
|
||||||
|
impersonatedActor
|
||||||
|
)
|
||||||
val replyFuture = SettableFuture.create<Any>()
|
val replyFuture = SettableFuture.create<Any>()
|
||||||
sessionAndProducerPool.run {
|
require(rpcReplyMap.put(replyId, replyFuture) == null) {
|
||||||
val message = it.session.createMessage(false)
|
"Generated several RPC requests with same ID $replyId"
|
||||||
request.writeToClientMessage(message)
|
|
||||||
|
|
||||||
log.debug {
|
|
||||||
val argumentsString = arguments?.joinToString() ?: ""
|
|
||||||
"-> RPC(${replyId.value}) -> ${method.name}($argumentsString): ${method.returnType}"
|
|
||||||
}
|
|
||||||
|
|
||||||
require(rpcReplyMap.put(replyId, replyFuture) == null) {
|
|
||||||
"Generated several RPC requests with same ID $replyId"
|
|
||||||
}
|
|
||||||
it.producer.send(message)
|
|
||||||
it.session.commit()
|
|
||||||
}
|
}
|
||||||
|
sendMessage(request)
|
||||||
return replyFuture.getOrThrow()
|
return replyFuture.getOrThrow()
|
||||||
} catch (e: RuntimeException) {
|
} catch (e: RuntimeException) {
|
||||||
// Already an unchecked exception, so just rethrow it
|
// Already an unchecked exception, so just rethrow it
|
||||||
@ -249,9 +241,24 @@ class RPCClientProxyHandler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun sendMessage(message: RPCApi.ClientToServer) {
|
||||||
|
val artemisMessage = producerSession!!.createMessage(false)
|
||||||
|
message.writeToClientMessage(artemisMessage)
|
||||||
|
sendExecutor!!.submit {
|
||||||
|
artemisMessage.putLongProperty(RPCApi.DEDUPLICATION_SEQUENCE_NUMBER_FIELD_NAME, deduplicationSequenceNumber.getAndIncrement())
|
||||||
|
log.debug { "-> RPC -> $message" }
|
||||||
|
rpcProducer!!.send(artemisMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// The handler for Artemis messages.
|
// The handler for Artemis messages.
|
||||||
private fun artemisMessageHandler(message: ClientMessage) {
|
private fun artemisMessageHandler(message: ClientMessage) {
|
||||||
val serverToClient = RPCApi.ServerToClient.fromClientMessage(serializationContextWithObservableContext, message)
|
val serverToClient = RPCApi.ServerToClient.fromClientMessage(serializationContextWithObservableContext, message)
|
||||||
|
val deduplicationSequenceNumber = message.getLongProperty(RPCApi.DEDUPLICATION_SEQUENCE_NUMBER_FIELD_NAME)
|
||||||
|
if (deduplicationChecker.checkDuplicateMessageId(serverToClient.deduplicationIdentity, deduplicationSequenceNumber)) {
|
||||||
|
log.info("Message duplication detected, discarding message")
|
||||||
|
return
|
||||||
|
}
|
||||||
log.debug { "Got message from RPC server $serverToClient" }
|
log.debug { "Got message from RPC server $serverToClient" }
|
||||||
when (serverToClient) {
|
when (serverToClient) {
|
||||||
is RPCApi.ServerToClient.RpcReply -> {
|
is RPCApi.ServerToClient.RpcReply -> {
|
||||||
@ -325,14 +332,12 @@ class RPCClientProxyHandler(
|
|||||||
* @param notify whether to notify observables or not.
|
* @param notify whether to notify observables or not.
|
||||||
*/
|
*/
|
||||||
private fun close(notify: Boolean = true) {
|
private fun close(notify: Boolean = true) {
|
||||||
sessionAndConsumer?.sessionFactory?.close()
|
sessionFactory?.close()
|
||||||
reaperScheduledFuture?.cancel(false)
|
reaperScheduledFuture?.cancel(false)
|
||||||
observableContext.observableMap.invalidateAll()
|
observableContext.observableMap.invalidateAll()
|
||||||
reapObservables(notify)
|
reapObservables(notify)
|
||||||
reaperExecutor?.shutdownNow()
|
reaperExecutor?.shutdownNow()
|
||||||
sessionAndProducerPool.close().forEach {
|
sendExecutor?.shutdownNow()
|
||||||
it.sessionFactory.close()
|
|
||||||
}
|
|
||||||
// Note the ordering is important, we shut down the consumer *before* the observation executor, otherwise we may
|
// Note the ordering is important, we shut down the consumer *before* the observation executor, otherwise we may
|
||||||
// leak borrowed executors.
|
// leak borrowed executors.
|
||||||
val observationExecutors = observationExecutorPool.close()
|
val observationExecutors = observationExecutorPool.close()
|
||||||
@ -385,11 +390,7 @@ class RPCClientProxyHandler(
|
|||||||
}
|
}
|
||||||
if (observableIds != null) {
|
if (observableIds != null) {
|
||||||
log.debug { "Reaping ${observableIds.size} observables" }
|
log.debug { "Reaping ${observableIds.size} observables" }
|
||||||
sessionAndProducerPool.run {
|
sendMessage(RPCApi.ClientToServer.ObservablesClosed(observableIds))
|
||||||
val message = it.session.createMessage(false)
|
|
||||||
RPCApi.ClientToServer.ObservablesClosed(observableIds).writeToClientMessage(message)
|
|
||||||
it.producer.send(message)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import com.google.common.base.Stopwatch
|
|||||||
import net.corda.client.rpc.internal.RPCClientConfiguration
|
import net.corda.client.rpc.internal.RPCClientConfiguration
|
||||||
import net.corda.core.internal.concurrent.doneFuture
|
import net.corda.core.internal.concurrent.doneFuture
|
||||||
import net.corda.core.messaging.RPCOps
|
import net.corda.core.messaging.RPCOps
|
||||||
|
import net.corda.core.utilities.days
|
||||||
import net.corda.core.utilities.minutes
|
import net.corda.core.utilities.minutes
|
||||||
import net.corda.core.utilities.seconds
|
import net.corda.core.utilities.seconds
|
||||||
import net.corda.node.services.messaging.RPCServerConfiguration
|
import net.corda.node.services.messaging.RPCServerConfiguration
|
||||||
@ -88,13 +89,10 @@ class RPCPerformanceTests : AbstractRPCTest() {
|
|||||||
val proxy = testProxy(
|
val proxy = testProxy(
|
||||||
RPCClientConfiguration.default.copy(
|
RPCClientConfiguration.default.copy(
|
||||||
cacheConcurrencyLevel = 16,
|
cacheConcurrencyLevel = 16,
|
||||||
observationExecutorPoolSize = 2,
|
observationExecutorPoolSize = 2
|
||||||
producerPoolBound = 2
|
|
||||||
),
|
),
|
||||||
RPCServerConfiguration.default.copy(
|
RPCServerConfiguration.default.copy(
|
||||||
rpcThreadPoolSize = 8,
|
rpcThreadPoolSize = 8
|
||||||
consumerPoolSize = 2,
|
|
||||||
producerPoolBound = 8
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -131,13 +129,10 @@ class RPCPerformanceTests : AbstractRPCTest() {
|
|||||||
val proxy = testProxy(
|
val proxy = testProxy(
|
||||||
RPCClientConfiguration.default.copy(
|
RPCClientConfiguration.default.copy(
|
||||||
reapInterval = 1.seconds,
|
reapInterval = 1.seconds,
|
||||||
cacheConcurrencyLevel = 16,
|
cacheConcurrencyLevel = 16
|
||||||
producerPoolBound = 8
|
|
||||||
),
|
),
|
||||||
RPCServerConfiguration.default.copy(
|
RPCServerConfiguration.default.copy(
|
||||||
rpcThreadPoolSize = 8,
|
rpcThreadPoolSize = 8
|
||||||
consumerPoolSize = 1,
|
|
||||||
producerPoolBound = 8
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
startPublishingFixedRateInjector(
|
startPublishingFixedRateInjector(
|
||||||
@ -167,9 +162,7 @@ class RPCPerformanceTests : AbstractRPCTest() {
|
|||||||
rpcDriver {
|
rpcDriver {
|
||||||
val proxy = testProxy(
|
val proxy = testProxy(
|
||||||
RPCClientConfiguration.default,
|
RPCClientConfiguration.default,
|
||||||
RPCServerConfiguration.default.copy(
|
RPCServerConfiguration.default
|
||||||
consumerPoolSize = 1
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
val numberOfMessages = 1000
|
val numberOfMessages = 1000
|
||||||
val bigSize = 10_000_000
|
val bigSize = 10_000_000
|
||||||
|
@ -14,7 +14,7 @@ dependencies {
|
|||||||
compile project(':core')
|
compile project(':core')
|
||||||
|
|
||||||
// Quasar, for suspendable fibres.
|
// Quasar, for suspendable fibres.
|
||||||
compileOnly "co.paralleluniverse:quasar-core:$quasar_version:jdk8"
|
compileOnly "$quasar_group:quasar-core:$quasar_version:jdk8"
|
||||||
|
|
||||||
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||||
testCompile "junit:junit:$junit_version"
|
testCompile "junit:junit:$junit_version"
|
||||||
|
@ -18,7 +18,7 @@ import net.corda.testing.core.ALICE_NAME
|
|||||||
import net.corda.testing.core.BOB_NAME
|
import net.corda.testing.core.BOB_NAME
|
||||||
import net.corda.testing.core.CHARLIE_NAME
|
import net.corda.testing.core.CHARLIE_NAME
|
||||||
import net.corda.testing.core.singleIdentity
|
import net.corda.testing.core.singleIdentity
|
||||||
import net.corda.testing.node.MockNetwork
|
import net.corda.testing.node.internal.InternalMockNetwork
|
||||||
import net.corda.testing.node.startFlow
|
import net.corda.testing.node.startFlow
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
@ -28,15 +28,15 @@ import kotlin.test.assertNotNull
|
|||||||
import kotlin.test.assertNull
|
import kotlin.test.assertNull
|
||||||
|
|
||||||
class IdentitySyncFlowTests {
|
class IdentitySyncFlowTests {
|
||||||
private lateinit var mockNet: MockNetwork
|
private lateinit var mockNet: InternalMockNetwork
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun before() {
|
fun before() {
|
||||||
// We run this in parallel threads to help catch any race conditions that may exist.
|
// We run this in parallel threads to help catch any race conditions that may exist.
|
||||||
mockNet = MockNetwork(
|
mockNet = InternalMockNetwork(
|
||||||
|
cordappPackages = listOf("net.corda.finance.contracts.asset", "net.corda.finance.schemas"),
|
||||||
networkSendManuallyPumped = false,
|
networkSendManuallyPumped = false,
|
||||||
threadPerNode = true,
|
threadPerNode = true
|
||||||
cordappPackages = listOf("net.corda.finance.contracts.asset", "net.corda.finance.schemas")
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,19 +3,19 @@ package net.corda.confidential
|
|||||||
import net.corda.core.identity.*
|
import net.corda.core.identity.*
|
||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.core.utilities.getOrThrow
|
||||||
import net.corda.testing.core.*
|
import net.corda.testing.core.*
|
||||||
import net.corda.testing.node.MockNetwork
|
import net.corda.testing.node.internal.InternalMockNetwork
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import net.corda.testing.node.startFlow
|
import net.corda.testing.node.startFlow
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import kotlin.test.*
|
import kotlin.test.*
|
||||||
|
|
||||||
class SwapIdentitiesFlowTests {
|
class SwapIdentitiesFlowTests {
|
||||||
private lateinit var mockNet: MockNetwork
|
private lateinit var mockNet: InternalMockNetwork
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
// We run this in parallel threads to help catch any race conditions that may exist.
|
// We run this in parallel threads to help catch any race conditions that may exist.
|
||||||
mockNet = MockNetwork(emptyList(), networkSendManuallyPumped = false, threadPerNode = true)
|
mockNet = InternalMockNetwork(emptyList(), networkSendManuallyPumped = false, threadPerNode = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
gradlePluginsVersion=3.0.5
|
gradlePluginsVersion=4.0.0
|
||||||
kotlinVersion=1.2.20
|
kotlinVersion=1.2.20
|
||||||
platformVersion=2
|
platformVersion=2
|
||||||
guavaVersion=21.0
|
guavaVersion=21.0
|
||||||
@ -6,4 +6,4 @@ bouncycastleVersion=1.57
|
|||||||
typesafeConfigVersion=1.3.1
|
typesafeConfigVersion=1.3.1
|
||||||
jsr305Version=3.0.2
|
jsr305Version=3.0.2
|
||||||
artifactoryPluginVersion=4.4.18
|
artifactoryPluginVersion=4.4.18
|
||||||
snakeYamlVersion=1.19
|
snakeYamlVersion=1.19
|
||||||
|
@ -78,7 +78,7 @@ dependencies {
|
|||||||
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||||
|
|
||||||
// Quasar, for suspendable fibres.
|
// Quasar, for suspendable fibres.
|
||||||
compileOnly "co.paralleluniverse:quasar-core:$quasar_version:jdk8"
|
compileOnly "$quasar_group:quasar-core:$quasar_version:jdk8"
|
||||||
|
|
||||||
// Thread safety annotations
|
// Thread safety annotations
|
||||||
compile "com.google.code.findbugs:jsr305:$jsr305_version"
|
compile "com.google.code.findbugs:jsr305:$jsr305_version"
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
package net.corda.core.cordapp
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown if an exception occurs in accessing or parsing cordapp configuration
|
||||||
|
*/
|
||||||
|
class CordappConfigException(msg: String, e: Throwable) : Exception(msg, e)
|
70
core/src/main/kotlin/net/corda/core/cordapp/CordappConfig.kt
Normal file
70
core/src/main/kotlin/net/corda/core/cordapp/CordappConfig.kt
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package net.corda.core.cordapp
|
||||||
|
|
||||||
|
import net.corda.core.DoNotImplement
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides access to cordapp configuration independent of the configuration provider.
|
||||||
|
*/
|
||||||
|
@DoNotImplement
|
||||||
|
interface CordappConfig {
|
||||||
|
/**
|
||||||
|
* Check if a config exists at path
|
||||||
|
*/
|
||||||
|
fun exists(path: String): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the value of the configuration at "path".
|
||||||
|
*
|
||||||
|
* @throws CordappConfigException If the configuration fails to load, parse, or find a value.
|
||||||
|
*/
|
||||||
|
fun get(path: String): Any
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the int value of the configuration at "path".
|
||||||
|
*
|
||||||
|
* @throws CordappConfigException If the configuration fails to load, parse, or find a value.
|
||||||
|
*/
|
||||||
|
fun getInt(path: String): Int
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the long value of the configuration at "path".
|
||||||
|
*
|
||||||
|
* @throws CordappConfigException If the configuration fails to load, parse, or find a value.
|
||||||
|
*/
|
||||||
|
fun getLong(path: String): Long
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the float value of the configuration at "path".
|
||||||
|
*
|
||||||
|
* @throws CordappConfigException If the configuration fails to load, parse, or find a value.
|
||||||
|
*/
|
||||||
|
fun getFloat(path: String): Float
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the double value of the configuration at "path".
|
||||||
|
*
|
||||||
|
* @throws CordappConfigException If the configuration fails to load, parse, or find a value.
|
||||||
|
*/
|
||||||
|
fun getDouble(path: String): Double
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the number value of the configuration at "path".
|
||||||
|
*
|
||||||
|
* @throws CordappConfigException If the configuration fails to load, parse, or find a value.
|
||||||
|
*/
|
||||||
|
fun getNumber(path: String): Number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the string value of the configuration at "path".
|
||||||
|
*
|
||||||
|
* @throws CordappConfigException If the configuration fails to load, parse, or find a value.
|
||||||
|
*/
|
||||||
|
fun getString(path: String): String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the boolean value of the configuration at "path".
|
||||||
|
*
|
||||||
|
* @throws CordappConfigException If the configuration fails to load, parse, or find a value.
|
||||||
|
*/
|
||||||
|
fun getBoolean(path: String): Boolean
|
||||||
|
}
|
@ -2,8 +2,6 @@ package net.corda.core.cordapp
|
|||||||
|
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
|
|
||||||
// TODO: Add per app config
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An app context provides information about where an app was loaded from, access to its classloader,
|
* An app context provides information about where an app was loaded from, access to its classloader,
|
||||||
* and (in the included [Cordapp] object) lists of annotated classes discovered via scanning the JAR.
|
* and (in the included [Cordapp] object) lists of annotated classes discovered via scanning the JAR.
|
||||||
@ -15,5 +13,11 @@ import net.corda.core.crypto.SecureHash
|
|||||||
* @property attachmentId For CorDapps containing [Contract] or [UpgradedContract] implementations this will be populated
|
* @property attachmentId For CorDapps containing [Contract] or [UpgradedContract] implementations this will be populated
|
||||||
* with the attachment containing those class files
|
* with the attachment containing those class files
|
||||||
* @property classLoader the classloader used to load this cordapp's classes
|
* @property classLoader the classloader used to load this cordapp's classes
|
||||||
|
* @property config Configuration for this CorDapp
|
||||||
*/
|
*/
|
||||||
class CordappContext(val cordapp: Cordapp, val attachmentId: SecureHash?, val classLoader: ClassLoader)
|
class CordappContext internal constructor(
|
||||||
|
val cordapp: Cordapp,
|
||||||
|
val attachmentId: SecureHash?,
|
||||||
|
val classLoader: ClassLoader,
|
||||||
|
val config: CordappConfig
|
||||||
|
)
|
||||||
|
101
core/src/main/kotlin/net/corda/core/flows/NotarisationRequest.kt
Normal file
101
core/src/main/kotlin/net/corda/core/flows/NotarisationRequest.kt
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
package net.corda.core.flows
|
||||||
|
|
||||||
|
import net.corda.core.contracts.StateRef
|
||||||
|
import net.corda.core.crypto.DigitalSignature
|
||||||
|
import net.corda.core.crypto.SecureHash
|
||||||
|
import net.corda.core.identity.Party
|
||||||
|
import net.corda.core.serialization.CordaSerializable
|
||||||
|
import net.corda.core.serialization.serialize
|
||||||
|
import net.corda.core.transactions.CoreTransaction
|
||||||
|
import net.corda.core.transactions.SignedTransaction
|
||||||
|
import java.security.InvalidKeyException
|
||||||
|
import java.security.SignatureException
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A notarisation request specifies a list of states to consume and the id of the consuming transaction. Its primary
|
||||||
|
* purpose is for notarisation traceability – a signature over the notarisation request, [NotarisationRequestSignature],
|
||||||
|
* allows a notary to prove that a certain party requested the consumption of a particular state.
|
||||||
|
*
|
||||||
|
* While the signature must be retained, the notarisation request does not need to be transferred or stored anywhere - it
|
||||||
|
* can be built from a [SignedTransaction] or a [CoreTransaction]. The notary can recompute it from the committed states index.
|
||||||
|
*
|
||||||
|
* In case there is a need to prove that a party spent a particular state, the notary will:
|
||||||
|
* 1) Locate the consuming transaction id in the index, along with all other states consumed in the same transaction.
|
||||||
|
* 2) Build a [NotarisationRequest].
|
||||||
|
* 3) Locate the [NotarisationRequestSignature] for the transaction id. The signature will contain the signing public key.
|
||||||
|
* 4) Demonstrate the signature verifies against the serialized request. The provided states are always sorted internally,
|
||||||
|
* to ensure the serialization does not get affected by the order.
|
||||||
|
*/
|
||||||
|
@CordaSerializable
|
||||||
|
class NotarisationRequest(statesToConsume: List<StateRef>, val transactionId: SecureHash) {
|
||||||
|
companion object {
|
||||||
|
/** Sorts in ascending order first by transaction hash, then by output index. */
|
||||||
|
private val stateRefComparator = compareBy<StateRef>({ it.txhash }, { it.index })
|
||||||
|
}
|
||||||
|
|
||||||
|
private val _statesToConsumeSorted = statesToConsume.sortedWith(stateRefComparator)
|
||||||
|
|
||||||
|
/** States this request specifies to be consumed. Sorted to ensure the serialized form does not get affected by the state order. */
|
||||||
|
val statesToConsume: List<StateRef> get() = _statesToConsumeSorted // Getter required for AMQP serialization
|
||||||
|
|
||||||
|
/** Verifies the signature against this notarisation request. Checks that the signature is issued by the right party. */
|
||||||
|
fun verifySignature(requestSignature: NotarisationRequestSignature, intendedSigner: Party) {
|
||||||
|
val signature = requestSignature.digitalSignature
|
||||||
|
if (intendedSigner.owningKey != signature.by) {
|
||||||
|
val errorMessage = "Expected a signature by ${intendedSigner.owningKey}, but received by ${signature.by}}"
|
||||||
|
throw NotaryException(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 NotaryException(error)
|
||||||
|
}
|
||||||
|
else -> throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A wrapper around a digital signature used for notarisation requests.
|
||||||
|
*
|
||||||
|
* The [platformVersion] is required so the notary can verify the signature against the right version of serialized
|
||||||
|
* bytes of the [NotarisationRequest]. Otherwise, the request may be rejected.
|
||||||
|
*/
|
||||||
|
@CordaSerializable
|
||||||
|
data class NotarisationRequestSignature(val digitalSignature: DigitalSignature.WithKey, val platformVersion: Int)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Container for the transaction and notarisation request signature.
|
||||||
|
* This is the payload that gets sent by a client to a notary service for committing the input states of the [transaction].
|
||||||
|
*/
|
||||||
|
@CordaSerializable
|
||||||
|
data class NotarisationPayload(val transaction: Any, val requestSignature: NotarisationRequestSignature) {
|
||||||
|
init {
|
||||||
|
require(transaction is SignedTransaction || transaction is CoreTransaction) {
|
||||||
|
"Unsupported transaction type in the notarisation payload: ${transaction.javaClass.simpleName}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A helper for automatically casting the underlying [transaction] payload to a [SignedTransaction].
|
||||||
|
* Should only be used by validating notaries.
|
||||||
|
*/
|
||||||
|
val signedTransaction get() = transaction as SignedTransaction
|
||||||
|
/**
|
||||||
|
* A helper for automatically casting the underlying [transaction] payload to a [CoreTransaction].
|
||||||
|
* Should only be used by non-validating notaries.
|
||||||
|
*/
|
||||||
|
val coreTransaction get() = transaction as CoreTransaction
|
||||||
|
}
|
@ -9,10 +9,12 @@ import net.corda.core.crypto.TransactionSignature
|
|||||||
import net.corda.core.crypto.keys
|
import net.corda.core.crypto.keys
|
||||||
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.node.services.NotaryService
|
import net.corda.core.node.services.NotaryService
|
||||||
import net.corda.core.node.services.TrustedAuthorityNotaryService
|
import net.corda.core.node.services.TrustedAuthorityNotaryService
|
||||||
import net.corda.core.node.services.UniquenessProvider
|
import net.corda.core.node.services.UniquenessProvider
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
|
import net.corda.core.transactions.CoreTransaction
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.utilities.ProgressTracker
|
import net.corda.core.utilities.ProgressTracker
|
||||||
import net.corda.core.utilities.UntrustworthyData
|
import net.corda.core.utilities.UntrustworthyData
|
||||||
@ -73,15 +75,17 @@ class NotaryFlow {
|
|||||||
return notaryParty
|
return notaryParty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Notarises the transaction with the [notaryParty], obtains the notary's signature(s). */
|
||||||
@Throws(NotaryException::class)
|
@Throws(NotaryException::class)
|
||||||
@Suspendable
|
@Suspendable
|
||||||
protected fun notarise(notaryParty: Party): UntrustworthyData<List<TransactionSignature>> {
|
protected fun notarise(notaryParty: Party): UntrustworthyData<List<TransactionSignature>> {
|
||||||
return try {
|
return try {
|
||||||
val session = initiateFlow(notaryParty)
|
val session = initiateFlow(notaryParty)
|
||||||
|
val requestSignature = NotarisationRequest(stx.inputs, stx.id).generateSignature(serviceHub)
|
||||||
if (serviceHub.networkMapCache.isValidatingNotary(notaryParty)) {
|
if (serviceHub.networkMapCache.isValidatingNotary(notaryParty)) {
|
||||||
sendAndReceiveValidating(session)
|
sendAndReceiveValidating(session, requestSignature)
|
||||||
} else {
|
} else {
|
||||||
sendAndReceiveNonValidating(notaryParty, session)
|
sendAndReceiveNonValidating(notaryParty, session, requestSignature)
|
||||||
}
|
}
|
||||||
} catch (e: NotaryException) {
|
} catch (e: NotaryException) {
|
||||||
if (e.error is NotaryError.Conflict) {
|
if (e.error is NotaryError.Conflict) {
|
||||||
@ -92,21 +96,23 @@ class NotaryFlow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
protected open fun sendAndReceiveValidating(session: FlowSession): UntrustworthyData<List<TransactionSignature>> {
|
private fun sendAndReceiveValidating(session: FlowSession, signature: NotarisationRequestSignature): UntrustworthyData<List<TransactionSignature>> {
|
||||||
subFlow(SendTransactionWithRetry(session, stx))
|
val payload = NotarisationPayload(stx, signature)
|
||||||
|
subFlow(NotarySendTransactionFlow(session, payload))
|
||||||
return session.receive()
|
return session.receive()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
protected open fun sendAndReceiveNonValidating(notaryParty: Party, session: FlowSession): UntrustworthyData<List<TransactionSignature>> {
|
private fun sendAndReceiveNonValidating(notaryParty: Party, session: FlowSession, signature: NotarisationRequestSignature): UntrustworthyData<List<TransactionSignature>> {
|
||||||
val tx: Any = if (stx.isNotaryChangeTransaction()) {
|
val tx: CoreTransaction = if (stx.isNotaryChangeTransaction()) {
|
||||||
stx.notaryChangeTx // Notary change transactions do not support filtering
|
stx.notaryChangeTx // Notary change transactions do not support filtering
|
||||||
} else {
|
} else {
|
||||||
stx.buildFilteredTransaction(Predicate { it is StateRef || it is TimeWindow || it == notaryParty })
|
stx.buildFilteredTransaction(Predicate { it is StateRef || it is TimeWindow || it == notaryParty })
|
||||||
}
|
}
|
||||||
return session.sendAndReceiveWithRetry(tx)
|
return session.sendAndReceiveWithRetry(NotarisationPayload(tx, signature))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Checks that the notary's signature(s) is/are valid. */
|
||||||
protected fun validateResponse(response: UntrustworthyData<List<TransactionSignature>>, notaryParty: Party): List<TransactionSignature> {
|
protected fun validateResponse(response: UntrustworthyData<List<TransactionSignature>>, notaryParty: Party): List<TransactionSignature> {
|
||||||
return response.unwrap { signatures ->
|
return response.unwrap { signatures ->
|
||||||
signatures.forEach { validateSignature(it, stx.id, notaryParty) }
|
signatures.forEach { validateSignature(it, stx.id, notaryParty) }
|
||||||
@ -118,16 +124,16 @@ class NotaryFlow {
|
|||||||
check(sig.by in notaryParty.owningKey.keys) { "Invalid signer for the notary result" }
|
check(sig.by in notaryParty.owningKey.keys) { "Invalid signer for the notary result" }
|
||||||
sig.verify(txId)
|
sig.verify(txId)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The [SendTransactionWithRetry] flow is equivalent to [SendTransactionFlow] but using [sendAndReceiveWithRetry]
|
* The [NotarySendTransactionFlow] flow is similar to [SendTransactionFlow], but uses [NotarisationPayload] as the
|
||||||
* instead of [sendAndReceive], [SendTransactionWithRetry] is intended to be use by the notary client only.
|
* initial message, and retries message delivery.
|
||||||
*/
|
*/
|
||||||
private class SendTransactionWithRetry(otherSideSession: FlowSession, stx: SignedTransaction) : SendTransactionFlow(otherSideSession, stx) {
|
private class NotarySendTransactionFlow(otherSide: FlowSession, payload: NotarisationPayload) : DataVendingFlow(otherSide, payload) {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun sendPayloadAndReceiveDataRequest(otherSideSession: FlowSession, payload: Any): UntrustworthyData<FetchDataFlow.Request> {
|
override fun sendPayloadAndReceiveDataRequest(otherSideSession: FlowSession, payload: Any): UntrustworthyData<FetchDataFlow.Request> {
|
||||||
return otherSideSession.sendAndReceiveWithRetry(payload)
|
return otherSideSession.sendAndReceiveWithRetry(payload)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,10 +192,16 @@ class NotaryFlow {
|
|||||||
*/
|
*/
|
||||||
data class TransactionParts(val id: SecureHash, val inputs: List<StateRef>, val timestamp: TimeWindow?, val notary: Party?)
|
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(val error: NotaryError) : FlowException("Unable to notarise: $error")
|
class NotaryException(val error: NotaryError) : FlowException("Unable to notarise: $error")
|
||||||
|
|
||||||
|
/** Specifies the cause for notarisation request failure. */
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
sealed class NotaryError {
|
sealed class NotaryError {
|
||||||
|
/** Occurs when one or more input states of transaction with [txId] have already been consumed by another transaction. */
|
||||||
data class Conflict(val txId: SecureHash, val conflict: SignedData<UniquenessProvider.Conflict>) : NotaryError() {
|
data class Conflict(val txId: SecureHash, val conflict: SignedData<UniquenessProvider.Conflict>) : NotaryError() {
|
||||||
override fun toString() = "One or more input states for transaction $txId have been used in another transaction"
|
override fun toString() = "One or more input states for transaction $txId have been used in another transaction"
|
||||||
}
|
}
|
||||||
@ -199,18 +211,27 @@ sealed class NotaryError {
|
|||||||
override fun toString() = "Current time $currentTime is outside the time bounds specified by the transaction: $txTimeWindow"
|
override fun toString() = "Current time $currentTime is outside the time bounds specified by the transaction: $txTimeWindow"
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmField @Deprecated("Here only for binary compatibility purposes, do not use.")
|
@JvmField
|
||||||
|
@Deprecated("Here only for binary compatibility purposes, do not use.")
|
||||||
val INSTANCE = TimeWindowInvalid(Instant.EPOCH, TimeWindow.fromOnly(Instant.EPOCH))
|
val INSTANCE = TimeWindowInvalid(Instant.EPOCH, TimeWindow.fromOnly(Instant.EPOCH))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Occurs when the provided transaction fails to verify. */
|
||||||
data class TransactionInvalid(val cause: Throwable) : NotaryError() {
|
data class TransactionInvalid(val cause: Throwable) : NotaryError() {
|
||||||
override fun toString() = cause.toString()
|
override fun toString() = cause.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Occurs when the transaction sent for notarisation is assigned to a different notary identity. */
|
||||||
object WrongNotary : NotaryError()
|
object WrongNotary : NotaryError()
|
||||||
|
|
||||||
data class General(val cause: String): NotaryError() {
|
/** Occurs when the notarisation request signature does not verify for the provided transaction. */
|
||||||
override fun toString() = cause
|
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ open class SendTransactionFlow(otherSide: FlowSession, stx: SignedTransaction) :
|
|||||||
*/
|
*/
|
||||||
open class SendStateAndRefFlow(otherSideSession: FlowSession, stateAndRefs: List<StateAndRef<*>>) : DataVendingFlow(otherSideSession, stateAndRefs)
|
open class SendStateAndRefFlow(otherSideSession: FlowSession, stateAndRefs: List<StateAndRef<*>>) : DataVendingFlow(otherSideSession, stateAndRefs)
|
||||||
|
|
||||||
sealed class DataVendingFlow(val otherSideSession: FlowSession, val payload: Any) : FlowLogic<Void?>() {
|
open class DataVendingFlow(val otherSideSession: FlowSession, val payload: Any) : FlowLogic<Void?>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
protected open fun sendPayloadAndReceiveDataRequest(otherSideSession: FlowSession, payload: Any) = otherSideSession.sendAndReceive<FetchDataFlow.Request>(payload)
|
protected open fun sendPayloadAndReceiveDataRequest(otherSideSession: FlowSession, payload: Any) = otherSideSession.sendAndReceive<FetchDataFlow.Request>(payload)
|
||||||
|
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
package net.corda.core.internal
|
|
||||||
|
|
||||||
import net.corda.core.node.NetworkParameters
|
|
||||||
|
|
||||||
// TODO: This will cause problems when we run tests in parallel, make each node have its own properties.
|
|
||||||
object GlobalProperties {
|
|
||||||
private var _networkParameters: NetworkParameters? = null
|
|
||||||
|
|
||||||
var networkParameters: NetworkParameters
|
|
||||||
get() = checkNotNull(_networkParameters) { "Property 'networkParameters' has not been initialised." }
|
|
||||||
set(value) {
|
|
||||||
_networkParameters = value
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,9 +2,16 @@
|
|||||||
|
|
||||||
package net.corda.core.internal
|
package net.corda.core.internal
|
||||||
|
|
||||||
|
import net.corda.core.cordapp.Cordapp
|
||||||
|
import net.corda.core.cordapp.CordappConfig
|
||||||
|
import net.corda.core.cordapp.CordappContext
|
||||||
import net.corda.core.cordapp.CordappProvider
|
import net.corda.core.cordapp.CordappProvider
|
||||||
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
|
||||||
@ -375,3 +382,23 @@ inline fun <T : Any> SerializedBytes<T>.sign(keyPair: KeyPair): SignedData<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun ByteBuffer.copyBytes() = ByteArray(remaining()).also { get(it) }
|
fun ByteBuffer.copyBytes() = ByteArray(remaining()).also { get(it) }
|
||||||
|
|
||||||
|
fun createCordappContext(cordapp: Cordapp, attachmentId: SecureHash?, classLoader: ClassLoader, config: CordappConfig): CordappContext {
|
||||||
|
return CordappContext(cordapp, attachmentId, classLoader, config)
|
||||||
|
}
|
||||||
|
/** Verifies that the correct notarisation request was signed by the counterparty. */
|
||||||
|
fun NotaryFlow.Service.validateRequest(request: NotarisationRequest, signature: NotarisationRequestSignature) {
|
||||||
|
val requestingParty = otherSideSession.counterparty
|
||||||
|
request.verifySignature(signature, requestingParty)
|
||||||
|
// TODO: persist the signature for traceability. Do we need to persist the request as well?
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 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)
|
||||||
|
}
|
@ -26,7 +26,9 @@ data class NodeInfo(val addresses: List<NetworkHostAndPort>,
|
|||||||
) {
|
) {
|
||||||
// TODO We currently don't support multi-IP/multi-identity nodes, we only left slots in the data structures.
|
// TODO We currently don't support multi-IP/multi-identity nodes, we only left slots in the data structures.
|
||||||
init {
|
init {
|
||||||
require(legalIdentitiesAndCerts.isNotEmpty()) { "Node should have at least one legal identity" }
|
require(addresses.isNotEmpty()) { "Node must have at least one address" }
|
||||||
|
require(legalIdentitiesAndCerts.isNotEmpty()) { "Node must have at least one legal identity" }
|
||||||
|
require(platformVersion > 0) { "Platform version must be at least 1" }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transient private var _legalIdentities: List<Party>? = null
|
@Transient private var _legalIdentities: List<Party>? = null
|
||||||
|
@ -2,6 +2,7 @@ package net.corda.core.node
|
|||||||
|
|
||||||
import net.corda.core.DoNotImplement
|
import net.corda.core.DoNotImplement
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
|
import net.corda.core.cordapp.CordappContext
|
||||||
import net.corda.core.cordapp.CordappProvider
|
import net.corda.core.cordapp.CordappProvider
|
||||||
import net.corda.core.crypto.Crypto
|
import net.corda.core.crypto.Crypto
|
||||||
import net.corda.core.crypto.SignableData
|
import net.corda.core.crypto.SignableData
|
||||||
@ -60,6 +61,9 @@ interface ServicesForResolution : StateLoader {
|
|||||||
|
|
||||||
/** Provides access to anything relating to cordapps including contract attachment resolution and app context */
|
/** Provides access to anything relating to cordapps including contract attachment resolution and app context */
|
||||||
val cordappProvider: CordappProvider
|
val cordappProvider: CordappProvider
|
||||||
|
|
||||||
|
/** Returns the network parameters the node is operating under. */
|
||||||
|
val networkParameters: NetworkParameters
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -369,4 +373,9 @@ interface ServiceHub : ServicesForResolution {
|
|||||||
* node starts.
|
* node starts.
|
||||||
*/
|
*/
|
||||||
fun registerUnloadHandler(runOnStop: () -> Unit)
|
fun registerUnloadHandler(runOnStop: () -> Unit)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See [CordappProvider.getAppContext]
|
||||||
|
*/
|
||||||
|
fun getAppContext(): CordappContext = cordappProvider.getAppContext()
|
||||||
}
|
}
|
||||||
|
@ -67,12 +67,22 @@ interface NetworkMapCacheBase {
|
|||||||
fun track(): DataFeed<List<NodeInfo>, NetworkMapCache.MapChange>
|
fun track(): DataFeed<List<NodeInfo>, NetworkMapCache.MapChange>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Look up the node info for a legal name.
|
* Return a [NodeInfo] which has the given legal name for one of its identities, or null if no such node is found.
|
||||||
* Notice that when there are more than one node for a given name (in case of distributed services) first service node
|
*
|
||||||
* found will be returned.
|
* @throws IllegalArgumentException If more than one matching node is found, in the case of a distributed service identity
|
||||||
|
* (such as with a notary cluster). For such a scenerio use [getNodesByLegalName] instead.
|
||||||
*/
|
*/
|
||||||
fun getNodeByLegalName(name: CordaX500Name): NodeInfo?
|
fun getNodeByLegalName(name: CordaX500Name): NodeInfo?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a list of [NodeInfo]s which have the given legal name for one of their identities, or an empty list if no
|
||||||
|
* such nodes are found.
|
||||||
|
*
|
||||||
|
* Normally there is at most one node for a legal name, but for distributed service identities (such as with a notary
|
||||||
|
* cluster) there can be multiple nodes sharing the same identity.
|
||||||
|
*/
|
||||||
|
fun getNodesByLegalName(name: CordaX500Name): List<NodeInfo>
|
||||||
|
|
||||||
/** Look up the node info for a host and port. */
|
/** Look up the node info for a host and port. */
|
||||||
fun getNodeByAddress(address: NetworkHostAndPort): NodeInfo?
|
fun getNodeByAddress(address: NetworkHostAndPort): NodeInfo?
|
||||||
|
|
||||||
@ -100,13 +110,6 @@ interface NetworkMapCacheBase {
|
|||||||
*/
|
*/
|
||||||
fun getNodesByLegalIdentityKey(identityKey: PublicKey): List<NodeInfo>
|
fun getNodesByLegalIdentityKey(identityKey: PublicKey): List<NodeInfo>
|
||||||
|
|
||||||
/**
|
|
||||||
* Look up the node information entries for a legal name.
|
|
||||||
* Note that normally there will be only one node for a legal name, but for clusters of nodes or distributed services there
|
|
||||||
* can be multiple nodes.
|
|
||||||
*/
|
|
||||||
fun getNodesByLegalName(name: CordaX500Name): List<NodeInfo>
|
|
||||||
|
|
||||||
/** Returns information about the party, which may be a specific node or a service */
|
/** Returns information about the party, which may be a specific node or a service */
|
||||||
fun getPartyInfo(party: Party): PartyInfo?
|
fun getPartyInfo(party: Party): PartyInfo?
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ abstract class TrustedAuthorityNotaryService : NotaryService() {
|
|||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
log.error("Internal error", e)
|
log.error("Internal error", e)
|
||||||
throw NotaryException(NotaryError.General("Service unavailable, please try again later"))
|
throw NotaryException(NotaryError.General(Exception("Service unavailable, please try again later")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,12 +3,14 @@ package net.corda.core.transactions
|
|||||||
import net.corda.core.contracts.ContractState
|
import net.corda.core.contracts.ContractState
|
||||||
import net.corda.core.contracts.StateAndRef
|
import net.corda.core.contracts.StateAndRef
|
||||||
import net.corda.core.contracts.StateRef
|
import net.corda.core.contracts.StateRef
|
||||||
|
import net.corda.core.serialization.CordaSerializable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A transaction with the minimal amount of information required to compute the unique transaction [id], and
|
* A transaction with the minimal amount of information required to compute the unique transaction [id], and
|
||||||
* resolve a [FullTransaction]. This type of transaction, wrapped in [SignedTransaction], gets transferred across the
|
* resolve a [FullTransaction]. This type of transaction, wrapped in [SignedTransaction], gets transferred across the
|
||||||
* wire and recorded to storage.
|
* wire and recorded to storage.
|
||||||
*/
|
*/
|
||||||
|
@CordaSerializable
|
||||||
abstract class CoreTransaction : BaseTransaction() {
|
abstract class CoreTransaction : BaseTransaction() {
|
||||||
/** The inputs of this transaction, containing state references only **/
|
/** The inputs of this transaction, containing state references only **/
|
||||||
abstract override val inputs: List<StateRef>
|
abstract override val inputs: List<StateRef>
|
||||||
|
@ -5,7 +5,6 @@ import net.corda.core.contracts.ComponentGroupEnum.*
|
|||||||
import net.corda.core.crypto.*
|
import net.corda.core.crypto.*
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.internal.Emoji
|
import net.corda.core.internal.Emoji
|
||||||
import net.corda.core.internal.GlobalProperties
|
|
||||||
import net.corda.core.node.ServicesForResolution
|
import net.corda.core.node.ServicesForResolution
|
||||||
import net.corda.core.node.services.AttachmentId
|
import net.corda.core.node.services.AttachmentId
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
@ -85,11 +84,12 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
|
|||||||
*/
|
*/
|
||||||
@Throws(AttachmentResolutionException::class, TransactionResolutionException::class)
|
@Throws(AttachmentResolutionException::class, TransactionResolutionException::class)
|
||||||
fun toLedgerTransaction(services: ServicesForResolution): LedgerTransaction {
|
fun toLedgerTransaction(services: ServicesForResolution): LedgerTransaction {
|
||||||
return toLedgerTransaction(
|
return toLedgerTransactionInternal(
|
||||||
resolveIdentity = { services.identityService.partyFromKey(it) },
|
resolveIdentity = { services.identityService.partyFromKey(it) },
|
||||||
resolveAttachment = { services.attachments.openAttachment(it) },
|
resolveAttachment = { services.attachments.openAttachment(it) },
|
||||||
resolveStateRef = { services.loadState(it) },
|
resolveStateRef = { services.loadState(it) },
|
||||||
resolveContractAttachment = { services.cordappProvider.getContractAttachmentID(it.contract) }
|
resolveContractAttachment = { services.cordappProvider.getContractAttachmentID(it.contract) },
|
||||||
|
maxTransactionSize = services.networkParameters.maxTransactionSize
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,12 +100,23 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
|
|||||||
* @throws AttachmentResolutionException if a required attachment was not found using [resolveAttachment].
|
* @throws AttachmentResolutionException if a required attachment was not found using [resolveAttachment].
|
||||||
* @throws TransactionResolutionException if an input was not found not using [resolveStateRef].
|
* @throws TransactionResolutionException if an input was not found not using [resolveStateRef].
|
||||||
*/
|
*/
|
||||||
|
@Deprecated("Use toLedgerTransaction(ServicesForTransaction) instead")
|
||||||
@Throws(AttachmentResolutionException::class, TransactionResolutionException::class)
|
@Throws(AttachmentResolutionException::class, TransactionResolutionException::class)
|
||||||
fun toLedgerTransaction(
|
fun toLedgerTransaction(
|
||||||
resolveIdentity: (PublicKey) -> Party?,
|
resolveIdentity: (PublicKey) -> Party?,
|
||||||
resolveAttachment: (SecureHash) -> Attachment?,
|
resolveAttachment: (SecureHash) -> Attachment?,
|
||||||
resolveStateRef: (StateRef) -> TransactionState<*>?,
|
resolveStateRef: (StateRef) -> TransactionState<*>?,
|
||||||
resolveContractAttachment: (TransactionState<ContractState>) -> AttachmentId?
|
resolveContractAttachment: (TransactionState<ContractState>) -> AttachmentId?
|
||||||
|
): LedgerTransaction {
|
||||||
|
return toLedgerTransactionInternal(resolveIdentity, resolveAttachment, resolveStateRef, resolveContractAttachment, 10485760)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun toLedgerTransactionInternal(
|
||||||
|
resolveIdentity: (PublicKey) -> Party?,
|
||||||
|
resolveAttachment: (SecureHash) -> Attachment?,
|
||||||
|
resolveStateRef: (StateRef) -> TransactionState<*>?,
|
||||||
|
resolveContractAttachment: (TransactionState<ContractState>) -> AttachmentId?,
|
||||||
|
maxTransactionSize: Int
|
||||||
): LedgerTransaction {
|
): LedgerTransaction {
|
||||||
// Look up public keys to authenticated identities. This is just a stub placeholder and will all change in future.
|
// Look up public keys to authenticated identities. This is just a stub placeholder and will all change in future.
|
||||||
val authenticatedArgs = commands.map {
|
val authenticatedArgs = commands.map {
|
||||||
@ -120,15 +131,15 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
|
|||||||
// Order of attachments is important since contracts may refer to indexes so only append automatic attachments
|
// Order of attachments is important since contracts may refer to indexes so only append automatic attachments
|
||||||
val attachments = (attachments.map { resolveAttachment(it) ?: throw AttachmentResolutionException(it) } + contractAttachments).distinct()
|
val attachments = (attachments.map { resolveAttachment(it) ?: throw AttachmentResolutionException(it) } + contractAttachments).distinct()
|
||||||
val ltx = LedgerTransaction(resolvedInputs, outputs, authenticatedArgs, attachments, id, notary, timeWindow, privacySalt)
|
val ltx = LedgerTransaction(resolvedInputs, outputs, authenticatedArgs, attachments, id, notary, timeWindow, privacySalt)
|
||||||
checkTransactionSize(ltx)
|
checkTransactionSize(ltx, maxTransactionSize)
|
||||||
return ltx
|
return ltx
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkTransactionSize(ltx: LedgerTransaction) {
|
private fun checkTransactionSize(ltx: LedgerTransaction, maxTransactionSize: Int) {
|
||||||
var remainingTransactionSize = GlobalProperties.networkParameters.maxTransactionSize
|
var remainingTransactionSize = maxTransactionSize
|
||||||
|
|
||||||
fun minus(size: Int) {
|
fun minus(size: Int) {
|
||||||
require(remainingTransactionSize > size) { "Transaction exceeded network's maximum transaction size limit : ${GlobalProperties.networkParameters.maxTransactionSize} bytes." }
|
require(remainingTransactionSize > size) { "Transaction exceeded network's maximum transaction size limit : $maxTransactionSize bytes." }
|
||||||
remainingTransactionSize -= size
|
remainingTransactionSize -= size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,19 +58,19 @@ class PartialMerkleTreeTest {
|
|||||||
hashed = nodes.map { it.serialize().sha256() }
|
hashed = nodes.map { it.serialize().sha256() }
|
||||||
expectedRoot = MerkleTree.getMerkleTree(hashed.toMutableList() + listOf(zeroHash, zeroHash)).hash
|
expectedRoot = MerkleTree.getMerkleTree(hashed.toMutableList() + listOf(zeroHash, zeroHash)).hash
|
||||||
merkleTree = MerkleTree.getMerkleTree(hashed)
|
merkleTree = MerkleTree.getMerkleTree(hashed)
|
||||||
testLedger = MockServices(emptyList(), rigorousMock<IdentityServiceInternal>().also {
|
testLedger = MockServices(emptyList(), MEGA_CORP.name, rigorousMock<IdentityServiceInternal>().also {
|
||||||
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
|
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
|
||||||
}, MEGA_CORP.name).ledger(DUMMY_NOTARY) {
|
}).ledger(DUMMY_NOTARY) {
|
||||||
unverifiedTransaction {
|
unverifiedTransaction {
|
||||||
attachments(Cash.PROGRAM_ID)
|
attachments(Cash.PROGRAM_ID)
|
||||||
output(Cash.PROGRAM_ID, "MEGA_CORP cash",
|
output(Cash.PROGRAM_ID, "MEGA_CORP cash",
|
||||||
Cash.State(
|
Cash.State(
|
||||||
amount = 1000.DOLLARS `issued by` MEGA_CORP.ref(1, 1),
|
amount = 1000.DOLLARS `issued by` MEGA_CORP.ref(1, 1),
|
||||||
owner = MEGA_CORP))
|
owner = MEGA_CORP))
|
||||||
output(Cash.PROGRAM_ID, "dummy cash 1",
|
output(Cash.PROGRAM_ID, "dummy cash 1",
|
||||||
Cash.State(
|
Cash.State(
|
||||||
amount = 900.DOLLARS `issued by` MEGA_CORP.ref(1, 1),
|
amount = 900.DOLLARS `issued by` MEGA_CORP.ref(1, 1),
|
||||||
owner = MINI_CORP))
|
owner = MINI_CORP))
|
||||||
}
|
}
|
||||||
transaction {
|
transaction {
|
||||||
attachments(Cash.PROGRAM_ID)
|
attachments(Cash.PROGRAM_ID)
|
||||||
|
@ -13,7 +13,6 @@ import net.corda.node.services.persistence.NodeAttachmentService
|
|||||||
import net.corda.testing.core.ALICE_NAME
|
import net.corda.testing.core.ALICE_NAME
|
||||||
import net.corda.testing.core.BOB_NAME
|
import net.corda.testing.core.BOB_NAME
|
||||||
import net.corda.testing.core.singleIdentity
|
import net.corda.testing.core.singleIdentity
|
||||||
import net.corda.testing.node.MockNetwork
|
|
||||||
import net.corda.testing.node.MockNodeParameters
|
import net.corda.testing.node.MockNodeParameters
|
||||||
import net.corda.testing.node.internal.InternalMockNetwork
|
import net.corda.testing.node.internal.InternalMockNetwork
|
||||||
import net.corda.testing.node.startFlow
|
import net.corda.testing.node.startFlow
|
||||||
|
@ -11,12 +11,13 @@ import net.corda.core.identity.groupAbstractPartyByWellKnownParty
|
|||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.transactions.TransactionBuilder
|
import net.corda.core.transactions.TransactionBuilder
|
||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.core.utilities.getOrThrow
|
||||||
|
import net.corda.node.internal.StartedNode
|
||||||
import net.corda.testing.contracts.DummyContract
|
import net.corda.testing.contracts.DummyContract
|
||||||
import net.corda.testing.core.*
|
import net.corda.testing.core.*
|
||||||
import net.corda.testing.internal.rigorousMock
|
import net.corda.testing.internal.rigorousMock
|
||||||
import net.corda.testing.node.MockNetwork
|
|
||||||
import net.corda.testing.node.StartedMockNode
|
|
||||||
import net.corda.testing.node.MockServices
|
import net.corda.testing.node.MockServices
|
||||||
|
import net.corda.testing.node.internal.InternalMockNetwork
|
||||||
|
import net.corda.testing.node.internal.InternalMockNetwork.MockNode
|
||||||
import net.corda.testing.node.startFlow
|
import net.corda.testing.node.startFlow
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
@ -29,10 +30,10 @@ class CollectSignaturesFlowTests {
|
|||||||
private val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB"))
|
private val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB"))
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var mockNet: MockNetwork
|
private lateinit var mockNet: InternalMockNetwork
|
||||||
private lateinit var aliceNode: StartedMockNode
|
private lateinit var aliceNode: StartedNode<MockNode>
|
||||||
private lateinit var bobNode: StartedMockNode
|
private lateinit var bobNode: StartedNode<MockNode>
|
||||||
private lateinit var charlieNode: StartedMockNode
|
private lateinit var charlieNode: StartedNode<MockNode>
|
||||||
private lateinit var alice: Party
|
private lateinit var alice: Party
|
||||||
private lateinit var bob: Party
|
private lateinit var bob: Party
|
||||||
private lateinit var charlie: Party
|
private lateinit var charlie: Party
|
||||||
@ -40,7 +41,7 @@ class CollectSignaturesFlowTests {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
mockNet = MockNetwork(cordappPackages = listOf("net.corda.testing.contracts"))
|
mockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.testing.contracts"))
|
||||||
aliceNode = mockNet.createPartyNode(ALICE_NAME)
|
aliceNode = mockNet.createPartyNode(ALICE_NAME)
|
||||||
bobNode = mockNet.createPartyNode(BOB_NAME)
|
bobNode = mockNet.createPartyNode(BOB_NAME)
|
||||||
charlieNode = mockNet.createPartyNode(CHARLIE_NAME)
|
charlieNode = mockNet.createPartyNode(CHARLIE_NAME)
|
||||||
@ -136,7 +137,7 @@ class CollectSignaturesFlowTests {
|
|||||||
@Test
|
@Test
|
||||||
fun `fails when not signed by initiator`() {
|
fun `fails when not signed by initiator`() {
|
||||||
val onePartyDummyContract = DummyContract.generateInitial(1337, notary, alice.ref(1))
|
val onePartyDummyContract = DummyContract.generateInitial(1337, notary, alice.ref(1))
|
||||||
val miniCorpServices = MockServices(listOf("net.corda.testing.contracts"), rigorousMock(), miniCorp)
|
val miniCorpServices = MockServices(listOf("net.corda.testing.contracts"), miniCorp, rigorousMock())
|
||||||
val ptx = miniCorpServices.signInitialTransaction(onePartyDummyContract)
|
val ptx = miniCorpServices.signInitialTransaction(onePartyDummyContract)
|
||||||
val flow = aliceNode.services.startFlow(CollectSignaturesFlow(ptx, emptySet()))
|
val flow = aliceNode.services.startFlow(CollectSignaturesFlow(ptx, emptySet()))
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
|
@ -7,9 +7,9 @@ import net.corda.core.serialization.deserialize
|
|||||||
import net.corda.core.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
import net.corda.nodeapi.internal.crypto.X509KeyStore
|
import net.corda.nodeapi.internal.crypto.X509KeyStore
|
||||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||||
import net.corda.testing.core.DEV_ROOT_CA
|
|
||||||
import net.corda.testing.core.SerializationEnvironmentRule
|
import net.corda.testing.core.SerializationEnvironmentRule
|
||||||
import net.corda.testing.core.getTestPartyAndCertificate
|
import net.corda.testing.core.getTestPartyAndCertificate
|
||||||
|
import net.corda.testing.internal.DEV_ROOT_CA
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
@ -8,10 +8,11 @@ import net.corda.core.identity.Party
|
|||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.core.utilities.getOrThrow
|
||||||
import net.corda.core.utilities.sequence
|
import net.corda.core.utilities.sequence
|
||||||
|
import net.corda.node.internal.StartedNode
|
||||||
import net.corda.testing.contracts.DummyContract
|
import net.corda.testing.contracts.DummyContract
|
||||||
import net.corda.testing.node.MockNetwork
|
|
||||||
import net.corda.testing.core.singleIdentity
|
import net.corda.testing.core.singleIdentity
|
||||||
import net.corda.testing.node.StartedMockNode
|
import net.corda.testing.node.internal.InternalMockNetwork
|
||||||
|
import net.corda.testing.node.internal.InternalMockNetwork.MockNode
|
||||||
import net.corda.testing.node.startFlow
|
import net.corda.testing.node.startFlow
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
@ -27,17 +28,17 @@ import kotlin.test.assertNull
|
|||||||
|
|
||||||
// DOCSTART 3
|
// DOCSTART 3
|
||||||
class ResolveTransactionsFlowTest {
|
class ResolveTransactionsFlowTest {
|
||||||
private lateinit var mockNet: MockNetwork
|
private lateinit var mockNet: InternalMockNetwork
|
||||||
private lateinit var notaryNode: StartedMockNode
|
private lateinit var notaryNode: StartedNode<MockNode>
|
||||||
private lateinit var megaCorpNode: StartedMockNode
|
private lateinit var megaCorpNode: StartedNode<MockNode>
|
||||||
private lateinit var miniCorpNode: StartedMockNode
|
private lateinit var miniCorpNode: StartedNode<MockNode>
|
||||||
private lateinit var megaCorp: Party
|
private lateinit var megaCorp: Party
|
||||||
private lateinit var miniCorp: Party
|
private lateinit var miniCorp: Party
|
||||||
private lateinit var notary: Party
|
private lateinit var notary: Party
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
mockNet = MockNetwork(cordappPackages = listOf("net.corda.testing.contracts"))
|
mockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.testing.contracts"))
|
||||||
notaryNode = mockNet.defaultNotaryNode
|
notaryNode = mockNet.defaultNotaryNode
|
||||||
megaCorpNode = mockNet.createPartyNode(CordaX500Name("MegaCorp", "London", "GB"))
|
megaCorpNode = mockNet.createPartyNode(CordaX500Name("MegaCorp", "London", "GB"))
|
||||||
miniCorpNode = mockNet.createPartyNode(CordaX500Name("MiniCorp", "London", "GB"))
|
miniCorpNode = mockNet.createPartyNode(CordaX500Name("MiniCorp", "London", "GB"))
|
||||||
|
@ -18,7 +18,6 @@ import net.corda.node.services.persistence.NodeAttachmentService
|
|||||||
import net.corda.nodeapi.internal.persistence.currentDBSession
|
import net.corda.nodeapi.internal.persistence.currentDBSession
|
||||||
import net.corda.testing.core.ALICE_NAME
|
import net.corda.testing.core.ALICE_NAME
|
||||||
import net.corda.testing.core.BOB_NAME
|
import net.corda.testing.core.BOB_NAME
|
||||||
import net.corda.testing.node.MockNetwork
|
|
||||||
import net.corda.testing.node.MockNodeParameters
|
import net.corda.testing.node.MockNodeParameters
|
||||||
import net.corda.testing.core.singleIdentity
|
import net.corda.testing.core.singleIdentity
|
||||||
import net.corda.testing.node.internal.InternalMockNetwork
|
import net.corda.testing.node.internal.InternalMockNetwork
|
||||||
|
@ -62,8 +62,8 @@ class TransactionSerializationTests {
|
|||||||
val inputState = StateAndRef(TransactionState(TestCash.State(depositRef, 100.POUNDS, MEGA_CORP), TEST_CASH_PROGRAM_ID, DUMMY_NOTARY), fakeStateRef)
|
val inputState = StateAndRef(TransactionState(TestCash.State(depositRef, 100.POUNDS, MEGA_CORP), TEST_CASH_PROGRAM_ID, DUMMY_NOTARY), fakeStateRef)
|
||||||
val outputState = TransactionState(TestCash.State(depositRef, 600.POUNDS, MEGA_CORP), TEST_CASH_PROGRAM_ID, DUMMY_NOTARY)
|
val outputState = TransactionState(TestCash.State(depositRef, 600.POUNDS, MEGA_CORP), TEST_CASH_PROGRAM_ID, DUMMY_NOTARY)
|
||||||
val changeState = TransactionState(TestCash.State(depositRef, 400.POUNDS, MEGA_CORP), TEST_CASH_PROGRAM_ID, DUMMY_NOTARY)
|
val changeState = TransactionState(TestCash.State(depositRef, 400.POUNDS, MEGA_CORP), TEST_CASH_PROGRAM_ID, DUMMY_NOTARY)
|
||||||
val megaCorpServices = MockServices(listOf("net.corda.core.serialization"), rigorousMock(), MEGA_CORP.name, MEGA_CORP_KEY)
|
val megaCorpServices = MockServices(listOf("net.corda.core.serialization"), MEGA_CORP.name, rigorousMock(), MEGA_CORP_KEY)
|
||||||
val notaryServices = MockServices(listOf("net.corda.core.serialization"), rigorousMock(), DUMMY_NOTARY.name, DUMMY_NOTARY_KEY)
|
val notaryServices = MockServices(listOf("net.corda.core.serialization"), DUMMY_NOTARY.name, rigorousMock(), DUMMY_NOTARY_KEY)
|
||||||
lateinit var tx: TransactionBuilder
|
lateinit var tx: TransactionBuilder
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@ -107,7 +107,7 @@ class TransactionSerializationTests {
|
|||||||
Command(TestCash.Commands.Move(), DUMMY_KEY_2.public))
|
Command(TestCash.Commands.Move(), DUMMY_KEY_2.public))
|
||||||
|
|
||||||
val ptx2 = notaryServices.signInitialTransaction(tx2)
|
val ptx2 = notaryServices.signInitialTransaction(tx2)
|
||||||
val dummyServices = MockServices(emptyList(), rigorousMock(), MEGA_CORP.name, DUMMY_KEY_2)
|
val dummyServices = MockServices(emptyList(), MEGA_CORP.name, rigorousMock(), DUMMY_KEY_2)
|
||||||
val stx2 = dummyServices.addSignature(ptx2)
|
val stx2 = dummyServices.addSignature(ptx2)
|
||||||
|
|
||||||
stx.copy(sigs = stx2.sigs).verifyRequiredSignatures()
|
stx.copy(sigs = stx2.sigs).verifyRequiredSignatures()
|
||||||
|
@ -10,6 +10,7 @@ import net.corda.core.identity.Party
|
|||||||
import net.corda.node.services.api.IdentityServiceInternal
|
import net.corda.node.services.api.IdentityServiceInternal
|
||||||
import net.corda.testing.contracts.DummyContract
|
import net.corda.testing.contracts.DummyContract
|
||||||
import net.corda.testing.core.*
|
import net.corda.testing.core.*
|
||||||
|
import net.corda.testing.internal.MockCordappProvider
|
||||||
import net.corda.testing.internal.rigorousMock
|
import net.corda.testing.internal.rigorousMock
|
||||||
import net.corda.testing.node.MockServices
|
import net.corda.testing.node.MockServices
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
@ -29,14 +30,15 @@ class LedgerTransactionQueryTests {
|
|||||||
@JvmField
|
@JvmField
|
||||||
val testSerialization = SerializationEnvironmentRule()
|
val testSerialization = SerializationEnvironmentRule()
|
||||||
private val keyPair = generateKeyPair()
|
private val keyPair = generateKeyPair()
|
||||||
private val services = MockServices(emptyList(), rigorousMock<IdentityServiceInternal>().also {
|
private val services = MockServices(emptyList(), CordaX500Name("MegaCorp", "London", "GB"),
|
||||||
doReturn(null).whenever(it).partyFromKey(keyPair.public)
|
rigorousMock<IdentityServiceInternal>().also {
|
||||||
}, CordaX500Name("MegaCorp", "London", "GB"), keyPair)
|
doReturn(null).whenever(it).partyFromKey(keyPair.public)
|
||||||
|
}, keyPair)
|
||||||
private val identity: Party = services.myInfo.singleIdentity()
|
private val identity: Party = services.myInfo.singleIdentity()
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
services.mockCordappProvider.addMockCordapp(DummyContract.PROGRAM_ID, services.attachments)
|
services.addMockCordapp(DummyContract.PROGRAM_ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Commands {
|
interface Commands {
|
||||||
|
@ -51,7 +51,8 @@ class TransactionEncumbranceTests {
|
|||||||
class DummyTimeLock : Contract {
|
class DummyTimeLock : Contract {
|
||||||
override fun verify(tx: LedgerTransaction) {
|
override fun verify(tx: LedgerTransaction) {
|
||||||
val timeLockInput = tx.inputsOfType<State>().singleOrNull() ?: return
|
val timeLockInput = tx.inputsOfType<State>().singleOrNull() ?: return
|
||||||
val time = tx.timeWindow?.untilTime ?: throw IllegalArgumentException("Transactions containing time-locks must have a time-window")
|
val time = tx.timeWindow?.untilTime
|
||||||
|
?: throw IllegalArgumentException("Transactions containing time-locks must have a time-window")
|
||||||
requireThat {
|
requireThat {
|
||||||
"the time specified in the time-lock has passed" using (time >= timeLockInput.validFrom)
|
"the time specified in the time-lock has passed" using (time >= timeLockInput.validFrom)
|
||||||
}
|
}
|
||||||
@ -64,9 +65,10 @@ class TransactionEncumbranceTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val ledgerServices = MockServices(emptyList(), rigorousMock<IdentityServiceInternal>().also {
|
private val ledgerServices = MockServices(emptyList(), MEGA_CORP.name,
|
||||||
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
|
rigorousMock<IdentityServiceInternal>().also {
|
||||||
}, MEGA_CORP.name)
|
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
|
||||||
|
})
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `state can be encumbered`() {
|
fun `state can be encumbered`() {
|
||||||
|
@ -615,6 +615,24 @@ which the signatures are allowed to be missing:
|
|||||||
:end-before: DOCEND 36
|
:end-before: DOCEND 36
|
||||||
:dedent: 16
|
:dedent: 16
|
||||||
|
|
||||||
|
There is also an overload of ``SignedTransaction.verifySignaturesExcept``, which takes a ``Collection`` of the
|
||||||
|
public keys for which the signatures are allowed to be missing:
|
||||||
|
|
||||||
|
.. container:: codeset
|
||||||
|
|
||||||
|
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
|
||||||
|
:language: kotlin
|
||||||
|
:start-after: DOCSTART 54
|
||||||
|
:end-before: DOCEND 54
|
||||||
|
:dedent: 8
|
||||||
|
|
||||||
|
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
|
||||||
|
:language: java
|
||||||
|
:start-after: DOCSTART 54
|
||||||
|
:end-before: DOCEND 54
|
||||||
|
:dedent: 16
|
||||||
|
|
||||||
|
|
||||||
If the transaction is missing any signatures without the corresponding public keys being passed in, a
|
If the transaction is missing any signatures without the corresponding public keys being passed in, a
|
||||||
``SignaturesMissingException`` is thrown.
|
``SignaturesMissingException`` is thrown.
|
||||||
|
|
||||||
|
@ -7,6 +7,13 @@ from previous releases. Please refer to :doc:`upgrade-notes` for detailed instru
|
|||||||
UNRELEASED
|
UNRELEASED
|
||||||
----------
|
----------
|
||||||
|
|
||||||
|
* Added ``NetworkMapCache.getNodesByLegalName`` for querying nodes belonging to a distributed service such as a notary cluster
|
||||||
|
where they all share a common identity. ``NetworkMapCache.getNodeByLegalName`` has been tightened to throw if more than
|
||||||
|
one node with the legal name is found.
|
||||||
|
|
||||||
|
* Per CorDapp configuration is now exposed. ``CordappContext`` now exposes a ``CordappConfig`` object that is populated
|
||||||
|
at CorDapp context creation time from a file source during runtime.
|
||||||
|
|
||||||
* Introduced Flow Draining mode, in which a node continues executing existing flows, but does not start new. This is to support graceful node shutdown/restarts.
|
* Introduced Flow Draining mode, in which a node continues executing existing flows, but does not start new. This is to support graceful node shutdown/restarts.
|
||||||
In particular, when this mode is on, new flows through RPC will be rejected, scheduled flows will be ignored, and initial session messages will not be consumed.
|
In particular, when this mode is on, new flows through RPC will be rejected, scheduled flows will be ignored, and initial session messages will not be consumed.
|
||||||
This will ensure that the number of checkpoints will strictly diminish with time, allowing for a clean shutdown.
|
This will ensure that the number of checkpoints will strictly diminish with time, allowing for a clean shutdown.
|
||||||
@ -80,7 +87,10 @@ R3 Corda 3.0 Developer Preview
|
|||||||
:doc:`corda-configuration-file` for more details.
|
:doc:`corda-configuration-file` for more details.
|
||||||
|
|
||||||
* Introducing the concept of network parameters which are a set of constants which all nodes on a network must agree on
|
* Introducing the concept of network parameters which are a set of constants which all nodes on a network must agree on
|
||||||
to correctly interop.
|
to correctly interop. These can be retrieved from ``ServiceHub.networkParameters``.
|
||||||
|
|
||||||
|
* One of these parameters, ``maxTransactionSize``, limits the size of a transaction, including its attachments, so that
|
||||||
|
all nodes have sufficient memory to validate transactions.
|
||||||
|
|
||||||
* The set of valid notaries has been moved to the network parameters. Notaries are no longer identified by the CN in
|
* The set of valid notaries has been moved to the network parameters. Notaries are no longer identified by the CN in
|
||||||
their X.500 name.
|
their X.500 name.
|
||||||
@ -93,9 +103,6 @@ R3 Corda 3.0 Developer Preview
|
|||||||
* Moved ``NodeInfoSchema`` to internal package as the node info's database schema is not part of the public API. This
|
* Moved ``NodeInfoSchema`` to internal package as the node info's database schema is not part of the public API. This
|
||||||
was needed to allow changes to the schema.
|
was needed to allow changes to the schema.
|
||||||
|
|
||||||
* Introduced max transaction size limit on transactions. The max transaction size parameter is set by the compatibility zone
|
|
||||||
operator. The parameter is distributed to Corda nodes by network map service as part of the ``NetworkParameters``.
|
|
||||||
|
|
||||||
* Support for external user credentials data source and password encryption [CORDA-827].
|
* Support for external user credentials data source and password encryption [CORDA-827].
|
||||||
|
|
||||||
* Integrate database migration tool: http://www.liquibase.org/ :
|
* Integrate database migration tool: http://www.liquibase.org/ :
|
||||||
@ -224,9 +231,9 @@ R3 Corda 3.0 Developer Preview
|
|||||||
|
|
||||||
* The test utils in ``Expect.kt``, ``SerializationTestHelpers.kt``, ``TestConstants.kt`` and ``TestUtils.kt`` have moved
|
* The test utils in ``Expect.kt``, ``SerializationTestHelpers.kt``, ``TestConstants.kt`` and ``TestUtils.kt`` have moved
|
||||||
from the ``net.corda.testing`` package to the ``net.corda.testing.core`` package, and ``FlowStackSnapshot.kt`` has moved to the
|
from the ``net.corda.testing`` package to the ``net.corda.testing.core`` package, and ``FlowStackSnapshot.kt`` has moved to the
|
||||||
``net.corda.testing.services`` package. Moving items out of the ``net.corda.testing.*`` package will help make it clearer which
|
``net.corda.testing.services`` package. Moving existing classes out of the ``net.corda.testing.*`` package
|
||||||
parts of the api are stable. The bash script ``tools\scripts\update-test-packages.sh`` can be used to smooth the upgrade
|
will help make it clearer which parts of the api are stable. Scripts have been provided to smooth the upgrade
|
||||||
process for existing projects.
|
process for existing projects in the ``tools\scripts`` directory of the Corda repo.
|
||||||
|
|
||||||
.. _changelog_v2:
|
.. _changelog_v2:
|
||||||
|
|
||||||
|
@ -203,8 +203,8 @@ path to the node's base directory.
|
|||||||
:publicKeyFile: Path to the public key file for SSH authentication.
|
:publicKeyFile: Path to the public key file for SSH authentication.
|
||||||
:sshPort: Port to be used for SSH connection, default ``22``.
|
:sshPort: Port to be used for SSH connection, default ``22``.
|
||||||
|
|
||||||
:exportJMXTo: If set to ``http``, will enable JMX metrics reporting via the Jolokia HTTP/JSON agent.
|
:jmxMonitoringHttpPort: If set, will enable JMX metrics reporting via the Jolokia HTTP/JSON agent on the corresponding port.
|
||||||
Default Jolokia access url is http://127.0.0.1:7005/jolokia/
|
Default Jolokia access url is http://127.0.0.1:port/jolokia/
|
||||||
|
|
||||||
:transactionCacheSizeMegaBytes: Optionally specify how much memory should be used for caching of ledger transactions in memory.
|
:transactionCacheSizeMegaBytes: Optionally specify how much memory should be used for caching of ledger transactions in memory.
|
||||||
Otherwise defaults to 8MB plus 5% of all heap memory above 300MB.
|
Otherwise defaults to 8MB plus 5% of all heap memory above 300MB.
|
||||||
|
@ -157,3 +157,20 @@ Installing the CorDapp JAR
|
|||||||
At runtime, nodes will load any CorDapps present in their ``cordapps`` folder. Therefore in order to install a CorDapp on
|
At runtime, nodes will load any CorDapps present in their ``cordapps`` folder. Therefore in order to install a CorDapp on
|
||||||
a node, the CorDapp JAR must be added to the ``<node_dir>/cordapps/`` folder, where ``node_dir`` is the folder in which
|
a node, the CorDapp JAR must be added to the ``<node_dir>/cordapps/`` folder, where ``node_dir`` is the folder in which
|
||||||
the node's JAR and configuration files are stored.
|
the node's JAR and configuration files are stored.
|
||||||
|
|
||||||
|
CorDapp configuration files
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
CorDapp configuration files should be placed in ``<node_dir>/cordapps/config``. The name of the file should match the
|
||||||
|
name of the JAR of the CorDapp (eg; if your CorDapp is called ``hello-0.1.jar`` the config should be ``config/hello-0.1.conf``).
|
||||||
|
|
||||||
|
Config files are currently only available in the `Typesafe/Lightbend <https://github.com/lightbend/config>`_ config format.
|
||||||
|
These files are loaded when a CorDapp context is created and so can change during runtime.
|
||||||
|
|
||||||
|
CorDapp configuration can be accessed from ``CordappContext::config`` whenever a ``CordappContext`` is available.
|
||||||
|
|
||||||
|
There is an example project that demonstrates in ``samples` called ``cordapp-configuration`` and API documentation in
|
||||||
|
<api/kotlin/corda/net.corda.core.cordapp/index.html>`_.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -30,8 +30,8 @@ handling, and ensures the Corda service is run at boot.
|
|||||||
4. (Optional) Download the `Corda webserver jar <http://r3.bintray.com/corda/net/corda/corda-webserver/>`_
|
4. (Optional) Download the `Corda webserver jar <http://r3.bintray.com/corda/net/corda/corda-webserver/>`_
|
||||||
(under ``/VERSION_NUMBER/corda-VERSION_NUMBER.jar``) and place it in ``/opt/corda``
|
(under ``/VERSION_NUMBER/corda-VERSION_NUMBER.jar``) and place it in ``/opt/corda``
|
||||||
|
|
||||||
5. Create a directory called ``plugins`` in ``/opt/corda`` and save your CorDapp jar file to it. Alternatively, download one of
|
5. Create a directory called ``cordapps`` in ``/opt/corda`` and save your CorDapp jar file to it. Alternatively, download one of
|
||||||
our `sample CorDapps <https://www.corda.net/samples/>`_ to the ``plugins`` directory
|
our `sample CorDapps <https://www.corda.net/samples/>`_ to the ``cordapps`` directory
|
||||||
|
|
||||||
6. Save the below as ``/opt/corda/node.conf``. See :doc:`corda-configuration-file` for a description of these options
|
6. Save the below as ``/opt/corda/node.conf``. See :doc:`corda-configuration-file` for a description of these options
|
||||||
|
|
||||||
@ -199,8 +199,8 @@ at boot, and means the Corda service stays running with no users connected to th
|
|||||||
mkdir C:\Corda
|
mkdir C:\Corda
|
||||||
wget http://jcenter.bintray.com/net/corda/corda/VERSION_NUMBER/corda-VERSION_NUMBER.jar -OutFile C:\Corda\corda.jar
|
wget http://jcenter.bintray.com/net/corda/corda/VERSION_NUMBER/corda-VERSION_NUMBER.jar -OutFile C:\Corda\corda.jar
|
||||||
|
|
||||||
2. Create a directory called ``plugins`` in ``/opt/corda`` and save your CorDapp jar file to it. Alternatively,
|
2. Create a directory called ``cordapps`` in ``/opt/corda`` and save your CorDapp jar file to it. Alternatively,
|
||||||
download one of our `sample CorDapps <https://www.corda.net/samples/>`_ to the ``plugins`` directory
|
download one of our `sample CorDapps <https://www.corda.net/samples/>`_ to the ``cordapps`` directory
|
||||||
|
|
||||||
3. Save the below as ``C:\Corda\node.conf``. See :doc:`corda-configuration-file` for a description of these options
|
3. Save the below as ``C:\Corda\node.conf``. See :doc:`corda-configuration-file` for a description of these options
|
||||||
|
|
||||||
@ -282,4 +282,4 @@ You can verify Corda is running by connecting to your RPC port from another host
|
|||||||
``telnet your-hostname.example.com 10002``
|
``telnet your-hostname.example.com 10002``
|
||||||
|
|
||||||
If you receive the message "Escape character is ^]", Corda is running and accessible. Press Ctrl-] and Ctrl-D to exit
|
If you receive the message "Escape character is ^]", Corda is running and accessible. Press Ctrl-] and Ctrl-D to exit
|
||||||
telnet.
|
telnet.
|
||||||
|
@ -18,10 +18,10 @@ import net.corda.node.services.Permissions.Companion.startFlow
|
|||||||
import net.corda.testing.core.*
|
import net.corda.testing.core.*
|
||||||
import net.corda.testing.driver.DriverParameters
|
import net.corda.testing.driver.DriverParameters
|
||||||
import net.corda.testing.driver.driver
|
import net.corda.testing.driver.driver
|
||||||
import net.corda.testing.node.User
|
|
||||||
import net.corda.testing.internal.IntegrationTest
|
import net.corda.testing.internal.IntegrationTest
|
||||||
import net.corda.testing.internal.IntegrationTestSchemas
|
import net.corda.testing.internal.IntegrationTestSchemas
|
||||||
import net.corda.testing.internal.toDatabaseSchemaName
|
import net.corda.testing.internal.toDatabaseSchemaName
|
||||||
|
import net.corda.testing.node.User
|
||||||
import org.junit.ClassRule
|
import org.junit.ClassRule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
@ -540,12 +540,21 @@ public class FlowCookbookJava {
|
|||||||
// DOCEND 35
|
// DOCEND 35
|
||||||
|
|
||||||
// If the transaction is only partially signed, we have to pass in
|
// If the transaction is only partially signed, we have to pass in
|
||||||
// a list of the public keys corresponding to the missing
|
// a vararg of the public keys corresponding to the missing
|
||||||
// signatures, explicitly telling the system not to check them.
|
// signatures, explicitly telling the system not to check them.
|
||||||
// DOCSTART 36
|
// DOCSTART 36
|
||||||
onceSignedTx.verifySignaturesExcept(counterpartyPubKey);
|
onceSignedTx.verifySignaturesExcept(counterpartyPubKey);
|
||||||
// DOCEND 36
|
// DOCEND 36
|
||||||
|
|
||||||
|
// There is also an overload of ``verifySignaturesExcept`` which accepts
|
||||||
|
// a ``Collection`` of the public keys corresponding to the missing
|
||||||
|
// signatures. In the example below, we could also use
|
||||||
|
// ``Arrays.asList(counterpartyPubKey)`` instead of
|
||||||
|
// ``Collections.singletonList(counterpartyPubKey)``.
|
||||||
|
// DOCSTART 54
|
||||||
|
onceSignedTx.verifySignaturesExcept(Collections.singletonList(counterpartyPubKey));
|
||||||
|
// DOCEND 54
|
||||||
|
|
||||||
// We can also choose to only check the signatures that are
|
// We can also choose to only check the signatures that are
|
||||||
// present. BE VERY CAREFUL - this function provides no guarantees
|
// present. BE VERY CAREFUL - this function provides no guarantees
|
||||||
// that the signatures are correct, or that none are missing.
|
// that the signatures are correct, or that none are missing.
|
||||||
|
@ -522,12 +522,19 @@ class InitiatorFlow(val arg1: Boolean, val arg2: Int, private val counterparty:
|
|||||||
// DOCEND 35
|
// DOCEND 35
|
||||||
|
|
||||||
// If the transaction is only partially signed, we have to pass in
|
// If the transaction is only partially signed, we have to pass in
|
||||||
// a list of the public keys corresponding to the missing
|
// a vararg of the public keys corresponding to the missing
|
||||||
// signatures, explicitly telling the system not to check them.
|
// signatures, explicitly telling the system not to check them.
|
||||||
// DOCSTART 36
|
// DOCSTART 36
|
||||||
onceSignedTx.verifySignaturesExcept(counterpartyPubKey)
|
onceSignedTx.verifySignaturesExcept(counterpartyPubKey)
|
||||||
// DOCEND 36
|
// DOCEND 36
|
||||||
|
|
||||||
|
// There is also an overload of ``verifySignaturesExcept`` which accepts
|
||||||
|
// a ``Collection`` of the public keys corresponding to the missing
|
||||||
|
// signatures.
|
||||||
|
// DOCSTART 54
|
||||||
|
onceSignedTx.verifySignaturesExcept(listOf(counterpartyPubKey))
|
||||||
|
// DOCEND 54
|
||||||
|
|
||||||
// We can also choose to only check the signatures that are
|
// We can also choose to only check the signatures that are
|
||||||
// present. BE VERY CAREFUL - this function provides no guarantees
|
// present. BE VERY CAREFUL - this function provides no guarantees
|
||||||
// that the signatures are correct, or that none are missing.
|
// that the signatures are correct, or that none are missing.
|
||||||
|
@ -74,16 +74,14 @@ class CustomVaultQueryTest {
|
|||||||
|
|
||||||
private fun getBalances(): Pair<Map<Currency, Amount<Currency>>, Map<Currency, Amount<Currency>>> {
|
private fun getBalances(): Pair<Map<Currency, Amount<Currency>>, Map<Currency, Amount<Currency>>> {
|
||||||
// Print out the balances
|
// Print out the balances
|
||||||
val balancesNodesA =
|
val balancesNodesA = nodeA.transaction {
|
||||||
nodeA.database.transaction {
|
nodeA.services.getCashBalances()
|
||||||
nodeA.services.getCashBalances()
|
}
|
||||||
}
|
|
||||||
println("BalanceA\n" + balancesNodesA)
|
println("BalanceA\n" + balancesNodesA)
|
||||||
|
|
||||||
val balancesNodesB =
|
val balancesNodesB = nodeB.transaction {
|
||||||
nodeB.database.transaction {
|
nodeB.services.getCashBalances()
|
||||||
nodeB.services.getCashBalances()
|
}
|
||||||
}
|
|
||||||
println("BalanceB\n" + balancesNodesB)
|
println("BalanceB\n" + balancesNodesB)
|
||||||
|
|
||||||
return Pair(balancesNodesA, balancesNodesB)
|
return Pair(balancesNodesA, balancesNodesB)
|
||||||
|
@ -68,13 +68,14 @@ class FxTransactionBuildTutorialTest {
|
|||||||
doIt.getOrThrow()
|
doIt.getOrThrow()
|
||||||
// Get the balances when the vault updates
|
// Get the balances when the vault updates
|
||||||
nodeAVaultUpdate.get()
|
nodeAVaultUpdate.get()
|
||||||
val balancesA = nodeA.database.transaction {
|
val balancesA = nodeA.transaction {
|
||||||
nodeA.services.getCashBalances()
|
nodeA.services.getCashBalances()
|
||||||
}
|
}
|
||||||
nodeBVaultUpdate.get()
|
nodeBVaultUpdate.get()
|
||||||
val balancesB = nodeB.database.transaction {
|
val balancesB = nodeB.transaction {
|
||||||
nodeB.services.getCashBalances()
|
nodeB.services.getCashBalances()
|
||||||
}
|
}
|
||||||
|
|
||||||
println("BalanceA\n" + balancesA)
|
println("BalanceA\n" + balancesA)
|
||||||
println("BalanceB\n" + balancesB)
|
println("BalanceB\n" + balancesB)
|
||||||
// Verify the transfers occurred as expected
|
// Verify the transfers occurred as expected
|
||||||
@ -86,10 +87,10 @@ class FxTransactionBuildTutorialTest {
|
|||||||
|
|
||||||
private fun printBalances() {
|
private fun printBalances() {
|
||||||
// Print out the balances
|
// Print out the balances
|
||||||
nodeA.database.transaction {
|
nodeA.transaction {
|
||||||
println("BalanceA\n" + nodeA.services.getCashBalances())
|
println("BalanceA\n" + nodeA.services.getCashBalances())
|
||||||
}
|
}
|
||||||
nodeB.database.transaction {
|
nodeB.transaction {
|
||||||
println("BalanceB\n" + nodeB.services.getCashBalances())
|
println("BalanceB\n" + nodeB.services.getCashBalances())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,11 +38,11 @@ class CommercialPaperTest {
|
|||||||
@Rule
|
@Rule
|
||||||
@JvmField
|
@JvmField
|
||||||
val testSerialization = SerializationEnvironmentRule()
|
val testSerialization = SerializationEnvironmentRule()
|
||||||
private val ledgerServices = MockServices(emptyList(), rigorousMock<IdentityService>().also {
|
private val ledgerServices = MockServices(emptyList(), MEGA_CORP.name, rigorousMock<IdentityService>().also {
|
||||||
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
|
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
|
||||||
doReturn(null).whenever(it).partyFromKey(BIG_CORP_PUBKEY)
|
doReturn(null).whenever(it).partyFromKey(BIG_CORP_PUBKEY)
|
||||||
doReturn(null).whenever(it).partyFromKey(ALICE_PUBKEY)
|
doReturn(null).whenever(it).partyFromKey(ALICE_PUBKEY)
|
||||||
}, MEGA_CORP.name)
|
})
|
||||||
|
|
||||||
// DOCSTART 1
|
// DOCSTART 1
|
||||||
fun getPaper(): ICommercialPaperState = CommercialPaper.State(
|
fun getPaper(): ICommercialPaperState = CommercialPaper.State(
|
||||||
|
@ -108,7 +108,7 @@ Here are a few ways to build dashboards and extract monitoring data for a node:
|
|||||||
It can bridge any data input to any output using their plugin system, for example, Telegraf can
|
It can bridge any data input to any output using their plugin system, for example, Telegraf can
|
||||||
be configured to collect data from Jolokia and write to DataDog web api.
|
be configured to collect data from Jolokia and write to DataDog web api.
|
||||||
|
|
||||||
The Node configuration parameter `exportJMXTo` should be set to ``http`` to ensure a Jolokia agent is instrumented with
|
The Node configuration parameter `jmxMonitoringHttpPort` has to be present in order to ensure a Jolokia agent is instrumented with
|
||||||
the JVM run-time.
|
the JVM run-time.
|
||||||
|
|
||||||
The following JMX statistics are exported:
|
The following JMX statistics are exported:
|
||||||
|
@ -403,6 +403,51 @@ Providing a public getter, as per the following example, is acceptable:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Mismatched Class Properties / Constructor Parameters
|
||||||
|
````````````````````````````````````````````````````
|
||||||
|
|
||||||
|
Consider an example where you wish to ensure that a property of class whose type is some form of container is always sorted using some specific criteria yet you wish to maintain the immutability of the class.
|
||||||
|
|
||||||
|
This could be codified as follows:
|
||||||
|
|
||||||
|
.. container:: codeset
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
@CordaSerializable
|
||||||
|
class ConfirmRequest(statesToConsume: List<StateRef>, val transactionId: SecureHash) {
|
||||||
|
companion object {
|
||||||
|
private val stateRefComparator = compareBy<StateRef>({ it.txhash }, { it.index })
|
||||||
|
}
|
||||||
|
|
||||||
|
private val states = statesToConsume.sortedWith(stateRefComparator)
|
||||||
|
}
|
||||||
|
|
||||||
|
The intention in the example is to always ensure that the states are stored in a specific order regardless of the ordering
|
||||||
|
of the list used to initialise instances of the class. This is achieved by using the first constructor parameter as the
|
||||||
|
basis for a private member. However, because that member is not mentioned in the constructor (whose parameters determine
|
||||||
|
what is serializable as discussed above) it would not be serialized. In addition, as there is no provided mechanism to retrieve
|
||||||
|
a value for ``statesToConsume`` we would fail to build a serializer for this Class.
|
||||||
|
|
||||||
|
In this case a secondary constructor annotated with ``@ConstructorForDeserialization`` would not be a valid solution as the
|
||||||
|
two signatures would be the same. Best practice is thus to provide a getter for the constructor parameter which explicitly
|
||||||
|
associates it with the actual member variable.
|
||||||
|
|
||||||
|
.. container:: codeset
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
@CordaSerializable
|
||||||
|
class ConfirmRequest(statesToConsume: List<StateRef>, val transactionId: SecureHash) {
|
||||||
|
companion object {
|
||||||
|
private val stateRefComparator = compareBy<StateRef>({ it.txhash }, { it.index })
|
||||||
|
}
|
||||||
|
|
||||||
|
private val states = statesToConsume.sortedWith(stateRefComparator)
|
||||||
|
|
||||||
|
//Explicit "getter" for a property identified from the constructor parameters
|
||||||
|
fun getStatesToConsume() = states
|
||||||
|
}
|
||||||
|
|
||||||
Enums
|
Enums
|
||||||
`````
|
`````
|
||||||
@ -458,5 +503,3 @@ and a version of the current state of the class instantiated.
|
|||||||
|
|
||||||
More detail can be found in :doc:`serialization-default-evolution`.
|
More detail can be found in :doc:`serialization-default-evolution`.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ locally so they are not re-requested if encountered again. Attachments typically
|
|||||||
* Metadata about a transaction, such as PDF version of an invoice being settled
|
* Metadata about a transaction, such as PDF version of an invoice being settled
|
||||||
* Shared information to be permanently recorded on the ledger
|
* Shared information to be permanently recorded on the ledger
|
||||||
|
|
||||||
To add attachments the file must first be added to uploaded to the node, which returns a unique ID that can be added
|
To add attachments the file must first be uploaded to the node, which returns a unique ID that can be added
|
||||||
using ``TransactionBuilder.addAttachment()``. Attachments can be uploaded and downloaded via RPC and the Corda
|
using ``TransactionBuilder.addAttachment()``. Attachments can be uploaded and downloaded via RPC and the Corda
|
||||||
:doc:`shell`.
|
:doc:`shell`.
|
||||||
|
|
||||||
|
58
experimental/behave/README.md
Normal file
58
experimental/behave/README.md
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
# Introduction
|
||||||
|
|
||||||
|
This project illustrates how one can use Cucumber / BDD to drive
|
||||||
|
and test homogeneous and heterogeneous Corda networks on a local
|
||||||
|
machine. The framework has built-in support for Dockerised node
|
||||||
|
dependencies so that you easily can spin up a Corda node locally
|
||||||
|
that, for instance, uses a 3rd party database provider such as
|
||||||
|
MS SQL Server or Postgres.
|
||||||
|
|
||||||
|
# Structure
|
||||||
|
|
||||||
|
The project is split into three pieces:
|
||||||
|
|
||||||
|
* **Testing Library** (main) - This library contains auxiliary
|
||||||
|
functions that help in configuring and bootstrapping Corda
|
||||||
|
networks on a local machine. The purpose of the library is to
|
||||||
|
aid in black-box testing and automation.
|
||||||
|
|
||||||
|
* **Unit Tests** (test) - These are various tests for the
|
||||||
|
library described above. Note that there's only limited
|
||||||
|
coverage for now.
|
||||||
|
|
||||||
|
* **BDD Framework** (scenario) - This module shows how to use
|
||||||
|
BDD-style frameworks to control the testing of Corda networks;
|
||||||
|
more specifically, using [Cucumber](cucumber.io).
|
||||||
|
|
||||||
|
# Setup
|
||||||
|
|
||||||
|
To get started, please follow the instructions below:
|
||||||
|
|
||||||
|
* Go up to the root directory and build the capsule JAR.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ cd ../../
|
||||||
|
$ ./gradlew install
|
||||||
|
```
|
||||||
|
|
||||||
|
* Come back to this folder and run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ cd experimental/behave
|
||||||
|
$ ./prepare.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
This script will download necessary database drivers and set up
|
||||||
|
the dependencies directory with copies of the Corda fat-JAR and
|
||||||
|
the network bootstrapping tool.
|
||||||
|
|
||||||
|
# Selective Runs
|
||||||
|
|
||||||
|
If you only want to run tests of a specific tag, you can append
|
||||||
|
the following parameter to the Gradle command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ ../../gradlew scenario -Ptags="@cash"
|
||||||
|
# or
|
||||||
|
$ ../../gradlew scenario -Ptags="@cash,@logging"
|
||||||
|
```
|
119
experimental/behave/build.gradle
Normal file
119
experimental/behave/build.gradle
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
buildscript {
|
||||||
|
ext.commonsio_version = '2.6'
|
||||||
|
ext.commonslogging_version = '1.2'
|
||||||
|
ext.cucumber_version = '1.2.5'
|
||||||
|
ext.crash_version = 'cce5a00f114343c1145c1d7756e1dd6df3ea984e'
|
||||||
|
ext.docker_client_version = '8.11.0'
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
maven {
|
||||||
|
jcenter()
|
||||||
|
url 'https://jitpack.io'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
group 'net.corda.behave'
|
||||||
|
|
||||||
|
apply plugin: 'java'
|
||||||
|
apply plugin: 'kotlin'
|
||||||
|
|
||||||
|
sourceCompatibility = 1.8
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
scenario {
|
||||||
|
java {
|
||||||
|
compileClasspath += main.output
|
||||||
|
runtimeClasspath += main.output
|
||||||
|
srcDir file('src/scenario/kotlin')
|
||||||
|
}
|
||||||
|
resources.srcDir file('src/scenario/resources')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
scenarioCompile.extendsFrom testCompile
|
||||||
|
scenarioRuntime.extendsFrom testRuntime
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
|
||||||
|
// Library
|
||||||
|
|
||||||
|
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
|
||||||
|
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||||
|
|
||||||
|
compile("com.github.corda.crash:crash.shell:$crash_version") {
|
||||||
|
exclude group: "org.slf4j", module: "slf4j-jdk14"
|
||||||
|
exclude group: "org.bouncycastle"
|
||||||
|
}
|
||||||
|
|
||||||
|
compile("com.github.corda.crash:crash.connectors.ssh:$crash_version") {
|
||||||
|
exclude group: "org.slf4j", module: "slf4j-jdk14"
|
||||||
|
exclude group: "org.bouncycastle"
|
||||||
|
}
|
||||||
|
|
||||||
|
compile "org.slf4j:log4j-over-slf4j:$slf4j_version"
|
||||||
|
compile "org.slf4j:jul-to-slf4j:$slf4j_version"
|
||||||
|
compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
|
||||||
|
compile "org.apache.logging.log4j:log4j-core:$log4j_version"
|
||||||
|
|
||||||
|
compile "commons-io:commons-io:$commonsio_version"
|
||||||
|
compile "commons-logging:commons-logging:$commonslogging_version"
|
||||||
|
compile "com.spotify:docker-client:$docker_client_version"
|
||||||
|
compile "io.reactivex:rxjava:$rxjava_version"
|
||||||
|
|
||||||
|
compile project(':finance')
|
||||||
|
compile project(':node-api')
|
||||||
|
compile project(':client:rpc')
|
||||||
|
|
||||||
|
// Unit Tests
|
||||||
|
|
||||||
|
testCompile "junit:junit:$junit_version"
|
||||||
|
testCompile "org.assertj:assertj-core:$assertj_version"
|
||||||
|
|
||||||
|
// Scenarios / End-to-End Tests
|
||||||
|
|
||||||
|
scenarioCompile "info.cukes:cucumber-java8:$cucumber_version"
|
||||||
|
scenarioCompile "info.cukes:cucumber-junit:$cucumber_version"
|
||||||
|
scenarioCompile "info.cukes:cucumber-picocontainer:$cucumber_version"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
compileKotlin {
|
||||||
|
kotlinOptions.jvmTarget = "1.8"
|
||||||
|
}
|
||||||
|
|
||||||
|
compileTestKotlin {
|
||||||
|
kotlinOptions.jvmTarget = "1.8"
|
||||||
|
}
|
||||||
|
|
||||||
|
compileScenarioKotlin {
|
||||||
|
kotlinOptions.jvmTarget = "1.8"
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
testLogging.showStandardStreams = true
|
||||||
|
}
|
||||||
|
|
||||||
|
task scenarios(type: Test) {
|
||||||
|
setTestClassesDirs sourceSets.scenario.output.getClassesDirs()
|
||||||
|
classpath = sourceSets.scenario.runtimeClasspath
|
||||||
|
outputs.upToDateWhen { false }
|
||||||
|
|
||||||
|
if (project.hasProperty("tags")) {
|
||||||
|
systemProperty "cucumber.options", "--tags $tags"
|
||||||
|
logger.warn("Only running tests tagged with: $tags ...")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//scenarios.mustRunAfter test
|
||||||
|
//scenarios.dependsOn test
|
1
experimental/behave/deps/.gitignore
vendored
Normal file
1
experimental/behave/deps/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
*.jar
|
0
experimental/behave/deps/corda/3.0.0/.gitkeep
Normal file
0
experimental/behave/deps/corda/3.0.0/.gitkeep
Normal file
0
experimental/behave/deps/corda/3.0.0/apps/.gitkeep
Normal file
0
experimental/behave/deps/corda/3.0.0/apps/.gitkeep
Normal file
0
experimental/behave/deps/drivers/.gitkeep
Normal file
0
experimental/behave/deps/drivers/.gitkeep
Normal file
3
experimental/behave/deps/drivers/README.md
Normal file
3
experimental/behave/deps/drivers/README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
Download and store database drivers here; for example:
|
||||||
|
- h2-1.4.196.jar
|
||||||
|
- mssql-jdbc-6.2.2.jre8.jar
|
24
experimental/behave/prepare.sh
Executable file
24
experimental/behave/prepare.sh
Executable file
@ -0,0 +1,24 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
VERSION=3.0.0
|
||||||
|
|
||||||
|
# Set up directories
|
||||||
|
mkdir -p deps/corda/${VERSION}/apps
|
||||||
|
mkdir -p deps/drivers
|
||||||
|
|
||||||
|
# Copy Corda capsule into deps
|
||||||
|
cp -v $(ls ../../node/capsule/build/libs/corda-*.jar | tail -n1) deps/corda/${VERSION}/corda.jar
|
||||||
|
|
||||||
|
# Download database drivers
|
||||||
|
curl "https://search.maven.org/remotecontent?filepath=com/h2database/h2/1.4.196/h2-1.4.196.jar" > deps/drivers/h2-1.4.196.jar
|
||||||
|
curl -L "https://github.com/Microsoft/mssql-jdbc/releases/download/v6.2.2/mssql-jdbc-6.2.2.jre8.jar" > deps/drivers/mssql-jdbc-6.2.2.jre8.jar
|
||||||
|
|
||||||
|
# Build required artefacts
|
||||||
|
cd ../..
|
||||||
|
./gradlew buildBootstrapperJar
|
||||||
|
./gradlew :finance:jar
|
||||||
|
|
||||||
|
# Copy build artefacts into deps
|
||||||
|
cd experimental/behave
|
||||||
|
cp -v $(ls ../../tools/bootstrapper/build/libs/*.jar | tail -n1) deps/corda/${VERSION}/network-bootstrapper.jar
|
||||||
|
cp -v $(ls ../../finance/build/libs/corda-finance-*.jar | tail -n1) deps/corda/${VERSION}/apps/corda-finance.jar
|
@ -0,0 +1,28 @@
|
|||||||
|
package net.corda.behave
|
||||||
|
|
||||||
|
import java.time.Duration
|
||||||
|
import java.util.concurrent.CountDownLatch
|
||||||
|
|
||||||
|
val Int.millisecond: Duration
|
||||||
|
get() = Duration.ofMillis(this.toLong())
|
||||||
|
|
||||||
|
val Int.milliseconds: Duration
|
||||||
|
get() = Duration.ofMillis(this.toLong())
|
||||||
|
|
||||||
|
val Int.second: Duration
|
||||||
|
get() = Duration.ofSeconds(this.toLong())
|
||||||
|
|
||||||
|
val Int.seconds: Duration
|
||||||
|
get() = Duration.ofSeconds(this.toLong())
|
||||||
|
|
||||||
|
val Int.minute: Duration
|
||||||
|
get() = Duration.ofMinutes(this.toLong())
|
||||||
|
|
||||||
|
val Int.minutes: Duration
|
||||||
|
get() = Duration.ofMinutes(this.toLong())
|
||||||
|
|
||||||
|
fun CountDownLatch.await(duration: Duration) =
|
||||||
|
this.await(duration.toMillis(), java.util.concurrent.TimeUnit.MILLISECONDS)
|
||||||
|
|
||||||
|
fun Process.waitFor(duration: Duration) =
|
||||||
|
this.waitFor(duration.toMillis(), java.util.concurrent.TimeUnit.MILLISECONDS)
|
@ -0,0 +1,13 @@
|
|||||||
|
package net.corda.behave.database
|
||||||
|
|
||||||
|
import net.corda.behave.node.configuration.DatabaseConfiguration
|
||||||
|
|
||||||
|
open class DatabaseConfigurationTemplate {
|
||||||
|
|
||||||
|
open val connectionString: (DatabaseConfiguration) -> String = { "" }
|
||||||
|
|
||||||
|
protected open val config: (DatabaseConfiguration) -> String = { "" }
|
||||||
|
|
||||||
|
fun generate(config: DatabaseConfiguration) = config(config).trimMargin()
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,85 @@
|
|||||||
|
package net.corda.behave.database
|
||||||
|
|
||||||
|
import net.corda.behave.node.configuration.DatabaseConfiguration
|
||||||
|
import java.io.Closeable
|
||||||
|
import java.sql.*
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class DatabaseConnection(
|
||||||
|
private val config: DatabaseConfiguration,
|
||||||
|
template: DatabaseConfigurationTemplate
|
||||||
|
) : Closeable {
|
||||||
|
|
||||||
|
private val connectionString = template.connectionString(config)
|
||||||
|
|
||||||
|
private var conn: Connection? = null
|
||||||
|
|
||||||
|
fun open(): Connection {
|
||||||
|
try {
|
||||||
|
val connectionProps = Properties()
|
||||||
|
connectionProps.put("user", config.username)
|
||||||
|
connectionProps.put("password", config.password)
|
||||||
|
retry (5) {
|
||||||
|
conn = DriverManager.getConnection(connectionString, connectionProps)
|
||||||
|
}
|
||||||
|
return conn ?: throw Exception("Unable to open connection")
|
||||||
|
} catch (ex: SQLException) {
|
||||||
|
throw Exception("An error occurred whilst connecting to \"$connectionString\". " +
|
||||||
|
"Maybe the user and/or password is invalid?", ex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
val connection = conn
|
||||||
|
if (connection != null) {
|
||||||
|
try {
|
||||||
|
conn = null
|
||||||
|
connection.close()
|
||||||
|
} catch (ex: SQLException) {
|
||||||
|
throw Exception("Failed to close database connection to \"$connectionString\"", ex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun query(conn: Connection?, stmt: String? = null) {
|
||||||
|
var statement: Statement? = null
|
||||||
|
val resultset: ResultSet?
|
||||||
|
try {
|
||||||
|
statement = conn?.prepareStatement(stmt
|
||||||
|
?: "SELECT name FROM sys.tables WHERE name = ?")
|
||||||
|
statement?.setString(1, "Test")
|
||||||
|
resultset = statement?.executeQuery()
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (resultset?.next() == true) {
|
||||||
|
val name = resultset.getString("name")
|
||||||
|
println(name)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
} finally {
|
||||||
|
resultset?.close()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
} finally {
|
||||||
|
statement?.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun retry(numberOfTimes: Int, action: () -> Unit) {
|
||||||
|
var i = numberOfTimes
|
||||||
|
while (numberOfTimes > 0) {
|
||||||
|
Thread.sleep(2000)
|
||||||
|
try {
|
||||||
|
action()
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
if (i == 1) {
|
||||||
|
throw ex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i -= 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
package net.corda.behave.database
|
||||||
|
|
||||||
|
import net.corda.behave.node.configuration.Configuration
|
||||||
|
import net.corda.behave.node.configuration.DatabaseConfiguration
|
||||||
|
import net.corda.behave.service.Service
|
||||||
|
import net.corda.behave.service.ServiceInitiator
|
||||||
|
|
||||||
|
class DatabaseSettings {
|
||||||
|
|
||||||
|
var databaseName: String = "node"
|
||||||
|
private set
|
||||||
|
|
||||||
|
var schemaName: String = "dbo"
|
||||||
|
private set
|
||||||
|
|
||||||
|
var userName: String = "sa"
|
||||||
|
private set
|
||||||
|
|
||||||
|
private var databaseConfigTemplate: DatabaseConfigurationTemplate = DatabaseConfigurationTemplate()
|
||||||
|
|
||||||
|
private val serviceInitiators = mutableListOf<ServiceInitiator>()
|
||||||
|
|
||||||
|
fun withDatabase(name: String): DatabaseSettings {
|
||||||
|
databaseName = name
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun withSchema(name: String): DatabaseSettings {
|
||||||
|
schemaName = name
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun withUser(name: String): DatabaseSettings {
|
||||||
|
userName = name
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun withServiceInitiator(initiator: ServiceInitiator): DatabaseSettings {
|
||||||
|
serviceInitiators.add(initiator)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun withConfigTemplate(configTemplate: DatabaseConfigurationTemplate): DatabaseSettings {
|
||||||
|
databaseConfigTemplate = configTemplate
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun config(config: DatabaseConfiguration): String {
|
||||||
|
return databaseConfigTemplate.generate(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dependencies(config: Configuration): List<Service> {
|
||||||
|
return serviceInitiators.map { it(config) }
|
||||||
|
}
|
||||||
|
|
||||||
|
val template: DatabaseConfigurationTemplate
|
||||||
|
get() = databaseConfigTemplate
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
package net.corda.behave.database
|
||||||
|
|
||||||
|
import net.corda.behave.database.configuration.H2ConfigurationTemplate
|
||||||
|
import net.corda.behave.database.configuration.SqlServerConfigurationTemplate
|
||||||
|
import net.corda.behave.node.configuration.Configuration
|
||||||
|
import net.corda.behave.node.configuration.DatabaseConfiguration
|
||||||
|
import net.corda.behave.service.database.H2Service
|
||||||
|
import net.corda.behave.service.database.SqlServerService
|
||||||
|
|
||||||
|
enum class DatabaseType(val settings: DatabaseSettings) {
|
||||||
|
|
||||||
|
H2(DatabaseSettings()
|
||||||
|
.withDatabase(H2Service.database)
|
||||||
|
.withSchema(H2Service.schema)
|
||||||
|
.withUser(H2Service.username)
|
||||||
|
.withConfigTemplate(H2ConfigurationTemplate())
|
||||||
|
.withServiceInitiator {
|
||||||
|
H2Service("h2-${it.name}", it.database.port)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
|
||||||
|
SQL_SERVER(DatabaseSettings()
|
||||||
|
.withDatabase(SqlServerService.database)
|
||||||
|
.withSchema(SqlServerService.schema)
|
||||||
|
.withUser(SqlServerService.username)
|
||||||
|
.withConfigTemplate(SqlServerConfigurationTemplate())
|
||||||
|
.withServiceInitiator {
|
||||||
|
SqlServerService("sqlserver-${it.name}", it.database.port, it.database.password)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
fun dependencies(config: Configuration) = settings.dependencies(config)
|
||||||
|
|
||||||
|
fun connection(config: DatabaseConfiguration) = DatabaseConnection(config, settings.template)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun fromName(name: String): DatabaseType? = when (name.toLowerCase()) {
|
||||||
|
"h2" -> H2
|
||||||
|
"sql_server" -> SQL_SERVER
|
||||||
|
"sql server" -> SQL_SERVER
|
||||||
|
"sqlserver" -> SQL_SERVER
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package net.corda.behave.database.configuration
|
||||||
|
|
||||||
|
import net.corda.behave.database.DatabaseConfigurationTemplate
|
||||||
|
import net.corda.behave.node.configuration.DatabaseConfiguration
|
||||||
|
|
||||||
|
class H2ConfigurationTemplate : DatabaseConfigurationTemplate() {
|
||||||
|
|
||||||
|
override val connectionString: (DatabaseConfiguration) -> String
|
||||||
|
get() = { "jdbc:h2:tcp://${it.host}:${it.port}/${it.database}" }
|
||||||
|
|
||||||
|
override val config: (DatabaseConfiguration) -> String
|
||||||
|
get() = {
|
||||||
|
"""
|
||||||
|
|h2port=${it.port}
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package net.corda.behave.database.configuration
|
||||||
|
|
||||||
|
import net.corda.behave.database.DatabaseConfigurationTemplate
|
||||||
|
import net.corda.behave.node.configuration.DatabaseConfiguration
|
||||||
|
|
||||||
|
class SqlServerConfigurationTemplate : DatabaseConfigurationTemplate() {
|
||||||
|
|
||||||
|
override val connectionString: (DatabaseConfiguration) -> String
|
||||||
|
get() = { "jdbc:sqlserver://${it.host}:${it.port};database=${it.database}" }
|
||||||
|
|
||||||
|
override val config: (DatabaseConfiguration) -> String
|
||||||
|
get() = {
|
||||||
|
"""
|
||||||
|
|dataSourceProperties = {
|
||||||
|
| dataSourceClassName = "com.microsoft.sqlserver.jdbc.SQLServerDataSource"
|
||||||
|
| dataSource.url = "${connectionString(it)}"
|
||||||
|
| dataSource.user = "${it.username}"
|
||||||
|
| dataSource.password = "${it.password}"
|
||||||
|
|}
|
||||||
|
|database = {
|
||||||
|
| initialiseSchema=true
|
||||||
|
| transactionIsolationLevel = READ_COMMITTED
|
||||||
|
| schema="${it.schema}"
|
||||||
|
|}
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package net.corda.behave.file
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
val currentDirectory: File
|
||||||
|
get() = File(System.getProperty("user.dir"))
|
||||||
|
|
||||||
|
operator fun File.div(relative: String): File = this.resolve(relative)
|
@ -0,0 +1,42 @@
|
|||||||
|
package net.corda.behave.file
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class LogSource(
|
||||||
|
private val directory: File,
|
||||||
|
filePattern: String? = ".*\\.log",
|
||||||
|
private val filePatternUsedForExclusion: Boolean = false
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val fileRegex = Regex(filePattern ?: ".*")
|
||||||
|
|
||||||
|
data class MatchedLogContent(
|
||||||
|
val filename: File,
|
||||||
|
val contents: String
|
||||||
|
)
|
||||||
|
|
||||||
|
fun find(pattern: String? = null): List<MatchedLogContent> {
|
||||||
|
val regex = if (pattern != null) {
|
||||||
|
Regex(pattern)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
val logFiles = directory.listFiles({ file ->
|
||||||
|
(!filePatternUsedForExclusion && file.name.matches(fileRegex)) ||
|
||||||
|
(filePatternUsedForExclusion && !file.name.matches(fileRegex))
|
||||||
|
})
|
||||||
|
val result = mutableListOf<MatchedLogContent>()
|
||||||
|
for (file in logFiles) {
|
||||||
|
val contents = file.readText()
|
||||||
|
if (regex != null) {
|
||||||
|
result.addAll(regex.findAll(contents).map { match ->
|
||||||
|
MatchedLogContent(file, match.value)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
result.add(MatchedLogContent(file, contents))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package net.corda.behave.logging
|
||||||
|
|
||||||
|
import org.slf4j.Logger
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
|
inline fun <reified T> getLogger(): Logger =
|
||||||
|
LoggerFactory.getLogger(T::class.java)
|
@ -0,0 +1,23 @@
|
|||||||
|
package net.corda.behave.monitoring
|
||||||
|
|
||||||
|
import net.corda.behave.await
|
||||||
|
import rx.Observable
|
||||||
|
import java.time.Duration
|
||||||
|
import java.util.concurrent.CountDownLatch
|
||||||
|
|
||||||
|
class ConjunctiveWatch(
|
||||||
|
private val left: Watch,
|
||||||
|
private val right: Watch
|
||||||
|
) : Watch() {
|
||||||
|
|
||||||
|
override fun await(observable: Observable<String>, timeout: Duration): Boolean {
|
||||||
|
val latch = CountDownLatch(2)
|
||||||
|
listOf(left, right).parallelStream().forEach {
|
||||||
|
if (it.await(observable, timeout)) {
|
||||||
|
latch.countDown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return latch.await(timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package net.corda.behave.monitoring
|
||||||
|
|
||||||
|
import net.corda.behave.await
|
||||||
|
import rx.Observable
|
||||||
|
import java.time.Duration
|
||||||
|
import java.util.concurrent.CountDownLatch
|
||||||
|
|
||||||
|
class DisjunctiveWatch(
|
||||||
|
private val left: Watch,
|
||||||
|
private val right: Watch
|
||||||
|
) : Watch() {
|
||||||
|
|
||||||
|
override fun await(observable: Observable<String>, timeout: Duration): Boolean {
|
||||||
|
val latch = CountDownLatch(1)
|
||||||
|
listOf(left, right).parallelStream().forEach {
|
||||||
|
if (it.await(observable, timeout)) {
|
||||||
|
latch.countDown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return latch.await(timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
|||||||
|
package net.corda.behave.monitoring
|
||||||
|
|
||||||
|
class PatternWatch(
|
||||||
|
pattern: String,
|
||||||
|
ignoreCase: Boolean = false
|
||||||
|
) : Watch() {
|
||||||
|
|
||||||
|
private val regularExpression = if (ignoreCase) {
|
||||||
|
Regex("^.*$pattern.*$", RegexOption.IGNORE_CASE)
|
||||||
|
} else {
|
||||||
|
Regex("^.*$pattern.*$")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun match(data: String) = regularExpression.matches(data.trim())
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
val EMPTY = PatternWatch("")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package net.corda.behave.monitoring
|
||||||
|
|
||||||
|
import net.corda.behave.await
|
||||||
|
import net.corda.behave.seconds
|
||||||
|
import rx.Observable
|
||||||
|
import java.time.Duration
|
||||||
|
import java.util.concurrent.CountDownLatch
|
||||||
|
|
||||||
|
abstract class Watch {
|
||||||
|
|
||||||
|
private val latch = CountDownLatch(1)
|
||||||
|
|
||||||
|
open fun await(
|
||||||
|
observable: Observable<String>,
|
||||||
|
timeout: Duration = 10.seconds
|
||||||
|
): Boolean {
|
||||||
|
observable
|
||||||
|
.filter { match(it) }
|
||||||
|
.forEach { latch.countDown() }
|
||||||
|
return latch.await(timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun match(data: String): Boolean = false
|
||||||
|
|
||||||
|
operator fun times(other: Watch): Watch {
|
||||||
|
return ConjunctiveWatch(this, other)
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun div(other: Watch): Watch {
|
||||||
|
return DisjunctiveWatch(this, other)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,301 @@
|
|||||||
|
package net.corda.behave.network
|
||||||
|
|
||||||
|
import net.corda.behave.database.DatabaseType
|
||||||
|
import net.corda.behave.file.LogSource
|
||||||
|
import net.corda.behave.file.currentDirectory
|
||||||
|
import net.corda.behave.file.div
|
||||||
|
import net.corda.behave.logging.getLogger
|
||||||
|
import net.corda.behave.minutes
|
||||||
|
import net.corda.behave.node.Distribution
|
||||||
|
import net.corda.behave.node.Node
|
||||||
|
import net.corda.behave.node.configuration.NotaryType
|
||||||
|
import net.corda.behave.process.JarCommand
|
||||||
|
import org.apache.commons.io.FileUtils
|
||||||
|
import java.io.Closeable
|
||||||
|
import java.io.File
|
||||||
|
import java.time.Duration
|
||||||
|
import java.time.Instant
|
||||||
|
import java.time.ZoneId
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
import java.util.concurrent.CountDownLatch
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
class Network private constructor(
|
||||||
|
private val nodes: Map<String, Node>,
|
||||||
|
private val targetDirectory: File,
|
||||||
|
private val timeout: Duration = 2.minutes
|
||||||
|
) : Closeable, Iterable<Node> {
|
||||||
|
|
||||||
|
private val log = getLogger<Network>()
|
||||||
|
|
||||||
|
private val latch = CountDownLatch(1)
|
||||||
|
|
||||||
|
private var isRunning = false
|
||||||
|
|
||||||
|
private var isStopped = false
|
||||||
|
|
||||||
|
private var hasError = false
|
||||||
|
|
||||||
|
class Builder internal constructor(
|
||||||
|
private val timeout: Duration
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val nodes = mutableMapOf<String, Node>()
|
||||||
|
|
||||||
|
private val startTime = DateTimeFormatter
|
||||||
|
.ofPattern("yyyyMMDD-HHmmss")
|
||||||
|
.withZone(ZoneId.of("UTC"))
|
||||||
|
.format(Instant.now())
|
||||||
|
|
||||||
|
private val directory = currentDirectory / "build/runs/$startTime"
|
||||||
|
|
||||||
|
fun addNode(
|
||||||
|
name: String,
|
||||||
|
distribution: Distribution = Distribution.LATEST_MASTER,
|
||||||
|
databaseType: DatabaseType = DatabaseType.H2,
|
||||||
|
notaryType: NotaryType = NotaryType.NONE,
|
||||||
|
issuableCurrencies: List<String> = emptyList()
|
||||||
|
): Builder {
|
||||||
|
return addNode(Node.new()
|
||||||
|
.withName(name)
|
||||||
|
.withDistribution(distribution)
|
||||||
|
.withDatabaseType(databaseType)
|
||||||
|
.withNotaryType(notaryType)
|
||||||
|
.withIssuableCurrencies(*issuableCurrencies.toTypedArray())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addNode(nodeBuilder: Node.Builder): Builder {
|
||||||
|
nodeBuilder
|
||||||
|
.withDirectory(directory)
|
||||||
|
.withTimeout(timeout)
|
||||||
|
val node = nodeBuilder.build()
|
||||||
|
nodes[node.config.name] = node
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun generate(): Network {
|
||||||
|
val network = Network(nodes, directory, timeout)
|
||||||
|
network.bootstrapNetwork()
|
||||||
|
return network
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun copyDatabaseDrivers() {
|
||||||
|
val driverDirectory = targetDirectory / "libs"
|
||||||
|
FileUtils.forceMkdir(driverDirectory)
|
||||||
|
FileUtils.copyDirectory(
|
||||||
|
currentDirectory / "deps/drivers",
|
||||||
|
driverDirectory
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun configureNodes(): Boolean {
|
||||||
|
var allDependenciesStarted = true
|
||||||
|
log.info("Configuring nodes ...")
|
||||||
|
for (node in nodes.values) {
|
||||||
|
node.configure()
|
||||||
|
if (!node.startDependencies()) {
|
||||||
|
allDependenciesStarted = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return if (allDependenciesStarted) {
|
||||||
|
log.info("Nodes configured")
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bootstrapNetwork() {
|
||||||
|
copyDatabaseDrivers()
|
||||||
|
if (!configureNodes()) {
|
||||||
|
hasError = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val bootstrapper = nodes.values
|
||||||
|
.sortedByDescending { it.config.distribution.version }
|
||||||
|
.first()
|
||||||
|
.config.distribution.networkBootstrapper
|
||||||
|
|
||||||
|
if (!bootstrapper.exists()) {
|
||||||
|
log.warn("Network bootstrapping tool does not exist; continuing ...")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("Bootstrapping network, please wait ...")
|
||||||
|
val command = JarCommand(
|
||||||
|
bootstrapper,
|
||||||
|
arrayOf("$targetDirectory"),
|
||||||
|
targetDirectory,
|
||||||
|
timeout
|
||||||
|
)
|
||||||
|
log.info("Running command: {}", command)
|
||||||
|
command.output.subscribe {
|
||||||
|
if (it.contains("Exception")) {
|
||||||
|
log.warn("Found error in output; interrupting bootstrapping action ...\n{}", it)
|
||||||
|
command.interrupt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
command.start()
|
||||||
|
if (!command.waitFor()) {
|
||||||
|
hasError = true
|
||||||
|
error("Failed to bootstrap network") {
|
||||||
|
val matches = LogSource(targetDirectory)
|
||||||
|
.find(".*[Ee]xception.*")
|
||||||
|
.groupBy { it.filename.absolutePath }
|
||||||
|
for (match in matches) {
|
||||||
|
log.info("Log(${match.key}):\n${match.value.joinToString("\n") { it.contents }}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.info("Network set-up completed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun cleanup() {
|
||||||
|
try {
|
||||||
|
if (!hasError || CLEANUP_ON_ERROR) {
|
||||||
|
log.info("Cleaning up runtime ...")
|
||||||
|
FileUtils.deleteDirectory(targetDirectory)
|
||||||
|
} else {
|
||||||
|
log.info("Deleting temporary files, but retaining logs and config ...")
|
||||||
|
for (node in nodes.values.map { it.config.name }) {
|
||||||
|
val nodeFolder = targetDirectory / node
|
||||||
|
FileUtils.deleteDirectory(nodeFolder / "additional-node-infos")
|
||||||
|
FileUtils.deleteDirectory(nodeFolder / "artemis")
|
||||||
|
FileUtils.deleteDirectory(nodeFolder / "certificates")
|
||||||
|
FileUtils.deleteDirectory(nodeFolder / "cordapps")
|
||||||
|
FileUtils.deleteDirectory(nodeFolder / "shell-commands")
|
||||||
|
FileUtils.deleteDirectory(nodeFolder / "sshkey")
|
||||||
|
FileUtils.deleteQuietly(nodeFolder / "corda.jar")
|
||||||
|
FileUtils.deleteQuietly(nodeFolder / "network-parameters")
|
||||||
|
FileUtils.deleteQuietly(nodeFolder / "persistence.mv.db")
|
||||||
|
FileUtils.deleteQuietly(nodeFolder / "process-id")
|
||||||
|
|
||||||
|
for (nodeInfo in nodeFolder.listFiles({
|
||||||
|
file -> file.name.matches(Regex("nodeInfo-.*"))
|
||||||
|
})) {
|
||||||
|
FileUtils.deleteQuietly(nodeInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FileUtils.deleteDirectory(targetDirectory / "libs")
|
||||||
|
FileUtils.deleteDirectory(targetDirectory / ".cache")
|
||||||
|
}
|
||||||
|
log.info("Network was shut down successfully")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
log.warn("Failed to cleanup runtime environment")
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun error(message: String, ex: Throwable? = null, action: (() -> Unit)? = null) {
|
||||||
|
hasError = true
|
||||||
|
log.warn(message, ex)
|
||||||
|
action?.invoke()
|
||||||
|
stop()
|
||||||
|
throw Exception(message, ex)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun start() {
|
||||||
|
if (isRunning || hasError) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isRunning = true
|
||||||
|
for (node in nodes.values) {
|
||||||
|
node.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun waitUntilRunning(waitDuration: Duration? = null): Boolean {
|
||||||
|
if (hasError) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
var failedNodes = 0
|
||||||
|
val nodesLatch = CountDownLatch(nodes.size)
|
||||||
|
nodes.values.parallelStream().forEach {
|
||||||
|
if (!it.waitUntilRunning(waitDuration ?: timeout)) {
|
||||||
|
failedNodes += 1
|
||||||
|
}
|
||||||
|
nodesLatch.countDown()
|
||||||
|
}
|
||||||
|
nodesLatch.await()
|
||||||
|
return if (failedNodes > 0) {
|
||||||
|
error("$failedNodes node(s) did not start up as expected within the given time frame") {
|
||||||
|
signal()
|
||||||
|
keepAlive(timeout)
|
||||||
|
}
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
log.info("All nodes are running")
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun signalFailure(message: String?, ex: Throwable? = null) {
|
||||||
|
error(message ?: "Signaling error to network ...", ex) {
|
||||||
|
signal()
|
||||||
|
keepAlive(timeout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun signal() {
|
||||||
|
log.info("Sending termination signal ...")
|
||||||
|
latch.countDown()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun keepAlive(timeout: Duration) {
|
||||||
|
val secs = timeout.seconds
|
||||||
|
log.info("Waiting for up to {} second(s) for termination signal ...", secs)
|
||||||
|
val wasSignalled = latch.await(secs, TimeUnit.SECONDS)
|
||||||
|
log.info(if (wasSignalled) {
|
||||||
|
"Received termination signal"
|
||||||
|
} else {
|
||||||
|
"Timed out. No termination signal received during wait period"
|
||||||
|
})
|
||||||
|
stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stop() {
|
||||||
|
if (isStopped) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.info("Shutting down network ...")
|
||||||
|
isStopped = true
|
||||||
|
for (node in nodes.values) {
|
||||||
|
node.shutDown()
|
||||||
|
}
|
||||||
|
cleanup()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun use(action: (Network) -> Unit) {
|
||||||
|
this.start()
|
||||||
|
action(this)
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun iterator(): Iterator<Node> {
|
||||||
|
return nodes.values.iterator()
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun get(nodeName: String): Node? {
|
||||||
|
return nodes[nodeName]
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
const val CLEANUP_ON_ERROR = false
|
||||||
|
|
||||||
|
fun new(
|
||||||
|
timeout: Duration = 2.minutes
|
||||||
|
): Builder = Builder(timeout)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,116 @@
|
|||||||
|
package net.corda.behave.node
|
||||||
|
|
||||||
|
import net.corda.behave.file.div
|
||||||
|
import org.apache.commons.io.FileUtils
|
||||||
|
import java.io.File
|
||||||
|
import java.net.URL
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Corda distribution.
|
||||||
|
*/
|
||||||
|
class Distribution private constructor(
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The version string of the Corda distribution.
|
||||||
|
*/
|
||||||
|
val version: String,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The path of the distribution fat JAR on disk, if available.
|
||||||
|
*/
|
||||||
|
file: File? = null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The URL of the distribution fat JAR, if available.
|
||||||
|
*/
|
||||||
|
val url: URL? = null
|
||||||
|
|
||||||
|
) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The path to the distribution fat JAR.
|
||||||
|
*/
|
||||||
|
val jarFile: File = file ?: nodePrefix / "$version/corda.jar"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The path to available Cordapps for this distribution.
|
||||||
|
*/
|
||||||
|
val cordappDirectory: File = nodePrefix / "$version/apps"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The path to network bootstrapping tool.
|
||||||
|
*/
|
||||||
|
val networkBootstrapper: File = nodePrefix / "$version/network-bootstrapper.jar"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure that the distribution is available on disk.
|
||||||
|
*/
|
||||||
|
fun ensureAvailable() {
|
||||||
|
if (!jarFile.exists()) {
|
||||||
|
if (url != null) {
|
||||||
|
try {
|
||||||
|
FileUtils.forceMkdirParent(jarFile)
|
||||||
|
FileUtils.copyURLToFile(url, jarFile)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw Exception("Invalid Corda version $version", e)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw Exception("File not found $jarFile")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Human-readable representation of the distribution.
|
||||||
|
*/
|
||||||
|
override fun toString() = "Corda(version = $version, path = $jarFile)"
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private val distributions = mutableListOf<Distribution>()
|
||||||
|
|
||||||
|
private val directory = File(System.getProperty("user.dir"))
|
||||||
|
|
||||||
|
private val nodePrefix = directory / "deps/corda"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Corda Open Source, version 3.0.0
|
||||||
|
*/
|
||||||
|
val V3 = fromJarFile("3.0.0")
|
||||||
|
|
||||||
|
val LATEST_MASTER = V3
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get representation of an open source distribution based on its version string.
|
||||||
|
* @param version The version of the open source Corda distribution.
|
||||||
|
*/
|
||||||
|
fun fromOpenSourceVersion(version: String): Distribution {
|
||||||
|
val url = URL("https://dl.bintray.com/r3/corda/net/corda/corda/$version/corda-$version.jar")
|
||||||
|
val distribution = Distribution(version, url = url)
|
||||||
|
distributions.add(distribution)
|
||||||
|
return distribution
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get representation of a Corda distribution based on its version string and fat JAR path.
|
||||||
|
* @param version The version of the Corda distribution.
|
||||||
|
* @param jarFile The path to the Corda fat JAR.
|
||||||
|
*/
|
||||||
|
fun fromJarFile(version: String, jarFile: File? = null): Distribution {
|
||||||
|
val distribution = Distribution(version, file = jarFile)
|
||||||
|
distributions.add(distribution)
|
||||||
|
return distribution
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get registered representation of a Corda distribution based on its version string.
|
||||||
|
* @param version The version of the Corda distribution
|
||||||
|
*/
|
||||||
|
fun fromVersionString(version: String): Distribution? = when (version.toLowerCase()) {
|
||||||
|
"master" -> LATEST_MASTER
|
||||||
|
else -> distributions.firstOrNull { it.version == version }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,343 @@
|
|||||||
|
package net.corda.behave.node
|
||||||
|
|
||||||
|
import net.corda.behave.database.DatabaseConnection
|
||||||
|
import net.corda.behave.database.DatabaseType
|
||||||
|
import net.corda.behave.file.LogSource
|
||||||
|
import net.corda.behave.file.currentDirectory
|
||||||
|
import net.corda.behave.file.div
|
||||||
|
import net.corda.behave.logging.getLogger
|
||||||
|
import net.corda.behave.monitoring.PatternWatch
|
||||||
|
import net.corda.behave.node.configuration.*
|
||||||
|
import net.corda.behave.process.JarCommand
|
||||||
|
import net.corda.behave.seconds
|
||||||
|
import net.corda.behave.service.Service
|
||||||
|
import net.corda.behave.service.ServiceSettings
|
||||||
|
import net.corda.behave.ssh.MonitoringSSHClient
|
||||||
|
import net.corda.behave.ssh.SSHClient
|
||||||
|
import net.corda.client.rpc.CordaRPCClient
|
||||||
|
import net.corda.client.rpc.CordaRPCClientConfiguration
|
||||||
|
import net.corda.core.messaging.CordaRPCOps
|
||||||
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
|
import org.apache.commons.io.FileUtils
|
||||||
|
import java.io.File
|
||||||
|
import java.time.Duration
|
||||||
|
import java.util.concurrent.CountDownLatch
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Corda node.
|
||||||
|
*/
|
||||||
|
class Node(
|
||||||
|
val config: Configuration,
|
||||||
|
private val rootDirectory: File = currentDirectory,
|
||||||
|
private val settings: ServiceSettings = ServiceSettings()
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val log = getLogger<Node>()
|
||||||
|
|
||||||
|
private val runtimeDirectory = rootDirectory / config.name
|
||||||
|
|
||||||
|
private val logDirectory = runtimeDirectory / "logs"
|
||||||
|
|
||||||
|
private val command = JarCommand(
|
||||||
|
config.distribution.jarFile,
|
||||||
|
arrayOf("--config", "node.conf"),
|
||||||
|
runtimeDirectory,
|
||||||
|
settings.timeout,
|
||||||
|
enableRemoteDebugging = false
|
||||||
|
)
|
||||||
|
|
||||||
|
private val isAliveLatch = PatternWatch("Node for \".*\" started up and registered")
|
||||||
|
|
||||||
|
private var isConfigured = false
|
||||||
|
|
||||||
|
private val serviceDependencies = mutableListOf<Service>()
|
||||||
|
|
||||||
|
private var isStarted = false
|
||||||
|
|
||||||
|
private var haveDependenciesStarted = false
|
||||||
|
|
||||||
|
private var haveDependenciesStopped = false
|
||||||
|
|
||||||
|
fun describe(): String {
|
||||||
|
val network = config.nodeInterface
|
||||||
|
val database = config.database
|
||||||
|
return """
|
||||||
|
|Node Information: ${config.name}
|
||||||
|
| - P2P: ${network.host}:${network.p2pPort}
|
||||||
|
| - RPC: ${network.host}:${network.rpcPort}
|
||||||
|
| - SSH: ${network.host}:${network.sshPort}
|
||||||
|
| - DB: ${network.host}:${database.port} (${database.type})
|
||||||
|
|""".trimMargin()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun configure() {
|
||||||
|
if (isConfigured) { return }
|
||||||
|
isConfigured = true
|
||||||
|
log.info("Configuring {} ...", this)
|
||||||
|
serviceDependencies.addAll(config.database.type.dependencies(config))
|
||||||
|
config.distribution.ensureAvailable()
|
||||||
|
config.writeToFile(rootDirectory / "${config.name}.conf")
|
||||||
|
installApps()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun start(): Boolean {
|
||||||
|
if (!startDependencies()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
log.info("Starting {} ...", this)
|
||||||
|
return try {
|
||||||
|
command.start()
|
||||||
|
isStarted = true
|
||||||
|
true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
log.warn("Failed to start {}", this)
|
||||||
|
e.printStackTrace()
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun waitUntilRunning(waitDuration: Duration? = null): Boolean {
|
||||||
|
val ok = isAliveLatch.await(command.output, waitDuration ?: settings.timeout)
|
||||||
|
if (!ok) {
|
||||||
|
log.warn("{} did not start up as expected within the given time frame", this)
|
||||||
|
} else {
|
||||||
|
log.info("{} is running and ready for incoming connections", this)
|
||||||
|
}
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
fun shutDown(): Boolean {
|
||||||
|
return try {
|
||||||
|
if (isStarted) {
|
||||||
|
log.info("Shutting down {} ...", this)
|
||||||
|
command.kill()
|
||||||
|
}
|
||||||
|
stopDependencies()
|
||||||
|
true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
log.warn("Failed to shut down {} cleanly", this)
|
||||||
|
e.printStackTrace()
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val nodeInfoGenerationOutput: LogSource by lazy {
|
||||||
|
LogSource(logDirectory, "node-info-gen.log")
|
||||||
|
}
|
||||||
|
|
||||||
|
val logOutput: LogSource by lazy {
|
||||||
|
LogSource(logDirectory, "node-info-gen.log", filePatternUsedForExclusion = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
val database: DatabaseConnection by lazy {
|
||||||
|
DatabaseConnection(config.database, config.databaseType.settings.template)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ssh(
|
||||||
|
exitLatch: CountDownLatch? = null,
|
||||||
|
clientLogic: (MonitoringSSHClient) -> Unit
|
||||||
|
) {
|
||||||
|
Thread(Runnable {
|
||||||
|
val network = config.nodeInterface
|
||||||
|
val user = config.users.first()
|
||||||
|
val client = SSHClient.connect(network.sshPort, user.password, username = user.username)
|
||||||
|
MonitoringSSHClient(client).use {
|
||||||
|
log.info("Connected to {} over SSH", this)
|
||||||
|
clientLogic(it)
|
||||||
|
log.info("Disconnecting from {} ...", this)
|
||||||
|
it.writeLine("bye")
|
||||||
|
exitLatch?.countDown()
|
||||||
|
}
|
||||||
|
}).start()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> rpc(action: (CordaRPCOps) -> T): T {
|
||||||
|
var result: T? = null
|
||||||
|
val user = config.users.first()
|
||||||
|
val address = config.nodeInterface
|
||||||
|
val targetHost = NetworkHostAndPort(address.host, address.rpcPort)
|
||||||
|
val config = CordaRPCClientConfiguration(
|
||||||
|
connectionMaxRetryInterval = 10.seconds
|
||||||
|
)
|
||||||
|
log.info("Establishing RPC connection to ${targetHost.host} on port ${targetHost.port} ...")
|
||||||
|
CordaRPCClient(targetHost, config).use(user.username, user.password) {
|
||||||
|
log.info("RPC connection to ${targetHost.host}:${targetHost.port} established")
|
||||||
|
val client = it.proxy
|
||||||
|
result = action(client)
|
||||||
|
}
|
||||||
|
return result ?: error("Failed to run RPC action")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "Node(name = ${config.name}, version = ${config.distribution.version})"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startDependencies(): Boolean {
|
||||||
|
if (haveDependenciesStarted) { return true }
|
||||||
|
haveDependenciesStarted = true
|
||||||
|
|
||||||
|
if (serviceDependencies.isEmpty()) { return true }
|
||||||
|
|
||||||
|
log.info("Starting dependencies for {} ...", this)
|
||||||
|
val latch = CountDownLatch(serviceDependencies.size)
|
||||||
|
var failed = false
|
||||||
|
serviceDependencies.parallelStream().forEach {
|
||||||
|
val wasStarted = it.start()
|
||||||
|
latch.countDown()
|
||||||
|
if (!wasStarted) {
|
||||||
|
failed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
latch.await()
|
||||||
|
return if (!failed) {
|
||||||
|
log.info("Dependencies started for {}", this)
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
log.warn("Failed to start one or more dependencies for {}", this)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun stopDependencies() {
|
||||||
|
if (haveDependenciesStopped) { return }
|
||||||
|
haveDependenciesStopped = true
|
||||||
|
|
||||||
|
if (serviceDependencies.isEmpty()) { return }
|
||||||
|
|
||||||
|
log.info("Stopping dependencies for {} ...", this)
|
||||||
|
val latch = CountDownLatch(serviceDependencies.size)
|
||||||
|
serviceDependencies.parallelStream().forEach {
|
||||||
|
it.stop()
|
||||||
|
latch.countDown()
|
||||||
|
}
|
||||||
|
latch.await()
|
||||||
|
log.info("Dependencies stopped for {}", this)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun installApps() {
|
||||||
|
val version = config.distribution.version
|
||||||
|
val appDirectory = rootDirectory / "../../../deps/corda/$version/apps"
|
||||||
|
if (appDirectory.exists()) {
|
||||||
|
val targetAppDirectory = runtimeDirectory / "cordapps"
|
||||||
|
FileUtils.copyDirectory(appDirectory, targetAppDirectory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Builder {
|
||||||
|
|
||||||
|
var name: String? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
private var distribution = Distribution.V3
|
||||||
|
|
||||||
|
private var databaseType = DatabaseType.H2
|
||||||
|
|
||||||
|
private var notaryType = NotaryType.NONE
|
||||||
|
|
||||||
|
private val issuableCurrencies = mutableListOf<String>()
|
||||||
|
|
||||||
|
private var location: String = "London"
|
||||||
|
|
||||||
|
private var country: String = "GB"
|
||||||
|
|
||||||
|
private val apps = mutableListOf<String>()
|
||||||
|
|
||||||
|
private var includeFinance = false
|
||||||
|
|
||||||
|
private var directory: File? = null
|
||||||
|
|
||||||
|
private var timeout = Duration.ofSeconds(60)
|
||||||
|
|
||||||
|
fun withName(newName: String): Builder {
|
||||||
|
name = newName
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun withDistribution(newDistribution: Distribution): Builder {
|
||||||
|
distribution = newDistribution
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun withDatabaseType(newDatabaseType: DatabaseType): Builder {
|
||||||
|
databaseType = newDatabaseType
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun withNotaryType(newNotaryType: NotaryType): Builder {
|
||||||
|
notaryType = newNotaryType
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun withIssuableCurrencies(vararg currencies: String): Builder {
|
||||||
|
issuableCurrencies.addAll(currencies)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun withIssuableCurrencies(currencies: List<String>): Builder {
|
||||||
|
issuableCurrencies.addAll(currencies)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun withLocation(location: String, country: String): Builder {
|
||||||
|
this.location = location
|
||||||
|
this.country = country
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun withFinanceApp(): Builder {
|
||||||
|
includeFinance = true
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun withApp(app: String): Builder {
|
||||||
|
apps.add(app)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun withDirectory(newDirectory: File): Builder {
|
||||||
|
directory = newDirectory
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun withTimeout(newTimeout: Duration): Builder {
|
||||||
|
timeout = newTimeout
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun build(): Node {
|
||||||
|
val name = name ?: error("Node name not set")
|
||||||
|
val directory = directory ?: error("Runtime directory not set")
|
||||||
|
return Node(
|
||||||
|
Configuration(
|
||||||
|
name,
|
||||||
|
distribution,
|
||||||
|
databaseType,
|
||||||
|
location = location,
|
||||||
|
country = country,
|
||||||
|
configElements = *arrayOf(
|
||||||
|
NotaryConfiguration(notaryType),
|
||||||
|
CurrencyConfiguration(issuableCurrencies),
|
||||||
|
CordappConfiguration(
|
||||||
|
apps = *apps.toTypedArray(),
|
||||||
|
includeFinance = includeFinance
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
directory,
|
||||||
|
ServiceSettings(timeout)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <T> error(message: String): T {
|
||||||
|
throw IllegalArgumentException(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun new() = Builder()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
package net.corda.behave.node.configuration
|
||||||
|
|
||||||
|
import net.corda.behave.database.DatabaseType
|
||||||
|
import net.corda.behave.node.*
|
||||||
|
import org.apache.commons.io.FileUtils
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class Configuration(
|
||||||
|
val name: String,
|
||||||
|
val distribution: Distribution = Distribution.LATEST_MASTER,
|
||||||
|
val databaseType: DatabaseType = DatabaseType.H2,
|
||||||
|
val location: String = "London",
|
||||||
|
val country: String = "GB",
|
||||||
|
val users: UserConfiguration = UserConfiguration().withUser("corda", DEFAULT_PASSWORD),
|
||||||
|
val nodeInterface: NetworkInterface = NetworkInterface(),
|
||||||
|
val database: DatabaseConfiguration = DatabaseConfiguration(
|
||||||
|
databaseType,
|
||||||
|
nodeInterface.host,
|
||||||
|
nodeInterface.dbPort,
|
||||||
|
password = DEFAULT_PASSWORD
|
||||||
|
),
|
||||||
|
vararg configElements: ConfigurationTemplate
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val developerMode = true
|
||||||
|
|
||||||
|
private val useHttps = false
|
||||||
|
|
||||||
|
private val basicConfig = """
|
||||||
|
|myLegalName="C=$country,L=$location,O=$name"
|
||||||
|
|keyStorePassword="cordacadevpass"
|
||||||
|
|trustStorePassword="trustpass"
|
||||||
|
|extraAdvertisedServiceIds=[ "" ]
|
||||||
|
|useHTTPS=$useHttps
|
||||||
|
|devMode=$developerMode
|
||||||
|
|jarDirs = [ "../libs" ]
|
||||||
|
""".trimMargin()
|
||||||
|
|
||||||
|
private val extraConfig = (configElements.toList() + listOf(users, nodeInterface))
|
||||||
|
.joinToString(separator = "\n") { it.generate(this) }
|
||||||
|
|
||||||
|
fun writeToFile(file: File) {
|
||||||
|
FileUtils.writeStringToFile(file, this.generate(), "UTF-8")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generate() = listOf(basicConfig, database.config(), extraConfig)
|
||||||
|
.filter { it.isNotBlank() }
|
||||||
|
.joinToString("\n")
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private val DEFAULT_PASSWORD = "S0meS3cretW0rd"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package net.corda.behave.node.configuration
|
||||||
|
|
||||||
|
open class ConfigurationTemplate {
|
||||||
|
|
||||||
|
protected open val config: (Configuration) -> String = { "" }
|
||||||
|
|
||||||
|
fun generate(config: Configuration) = config(config).trimMargin()
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package net.corda.behave.node.configuration
|
||||||
|
|
||||||
|
class CordappConfiguration(vararg apps: String, var includeFinance: Boolean = false) : ConfigurationTemplate() {
|
||||||
|
|
||||||
|
private val applications = apps.toList() + if (includeFinance) {
|
||||||
|
listOf("net.corda:corda-finance:CORDA_VERSION")
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
override val config: (Configuration) -> String
|
||||||
|
get() = { config ->
|
||||||
|
if (applications.isEmpty()) {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
"""
|
||||||
|
|cordapps = [
|
||||||
|
|${applications.joinToString(", ") { formatApp(config, it) }}
|
||||||
|
|]
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun formatApp(config: Configuration, app: String): String {
|
||||||
|
return "\"${app.replace("CORDA_VERSION", config.distribution.version)}\""
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package net.corda.behave.node.configuration
|
||||||
|
|
||||||
|
class CurrencyConfiguration(private val issuableCurrencies: List<String>) : ConfigurationTemplate() {
|
||||||
|
|
||||||
|
override val config: (Configuration) -> String
|
||||||
|
get() = {
|
||||||
|
if (issuableCurrencies.isEmpty()) {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
"""
|
||||||
|
|issuableCurrencies=[
|
||||||
|
| ${issuableCurrencies.joinToString(", ")}
|
||||||
|
|]
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package net.corda.behave.node.configuration
|
||||||
|
|
||||||
|
import net.corda.behave.database.DatabaseType
|
||||||
|
|
||||||
|
data class DatabaseConfiguration(
|
||||||
|
val type: DatabaseType,
|
||||||
|
val host: String,
|
||||||
|
val port: Int,
|
||||||
|
val username: String = type.settings.userName,
|
||||||
|
val password: String,
|
||||||
|
val database: String = type.settings.databaseName,
|
||||||
|
val schema: String = type.settings.schemaName
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun config() = type.settings.config(this)
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
package net.corda.behave.node.configuration
|
||||||
|
|
||||||
|
import java.net.Socket
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
|
data class NetworkInterface(
|
||||||
|
val host: String = "localhost",
|
||||||
|
val sshPort: Int = getPort(2222 + nodeIndex),
|
||||||
|
val p2pPort: Int = getPort(12001 + (nodeIndex * 5)),
|
||||||
|
val rpcPort: Int = getPort(12002 + (nodeIndex * 5)),
|
||||||
|
val rpcAdminPort: Int = getPort(12003 + (nodeIndex * 5)),
|
||||||
|
val webPort: Int = getPort(12004 + (nodeIndex * 5)),
|
||||||
|
val dbPort: Int = getPort(12005 + (nodeIndex * 5))
|
||||||
|
) : ConfigurationTemplate() {
|
||||||
|
|
||||||
|
init {
|
||||||
|
nodeIndex += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
override val config: (Configuration) -> String
|
||||||
|
get() = {
|
||||||
|
"""
|
||||||
|
|sshd={ port=$sshPort }
|
||||||
|
|p2pAddress="$host:$p2pPort"
|
||||||
|
|rpcSettings = {
|
||||||
|
| useSsl = false
|
||||||
|
| standAloneBroker = false
|
||||||
|
| address = "$host:$rpcPort"
|
||||||
|
| adminAddress = "$host:$rpcAdminPort"
|
||||||
|
|}
|
||||||
|
|webAddress="$host:$webPort"
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private var nodeIndex = 0
|
||||||
|
|
||||||
|
private var startOfBackupRange = AtomicInteger(40000)
|
||||||
|
|
||||||
|
private fun getPort(suggestedPortNumber: Int): Int {
|
||||||
|
var portNumber = suggestedPortNumber
|
||||||
|
while (isPortInUse(portNumber)) {
|
||||||
|
portNumber = startOfBackupRange.getAndIncrement()
|
||||||
|
}
|
||||||
|
if (portNumber >= 65535) {
|
||||||
|
throw Exception("No free port found (suggested $suggestedPortNumber)")
|
||||||
|
}
|
||||||
|
return portNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isPortInUse(portNumber: Int): Boolean {
|
||||||
|
return try {
|
||||||
|
val s = Socket("localhost", portNumber)
|
||||||
|
s.close()
|
||||||
|
true
|
||||||
|
|
||||||
|
} catch (_: Exception) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package net.corda.behave.node.configuration
|
||||||
|
|
||||||
|
class NotaryConfiguration(private val notaryType: NotaryType) : ConfigurationTemplate() {
|
||||||
|
|
||||||
|
override val config: (Configuration) -> String
|
||||||
|
get() = {
|
||||||
|
when (notaryType) {
|
||||||
|
NotaryType.NONE -> ""
|
||||||
|
NotaryType.NON_VALIDATING ->
|
||||||
|
"notary { validating = false }"
|
||||||
|
NotaryType.VALIDATING ->
|
||||||
|
"notary { validating = true }"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package net.corda.behave.node.configuration
|
||||||
|
|
||||||
|
enum class NotaryType {
|
||||||
|
|
||||||
|
NONE,
|
||||||
|
VALIDATING,
|
||||||
|
NON_VALIDATING
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String.toNotaryType(): NotaryType? {
|
||||||
|
return when (this.toLowerCase()) {
|
||||||
|
"non-validating" -> NotaryType.NON_VALIDATING
|
||||||
|
"nonvalidating" -> NotaryType.NON_VALIDATING
|
||||||
|
"validating" -> NotaryType.VALIDATING
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
package net.corda.behave.node.configuration
|
||||||
|
|
||||||
|
class UserConfiguration : ConfigurationTemplate(), Iterable<UserConfiguration.User> {
|
||||||
|
|
||||||
|
data class User(val username: String, val password: String, val permissions: List<String>)
|
||||||
|
|
||||||
|
private val users = mutableListOf<User>()
|
||||||
|
|
||||||
|
fun withUser(username: String, password: String, permissions: List<String> = listOf("ALL")): UserConfiguration {
|
||||||
|
users.add(User(username, password, permissions))
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun iterator(): Iterator<User> {
|
||||||
|
return users.iterator()
|
||||||
|
}
|
||||||
|
|
||||||
|
override val config: (Configuration) -> String
|
||||||
|
get() = {
|
||||||
|
"""
|
||||||
|
|rpcUsers=[
|
||||||
|
|${users.joinToString("\n") { userObject(it) }}
|
||||||
|
|]
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun userObject(user: User): String {
|
||||||
|
return """
|
||||||
|
|{
|
||||||
|
| username="${user.username}"
|
||||||
|
| password="${user.password}"
|
||||||
|
| permissions=[${user.permissions.joinToString(", ")}]
|
||||||
|
|}
|
||||||
|
""".trimMargin()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,157 @@
|
|||||||
|
package net.corda.behave.process
|
||||||
|
|
||||||
|
import net.corda.behave.*
|
||||||
|
import net.corda.behave.file.currentDirectory
|
||||||
|
import net.corda.behave.logging.getLogger
|
||||||
|
import net.corda.behave.process.output.OutputListener
|
||||||
|
import rx.Observable
|
||||||
|
import java.io.Closeable
|
||||||
|
import java.io.File
|
||||||
|
import java.io.IOException
|
||||||
|
import java.time.Duration
|
||||||
|
import java.util.concurrent.CountDownLatch
|
||||||
|
|
||||||
|
open class Command(
|
||||||
|
private val command: List<String>,
|
||||||
|
private val directory: File = currentDirectory,
|
||||||
|
private val timeout: Duration = 2.minutes
|
||||||
|
): Closeable {
|
||||||
|
|
||||||
|
protected val log = getLogger<Command>()
|
||||||
|
|
||||||
|
private val terminationLatch = CountDownLatch(1)
|
||||||
|
|
||||||
|
private val outputCapturedLatch = CountDownLatch(1)
|
||||||
|
|
||||||
|
private var isInterrupted = false
|
||||||
|
|
||||||
|
private var process: Process? = null
|
||||||
|
|
||||||
|
private lateinit var outputListener: OutputListener
|
||||||
|
|
||||||
|
var exitCode = -1
|
||||||
|
private set
|
||||||
|
|
||||||
|
val output: Observable<String> = Observable.create<String> { emitter ->
|
||||||
|
outputListener = object : OutputListener {
|
||||||
|
override fun onNewLine(line: String) {
|
||||||
|
emitter.onNext(line)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onEndOfStream() {
|
||||||
|
emitter.onCompleted()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val thread = Thread(Runnable {
|
||||||
|
try {
|
||||||
|
val processBuilder = ProcessBuilder(command)
|
||||||
|
.directory(directory)
|
||||||
|
.redirectErrorStream(true)
|
||||||
|
processBuilder.environment().putAll(System.getenv())
|
||||||
|
process = processBuilder.start()
|
||||||
|
val process = process!!
|
||||||
|
Thread(Runnable {
|
||||||
|
val input = process.inputStream.bufferedReader()
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
val line = input.readLine()?.trimEnd() ?: break
|
||||||
|
outputListener.onNewLine(line)
|
||||||
|
} catch (_: IOException) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
input.close()
|
||||||
|
outputListener.onEndOfStream()
|
||||||
|
outputCapturedLatch.countDown()
|
||||||
|
}).start()
|
||||||
|
val streamIsClosed = outputCapturedLatch.await(timeout)
|
||||||
|
val timeout = if (!streamIsClosed || isInterrupted) {
|
||||||
|
1.second
|
||||||
|
} else {
|
||||||
|
timeout
|
||||||
|
}
|
||||||
|
if (!process.waitFor(timeout)) {
|
||||||
|
process.destroy()
|
||||||
|
process.waitFor(WAIT_BEFORE_KILL)
|
||||||
|
if (process.isAlive) {
|
||||||
|
process.destroyForcibly()
|
||||||
|
process.waitFor()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exitCode = process.exitValue()
|
||||||
|
if (isInterrupted) {
|
||||||
|
log.warn("Process ended after interruption")
|
||||||
|
} else if (exitCode != 0 && exitCode != 143 /* SIGTERM */) {
|
||||||
|
log.warn("Process {} ended with exit code {}", this, exitCode)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
log.warn("Error occurred when trying to run process", e)
|
||||||
|
}
|
||||||
|
process = null
|
||||||
|
terminationLatch.countDown()
|
||||||
|
})
|
||||||
|
|
||||||
|
fun start() {
|
||||||
|
output.subscribe()
|
||||||
|
thread.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun interrupt() {
|
||||||
|
isInterrupted = true
|
||||||
|
outputCapturedLatch.countDown()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun waitFor(): Boolean {
|
||||||
|
terminationLatch.await()
|
||||||
|
return exitCode == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fun kill() {
|
||||||
|
process?.destroy()
|
||||||
|
process?.waitFor(WAIT_BEFORE_KILL)
|
||||||
|
if (process?.isAlive == true) {
|
||||||
|
process?.destroyForcibly()
|
||||||
|
}
|
||||||
|
if (process != null) {
|
||||||
|
terminationLatch.await()
|
||||||
|
}
|
||||||
|
process = null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
waitFor()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun run() = use { _ -> }
|
||||||
|
|
||||||
|
fun use(action: (Command) -> Unit): Int {
|
||||||
|
try {
|
||||||
|
start()
|
||||||
|
action(this)
|
||||||
|
} finally {
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
return exitCode
|
||||||
|
}
|
||||||
|
|
||||||
|
fun use(action: (Command, Observable<String>) -> Unit = { _, _ -> }): Int {
|
||||||
|
try {
|
||||||
|
start()
|
||||||
|
action(this, output)
|
||||||
|
} finally {
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
return exitCode
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString() = "Command(${command.joinToString(" ")})"
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private val WAIT_BEFORE_KILL: Duration = 5.seconds
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
package net.corda.behave.process
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
import java.time.Duration
|
||||||
|
|
||||||
|
class JarCommand(
|
||||||
|
jarFile: File,
|
||||||
|
arguments: Array<String>,
|
||||||
|
directory: File,
|
||||||
|
timeout: Duration,
|
||||||
|
enableRemoteDebugging: Boolean = false
|
||||||
|
) : Command(
|
||||||
|
command = listOf(
|
||||||
|
"/usr/bin/java",
|
||||||
|
*extraArguments(enableRemoteDebugging),
|
||||||
|
"-jar", "$jarFile",
|
||||||
|
*arguments
|
||||||
|
),
|
||||||
|
directory = directory,
|
||||||
|
timeout = timeout
|
||||||
|
) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private fun extraArguments(enableRemoteDebugging: Boolean) =
|
||||||
|
if (enableRemoteDebugging) {
|
||||||
|
arrayOf("-Dcapsule.jvm.args=-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005")
|
||||||
|
} else {
|
||||||
|
arrayOf()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package net.corda.behave.process.output
|
||||||
|
|
||||||
|
interface OutputListener {
|
||||||
|
|
||||||
|
fun onNewLine(line: String)
|
||||||
|
|
||||||
|
fun onEndOfStream()
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,122 @@
|
|||||||
|
package net.corda.behave.service
|
||||||
|
|
||||||
|
import com.spotify.docker.client.DefaultDockerClient
|
||||||
|
import com.spotify.docker.client.DockerClient
|
||||||
|
import com.spotify.docker.client.messages.ContainerConfig
|
||||||
|
import com.spotify.docker.client.messages.HostConfig
|
||||||
|
import com.spotify.docker.client.messages.PortBinding
|
||||||
|
import net.corda.behave.monitoring.PatternWatch
|
||||||
|
import net.corda.behave.monitoring.Watch
|
||||||
|
import rx.Observable
|
||||||
|
import java.io.Closeable
|
||||||
|
|
||||||
|
abstract class ContainerService(
|
||||||
|
name: String,
|
||||||
|
port: Int,
|
||||||
|
settings: ServiceSettings = ServiceSettings()
|
||||||
|
) : Service(name, port, settings), Closeable {
|
||||||
|
|
||||||
|
protected val client: DockerClient = DefaultDockerClient.fromEnv().build()
|
||||||
|
|
||||||
|
protected var id: String? = null
|
||||||
|
|
||||||
|
protected open val baseImage: String = ""
|
||||||
|
|
||||||
|
protected open val imageTag: String = "latest"
|
||||||
|
|
||||||
|
protected abstract val internalPort: Int
|
||||||
|
|
||||||
|
private var isClientOpen: Boolean = true
|
||||||
|
|
||||||
|
private val environmentVariables: MutableList<String> = mutableListOf()
|
||||||
|
|
||||||
|
private var startupStatement: Watch = PatternWatch.EMPTY
|
||||||
|
|
||||||
|
private val imageReference: String
|
||||||
|
get() = "$baseImage:$imageTag"
|
||||||
|
|
||||||
|
override fun startService(): Boolean {
|
||||||
|
return try {
|
||||||
|
val port = "$internalPort"
|
||||||
|
val portBindings = mapOf(
|
||||||
|
port to listOf(PortBinding.of("0.0.0.0", this.port))
|
||||||
|
)
|
||||||
|
val hostConfig = HostConfig.builder().portBindings(portBindings).build()
|
||||||
|
val containerConfig = ContainerConfig.builder()
|
||||||
|
.hostConfig(hostConfig)
|
||||||
|
.image(imageReference)
|
||||||
|
.exposedPorts(port)
|
||||||
|
.env(*environmentVariables.toTypedArray())
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val creation = client.createContainer(containerConfig)
|
||||||
|
id = creation.id()
|
||||||
|
client.startContainer(id)
|
||||||
|
true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
id = null
|
||||||
|
e.printStackTrace()
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun stopService(): Boolean {
|
||||||
|
if (id != null) {
|
||||||
|
client.stopContainer(id, 30)
|
||||||
|
client.removeContainer(id)
|
||||||
|
id = null
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun addEnvironmentVariable(name: String, value: String) {
|
||||||
|
environmentVariables.add("$name=$value")
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun setStartupStatement(statement: String) {
|
||||||
|
startupStatement = PatternWatch(statement)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun checkPrerequisites() {
|
||||||
|
if (!client.listImages().any { true == it.repoTags()?.contains(imageReference) }) {
|
||||||
|
log.info("Pulling image $imageReference ...")
|
||||||
|
client.pull(imageReference, { _ ->
|
||||||
|
run { }
|
||||||
|
})
|
||||||
|
log.info("Image $imageReference downloaded")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun verify(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun waitUntilStarted(): Boolean {
|
||||||
|
try {
|
||||||
|
var timeout = settings.startupTimeout.toMillis()
|
||||||
|
while (timeout > 0) {
|
||||||
|
client.logs(id, DockerClient.LogsParam.stdout(), DockerClient.LogsParam.stderr()).use {
|
||||||
|
val contents = it.readFully()
|
||||||
|
val observable = Observable.from(contents.split("\n"))
|
||||||
|
if (startupStatement.await(observable, settings.pollInterval)) {
|
||||||
|
log.info("Found process start-up statement for {}", this)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
timeout -= settings.pollInterval.toMillis()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
if (isClientOpen) {
|
||||||
|
isClientOpen = false
|
||||||
|
client.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
package net.corda.behave.service
|
||||||
|
|
||||||
|
import net.corda.behave.logging.getLogger
|
||||||
|
import java.io.Closeable
|
||||||
|
|
||||||
|
abstract class Service(
|
||||||
|
val name: String,
|
||||||
|
val port: Int,
|
||||||
|
val settings: ServiceSettings = ServiceSettings()
|
||||||
|
) : Closeable {
|
||||||
|
|
||||||
|
private var isRunning: Boolean = false
|
||||||
|
|
||||||
|
protected val log = getLogger<Service>()
|
||||||
|
|
||||||
|
fun start(): Boolean {
|
||||||
|
if (isRunning) {
|
||||||
|
log.warn("{} is already running", this)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
log.info("Starting {} ...", this)
|
||||||
|
checkPrerequisites()
|
||||||
|
if (!startService()) {
|
||||||
|
log.warn("Failed to start {}", this)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
isRunning = true
|
||||||
|
Thread.sleep(settings.startupDelay.toMillis())
|
||||||
|
return if (!waitUntilStarted()) {
|
||||||
|
log.warn("Failed to start {}", this)
|
||||||
|
stop()
|
||||||
|
false
|
||||||
|
} else if (!verify()) {
|
||||||
|
log.warn("Failed to verify start-up of {}", this)
|
||||||
|
stop()
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
log.info("{} started and available", this)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stop() {
|
||||||
|
if (!isRunning) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.info("Stopping {} ...", this)
|
||||||
|
if (stopService()) {
|
||||||
|
log.info("{} stopped", this)
|
||||||
|
isRunning = false
|
||||||
|
} else {
|
||||||
|
log.warn("Failed to stop {}", this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString() = "Service(name = $name, port = $port)"
|
||||||
|
|
||||||
|
protected open fun checkPrerequisites() { }
|
||||||
|
|
||||||
|
protected open fun startService() = true
|
||||||
|
|
||||||
|
protected open fun stopService() = true
|
||||||
|
|
||||||
|
protected open fun verify() = true
|
||||||
|
|
||||||
|
protected open fun waitUntilStarted() = true
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package net.corda.behave.service
|
||||||
|
|
||||||
|
import net.corda.behave.node.configuration.Configuration
|
||||||
|
|
||||||
|
typealias ServiceInitiator = (Configuration) -> Service
|
@ -0,0 +1,13 @@
|
|||||||
|
package net.corda.behave.service
|
||||||
|
|
||||||
|
import net.corda.behave.minute
|
||||||
|
import net.corda.behave.second
|
||||||
|
import net.corda.behave.seconds
|
||||||
|
import java.time.Duration
|
||||||
|
|
||||||
|
data class ServiceSettings(
|
||||||
|
val timeout: Duration = 1.minute,
|
||||||
|
val startupDelay: Duration = 1.second,
|
||||||
|
val startupTimeout: Duration = 15.seconds,
|
||||||
|
val pollInterval: Duration = 1.second
|
||||||
|
)
|
@ -0,0 +1,18 @@
|
|||||||
|
package net.corda.behave.service.database
|
||||||
|
|
||||||
|
import net.corda.behave.service.Service
|
||||||
|
|
||||||
|
class H2Service(
|
||||||
|
name: String,
|
||||||
|
port: Int
|
||||||
|
) : Service(name, port) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
val database = "node"
|
||||||
|
val schema = "dbo"
|
||||||
|
val username = "sa"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
package net.corda.behave.service.database
|
||||||
|
|
||||||
|
import net.corda.behave.database.DatabaseConnection
|
||||||
|
import net.corda.behave.database.DatabaseType
|
||||||
|
import net.corda.behave.database.configuration.SqlServerConfigurationTemplate
|
||||||
|
import net.corda.behave.node.configuration.DatabaseConfiguration
|
||||||
|
import net.corda.behave.service.ContainerService
|
||||||
|
import net.corda.behave.service.ServiceSettings
|
||||||
|
|
||||||
|
class SqlServerService(
|
||||||
|
name: String,
|
||||||
|
port: Int,
|
||||||
|
private val password: String,
|
||||||
|
settings: ServiceSettings = ServiceSettings()
|
||||||
|
) : ContainerService(name, port, settings) {
|
||||||
|
|
||||||
|
override val baseImage = "microsoft/mssql-server-linux"
|
||||||
|
|
||||||
|
override val internalPort = 1433
|
||||||
|
|
||||||
|
init {
|
||||||
|
addEnvironmentVariable("ACCEPT_EULA", "Y")
|
||||||
|
addEnvironmentVariable("SA_PASSWORD", password)
|
||||||
|
setStartupStatement("SQL Server is now ready for client connections")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun verify(): Boolean {
|
||||||
|
val config = DatabaseConfiguration(
|
||||||
|
type = DatabaseType.SQL_SERVER,
|
||||||
|
host = host,
|
||||||
|
port = port,
|
||||||
|
database = database,
|
||||||
|
schema = schema,
|
||||||
|
username = username,
|
||||||
|
password = password
|
||||||
|
)
|
||||||
|
val connection = DatabaseConnection(config, SqlServerConfigurationTemplate())
|
||||||
|
try {
|
||||||
|
connection.use {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
log.warn(ex.message, ex)
|
||||||
|
ex.printStackTrace()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
val host = "localhost"
|
||||||
|
val database = "master"
|
||||||
|
val schema = "dbo"
|
||||||
|
val username = "sa"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,69 @@
|
|||||||
|
package net.corda.behave.ssh
|
||||||
|
|
||||||
|
import net.corda.behave.process.output.OutputListener
|
||||||
|
import rx.Observable
|
||||||
|
import java.io.Closeable
|
||||||
|
import java.io.InterruptedIOException
|
||||||
|
|
||||||
|
class MonitoringSSHClient(
|
||||||
|
private val client: SSHClient
|
||||||
|
) : Closeable {
|
||||||
|
|
||||||
|
private var isRunning = false
|
||||||
|
|
||||||
|
private lateinit var outputListener: OutputListener
|
||||||
|
|
||||||
|
val output: Observable<String> = Observable.create<String> { emitter ->
|
||||||
|
outputListener = object : OutputListener {
|
||||||
|
override fun onNewLine(line: String) {
|
||||||
|
emitter.onNext(line)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onEndOfStream() {
|
||||||
|
emitter.onCompleted()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val thread = Thread(Runnable {
|
||||||
|
while (isRunning) {
|
||||||
|
try {
|
||||||
|
val line = client.readLine() ?: break
|
||||||
|
outputListener.onNewLine(line)
|
||||||
|
} catch (_: InterruptedIOException) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outputListener.onEndOfStream()
|
||||||
|
})
|
||||||
|
|
||||||
|
init {
|
||||||
|
isRunning = true
|
||||||
|
output.subscribe()
|
||||||
|
thread.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
isRunning = false
|
||||||
|
thread.join(1000)
|
||||||
|
if (thread.isAlive) {
|
||||||
|
thread.interrupt()
|
||||||
|
}
|
||||||
|
client.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun use(action: (MonitoringSSHClient) -> Unit) {
|
||||||
|
try {
|
||||||
|
action(this)
|
||||||
|
} finally {
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun write(vararg bytes: Byte) = client.write(*bytes)
|
||||||
|
|
||||||
|
fun write(charSequence: CharSequence) = client.write(charSequence)
|
||||||
|
|
||||||
|
fun writeLine(string: String) = client.writeLine(string)
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,161 @@
|
|||||||
|
package net.corda.behave.ssh
|
||||||
|
|
||||||
|
import net.corda.behave.logging.getLogger
|
||||||
|
import org.apache.sshd.client.SshClient
|
||||||
|
import org.apache.sshd.client.channel.ChannelShell
|
||||||
|
import org.apache.sshd.client.session.ClientSession
|
||||||
|
import org.apache.sshd.common.channel.SttySupport
|
||||||
|
import org.crsh.util.Utils
|
||||||
|
import java.io.*
|
||||||
|
import java.time.Duration
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
open class SSHClient private constructor(
|
||||||
|
private val client: SshClient,
|
||||||
|
private val outputStream: OutputStream,
|
||||||
|
private val inputStream: InputStream,
|
||||||
|
private val session: ClientSession,
|
||||||
|
private val channel: ChannelShell
|
||||||
|
) : Closeable {
|
||||||
|
|
||||||
|
private var isClosed = false
|
||||||
|
|
||||||
|
fun read(): Int? {
|
||||||
|
if (isClosed) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val char = inputStream.read()
|
||||||
|
return if (char != -1) {
|
||||||
|
char
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun readLine(): String? {
|
||||||
|
if (isClosed) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
var ch: Int?
|
||||||
|
val lineBuffer = mutableListOf<Char>()
|
||||||
|
while (true) {
|
||||||
|
ch = read()
|
||||||
|
if (ch == null) {
|
||||||
|
if (lineBuffer.isEmpty()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
lineBuffer.add(ch.toChar())
|
||||||
|
if (ch == 10) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return String(lineBuffer.toCharArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun write(s: CharSequence) {
|
||||||
|
if (isClosed) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
write(*s.toString().toByteArray(UTF8))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun write(vararg bytes: Byte) {
|
||||||
|
if (isClosed) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
outputStream.write(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun writeLine(s: String) {
|
||||||
|
write("$s\n")
|
||||||
|
flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun flush() {
|
||||||
|
if (isClosed) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
outputStream.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
if (isClosed) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Utils.close(outputStream)
|
||||||
|
channel.close(false)
|
||||||
|
session.close(false)
|
||||||
|
client.stop()
|
||||||
|
} finally {
|
||||||
|
isClosed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private val log = getLogger<SSHClient>()
|
||||||
|
|
||||||
|
fun connect(
|
||||||
|
port: Int,
|
||||||
|
password: String,
|
||||||
|
hostname: String = "localhost",
|
||||||
|
username: String = "corda",
|
||||||
|
timeout: Duration = Duration.ofSeconds(4)
|
||||||
|
): SSHClient {
|
||||||
|
val tty = SttySupport.parsePtyModes(TTY)
|
||||||
|
val client = SshClient.setUpDefaultClient()
|
||||||
|
client.start()
|
||||||
|
|
||||||
|
log.info("Connecting to $hostname:$port ...")
|
||||||
|
val session = client
|
||||||
|
.connect(username, hostname, port)
|
||||||
|
.verify(timeout.seconds, TimeUnit.SECONDS)
|
||||||
|
.session
|
||||||
|
|
||||||
|
log.info("Authenticating using password identity ...")
|
||||||
|
session.addPasswordIdentity(password)
|
||||||
|
val authFuture = session.auth().verify(timeout.seconds, TimeUnit.SECONDS)
|
||||||
|
|
||||||
|
authFuture.addListener {
|
||||||
|
log.info("Authentication completed with " + if (it.isSuccess) "success" else "failure")
|
||||||
|
}
|
||||||
|
|
||||||
|
val channel = session.createShellChannel()
|
||||||
|
channel.ptyModes = tty
|
||||||
|
|
||||||
|
val outputStream = PipedOutputStream()
|
||||||
|
val channelIn = PipedInputStream(outputStream)
|
||||||
|
|
||||||
|
val channelOut = PipedOutputStream()
|
||||||
|
val inputStream = PipedInputStream(channelOut)
|
||||||
|
|
||||||
|
channel.`in` = channelIn
|
||||||
|
channel.out = channelOut
|
||||||
|
channel.err = ByteArrayOutputStream()
|
||||||
|
channel.open()
|
||||||
|
|
||||||
|
return SSHClient(client, outputStream, inputStream, session, channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val TTY = "speed 9600 baud; 36 rows; 180 columns;\n" +
|
||||||
|
"lflags: icanon isig iexten echo echoe -echok echoke -echonl echoctl\n" +
|
||||||
|
"\t-echoprt -altwerase -noflsh -tostop -flusho pendin -nokerninfo\n" +
|
||||||
|
"\t-extproc\n" +
|
||||||
|
"iflags: -istrip icrnl -inlcr -igncr ixon -ixoff ixany imaxbel iutf8\n" +
|
||||||
|
"\t-ignbrk brkint -inpck -ignpar -parmrk\n" +
|
||||||
|
"oflags: opost onlcr -oxtabs -onocr -onlret\n" +
|
||||||
|
"cflags: cread cs8 -parenb -parodd hupcl -clocal -cstopb -crtscts -dsrflow\n" +
|
||||||
|
"\t-dtrflow -mdmbuf\n" +
|
||||||
|
"cchars: discard = ^O; dsusp = ^Y; eof = ^D; eol = <undef>;\n" +
|
||||||
|
"\teol2 = <undef>; erase = ^?; intr = ^C; kill = ^U; lnext = ^V;\n" +
|
||||||
|
"\tmin = 1; quit = ^\\; reprint = ^R; start = ^Q; status = ^T;\n" +
|
||||||
|
"\tstop = ^S; susp = ^Z; time = 0; werase = ^W;"
|
||||||
|
|
||||||
|
private val UTF8 = charset("UTF-8")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
11
experimental/behave/src/scenario/kotlin/Scenarios.kt
Normal file
11
experimental/behave/src/scenario/kotlin/Scenarios.kt
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import cucumber.api.CucumberOptions
|
||||||
|
import cucumber.api.junit.Cucumber
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
@RunWith(Cucumber::class)
|
||||||
|
@CucumberOptions(
|
||||||
|
glue = arrayOf("net.corda.behave.scenarios"),
|
||||||
|
plugin = arrayOf("pretty")
|
||||||
|
)
|
||||||
|
@Suppress("KDocMissingDocumentation")
|
||||||
|
class CucumberTest
|
@ -0,0 +1,18 @@
|
|||||||
|
package net.corda.behave.scenarios
|
||||||
|
|
||||||
|
import cucumber.api.java.After
|
||||||
|
import cucumber.api.java.Before
|
||||||
|
|
||||||
|
@Suppress("KDocMissingDocumentation")
|
||||||
|
class ScenarioHooks(private val state: ScenarioState) {
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun beforeScenario() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun afterScenario() {
|
||||||
|
state.stopNetwork()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,86 @@
|
|||||||
|
package net.corda.behave.scenarios
|
||||||
|
|
||||||
|
import net.corda.behave.logging.getLogger
|
||||||
|
import net.corda.behave.network.Network
|
||||||
|
import net.corda.behave.node.Node
|
||||||
|
import net.corda.core.messaging.CordaRPCOps
|
||||||
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
|
||||||
|
class ScenarioState {
|
||||||
|
|
||||||
|
private val log = getLogger<ScenarioState>()
|
||||||
|
|
||||||
|
private val nodes = mutableListOf<Node.Builder>()
|
||||||
|
|
||||||
|
private var network: Network? = null
|
||||||
|
|
||||||
|
fun fail(message: String) {
|
||||||
|
error<Unit>(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun<T> error(message: String, ex: Throwable? = null): T {
|
||||||
|
this.network?.signalFailure(message, ex)
|
||||||
|
if (ex != null) {
|
||||||
|
throw Exception(message, ex)
|
||||||
|
} else {
|
||||||
|
throw Exception(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun node(name: String): Node {
|
||||||
|
val network = network ?: error("Network is not running")
|
||||||
|
return network[nodeName(name)] ?: error("Node '$name' not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun nodeBuilder(name: String): Node.Builder {
|
||||||
|
return nodes.firstOrNull { it.name == nodeName(name) } ?: newNode(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ensureNetworkIsRunning() {
|
||||||
|
if (network != null) {
|
||||||
|
// Network is already running
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val networkBuilder = Network.new()
|
||||||
|
for (node in nodes) {
|
||||||
|
networkBuilder.addNode(node)
|
||||||
|
}
|
||||||
|
network = networkBuilder.generate()
|
||||||
|
network?.start()
|
||||||
|
assertThat(network?.waitUntilRunning()).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <T> withNetwork(action: ScenarioState.() -> T): T {
|
||||||
|
ensureNetworkIsRunning()
|
||||||
|
return action()
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <T> withClient(nodeName: String, crossinline action: (CordaRPCOps) -> T): T {
|
||||||
|
withNetwork {
|
||||||
|
return node(nodeName).rpc {
|
||||||
|
action(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stopNetwork() {
|
||||||
|
val network = network ?: return
|
||||||
|
for (node in network) {
|
||||||
|
val matches = node.logOutput.find("\\[ERR")
|
||||||
|
if (matches.any()) {
|
||||||
|
fail("Found errors in the log for node '${node.config.name}': ${matches.first().filename}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
network.stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun nodeName(name: String) = "Entity$name"
|
||||||
|
|
||||||
|
private fun newNode(name: String): Node.Builder {
|
||||||
|
val builder = Node.new()
|
||||||
|
.withName(nodeName(name))
|
||||||
|
nodes.add(builder)
|
||||||
|
return builder
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user