Merge remote-tracking branch 'open/master' into aslemmer-merge-19-Feb

This commit is contained in:
Andras Slemmer 2018-02-20 15:52:09 +00:00
commit 25263c20c7
318 changed files with 5853 additions and 4008 deletions

View File

@ -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 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 <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.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()
##
@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)
@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()
@org.jetbrains.annotations.NotNull public final net.corda.core.flows.FlowSession getOtherSideSession()
@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.NotNull public final net.corda.core.flows.StateMachineRunId getRunId()
@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 public final void persistFlowStackSnapshot()
@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, boolean)
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, boolean)
@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 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
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()
@ -1333,11 +1380,20 @@ public @interface net.corda.core.flows.InitiatingFlow
@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
public <init>(String)
@org.jetbrains.annotations.NotNull public final String component1()
@org.jetbrains.annotations.NotNull public final net.corda.core.flows.NotaryError$General copy(String)
public <init>(Throwable)
@org.jetbrains.annotations.NotNull public final Throwable component1()
@org.jetbrains.annotations.NotNull public final net.corda.core.flows.NotaryError$General copy(Throwable)
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()
@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 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 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)
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.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.cordapp.CordappContext getAppContext()
@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.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.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.NetworkParameters getNetworkParameters()
##
@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)
@ -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 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>()
@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 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)
@org.jetbrains.annotations.NotNull public final List getAttachments()
@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 Set getRequiredSigningKeys()
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 String toString()
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 int hashCode()
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()
@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
@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.NotNull public final net.corda.node.services.config.VerifierType getVerifierType()
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()
@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 <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()
##
@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 final rx.Observable getReceivedMessages()
@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)
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 <init>(net.corda.node.services.messaging.MessagingService)
@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 <init>(List)
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 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 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 List getCordappPackages()
@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.MockNetworkParameters getDefaultParameters()
public final boolean getInitialiseSerialization()
public final int getMaxTransactionSize()
@org.jetbrains.annotations.NotNull public final net.corda.core.node.NetworkParameters getNetworkParameters()
public final boolean getNetworkSendManuallyPumped()
public final int getNextNodeId()
@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 <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 component2()
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy component3()
public final boolean component4()
@org.jetbrains.annotations.NotNull public final List 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, boolean, List, int)
@org.jetbrains.annotations.NotNull public final List component4()
@org.jetbrains.annotations.NotNull public final net.corda.core.node.NetworkParameters component5()
@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)
public boolean equals(Object)
public final boolean getInitialiseSerialization()
public final int getMaxTransactionSize()
@org.jetbrains.annotations.NotNull public final net.corda.core.node.NetworkParameters getNetworkParameters()
public final boolean getNetworkSendManuallyPumped()
@org.jetbrains.annotations.NotNull public final List getNotarySpecs()
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy getServicePeerAllocationStrategy()
public final boolean getThreadPerNode()
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()
@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 <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.NotNull public final net.corda.node.VersionInfo getVersion()
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()
@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 <init>()
public <init>(List)
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.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, java.security.PublicKey)
@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.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.cordapp.CordappContext getAppContext()
@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 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.KeyManagementService getKeyManagementService()
@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.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.node.services.api.WritableTransactionStorage getValidatedTransactions()
@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.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.cordapp.CordappContext getAppContext()
@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 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.NodeInfo getMyInfo()
@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.core.serialization.SerializeAsToken getServiceInstance()
@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
@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()
@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()
@ -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)
public final void setMessagingServiceSpy(net.corda.testing.node.MessagingServiceSpy)
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 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 synchronized final void advanceBy(java.time.Duration)
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 <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, 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, net.corda.core.context.Trace, net.corda.core.context.Actor)
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 <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 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
@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 BOB_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.cordapp.CordappProvider getCordappProvider()
@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 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 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()
##

View File

@ -31,8 +31,7 @@ if [ $removalCount -gt 0 ]; then
fi
# 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() {
awk '/DoNotImplement/,/^##/{ next }{ print }' $1
}
@ -45,13 +44,28 @@ $newAbstracts
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
if [ $abstractCount -gt 0 ]; then
echo "$newAbstracts"
echo
fi
badChanges=$(($removalCount + $abstractCount))
badChanges=$(($removalCount + $abstractCount + $internalCount))
if [ $badChanges -gt 255 ]; then
echo "OVERFLOW! Number of bad API changes: $badChanges"
badChanges=255

56
.idea/compiler.xml generated
View File

@ -1,15 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="1.8">
<module name="api-scanner_main" target="1.8" />
<module name="api-scanner_test" target="1.8" />
<bytecodeTargetLevel>
<module name="attachment-demo_integrationTest" target="1.8" />
<module name="attachment-demo_main" 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_main" 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_test" target="1.8" />
<module name="buildSrc_main" target="1.8" />
@ -25,40 +26,28 @@
<module name="client_test" target="1.8" />
<module name="confidential-identities_main" 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_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_main" target="1.8" />
<module name="cordapp_test" target="1.8" />
<module name="cordform-common_main" 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_main" 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="dbmigration_main" target="1.8" />
<module name="dbmigration_test" target="1.8" />
<module name="demobench_main" target="1.8" />
<module name="demobench_test" 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="example-code_integrationTest" target="1.8" />
<module name="example-code_main" 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_test" target="1.8" />
<module name="explorer-capsule_main" target="1.6" />
@ -70,21 +59,10 @@
<module name="finance_test" target="1.8" />
<module name="flow-hook_main" 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_test" target="1.8" />
<module name="intellij-plugin_main" 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_main" 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_integrationTest" 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="notary-demo_main" 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_main" 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_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_main" target="1.8" />
<module name="rpc_smokeTest" 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_test" target="1.8" />
<module name="sandbox_main" target="1.8" />
<module name="sandbox_test" target="1.8" />
<module name="sgx-hsm-tool_main" 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_main" 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_test" target="1.8" />
<module name="testing-test-common_main" target="1.8" />
@ -188,7 +147,4 @@
<module name="webserver_test" target="1.8" />
</bytecodeTargetLevel>
</component>
<component name="JavacSettings">
<option name="ADDITIONAL_OPTIONS_STRING" value="-parameters" />
</component>
</project>

View File

@ -15,7 +15,9 @@ buildscript {
//
// TODO: Sort this alphabetically.
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
// 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.jetty_version = '9.4.7.v20170914'
ext.jersey_version = '2.25'
ext.jolokia_version = '1.3.7'
ext.assertj_version = '3.8.0'
ext.slf4j_version = '1.7.25'
ext.log4j_version = '2.9.1'
@ -75,6 +76,7 @@ buildscript {
ext.docker_compose_rule_version = '0.33.0'
ext.selenium_version = '3.8.1'
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:
ext.java8_minUpdateVersion = '131'

View File

@ -30,6 +30,7 @@ import java.util.concurrent.Executors
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicLong
class RPCStabilityTests {
@Rule
@ -127,7 +128,7 @@ class RPCStabilityTests {
rpcDriver {
fun startAndCloseServer(broker: RpcBrokerHandle) {
startRpcServerWithBrokerRunning(
configuration = RPCServerConfiguration.default.copy(consumerPoolSize = 1, producerPoolBound = 1),
configuration = RPCServerConfiguration.default,
ops = DummyOps,
brokerHandle = broker
).rpcServer.close()
@ -148,7 +149,7 @@ class RPCStabilityTests {
@Test
fun `rpc client close doesnt leak broker resources`() {
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()
val initial = server.broker.getStats()
repeat(100) {
@ -337,11 +338,12 @@ class RPCStabilityTests {
val request = RPCApi.ClientToServer.RpcRequest(
clientAddress = SimpleString(myQueue),
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(),
sessionId = Trace.SessionId.newInstance()
)
request.writeToClientMessage(message)
message.putLongProperty(RPCApi.DEDUPLICATION_SEQUENCE_NUMBER_FIELD_NAME, 0)
producer.send(message)
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) {

View File

@ -70,11 +70,24 @@ data class CordaRPCClientConfiguration(val connectionMaxRetryInterval: Duration)
* @param configuration An optional configuration used to tweak client behaviour.
* @param sslConfiguration An optional [SSLConfiguration] used to enable secure communication with the server.
*/
class CordaRPCClient @JvmOverloads constructor(
class CordaRPCClient private constructor(
hostAndPort: NetworkHostAndPort,
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT,
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 {
try {
effectiveSerializationEnv

View File

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

View File

@ -1,5 +1,7 @@
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.RPCException
import net.corda.core.context.Actor
@ -42,8 +44,6 @@ data class RPCClientConfiguration(
val reapInterval: Duration,
/** The number of threads to use for observations (for executing [Observable.onNext]) */
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
* 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,
/** Maximum retry interval */
val connectionMaxRetryInterval: Duration,
/** Maximum reconnect attempts on failover */
val maxReconnectAttempts: Int,
/** Maximum file size */
val maxFileSize: Int
val maxFileSize: Int,
/** The cache expiry of a deduplication watermark per client. */
val deduplicationCacheExpiry: Duration
) {
companion object {
val unlimitedReconnectAttempts = -1
@ -68,14 +71,14 @@ data class RPCClientConfiguration(
trackRpcCallSites = false,
reapInterval = 1.seconds,
observationExecutorPoolSize = 4,
producerPoolBound = 1,
cacheConcurrencyLevel = 8,
connectionRetryInterval = 5.seconds,
connectionRetryIntervalMultiplier = 1.5,
connectionMaxRetryInterval = 3.minutes,
maxReconnectAttempts = unlimitedReconnectAttempts,
/** 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
)
}
}

View File

@ -15,7 +15,6 @@ import net.corda.client.rpc.RPCSinceVersion
import net.corda.core.context.Actor
import net.corda.core.context.Trace
import net.corda.core.context.Trace.InvocationId
import net.corda.core.internal.LazyPool
import net.corda.core.internal.LazyStickyPool
import net.corda.core.internal.LifeCycle
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.debug
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.internal.DeduplicationChecker
import org.apache.activemq.artemis.api.core.RoutingType
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.ClientMessage
import org.apache.activemq.artemis.api.core.client.ServerLocator
import rx.Notification
import rx.Observable
import rx.subjects.UnicastSubject
@ -43,6 +40,7 @@ import java.time.Instant
import java.util.*
import java.util.concurrent.*
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicLong
import kotlin.reflect.jvm.javaMethod
/**
@ -111,6 +109,8 @@ class RPCClientProxyHandler(
// Used for reaping
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.
private val observationExecutorThreadFactory = ThreadFactoryBuilder().setNameFormat("rpc-client-observation-pool-%d").setDaemon(true).build()
@ -161,22 +161,14 @@ class RPCClientProxyHandler(
build()
}
// We cannot pool consumers as we need to preserve the original muxed message order.
// TODO We may need to pool these somehow anyway, otherwise if the server sends many big messages in parallel a
// single consumer may be starved for flow control credits. Recheck this once Artemis's large message streaming is
// integrated properly.
private var sessionAndConsumer: ArtemisConsumer? = null
// Pool producers to reduce contention on the client side.
private val sessionAndProducerPool = LazyPool(bound = rpcConfiguration.producerPoolBound) {
// Note how we create new sessions *and* session factories per producer.
// 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))
}
private var sessionFactory: ClientSessionFactory? = null
private var producerSession: ClientSession? = null
private var consumerSession: ClientSession? = null
private var rpcProducer: ClientProducer? = null
private var rpcConsumer: ClientConsumer? = null
private val deduplicationChecker = DeduplicationChecker(rpcConfiguration.deduplicationCacheExpiry)
private val deduplicationSequenceNumber = AtomicLong(0)
/**
* Start the client. This creates the per-client queue, starts the consumer session and the reaper.
@ -187,22 +179,25 @@ class RPCClientProxyHandler(
1,
ThreadFactoryBuilder().setNameFormat("rpc-client-reaper-%d").setDaemon(true).build()
)
sendExecutor = Executors.newSingleThreadExecutor(
ThreadFactoryBuilder().setNameFormat("rpc-client-sender-%d").build()
)
reaperScheduledFuture = reaperExecutor!!.scheduleAtFixedRate(
this::reapObservablesAndNotify,
rpcConfiguration.reapInterval.toMillis(),
rpcConfiguration.reapInterval.toMillis(),
TimeUnit.MILLISECONDS
)
sessionAndProducerPool.run {
it.session.createTemporaryQueue(clientAddress, RoutingType.ANYCAST, clientAddress)
}
val sessionFactory = serverLocator.createSessionFactory()
val session = sessionFactory.createSession(rpcUsername, rpcPassword, false, true, true, false, DEFAULT_ACK_BATCH_SIZE)
val consumer = session.createConsumer(clientAddress)
consumer.setMessageHandler(this@RPCClientProxyHandler::artemisMessageHandler)
sessionAndConsumer = ArtemisConsumer(sessionFactory, session, consumer)
sessionFactory = serverLocator.createSessionFactory()
producerSession = sessionFactory!!.createSession(rpcUsername, rpcPassword, false, true, true, false, DEFAULT_ACK_BATCH_SIZE)
rpcProducer = producerSession!!.createProducer(RPCApi.RPC_SERVER_QUEUE_NAME)
consumerSession = sessionFactory!!.createSession(rpcUsername, rpcPassword, false, true, true, false, DEFAULT_ACK_BATCH_SIZE)
consumerSession!!.createTemporaryQueue(clientAddress, RoutingType.ANYCAST, clientAddress)
rpcConsumer = consumerSession!!.createConsumer(clientAddress)
rpcConsumer!!.setMessageHandler(this::artemisMessageHandler)
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.
@ -212,7 +207,7 @@ class RPCClientProxyHandler(
if (method == toStringMethod) {
return "Client RPC proxy for $rpcOpsClass"
}
if (sessionAndConsumer!!.session.isClosed) {
if (consumerSession!!.isClosed) {
throw RPCException("RPC Proxy is closed")
}
@ -220,23 +215,20 @@ class RPCClientProxyHandler(
callSiteMap?.set(replyId, Throwable("<Call site of root RPC '${method.name}'>"))
try {
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>()
sessionAndProducerPool.run {
val message = it.session.createMessage(false)
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()
} catch (e: RuntimeException) {
// 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.
private fun artemisMessageHandler(message: ClientMessage) {
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" }
when (serverToClient) {
is RPCApi.ServerToClient.RpcReply -> {
@ -325,14 +332,12 @@ class RPCClientProxyHandler(
* @param notify whether to notify observables or not.
*/
private fun close(notify: Boolean = true) {
sessionAndConsumer?.sessionFactory?.close()
sessionFactory?.close()
reaperScheduledFuture?.cancel(false)
observableContext.observableMap.invalidateAll()
reapObservables(notify)
reaperExecutor?.shutdownNow()
sessionAndProducerPool.close().forEach {
it.sessionFactory.close()
}
sendExecutor?.shutdownNow()
// Note the ordering is important, we shut down the consumer *before* the observation executor, otherwise we may
// leak borrowed executors.
val observationExecutors = observationExecutorPool.close()
@ -385,11 +390,7 @@ class RPCClientProxyHandler(
}
if (observableIds != null) {
log.debug { "Reaping ${observableIds.size} observables" }
sessionAndProducerPool.run {
val message = it.session.createMessage(false)
RPCApi.ClientToServer.ObservablesClosed(observableIds).writeToClientMessage(message)
it.producer.send(message)
}
sendMessage(RPCApi.ClientToServer.ObservablesClosed(observableIds))
}
}
}

View File

@ -4,6 +4,7 @@ import com.google.common.base.Stopwatch
import net.corda.client.rpc.internal.RPCClientConfiguration
import net.corda.core.internal.concurrent.doneFuture
import net.corda.core.messaging.RPCOps
import net.corda.core.utilities.days
import net.corda.core.utilities.minutes
import net.corda.core.utilities.seconds
import net.corda.node.services.messaging.RPCServerConfiguration
@ -88,13 +89,10 @@ class RPCPerformanceTests : AbstractRPCTest() {
val proxy = testProxy(
RPCClientConfiguration.default.copy(
cacheConcurrencyLevel = 16,
observationExecutorPoolSize = 2,
producerPoolBound = 2
observationExecutorPoolSize = 2
),
RPCServerConfiguration.default.copy(
rpcThreadPoolSize = 8,
consumerPoolSize = 2,
producerPoolBound = 8
rpcThreadPoolSize = 8
)
)
@ -131,13 +129,10 @@ class RPCPerformanceTests : AbstractRPCTest() {
val proxy = testProxy(
RPCClientConfiguration.default.copy(
reapInterval = 1.seconds,
cacheConcurrencyLevel = 16,
producerPoolBound = 8
cacheConcurrencyLevel = 16
),
RPCServerConfiguration.default.copy(
rpcThreadPoolSize = 8,
consumerPoolSize = 1,
producerPoolBound = 8
rpcThreadPoolSize = 8
)
)
startPublishingFixedRateInjector(
@ -167,9 +162,7 @@ class RPCPerformanceTests : AbstractRPCTest() {
rpcDriver {
val proxy = testProxy(
RPCClientConfiguration.default,
RPCServerConfiguration.default.copy(
consumerPoolSize = 1
)
RPCServerConfiguration.default
)
val numberOfMessages = 1000
val bigSize = 10_000_000

View File

@ -14,7 +14,7 @@ dependencies {
compile project(':core')
// 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 "junit:junit:$junit_version"

View File

@ -18,7 +18,7 @@ import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.CHARLIE_NAME
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 org.junit.After
import org.junit.Before
@ -28,15 +28,15 @@ import kotlin.test.assertNotNull
import kotlin.test.assertNull
class IdentitySyncFlowTests {
private lateinit var mockNet: MockNetwork
private lateinit var mockNet: InternalMockNetwork
@Before
fun before() {
// 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,
threadPerNode = true,
cordappPackages = listOf("net.corda.finance.contracts.asset", "net.corda.finance.schemas")
threadPerNode = true
)
}

View File

@ -3,19 +3,19 @@ package net.corda.confidential
import net.corda.core.identity.*
import net.corda.core.utilities.getOrThrow
import net.corda.testing.core.*
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.internal.InternalMockNetwork
import org.junit.Before
import net.corda.testing.node.startFlow
import org.junit.Test
import kotlin.test.*
class SwapIdentitiesFlowTests {
private lateinit var mockNet: MockNetwork
private lateinit var mockNet: InternalMockNetwork
@Before
fun setup() {
// 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

View File

@ -1,4 +1,4 @@
gradlePluginsVersion=3.0.5
gradlePluginsVersion=4.0.0
kotlinVersion=1.2.20
platformVersion=2
guavaVersion=21.0

View File

@ -78,7 +78,7 @@ dependencies {
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
// Quasar, for suspendable fibres.
compileOnly "co.paralleluniverse:quasar-core:$quasar_version:jdk8"
compileOnly "$quasar_group:quasar-core:$quasar_version:jdk8"
// Thread safety annotations
compile "com.google.code.findbugs:jsr305:$jsr305_version"

View File

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

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

View File

@ -2,8 +2,6 @@ package net.corda.core.cordapp
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,
* 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
* with the attachment containing those class files
* @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
)

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

View File

@ -9,10 +9,12 @@ import net.corda.core.crypto.TransactionSignature
import net.corda.core.crypto.keys
import net.corda.core.identity.Party
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.TrustedAuthorityNotaryService
import net.corda.core.node.services.UniquenessProvider
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.CoreTransaction
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.UntrustworthyData
@ -73,15 +75,17 @@ class NotaryFlow {
return notaryParty
}
/** Notarises the transaction with the [notaryParty], obtains the notary's signature(s). */
@Throws(NotaryException::class)
@Suspendable
protected fun notarise(notaryParty: Party): UntrustworthyData<List<TransactionSignature>> {
return try {
val session = initiateFlow(notaryParty)
val requestSignature = NotarisationRequest(stx.inputs, stx.id).generateSignature(serviceHub)
if (serviceHub.networkMapCache.isValidatingNotary(notaryParty)) {
sendAndReceiveValidating(session)
sendAndReceiveValidating(session, requestSignature)
} else {
sendAndReceiveNonValidating(notaryParty, session)
sendAndReceiveNonValidating(notaryParty, session, requestSignature)
}
} catch (e: NotaryException) {
if (e.error is NotaryError.Conflict) {
@ -92,21 +96,23 @@ class NotaryFlow {
}
@Suspendable
protected open fun sendAndReceiveValidating(session: FlowSession): UntrustworthyData<List<TransactionSignature>> {
subFlow(SendTransactionWithRetry(session, stx))
private fun sendAndReceiveValidating(session: FlowSession, signature: NotarisationRequestSignature): UntrustworthyData<List<TransactionSignature>> {
val payload = NotarisationPayload(stx, signature)
subFlow(NotarySendTransactionFlow(session, payload))
return session.receive()
}
@Suspendable
protected open fun sendAndReceiveNonValidating(notaryParty: Party, session: FlowSession): UntrustworthyData<List<TransactionSignature>> {
val tx: Any = if (stx.isNotaryChangeTransaction()) {
private fun sendAndReceiveNonValidating(notaryParty: Party, session: FlowSession, signature: NotarisationRequestSignature): UntrustworthyData<List<TransactionSignature>> {
val tx: CoreTransaction = if (stx.isNotaryChangeTransaction()) {
stx.notaryChangeTx // Notary change transactions do not support filtering
} else {
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> {
return response.unwrap { signatures ->
signatures.forEach { validateSignature(it, stx.id, notaryParty) }
@ -118,18 +124,18 @@ class NotaryFlow {
check(sig.by in notaryParty.owningKey.keys) { "Invalid signer for the notary result" }
sig.verify(txId)
}
}
/**
* The [SendTransactionWithRetry] flow is equivalent to [SendTransactionFlow] but using [sendAndReceiveWithRetry]
* instead of [sendAndReceive], [SendTransactionWithRetry] is intended to be use by the notary client only.
* The [NotarySendTransactionFlow] flow is similar to [SendTransactionFlow], but uses [NotarisationPayload] as the
* 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
override fun sendPayloadAndReceiveDataRequest(otherSideSession: FlowSession, payload: Any): UntrustworthyData<FetchDataFlow.Request> {
return otherSideSession.sendAndReceiveWithRetry(payload)
}
}
}
/**
* A flow run by a notary service that handles notarisation requests.
@ -186,10 +192,16 @@ class NotaryFlow {
*/
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")
/** Specifies the cause for notarisation request failure. */
@CordaSerializable
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() {
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"
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))
}
}
/** Occurs when the provided transaction fails to verify. */
data class TransactionInvalid(val cause: Throwable) : NotaryError() {
override fun toString() = cause.toString()
}
/** Occurs when the transaction sent for notarisation is assigned to a different notary identity. */
object WrongNotary : NotaryError()
data class General(val cause: String): NotaryError() {
override fun toString() = cause
/** Occurs when the notarisation request signature does not verify for the provided transaction. */
data class RequestSignatureInvalid(val cause: Throwable) : NotaryError() {
override fun toString() = "Request signature invalid: $cause"
}
/** Occurs when the notary service encounters an unexpected issue or becomes temporarily unavailable. */
data class General(val cause: Throwable) : NotaryError() {
override fun toString() = cause.toString()
}
}

View File

@ -28,7 +28,7 @@ open class SendTransactionFlow(otherSide: FlowSession, stx: SignedTransaction) :
*/
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
protected open fun sendPayloadAndReceiveDataRequest(otherSideSession: FlowSession, payload: Any) = otherSideSession.sendAndReceive<FetchDataFlow.Request>(payload)

View File

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

View File

@ -2,9 +2,16 @@
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.crypto.*
import net.corda.core.flows.NotarisationRequest
import net.corda.core.flows.NotarisationRequestSignature
import net.corda.core.flows.NotaryFlow
import net.corda.core.identity.CordaX500Name
import net.corda.core.node.ServiceHub
import net.corda.core.node.ServicesForResolution
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializedBytes
@ -375,3 +382,23 @@ inline fun <T : Any> SerializedBytes<T>.sign(keyPair: KeyPair): SignedData<T> {
}
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)
}

View File

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

View File

@ -2,6 +2,7 @@ package net.corda.core.node
import net.corda.core.DoNotImplement
import net.corda.core.contracts.*
import net.corda.core.cordapp.CordappContext
import net.corda.core.cordapp.CordappProvider
import net.corda.core.crypto.Crypto
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 */
val cordappProvider: CordappProvider
/** Returns the network parameters the node is operating under. */
val networkParameters: NetworkParameters
}
/**
@ -369,4 +373,9 @@ interface ServiceHub : ServicesForResolution {
* node starts.
*/
fun registerUnloadHandler(runOnStop: () -> Unit)
/**
* See [CordappProvider.getAppContext]
*/
fun getAppContext(): CordappContext = cordappProvider.getAppContext()
}

View File

@ -67,12 +67,22 @@ interface NetworkMapCacheBase {
fun track(): DataFeed<List<NodeInfo>, NetworkMapCache.MapChange>
/**
* Look up the node info for a legal name.
* 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.
* Return a [NodeInfo] which has the given legal name for one of its identities, or null if no such node is found.
*
* @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?
/**
* 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. */
fun getNodeByAddress(address: NetworkHostAndPort): NodeInfo?
@ -100,13 +110,6 @@ interface NetworkMapCacheBase {
*/
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 */
fun getPartyInfo(party: Party): PartyInfo?

View File

@ -94,7 +94,7 @@ abstract class TrustedAuthorityNotaryService : NotaryService() {
}
} catch (e: Exception) {
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")))
}
}

View File

@ -3,12 +3,14 @@ package net.corda.core.transactions
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateAndRef
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
* resolve a [FullTransaction]. This type of transaction, wrapped in [SignedTransaction], gets transferred across the
* wire and recorded to storage.
*/
@CordaSerializable
abstract class CoreTransaction : BaseTransaction() {
/** The inputs of this transaction, containing state references only **/
abstract override val inputs: List<StateRef>

View File

@ -5,7 +5,6 @@ import net.corda.core.contracts.ComponentGroupEnum.*
import net.corda.core.crypto.*
import net.corda.core.identity.Party
import net.corda.core.internal.Emoji
import net.corda.core.internal.GlobalProperties
import net.corda.core.node.ServicesForResolution
import net.corda.core.node.services.AttachmentId
import net.corda.core.serialization.CordaSerializable
@ -85,11 +84,12 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
*/
@Throws(AttachmentResolutionException::class, TransactionResolutionException::class)
fun toLedgerTransaction(services: ServicesForResolution): LedgerTransaction {
return toLedgerTransaction(
return toLedgerTransactionInternal(
resolveIdentity = { services.identityService.partyFromKey(it) },
resolveAttachment = { services.attachments.openAttachment(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 TransactionResolutionException if an input was not found not using [resolveStateRef].
*/
@Deprecated("Use toLedgerTransaction(ServicesForTransaction) instead")
@Throws(AttachmentResolutionException::class, TransactionResolutionException::class)
fun toLedgerTransaction(
resolveIdentity: (PublicKey) -> Party?,
resolveAttachment: (SecureHash) -> Attachment?,
resolveStateRef: (StateRef) -> TransactionState<*>?,
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 {
// Look up public keys to authenticated identities. This is just a stub placeholder and will all change in future.
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
val attachments = (attachments.map { resolveAttachment(it) ?: throw AttachmentResolutionException(it) } + contractAttachments).distinct()
val ltx = LedgerTransaction(resolvedInputs, outputs, authenticatedArgs, attachments, id, notary, timeWindow, privacySalt)
checkTransactionSize(ltx)
checkTransactionSize(ltx, maxTransactionSize)
return ltx
}
private fun checkTransactionSize(ltx: LedgerTransaction) {
var remainingTransactionSize = GlobalProperties.networkParameters.maxTransactionSize
private fun checkTransactionSize(ltx: LedgerTransaction, maxTransactionSize: Int) {
var remainingTransactionSize = maxTransactionSize
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
}

View File

@ -58,9 +58,9 @@ class PartialMerkleTreeTest {
hashed = nodes.map { it.serialize().sha256() }
expectedRoot = MerkleTree.getMerkleTree(hashed.toMutableList() + listOf(zeroHash, zeroHash)).hash
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)
}, MEGA_CORP.name).ledger(DUMMY_NOTARY) {
}).ledger(DUMMY_NOTARY) {
unverifiedTransaction {
attachments(Cash.PROGRAM_ID)
output(Cash.PROGRAM_ID, "MEGA_CORP cash",

View File

@ -13,7 +13,6 @@ import net.corda.node.services.persistence.NodeAttachmentService
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.singleIdentity
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.MockNodeParameters
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.startFlow

View File

@ -11,12 +11,13 @@ import net.corda.core.identity.groupAbstractPartyByWellKnownParty
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.getOrThrow
import net.corda.node.internal.StartedNode
import net.corda.testing.contracts.DummyContract
import net.corda.testing.core.*
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.internal.InternalMockNetwork
import net.corda.testing.node.internal.InternalMockNetwork.MockNode
import net.corda.testing.node.startFlow
import org.junit.After
import org.junit.Before
@ -29,10 +30,10 @@ class CollectSignaturesFlowTests {
private val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB"))
}
private lateinit var mockNet: MockNetwork
private lateinit var aliceNode: StartedMockNode
private lateinit var bobNode: StartedMockNode
private lateinit var charlieNode: StartedMockNode
private lateinit var mockNet: InternalMockNetwork
private lateinit var aliceNode: StartedNode<MockNode>
private lateinit var bobNode: StartedNode<MockNode>
private lateinit var charlieNode: StartedNode<MockNode>
private lateinit var alice: Party
private lateinit var bob: Party
private lateinit var charlie: Party
@ -40,7 +41,7 @@ class CollectSignaturesFlowTests {
@Before
fun setup() {
mockNet = MockNetwork(cordappPackages = listOf("net.corda.testing.contracts"))
mockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.testing.contracts"))
aliceNode = mockNet.createPartyNode(ALICE_NAME)
bobNode = mockNet.createPartyNode(BOB_NAME)
charlieNode = mockNet.createPartyNode(CHARLIE_NAME)
@ -136,7 +137,7 @@ class CollectSignaturesFlowTests {
@Test
fun `fails when not signed by initiator`() {
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 flow = aliceNode.services.startFlow(CollectSignaturesFlow(ptx, emptySet()))
mockNet.runNetwork()

View File

@ -7,9 +7,9 @@ import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.nodeapi.internal.crypto.X509KeyStore
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.getTestPartyAndCertificate
import net.corda.testing.internal.DEV_ROOT_CA
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test

View File

@ -8,10 +8,11 @@ import net.corda.core.identity.Party
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.sequence
import net.corda.node.internal.StartedNode
import net.corda.testing.contracts.DummyContract
import net.corda.testing.node.MockNetwork
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 org.junit.After
import org.junit.Before
@ -27,17 +28,17 @@ import kotlin.test.assertNull
// DOCSTART 3
class ResolveTransactionsFlowTest {
private lateinit var mockNet: MockNetwork
private lateinit var notaryNode: StartedMockNode
private lateinit var megaCorpNode: StartedMockNode
private lateinit var miniCorpNode: StartedMockNode
private lateinit var mockNet: InternalMockNetwork
private lateinit var notaryNode: StartedNode<MockNode>
private lateinit var megaCorpNode: StartedNode<MockNode>
private lateinit var miniCorpNode: StartedNode<MockNode>
private lateinit var megaCorp: Party
private lateinit var miniCorp: Party
private lateinit var notary: Party
@Before
fun setup() {
mockNet = MockNetwork(cordappPackages = listOf("net.corda.testing.contracts"))
mockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.testing.contracts"))
notaryNode = mockNet.defaultNotaryNode
megaCorpNode = mockNet.createPartyNode(CordaX500Name("MegaCorp", "London", "GB"))
miniCorpNode = mockNet.createPartyNode(CordaX500Name("MiniCorp", "London", "GB"))

View File

@ -18,7 +18,6 @@ import net.corda.node.services.persistence.NodeAttachmentService
import net.corda.nodeapi.internal.persistence.currentDBSession
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.MockNodeParameters
import net.corda.testing.core.singleIdentity
import net.corda.testing.node.internal.InternalMockNetwork

View File

@ -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 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 megaCorpServices = MockServices(listOf("net.corda.core.serialization"), rigorousMock(), MEGA_CORP.name, MEGA_CORP_KEY)
val notaryServices = MockServices(listOf("net.corda.core.serialization"), rigorousMock(), DUMMY_NOTARY.name, DUMMY_NOTARY_KEY)
val megaCorpServices = MockServices(listOf("net.corda.core.serialization"), MEGA_CORP.name, rigorousMock(), MEGA_CORP_KEY)
val notaryServices = MockServices(listOf("net.corda.core.serialization"), DUMMY_NOTARY.name, rigorousMock(), DUMMY_NOTARY_KEY)
lateinit var tx: TransactionBuilder
@Before
@ -107,7 +107,7 @@ class TransactionSerializationTests {
Command(TestCash.Commands.Move(), DUMMY_KEY_2.public))
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)
stx.copy(sigs = stx2.sigs).verifyRequiredSignatures()

View File

@ -10,6 +10,7 @@ import net.corda.core.identity.Party
import net.corda.node.services.api.IdentityServiceInternal
import net.corda.testing.contracts.DummyContract
import net.corda.testing.core.*
import net.corda.testing.internal.MockCordappProvider
import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.MockServices
import org.junit.Before
@ -29,14 +30,15 @@ class LedgerTransactionQueryTests {
@JvmField
val testSerialization = SerializationEnvironmentRule()
private val keyPair = generateKeyPair()
private val services = MockServices(emptyList(), rigorousMock<IdentityServiceInternal>().also {
private val services = MockServices(emptyList(), CordaX500Name("MegaCorp", "London", "GB"),
rigorousMock<IdentityServiceInternal>().also {
doReturn(null).whenever(it).partyFromKey(keyPair.public)
}, CordaX500Name("MegaCorp", "London", "GB"), keyPair)
}, keyPair)
private val identity: Party = services.myInfo.singleIdentity()
@Before
fun setup() {
services.mockCordappProvider.addMockCordapp(DummyContract.PROGRAM_ID, services.attachments)
services.addMockCordapp(DummyContract.PROGRAM_ID)
}
interface Commands {

View File

@ -51,7 +51,8 @@ class TransactionEncumbranceTests {
class DummyTimeLock : Contract {
override fun verify(tx: LedgerTransaction) {
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 {
"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,
rigorousMock<IdentityServiceInternal>().also {
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
}, MEGA_CORP.name)
})
@Test
fun `state can be encumbered`() {

View File

@ -615,6 +615,24 @@ which the signatures are allowed to be missing:
:end-before: DOCEND 36
: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
``SignaturesMissingException`` is thrown.

View File

@ -7,6 +7,13 @@ from previous releases. Please refer to :doc:`upgrade-notes` for detailed instru
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.
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.
@ -80,7 +87,10 @@ R3 Corda 3.0 Developer Preview
: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
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
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
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].
* 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
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
parts of the api are stable. The bash script ``tools\scripts\update-test-packages.sh`` can be used to smooth the upgrade
process for existing projects.
``net.corda.testing.services`` package. Moving existing classes out of the ``net.corda.testing.*`` package
will help make it clearer which parts of the api are stable. Scripts have been provided to smooth the upgrade
process for existing projects in the ``tools\scripts`` directory of the Corda repo.
.. _changelog_v2:

View File

@ -203,8 +203,8 @@ path to the node's base directory.
:publicKeyFile: Path to the public key file for SSH authentication.
: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.
Default Jolokia access url is http://127.0.0.1:7005/jolokia/
: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:port/jolokia/
: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.

View File

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

View File

@ -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/>`_
(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
our `sample CorDapps <https://www.corda.net/samples/>`_ to the ``plugins`` directory
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 ``cordapps`` directory
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
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,
download one of our `sample CorDapps <https://www.corda.net/samples/>`_ to the ``plugins`` directory
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 ``cordapps`` directory
3. Save the below as ``C:\Corda\node.conf``. See :doc:`corda-configuration-file` for a description of these options

View File

@ -18,10 +18,10 @@ import net.corda.node.services.Permissions.Companion.startFlow
import net.corda.testing.core.*
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.driver
import net.corda.testing.node.User
import net.corda.testing.internal.IntegrationTest
import net.corda.testing.internal.IntegrationTestSchemas
import net.corda.testing.internal.toDatabaseSchemaName
import net.corda.testing.node.User
import org.junit.ClassRule
import org.junit.Test
import kotlin.test.assertEquals

View File

@ -540,12 +540,21 @@ public class FlowCookbookJava {
// DOCEND 35
// 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.
// DOCSTART 36
onceSignedTx.verifySignaturesExcept(counterpartyPubKey);
// 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
// present. BE VERY CAREFUL - this function provides no guarantees
// that the signatures are correct, or that none are missing.

View File

@ -522,12 +522,19 @@ class InitiatorFlow(val arg1: Boolean, val arg2: Int, private val counterparty:
// DOCEND 35
// 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.
// DOCSTART 36
onceSignedTx.verifySignaturesExcept(counterpartyPubKey)
// 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
// present. BE VERY CAREFUL - this function provides no guarantees
// that the signatures are correct, or that none are missing.

View File

@ -74,14 +74,12 @@ class CustomVaultQueryTest {
private fun getBalances(): Pair<Map<Currency, Amount<Currency>>, Map<Currency, Amount<Currency>>> {
// Print out the balances
val balancesNodesA =
nodeA.database.transaction {
val balancesNodesA = nodeA.transaction {
nodeA.services.getCashBalances()
}
println("BalanceA\n" + balancesNodesA)
val balancesNodesB =
nodeB.database.transaction {
val balancesNodesB = nodeB.transaction {
nodeB.services.getCashBalances()
}
println("BalanceB\n" + balancesNodesB)

View File

@ -68,13 +68,14 @@ class FxTransactionBuildTutorialTest {
doIt.getOrThrow()
// Get the balances when the vault updates
nodeAVaultUpdate.get()
val balancesA = nodeA.database.transaction {
val balancesA = nodeA.transaction {
nodeA.services.getCashBalances()
}
nodeBVaultUpdate.get()
val balancesB = nodeB.database.transaction {
val balancesB = nodeB.transaction {
nodeB.services.getCashBalances()
}
println("BalanceA\n" + balancesA)
println("BalanceB\n" + balancesB)
// Verify the transfers occurred as expected
@ -86,10 +87,10 @@ class FxTransactionBuildTutorialTest {
private fun printBalances() {
// Print out the balances
nodeA.database.transaction {
nodeA.transaction {
println("BalanceA\n" + nodeA.services.getCashBalances())
}
nodeB.database.transaction {
nodeB.transaction {
println("BalanceB\n" + nodeB.services.getCashBalances())
}
}

View File

@ -38,11 +38,11 @@ class CommercialPaperTest {
@Rule
@JvmField
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(null).whenever(it).partyFromKey(BIG_CORP_PUBKEY)
doReturn(null).whenever(it).partyFromKey(ALICE_PUBKEY)
}, MEGA_CORP.name)
})
// DOCSTART 1
fun getPaper(): ICommercialPaperState = CommercialPaper.State(

View File

@ -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
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 following JMX statistics are exported:

View File

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

View File

@ -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
* 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
:doc:`shell`.

View 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"
```

View 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
View File

@ -0,0 +1 @@
*.jar

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,9 @@
package net.corda.behave.process.output
interface OutputListener {
fun onNewLine(line: String)
fun onEndOfStream()
}

View File

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

View File

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

View File

@ -0,0 +1,5 @@
package net.corda.behave.service
import net.corda.behave.node.configuration.Configuration
typealias ServiceInitiator = (Configuration) -> Service

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View File

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

View File

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