diff --git a/.ci/api-current.txt b/.ci/api-current.txt index 57badada35..485fb66e00 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -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 (String, Throwable) +## public final class net.corda.core.cordapp.CordappContext extends java.lang.Object - public (net.corda.core.cordapp.Cordapp, net.corda.core.crypto.SecureHash, ClassLoader) + public (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 (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 (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 (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 (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 (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 (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 (String) - @org.jetbrains.annotations.NotNull public final String component1() - @org.jetbrains.annotations.NotNull public final net.corda.core.flows.NotaryError$General copy(String) + public (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 (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 () @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 (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 (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 (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 (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 (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 (List) public (List, net.corda.testing.node.MockNetworkParameters) - public (List, net.corda.testing.node.MockNetworkParameters, boolean, boolean, net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy, boolean, List, int) + public (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 () - public (boolean, boolean, net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy, boolean, List, int) + public (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 () @@ -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 () + public (List) public (List, net.corda.core.identity.CordaX500Name) - public (List, net.corda.core.node.services.IdentityService, net.corda.core.identity.CordaX500Name) + public (List, net.corda.core.identity.CordaX500Name, net.corda.core.node.services.IdentityService) public (net.corda.core.identity.CordaX500Name) - public (net.corda.core.node.services.IdentityService, net.corda.core.identity.CordaX500Name) + public (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 (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 (net.corda.core.utilities.NetworkHostAndPort) public (net.corda.core.utilities.NetworkHostAndPort, net.corda.client.rpc.CordaRPCClientConfiguration) - public (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 (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 (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() -## diff --git a/.ci/check-api-changes.sh b/.ci/check-api-changes.sh index 987d0ced3c..5896f6ebbf 100755 --- a/.ci/check-api-changes.sh +++ b/.ci/check-api-changes.sh @@ -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 "^$" < - - - + + + + @@ -25,40 +26,28 @@ - - - - - + + - - - - - - - - - @@ -70,21 +59,10 @@ - - - - - - - - - - - @@ -120,7 +98,6 @@ - @@ -129,39 +106,21 @@ - - - - - - - - - - - - - - - - - - @@ -188,7 +147,4 @@ - - \ No newline at end of file diff --git a/build.gradle b/build.gradle index 46a7a34f95..2bd54c41d8 100644 --- a/build.gradle +++ b/build.gradle @@ -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' diff --git a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt index 8e3b4ed405..0ac85510f9 100644 --- a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt +++ b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt @@ -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(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() + 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().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(broker.hostAndPort!!).getOrThrow() + } + } } fun RPCDriverDSL.pollUntilClientNumber(server: RpcServerHandle, expected: Int) { diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt index 0c72192854..ef1eb579f2 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt @@ -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 diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/CordaRPCClientUtils.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/CordaRPCClientUtils.kt new file mode 100644 index 0000000000..d5787f5dec --- /dev/null +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/CordaRPCClientUtils.kt @@ -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) \ No newline at end of file diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClient.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClient.kt index 756f07216e..79ad14f5dd 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClient.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClient.kt @@ -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 ) } } diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClientProxyHandler.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClientProxyHandler.kt index 813569dc89..2f65f21e40 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClientProxyHandler.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClientProxyHandler.kt @@ -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("")) 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() - 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() + require(rpcReplyMap.put(replyId, replyFuture) == null) { + "Generated several RPC requests with same ID $replyId" } + 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)) } } } diff --git a/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPerformanceTests.kt b/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPerformanceTests.kt index 19581b81d4..85a8d4e019 100644 --- a/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPerformanceTests.kt +++ b/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPerformanceTests.kt @@ -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 diff --git a/confidential-identities/build.gradle b/confidential-identities/build.gradle index c6a29c0d2e..309dce7946 100644 --- a/confidential-identities/build.gradle +++ b/confidential-identities/build.gradle @@ -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" diff --git a/confidential-identities/src/test/kotlin/net/corda/confidential/IdentitySyncFlowTests.kt b/confidential-identities/src/test/kotlin/net/corda/confidential/IdentitySyncFlowTests.kt index aae595ca7f..c171ee62c8 100644 --- a/confidential-identities/src/test/kotlin/net/corda/confidential/IdentitySyncFlowTests.kt +++ b/confidential-identities/src/test/kotlin/net/corda/confidential/IdentitySyncFlowTests.kt @@ -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 ) } diff --git a/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt b/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt index f8fbc6668d..111b8e4ac6 100644 --- a/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt +++ b/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt @@ -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 diff --git a/constants.properties b/constants.properties index 930a39acd2..49ea962adb 100644 --- a/constants.properties +++ b/constants.properties @@ -1,4 +1,4 @@ -gradlePluginsVersion=3.0.5 +gradlePluginsVersion=4.0.0 kotlinVersion=1.2.20 platformVersion=2 guavaVersion=21.0 @@ -6,4 +6,4 @@ bouncycastleVersion=1.57 typesafeConfigVersion=1.3.1 jsr305Version=3.0.2 artifactoryPluginVersion=4.4.18 -snakeYamlVersion=1.19 \ No newline at end of file +snakeYamlVersion=1.19 diff --git a/core/build.gradle b/core/build.gradle index 0b6b615236..ebb9550690 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -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" diff --git a/core/src/main/kotlin/net/corda/core/cordapp/ConfigException.kt b/core/src/main/kotlin/net/corda/core/cordapp/ConfigException.kt new file mode 100644 index 0000000000..affbce62dd --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/cordapp/ConfigException.kt @@ -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) \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/cordapp/CordappConfig.kt b/core/src/main/kotlin/net/corda/core/cordapp/CordappConfig.kt new file mode 100644 index 0000000000..664e69fe80 --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/cordapp/CordappConfig.kt @@ -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 +} \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/cordapp/CordappContext.kt b/core/src/main/kotlin/net/corda/core/cordapp/CordappContext.kt index 3c7be4a3e2..b91acec452 100644 --- a/core/src/main/kotlin/net/corda/core/cordapp/CordappContext.kt +++ b/core/src/main/kotlin/net/corda/core/cordapp/CordappContext.kt @@ -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 +) diff --git a/core/src/main/kotlin/net/corda/core/flows/NotarisationRequest.kt b/core/src/main/kotlin/net/corda/core/flows/NotarisationRequest.kt new file mode 100644 index 0000000000..166d38dc68 --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/flows/NotarisationRequest.kt @@ -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, val transactionId: SecureHash) { + companion object { + /** Sorts in ascending order first by transaction hash, then by output index. */ + private val stateRefComparator = compareBy({ 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 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 +} \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt b/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt index 8e38471384..9c2cbee976 100644 --- a/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt @@ -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> { 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> { - subFlow(SendTransactionWithRetry(session, stx)) + private fun sendAndReceiveValidating(session: FlowSession, signature: NotarisationRequestSignature): UntrustworthyData> { + val payload = NotarisationPayload(stx, signature) + subFlow(NotarySendTransactionFlow(session, payload)) return session.receive() } @Suspendable - protected open fun sendAndReceiveNonValidating(notaryParty: Party, session: FlowSession): UntrustworthyData> { - val tx: Any = if (stx.isNotaryChangeTransaction()) { + private fun sendAndReceiveNonValidating(notaryParty: Party, session: FlowSession, signature: NotarisationRequestSignature): UntrustworthyData> { + 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>, notaryParty: Party): List { return response.unwrap { signatures -> signatures.forEach { validateSignature(it, stx.id, notaryParty) } @@ -118,16 +124,16 @@ class NotaryFlow { check(sig.by in notaryParty.owningKey.keys) { "Invalid signer for the notary result" } 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. - */ - private class SendTransactionWithRetry(otherSideSession: FlowSession, stx: SignedTransaction) : SendTransactionFlow(otherSideSession, stx) { - @Suspendable - override fun sendPayloadAndReceiveDataRequest(otherSideSession: FlowSession, payload: Any): UntrustworthyData { - return otherSideSession.sendAndReceiveWithRetry(payload) + /** + * The [NotarySendTransactionFlow] flow is similar to [SendTransactionFlow], but uses [NotarisationPayload] as the + * initial message, and retries message delivery. + */ + private class NotarySendTransactionFlow(otherSide: FlowSession, payload: NotarisationPayload) : DataVendingFlow(otherSide, payload) { + @Suspendable + override fun sendPayloadAndReceiveDataRequest(otherSideSession: FlowSession, payload: Any): UntrustworthyData { + return otherSideSession.sendAndReceiveWithRetry(payload) + } } } @@ -186,10 +192,16 @@ class NotaryFlow { */ data class TransactionParts(val id: SecureHash, val inputs: List, 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) : 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() } } diff --git a/core/src/main/kotlin/net/corda/core/flows/SendTransactionFlow.kt b/core/src/main/kotlin/net/corda/core/flows/SendTransactionFlow.kt index 9352d3e178..16f402c486 100644 --- a/core/src/main/kotlin/net/corda/core/flows/SendTransactionFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/SendTransactionFlow.kt @@ -28,7 +28,7 @@ open class SendTransactionFlow(otherSide: FlowSession, stx: SignedTransaction) : */ open class SendStateAndRefFlow(otherSideSession: FlowSession, stateAndRefs: List>) : DataVendingFlow(otherSideSession, stateAndRefs) -sealed class DataVendingFlow(val otherSideSession: FlowSession, val payload: Any) : FlowLogic() { +open class DataVendingFlow(val otherSideSession: FlowSession, val payload: Any) : FlowLogic() { @Suspendable protected open fun sendPayloadAndReceiveDataRequest(otherSideSession: FlowSession, payload: Any) = otherSideSession.sendAndReceive(payload) diff --git a/core/src/main/kotlin/net/corda/core/internal/GlobalProperties.kt b/core/src/main/kotlin/net/corda/core/internal/GlobalProperties.kt deleted file mode 100644 index bedac0269a..0000000000 --- a/core/src/main/kotlin/net/corda/core/internal/GlobalProperties.kt +++ /dev/null @@ -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 - } -} \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt index 18dcced593..8a17ec5966 100644 --- a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt @@ -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 SerializedBytes.sign(keyPair: KeyPair): SignedData { } 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) +} \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/node/NodeInfo.kt b/core/src/main/kotlin/net/corda/core/node/NodeInfo.kt index 1977b601d9..d24fa67e7a 100644 --- a/core/src/main/kotlin/net/corda/core/node/NodeInfo.kt +++ b/core/src/main/kotlin/net/corda/core/node/NodeInfo.kt @@ -26,7 +26,9 @@ data class NodeInfo(val addresses: List, ) { // 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? = null diff --git a/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt b/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt index 1832ac1cb5..0d2f36ff43 100644 --- a/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt +++ b/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt @@ -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() } diff --git a/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt b/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt index 69ab1a7307..395e5cbccb 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt @@ -67,12 +67,22 @@ interface NetworkMapCacheBase { fun track(): DataFeed, 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 + /** 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 - /** - * 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 - /** Returns information about the party, which may be a specific node or a service */ fun getPartyInfo(party: Party): PartyInfo? diff --git a/core/src/main/kotlin/net/corda/core/node/services/NotaryService.kt b/core/src/main/kotlin/net/corda/core/node/services/NotaryService.kt index bc77fe9683..68badaa4e7 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/NotaryService.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/NotaryService.kt @@ -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"))) } } diff --git a/core/src/main/kotlin/net/corda/core/transactions/BaseTransactions.kt b/core/src/main/kotlin/net/corda/core/transactions/BaseTransactions.kt index 2d82c4850b..10031d795e 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/BaseTransactions.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/BaseTransactions.kt @@ -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 diff --git a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt index d973152262..8e397b9051 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt @@ -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, 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, 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) -> AttachmentId? + ): LedgerTransaction { + return toLedgerTransactionInternal(resolveIdentity, resolveAttachment, resolveStateRef, resolveContractAttachment, 10485760) + } + + private fun toLedgerTransactionInternal( + resolveIdentity: (PublicKey) -> Party?, + resolveAttachment: (SecureHash) -> Attachment?, + resolveStateRef: (StateRef) -> TransactionState<*>?, + resolveContractAttachment: (TransactionState) -> 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, 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 } diff --git a/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt b/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt index 725dd34c2b..88df92acf3 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt @@ -58,19 +58,19 @@ 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().also { + testLedger = MockServices(emptyList(), MEGA_CORP.name, rigorousMock().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", - Cash.State( - amount = 1000.DOLLARS `issued by` MEGA_CORP.ref(1, 1), - owner = MEGA_CORP)) + Cash.State( + amount = 1000.DOLLARS `issued by` MEGA_CORP.ref(1, 1), + owner = MEGA_CORP)) output(Cash.PROGRAM_ID, "dummy cash 1", - Cash.State( - amount = 900.DOLLARS `issued by` MEGA_CORP.ref(1, 1), - owner = MINI_CORP)) + Cash.State( + amount = 900.DOLLARS `issued by` MEGA_CORP.ref(1, 1), + owner = MINI_CORP)) } transaction { attachments(Cash.PROGRAM_ID) diff --git a/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt b/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt index 6c31273511..608d7c894e 100644 --- a/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt @@ -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 diff --git a/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt b/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt index 2dfbb59352..e2c24061a3 100644 --- a/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt @@ -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 + private lateinit var bobNode: StartedNode + private lateinit var charlieNode: StartedNode 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() diff --git a/core/src/test/kotlin/net/corda/core/identity/PartyAndCertificateTest.kt b/core/src/test/kotlin/net/corda/core/identity/PartyAndCertificateTest.kt index f89f6a7829..8831e5905e 100644 --- a/core/src/test/kotlin/net/corda/core/identity/PartyAndCertificateTest.kt +++ b/core/src/test/kotlin/net/corda/core/identity/PartyAndCertificateTest.kt @@ -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 diff --git a/core/src/test/kotlin/net/corda/core/internal/ResolveTransactionsFlowTest.kt b/core/src/test/kotlin/net/corda/core/internal/ResolveTransactionsFlowTest.kt index fa6ab2b0ff..f6d9432f6a 100644 --- a/core/src/test/kotlin/net/corda/core/internal/ResolveTransactionsFlowTest.kt +++ b/core/src/test/kotlin/net/corda/core/internal/ResolveTransactionsFlowTest.kt @@ -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 + private lateinit var megaCorpNode: StartedNode + private lateinit var miniCorpNode: StartedNode 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")) diff --git a/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt b/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt index 8551845849..88acc9a8c2 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt @@ -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 diff --git a/core/src/test/kotlin/net/corda/core/serialization/TransactionSerializationTests.kt b/core/src/test/kotlin/net/corda/core/serialization/TransactionSerializationTests.kt index 1d326554e3..d7894ed1ee 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/TransactionSerializationTests.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/TransactionSerializationTests.kt @@ -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() diff --git a/core/src/test/kotlin/net/corda/core/transactions/LedgerTransactionQueryTests.kt b/core/src/test/kotlin/net/corda/core/transactions/LedgerTransactionQueryTests.kt index 973752a197..9482764d99 100644 --- a/core/src/test/kotlin/net/corda/core/transactions/LedgerTransactionQueryTests.kt +++ b/core/src/test/kotlin/net/corda/core/transactions/LedgerTransactionQueryTests.kt @@ -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().also { - doReturn(null).whenever(it).partyFromKey(keyPair.public) - }, CordaX500Name("MegaCorp", "London", "GB"), keyPair) + private val services = MockServices(emptyList(), CordaX500Name("MegaCorp", "London", "GB"), + rigorousMock().also { + doReturn(null).whenever(it).partyFromKey(keyPair.public) + }, 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 { diff --git a/core/src/test/kotlin/net/corda/core/transactions/TransactionEncumbranceTests.kt b/core/src/test/kotlin/net/corda/core/transactions/TransactionEncumbranceTests.kt index 1a3878b586..6b7e98617f 100644 --- a/core/src/test/kotlin/net/corda/core/transactions/TransactionEncumbranceTests.kt +++ b/core/src/test/kotlin/net/corda/core/transactions/TransactionEncumbranceTests.kt @@ -51,7 +51,8 @@ class TransactionEncumbranceTests { class DummyTimeLock : Contract { override fun verify(tx: LedgerTransaction) { val timeLockInput = tx.inputsOfType().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().also { - doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY) - }, MEGA_CORP.name) + private val ledgerServices = MockServices(emptyList(), MEGA_CORP.name, + rigorousMock().also { + doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY) + }) @Test fun `state can be encumbered`() { diff --git a/docs/source/api-transactions.rst b/docs/source/api-transactions.rst index 0c02f4b565..888aed09e7 100644 --- a/docs/source/api-transactions.rst +++ b/docs/source/api-transactions.rst @@ -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. diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 82bbcc2645..d0882313f7 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -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: diff --git a/docs/source/corda-configuration-file.rst b/docs/source/corda-configuration-file.rst index 8cf4e49d97..d582d6f1af 100644 --- a/docs/source/corda-configuration-file.rst +++ b/docs/source/corda-configuration-file.rst @@ -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. diff --git a/docs/source/cordapp-build-systems.rst b/docs/source/cordapp-build-systems.rst index 43788a1c7d..380c9aed42 100644 --- a/docs/source/cordapp-build-systems.rst +++ b/docs/source/cordapp-build-systems.rst @@ -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 ``/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 ``/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 `_ 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 +`_. + + + diff --git a/docs/source/deploying-a-node.rst b/docs/source/deploying-a-node.rst index 6b17b9dd5d..6918cc743d 100644 --- a/docs/source/deploying-a-node.rst +++ b/docs/source/deploying-a-node.rst @@ -30,8 +30,8 @@ handling, and ensures the Corda service is run at boot. 4. (Optional) Download the `Corda webserver jar `_ (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 `_ 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 `_ 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 `_ 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 `_ to the ``cordapps`` directory 3. Save the below as ``C:\Corda\node.conf``. See :doc:`corda-configuration-file` for a description of these options @@ -282,4 +282,4 @@ You can verify Corda is running by connecting to your RPC port from another host ``telnet your-hostname.example.com 10002`` If you receive the message "Escape character is ^]", Corda is running and accessible. Press Ctrl-] and Ctrl-D to exit -telnet. \ No newline at end of file +telnet. diff --git a/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt b/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt index 4b2727ff42..173af51a61 100644 --- a/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt +++ b/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt @@ -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 diff --git a/docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java b/docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java index b9356d1d9f..d17615841d 100644 --- a/docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java +++ b/docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java @@ -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. diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt index 81792eb864..08827f1d30 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt @@ -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. diff --git a/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt b/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt index 2b194b1e94..6867695965 100644 --- a/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt +++ b/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt @@ -74,16 +74,14 @@ class CustomVaultQueryTest { private fun getBalances(): Pair>, Map>> { // Print out the balances - val balancesNodesA = - nodeA.database.transaction { - nodeA.services.getCashBalances() - } + val balancesNodesA = nodeA.transaction { + nodeA.services.getCashBalances() + } println("BalanceA\n" + balancesNodesA) - val balancesNodesB = - nodeB.database.transaction { - nodeB.services.getCashBalances() - } + val balancesNodesB = nodeB.transaction { + nodeB.services.getCashBalances() + } println("BalanceB\n" + balancesNodesB) return Pair(balancesNodesA, balancesNodesB) diff --git a/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt b/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt index 0805565e03..67a4c919f7 100644 --- a/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt +++ b/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt @@ -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()) } } diff --git a/docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt b/docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt index ef7e62920b..3b3d5c003a 100644 --- a/docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt +++ b/docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt @@ -38,11 +38,11 @@ class CommercialPaperTest { @Rule @JvmField val testSerialization = SerializationEnvironmentRule() - private val ledgerServices = MockServices(emptyList(), rigorousMock().also { + private val ledgerServices = MockServices(emptyList(), MEGA_CORP.name, rigorousMock().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( diff --git a/docs/source/node-administration.rst b/docs/source/node-administration.rst index 3ac4cfcd80..a6242173fb 100644 --- a/docs/source/node-administration.rst +++ b/docs/source/node-administration.rst @@ -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: diff --git a/docs/source/serialization.rst b/docs/source/serialization.rst index 75a35705c0..36ba5addd5 100644 --- a/docs/source/serialization.rst +++ b/docs/source/serialization.rst @@ -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, val transactionId: SecureHash) { + companion object { + private val stateRefComparator = compareBy({ 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, val transactionId: SecureHash) { + companion object { + private val stateRefComparator = compareBy({ 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`. - - diff --git a/docs/source/tutorial-attachments.rst b/docs/source/tutorial-attachments.rst index cbb88cf92b..df5e4b5a5e 100644 --- a/docs/source/tutorial-attachments.rst +++ b/docs/source/tutorial-attachments.rst @@ -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`. diff --git a/experimental/behave/README.md b/experimental/behave/README.md new file mode 100644 index 0000000000..b3b37ca2e9 --- /dev/null +++ b/experimental/behave/README.md @@ -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" +``` \ No newline at end of file diff --git a/experimental/behave/build.gradle b/experimental/behave/build.gradle new file mode 100644 index 0000000000..4148d9d462 --- /dev/null +++ b/experimental/behave/build.gradle @@ -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 \ No newline at end of file diff --git a/experimental/behave/deps/.gitignore b/experimental/behave/deps/.gitignore new file mode 100644 index 0000000000..d392f0e82c --- /dev/null +++ b/experimental/behave/deps/.gitignore @@ -0,0 +1 @@ +*.jar diff --git a/experimental/behave/deps/corda/3.0.0/.gitkeep b/experimental/behave/deps/corda/3.0.0/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/experimental/behave/deps/corda/3.0.0/apps/.gitkeep b/experimental/behave/deps/corda/3.0.0/apps/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/experimental/behave/deps/drivers/.gitkeep b/experimental/behave/deps/drivers/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/experimental/behave/deps/drivers/README.md b/experimental/behave/deps/drivers/README.md new file mode 100644 index 0000000000..19ff783c5f --- /dev/null +++ b/experimental/behave/deps/drivers/README.md @@ -0,0 +1,3 @@ +Download and store database drivers here; for example: + - h2-1.4.196.jar + - mssql-jdbc-6.2.2.jre8.jar diff --git a/experimental/behave/prepare.sh b/experimental/behave/prepare.sh new file mode 100755 index 0000000000..02c5df162b --- /dev/null +++ b/experimental/behave/prepare.sh @@ -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 diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/Utilities.kt b/experimental/behave/src/main/kotlin/net/corda/behave/Utilities.kt new file mode 100644 index 0000000000..1fdc9f9bcc --- /dev/null +++ b/experimental/behave/src/main/kotlin/net/corda/behave/Utilities.kt @@ -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) diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/database/DatabaseConfigurationTemplate.kt b/experimental/behave/src/main/kotlin/net/corda/behave/database/DatabaseConfigurationTemplate.kt new file mode 100644 index 0000000000..0acff416e8 --- /dev/null +++ b/experimental/behave/src/main/kotlin/net/corda/behave/database/DatabaseConfigurationTemplate.kt @@ -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() + +} \ No newline at end of file diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/database/DatabaseConnection.kt b/experimental/behave/src/main/kotlin/net/corda/behave/database/DatabaseConnection.kt new file mode 100644 index 0000000000..1916000bb2 --- /dev/null +++ b/experimental/behave/src/main/kotlin/net/corda/behave/database/DatabaseConnection.kt @@ -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 + } + } + +} \ No newline at end of file diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/database/DatabaseSettings.kt b/experimental/behave/src/main/kotlin/net/corda/behave/database/DatabaseSettings.kt new file mode 100644 index 0000000000..4e870f77de --- /dev/null +++ b/experimental/behave/src/main/kotlin/net/corda/behave/database/DatabaseSettings.kt @@ -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() + + 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 { + return serviceInitiators.map { it(config) } + } + + val template: DatabaseConfigurationTemplate + get() = databaseConfigTemplate + +} diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/database/DatabaseType.kt b/experimental/behave/src/main/kotlin/net/corda/behave/database/DatabaseType.kt new file mode 100644 index 0000000000..851a8d1387 --- /dev/null +++ b/experimental/behave/src/main/kotlin/net/corda/behave/database/DatabaseType.kt @@ -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 + } + + } + +} \ No newline at end of file diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/database/configuration/H2ConfigurationTemplate.kt b/experimental/behave/src/main/kotlin/net/corda/behave/database/configuration/H2ConfigurationTemplate.kt new file mode 100644 index 0000000000..6bdfa2ad80 --- /dev/null +++ b/experimental/behave/src/main/kotlin/net/corda/behave/database/configuration/H2ConfigurationTemplate.kt @@ -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} + """ + } + +} \ No newline at end of file diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/database/configuration/SqlServerConfigurationTemplate.kt b/experimental/behave/src/main/kotlin/net/corda/behave/database/configuration/SqlServerConfigurationTemplate.kt new file mode 100644 index 0000000000..370a241d20 --- /dev/null +++ b/experimental/behave/src/main/kotlin/net/corda/behave/database/configuration/SqlServerConfigurationTemplate.kt @@ -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}" + |} + """ + } + +} \ No newline at end of file diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/file/FileUtilities.kt b/experimental/behave/src/main/kotlin/net/corda/behave/file/FileUtilities.kt new file mode 100644 index 0000000000..405404fcb2 --- /dev/null +++ b/experimental/behave/src/main/kotlin/net/corda/behave/file/FileUtilities.kt @@ -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) diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/file/LogSource.kt b/experimental/behave/src/main/kotlin/net/corda/behave/file/LogSource.kt new file mode 100644 index 0000000000..4649a4898a --- /dev/null +++ b/experimental/behave/src/main/kotlin/net/corda/behave/file/LogSource.kt @@ -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 { + 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() + 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 + } + +} \ No newline at end of file diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/logging/LogUtilities.kt b/experimental/behave/src/main/kotlin/net/corda/behave/logging/LogUtilities.kt new file mode 100644 index 0000000000..76a66c9c4e --- /dev/null +++ b/experimental/behave/src/main/kotlin/net/corda/behave/logging/LogUtilities.kt @@ -0,0 +1,7 @@ +package net.corda.behave.logging + +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +inline fun getLogger(): Logger = + LoggerFactory.getLogger(T::class.java) diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/monitoring/ConjunctiveWatch.kt b/experimental/behave/src/main/kotlin/net/corda/behave/monitoring/ConjunctiveWatch.kt new file mode 100644 index 0000000000..c0308fca1e --- /dev/null +++ b/experimental/behave/src/main/kotlin/net/corda/behave/monitoring/ConjunctiveWatch.kt @@ -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, timeout: Duration): Boolean { + val latch = CountDownLatch(2) + listOf(left, right).parallelStream().forEach { + if (it.await(observable, timeout)) { + latch.countDown() + } + } + return latch.await(timeout) + } + +} \ No newline at end of file diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/monitoring/DisjunctiveWatch.kt b/experimental/behave/src/main/kotlin/net/corda/behave/monitoring/DisjunctiveWatch.kt new file mode 100644 index 0000000000..061ca1ed61 --- /dev/null +++ b/experimental/behave/src/main/kotlin/net/corda/behave/monitoring/DisjunctiveWatch.kt @@ -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, timeout: Duration): Boolean { + val latch = CountDownLatch(1) + listOf(left, right).parallelStream().forEach { + if (it.await(observable, timeout)) { + latch.countDown() + } + } + return latch.await(timeout) + } + +} + diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/monitoring/PatternWatch.kt b/experimental/behave/src/main/kotlin/net/corda/behave/monitoring/PatternWatch.kt new file mode 100644 index 0000000000..50a715dd79 --- /dev/null +++ b/experimental/behave/src/main/kotlin/net/corda/behave/monitoring/PatternWatch.kt @@ -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("") + + } + +} \ No newline at end of file diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/monitoring/Watch.kt b/experimental/behave/src/main/kotlin/net/corda/behave/monitoring/Watch.kt new file mode 100644 index 0000000000..c5b7d94920 --- /dev/null +++ b/experimental/behave/src/main/kotlin/net/corda/behave/monitoring/Watch.kt @@ -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, + 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) + } + +} \ No newline at end of file diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/network/Network.kt b/experimental/behave/src/main/kotlin/net/corda/behave/network/Network.kt new file mode 100644 index 0000000000..d0cabfc02c --- /dev/null +++ b/experimental/behave/src/main/kotlin/net/corda/behave/network/Network.kt @@ -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, + private val targetDirectory: File, + private val timeout: Duration = 2.minutes +) : Closeable, Iterable { + + private val log = getLogger() + + 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() + + 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 = 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 { + 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) + + } + +} \ No newline at end of file diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/node/Distribution.kt b/experimental/behave/src/main/kotlin/net/corda/behave/node/Distribution.kt new file mode 100644 index 0000000000..cca3570f39 --- /dev/null +++ b/experimental/behave/src/main/kotlin/net/corda/behave/node/Distribution.kt @@ -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() + + 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 } + } + + } + +} diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/node/Node.kt b/experimental/behave/src/main/kotlin/net/corda/behave/node/Node.kt new file mode 100644 index 0000000000..ed6b9f097a --- /dev/null +++ b/experimental/behave/src/main/kotlin/net/corda/behave/node/Node.kt @@ -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() + + 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() + + 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 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() + + private var location: String = "London" + + private var country: String = "GB" + + private val apps = mutableListOf() + + 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): 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 error(message: String): T { + throw IllegalArgumentException(message) + } + + } + + companion object { + + fun new() = Builder() + + } + +} \ No newline at end of file diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/node/configuration/Configuration.kt b/experimental/behave/src/main/kotlin/net/corda/behave/node/configuration/Configuration.kt new file mode 100644 index 0000000000..ce1cdc44e2 --- /dev/null +++ b/experimental/behave/src/main/kotlin/net/corda/behave/node/configuration/Configuration.kt @@ -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" + + } + +} diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/node/configuration/ConfigurationTemplate.kt b/experimental/behave/src/main/kotlin/net/corda/behave/node/configuration/ConfigurationTemplate.kt new file mode 100644 index 0000000000..332e7de951 --- /dev/null +++ b/experimental/behave/src/main/kotlin/net/corda/behave/node/configuration/ConfigurationTemplate.kt @@ -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() + +} diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/node/configuration/CordappConfiguration.kt b/experimental/behave/src/main/kotlin/net/corda/behave/node/configuration/CordappConfiguration.kt new file mode 100644 index 0000000000..e6a2c94be2 --- /dev/null +++ b/experimental/behave/src/main/kotlin/net/corda/behave/node/configuration/CordappConfiguration.kt @@ -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)}\"" + } + +} diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/node/configuration/CurrencyConfiguration.kt b/experimental/behave/src/main/kotlin/net/corda/behave/node/configuration/CurrencyConfiguration.kt new file mode 100644 index 0000000000..fd639141e0 --- /dev/null +++ b/experimental/behave/src/main/kotlin/net/corda/behave/node/configuration/CurrencyConfiguration.kt @@ -0,0 +1,18 @@ +package net.corda.behave.node.configuration + +class CurrencyConfiguration(private val issuableCurrencies: List) : ConfigurationTemplate() { + + override val config: (Configuration) -> String + get() = { + if (issuableCurrencies.isEmpty()) { + "" + } else { + """ + |issuableCurrencies=[ + | ${issuableCurrencies.joinToString(", ")} + |] + """ + } + } + +} \ No newline at end of file diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/node/configuration/DatabaseConfiguration.kt b/experimental/behave/src/main/kotlin/net/corda/behave/node/configuration/DatabaseConfiguration.kt new file mode 100644 index 0000000000..ca9f3c37a8 --- /dev/null +++ b/experimental/behave/src/main/kotlin/net/corda/behave/node/configuration/DatabaseConfiguration.kt @@ -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) + +} diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/node/configuration/NetworkInterface.kt b/experimental/behave/src/main/kotlin/net/corda/behave/node/configuration/NetworkInterface.kt new file mode 100644 index 0000000000..fe09792dd2 --- /dev/null +++ b/experimental/behave/src/main/kotlin/net/corda/behave/node/configuration/NetworkInterface.kt @@ -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 + } + } + + } + +} diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/node/configuration/NotaryConfiguration.kt b/experimental/behave/src/main/kotlin/net/corda/behave/node/configuration/NotaryConfiguration.kt new file mode 100644 index 0000000000..58dff6ad6d --- /dev/null +++ b/experimental/behave/src/main/kotlin/net/corda/behave/node/configuration/NotaryConfiguration.kt @@ -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 }" + } + } + +} diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/node/configuration/NotaryType.kt b/experimental/behave/src/main/kotlin/net/corda/behave/node/configuration/NotaryType.kt new file mode 100644 index 0000000000..abceb4b519 --- /dev/null +++ b/experimental/behave/src/main/kotlin/net/corda/behave/node/configuration/NotaryType.kt @@ -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 + } +} diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/node/configuration/UserConfiguration.kt b/experimental/behave/src/main/kotlin/net/corda/behave/node/configuration/UserConfiguration.kt new file mode 100644 index 0000000000..1c44a2f967 --- /dev/null +++ b/experimental/behave/src/main/kotlin/net/corda/behave/node/configuration/UserConfiguration.kt @@ -0,0 +1,37 @@ +package net.corda.behave.node.configuration + +class UserConfiguration : ConfigurationTemplate(), Iterable { + + data class User(val username: String, val password: String, val permissions: List) + + private val users = mutableListOf() + + fun withUser(username: String, password: String, permissions: List = listOf("ALL")): UserConfiguration { + users.add(User(username, password, permissions)) + return this + } + + override fun iterator(): Iterator { + 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() + } + +} diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/process/Command.kt b/experimental/behave/src/main/kotlin/net/corda/behave/process/Command.kt new file mode 100644 index 0000000000..a35ae287db --- /dev/null +++ b/experimental/behave/src/main/kotlin/net/corda/behave/process/Command.kt @@ -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, + private val directory: File = currentDirectory, + private val timeout: Duration = 2.minutes +): Closeable { + + protected val log = getLogger() + + 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 = Observable.create { 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) -> 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 + + } + +} diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/process/JarCommand.kt b/experimental/behave/src/main/kotlin/net/corda/behave/process/JarCommand.kt new file mode 100644 index 0000000000..d465170c17 --- /dev/null +++ b/experimental/behave/src/main/kotlin/net/corda/behave/process/JarCommand.kt @@ -0,0 +1,34 @@ +package net.corda.behave.process + +import java.io.File +import java.time.Duration + +class JarCommand( + jarFile: File, + arguments: Array, + 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() + } + + } + +} \ No newline at end of file diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/process/output/OutputListener.kt b/experimental/behave/src/main/kotlin/net/corda/behave/process/output/OutputListener.kt new file mode 100644 index 0000000000..18dcc065e6 --- /dev/null +++ b/experimental/behave/src/main/kotlin/net/corda/behave/process/output/OutputListener.kt @@ -0,0 +1,9 @@ +package net.corda.behave.process.output + +interface OutputListener { + + fun onNewLine(line: String) + + fun onEndOfStream() + +} \ No newline at end of file diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/service/ContainerService.kt b/experimental/behave/src/main/kotlin/net/corda/behave/service/ContainerService.kt new file mode 100644 index 0000000000..262c0fcb27 --- /dev/null +++ b/experimental/behave/src/main/kotlin/net/corda/behave/service/ContainerService.kt @@ -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 = 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() + } + } + +} diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/service/Service.kt b/experimental/behave/src/main/kotlin/net/corda/behave/service/Service.kt new file mode 100644 index 0000000000..64c7e869f8 --- /dev/null +++ b/experimental/behave/src/main/kotlin/net/corda/behave/service/Service.kt @@ -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() + + 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 + +} \ No newline at end of file diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/service/ServiceInitiator.kt b/experimental/behave/src/main/kotlin/net/corda/behave/service/ServiceInitiator.kt new file mode 100644 index 0000000000..eb3d042d7f --- /dev/null +++ b/experimental/behave/src/main/kotlin/net/corda/behave/service/ServiceInitiator.kt @@ -0,0 +1,5 @@ +package net.corda.behave.service + +import net.corda.behave.node.configuration.Configuration + +typealias ServiceInitiator = (Configuration) -> Service diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/service/ServiceSettings.kt b/experimental/behave/src/main/kotlin/net/corda/behave/service/ServiceSettings.kt new file mode 100644 index 0000000000..f94523691e --- /dev/null +++ b/experimental/behave/src/main/kotlin/net/corda/behave/service/ServiceSettings.kt @@ -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 +) diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/service/database/H2Service.kt b/experimental/behave/src/main/kotlin/net/corda/behave/service/database/H2Service.kt new file mode 100644 index 0000000000..484944ee29 --- /dev/null +++ b/experimental/behave/src/main/kotlin/net/corda/behave/service/database/H2Service.kt @@ -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" + + } + +} \ No newline at end of file diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/service/database/SqlServerService.kt b/experimental/behave/src/main/kotlin/net/corda/behave/service/database/SqlServerService.kt new file mode 100644 index 0000000000..6a18df586f --- /dev/null +++ b/experimental/behave/src/main/kotlin/net/corda/behave/service/database/SqlServerService.kt @@ -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" + + } + +} \ No newline at end of file diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/ssh/MonitoringSSHClient.kt b/experimental/behave/src/main/kotlin/net/corda/behave/ssh/MonitoringSSHClient.kt new file mode 100644 index 0000000000..fcbde2d54f --- /dev/null +++ b/experimental/behave/src/main/kotlin/net/corda/behave/ssh/MonitoringSSHClient.kt @@ -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 = Observable.create { 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) + +} \ No newline at end of file diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/ssh/SSHClient.kt b/experimental/behave/src/main/kotlin/net/corda/behave/ssh/SSHClient.kt new file mode 100644 index 0000000000..afa530cf3c --- /dev/null +++ b/experimental/behave/src/main/kotlin/net/corda/behave/ssh/SSHClient.kt @@ -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() + 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() + + 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 = ;\n" + + "\teol2 = ; 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") + + } + +} \ No newline at end of file diff --git a/experimental/behave/src/scenario/kotlin/Scenarios.kt b/experimental/behave/src/scenario/kotlin/Scenarios.kt new file mode 100644 index 0000000000..ac65923a00 --- /dev/null +++ b/experimental/behave/src/scenario/kotlin/Scenarios.kt @@ -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 diff --git a/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/ScenarioHooks.kt b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/ScenarioHooks.kt new file mode 100644 index 0000000000..f3fad092c9 --- /dev/null +++ b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/ScenarioHooks.kt @@ -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() + } + +} \ No newline at end of file diff --git a/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/ScenarioState.kt b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/ScenarioState.kt new file mode 100644 index 0000000000..f6cfb32298 --- /dev/null +++ b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/ScenarioState.kt @@ -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() + + private val nodes = mutableListOf() + + private var network: Network? = null + + fun fail(message: String) { + error(message) + } + + fun 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 withNetwork(action: ScenarioState.() -> T): T { + ensureNetworkIsRunning() + return action() + } + + inline fun 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 + } + +} \ No newline at end of file diff --git a/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/StepsBlock.kt b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/StepsBlock.kt new file mode 100644 index 0000000000..5880c939a3 --- /dev/null +++ b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/StepsBlock.kt @@ -0,0 +1,3 @@ +package net.corda.behave.scenarios + +typealias StepsBlock = (StepsContainer.() -> Unit) -> Unit \ No newline at end of file diff --git a/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/StepsContainer.kt b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/StepsContainer.kt new file mode 100644 index 0000000000..818c08916a --- /dev/null +++ b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/StepsContainer.kt @@ -0,0 +1,60 @@ +package net.corda.behave.scenarios + +import cucumber.api.java8.En +import net.corda.behave.scenarios.helpers.Cash +import net.corda.behave.scenarios.helpers.Database +import net.corda.behave.scenarios.helpers.Ssh +import net.corda.behave.scenarios.helpers.Startup +import net.corda.behave.scenarios.steps.* +import net.corda.core.messaging.CordaRPCOps +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +@Suppress("KDocMissingDocumentation") +class StepsContainer(val state: ScenarioState) : En { + + private val log: Logger = LoggerFactory.getLogger(StepsContainer::class.java) + + private val stepDefinitions: List<(StepsBlock) -> Unit> = listOf( + ::cashSteps, + ::configurationSteps, + ::databaseSteps, + ::networkSteps, + ::rpcSteps, + ::sshSteps, + ::startupSteps + ) + + init { + stepDefinitions.forEach { it({ this.steps(it) }) } + } + + fun succeed() = log.info("Step succeeded") + + fun fail(message: String) = state.fail(message) + + fun error(message: String) = state.error(message) + + fun node(name: String) = state.nodeBuilder(name) + + fun withNetwork(action: ScenarioState.() -> Unit) { + state.withNetwork(action) + } + + fun withClient(nodeName: String, action: (CordaRPCOps) -> T): T { + return state.withClient(nodeName, action) + } + + val startup = Startup(state) + + val database = Database(state) + + val ssh = Ssh(state) + + val cash = Cash(state) + + private fun steps(action: (StepsContainer.() -> Unit)) { + action(this) + } + +} diff --git a/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/helpers/Cash.kt b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/helpers/Cash.kt new file mode 100644 index 0000000000..0b59771164 --- /dev/null +++ b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/helpers/Cash.kt @@ -0,0 +1,31 @@ +package net.corda.behave.scenarios.helpers + +import net.corda.behave.scenarios.ScenarioState +import net.corda.core.messaging.startFlow +import net.corda.finance.flows.CashConfigDataFlow +import java.util.concurrent.TimeUnit + +class Cash(state: ScenarioState) : Substeps(state) { + + fun numberOfIssuableCurrencies(nodeName: String): Int { + return withClient(nodeName) { + for (flow in it.registeredFlows()) { + log.info(flow) + } + try { + val config = it.startFlow(::CashConfigDataFlow).returnValue.get(10, TimeUnit.SECONDS) + for (supportedCurrency in config.supportedCurrencies) { + log.info("Can use $supportedCurrency") + } + for (issuableCurrency in config.issuableCurrencies) { + log.info("Can issue $issuableCurrency") + } + return@withClient config.issuableCurrencies.size + } catch (ex: Exception) { + log.warn("Failed to retrieve cash configuration data", ex) + throw ex + } + } + } + +} \ No newline at end of file diff --git a/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/helpers/Database.kt b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/helpers/Database.kt new file mode 100644 index 0000000000..2406aff675 --- /dev/null +++ b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/helpers/Database.kt @@ -0,0 +1,23 @@ +package net.corda.behave.scenarios.helpers + +import net.corda.behave.await +import net.corda.behave.scenarios.ScenarioState +import net.corda.behave.seconds +import org.assertj.core.api.Assertions.assertThat +import java.util.concurrent.CountDownLatch + +class Database(state: ScenarioState) : Substeps(state) { + + fun canConnectTo(nodeName: String) { + withNetwork { + val latch = CountDownLatch(1) + log.info("Connecting to the database of node '$nodeName' ...") + node(nodeName).database.use { + log.info("Connected to the database of node '$nodeName'") + latch.countDown() + } + assertThat(latch.await(10.seconds)).isTrue() + } + } + +} \ No newline at end of file diff --git a/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/helpers/Ssh.kt b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/helpers/Ssh.kt new file mode 100644 index 0000000000..a1e7ddd875 --- /dev/null +++ b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/helpers/Ssh.kt @@ -0,0 +1,43 @@ +package net.corda.behave.scenarios.helpers + +import net.corda.behave.scenarios.ScenarioState +import org.assertj.core.api.Assertions.assertThat +import rx.observers.TestSubscriber +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit + +class Ssh(state: ScenarioState) : Substeps(state) { + + fun canConnectTo(nodeName: String) { + withNetwork { + log.info("Connecting to node '$nodeName' over SSH ...") + hasSshStartupMessage(nodeName) + val latch = CountDownLatch(1) + val subscriber = TestSubscriber() + node(nodeName).ssh { + it.output.subscribe(subscriber) + assertThat(subscriber.onNextEvents).isNotEmpty + log.info("Successfully connect to node '$nodeName' over SSH") + latch.countDown() + } + if (!latch.await(15, TimeUnit.SECONDS)) { + fail("Failed to connect to node '$nodeName' over SSH") + } + } + } + + private fun hasSshStartupMessage(nodeName: String) { + var i = 5 + while (i > 0) { + Thread.sleep(2000) + if (state.node(nodeName).logOutput.find(".*SSH server listening on port.*").any()) { + break + } + i -= 1 + } + if (i == 0) { + state.fail("Unable to find SSH start-up message for node $nodeName") + } + } + +} \ No newline at end of file diff --git a/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/helpers/Startup.kt b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/helpers/Startup.kt new file mode 100644 index 0000000000..3520acec43 --- /dev/null +++ b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/helpers/Startup.kt @@ -0,0 +1,65 @@ +package net.corda.behave.scenarios.helpers + +import net.corda.behave.scenarios.ScenarioState + +class Startup(state: ScenarioState) : Substeps(state) { + + fun hasLoggingInformation(nodeName: String) { + withNetwork { + log.info("Retrieving logging information for node '$nodeName' ...") + if (!node(nodeName).nodeInfoGenerationOutput.find("Logs can be found in.*").any()) { + fail("Unable to find logging information for node $nodeName") + } + } + } + + fun hasDatabaseDetails(nodeName: String) { + withNetwork { + log.info("Retrieving database details for node '$nodeName' ...") + if (!node(nodeName).nodeInfoGenerationOutput.find("Database connection url is.*").any()) { + fail("Unable to find database details for node $nodeName") + } + } + } + + fun hasPlatformVersion(nodeName: String, platformVersion: Int) { + withNetwork { + log.info("Finding platform version for node '$nodeName' ...") + val logOutput = node(nodeName).logOutput + if (!logOutput.find(".*Platform Version: $platformVersion .*").any()) { + val match = logOutput.find(".*Platform Version: .*").firstOrNull() + if (match == null) { + fail("Unable to find platform version for node '$nodeName'") + } else { + val foundVersion = Regex("Platform Version: (\\d+) ") + .find(match.contents) + ?.groups?.last()?.value + fail("Expected platform version $platformVersion for node '$nodeName', " + + "but found version $foundVersion") + + } + } + } + } + + fun hasVersion(nodeName: String, version: String) { + withNetwork { + log.info("Finding version for node '$nodeName' ...") + val logOutput = node(nodeName).logOutput + if (!logOutput.find(".*Release: $version .*").any()) { + val match = logOutput.find(".*Release: .*").firstOrNull() + if (match == null) { + fail("Unable to find version for node '$nodeName'") + } else { + val foundVersion = Regex("Version: ([^ ]+) ") + .find(match.contents) + ?.groups?.last()?.value + fail("Expected version $version for node '$nodeName', " + + "but found version $foundVersion") + + } + } + } + } + +} \ No newline at end of file diff --git a/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/helpers/Substeps.kt b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/helpers/Substeps.kt new file mode 100644 index 0000000000..bba2f052e0 --- /dev/null +++ b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/helpers/Substeps.kt @@ -0,0 +1,24 @@ +package net.corda.behave.scenarios.helpers + +import net.corda.behave.logging.getLogger +import net.corda.behave.scenarios.ScenarioState +import net.corda.core.messaging.CordaRPCOps + +abstract class Substeps(protected val state: ScenarioState) { + + protected val log = getLogger() + + protected fun withNetwork(action: ScenarioState.() -> Unit) = + state.withNetwork(action) + + protected fun withClient(nodeName: String, action: ScenarioState.(CordaRPCOps) -> T): T { + return state.withClient(nodeName, { + return@withClient try { + action(state, it) + } catch (ex: Exception) { + state.error(ex.message ?: "Failed to execute RPC call") + } + }) + } + +} \ No newline at end of file diff --git a/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/steps/CashSteps.kt b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/steps/CashSteps.kt new file mode 100644 index 0000000000..e26486c351 --- /dev/null +++ b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/steps/CashSteps.kt @@ -0,0 +1,20 @@ +package net.corda.behave.scenarios.steps + +import net.corda.behave.scenarios.StepsBlock +import org.assertj.core.api.Assertions.assertThat + +fun cashSteps(steps: StepsBlock) = steps { + + Then("^node (\\w+) has 1 issuable currency$") { name -> + withNetwork { + assertThat(cash.numberOfIssuableCurrencies(name)).isEqualTo(1) + } + } + + Then("^node (\\w+) has (\\w+) issuable currencies$") { name, count -> + withNetwork { + assertThat(cash.numberOfIssuableCurrencies(name)).isEqualTo(count.toInt()) + } + } + +} diff --git a/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/steps/ConfigurationSteps.kt b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/steps/ConfigurationSteps.kt new file mode 100644 index 0000000000..27def111b8 --- /dev/null +++ b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/steps/ConfigurationSteps.kt @@ -0,0 +1,49 @@ +package net.corda.behave.scenarios.steps + +import net.corda.behave.database.DatabaseType +import net.corda.behave.node.Distribution +import net.corda.behave.node.configuration.toNotaryType +import net.corda.behave.scenarios.StepsBlock + +fun configurationSteps(steps: StepsBlock) = steps { + + Given("^a node (\\w+) of version ([^ ]+)$") { name, version -> + node(name) + .withDistribution(Distribution.fromVersionString(version) + ?: error("Unknown version '$version'")) + } + + Given("^a (\\w+) notary (\\w+) of version ([^ ]+)$") { type, name, version -> + node(name) + .withDistribution(Distribution.fromVersionString(version) + ?: error("Unknown version '$version'")) + .withNotaryType(type.toNotaryType() + ?: error("Unknown notary type '$type'")) + } + + Given("^node (\\w+) uses database of type (.+)$") { name, type -> + node(name) + .withDatabaseType(DatabaseType.fromName(type) + ?: error("Unknown database type '$type'")) + } + + Given("^node (\\w+) can issue (.+)$") { name, currencies -> + node(name).withIssuableCurrencies(currencies + .replace(" and ", ", ") + .split(", ") + .map { it.toUpperCase() }) + } + + Given("^node (\\w+) is located in (\\w+), (\\w+)$") { name, location, country -> + node(name).withLocation(location, country) + } + + Given("^node (\\w+) has the finance app installed$") { name -> + node(name).withFinanceApp() + } + + Given("^node (\\w+) has app installed: (.+)$") { name, app -> + node(name).withApp(app) + } + +} diff --git a/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/steps/DatabaseSteps.kt b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/steps/DatabaseSteps.kt new file mode 100644 index 0000000000..9b21650a50 --- /dev/null +++ b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/steps/DatabaseSteps.kt @@ -0,0 +1,13 @@ +package net.corda.behave.scenarios.steps + +import net.corda.behave.scenarios.StepsBlock + +fun databaseSteps(steps: StepsBlock) = steps { + + Then("^user can connect to the database of node (\\w+)$") { name -> + withNetwork { + database.canConnectTo(name) + } + } + +} diff --git a/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/steps/NetworkSteps.kt b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/steps/NetworkSteps.kt new file mode 100644 index 0000000000..fcc544de29 --- /dev/null +++ b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/steps/NetworkSteps.kt @@ -0,0 +1,11 @@ +package net.corda.behave.scenarios.steps + +import net.corda.behave.scenarios.StepsBlock + +fun networkSteps(steps: StepsBlock) = steps { + + When("^the network is ready$") { + state.ensureNetworkIsRunning() + } + +} diff --git a/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/steps/RpcSteps.kt b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/steps/RpcSteps.kt new file mode 100644 index 0000000000..9accb28398 --- /dev/null +++ b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/steps/RpcSteps.kt @@ -0,0 +1,13 @@ +package net.corda.behave.scenarios.steps + +import net.corda.behave.scenarios.StepsBlock + +fun rpcSteps(steps: StepsBlock) = steps { + + Then("^user can connect to node (\\w+) using RPC$") { name -> + withClient(name) { + succeed() + } + } + +} diff --git a/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/steps/SshSteps.kt b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/steps/SshSteps.kt new file mode 100644 index 0000000000..516732f1e7 --- /dev/null +++ b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/steps/SshSteps.kt @@ -0,0 +1,13 @@ +package net.corda.behave.scenarios.steps + +import net.corda.behave.scenarios.StepsBlock + +fun sshSteps(steps: StepsBlock) = steps { + + Then("^user can connect to node (\\w+) using SSH$") { name -> + withNetwork { + ssh.canConnectTo(name) + } + } + +} diff --git a/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/steps/StartupSteps.kt b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/steps/StartupSteps.kt new file mode 100644 index 0000000000..f78415f2c8 --- /dev/null +++ b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/steps/StartupSteps.kt @@ -0,0 +1,31 @@ +package net.corda.behave.scenarios.steps + +import net.corda.behave.scenarios.StepsBlock + +fun startupSteps(steps: StepsBlock) = steps { + + Then("^user can retrieve database details for node (\\w+)$") { name -> + withNetwork { + startup.hasDatabaseDetails(name) + } + } + + Then("^user can retrieve logging information for node (\\w+)$") { name -> + withNetwork { + startup.hasLoggingInformation(name) + } + } + + Then("^node (\\w+) is on version ([^ ]+)$") { name, version -> + withNetwork { + startup.hasVersion(name, version) + } + } + + Then("^node (\\w+) is on platform version (\\w+)$") { name, platformVersion -> + withNetwork { + startup.hasPlatformVersion(name, platformVersion.toInt()) + } + } + +} \ No newline at end of file diff --git a/experimental/behave/src/scenario/resources/features/cash/currencies.feature b/experimental/behave/src/scenario/resources/features/cash/currencies.feature new file mode 100644 index 0000000000..3085ced84e --- /dev/null +++ b/experimental/behave/src/scenario/resources/features/cash/currencies.feature @@ -0,0 +1,15 @@ +@cash @issuance +Feature: Cash - Issuable Currencies + To have cash on ledger, certain nodes must have the ability to issue cash of various currencies. + + Scenario: Node can issue no currencies by default + Given a node A of version master + And node A has the finance app installed + When the network is ready + Then node A has 0 issuable currencies + + Scenario: Node can issue a currency + Given a node A of version master + And node A can issue USD + When the network is ready + Then node A has 1 issuable currency \ No newline at end of file diff --git a/experimental/behave/src/scenario/resources/features/database/connection.feature b/experimental/behave/src/scenario/resources/features/database/connection.feature new file mode 100644 index 0000000000..9be4fc7d2d --- /dev/null +++ b/experimental/behave/src/scenario/resources/features/database/connection.feature @@ -0,0 +1,14 @@ +@database @connectivity +Feature: Database - Connection + For Corda to work, a database must be running and appropriately configured. + + Scenario Outline: User can connect to node's database + Given a node A of version + And node A uses database of type + When the network is ready + Then user can connect to the database of node A + + Examples: + | Node-Version | Database-Type | + | MASTER | H2 | + #| MASTER | SQL Server | \ No newline at end of file diff --git a/experimental/behave/src/scenario/resources/features/startup/logging.feature b/experimental/behave/src/scenario/resources/features/startup/logging.feature new file mode 100644 index 0000000000..20531237c6 --- /dev/null +++ b/experimental/behave/src/scenario/resources/features/startup/logging.feature @@ -0,0 +1,21 @@ +@logging @startup +Feature: Startup Information - Logging + A Corda node should inform the user of important parameters during startup so that he/she can confirm the setup and + configure / connect relevant software to said node. + + Scenario: Node shows logging information on startup + Given a node A of version MASTER + And node A uses database of type H2 + And node A is located in London, GB + When the network is ready + Then user can retrieve logging information for node A + + Scenario: Node shows database details on startup + Given a node A of version MASTER + When the network is ready + Then user can retrieve database details for node A + + Scenario: Node shows version information on startup + Given a node A of version MASTER + Then node A is on platform version 2 + And node A is on version 3.0-SNAPSHOT diff --git a/experimental/behave/src/scenario/resources/log4j2.xml b/experimental/behave/src/scenario/resources/log4j2.xml new file mode 100644 index 0000000000..43fcf63c3d --- /dev/null +++ b/experimental/behave/src/scenario/resources/log4j2.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/experimental/behave/src/test/kotlin/net/corda/behave/monitoring/MonitoringTests.kt b/experimental/behave/src/test/kotlin/net/corda/behave/monitoring/MonitoringTests.kt new file mode 100644 index 0000000000..23e0718b6d --- /dev/null +++ b/experimental/behave/src/test/kotlin/net/corda/behave/monitoring/MonitoringTests.kt @@ -0,0 +1,64 @@ +package net.corda.behave.monitoring + +import net.corda.behave.second +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import rx.Observable + +class MonitoringTests { + + @Test + fun `watch gets triggered when pattern is observed`() { + val observable = Observable.just("first", "second", "third") + val result = PatternWatch("c.n").await(observable, 1.second) + assertThat(result).isTrue() + } + + @Test + fun `watch does not get triggered when pattern is not observed`() { + val observable = Observable.just("first", "second", "third") + val result = PatternWatch("forth").await(observable, 1.second) + assertThat(result).isFalse() + } + + @Test + fun `conjunctive watch gets triggered when all its constituents match on the input`() { + val observable = Observable.just("first", "second", "third") + val watch1 = PatternWatch("fir") + val watch2 = PatternWatch("ond") + val watch3 = PatternWatch("ird") + val aggregate = watch1 * watch2 * watch3 + assertThat(aggregate.await(observable, 1.second)).isTrue() + } + + @Test + fun `conjunctive watch does not get triggered when one or more of its constituents do not match on the input`() { + val observable = Observable.just("first", "second", "third") + val watch1 = PatternWatch("fir") + val watch2 = PatternWatch("ond") + val watch3 = PatternWatch("baz") + val aggregate = watch1 * watch2 * watch3 + assertThat(aggregate.await(observable, 1.second)).isFalse() + } + + @Test + fun `disjunctive watch gets triggered when one or more of its constituents match on the input`() { + val observable = Observable.just("first", "second", "third") + val watch1 = PatternWatch("foo") + val watch2 = PatternWatch("ond") + val watch3 = PatternWatch("bar") + val aggregate = watch1 / watch2 / watch3 + assertThat(aggregate.await(observable, 1.second)).isTrue() + } + + @Test + fun `disjunctive watch does not get triggered when none its constituents match on the input`() { + val observable = Observable.just("first", "second", "third") + val watch1 = PatternWatch("foo") + val watch2 = PatternWatch("baz") + val watch3 = PatternWatch("bar") + val aggregate = watch1 / watch2 / watch3 + assertThat(aggregate.await(observable, 1.second)).isFalse() + } + +} \ No newline at end of file diff --git a/experimental/behave/src/test/kotlin/net/corda/behave/network/NetworkTests.kt b/experimental/behave/src/test/kotlin/net/corda/behave/network/NetworkTests.kt new file mode 100644 index 0000000000..93801b1d8d --- /dev/null +++ b/experimental/behave/src/test/kotlin/net/corda/behave/network/NetworkTests.kt @@ -0,0 +1,42 @@ +package net.corda.behave.network + +import net.corda.behave.database.DatabaseType +import net.corda.behave.node.configuration.NotaryType +import net.corda.behave.seconds +import org.junit.Ignore +import org.junit.Test + +class NetworkTests { + + @Ignore + @Test + fun `network of two nodes can be spun up`() { + val network = Network + .new() + .addNode("Foo") + .addNode("Bar") + .generate() + network.use { + it.waitUntilRunning(30.seconds) + it.signal() + it.keepAlive(30.seconds) + } + } + + @Ignore + @Test + fun `network of three nodes and mixed databases can be spun up`() { + val network = Network + .new() + .addNode("Foo") + .addNode("Bar", databaseType = DatabaseType.SQL_SERVER) + .addNode("Baz", notaryType = NotaryType.NON_VALIDATING) + .generate() + network.use { + it.waitUntilRunning(30.seconds) + it.signal() + it.keepAlive(30.seconds) + } + } + +} \ No newline at end of file diff --git a/experimental/behave/src/test/kotlin/net/corda/behave/process/CommandTests.kt b/experimental/behave/src/test/kotlin/net/corda/behave/process/CommandTests.kt new file mode 100644 index 0000000000..4395ddb83a --- /dev/null +++ b/experimental/behave/src/test/kotlin/net/corda/behave/process/CommandTests.kt @@ -0,0 +1,34 @@ +package net.corda.behave.process + +import org.assertj.core.api.Assertions.* +import org.junit.Test +import rx.observers.TestSubscriber + +class CommandTests { + + @Test + fun `successful command returns zero`() { + val exitCode = Command(listOf("ls", "/")).run() + assertThat(exitCode).isEqualTo(0) + } + + @Test + fun `failed command returns non-zero`() { + val exitCode = Command(listOf("ls", "some-weird-path-that-does-not-exist")).run() + assertThat(exitCode).isNotEqualTo(0) + } + + @Test + fun `output stream for command can be observed`() { + val subscriber = TestSubscriber() + val exitCode = Command(listOf("ls", "/")).use { _, output -> + output.subscribe(subscriber) + subscriber.awaitTerminalEvent() + subscriber.assertCompleted() + subscriber.assertNoErrors() + assertThat(subscriber.onNextEvents).contains("bin", "etc", "var") + } + assertThat(exitCode).isEqualTo(0) + } + +} \ No newline at end of file diff --git a/experimental/behave/src/test/kotlin/net/corda/behave/service/SqlServerServiceTests.kt b/experimental/behave/src/test/kotlin/net/corda/behave/service/SqlServerServiceTests.kt new file mode 100644 index 0000000000..872574cb70 --- /dev/null +++ b/experimental/behave/src/test/kotlin/net/corda/behave/service/SqlServerServiceTests.kt @@ -0,0 +1,19 @@ +package net.corda.behave.service + +import net.corda.behave.service.database.SqlServerService +import org.assertj.core.api.Assertions.assertThat +import org.junit.Ignore +import org.junit.Test + +class SqlServerServiceTests { + + @Ignore + @Test + fun `sql server can be started and stopped`() { + val service = SqlServerService("test-mssql", 12345, "S0meS3cretW0rd") + val didStart = service.start() + service.stop() + assertThat(didStart).isTrue() + } + +} \ No newline at end of file diff --git a/experimental/behave/src/test/resources/log4j2.xml b/experimental/behave/src/test/resources/log4j2.xml new file mode 100644 index 0000000000..43fcf63c3d --- /dev/null +++ b/experimental/behave/src/test/resources/log4j2.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/experimental/flow-hook/build.gradle b/experimental/flow-hook/build.gradle index 8ae4292898..a07bca39f3 100644 --- a/experimental/flow-hook/build.gradle +++ b/experimental/flow-hook/build.gradle @@ -35,7 +35,7 @@ dependencies { compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" compile "javassist:javassist:$javaassist_version" compile "com.esotericsoftware:kryo:4.0.0" - compile "co.paralleluniverse:quasar-core:$quasar_version:jdk8" + compile "$quasar_group:quasar-core:$quasar_version:jdk8" } jar { diff --git a/experimental/kryo-hook/build.gradle b/experimental/kryo-hook/build.gradle index cf52f3c9bb..4ba1fe7495 100644 --- a/experimental/kryo-hook/build.gradle +++ b/experimental/kryo-hook/build.gradle @@ -34,7 +34,7 @@ dependencies { compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" compile "javassist:javassist:$javaassist_version" compile "com.esotericsoftware:kryo:4.0.0" - compile "co.paralleluniverse:quasar-core:$quasar_version:jdk8" + compile "$quasar_group:quasar-core:$quasar_version:jdk8" } jar { diff --git a/experimental/src/test/kotlin/net/corda/finance/contracts/universal/Cap.kt b/experimental/src/test/kotlin/net/corda/finance/contracts/universal/Cap.kt index 55e5b7fa3a..b276650b81 100644 --- a/experimental/src/test/kotlin/net/corda/finance/contracts/universal/Cap.kt +++ b/experimental/src/test/kotlin/net/corda/finance/contracts/universal/Cap.kt @@ -25,11 +25,12 @@ import java.time.LocalDate internal val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party fun transaction(script: TransactionDSL.() -> EnforceVerifyOrFail) = run { - MockServices(listOf("net.corda.finance.contracts.universal"), rigorousMock().also { - listOf(acmeCorp, highStreetBank, momAndPop).forEach { party -> - doReturn(null).whenever(it).partyFromKey(party.owningKey) - } - }, CordaX500Name("MegaCorp", "London", "GB")).transaction(DUMMY_NOTARY, script) + MockServices(listOf("net.corda.finance.contracts.universal"), CordaX500Name("MegaCorp", "London", "GB"), + rigorousMock().also { + listOf(acmeCorp, highStreetBank, momAndPop).forEach { party -> + doReturn(null).whenever(it).partyFromKey(party.owningKey) + } + }).transaction(DUMMY_NOTARY, script) } class Cap { @@ -312,14 +313,15 @@ class Cap { } } - @Test @Ignore + @Test + @Ignore fun `pretty print`() { - println ( prettyPrint(contractInitial) ) + println(prettyPrint(contractInitial)) - println ( prettyPrint(contractAfterFixingFirst) ) + println(prettyPrint(contractAfterFixingFirst)) - println ( prettyPrint(contractAfterExecutionFirst) ) + println(prettyPrint(contractAfterExecutionFirst)) - println ( prettyPrint(contractAfterFixingFinal) ) + println(prettyPrint(contractAfterFixingFinal)) } } diff --git a/finance/src/test/java/net/corda/finance/contracts/asset/CashTestsJava.java b/finance/src/test/java/net/corda/finance/contracts/asset/CashTestsJava.java index 034404e184..3e28b1aee1 100644 --- a/finance/src/test/java/net/corda/finance/contracts/asset/CashTestsJava.java +++ b/finance/src/test/java/net/corda/finance/contracts/asset/CashTestsJava.java @@ -38,7 +38,7 @@ public class CashTestsJava { IdentityServiceInternal identityService = rigorousMock(IdentityServiceInternal.class); doReturn(MEGA_CORP.getParty()).when(identityService).partyFromKey(MEGA_CORP.getPublicKey()); doReturn(MINI_CORP.getParty()).when(identityService).partyFromKey(MINI_CORP.getPublicKey()); - transaction(new MockServices(emptyList(), identityService, MEGA_CORP.getName()), DUMMY_NOTARY, tx -> { + transaction(new MockServices(emptyList(), MEGA_CORP.getName(), identityService), DUMMY_NOTARY, tx -> { tx.attachment(Cash.PROGRAM_ID); tx.input(Cash.PROGRAM_ID, inState); diff --git a/finance/src/test/kotlin/net/corda/finance/contracts/asset/ObligationTests.kt b/finance/src/test/kotlin/net/corda/finance/contracts/asset/ObligationTests.kt index 35a0db24c3..87f481396b 100644 --- a/finance/src/test/kotlin/net/corda/finance/contracts/asset/ObligationTests.kt +++ b/finance/src/test/kotlin/net/corda/finance/contracts/asset/ObligationTests.kt @@ -78,8 +78,8 @@ class ObligationTests { beneficiary = CHARLIE ) private val outState = inState.copy(beneficiary = AnonymousParty(BOB_PUBKEY)) - private val miniCorpServices = MockServices(listOf("net.corda.finance.contracts.asset"), rigorousMock(), miniCorp) - private val notaryServices = MockServices(emptyList(), rigorousMock(), MEGA_CORP.name, dummyNotary.keyPair) + private val miniCorpServices = MockServices(listOf("net.corda.finance.contracts.asset"), miniCorp, rigorousMock()) + private val notaryServices = MockServices(emptyList(), MEGA_CORP.name, rigorousMock(), dummyNotary.keyPair) private val identityService = rigorousMock().also { doReturn(null).whenever(it).partyFromKey(ALICE_PUBKEY) doReturn(null).whenever(it).partyFromKey(BOB_PUBKEY) @@ -87,8 +87,8 @@ class ObligationTests { doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY) doReturn(MINI_CORP).whenever(it).partyFromKey(MINI_CORP_PUBKEY) } - private val mockService = MockServices(listOf("net.corda.finance.contracts.asset"), identityService, MEGA_CORP.name) - private val ledgerServices get() = MockServices(emptyList(), identityService, MEGA_CORP.name) + private val mockService = MockServices(listOf("net.corda.finance.contracts.asset"), MEGA_CORP.name, identityService) + private val ledgerServices get() = MockServices(emptyList(), MEGA_CORP.name, identityService) private fun cashObligationTestRoots( group: LedgerDSL ) = group.apply { diff --git a/finance/src/test/kotlin/net/corda/finance/flows/CashPaymentFlowTests.kt b/finance/src/test/kotlin/net/corda/finance/flows/CashPaymentFlowTests.kt index 31dfa56f56..d1c52b7b7c 100644 --- a/finance/src/test/kotlin/net/corda/finance/flows/CashPaymentFlowTests.kt +++ b/finance/src/test/kotlin/net/corda/finance/flows/CashPaymentFlowTests.kt @@ -9,10 +9,11 @@ import net.corda.core.utilities.getOrThrow import net.corda.finance.DOLLARS import net.corda.finance.`issued by` import net.corda.finance.contracts.asset.Cash +import net.corda.node.internal.StartedNode import net.corda.testing.core.* import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin -import net.corda.testing.node.MockNetwork -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 @@ -21,16 +22,16 @@ import kotlin.test.assertEquals import kotlin.test.assertFailsWith class CashPaymentFlowTests { - private lateinit var mockNet: MockNetwork + private lateinit var mockNet: InternalMockNetwork private val initialBalance = 2000.DOLLARS private val ref = OpaqueBytes.of(0x01) - private lateinit var bankOfCordaNode: StartedMockNode + private lateinit var bankOfCordaNode: StartedNode private lateinit var bankOfCorda: Party - private lateinit var aliceNode: StartedMockNode + private lateinit var aliceNode: StartedNode @Before fun start() { - mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin(), cordappPackages = listOf("net.corda.finance.contracts.asset", "net.corda.finance.schemas")) + mockNet = InternalMockNetwork(servicePeerAllocationStrategy = RoundRobin(), cordappPackages = listOf("net.corda.finance.contracts.asset", "net.corda.finance.schemas")) bankOfCordaNode = mockNet.createPartyNode(BOC_NAME) bankOfCorda = bankOfCordaNode.info.identityFromX500Name(BOC_NAME) aliceNode = mockNet.createPartyNode(ALICE_NAME) diff --git a/gradle-plugins/README.rst b/gradle-plugins/README.rst deleted file mode 100644 index 0ede7c216f..0000000000 --- a/gradle-plugins/README.rst +++ /dev/null @@ -1,29 +0,0 @@ -Gradle Plugins for Cordapps -=========================== - -The projects at this level of the project are gradle plugins for cordapps and are published to Maven Local with -the rest of the Corda libraries. - -.. note:: - - Some of the plugins here are duplicated with the ones in buildSrc. While the duplication is unwanted any - currently known solution (such as publishing from buildSrc or setting up a separate project/repo) would - introduce a two step build which is less convenient. - -Version number --------------- - -To modify the version number edit constants.properties in root dir - -Installing ----------- - -If you need to bootstrap the corda repository you can install these plugins with - -.. code-block:: text - - cd publish-utils - ../../gradlew -u install - cd ../ - ../gradlew install - diff --git a/gradle-plugins/api-scanner/README.md b/gradle-plugins/api-scanner/README.md deleted file mode 100644 index aba34357d4..0000000000 --- a/gradle-plugins/api-scanner/README.md +++ /dev/null @@ -1,79 +0,0 @@ -# API Scanner - -Generates a text summary of Corda's public API that we can check for API-breaking changes. - -```bash -$ gradlew generateApi -``` - -See [here](../../docs/source/corda-api.rst) for Corda's public API strategy. We will need to -apply this plugin to other modules in future Corda releases as those modules' APIs stabilise. - -Basically, this plugin will document a module's `public` and `protected` classes/methods/fields, -excluding those from our `*.internal.*` packages, any synthetic methods, bridge methods, or methods -identified as having Kotlin's `internal` scope. (Kotlin doesn't seem to have implemented `internal` -scope for classes or fields yet as these are currently `public` inside the `.class` file.) - -## Usage -Include this line in the `build.gradle` file of every Corda module that exports public API: - -```gradle -apply plugin: 'net.corda.plugins.api-scanner' -``` - -This will create a Gradle task called `scanApi` which will analyse that module's Jar artifacts. More precisely, -it will analyse all of the Jar artifacts that have not been assigned a Maven classifier, on the basis -that these should be the module's main artifacts. - -The `scanApi` task supports the following configuration options: -```gradle -scanApi { - // Make the classpath-scanning phase more verbose. - verbose = {true|false} - - // Enable / disable the task within this module. - enabled = {true|false} - - // Names of classes that should be excluded from the output. - excludeClasses = [ - ... - ] -} -``` - -All of the `ScanApi` tasks write their output files to their own `$buildDir/api` directory, where they -are collated into a single output file by the `GenerateApi` task. The `GenerateApi` task is declared -in the root project's `build.gradle` file: - -```gradle -task generateApi(type: net.corda.plugins.GenerateApi){ - baseName = "api-corda" -} -``` - -The final API file is written to `$buildDir/api/$baseName-$project.version.txt` - -### Sample Output -``` -public interface net.corda.core.contracts.Attachment extends net.corda.core.contracts.NamedByHash - public abstract void extractFile(String, java.io.OutputStream) - @org.jetbrains.annotations.NotNull public abstract List getSigners() - @org.jetbrains.annotations.NotNull public abstract java.io.InputStream open() - @org.jetbrains.annotations.NotNull public abstract jar.JarInputStream openAsJAR() -## -public interface net.corda.core.contracts.AttachmentConstraint - public abstract boolean isSatisfiedBy(net.corda.core.contracts.Attachment) -## -public final class net.corda.core.contracts.AttachmentResolutionException extends net.corda.core.flows.FlowException - public (net.corda.core.crypto.SecureHash) - @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getHash() -## -``` - -#### Notes -The `GenerateApi` task will collate the output of every `ScanApi` task found either in the same project, -or in any of that project's subprojects. So it is _theoretically_ possible also to collate the API output -from subtrees of modules simply by defining a new `GenerateApi` task at the root of that subtree. - -## Plugin Installation -See [here](../README.rst) for full installation instructions. diff --git a/gradle-plugins/api-scanner/build.gradle b/gradle-plugins/api-scanner/build.gradle deleted file mode 100644 index c178472d58..0000000000 --- a/gradle-plugins/api-scanner/build.gradle +++ /dev/null @@ -1,19 +0,0 @@ -apply plugin: 'java' -apply plugin: 'net.corda.plugins.publish-utils' -apply plugin: 'com.jfrog.artifactory' - -description "Generates a summary of the artifact's public API" - -repositories { - mavenCentral() -} - -dependencies { - compile gradleApi() - compile "io.github.lukehutch:fast-classpath-scanner:2.7.0" - testCompile "junit:junit:4.12" -} - -publish { - name project.name -} diff --git a/gradle-plugins/api-scanner/src/main/java/net/corda/plugins/ApiScanner.java b/gradle-plugins/api-scanner/src/main/java/net/corda/plugins/ApiScanner.java deleted file mode 100644 index 75f238891c..0000000000 --- a/gradle-plugins/api-scanner/src/main/java/net/corda/plugins/ApiScanner.java +++ /dev/null @@ -1,70 +0,0 @@ -package net.corda.plugins; - -import org.gradle.api.Plugin; -import org.gradle.api.Project; -import org.gradle.api.artifacts.ConfigurationContainer; -import org.gradle.api.file.FileCollection; -import org.gradle.api.tasks.TaskCollection; -import org.gradle.jvm.tasks.Jar; - -public class ApiScanner implements Plugin { - - /** - * Identify the Gradle Jar tasks creating jars - * without Maven classifiers, and generate API - * documentation for them. - * @param p Current project. - */ - @Override - public void apply(Project p) { - p.getLogger().info("Applying API scanner to {}", p.getName()); - - ScannerExtension extension = p.getExtensions().create("scanApi", ScannerExtension.class); - - p.afterEvaluate(project -> { - TaskCollection jarTasks = project.getTasks() - .withType(Jar.class) - .matching(jarTask -> jarTask.getClassifier().isEmpty() && jarTask.isEnabled()); - if (jarTasks.isEmpty()) { - return; - } - - project.getLogger().info("Adding scanApi task to {}", project.getName()); - project.getTasks().create("scanApi", ScanApi.class, scanTask -> { - scanTask.setClasspath(compilationClasspath(project.getConfigurations())); - // Automatically creates a dependency on jar tasks. - scanTask.setSources(project.files(jarTasks)); - scanTask.setExcludeClasses(extension.getExcludeClasses()); - scanTask.setVerbose(extension.isVerbose()); - scanTask.setEnabled(extension.isEnabled()); - - // Declare this ScanApi task to be a dependency of any - // GenerateApi tasks belonging to any of our ancestors. - project.getRootProject().getTasks() - .withType(GenerateApi.class) - .matching(generateTask -> isAncestorOf(generateTask.getProject(), project)) - .forEach(generateTask -> generateTask.dependsOn(scanTask)); - }); - }); - } - - /* - * Recurse through a child project's parents until we reach the root, - * and return true iff we find our target project along the way. - */ - private static boolean isAncestorOf(Project target, Project child) { - Project p = child; - while (p != null) { - if (p == target) { - return true; - } - p = p.getParent(); - } - return false; - } - - private static FileCollection compilationClasspath(ConfigurationContainer configurations) { - return configurations.getByName("compile") - .plus(configurations.getByName("compileOnly")); - } -} diff --git a/gradle-plugins/api-scanner/src/main/java/net/corda/plugins/GenerateApi.java b/gradle-plugins/api-scanner/src/main/java/net/corda/plugins/GenerateApi.java deleted file mode 100644 index 9c87224075..0000000000 --- a/gradle-plugins/api-scanner/src/main/java/net/corda/plugins/GenerateApi.java +++ /dev/null @@ -1,61 +0,0 @@ -package net.corda.plugins; - -import org.gradle.api.DefaultTask; -import org.gradle.api.file.FileCollection; -import org.gradle.api.tasks.InputFiles; -import org.gradle.api.tasks.OutputFile; -import org.gradle.api.tasks.TaskAction; - -import java.io.*; -import java.nio.file.Files; - -import static java.util.Comparator.comparing; -import static java.util.stream.Collectors.toList; - -@SuppressWarnings("unused") -public class GenerateApi extends DefaultTask { - - private final File outputDir; - private String baseName; - - public GenerateApi() { - outputDir = new File(getProject().getBuildDir(), "api"); - baseName = "api-" + getProject().getName(); - } - - public void setBaseName(String baseName) { - this.baseName = baseName; - } - - @InputFiles - public FileCollection getSources() { - return getProject().files(getProject().getAllprojects().stream() - .flatMap(project -> project.getTasks() - .withType(ScanApi.class) - .matching(ScanApi::isEnabled) - .stream()) - .flatMap(scanTask -> scanTask.getTargets().getFiles().stream()) - .sorted(comparing(File::getName)) - .collect(toList()) - ); - } - - @OutputFile - public File getTarget() { - return new File(outputDir, String.format("%s-%s.txt", baseName, getProject().getVersion())); - } - - @TaskAction - public void generate() { - FileCollection apiFiles = getSources(); - if (!apiFiles.isEmpty()) { - try (OutputStream output = new BufferedOutputStream(new FileOutputStream(getTarget()))) { - for (File apiFile : apiFiles) { - Files.copy(apiFile.toPath(), output); - } - } catch (IOException e) { - getLogger().error("Failed to generate API file", e); - } - } - } -} diff --git a/gradle-plugins/api-scanner/src/main/java/net/corda/plugins/ScanApi.java b/gradle-plugins/api-scanner/src/main/java/net/corda/plugins/ScanApi.java deleted file mode 100644 index eb2b7e5599..0000000000 --- a/gradle-plugins/api-scanner/src/main/java/net/corda/plugins/ScanApi.java +++ /dev/null @@ -1,389 +0,0 @@ -package net.corda.plugins; - -import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner; -import io.github.lukehutch.fastclasspathscanner.scanner.ClassInfo; -import io.github.lukehutch.fastclasspathscanner.scanner.FieldInfo; -import io.github.lukehutch.fastclasspathscanner.scanner.MethodInfo; -import io.github.lukehutch.fastclasspathscanner.scanner.ScanResult; -import org.gradle.api.DefaultTask; -import org.gradle.api.file.ConfigurableFileCollection; -import org.gradle.api.file.FileCollection; -import org.gradle.api.tasks.CompileClasspath; -import org.gradle.api.tasks.Input; -import org.gradle.api.tasks.InputFiles; -import org.gradle.api.tasks.OutputFiles; -import org.gradle.api.tasks.TaskAction; - -import java.io.*; -import java.lang.annotation.Annotation; -import java.lang.annotation.Inherited; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.*; -import java.util.stream.StreamSupport; - -import static java.util.Collections.*; -import static java.util.stream.Collectors.*; - -@SuppressWarnings("unused") -public class ScanApi extends DefaultTask { - private static final int CLASS_MASK = Modifier.classModifiers(); - private static final int INTERFACE_MASK = Modifier.interfaceModifiers() & ~Modifier.ABSTRACT; - private static final int METHOD_MASK = Modifier.methodModifiers(); - private static final int FIELD_MASK = Modifier.fieldModifiers(); - private static final int VISIBILITY_MASK = Modifier.PUBLIC | Modifier.PROTECTED; - - private static final Set ANNOTATION_BLACKLIST; - static { - Set blacklist = new LinkedHashSet<>(); - blacklist.add("kotlin.jvm.JvmOverloads"); - ANNOTATION_BLACKLIST = unmodifiableSet(blacklist); - } - - /** - * This information has been lifted from: - * @link Metadata.kt - */ - private static final String KOTLIN_METADATA = "kotlin.Metadata"; - private static final String KOTLIN_CLASSTYPE_METHOD = "k"; - private static final int KOTLIN_SYNTHETIC = 3; - - private final ConfigurableFileCollection sources; - private final ConfigurableFileCollection classpath; - private final Set excludeClasses; - private final File outputDir; - private boolean verbose; - - public ScanApi() { - sources = getProject().files(); - classpath = getProject().files(); - excludeClasses = new LinkedHashSet<>(); - outputDir = new File(getProject().getBuildDir(), "api"); - } - - @InputFiles - public FileCollection getSources() { - return sources; - } - - void setSources(FileCollection sources) { - this.sources.setFrom(sources); - } - - @CompileClasspath - @InputFiles - public FileCollection getClasspath() { - return classpath; - } - - void setClasspath(FileCollection classpath) { - this.classpath.setFrom(classpath); - } - - @Input - public Collection getExcludeClasses() { - return unmodifiableSet(excludeClasses); - } - - void setExcludeClasses(Collection excludeClasses) { - this.excludeClasses.clear(); - this.excludeClasses.addAll(excludeClasses); - } - - @OutputFiles - public FileCollection getTargets() { - return getProject().files( - StreamSupport.stream(sources.spliterator(), false) - .map(this::toTarget) - .collect(toList()) - ); - } - - public boolean isVerbose() { - return verbose; - } - - void setVerbose(boolean verbose) { - this.verbose = verbose; - } - - private File toTarget(File source) { - return new File(outputDir, source.getName().replaceAll(".jar$", ".txt")); - } - - @TaskAction - public void scan() { - try (Scanner scanner = new Scanner(classpath)) { - for (File source : sources) { - scanner.scan(source); - } - } catch (IOException e) { - getLogger().error("Failed to write API file", e); - } - } - - class Scanner implements Closeable { - private final URLClassLoader classpathLoader; - private final Class metadataClass; - private final Method classTypeMethod; - - @SuppressWarnings("unchecked") - Scanner(URLClassLoader classpathLoader) { - this.classpathLoader = classpathLoader; - - Class kClass; - Method kMethod; - try { - kClass = (Class) Class.forName(KOTLIN_METADATA, true, classpathLoader); - kMethod = kClass.getDeclaredMethod(KOTLIN_CLASSTYPE_METHOD); - } catch (ClassNotFoundException | NoSuchMethodException e) { - kClass = null; - kMethod = null; - } - - metadataClass = kClass; - classTypeMethod = kMethod; - } - - Scanner(FileCollection classpath) throws MalformedURLException { - this(new URLClassLoader(toURLs(classpath))); - } - - @Override - public void close() throws IOException { - classpathLoader.close(); - } - - void scan(File source) { - File target = toTarget(source); - try ( - URLClassLoader appLoader = new URLClassLoader(new URL[]{ toURL(source) }, classpathLoader); - PrintWriter writer = new PrintWriter(target, "UTF-8") - ) { - scan(writer, appLoader); - } catch (IOException e) { - getLogger().error("API scan has failed", e); - } - } - - void scan(PrintWriter writer, ClassLoader appLoader) { - ScanResult result = new FastClasspathScanner(getScanSpecification()) - .overrideClassLoaders(appLoader) - .ignoreParentClassLoaders() - .ignoreMethodVisibility() - .ignoreFieldVisibility() - .enableMethodInfo() - .enableFieldInfo() - .verbose(verbose) - .scan(); - writeApis(writer, result); - } - - private String[] getScanSpecification() { - String[] spec = new String[2 + excludeClasses.size()]; - spec[0] = "!"; // Don't blacklist system classes from the output. - spec[1] = "-dir:"; // Ignore classes on the filesystem. - - int i = 2; - for (String excludeClass : excludeClasses) { - spec[i++] = '-' + excludeClass; - } - return spec; - } - - private void writeApis(PrintWriter writer, ScanResult result) { - Map allInfo = result.getClassNameToClassInfo(); - result.getNamesOfAllClasses().forEach(className -> { - if (className.contains(".internal.")) { - // These classes belong to internal Corda packages. - return; - } - ClassInfo classInfo = allInfo.get(className); - if (classInfo.getClassLoaders() == null) { - // Ignore classes that belong to one of our target ClassLoader's parents. - return; - } - - Class javaClass = result.classNameToClassRef(className); - if (!isVisible(javaClass.getModifiers())) { - // Excludes private and package-protected classes - return; - } - - int kotlinClassType = getKotlinClassType(javaClass); - if (kotlinClassType == KOTLIN_SYNTHETIC) { - // Exclude classes synthesised by the Kotlin compiler. - return; - } - - writeClass(writer, classInfo, javaClass.getModifiers()); - writeMethods(writer, classInfo.getMethodAndConstructorInfo()); - writeFields(writer, classInfo.getFieldInfo()); - writer.println("##"); - }); - } - - private void writeClass(PrintWriter writer, ClassInfo classInfo, int modifiers) { - if (classInfo.isAnnotation()) { - /* - * Annotation declaration. - */ - writer.append(Modifier.toString(modifiers & INTERFACE_MASK)); - writer.append(" @interface ").print(classInfo); - } else if (classInfo.isStandardClass()) { - /* - * Class declaration. - */ - List annotationNames = toNames(readClassAnnotationsFor(classInfo)); - if (!annotationNames.isEmpty()) { - writer.append(asAnnotations(annotationNames)); - } - writer.append(Modifier.toString(modifiers & CLASS_MASK)); - writer.append(" class ").print(classInfo); - Set superclasses = classInfo.getDirectSuperclasses(); - if (!superclasses.isEmpty()) { - writer.append(" extends ").print(stringOf(superclasses)); - } - Set interfaces = classInfo.getDirectlyImplementedInterfaces(); - if (!interfaces.isEmpty()) { - writer.append(" implements ").print(stringOf(interfaces)); - } - } else { - /* - * Interface declaration. - */ - List annotationNames = toNames(readInterfaceAnnotationsFor(classInfo)); - if (!annotationNames.isEmpty()) { - writer.append(asAnnotations(annotationNames)); - } - writer.append(Modifier.toString(modifiers & INTERFACE_MASK)); - writer.append(" interface ").print(classInfo); - Set superinterfaces = classInfo.getDirectSuperinterfaces(); - if (!superinterfaces.isEmpty()) { - writer.append(" extends ").print(stringOf(superinterfaces)); - } - } - writer.println(); - } - - private void writeMethods(PrintWriter writer, List methods) { - sort(methods); - for (MethodInfo method : methods) { - if (isVisible(method.getAccessFlags()) // Only public and protected methods - && isValid(method.getAccessFlags(), METHOD_MASK) // Excludes bridge and synthetic methods - && !isKotlinInternalScope(method)) { - writer.append(" ").println(filterAnnotationsFor(method)); - } - } - } - - private void writeFields(PrintWriter output, List fields) { - sort(fields); - for (FieldInfo field : fields) { - if (isVisible(field.getAccessFlags()) && isValid(field.getAccessFlags(), FIELD_MASK)) { - output.append(" ").println(field); - } - } - } - - private int getKotlinClassType(Class javaClass) { - if (metadataClass != null) { - Annotation metadata = javaClass.getAnnotation(metadataClass); - if (metadata != null) { - try { - return (int) classTypeMethod.invoke(metadata); - } catch (IllegalAccessException | InvocationTargetException e) { - getLogger().error("Failed to read Kotlin annotation", e); - } - } - } - return 0; - } - - private List toNames(Collection classes) { - return classes.stream() - .map(ClassInfo::toString) - .filter(ScanApi::isApplicationClass) - .collect(toList()); - } - - private Set readClassAnnotationsFor(ClassInfo classInfo) { - Set annotations = new HashSet<>(classInfo.getAnnotations()); - annotations.addAll(selectInheritedAnnotations(classInfo.getSuperclasses())); - annotations.addAll(selectInheritedAnnotations(classInfo.getImplementedInterfaces())); - return annotations; - } - - private Set readInterfaceAnnotationsFor(ClassInfo classInfo) { - Set annotations = new HashSet<>(classInfo.getAnnotations()); - annotations.addAll(selectInheritedAnnotations(classInfo.getSuperinterfaces())); - return annotations; - } - - /** - * Returns those annotations which have themselves been annotated as "Inherited". - */ - private List selectInheritedAnnotations(Collection classes) { - return classes.stream() - .flatMap(cls -> cls.getAnnotations().stream()) - .filter(ann -> ann.hasMetaAnnotation(Inherited.class.getName())) - .collect(toList()); - } - - private MethodInfo filterAnnotationsFor(MethodInfo method) { - return new MethodInfo( - method.getClassName(), - method.getMethodName(), - method.getAccessFlags(), - method.getTypeDescriptor(), - method.getAnnotationNames().stream() - .filter(ScanApi::isVisibleAnnotation) - .collect(toList()) - ); - } - } - - private static boolean isVisibleAnnotation(String annotationName) { - return !ANNOTATION_BLACKLIST.contains(annotationName); - } - - private static boolean isKotlinInternalScope(MethodInfo method) { - return method.getMethodName().indexOf('$') >= 0; - } - - private static boolean isValid(int modifiers, int mask) { - return (modifiers & mask) == modifiers; - } - - private static boolean isVisible(int accessFlags) { - return (accessFlags & VISIBILITY_MASK) != 0; - } - - private static String stringOf(Collection items) { - return items.stream().map(ClassInfo::toString).collect(joining(", ")); - } - - private static String asAnnotations(Collection items) { - return items.stream().collect(joining(" @", "@", " ")); - } - - private static boolean isApplicationClass(String typeName) { - return !typeName.startsWith("java.") && !typeName.startsWith("kotlin."); - } - - private static URL toURL(File file) throws MalformedURLException { - return file.toURI().toURL(); - } - - private static URL[] toURLs(Iterable files) throws MalformedURLException { - List urls = new LinkedList<>(); - for (File file : files) { - urls.add(toURL(file)); - } - return urls.toArray(new URL[urls.size()]); - } -} diff --git a/gradle-plugins/api-scanner/src/main/java/net/corda/plugins/ScannerExtension.java b/gradle-plugins/api-scanner/src/main/java/net/corda/plugins/ScannerExtension.java deleted file mode 100644 index 4e77437cfc..0000000000 --- a/gradle-plugins/api-scanner/src/main/java/net/corda/plugins/ScannerExtension.java +++ /dev/null @@ -1,37 +0,0 @@ -package net.corda.plugins; - -import java.util.List; - -import static java.util.Collections.emptyList; - -@SuppressWarnings("unused") -public class ScannerExtension { - - private boolean verbose; - private boolean enabled = true; - private List excludeClasses = emptyList(); - - public boolean isVerbose() { - return verbose; - } - - public void setVerbose(boolean verbose) { - this.verbose = verbose; - } - - public boolean isEnabled() { - return enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public List getExcludeClasses() { - return excludeClasses; - } - - public void setExcludeClasses(List excludeClasses) { - this.excludeClasses = excludeClasses; - } -} diff --git a/gradle-plugins/api-scanner/src/main/resources/META-INF/gradle-plugins/net.corda.plugins.api-scanner.properties b/gradle-plugins/api-scanner/src/main/resources/META-INF/gradle-plugins/net.corda.plugins.api-scanner.properties deleted file mode 100644 index fc9e2277a5..0000000000 --- a/gradle-plugins/api-scanner/src/main/resources/META-INF/gradle-plugins/net.corda.plugins.api-scanner.properties +++ /dev/null @@ -1 +0,0 @@ -implementation-class=net.corda.plugins.ApiScanner diff --git a/gradle-plugins/build.gradle b/gradle-plugins/build.gradle deleted file mode 100644 index 32763f85f6..0000000000 --- a/gradle-plugins/build.gradle +++ /dev/null @@ -1,84 +0,0 @@ -// This script exists just to allow bootstrapping the gradle plugins if maven central or jcenter are unavailable -// or if you are developing these plugins. See the readme for more information. - -buildscript { - // For sharing constants between builds - Properties constants = new Properties() - file("$projectDir/../constants.properties").withInputStream { constants.load(it) } - - // If you bump this version you must re-bootstrap the codebase. See the README for more information. - ext { - gradle_plugins_version = constants.getProperty("gradlePluginsVersion") - bouncycastle_version = constants.getProperty("bouncycastleVersion") - typesafe_config_version = constants.getProperty("typesafeConfigVersion") - jsr305_version = constants.getProperty("jsr305Version") - kotlin_version = constants.getProperty("kotlinVersion") - artifactory_plugin_version = constants.getProperty('artifactoryPluginVersion') - snake_yaml_version = constants.getProperty('snakeYamlVersion') - } - - repositories { - mavenLocal() - jcenter() - } - - dependencies { - classpath "net.corda.plugins:publish-utils:$gradle_plugins_version" - classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jfrog.buildinfo:build-info-extractor-gradle:$artifactory_plugin_version" - } -} - -apply plugin: 'net.corda.plugins.publish-utils' -apply plugin: 'com.jfrog.artifactory' - -allprojects { - version gradle_plugins_version - group 'net.corda.plugins' -} - -bintrayConfig { - user = System.getenv('CORDA_BINTRAY_USER') - key = System.getenv('CORDA_BINTRAY_KEY') - repo = 'corda' - org = 'r3' - licenses = ['Apache-2.0'] - vcsUrl = 'https://github.com/corda/corda' - projectUrl = 'https://github.com/corda/corda' - gpgSign = true - gpgPassphrase = System.getenv('CORDA_BINTRAY_GPG_PASSPHRASE') - publications = ['cordformation', 'quasar-utils', 'cordform-common', 'api-scanner', 'cordapp'] - license { - name = 'Apache-2.0' - url = 'https://www.apache.org/licenses/LICENSE-2.0' - distribution = 'repo' - } - developer { - id = 'R3' - name = 'R3' - email = 'dev@corda.net' - } -} - -artifactory { - publish { - contextUrl = 'https://ci-artifactory.corda.r3cev.com/artifactory' - repository { - repoKey = 'corda-dev' - username = 'teamcity' - password = System.getenv('CORDA_ARTIFACTORY_PASSWORD') - } - - defaults { - // Publish utils does not have a publish block because it would be circular for it to apply it's own - // extensions to itself - if(project.name == 'publish-utils') { - publications('publishUtils') - // Root project applies the plugin (for this block) but does not need to be published - } else if(project != rootProject) { - publications(project.extensions.publish.name()) - } - } - } -} \ No newline at end of file diff --git a/gradle-plugins/cordapp/README.md b/gradle-plugins/cordapp/README.md deleted file mode 100644 index 6b0cebe690..0000000000 --- a/gradle-plugins/cordapp/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# Cordapp Gradle Plugin - -## Purpose - -To transform any project this plugin is applied to into a cordapp project that generates a cordapp JAR. - -## Effects - -Will modify the default JAR task to create a CorDapp format JAR instead [see here](https://docs.corda.net/cordapp-build-systems.html) -for more information. \ No newline at end of file diff --git a/gradle-plugins/cordapp/build.gradle b/gradle-plugins/cordapp/build.gradle deleted file mode 100644 index 3d1ecb6b53..0000000000 --- a/gradle-plugins/cordapp/build.gradle +++ /dev/null @@ -1,19 +0,0 @@ -apply plugin: 'kotlin' -apply plugin: 'net.corda.plugins.publish-utils' -apply plugin: 'com.jfrog.artifactory' - -description 'Turns a project into a cordapp project that produces cordapp fat JARs' - -repositories { - mavenCentral() - jcenter() -} - -dependencies { - compile gradleApi() - compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" -} - -publish { - name project.name -} \ No newline at end of file diff --git a/gradle-plugins/cordapp/src/main/kotlin/net/corda/plugins/CordappPlugin.kt b/gradle-plugins/cordapp/src/main/kotlin/net/corda/plugins/CordappPlugin.kt deleted file mode 100644 index 62119cf0f5..0000000000 --- a/gradle-plugins/cordapp/src/main/kotlin/net/corda/plugins/CordappPlugin.kt +++ /dev/null @@ -1,75 +0,0 @@ -package net.corda.plugins - -import org.gradle.api.* -import org.gradle.api.artifacts.* -import org.gradle.jvm.tasks.Jar -import java.io.File - -/** - * The Cordapp plugin will turn a project into a cordapp project which builds cordapp JARs with the correct format - * and with the information needed to run on Corda. - */ -class CordappPlugin : Plugin { - override fun apply(project: Project) { - project.logger.info("Configuring ${project.name} as a cordapp") - - Utils.createCompileConfiguration("cordapp", project) - Utils.createCompileConfiguration("cordaCompile", project) - - val configuration: Configuration = project.configurations.create("cordaRuntime") - configuration.isTransitive = false - project.configurations.single { it.name == "runtime" }.extendsFrom(configuration) - - configureCordappJar(project) - } - - /** - * Configures this project's JAR as a Cordapp JAR - */ - private fun configureCordappJar(project: Project) { - // Note: project.afterEvaluate did not have full dependency resolution completed, hence a task is used instead - val task = project.task("configureCordappFatJar") - val jarTask = project.tasks.getByName("jar") as Jar - task.doLast { - jarTask.from(getDirectNonCordaDependencies(project).map { project.zipTree(it)}).apply { - exclude("META-INF/*.SF") - exclude("META-INF/*.DSA") - exclude("META-INF/*.RSA") - } - } - jarTask.dependsOn(task) - } - - private fun getDirectNonCordaDependencies(project: Project): Set { - project.logger.info("Finding direct non-corda dependencies for inclusion in CorDapp JAR") - val excludes = listOf( - mapOf("group" to "org.jetbrains.kotlin", "name" to "kotlin-stdlib"), - mapOf("group" to "org.jetbrains.kotlin", "name" to "kotlin-stdlib-jre8"), - mapOf("group" to "org.jetbrains.kotlin", "name" to "kotlin-reflect"), - mapOf("group" to "co.paralleluniverse", "name" to "quasar-core") - ) - - val runtimeConfiguration = project.configuration("runtime") - // The direct dependencies of this project - val excludeDeps = project.configuration("cordapp").allDependencies + - project.configuration("cordaCompile").allDependencies + - project.configuration("cordaRuntime").allDependencies - val directDeps = runtimeConfiguration.allDependencies - excludeDeps - // We want to filter out anything Corda related or provided by Corda, like kotlin-stdlib and quasar - val filteredDeps = directDeps.filter { dep -> - excludes.none { exclude -> (exclude["group"] == dep.group) && (exclude["name"] == dep.name) } - } - filteredDeps.forEach { - // net.corda or com.r3.corda may be a core dependency which shouldn't be included in this cordapp so give a warning - val group = it.group?.toString() ?: "" - if (group.startsWith("net.corda.") || group.startsWith("com.r3.corda.")) { - project.logger.warn("You appear to have included a Corda platform component ($it) using a 'compile' or 'runtime' dependency." + - "This can cause node stability problems. Please use 'corda' instead." + - "See http://docs.corda.net/cordapp-build-systems.html") - } else { - project.logger.info("Including dependency in CorDapp JAR: $it") - } - } - return filteredDeps.map { runtimeConfiguration.files(it) }.flatten().toSet() - } -} diff --git a/gradle-plugins/cordapp/src/main/kotlin/net/corda/plugins/Utils.kt b/gradle-plugins/cordapp/src/main/kotlin/net/corda/plugins/Utils.kt deleted file mode 100644 index 7572cd9876..0000000000 --- a/gradle-plugins/cordapp/src/main/kotlin/net/corda/plugins/Utils.kt +++ /dev/null @@ -1,35 +0,0 @@ -package net.corda.plugins - -import org.gradle.api.Project -import org.gradle.api.Task -import org.gradle.api.artifacts.Configuration -import org.gradle.api.plugins.ExtraPropertiesExtension - -/** - * Mimics the "project.ext" functionality in groovy which provides a direct - * accessor to the "ext" extention (See: ExtraPropertiesExtension) - */ -@Suppress("UNCHECKED_CAST") -fun Project.ext(name: String): T = (extensions.findByName("ext") as ExtraPropertiesExtension).get(name) as T -fun Project.configuration(name: String): Configuration = configurations.single { it.name == name } - -class Utils { - companion object { - @JvmStatic - fun createCompileConfiguration(name: String, project: Project) { - if(!project.configurations.any { it.name == name }) { - val configuration = project.configurations.create(name) - configuration.isTransitive = false - project.configurations.single { it.name == "compile" }.extendsFrom(configuration) - } - } - fun createRuntimeConfiguration(name: String, project: Project) { - if(!project.configurations.any { it.name == name }) { - val configuration = project.configurations.create(name) - configuration.isTransitive = false - project.configurations.single { it.name == "runtime" }.extendsFrom(configuration) - } - } - } - -} \ No newline at end of file diff --git a/gradle-plugins/cordapp/src/main/resources/META-INF/gradle-plugins/net.corda.plugins.cordapp.properties b/gradle-plugins/cordapp/src/main/resources/META-INF/gradle-plugins/net.corda.plugins.cordapp.properties deleted file mode 100644 index 90871e27c8..0000000000 --- a/gradle-plugins/cordapp/src/main/resources/META-INF/gradle-plugins/net.corda.plugins.cordapp.properties +++ /dev/null @@ -1 +0,0 @@ -implementation-class=net.corda.plugins.CordappPlugin diff --git a/gradle-plugins/cordform-common/README.md b/gradle-plugins/cordform-common/README.md deleted file mode 100644 index 8b83c20e93..0000000000 --- a/gradle-plugins/cordform-common/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Cordform Common - -This project contains common node types that both the Corda gradle plugin suite and Corda project -require in order to build Corda nodes. \ No newline at end of file diff --git a/gradle-plugins/cordform-common/build.gradle b/gradle-plugins/cordform-common/build.gradle deleted file mode 100644 index be2fa0cf16..0000000000 --- a/gradle-plugins/cordform-common/build.gradle +++ /dev/null @@ -1,24 +0,0 @@ -apply plugin: 'java' -apply plugin: 'maven-publish' -apply plugin: 'net.corda.plugins.publish-utils' -apply plugin: 'com.jfrog.artifactory' - -repositories { - mavenCentral() -} - -// This tracks the gradle plugins version and not Corda -version gradle_plugins_version -group 'net.corda.plugins' - -dependencies { - // JSR 305: Nullability annotations - compile "com.google.code.findbugs:jsr305:$jsr305_version" - - // TypeSafe Config: for simple and human friendly config files. - compile "com.typesafe:config:$typesafe_config_version" -} - -publish { - name project.name -} \ No newline at end of file diff --git a/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/CordformContext.java b/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/CordformContext.java deleted file mode 100644 index 7687f68a11..0000000000 --- a/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/CordformContext.java +++ /dev/null @@ -1,7 +0,0 @@ -package net.corda.cordform; - -import java.nio.file.Path; - -public interface CordformContext { - Path baseDirectory(String nodeName); -} diff --git a/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/CordformDefinition.java b/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/CordformDefinition.java deleted file mode 100644 index fc62b1bbee..0000000000 --- a/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/CordformDefinition.java +++ /dev/null @@ -1,40 +0,0 @@ -package net.corda.cordform; - -import javax.annotation.Nonnull; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - -public abstract class CordformDefinition { - private Path nodesDirectory = Paths.get("build", "nodes"); - private final List> nodeConfigurers = new ArrayList<>(); - private final List cordappPackages = new ArrayList<>(); - - public Path getNodesDirectory() { - return nodesDirectory; - } - - public void setNodesDirectory(Path nodesDirectory) { - this.nodesDirectory = nodesDirectory; - } - - public List> getNodeConfigurers() { - return nodeConfigurers; - } - - public void addNode(Consumer configurer) { - nodeConfigurers.add(configurer); - } - - public List getCordappPackages() { - return cordappPackages; - } - - /** - * Make arbitrary changes to the node directories before they are started. - * @param context Lookup of node directory by node name. - */ - public abstract void setup(@Nonnull CordformContext context); -} diff --git a/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/CordformNode.java b/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/CordformNode.java deleted file mode 100644 index c3dc01a5da..0000000000 --- a/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/CordformNode.java +++ /dev/null @@ -1,190 +0,0 @@ -package net.corda.cordform; - -import com.typesafe.config.Config; -import com.typesafe.config.ConfigFactory; -import com.typesafe.config.ConfigValueFactory; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.List; -import java.util.Map; - -import static java.util.Collections.emptyList; - -public class CordformNode implements NodeDefinition { - /** - * Path relative to the running node where the serialized NodeInfos are stored. - */ - public static final String NODE_INFO_DIRECTORY = "additional-node-infos"; - - protected static final String DEFAULT_HOST = "localhost"; - - /** - * Name of the node. Node will be placed in directory based on this name - all lowercase with whitespaces removed. - * Actual node name inside node.conf will be as set here. - */ - private String name; - - public String getName() { - return name; - } - - /** - * p2p Port. - */ - private int p2pPort = 10002; - - public int getP2pPort() { return p2pPort; } - - /** - * RPC Port. - */ - private int rpcPort = 10003; - - public int getRpcPort() { return rpcPort; } - - /** - * Set the RPC users for this node. This configuration block allows arbitrary configuration. - * The recommended current structure is: - * [[['username': "username_here", 'password': "password_here", 'permissions': ["permissions_here"]]] - * The above is a list to a map of keys to values using Groovy map and list shorthands. - * - * Incorrect configurations will not cause a DSL error. - */ - public List> rpcUsers = emptyList(); - - /** - * Apply the notary configuration if this node is a notary. The map is the config structure of - * net.corda.node.services.config.NotaryConfig - */ - public Map notary = null; - - public Map extraConfig = null; - - protected Config config = ConfigFactory.empty(); - - public Config getConfig() { - return config; - } - - /** - * Set the name of the node. - * - * @param name The node name. - */ - public void name(String name) { - this.name = name; - setValue("myLegalName", name); - } - - /** - * Get the artemis address for this node. - * - * @return This node's P2P address. - */ - @Nonnull - public String getP2pAddress() { - return config.getString("p2pAddress"); - } - - /** - * Set the Artemis P2P port for this node on localhost. - * - * @param p2pPort The Artemis messaging queue port. - */ - public void p2pPort(int p2pPort) { - p2pAddress(DEFAULT_HOST + ':' + p2pPort); - this.p2pPort = p2pPort; - } - - /** - * Set the Artemis P2P address for this node. - * - * @param p2pAddress The Artemis messaging queue host and port. - */ - public void p2pAddress(String p2pAddress) { - setValue("p2pAddress", p2pAddress); - } - - /** - * Returns the RPC address for this node, or null if one hasn't been specified. - */ - @Nullable - public String getRpcAddress() { - if (config.hasPath("rpcSettings.address")) { - return config.getConfig("rpcSettings").getString("address"); - } - return getOptionalString("rpcAddress"); - } - - /** - * Set the Artemis RPC port for this node on localhost. - * - * @param rpcPort The Artemis RPC queue port. - * @deprecated Use {@link CordformNode#rpcSettings(RpcSettings)} instead. - */ - @Deprecated - public void rpcPort(int rpcPort) { - rpcAddress(DEFAULT_HOST + ':' + rpcPort); - this.rpcPort = rpcPort; - } - - /** - * Set the Artemis RPC address for this node. - * - * @param rpcAddress The Artemis RPC queue host and port. - * @deprecated Use {@link CordformNode#rpcSettings(RpcSettings)} instead. - */ - @Deprecated - public void rpcAddress(String rpcAddress) { - setValue("rpcAddress", rpcAddress); - } - - /** - * Returns the address of the web server that will connect to the node, or null if one hasn't been specified. - */ - @Nullable - public String getWebAddress() { - return getOptionalString("webAddress"); - } - - /** - * Configure a webserver to connect to the node via RPC. This port will specify the port it will listen on. The node - * must have an RPC address configured. - */ - public void webPort(int webPort) { - webAddress(DEFAULT_HOST + ':' + webPort); - } - - /** - * Configure a webserver to connect to the node via RPC. This address will specify the port it will listen on. The node - * must have an RPC address configured. - */ - public void webAddress(String webAddress) { - setValue("webAddress", webAddress); - } - - /** - * Specifies RPC settings for the node. - */ - public void rpcSettings(RpcSettings settings) { - config = settings.addTo("rpcSettings", config); - } - - /** - * Set the path to a file with optional properties, which are appended to the generated node.conf file. - * - * @param configFile The file path. - */ - public void configFile(String configFile) { - setValue("configFile", configFile); - } - - private String getOptionalString(String path) { - return config.hasPath(path) ? config.getString(path) : null; - } - - private void setValue(String path, Object value) { - config = config.withValue(path, ConfigValueFactory.fromAnyRef(value)); - } -} diff --git a/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/NodeDefinition.java b/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/NodeDefinition.java deleted file mode 100644 index 0b86b98627..0000000000 --- a/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/NodeDefinition.java +++ /dev/null @@ -1,9 +0,0 @@ -package net.corda.cordform; - -import com.typesafe.config.Config; - -public interface NodeDefinition { - String getName(); - - Config getConfig(); -} diff --git a/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/RpcSettings.java b/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/RpcSettings.java deleted file mode 100644 index 1869733271..0000000000 --- a/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/RpcSettings.java +++ /dev/null @@ -1,80 +0,0 @@ -package net.corda.cordform; - -import com.typesafe.config.Config; -import com.typesafe.config.ConfigFactory; -import com.typesafe.config.ConfigValueFactory; - -public final class RpcSettings { - - private Config config = ConfigFactory.empty(); - - private int port = 10003; - private int adminPort = 10005; - - public int getPort() { - return port; - } - - public int getAdminPort() { - return adminPort; - } - - /** - * RPC address for the node. - */ - public final void address(final String value) { - setValue("address", value); - } - - /** - * RPC Port for the node - */ - public final void port(final int value) { - this.port = value; - setValue("address", "localhost:"+port); - } - - /** - * RPC admin address for the node (necessary if [useSsl] is false or unset). - */ - public final void adminAddress(final String value) { - setValue("adminAddress", value); - } - - public final void adminPort(final int value) { - this.adminPort = value; - setValue("adminAddress", "localhost:"+adminPort); - } - - /** - * Specifies whether the node RPC layer will require SSL from clients. - */ - public final void useSsl(final Boolean value) { - setValue("useSsl", value); - } - - /** - * Specifies whether the RPC broker is separate from the node. - */ - public final void standAloneBroker(final Boolean value) { - setValue("standAloneBroker", value); - } - - /** - * Specifies SSL certificates options for the RPC layer. - */ - public final void ssl(final SslOptions options) { - config = options.addTo("ssl", config); - } - - public final Config addTo(final String key, final Config config) { - if (this.config.isEmpty()) { - return config; - } - return config.withValue(key, this.config.root()); - } - - private void setValue(String path, Object value) { - config = config.withValue(path, ConfigValueFactory.fromAnyRef(value)); - } -} diff --git a/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/SslOptions.java b/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/SslOptions.java deleted file mode 100644 index 1444d4ed8c..0000000000 --- a/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/SslOptions.java +++ /dev/null @@ -1,56 +0,0 @@ -package net.corda.cordform; - -import com.typesafe.config.Config; -import com.typesafe.config.ConfigFactory; -import com.typesafe.config.ConfigValueFactory; - -public final class SslOptions { - - private Config config = ConfigFactory.empty(); - - /** - * Password for the keystore. - */ - public final void keyStorePassword(final String value) { - setValue("keyStorePassword", value); - } - - /** - * Password for the truststore. - */ - public final void trustStorePassword(final String value) { - setValue("trustStorePassword", value); - } - - /** - * Directory under which key stores are to be placed. - */ - public final void certificatesDirectory(final String value) { - setValue("certificatesDirectory", value); - } - - /** - * Absolute path to SSL keystore. Default: "[certificatesDirectory]/sslkeystore.jks" - */ - public final void sslKeystore(final String value) { - setValue("sslKeystore", value); - } - - /** - * Absolute path to SSL truststore. Default: "[certificatesDirectory]/truststore.jks" - */ - public final void trustStoreFile(final String value) { - setValue("trustStoreFile", value); - } - - public final Config addTo(final String key, final Config config) { - if (this.config.isEmpty()) { - return config; - } - return config.withValue(key, this.config.root()); - } - - private void setValue(String path, Object value) { - config = config.withValue(path, ConfigValueFactory.fromAnyRef(value)); - } -} diff --git a/gradle-plugins/cordformation/README.rst b/gradle-plugins/cordformation/README.rst deleted file mode 100644 index f619737a91..0000000000 --- a/gradle-plugins/cordformation/README.rst +++ /dev/null @@ -1 +0,0 @@ -Please refer to the documentation in /doc/build/html/running-a-node.html#cordformation. \ No newline at end of file diff --git a/gradle-plugins/cordformation/build.gradle b/gradle-plugins/cordformation/build.gradle deleted file mode 100644 index 5ac1e33473..0000000000 --- a/gradle-plugins/cordformation/build.gradle +++ /dev/null @@ -1,64 +0,0 @@ -buildscript { - repositories { - mavenCentral() - } - - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - -apply plugin: 'kotlin' -apply plugin: 'net.corda.plugins.publish-utils' -apply plugin: 'com.jfrog.artifactory' - -description 'A small gradle plugin for adding some basic Quasar tasks and configurations to reduce build.gradle bloat.' - -repositories { - mavenCentral() -} - -configurations { - noderunner - compile.extendsFrom noderunner -} - -sourceSets { - runnodes { - kotlin { - srcDir file('src/noderunner/kotlin') - compileClasspath += configurations.noderunner - } - } -} - -dependencies { - compile gradleApi() - compile project(":cordapp") - compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" - - noderunner "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" - - compile project(':cordform-common') - // Docker-compose file generation - compile "org.yaml:snakeyaml:$snake_yaml_version" -} - -task createNodeRunner(type: Jar, dependsOn: [classes]) { - manifest { - attributes('Main-Class': 'net.corda.plugins.NodeRunnerKt') - } - classifier = 'fatjar' - from { configurations.noderunner.collect { it.isDirectory() ? it : zipTree(it) } } - from sourceSets.runnodes.output -} - -jar { - from(createNodeRunner) { - rename { 'net/corda/plugins/runnodes.jar' } - } -} - -publish { - name project.name -} diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Baseform.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Baseform.kt deleted file mode 100644 index ea528aaf08..0000000000 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Baseform.kt +++ /dev/null @@ -1,174 +0,0 @@ -package net.corda.plugins - -import groovy.lang.Closure -import net.corda.cordform.CordformDefinition -import org.apache.tools.ant.filters.FixCrLfFilter -import org.gradle.api.DefaultTask -import org.gradle.api.plugins.JavaPluginConvention -import org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME -import org.gradle.api.tasks.TaskAction -import java.io.File -import java.lang.reflect.InvocationTargetException -import java.net.URLClassLoader -import java.nio.file.Path -import java.nio.file.Paths -import java.util.jar.JarInputStream - -/** - * Creates nodes based on the configuration of this task in the gradle configuration DSL. - * - * See documentation for examples. - */ -@Suppress("unused") -open class Baseform : DefaultTask() { - private companion object { - val nodeJarName = "corda.jar" - private val defaultDirectory: Path = Paths.get("build", "nodes") - } - - /** - * Optionally the name of a CordformDefinition subclass to which all configuration will be delegated. - */ - @Suppress("MemberVisibilityCanPrivate") - var definitionClass: String? = null - var directory = defaultDirectory - protected val nodes = mutableListOf() - - /** - * Set the directory to install nodes into. - * - * @param directory The directory the nodes will be installed into. - */ - fun directory(directory: String) { - this.directory = Paths.get(directory) - } - - /** - * Add a node configuration. - * - * @param configureClosure A node configuration that will be deployed. - */ - @Suppress("MemberVisibilityCanPrivate") - fun node(configureClosure: Closure) { - nodes += project.configure(Node(project), configureClosure) as Node - } - - /** - * Add a node configuration - * - * @param configureFunc A node configuration that will be deployed - */ - @Suppress("MemberVisibilityCanPrivate") - fun node(configureFunc: Node.() -> Any?): Node { - val node = Node(project).apply { configureFunc() } - nodes += node - return node - } - - /** - * Returns a node by name. - * - * @param name The name of the node as specified in the node configuration DSL. - * @return A node instance. - */ - private fun getNodeByName(name: String): Node? = nodes.firstOrNull { it.name == name } - - /** - * The definitionClass needn't be compiled until just before our build method, so we load it manually via sourceSets.main.runtimeClasspath. - */ - private fun loadCordformDefinition(): CordformDefinition { - val plugin = project.convention.getPlugin(JavaPluginConvention::class.java) - val classpath = plugin.sourceSets.getByName(MAIN_SOURCE_SET_NAME).runtimeClasspath - val urls = classpath.files.map { it.toURI().toURL() }.toTypedArray() - return URLClassLoader(urls, CordformDefinition::class.java.classLoader) - .loadClass(definitionClass) - .asSubclass(CordformDefinition::class.java) - .newInstance() - } - - /** - * The NetworkBootstrapper needn't be compiled until just before our build method, so we load it manually via sourceSets.main.runtimeClasspath. - */ - private fun loadNetworkBootstrapperClass(): Class<*> { - val plugin = project.convention.getPlugin(JavaPluginConvention::class.java) - val classpath = plugin.sourceSets.getByName(MAIN_SOURCE_SET_NAME).runtimeClasspath - val urls = classpath.files.map { it.toURI().toURL() }.toTypedArray() - return URLClassLoader(urls, javaClass.classLoader).loadClass("net.corda.nodeapi.internal.network.NetworkBootstrapper") - } - - /** - * Installs the corda fat JAR to the root directory, for the network bootstrapper to use. - */ - protected fun installCordaJar() { - val cordaJar = Cordformation.verifyAndGetRuntimeJar(project, "corda") - project.copy { - it.apply { - from(cordaJar) - into(directory) - rename(cordaJar.name, nodeJarName) - fileMode = Cordformation.executableFileMode - } - } - } - - protected fun initializeConfiguration() { - if (definitionClass != null) { - val cd = loadCordformDefinition() - // If the user has specified their own directory (even if it's the same default path) then let them know - // it's not used and should just rely on the one in CordformDefinition - require(directory === defaultDirectory) { - "'directory' cannot be used when 'definitionClass' is specified. Use CordformDefinition.nodesDirectory instead." - } - directory = cd.nodesDirectory - val cordapps = cd.getMatchingCordapps() - cd.nodeConfigurers.forEach { - val node = node { } - it.accept(node) - node.additionalCordapps.addAll(cordapps) - node.rootDir(directory) - } - cd.setup { nodeName -> project.projectDir.toPath().resolve(getNodeByName(nodeName)!!.nodeDir.toPath()) } - } else { - nodes.forEach { - it.rootDir(directory) - } - } - } - - protected fun bootstrapNetwork() { - val networkBootstrapperClass = loadNetworkBootstrapperClass() - val networkBootstrapper = networkBootstrapperClass.newInstance() - val bootstrapMethod = networkBootstrapperClass.getMethod("bootstrap", Path::class.java).apply { isAccessible = true } - // Call NetworkBootstrapper.bootstrap - try { - val rootDir = project.projectDir.toPath().resolve(directory).toAbsolutePath().normalize() - bootstrapMethod.invoke(networkBootstrapper, rootDir) - } catch (e: InvocationTargetException) { - throw e.cause!! - } - } - - private fun CordformDefinition.getMatchingCordapps(): List { - val cordappJars = project.configuration("cordapp").files - return cordappPackages.map { `package` -> - val cordappsWithPackage = cordappJars.filter { it.containsPackage(`package`) } - when (cordappsWithPackage.size) { - 0 -> throw IllegalArgumentException("There are no cordapp dependencies containing the package $`package`") - 1 -> cordappsWithPackage[0] - else -> throw IllegalArgumentException("More than one cordapp dependency contains the package $`package`: $cordappsWithPackage") - } - } - } - - private fun File.containsPackage(`package`: String): Boolean { - JarInputStream(inputStream()).use { - while (true) { - val name = it.nextJarEntry?.name ?: break - if (name.endsWith(".class") && name.replace('/', '.').startsWith(`package`)) { - return true - } - } - return false - } - } -} diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt deleted file mode 100644 index f207dc1818..0000000000 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt +++ /dev/null @@ -1,66 +0,0 @@ -package net.corda.plugins - -import org.apache.tools.ant.filters.FixCrLfFilter -import org.gradle.api.DefaultTask -import org.gradle.api.plugins.JavaPluginConvention -import org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME -import org.gradle.api.tasks.TaskAction -import java.nio.file.Path -import java.nio.file.Paths - -/** - * Creates nodes based on the configuration of this task in the gradle configuration DSL. - * - * See documentation for examples. - */ -@Suppress("unused") -open class Cordform : Baseform() { - private companion object { - val nodeJarName = "corda.jar" - private val defaultDirectory: Path = Paths.get("build", "nodes") - } - - /** - * Installs the run script into the nodes directory. - */ - private fun installRunScript() { - project.copy { - it.apply { - from(Cordformation.getPluginFile(project, "net/corda/plugins/runnodes.jar")) - fileMode = Cordformation.executableFileMode - into("$directory/") - } - } - - project.copy { - it.apply { - from(Cordformation.getPluginFile(project, "net/corda/plugins/runnodes")) - // Replaces end of line with lf to avoid issues with the bash interpreter and Windows style line endings. - filter(mapOf("eol" to FixCrLfFilter.CrLf.newInstance("lf")), FixCrLfFilter::class.java) - fileMode = Cordformation.executableFileMode - into("$directory/") - } - } - - project.copy { - it.apply { - from(Cordformation.getPluginFile(project, "net/corda/plugins/runnodes.bat")) - into("$directory/") - } - } - } - - /** - * This task action will create and install the nodes based on the node configurations added. - */ - @TaskAction - fun build() { - project.logger.info("Running Cordform task") - initializeConfiguration() - nodes.forEach(Node::installConfig) - installCordaJar() - installRunScript() - bootstrapNetwork() - nodes.forEach(Node::build) - } -} diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordformation.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordformation.kt deleted file mode 100644 index 82a818992d..0000000000 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordformation.kt +++ /dev/null @@ -1,62 +0,0 @@ -package net.corda.plugins - -import org.gradle.api.Plugin -import org.gradle.api.Project -import java.io.File - -/** - * The Cordformation plugin deploys nodes to a directory in a state ready to be used by a developer for experimentation, - * testing, and debugging. It will prepopulate several fields in the configuration and create a simple node runner. - */ -class Cordformation : Plugin { - internal companion object { - const val CORDFORMATION_TYPE = "cordformationInternal" - - /** - * Gets a resource file from this plugin's JAR file. - * - * @param project The project environment this plugin executes in. - * @param filePathInJar The file in the JAR, relative to root, you wish to access. - * @return A file handle to the file in the JAR. - */ - fun getPluginFile(project: Project, filePathInJar: String): File { - val archive = project.rootProject.buildscript.configurations - .single { it.name == "classpath" } - .first { it.name.contains("cordformation") } - return project.rootProject.resources.text - .fromArchiveEntry(archive, filePathInJar) - .asFile() - } - - /** - * Gets a current built corda jar file - * - * @param project The project environment this plugin executes in. - * @param jarName The name of the JAR you wish to access. - * @return A file handle to the file in the JAR. - */ - fun verifyAndGetRuntimeJar(project: Project, jarName: String): File { - val releaseVersion = project.rootProject.ext("corda_release_version") - val maybeJar = project.configuration("runtime").filter { - "$jarName-$releaseVersion.jar" in it.toString() || "$jarName-r3-$releaseVersion.jar" in it.toString() - } - if (maybeJar.isEmpty) { - throw IllegalStateException("No $jarName JAR found. Have you deployed the Corda project to Maven? Looked for \"$jarName-$releaseVersion.jar\"") - } else { - val jar = maybeJar.singleFile - require(jar.isFile) - return jar - } - } - - val executableFileMode = "0755".toInt(8) - } - - override fun apply(project: Project) { - Utils.createCompileConfiguration("cordapp", project) - Utils.createRuntimeConfiguration(CORDFORMATION_TYPE, project) - // TODO: improve how we re-use existing declared external variables from root gradle.build - val jolokiaVersion = try { project.rootProject.ext("jolokia_version") } catch (e: Exception) { "1.3.7" } - project.dependencies.add(CORDFORMATION_TYPE, "org.jolokia:jolokia-jvm:$jolokiaVersion:agent") - } -} diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Dockerform.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Dockerform.kt deleted file mode 100644 index 4e86e5caac..0000000000 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Dockerform.kt +++ /dev/null @@ -1,66 +0,0 @@ -package net.corda.plugins - -import org.apache.tools.ant.filters.FixCrLfFilter -import org.gradle.api.DefaultTask -import org.gradle.api.plugins.JavaPluginConvention -import org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME -import org.gradle.api.tasks.TaskAction -import org.yaml.snakeyaml.DumperOptions -import java.nio.file.Path -import java.nio.file.Paths -import org.yaml.snakeyaml.Yaml -import java.nio.charset.StandardCharsets -import java.nio.file.Files - -/** - * Creates docker-compose file and image definitions based on the configuration of this task in the gradle configuration DSL. - * - * See documentation for examples. - */ -@Suppress("unused") -open class Dockerform : Baseform() { - private companion object { - val nodeJarName = "corda.jar" - private val defaultDirectory: Path = Paths.get("build", "docker") - - private val dockerComposeFileVersion = "3" - - private val yamlOptions = DumperOptions().apply { - indent = 2 - defaultFlowStyle = DumperOptions.FlowStyle.BLOCK - } - private val yaml = Yaml(yamlOptions) - } - - private val directoryPath = project.projectDir.toPath().resolve(directory) - - val dockerComposePath = directoryPath.resolve("docker-compose.yml") - - /** - * This task action will create and install the nodes based on the node configurations added. - */ - @TaskAction - fun build() { - project.logger.info("Running Cordform task") - initializeConfiguration() - nodes.forEach(Node::installDockerConfig) - installCordaJar() - bootstrapNetwork() - nodes.forEach(Node::buildDocker) - - - // Transform nodes path the absolute ones - val services = nodes.map { it.containerName to mapOf( - "build" to directoryPath.resolve(it.nodeDir.name).toAbsolutePath().toString(), - "ports" to listOf(it.rpcPort)) }.toMap() - - - val dockerComposeObject = mapOf( - "version" to dockerComposeFileVersion, - "services" to services) - - val dockerComposeContent = yaml.dump(dockerComposeObject) - - Files.write(dockerComposePath, dockerComposeContent.toByteArray(StandardCharsets.UTF_8)) - } -} diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt deleted file mode 100644 index a9010f832b..0000000000 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt +++ /dev/null @@ -1,300 +0,0 @@ -package net.corda.plugins - -import com.typesafe.config.ConfigFactory -import com.typesafe.config.ConfigRenderOptions -import com.typesafe.config.ConfigValueFactory -import com.typesafe.config.ConfigObject -import groovy.lang.Closure -import net.corda.cordform.CordformNode -import net.corda.cordform.RpcSettings -import org.gradle.api.Project -import java.io.File -import java.nio.charset.StandardCharsets -import java.nio.file.Files -import java.nio.file.Path - -/** - * Represents a node that will be installed. - */ -class Node(private val project: Project) : CordformNode() { - companion object { - @JvmStatic - val webJarName = "corda-webserver.jar" - private val configFileProperty = "configFile" - } - - /** - * Set the list of CorDapps to install to the plugins directory. Each cordapp is a fully qualified Maven - * dependency name, eg: com.example:product-name:0.1 - * - * @note Your app will be installed by default and does not need to be included here. - * @note Type is any due to gradle's use of "GStrings" - each value will have "toString" called on it - */ - var cordapps = mutableListOf() - internal var additionalCordapps = mutableListOf() - internal lateinit var nodeDir: File - private set - internal lateinit var rootDir: File - private set - internal lateinit var containerName: String - private set - internal var rpcSettings: RpcSettings = RpcSettings() - private set - internal var webserverJar: String? = null - private set - - /** - * Sets whether this node will use HTTPS communication. - * - * @param isHttps True if this node uses HTTPS communication. - */ - fun https(isHttps: Boolean) { - config = config.withValue("useHTTPS", ConfigValueFactory.fromAnyRef(isHttps)) - } - - /** - * Sets the H2 port for this node - */ - fun h2Port(h2Port: Int) { - config = config.withValue("h2port", ConfigValueFactory.fromAnyRef(h2Port)) - } - - fun useTestClock(useTestClock: Boolean) { - config = config.withValue("useTestClock", ConfigValueFactory.fromAnyRef(useTestClock)) - } - - /** - * Specifies RPC settings for the node. - */ - fun rpcSettings(configureClosure: Closure) { - rpcSettings = project.configure(RpcSettings(), configureClosure) as RpcSettings - config = rpcSettings.addTo("rpcSettings", config) - } - - /** - * Enables SSH access on given port - * - * @param sshdPort The port for SSH server to listen on - */ - fun sshdPort(sshdPort: Int?) { - config = config.withValue("sshd.port", ConfigValueFactory.fromAnyRef(sshdPort)) - } - - /** - * The webserver JAR to be used by this node. - * - * If not provided, the default development webserver is used. - * - * @param webserverJar The file path of the webserver JAR to use. - */ - fun webserverJar(webserverJar: String) { - this.webserverJar = webserverJar - } - - internal fun build() { - if (config.hasPath("webAddress")) { - installWebserverJar() - } - installAgentJar() - installBuiltCordapp() - installCordapps() - } - - internal fun buildDocker() { - project.copy { - it.apply { - from(Cordformation.getPluginFile(project, "net/corda/plugins/Dockerfile")) - from(Cordformation.getPluginFile(project, "net/corda/plugins/run-corda.sh")) - into("$nodeDir/") - } - } - installAgentJar() - installBuiltCordapp() - installCordapps() - } - - internal fun rootDir(rootDir: Path) { - if (name == null) { - project.logger.error("Node has a null name - cannot create node") - throw IllegalStateException("Node has a null name - cannot create node") - } - // Parsing O= part directly because importing BouncyCastle provider in Cordformation causes problems - // with loading our custom X509EdDSAEngine. - val organizationName = name.trim().split(",").firstOrNull { it.startsWith("O=") }?.substringAfter("=") - val dirName = organizationName ?: name - containerName = dirName.replace("\\s+".toRegex(), "-").toLowerCase() - this.rootDir = rootDir.toFile() - nodeDir = File(this.rootDir, dirName.replace("\\s", "")) - Files.createDirectories(nodeDir.toPath()) - } - - private fun configureProperties() { - config = config.withValue("database.runMigration", ConfigValueFactory.fromAnyRef(true)) - config = config.withValue("rpcUsers", ConfigValueFactory.fromIterable(rpcUsers)) - if (notary != null) { - config = config.withValue("notary", ConfigValueFactory.fromMap(notary)) - } - if (extraConfig != null) { - config = config.withFallback(ConfigFactory.parseMap(extraConfig)) - } - } - - /** - * Installs the corda webserver JAR to the node directory - */ - private fun installWebserverJar() { - // If no webserver JAR is provided, the default development webserver is used. - val webJar = if (webserverJar == null) { - project.logger.info("Using default development webserver.") - Cordformation.verifyAndGetRuntimeJar(project, "corda-webserver") - } else { - project.logger.info("Using custom webserver: $webserverJar.") - File(webserverJar) - } - - project.copy { - it.apply { - from(webJar) - into(nodeDir) - rename(webJar.name, webJarName) - } - } - } - - /** - * Installs this project's cordapp to this directory. - */ - private fun installBuiltCordapp() { - val cordappsDir = File(nodeDir, "cordapps") - project.copy { - it.apply { - from(project.tasks.getByName("jar")) - into(cordappsDir) - } - } - } - - /** - * Installs the jolokia monitoring agent JAR to the node/drivers directory - */ - private fun installAgentJar() { - // TODO: improve how we re-use existing declared external variables from root gradle.build - val jolokiaVersion = try { project.rootProject.ext("jolokia_version") } catch (e: Exception) { "1.3.7" } - val agentJar = project.configuration("runtime").files { - (it.group == "org.jolokia") && - (it.name == "jolokia-jvm") && - (it.version == jolokiaVersion) - // TODO: revisit when classifier attribute is added. eg && (it.classifier = "agent") - }.first() // should always be the jolokia agent fat jar: eg. jolokia-jvm-1.3.7-agent.jar - project.logger.info("Jolokia agent jar: $agentJar") - if (agentJar.isFile) { - val driversDir = File(nodeDir, "drivers") - project.copy { - it.apply { - from(agentJar) - into(driversDir) - } - } - } - } - - private fun createTempConfigFile(configObject: ConfigObject): File { - val options = ConfigRenderOptions - .defaults() - .setOriginComments(false) - .setComments(false) - .setFormatted(true) - .setJson(false) - val configFileText = configObject.render(options).split("\n").toList() - // Need to write a temporary file first to use the project.copy, which resolves directories correctly. - val tmpDir = File(project.buildDir, "tmp") - Files.createDirectories(tmpDir.toPath()) - var fileName = "${nodeDir.name}.conf" - val tmpConfFile = File(tmpDir, fileName) - Files.write(tmpConfFile.toPath(), configFileText, StandardCharsets.UTF_8) - return tmpConfFile - } - - /** - * Installs the configuration file to the root directory and detokenises it. - */ - internal fun installConfig() { - configureProperties() - val tmpConfFile = createTempConfigFile(config.root()) - appendOptionalConfig(tmpConfFile) - project.copy { - it.apply { - from(tmpConfFile) - into(rootDir) - } - } - } - - /** - * Installs the Dockerized configuration file to the root directory and detokenises it. - */ - internal fun installDockerConfig() { - configureProperties() - val dockerConf = config - .withValue("p2pAddress", ConfigValueFactory.fromAnyRef("$containerName:$p2pPort")) - .withValue("rpcSettings.address", ConfigValueFactory.fromAnyRef("$containerName:${rpcSettings.port}")) - .withValue("rpcSettings.adminAddress", ConfigValueFactory.fromAnyRef("$containerName:${rpcSettings.adminPort}")) - .withValue("detectPublicIp", ConfigValueFactory.fromAnyRef(false)) - val tmpConfFile = createTempConfigFile(dockerConf.root()) - appendOptionalConfig(tmpConfFile) - project.copy { - it.apply { - from(tmpConfFile) - into(rootDir) - } - } - } - - /** - * Appends installed config file with properties from an optional file. - */ - private fun appendOptionalConfig(confFile: File) { - val optionalConfig: File? = when { - project.findProperty(configFileProperty) != null -> //provided by -PconfigFile command line property when running Gradle task - File(project.findProperty(configFileProperty) as String) - config.hasPath(configFileProperty) -> File(config.getString(configFileProperty)) - else -> null - } - - if (optionalConfig != null) { - if (!optionalConfig.exists()) { - project.logger.error("$configFileProperty '$optionalConfig' not found") - } else { - confFile.appendBytes(optionalConfig.readBytes()) - } - } - } - - /** - * Installs other cordapps to this node's cordapps directory. - */ - internal fun installCordapps() { - additionalCordapps.addAll(getCordappList()) - val cordappsDir = File(nodeDir, "cordapps") - project.copy { - it.apply { - from(additionalCordapps) - into(cordappsDir) - } - } - } - - /** - * Gets a list of cordapps based on what dependent cordapps were specified. - * - * @return List of this node's cordapps. - */ - private fun getCordappList(): Collection { - // Cordapps can sometimes contain a GString instance which fails the equality test with the Java string - @Suppress("RemoveRedundantCallsOfConversionMethods") - val cordapps: List = cordapps.map { it.toString() } - return project.configuration("cordapp").files { - cordapps.contains(it.group + ":" + it.name + ":" + it.version) - } - } -} diff --git a/gradle-plugins/cordformation/src/main/resources/META-INF/gradle-plugins/net.corda.plugins.cordformation.properties b/gradle-plugins/cordformation/src/main/resources/META-INF/gradle-plugins/net.corda.plugins.cordformation.properties deleted file mode 100644 index 4475d5c692..0000000000 --- a/gradle-plugins/cordformation/src/main/resources/META-INF/gradle-plugins/net.corda.plugins.cordformation.properties +++ /dev/null @@ -1 +0,0 @@ -implementation-class=net.corda.plugins.Cordformation diff --git a/gradle-plugins/cordformation/src/main/resources/net/corda/plugins/Dockerfile b/gradle-plugins/cordformation/src/main/resources/net/corda/plugins/Dockerfile deleted file mode 100644 index 66ab277852..0000000000 --- a/gradle-plugins/cordformation/src/main/resources/net/corda/plugins/Dockerfile +++ /dev/null @@ -1,44 +0,0 @@ -# Base image from (http://phusion.github.io/baseimage-docker) -FROM openjdk:8u151-jre-alpine - -ENV CORDA_VERSION=${BUILDTIME_CORDA_VERSION} -ENV JAVA_OPTIONS=${BUILDTIME_JAVA_OPTIONS} - -# Set image labels -LABEL net.corda.version = ${CORDA_VERSION} \ - maintainer = "" \ - vendor = "R3" - -RUN apk upgrade --update && \ - apk add --update --no-cache bash iputils && \ - rm -rf /var/cache/apk/* && \ - # Add user to run the app && \ - addgroup corda && \ - adduser -G corda -D -s /bin/bash corda && \ - # Create /opt/corda directory && \ - mkdir -p /opt/corda/plugins && \ - mkdir -p /opt/corda/logs - -# Copy corda files -ADD --chown=corda:corda corda.jar /opt/corda/corda.jar -ADD --chown=corda:corda node.conf /opt/corda/node.conf -ADD --chown=corda:corda network-parameters /opt/corda/ -ADD --chown=corda:corda cordapps/ /opt/corda/cordapps -ADD --chown=corda:corda additional-node-infos/ /opt/corda/additional-node-infos -ADD --chown=corda:corda certificates/ /opt/corda/certificates -ADD --chown=corda:corda drivers/ /opt/corda/drivers -ADD --chown=corda:corda persistence* /opt/corda/ - -COPY run-corda.sh /run-corda.sh - -RUN chmod +x /run-corda.sh && \ - sync && \ - chown -R corda:corda /opt/corda - -# Working directory for Corda -WORKDIR /opt/corda -ENV HOME=/opt/corda -USER corda - -# Start it -CMD ["/run-corda.sh"] \ No newline at end of file diff --git a/gradle-plugins/cordformation/src/main/resources/net/corda/plugins/run-corda.sh b/gradle-plugins/cordformation/src/main/resources/net/corda/plugins/run-corda.sh deleted file mode 100644 index c10604f859..0000000000 --- a/gradle-plugins/cordformation/src/main/resources/net/corda/plugins/run-corda.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/sh - -# If variable not present use default values -: ${CORDA_HOME:=/opt/corda} -: ${JAVA_OPTIONS:=-Xmx512m} - -export CORDA_HOME JAVA_OPTIONS - -cd ${CORDA_HOME} -java $JAVA_OPTIONS -jar ${CORDA_HOME}/corda.jar 2>&1 \ No newline at end of file diff --git a/gradle-plugins/cordformation/src/main/resources/net/corda/plugins/runnodes b/gradle-plugins/cordformation/src/main/resources/net/corda/plugins/runnodes deleted file mode 100644 index 9e3ba4c5be..0000000000 --- a/gradle-plugins/cordformation/src/main/resources/net/corda/plugins/runnodes +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -# Allow the script to be run from outside the nodes directory. -basedir=$( dirname "$0" ) -cd "$basedir" - -if which osascript >/dev/null; then - /usr/libexec/java_home -v 1.8 --exec java -jar runnodes.jar "$@" -else - "${JAVA_HOME:+$JAVA_HOME/bin/}java" -jar runnodes.jar "$@" -fi diff --git a/gradle-plugins/cordformation/src/main/resources/net/corda/plugins/runnodes.bat b/gradle-plugins/cordformation/src/main/resources/net/corda/plugins/runnodes.bat deleted file mode 100644 index a6acf1f737..0000000000 --- a/gradle-plugins/cordformation/src/main/resources/net/corda/plugins/runnodes.bat +++ /dev/null @@ -1,8 +0,0 @@ -@echo off - -REM Change to the directory of this script (%~dp0) -Pushd %~dp0 - -java -jar runnodes.jar %* - -Popd \ No newline at end of file diff --git a/gradle-plugins/cordformation/src/noderunner/kotlin/net/corda/plugins/NodeRunner.kt b/gradle-plugins/cordformation/src/noderunner/kotlin/net/corda/plugins/NodeRunner.kt deleted file mode 100644 index bb308a8191..0000000000 --- a/gradle-plugins/cordformation/src/noderunner/kotlin/net/corda/plugins/NodeRunner.kt +++ /dev/null @@ -1,151 +0,0 @@ -package net.corda.plugins - -import java.awt.GraphicsEnvironment -import java.io.File -import java.nio.file.Files -import java.util.* - -private val HEADLESS_FLAG = "--headless" -private val CAPSULE_DEBUG_FLAG = "--capsule-debug" - -private val os by lazy { - val osName = System.getProperty("os.name", "generic").toLowerCase(Locale.ENGLISH) - if ("mac" in osName || "darwin" in osName) OS.MACOS - else if ("win" in osName) OS.WINDOWS - else OS.LINUX -} - -private enum class OS { MACOS, WINDOWS, LINUX } - -private object debugPortAlloc { - private var basePort = 5005 - internal fun next() = basePort++ -} - -private object monitoringPortAlloc { - private var basePort = 7005 - internal fun next() = basePort++ -} - -fun main(args: Array) { - val startedProcesses = mutableListOf() - val headless = GraphicsEnvironment.isHeadless() || args.contains(HEADLESS_FLAG) - val capsuleDebugMode = args.contains(CAPSULE_DEBUG_FLAG) - val workingDir = File(System.getProperty("user.dir")) - val javaArgs = args.filter { it != HEADLESS_FLAG && it != CAPSULE_DEBUG_FLAG } - val jvmArgs = if (capsuleDebugMode) listOf("-Dcapsule.log=verbose") else emptyList() - println("Starting nodes in $workingDir") - workingDir.listFiles { file -> file.isDirectory }.forEach { dir -> - listOf(NodeJarType, WebJarType).forEach { jarType -> - jarType.acceptDirAndStartProcess(dir, headless, javaArgs, jvmArgs)?.let { startedProcesses += it } - } - } - println("Started ${startedProcesses.size} processes") - println("Finished starting nodes") -} - -private abstract class JarType(private val jarName: String) { - internal abstract fun acceptNodeConf(nodeConf: File): Boolean - internal fun acceptDirAndStartProcess(dir: File, headless: Boolean, javaArgs: List, jvmArgs: List): Process? { - if (!File(dir, jarName).exists()) { - return null - } - if (!File(dir, "node.conf").let { it.exists() && acceptNodeConf(it) }) { - return null - } - val debugPort = debugPortAlloc.next() - val monitoringPort = monitoringPortAlloc.next() - println("Starting $jarName in $dir on debug port $debugPort") - val process = (if (headless) ::HeadlessJavaCommand else ::TerminalWindowJavaCommand)(jarName, dir, debugPort, monitoringPort, javaArgs, jvmArgs).start() - if (os == OS.MACOS) Thread.sleep(1000) - return process - } -} - -private object NodeJarType : JarType("corda.jar") { - override fun acceptNodeConf(nodeConf: File) = true -} - -private object WebJarType : JarType("corda-webserver.jar") { - // TODO: Add a webserver.conf, or use TypeSafe config instead of this hack - override fun acceptNodeConf(nodeConf: File) = Files.lines(nodeConf.toPath()).anyMatch { "webAddress" in it } -} - -private abstract class JavaCommand( - jarName: String, - internal val dir: File, - debugPort: Int?, - monitoringPort: Int?, - internal val nodeName: String, - init: MutableList.() -> Unit, args: List, - jvmArgs: List -) { - private val jolokiaJar by lazy { - File("$dir/drivers").listFiles { _, filename -> - filename.matches("jolokia-jvm-.*-agent\\.jar$".toRegex()) - }.first().name - } - - internal val command: List = mutableListOf().apply { - add(getJavaPath()) - addAll(jvmArgs) - add("-Dname=$nodeName") - val jvmArgs: MutableList = mutableListOf() - null != debugPort && jvmArgs.add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=$debugPort") - null != monitoringPort && jvmArgs.add("-javaagent:drivers/$jolokiaJar=port=$monitoringPort") - if (jvmArgs.isNotEmpty()) { - add("-Dcapsule.jvm.args=${jvmArgs.joinToString(separator = " ")}") - } - add("-jar") - add(jarName) - init() - addAll(args) - } - - internal abstract fun processBuilder(): ProcessBuilder - internal fun start() = processBuilder().directory(dir).start() - internal abstract fun getJavaPath(): String -} - -private class HeadlessJavaCommand(jarName: String, dir: File, debugPort: Int?, monitoringPort: Int?, args: List, jvmArgs: List) - : JavaCommand(jarName, dir, debugPort, monitoringPort, dir.name, { add("--no-local-shell") }, args, jvmArgs) { - override fun processBuilder() = ProcessBuilder(command).redirectError(File("error.$nodeName.log")).inheritIO() - override fun getJavaPath() = File(File(System.getProperty("java.home"), "bin"), "java").path -} - -private class TerminalWindowJavaCommand(jarName: String, dir: File, debugPort: Int?, monitoringPort: Int?, args: List, jvmArgs: List) - : JavaCommand(jarName, dir, debugPort, monitoringPort, "${dir.name}-$jarName", {}, args, jvmArgs) { - override fun processBuilder() = ProcessBuilder(when (os) { - OS.MACOS -> { - listOf("osascript", "-e", """tell app "Terminal" - activate - delay 0.5 - tell app "System Events" to tell process "Terminal" to keystroke "t" using command down - delay 0.5 - do script "bash -c 'cd \"$dir\" ; \"${command.joinToString("""\" \"""")}\" && exit'" in selected tab of the front window -end tell""") - } - OS.WINDOWS -> { - listOf("cmd", "/C", "start ${command.joinToString(" ") { windowsSpaceEscape(it) }}") - } - OS.LINUX -> { - // Start shell to keep window open unless java terminated normally or due to SIGTERM: - val command = "${unixCommand()}; [ $? -eq 0 -o $? -eq 143 ] || sh" - if (isTmux()) { - listOf("tmux", "new-window", "-n", nodeName, command) - } else { - listOf("xterm", "-T", nodeName, "-e", command) - } - } - }) - - private fun unixCommand() = command.map(::quotedFormOf).joinToString(" ") - override fun getJavaPath(): String = File(File(System.getProperty("java.home"), "bin"), "java").path - - // Replace below is to fix an issue with spaces in paths on Windows. - // Quoting the entire path does not work, only the space or directory within the path. - private fun windowsSpaceEscape(s:String) = s.replace(" ", "\" \"") -} - -private fun quotedFormOf(text: String) = "'${text.replace("'", "'\\''")}'" // Suitable for UNIX shells. -private fun isTmux() = System.getenv("TMUX")?.isNotEmpty() ?: false diff --git a/gradle-plugins/publish-utils/README.rst b/gradle-plugins/publish-utils/README.rst deleted file mode 100644 index d1657ee5fe..0000000000 --- a/gradle-plugins/publish-utils/README.rst +++ /dev/null @@ -1,92 +0,0 @@ -Publish Utils -============= - -Publishing utilities adds a couple of tasks to any project it is applied to that hide some boilerplate that would -otherwise be placed in the Cordapp template's build.gradle. - -There are two tasks exposed: `sourceJar` and `javadocJar` and both return a `FileCollection`. - -It is used within the `publishing` block of a build.gradle as such; - -.. code-block:: text - - // This will publish the sources, javadoc, and Java components to Maven. - // See the `maven-publish` plugin for more info: https://docs.gradle.org/current/userguide/publishing_maven.html - publishing { - publications { - jarAndSources(MavenPublication) { - from components.java - // The two lines below are the tasks added by this plugin. - artifact sourceJar - artifact javadocJar - } - } - } - -Bintray Publishing ------------------- - -For large multibuild projects it can be inconvenient to store the entire configuration for bintray and maven central -per project (with a bintray and publishing block with extended POM information). Publish utils can bring the number of -configuration blocks down to one in the ideal scenario. - -To use this plugin you must first apply it to both the root project and any project that will be published with - -.. code-block:: text - - apply plugin: 'net.corda.plugins.publish-utils' - -Next you must setup the general bintray configuration you wish to use project wide, for example: - -.. code-block:: text - - bintrayConfig { - user = - key = - repo = 'example repo' - org = 'example organisation' - licenses = ['a license'] - vcsUrl = 'https://example.com' - projectUrl = 'https://example.com' - gpgSign = true // Whether to GPG sign - gpgPassphrase = // Only required if gpgSign is true and your key is passworded - publications = ['example'] // a list of publications (see below) - license { - name = 'example' - url = 'https://example.com' - distribution = 'repo' - } - developer { - id = 'a developer id' - name = 'a developer name' - email = 'example@example.com' - } - } - -.. note:: You can currently only have one license and developer in the maven POM sections - -**Publications** - -This plugin assumes, by default, that publications match the name of the project. This means, by default, you can -just list the names of the projects you wish to publish (e.g. to publish `test:myapp` you need `publications = ['myapp']`. -If a project requires a different name you can configure it *per project* with the project configuration block. - -The project configuration block has the following structure: - -.. code-block:: text - - publish { - disableDefaultJar = false // set to true to disable the default JAR being created (e.g. when creating a fat JAR) - name 'non-default-project-name' // Always put this last because it causes configuration to happen - } - -**Artifacts** - -To add additional artifacts to the project you can use the default gradle `artifacts` block with the `publish` -configuration. For example: - - artifacts { - publish buildFatJar { - // You can configure this as a regular maven publication - } - } diff --git a/gradle-plugins/publish-utils/build.gradle b/gradle-plugins/publish-utils/build.gradle deleted file mode 100644 index da9c659498..0000000000 --- a/gradle-plugins/publish-utils/build.gradle +++ /dev/null @@ -1,109 +0,0 @@ -apply plugin: 'groovy' -apply plugin: 'maven-publish' -apply plugin: 'com.jfrog.bintray' -apply plugin: 'com.jfrog.artifactory' - -// Used for bootstrapping project -buildscript { - Properties constants = new Properties() - file("../../constants.properties").withInputStream { constants.load(it) } - - ext { - gradle_plugins_version = constants.getProperty("gradlePluginsVersion") - artifactory_plugin_version = constants.getProperty('artifactoryPluginVersion') - } - - repositories { - jcenter() - } - - dependencies { - classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.4' - classpath "org.jfrog.buildinfo:build-info-extractor-gradle:$artifactory_plugin_version" - } -} - -version "$gradle_plugins_version" - -dependencies { - compile gradleApi() - compile localGroovy() -} - -repositories { - mavenCentral() -} - -task("sourceJar", type: Jar, dependsOn: classes) { - classifier = 'sources' - from sourceSets.main.allSource -} - -task("javadocJar", type: Jar, dependsOn: javadoc) { - classifier = 'javadoc' - from javadoc.destinationDir -} - -bintray { - user = System.getenv('CORDA_BINTRAY_USER') - key = System.getenv('CORDA_BINTRAY_KEY') - publications = ['publishUtils'] - dryRun = false - pkg { - repo = 'corda' - name = 'publish-utils' - userOrg = 'r3' - licenses = ['Apache-2.0'] - - version { - gpg { - sign = true - passphrase = System.getenv('CORDA_BINTRAY_GPG_PASSPHRASE') - } - } - } -} - -publishing { - publications { - publishUtils(MavenPublication) { - from components.java - groupId 'net.corda.plugins' - artifactId 'publish-utils' - - artifact sourceJar - artifact javadocJar - - pom.withXml { - asNode().children().last() + { - resolveStrategy = Closure.DELEGATE_FIRST - name 'publish-utils' - description 'A small gradle plugin that adds a couple of convenience functions for publishing to Maven' - url 'https://github.com/corda/corda' - scm { - url 'https://github.com/corda/corda' - } - - licenses { - license { - name 'Apache-2.0' - url 'https://www.apache.org/licenses/LICENSE-2.0' - distribution 'repo' - } - } - - developers { - developer { - id 'R3' - name 'R3' - email 'dev@corda.net' - } - } - } - } - } - } -} - -// Aliasing the publishToMavenLocal for simplicity. -task(install, dependsOn: 'publishToMavenLocal') diff --git a/gradle-plugins/publish-utils/src/main/groovy/net/corda/plugins/ProjectPublishExtension.groovy b/gradle-plugins/publish-utils/src/main/groovy/net/corda/plugins/ProjectPublishExtension.groovy deleted file mode 100644 index edb543fa51..0000000000 --- a/gradle-plugins/publish-utils/src/main/groovy/net/corda/plugins/ProjectPublishExtension.groovy +++ /dev/null @@ -1,39 +0,0 @@ -package net.corda.plugins - -class ProjectPublishExtension { - private PublishTasks task - - void setPublishTask(PublishTasks task) { - this.task = task - } - - /** - * Use a different name from the current project name for publishing. - * Set this after all other settings that need to be configured - */ - void name(String name) { - task.setPublishName(name) - } - - /** - * Get the publishing name for this project. - */ - String name() { - return task.getPublishName() - } - - /** - * True when we do not want to publish default Java components - */ - Boolean disableDefaultJar = false - - /** - * True if publishing a WAR instead of a JAR. Forces disableDefaultJAR to "true" when true - */ - Boolean publishWar = false - - /** - * True if publishing sources to remote repositories - */ - Boolean publishSources = true -} \ No newline at end of file diff --git a/gradle-plugins/publish-utils/src/main/groovy/net/corda/plugins/PublishTasks.groovy b/gradle-plugins/publish-utils/src/main/groovy/net/corda/plugins/PublishTasks.groovy deleted file mode 100644 index 772ac99d23..0000000000 --- a/gradle-plugins/publish-utils/src/main/groovy/net/corda/plugins/PublishTasks.groovy +++ /dev/null @@ -1,167 +0,0 @@ -package net.corda.plugins - -import org.gradle.api.* -import org.gradle.api.tasks.bundling.Jar -import org.gradle.api.tasks.javadoc.Javadoc -import org.gradle.api.Project -import org.gradle.api.publish.maven.MavenPublication -import org.gradle.api.publish.maven.MavenPom -import net.corda.plugins.bintray.* - -/** - * A utility plugin that when applied will automatically create source and javadoc publishing tasks - * To apply this plugin you must also add 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.4' to your - * buildscript's classpath dependencies. - * - * To use this plugin you can add a new configuration block (extension) to your root build.gradle. See the fields - * in BintrayConfigExtension. - */ -class PublishTasks implements Plugin { - Project project - String publishName - ProjectPublishExtension publishConfig - - void apply(Project project) { - this.project = project - this.publishName = project.name - - createTasks() - createExtensions() - createConfigurations() - } - - /** - * This call must come at the end of any publish block because it configures the publishing and any - * values set after this call in the DSL will not be configured properly (and will use the default value) - */ - void setPublishName(String publishName) { - project.logger.info("Changing publishing name from ${project.name} to ${publishName}") - this.publishName = publishName - checkAndConfigurePublishing() - } - - void checkAndConfigurePublishing() { - project.logger.info("Checking whether to publish $publishName") - def bintrayConfig = project.rootProject.extensions.findByType(BintrayConfigExtension.class) - if((bintrayConfig != null) && (bintrayConfig.publications) && (bintrayConfig.publications.findAll { it == publishName }.size() > 0)) { - configurePublishing(bintrayConfig) - } - } - - void configurePublishing(BintrayConfigExtension bintrayConfig) { - project.logger.info("Configuring bintray for ${publishName}") - configureMavenPublish(bintrayConfig) - configureBintray(bintrayConfig) - } - - void configureMavenPublish(BintrayConfigExtension bintrayConfig) { - project.logger.info("Configuring maven publish for $publishName") - project.apply([plugin: 'maven-publish']) - project.publishing.publications.create(publishName, MavenPublication) { - groupId project.group - artifactId publishName - - if (publishConfig.publishSources) { - project.logger.info("Publishing sources for $publishName") - artifact project.tasks.sourceJar - } - artifact project.tasks.javadocJar - - project.configurations.publish.artifacts.each { - project.logger.debug("Adding artifact: $it") - delegate.artifact it - } - - if (!publishConfig.disableDefaultJar && !publishConfig.publishWar) { - from project.components.java - } else if (publishConfig.publishWar) { - from project.components.web - } - - extendPomForMavenCentral(pom, bintrayConfig) - } - project.task("install", dependsOn: "publishToMavenLocal") - } - - // Maven central requires all of the below fields for this to be a valid POM - void extendPomForMavenCentral(MavenPom pom, BintrayConfigExtension config) { - pom.withXml { - asNode().children().last() + { - resolveStrategy = Closure.DELEGATE_FIRST - name publishName - description project.description - url config.projectUrl - scm { - url config.vcsUrl - } - - licenses { - license { - name config.license.name - url config.license.url - distribution config.license.url - } - } - - developers { - developer { - id config.developer.id - name config.developer.name - email config.developer.email - } - } - } - } - } - - void configureBintray(BintrayConfigExtension bintrayConfig) { - project.apply([plugin: 'com.jfrog.bintray']) - project.bintray { - user = bintrayConfig.user - key = bintrayConfig.key - publications = [ publishName ] - dryRun = bintrayConfig.dryRun ?: false - pkg { - repo = bintrayConfig.repo - name = publishName - userOrg = bintrayConfig.org - licenses = bintrayConfig.licenses - - version { - gpg { - sign = bintrayConfig.gpgSign ?: false - passphrase = bintrayConfig.gpgPassphrase - } - } - } - } - } - - void createTasks() { - if(project.hasProperty('classes')) { - project.task("sourceJar", type: Jar, dependsOn: project.classes) { - classifier = 'sources' - from project.sourceSets.main.allSource - } - } - - if(project.hasProperty('javadoc')) { - project.task("javadocJar", type: Jar, dependsOn: project.javadoc) { - classifier = 'javadoc' - from project.javadoc.destinationDir - } - } - } - - void createExtensions() { - if(project == project.rootProject) { - project.extensions.create("bintrayConfig", BintrayConfigExtension) - } - publishConfig = project.extensions.create("publish", ProjectPublishExtension) - publishConfig.setPublishTask(this) - } - - void createConfigurations() { - project.configurations.create("publish") - } -} diff --git a/gradle-plugins/publish-utils/src/main/groovy/net/corda/plugins/bintray/BintrayConfigExtension.groovy b/gradle-plugins/publish-utils/src/main/groovy/net/corda/plugins/bintray/BintrayConfigExtension.groovy deleted file mode 100644 index 1a1c4e49e5..0000000000 --- a/gradle-plugins/publish-utils/src/main/groovy/net/corda/plugins/bintray/BintrayConfigExtension.groovy +++ /dev/null @@ -1,70 +0,0 @@ -package net.corda.plugins.bintray - -import org.gradle.util.ConfigureUtil - -class BintrayConfigExtension { - /** - * Bintray username - */ - String user - /** - * Bintray access key - */ - String key - /** - * Bintray repository - */ - String repo - /** - * Bintray organisation - */ - String org - /** - * Licenses for packages uploaded by this configuration - */ - String[] licenses - /** - * Whether to sign packages uploaded by this configuration - */ - Boolean gpgSign - /** - * The passphrase for the key used to sign releases. - */ - String gpgPassphrase - /** - * VCS URL - */ - String vcsUrl - /** - * Project URL - */ - String projectUrl - /** - * The publications that will be uploaded as a part of this configuration. These must match both the name on - * bintray and the gradle module name. ie; it must be "some-package" as a gradle sub-module (root project not - * supported, this extension is to improve multi-build bintray uploads). The publication must also be called - * "some-package". Only one publication can be uploaded per module (a bintray plugin restriction(. - * If any of these conditions are not met your package will not be uploaded. - */ - String[] publications - /** - * Whether to test the publication without uploading to bintray. - */ - Boolean dryRun - /** - * The license this project will use (currently limited to one) - */ - License license = new License() - /** - * The developer of this project (currently limited to one) - */ - Developer developer = new Developer() - - void license(Closure closure) { - ConfigureUtil.configure(closure, license) - } - - void developer(Closure closure) { - ConfigureUtil.configure(closure, developer) - } -} \ No newline at end of file diff --git a/gradle-plugins/publish-utils/src/main/groovy/net/corda/plugins/bintray/Developer.groovy b/gradle-plugins/publish-utils/src/main/groovy/net/corda/plugins/bintray/Developer.groovy deleted file mode 100644 index 1d66f68c7d..0000000000 --- a/gradle-plugins/publish-utils/src/main/groovy/net/corda/plugins/bintray/Developer.groovy +++ /dev/null @@ -1,16 +0,0 @@ -package net.corda.plugins.bintray - -class Developer { - /** - * A unique identifier the developer (eg; organisation ID) - */ - String id - /** - * The full name of the developer - */ - String name - /** - * An email address for contacting the developer - */ - String email -} \ No newline at end of file diff --git a/gradle-plugins/publish-utils/src/main/groovy/net/corda/plugins/bintray/License.groovy b/gradle-plugins/publish-utils/src/main/groovy/net/corda/plugins/bintray/License.groovy deleted file mode 100644 index 1d06867bcf..0000000000 --- a/gradle-plugins/publish-utils/src/main/groovy/net/corda/plugins/bintray/License.groovy +++ /dev/null @@ -1,16 +0,0 @@ -package net.corda.plugins.bintray - -class License { - /** - * The name of license (eg; Apache 2.0) - */ - String name - /** - * URL to the full license file - */ - String url - /** - * The distribution level this license corresponds to (eg: repo) - */ - String distribution -} \ No newline at end of file diff --git a/gradle-plugins/publish-utils/src/main/resources/META-INF/gradle-plugins/net.corda.plugins.publish-utils.properties b/gradle-plugins/publish-utils/src/main/resources/META-INF/gradle-plugins/net.corda.plugins.publish-utils.properties deleted file mode 100644 index b680f7d301..0000000000 --- a/gradle-plugins/publish-utils/src/main/resources/META-INF/gradle-plugins/net.corda.plugins.publish-utils.properties +++ /dev/null @@ -1 +0,0 @@ -implementation-class=net.corda.plugins.PublishTasks diff --git a/gradle-plugins/quasar-utils/README.rst b/gradle-plugins/quasar-utils/README.rst deleted file mode 100644 index 481d8ec66b..0000000000 --- a/gradle-plugins/quasar-utils/README.rst +++ /dev/null @@ -1,4 +0,0 @@ -Quasar Utils -============ - -Quasar utilities adds several tasks and configuration that provide a default Quasar setup and removes some boilerplate. \ No newline at end of file diff --git a/gradle-plugins/quasar-utils/build.gradle b/gradle-plugins/quasar-utils/build.gradle deleted file mode 100644 index 8f9eca30f2..0000000000 --- a/gradle-plugins/quasar-utils/build.gradle +++ /dev/null @@ -1,19 +0,0 @@ -apply plugin: 'groovy' -apply plugin: 'maven-publish' -apply plugin: 'net.corda.plugins.publish-utils' -apply plugin: 'com.jfrog.artifactory' - -description 'A small gradle plugin for adding some basic Quasar tasks and configurations to reduce build.gradle bloat.' - -repositories { - mavenCentral() -} - -dependencies { - compile gradleApi() - compile localGroovy() -} - -publish { - name project.name -} diff --git a/gradle-plugins/quasar-utils/src/main/groovy/net/corda/plugins/QuasarPlugin.groovy b/gradle-plugins/quasar-utils/src/main/groovy/net/corda/plugins/QuasarPlugin.groovy deleted file mode 100644 index 6a4ffaf25a..0000000000 --- a/gradle-plugins/quasar-utils/src/main/groovy/net/corda/plugins/QuasarPlugin.groovy +++ /dev/null @@ -1,28 +0,0 @@ -package net.corda.plugins - -import org.gradle.api.Project -import org.gradle.api.Plugin -import org.gradle.api.tasks.testing.Test -import org.gradle.api.tasks.JavaExec - -/** - * QuasarPlugin creates a "quasar" configuration and adds quasar as a dependency. - */ -class QuasarPlugin implements Plugin { - void apply(Project project) { - project.configurations.create("quasar") -// To add a local .jar dependency: -// project.dependencies.add("quasar", project.files("${project.rootProject.projectDir}/lib/quasar.jar")) - project.dependencies.add("quasar", "co.paralleluniverse:quasar-core:${project.rootProject.ext.quasar_version}:jdk8@jar") - project.dependencies.add("runtime", project.configurations.getByName("quasar")) - - project.tasks.withType(Test) { - jvmArgs "-javaagent:${project.configurations.quasar.singleFile}" - jvmArgs "-Dco.paralleluniverse.fibers.verifyInstrumentation" - } - project.tasks.withType(JavaExec) { - jvmArgs "-javaagent:${project.configurations.quasar.singleFile}" - jvmArgs "-Dco.paralleluniverse.fibers.verifyInstrumentation" - } - } -} diff --git a/gradle-plugins/quasar-utils/src/main/resources/META-INF/gradle-plugins/net.corda.plugins.quasar-utils.properties b/gradle-plugins/quasar-utils/src/main/resources/META-INF/gradle-plugins/net.corda.plugins.quasar-utils.properties deleted file mode 100644 index fb7042bb42..0000000000 --- a/gradle-plugins/quasar-utils/src/main/resources/META-INF/gradle-plugins/net.corda.plugins.quasar-utils.properties +++ /dev/null @@ -1 +0,0 @@ -implementation-class=net.corda.plugins.QuasarPlugin diff --git a/gradle-plugins/settings.gradle b/gradle-plugins/settings.gradle deleted file mode 100644 index 995cd8c899..0000000000 --- a/gradle-plugins/settings.gradle +++ /dev/null @@ -1,7 +0,0 @@ -rootProject.name = 'corda-gradle-plugins' -include 'publish-utils' -include 'quasar-utils' -include 'cordformation' -include 'cordform-common' -include 'api-scanner' -include 'cordapp' \ No newline at end of file diff --git a/lib/quasar.jar b/lib/quasar.jar index c9f0010e79..074ecc3b7c 100644 Binary files a/lib/quasar.jar and b/lib/quasar.jar differ diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/RPCApi.kt b/node-api/src/main/kotlin/net/corda/nodeapi/RPCApi.kt index 96bd8c0560..9392c176c7 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/RPCApi.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/RPCApi.kt @@ -10,6 +10,7 @@ import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.utilities.Id +import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.Try import org.apache.activemq.artemis.api.core.ActiveMQBuffer import org.apache.activemq.artemis.api.core.SimpleString @@ -72,6 +73,8 @@ object RPCApi { const val RPC_CLIENT_BINDING_ADDITIONS = "rpc.clientqueueadditions" const val RPC_TARGET_LEGAL_IDENTITY = "rpc-target-legal-identity" + const val DEDUPLICATION_SEQUENCE_NUMBER_FIELD_NAME = "deduplication-sequence-number" + val RPC_CLIENT_BINDING_REMOVAL_FILTER_EXPRESSION = "${ManagementHelper.HDR_NOTIFICATION_TYPE} = '${CoreNotificationType.BINDING_REMOVED.name}' AND " + "${ManagementHelper.HDR_ROUTING_NAME} LIKE '$RPC_CLIENT_QUEUE_NAME_PREFIX.%'" @@ -94,6 +97,8 @@ object RPCApi { OBSERVABLES_CLOSED } + abstract fun writeToClientMessage(message: ClientMessage) + /** * Request to a server to trigger the specified method with the provided arguments. * @@ -105,13 +110,13 @@ object RPCApi { data class RpcRequest( val clientAddress: SimpleString, val methodName: String, - val serialisedArguments: ByteArray, + val serialisedArguments: OpaqueBytes, val replyId: InvocationId, val sessionId: SessionId, val externalTrace: Trace? = null, val impersonatedActor: Actor? = null ) : ClientToServer() { - fun writeToClientMessage(message: ClientMessage) { + override fun writeToClientMessage(message: ClientMessage) { MessageUtil.setJMSReplyTo(message, clientAddress) message.putIntProperty(TAG_FIELD_NAME, Tag.RPC_REQUEST.ordinal) @@ -122,12 +127,12 @@ object RPCApi { impersonatedActor?.mapToImpersonated(message) message.putStringProperty(METHOD_NAME_FIELD_NAME, methodName) - message.bodyBuffer.writeBytes(serialisedArguments) + message.bodyBuffer.writeBytes(serialisedArguments.bytes) } } data class ObservablesClosed(val ids: List) : ClientToServer() { - fun writeToClientMessage(message: ClientMessage) { + override fun writeToClientMessage(message: ClientMessage) { message.putIntProperty(TAG_FIELD_NAME, Tag.OBSERVABLES_CLOSED.ordinal) val buffer = message.bodyBuffer buffer.writeInt(ids.size) @@ -144,7 +149,7 @@ object RPCApi { RPCApi.ClientToServer.Tag.RPC_REQUEST -> RpcRequest( clientAddress = MessageUtil.getJMSReplyTo(message), methodName = message.getStringProperty(METHOD_NAME_FIELD_NAME), - serialisedArguments = message.getBodyAsByteArray(), + serialisedArguments = OpaqueBytes(message.getBodyAsByteArray()), replyId = message.replyId(), sessionId = message.sessionId(), externalTrace = message.externalTrace(), @@ -175,13 +180,20 @@ object RPCApi { abstract fun writeToClientMessage(context: SerializationContext, message: ClientMessage) - /** Reply in response to an [ClientToServer.RpcRequest]. */ + /** The identity used to identify the deduplication ID sequence. This should be unique per server JVM run */ + abstract val deduplicationIdentity: String + + /** + * Reply in response to an [ClientToServer.RpcRequest]. + */ data class RpcReply( val id: InvocationId, - val result: Try + val result: Try, + override val deduplicationIdentity: String ) : ServerToClient() { override fun writeToClientMessage(context: SerializationContext, message: ClientMessage) { message.putIntProperty(TAG_FIELD_NAME, Tag.RPC_REPLY.ordinal) + message.putStringProperty(DEDUPLICATION_IDENTITY_FIELD_NAME, deduplicationIdentity) id.mapTo(message, RPC_ID_FIELD_NAME, RPC_ID_TIMESTAMP_FIELD_NAME) message.bodyBuffer.writeBytes(result.safeSerialize(context) { Try.Failure(it) }.bytes) } @@ -189,10 +201,12 @@ object RPCApi { data class Observation( val id: InvocationId, - val content: Notification<*> + val content: Notification<*>, + override val deduplicationIdentity: String ) : ServerToClient() { override fun writeToClientMessage(context: SerializationContext, message: ClientMessage) { message.putIntProperty(TAG_FIELD_NAME, Tag.OBSERVATION.ordinal) + message.putStringProperty(DEDUPLICATION_IDENTITY_FIELD_NAME, deduplicationIdentity) id.mapTo(message, OBSERVABLE_ID_FIELD_NAME, OBSERVABLE_ID_TIMESTAMP_FIELD_NAME) message.bodyBuffer.writeBytes(content.safeSerialize(context) { Notification.createOnError(it) }.bytes) } @@ -207,17 +221,26 @@ object RPCApi { fun fromClientMessage(context: SerializationContext, message: ClientMessage): ServerToClient { val tag = Tag.values()[message.getIntProperty(TAG_FIELD_NAME)] + val deduplicationIdentity = message.getStringProperty(DEDUPLICATION_IDENTITY_FIELD_NAME) return when (tag) { RPCApi.ServerToClient.Tag.RPC_REPLY -> { val id = message.invocationId(RPC_ID_FIELD_NAME, RPC_ID_TIMESTAMP_FIELD_NAME) ?: throw IllegalStateException("Cannot parse invocation id from client message.") val poolWithIdContext = context.withProperty(RpcRequestOrObservableIdKey, id) - RpcReply(id, message.getBodyAsByteArray().deserialize(context = poolWithIdContext)) + RpcReply( + id = id, + deduplicationIdentity = deduplicationIdentity, + result = message.getBodyAsByteArray().deserialize(context = poolWithIdContext) + ) } RPCApi.ServerToClient.Tag.OBSERVATION -> { val observableId = message.invocationId(OBSERVABLE_ID_FIELD_NAME, OBSERVABLE_ID_TIMESTAMP_FIELD_NAME) ?: throw IllegalStateException("Cannot parse invocation id from client message.") val poolWithIdContext = context.withProperty(RpcRequestOrObservableIdKey, observableId) val payload = message.getBodyAsByteArray().deserialize>(context = poolWithIdContext) - Observation(observableId, payload) + Observation( + id = observableId, + deduplicationIdentity = deduplicationIdentity, + content = payload + ) } } } @@ -225,18 +248,6 @@ object RPCApi { } } -data class ArtemisProducer( - val sessionFactory: ClientSessionFactory, - val session: ClientSession, - val producer: ClientProducer -) - -data class ArtemisConsumer( - val sessionFactory: ClientSessionFactory, - val session: ClientSession, - val consumer: ClientConsumer -) - private val TAG_FIELD_NAME = "tag" private val RPC_ID_FIELD_NAME = "rpc-id" private val RPC_ID_TIMESTAMP_FIELD_NAME = "rpc-id-timestamp" @@ -249,6 +260,7 @@ private val RPC_EXTERNAL_SESSION_ID_TIMESTAMP_FIELD_NAME = "rpc-external-session private val RPC_IMPERSONATED_ACTOR_ID = "rpc-impersonated-actor-id" private val RPC_IMPERSONATED_ACTOR_STORE_ID = "rpc-impersonated-actor-store-id" private val RPC_IMPERSONATED_ACTOR_OWNING_LEGAL_IDENTITY = "rpc-impersonated-actor-owningLegalIdentity" +private val DEDUPLICATION_IDENTITY_FIELD_NAME = "deduplication-identity" private val OBSERVABLE_ID_FIELD_NAME = "observable-id" private val OBSERVABLE_ID_TIMESTAMP_FIELD_NAME = "observable-id-timestamp" private val METHOD_NAME_FIELD_NAME = "method-name" diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/ArtemisMessagingClient.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/ArtemisMessagingClient.kt index 1050fa1a3c..6d65f8c381 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/ArtemisMessagingClient.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/ArtemisMessagingClient.kt @@ -10,6 +10,12 @@ import net.corda.nodeapi.internal.config.SSLConfiguration import org.apache.activemq.artemis.api.core.client.* import org.apache.activemq.artemis.api.core.client.ActiveMQClient.DEFAULT_ACK_BATCH_SIZE +interface ArtemisSessionProvider { + fun start(): ArtemisMessagingClient.Started + fun stop() + val started: ArtemisMessagingClient.Started? +} + class ArtemisMessagingClient( private val config: SSLConfiguration, private val serverAddress: NetworkHostAndPort, @@ -17,17 +23,17 @@ class ArtemisMessagingClient( private val autoCommitSends: Boolean = true, private val autoCommitAcks: Boolean = true, private val confirmationWindowSize: Int = -1 -) { +): ArtemisSessionProvider { companion object { private val log = loggerFor() } class Started(val serverLocator: ServerLocator, val sessionFactory: ClientSessionFactory, val session: ClientSession, val producer: ClientProducer) - var started: Started? = null + override var started: Started? = null private set - fun start(): Started = synchronized(this) { + override fun start(): Started = synchronized(this) { check(started == null) { "start can't be called twice" } log.info("Connecting to message broker: $serverAddress") // TODO Add broker CN to config for host verification in case the embedded broker isn't used @@ -53,7 +59,7 @@ class ArtemisMessagingClient( return Started(locator, sessionFactory, session, producer).also { started = it } } - fun stop() = synchronized(this) { + override fun stop() = synchronized(this) { started?.run { producer.close() // Ensure any trailing messages are committed to the journal diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/DeduplicationChecker.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/DeduplicationChecker.kt new file mode 100644 index 0000000000..b35b8922a3 --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/DeduplicationChecker.kt @@ -0,0 +1,30 @@ +package net.corda.nodeapi.internal + +import com.google.common.cache.CacheBuilder +import com.google.common.cache.CacheLoader +import java.time.Duration +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicLong + +/** + * A class allowing the deduplication of a strictly incrementing sequence number. + */ +class DeduplicationChecker(cacheExpiry: Duration) { + // dedupe identity -> watermark cache + private val watermarkCache = CacheBuilder.newBuilder() + .expireAfterAccess(cacheExpiry.toNanos(), TimeUnit.NANOSECONDS) + .build(WatermarkCacheLoader) + + private object WatermarkCacheLoader : CacheLoader() { + override fun load(key: Any) = AtomicLong(-1) + } + + /** + * @param identity the identity that generates the sequence numbers. + * @param sequenceNumber the sequence number to check. + * @return true if the message is unique, false if it's a duplicate. + */ + fun checkDuplicateMessageId(identity: Any, sequenceNumber: Long): Boolean { + return watermarkCache[identity].getAndUpdate { maxOf(sequenceNumber, it) } >= sequenceNumber + } +} diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/bridging/AMQPBridgeManager.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/bridging/AMQPBridgeManager.kt index 523369b370..41f5a7d72b 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/bridging/AMQPBridgeManager.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/bridging/AMQPBridgeManager.kt @@ -12,6 +12,7 @@ import net.corda.nodeapi.internal.ArtemisMessagingComponent import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER import net.corda.nodeapi.internal.ArtemisMessagingComponent.RemoteInboxAddress.Companion.translateLocalQueueToInboxAddress +import net.corda.nodeapi.internal.ArtemisSessionProvider import net.corda.nodeapi.internal.bridging.AMQPBridgeManager.AMQPBridge.Companion.getBridgeName import net.corda.nodeapi.internal.config.NodeSSLConfiguration import net.corda.nodeapi.internal.protonwrapper.messages.MessageStatus @@ -35,7 +36,7 @@ import kotlin.concurrent.withLock * The Netty thread pool used by the AMQPBridges is also shared and managed by the AMQPBridgeManager. */ @VisibleForTesting -class AMQPBridgeManager(val config: NodeSSLConfiguration, val p2pAddress: NetworkHostAndPort, val maxMessageSize: Int) : BridgeManager { +class AMQPBridgeManager(config: NodeSSLConfiguration, val artemisMessageClientFactory: () -> ArtemisSessionProvider) : BridgeManager { private val lock = ReentrantLock() private val bridgeNameToBridgeMap = mutableMapOf() @@ -43,7 +44,9 @@ class AMQPBridgeManager(val config: NodeSSLConfiguration, val p2pAddress: Networ private val keyStore = config.loadSslKeyStore().internal private val keyStorePrivateKeyPassword: String = config.keyStorePassword private val trustStore = config.loadTrustStore().internal - private var artemis: ArtemisMessagingClient? = null + private var artemis: ArtemisSessionProvider? = null + + constructor(config: NodeSSLConfiguration, p2pAddress: NetworkHostAndPort, maxMessageSize: Int) : this(config, { ArtemisMessagingClient(config, p2pAddress, maxMessageSize) }) companion object { private const val NUM_BRIDGE_THREADS = 0 // Default sized pool @@ -64,7 +67,7 @@ class AMQPBridgeManager(val config: NodeSSLConfiguration, val p2pAddress: Networ keyStorePrivateKeyPassword: String, trustStore: KeyStore, sharedEventGroup: EventLoopGroup, - private val artemis: ArtemisMessagingClient) { + private val artemis: ArtemisSessionProvider) { companion object { fun getBridgeName(queueName: String, hostAndPort: NetworkHostAndPort): String = "$queueName -> $hostAndPort" } @@ -155,9 +158,8 @@ class AMQPBridgeManager(val config: NodeSSLConfiguration, val p2pAddress: Networ } } - private fun gatherAddresses(node: NodeInfo): Sequence { - val address = node.addresses.single() - return node.legalIdentitiesAndCerts.map { ArtemisMessagingComponent.NodeAddress(it.party.owningKey, address) }.asSequence() + private fun gatherAddresses(node: NodeInfo): List { + return node.legalIdentitiesAndCerts.map { ArtemisMessagingComponent.NodeAddress(it.party.owningKey, node.addresses[0]) } } override fun deployBridge(queueName: String, target: NetworkHostAndPort, legalNames: Set) { @@ -191,7 +193,7 @@ class AMQPBridgeManager(val config: NodeSSLConfiguration, val p2pAddress: Networ override fun start() { sharedEventLoopGroup = NioEventLoopGroup(NUM_BRIDGE_THREADS) - val artemis = ArtemisMessagingClient(config, p2pAddress, maxMessageSize) + val artemis = artemisMessageClientFactory() this.artemis = artemis artemis.start() } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/bridging/BridgeControlListener.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/bridging/BridgeControlListener.kt index 570f28cf48..2d0d53a19d 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/bridging/BridgeControlListener.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/bridging/BridgeControlListener.kt @@ -10,6 +10,7 @@ import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.BRIDGE_CON import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.BRIDGE_NOTIFY import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_PREFIX import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEERS_PREFIX +import net.corda.nodeapi.internal.ArtemisSessionProvider import net.corda.nodeapi.internal.config.NodeSSLConfiguration import org.apache.activemq.artemis.api.core.RoutingType import org.apache.activemq.artemis.api.core.SimpleString @@ -18,14 +19,17 @@ import org.apache.activemq.artemis.api.core.client.ClientMessage import java.util.* class BridgeControlListener(val config: NodeSSLConfiguration, - val p2pAddress: NetworkHostAndPort, - val maxMessageSize: Int) : AutoCloseable { + val artemisMessageClientFactory: () -> ArtemisSessionProvider) : AutoCloseable { private val bridgeId: String = UUID.randomUUID().toString() - private val bridgeManager: BridgeManager = AMQPBridgeManager(config, p2pAddress, maxMessageSize) + private val bridgeManager: BridgeManager = AMQPBridgeManager(config, artemisMessageClientFactory) private val validInboundQueues = mutableSetOf() - private var artemis: ArtemisMessagingClient? = null + private var artemis: ArtemisSessionProvider? = null private var controlConsumer: ClientConsumer? = null + constructor(config: NodeSSLConfiguration, + p2pAddress: NetworkHostAndPort, + maxMessageSize: Int) : this(config, { ArtemisMessagingClient(config, p2pAddress, maxMessageSize) }) + companion object { private val log = contextLogger() } @@ -33,7 +37,7 @@ class BridgeControlListener(val config: NodeSSLConfiguration, fun start() { stop() bridgeManager.start() - val artemis = ArtemisMessagingClient(config, p2pAddress, maxMessageSize) + val artemis = artemisMessageClientFactory() this.artemis = artemis artemis.start() val artemisClient = artemis.started!! @@ -56,6 +60,7 @@ class BridgeControlListener(val config: NodeSSLConfiguration, } fun stop() { + validInboundQueues.clear() controlConsumer?.close() controlConsumer = null artemis?.stop() @@ -65,6 +70,10 @@ class BridgeControlListener(val config: NodeSSLConfiguration, override fun close() = stop() + fun validateReceiveTopic(topic: String): Boolean { + return topic in validInboundQueues + } + private fun validateInboxQueueName(queueName: String): Boolean { return queueName.startsWith(P2P_PREFIX) && artemis!!.started!!.session.queueQuery(SimpleString(queueName)).isExists } @@ -90,7 +99,6 @@ class BridgeControlListener(val config: NodeSSLConfiguration, for (outQueue in controlMessage.sendQueues) { bridgeManager.deployBridge(outQueue.queueName, outQueue.targets.first(), outQueue.legalNames.toSet()) } - // TODO For now we just record the inboxes, but we don't use the information, but eventually out of process bridges will use this for validating inbound messages. validInboundQueues.addAll(controlMessage.inboxQueues) } is BridgeControl.BridgeToNodeSnapshotRequest -> { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt index a1c6210a5e..683af3f8f3 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt @@ -47,6 +47,7 @@ private val _contextDatabase = ThreadLocal() var contextDatabase: CordaPersistence get() = _contextDatabase.get() ?: error("Was expecting to find CordaPersistence set on current thread: ${Strand.currentStrand()}") set(database) = _contextDatabase.set(database) +val contextDatabaseOrNull: CordaPersistence? get() = _contextDatabase.get() class CordaPersistence( val dataSource: DataSource, diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/engine/ConnectionStateMachine.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/engine/ConnectionStateMachine.kt index 1ab83fcc4e..473c28876c 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/engine/ConnectionStateMachine.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/engine/ConnectionStateMachine.kt @@ -168,12 +168,14 @@ internal class ConnectionStateMachine(serverMode: Boolean, val transport = event.transport log.debug { "Transport Head Closed $transport" } transport.close_tail() + onTransportInternal(transport) } override fun onTransportTailClosed(event: Event) { val transport = event.transport log.debug { "Transport Tail Closed $transport" } transport.close_head() + onTransportInternal(transport) } override fun onTransportClosed(event: Event) { @@ -195,6 +197,7 @@ internal class ConnectionStateMachine(serverMode: Boolean, } else { log.info("Error (no description returned).") } + onTransportInternal(transport) } override fun onTransport(event: Event) { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/engine/EventProcessor.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/engine/EventProcessor.kt index 14c21b97f2..c02c6f2541 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/engine/EventProcessor.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/engine/EventProcessor.kt @@ -79,7 +79,10 @@ internal class EventProcessor(channel: Channel, if ((connection.localState != EndpointState.CLOSED) && !connection.transport.isClosed) { val now = System.currentTimeMillis() val tickDelay = Math.max(0L, connection.transport.tick(now) - now) - executor.schedule({ tick(connection) }, tickDelay, TimeUnit.MILLISECONDS) + executor.schedule({ + tick(connection) + processEvents() + }, tickDelay, TimeUnit.MILLISECONDS) } } catch (ex: Exception) { connection.transport.close() diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPChannelHandler.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPChannelHandler.kt index 4054b7c7fd..25855df558 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPChannelHandler.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPChannelHandler.kt @@ -38,8 +38,8 @@ internal class AMQPChannelHandler(private val serverMode: Boolean, private val onReceive: (ReceivedMessage) -> Unit) : ChannelDuplexHandler() { private val log = LoggerFactory.getLogger(allowedRemoteLegalNames?.firstOrNull()?.toString() ?: "AMQPChannelHandler") private lateinit var remoteAddress: InetSocketAddress - private lateinit var localCert: X509Certificate - private lateinit var remoteCert: X509Certificate + private var localCert: X509Certificate? = null + private var remoteCert: X509Certificate? = null private var eventProcessor: EventProcessor? = null override fun channelActive(ctx: ChannelHandlerContext) { @@ -51,7 +51,7 @@ internal class AMQPChannelHandler(private val serverMode: Boolean, private fun createAMQPEngine(ctx: ChannelHandlerContext) { val ch = ctx.channel() - eventProcessor = EventProcessor(ch, serverMode, localCert.subjectX500Principal.toString(), remoteCert.subjectX500Principal.toString(), userName, password) + eventProcessor = EventProcessor(ch, serverMode, localCert!!.subjectX500Principal.toString(), remoteCert!!.subjectX500Principal.toString(), userName, password) val connection = eventProcessor!!.connection val transport = connection.transport as ProtonJTransport if (trace) { @@ -72,7 +72,7 @@ internal class AMQPChannelHandler(private val serverMode: Boolean, override fun channelInactive(ctx: ChannelHandlerContext) { val ch = ctx.channel() log.info("Closed client connection ${ch.id()} from $remoteAddress to ${ch.localAddress()}") - onClose(Pair(ch as SocketChannel, ConnectionChange(remoteAddress, null, false))) + onClose(Pair(ch as SocketChannel, ConnectionChange(remoteAddress, remoteCert, false))) eventProcessor?.close() ctx.fireChannelInactive() } @@ -84,7 +84,7 @@ internal class AMQPChannelHandler(private val serverMode: Boolean, localCert = sslHandler.engine().session.localCertificates[0].x509 remoteCert = sslHandler.engine().session.peerCertificates[0].x509 try { - val remoteX500Name = CordaX500Name.build(remoteCert.subjectX500Principal) + val remoteX500Name = CordaX500Name.build(remoteCert!!.subjectX500Principal) require(allowedRemoteLegalNames == null || remoteX500Name in allowedRemoteLegalNames) log.info("handshake completed subject: $remoteX500Name") } catch (ex: IllegalArgumentException) { @@ -124,7 +124,7 @@ internal class AMQPChannelHandler(private val serverMode: Boolean, require(inetAddress == remoteAddress) { "Message for incorrect endpoint" } - require(CordaX500Name.parse(msg.destinationLegalName) == CordaX500Name.build(remoteCert.subjectX500Principal)) { + require(CordaX500Name.parse(msg.destinationLegalName) == CordaX500Name.build(remoteCert!!.subjectX500Principal)) { "Message for incorrect legal identity" } log.debug { "channel write ${msg.applicationProperties["_AMQ_DUPL_ID"]}" } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPServer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPServer.kt index 588c7fb8a0..9c98767d0e 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPServer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/AMQPServer.kt @@ -38,7 +38,7 @@ class AMQPServer(val hostName: String, private val userName: String?, private val password: String?, private val keyStore: KeyStore, - private val keyStorePrivateKeyPassword: String, + private val keyStorePrivateKeyPassword: CharArray, private val trustStore: KeyStore, private val trace: Boolean = false) : AutoCloseable { @@ -59,15 +59,21 @@ class AMQPServer(val hostName: String, private var serverChannel: Channel? = null private val clientChannels = ConcurrentHashMap() - init { - } + constructor(hostName: String, + port: Int, + userName: String?, + password: String?, + keyStore: KeyStore, + keyStorePrivateKeyPassword: String, + trustStore: KeyStore, + trace: Boolean = false) : this(hostName, port, userName, password, keyStore, keyStorePrivateKeyPassword.toCharArray(), trustStore, trace) private class ServerChannelInitializer(val parent: AMQPServer) : ChannelInitializer() { private val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()) private val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) init { - keyManagerFactory.init(parent.keyStore, parent.keyStorePrivateKeyPassword.toCharArray()) + keyManagerFactory.init(parent.keyStore, parent.keyStorePrivateKeyPassword) trustManagerFactory.init(parent.trustStore) } @@ -169,6 +175,13 @@ class AMQPServer(val hostName: String, } } + fun dropConnection(connectionRemoteHost: InetSocketAddress) { + val channel = clientChannels[connectionRemoteHost] + if (channel != null) { + channel.close() + } + } + fun complete(delivery: Delivery, target: InetSocketAddress) { val channel = clientChannels[target] channel?.apply { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt index 4ce172cc78..c9f41fcf22 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt @@ -213,8 +213,13 @@ internal fun propertiesForSerializationFromConstructor( return mutableListOf().apply { kotlinConstructor.parameters.withIndex().forEach { param -> - val name = param.value.name ?: throw NotSerializableException( - "Constructor parameter of $clazz has no name.") + // If a parameter doesn't have a name *at all* then chances are it's a synthesised + // one. A good example of this is non static nested classes in Java where instances + // of the nested class require access to the outer class without breaking + // encapsulation. Thus a parameter is inserted into the constructor that passes a + // reference to the enclosing class. In this case we can't do anything with + // it so just ignore it as it'll be supplied at runtime anyway on invocation + val name = param.value.name ?: return@forEach val propertyReader = if (name in classProperties) { if (classProperties[name]!!.getter != null) { diff --git a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaSerializationOutputTests.java b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaSerializationOutputTests.java index 0441b2720f..c51ae5aff9 100644 --- a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaSerializationOutputTests.java +++ b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaSerializationOutputTests.java @@ -1,5 +1,8 @@ package net.corda.nodeapi.internal.serialization.amqp; +import com.google.common.collect.ImmutableList; +import net.corda.core.contracts.ContractState; +import net.corda.core.identity.AbstractParty; import net.corda.nodeapi.internal.serialization.AllWhitelist; import net.corda.core.serialization.SerializedBytes; import org.apache.qpid.proton.codec.DecoderImpl; @@ -9,6 +12,7 @@ import org.junit.Test; import javax.annotation.Nonnull; import java.io.NotSerializableException; import java.nio.ByteBuffer; +import java.util.List; import java.util.Objects; import static org.junit.Assert.assertTrue; @@ -237,4 +241,24 @@ public class JavaSerializationOutputTests { BoxedFooNotNull obj = new BoxedFooNotNull("Hello World!", 123); serdes(obj); } + + protected class DummyState implements ContractState { + @Override + public List getParticipants() { + return ImmutableList.of(); + } + } + + @Test + public void dummyStateSerialize() throws NotSerializableException { + SerializerFactory factory1 = new SerializerFactory( + AllWhitelist.INSTANCE, + ClassLoader.getSystemClassLoader(), + new EvolutionSerializerGetter(), + new SerializerFingerPrinter()); + + SerializationOutput serializer = new SerializationOutput(factory1); + + serializer.serialize(new DummyState()); + } } diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderStaticContractTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderStaticContractTests.kt index 519c1632fc..c264264bc6 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderStaticContractTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderStaticContractTests.kt @@ -17,6 +17,7 @@ import net.corda.testing.core.DUMMY_NOTARY_NAME import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.TestIdentity import net.corda.testing.internal.rigorousMock +import net.corda.testing.internal.MockCordappConfigProvider import net.corda.testing.services.MockAttachmentStorage import org.junit.Assert.* import org.junit.Rule @@ -58,7 +59,7 @@ class AttachmentsClassLoaderStaticContractTests { } private val serviceHub = rigorousMock().also { - doReturn(CordappProviderImpl(CordappLoader.createWithTestPackages(listOf("net.corda.nodeapi.internal")), MockAttachmentStorage())).whenever(it).cordappProvider + doReturn(CordappProviderImpl(CordappLoader.createWithTestPackages(listOf("net.corda.nodeapi.internal")), MockCordappConfigProvider(), MockAttachmentStorage())).whenever(it).cordappProvider } @Test diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderTests.kt index af8e806874..5ee6355980 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderTests.kt @@ -23,6 +23,7 @@ import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.TestIdentity import net.corda.testing.internal.kryoSpecific import net.corda.testing.internal.rigorousMock +import net.corda.testing.internal.MockCordappConfigProvider import net.corda.testing.services.MockAttachmentStorage import org.apache.commons.io.IOUtils import org.junit.Assert.* @@ -57,7 +58,7 @@ class AttachmentsClassLoaderTests { @JvmField val testSerialization = SerializationEnvironmentRule() private val attachments = MockAttachmentStorage() - private val cordappProvider = CordappProviderImpl(CordappLoader.createDevMode(listOf(ISOLATED_CONTRACTS_JAR_PATH)), attachments) + private val cordappProvider = CordappProviderImpl(CordappLoader.createDevMode(listOf(ISOLATED_CONTRACTS_JAR_PATH)), MockCordappConfigProvider(), attachments) private val cordapp get() = cordappProvider.cordapps.first() private val attachmentId get() = cordappProvider.getCordappAttachmentId(cordapp)!! private val appContext get() = cordappProvider.getAppContext(cordapp) diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/ContractAttachmentSerializerTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/ContractAttachmentSerializerTest.kt index e39c2e87a9..01e296324d 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/ContractAttachmentSerializerTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/ContractAttachmentSerializerTest.kt @@ -25,7 +25,7 @@ class ContractAttachmentSerializerTest { private lateinit var factory: SerializationFactory private lateinit var context: SerializationContext private lateinit var contextWithToken: SerializationContext - private val mockServices = MockServices(emptyList(), rigorousMock(), CordaX500Name("MegaCorp", "London", "GB")) + private val mockServices = MockServices(emptyList(), CordaX500Name("MegaCorp", "London", "GB"), rigorousMock()) @Before fun setup() { diff --git a/node/build.gradle b/node/build.gradle index 8b4ff9c806..62ffb40f0a 100644 --- a/node/build.gradle +++ b/node/build.gradle @@ -9,6 +9,14 @@ apply plugin: 'com.jfrog.artifactory' description 'Corda node modules' +// Import private compile time constants +buildscript { + def properties = new Properties() + file("$projectDir/src/main/resources/build.properties").withInputStream { properties.load(it) } + + ext.jolokia_version = properties.getProperty('jolokiaAgentVersion') +} + //noinspection GroovyAssignabilityCheck configurations { compile { @@ -178,6 +186,9 @@ dependencies { // Jsh: Testing SSH server integrationTestCompile group: 'com.jcraft', name: 'jsch', version: '0.1.54' + // AgentLoader: dynamic loading of JVM agents + compile group: 'com.ea.agentloader', name: 'ea-agent-loader', version: "${eaagentloader_version}" + // Jetty dependencies for NetworkMapClient test. // Web stuff: for HTTP[S] servlets testCompile "org.eclipse.jetty:jetty-servlet:${jetty_version}" diff --git a/node/capsule/build.gradle b/node/capsule/build.gradle index 1fcb9e2bdf..ff5ee676f4 100644 --- a/node/capsule/build.gradle +++ b/node/capsule/build.gradle @@ -59,11 +59,6 @@ task buildCordaJAR(type: FatCapsule, dependsOn: project(':node').compileJava) { // If you change these flags, please also update Driver.kt jvmArgs = ['-Xmx200m', '-XX:+UseG1GC'] } - - // Make the resulting JAR file directly executable on UNIX by prepending a shell script to it. - // This lets you run the file like so: ./corda.jar - // Other than being slightly less typing, this has one big advantage: Ctrl-C works properly in the terminal. - reallyExecutable { trampolining() } } artifacts { diff --git a/node/src/integration-test/kotlin/net/corda/node/CordappConfigFileProviderTests.kt b/node/src/integration-test/kotlin/net/corda/node/CordappConfigFileProviderTests.kt new file mode 100644 index 0000000000..ee2fd4674f --- /dev/null +++ b/node/src/integration-test/kotlin/net/corda/node/CordappConfigFileProviderTests.kt @@ -0,0 +1,60 @@ +package net.corda.node + +import com.typesafe.config.Config +import com.typesafe.config.ConfigException +import com.typesafe.config.ConfigFactory +import com.typesafe.config.ConfigRenderOptions +import net.corda.node.internal.cordapp.CordappConfigFileProvider +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import java.io.File +import java.nio.file.Files +import java.nio.file.Path + +class CordappConfigFileProviderTests { + private companion object { + val cordappConfDir = File("build/tmp/cordapps/config") + val cordappName = "test" + val cordappConfFile = File(cordappConfDir, cordappName + ".conf").toPath() + + val validConfig = ConfigFactory.parseString("key=value") + val alternateValidConfig = ConfigFactory.parseString("key=alternateValue") + val invalidConfig = "Invalid" + } + + val provider = CordappConfigFileProvider(cordappConfDir) + + @Test + fun `test that config can be loaded`() { + writeConfig(validConfig, cordappConfFile) + assertThat(provider.getConfigByName(cordappName)).isEqualTo(validConfig) + } + + @Test + fun `config is idempotent if the underlying file is not changed`() { + writeConfig(validConfig, cordappConfFile) + assertThat(provider.getConfigByName(cordappName)).isEqualTo(validConfig) + assertThat(provider.getConfigByName(cordappName)).isEqualTo(validConfig) + } + + @Test + fun `config is not idempotent if the underlying file is changed`() { + writeConfig(validConfig, cordappConfFile) + assertThat(provider.getConfigByName(cordappName)).isEqualTo(validConfig) + + writeConfig(alternateValidConfig, cordappConfFile) + assertThat(provider.getConfigByName(cordappName)).isEqualTo(alternateValidConfig) + } + + @Test(expected = ConfigException.Parse::class) + fun `an invalid config throws an exception`() { + Files.write(cordappConfFile, invalidConfig.toByteArray()) + + provider.getConfigByName(cordappName) + } + + /** + * Writes the config to the path provided - will (and must) overwrite any existing config + */ + private fun writeConfig(config: Config, to: Path) = Files.write(cordappConfFile, config.root().render(ConfigRenderOptions.concise()).toByteArray()) +} \ No newline at end of file diff --git a/node/src/integration-test/kotlin/net/corda/node/amqp/AMQPBridgeTest.kt b/node/src/integration-test/kotlin/net/corda/node/amqp/AMQPBridgeTest.kt index 51a940109c..c6408bb420 100644 --- a/node/src/integration-test/kotlin/net/corda/node/amqp/AMQPBridgeTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/amqp/AMQPBridgeTest.kt @@ -139,7 +139,7 @@ class AMQPBridgeTest { doReturn("trustpass").whenever(it).trustStorePassword doReturn("cordacadevpass").whenever(it).keyStorePassword doReturn(artemisAddress).whenever(it).p2pAddress - doReturn("").whenever(it).exportJMXto + doReturn(null).whenever(it).jmxMonitoringHttpPort doReturn(emptyList()).whenever(it).certificateChainCheckPolicies doReturn(EnterpriseConfiguration(MutualExclusionConfiguration(false, "", 20000, 40000))).whenever(it).enterpriseConfiguration } diff --git a/node/src/integration-test/kotlin/net/corda/node/amqp/ProtonWrapperTests.kt b/node/src/integration-test/kotlin/net/corda/node/amqp/ProtonWrapperTests.kt index 0ab376e936..704461aed6 100644 --- a/node/src/integration-test/kotlin/net/corda/node/amqp/ProtonWrapperTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/amqp/ProtonWrapperTests.kt @@ -220,7 +220,7 @@ class ProtonWrapperTests { doReturn("trustpass").whenever(it).trustStorePassword doReturn("cordacadevpass").whenever(it).keyStorePassword doReturn(NetworkHostAndPort("0.0.0.0", artemisPort)).whenever(it).p2pAddress - doReturn("").whenever(it).exportJMXto + doReturn(null).whenever(it).jmxMonitoringHttpPort doReturn(emptyList()).whenever(it).certificateChainCheckPolicies doReturn(EnterpriseConfiguration(MutualExclusionConfiguration(false, "", 20000, 40000))).whenever(it).enterpriseConfiguration } diff --git a/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt index 31cefd3121..2897d60049 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt @@ -4,6 +4,9 @@ import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever import net.corda.core.contracts.Contract import net.corda.core.contracts.PartyAndReference +import net.corda.core.contracts.StateRef +import net.corda.core.contracts.TransactionState +import net.corda.core.cordapp.CordappProvider import net.corda.core.flows.FlowLogic import net.corda.core.flows.UnexpectedFlowEndException import net.corda.core.identity.CordaX500Name @@ -12,7 +15,9 @@ import net.corda.core.internal.concurrent.transpose import net.corda.core.internal.createDirectories import net.corda.core.internal.div import net.corda.core.internal.toLedgerTransaction +import net.corda.core.node.NetworkParameters import net.corda.core.node.ServicesForResolution +import net.corda.core.node.services.AttachmentStorage import net.corda.core.node.services.IdentityService import net.corda.core.serialization.SerializationFactory import net.corda.core.transactions.TransactionBuilder @@ -20,6 +25,7 @@ import net.corda.core.utilities.contextLogger import net.corda.core.utilities.getOrThrow import net.corda.node.internal.cordapp.CordappLoader import net.corda.node.internal.cordapp.CordappProviderImpl +import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.core.* import net.corda.testing.driver.DriverDSL import net.corda.testing.driver.NodeHandle @@ -39,7 +45,7 @@ class AttachmentLoadingTests : IntegrationTest() { @JvmField val testSerialization = SerializationEnvironmentRule() private val attachments = MockAttachmentStorage() - private val provider = CordappProviderImpl(CordappLoader.createDevMode(listOf(isolatedJAR)), attachments) + private val provider = CordappProviderImpl(CordappLoader.createDevMode(listOf(isolatedJAR)), MockCordappConfigProvider(), attachments) private val cordapp get() = provider.cordapps.first() private val attachmentId get() = provider.getCordappAttachmentId(cordapp)!! private val appContext get() = provider.getAppContext(cordapp) @@ -79,12 +85,14 @@ class AttachmentLoadingTests : IntegrationTest() { } } - private val services = rigorousMock().also { - doReturn(attachments).whenever(it).attachments - doReturn(provider).whenever(it).cordappProvider - doReturn(rigorousMock().also { - doReturn(null).whenever(it).partyFromKey(DUMMY_BANK_A.owningKey) - }).whenever(it).identityService + private val services = object : ServicesForResolution { + override fun loadState(stateRef: StateRef): TransactionState<*> = throw NotImplementedError() + override val identityService = rigorousMock().apply { + doReturn(null).whenever(this).partyFromKey(DUMMY_BANK_A.owningKey) + } + override val attachments: AttachmentStorage get() = this@AttachmentLoadingTests.attachments + override val cordappProvider: CordappProvider get() = this@AttachmentLoadingTests.provider + override val networkParameters: NetworkParameters = testNetworkParameters() } @Test diff --git a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt index ad379594d1..35030d4884 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt @@ -19,6 +19,7 @@ import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.Try import net.corda.core.utilities.getOrThrow +import net.corda.node.internal.StartedNode import net.corda.node.services.config.BFTSMaRtConfiguration import net.corda.node.services.config.NotaryConfig import net.corda.node.services.transactions.minClusterSize @@ -31,9 +32,9 @@ import net.corda.testing.core.chooseIdentity import net.corda.testing.core.dummyCommand import net.corda.testing.internal.IntegrationTest import net.corda.testing.internal.IntegrationTestSchemas -import net.corda.testing.node.MockNetwork -import net.corda.testing.node.StartedMockNode import net.corda.testing.node.MockNodeParameters +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 @@ -50,13 +51,13 @@ class BFTNotaryServiceTests : IntegrationTest() { "node_6", "node_7", "node_8", "node_9") } - private lateinit var mockNet: MockNetwork + private lateinit var mockNet: InternalMockNetwork private lateinit var notary: Party - private lateinit var node: StartedMockNode + private lateinit var node: StartedNode @Before fun before() { - mockNet = MockNetwork(emptyList()) + mockNet = InternalMockNetwork(emptyList()) } @After @@ -162,7 +163,7 @@ class BFTNotaryServiceTests : IntegrationTest() { } } - private fun StartedMockNode.signInitialTransaction(notary: Party, block: TransactionBuilder.() -> Any?): SignedTransaction { + private fun StartedNode.signInitialTransaction(notary: Party, block: TransactionBuilder.() -> Any?): SignedTransaction { return services.signInitialTransaction( TransactionBuilder(notary).apply { addCommand(dummyCommand(services.myInfo.chooseIdentity().owningKey)) diff --git a/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt index a698c3e417..a749674516 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt @@ -23,7 +23,6 @@ import net.corda.testing.internal.IntegrationTest import net.corda.testing.internal.IntegrationTestSchemas import net.corda.testing.internal.toDatabaseSchemaName import net.corda.testing.internal.toDatabaseSchemaNames -import net.corda.testing.node.ClusterSpec import net.corda.testing.node.NotarySpec import net.corda.testing.node.User import net.corda.testing.node.internal.DummyClusterSpec diff --git a/node/src/integration-test/kotlin/net/corda/node/services/MySQLNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/MySQLNotaryServiceTests.kt index 4a0b21970f..60317e1ccf 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/MySQLNotaryServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/MySQLNotaryServiceTests.kt @@ -13,6 +13,7 @@ import net.corda.core.identity.Party import net.corda.core.node.NotaryInfo import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.getOrThrow +import net.corda.node.internal.StartedNode import net.corda.node.services.config.MySQLConfiguration import net.corda.node.services.config.NotaryConfig import net.corda.nodeapi.internal.DevIdentityGenerator @@ -23,8 +24,11 @@ import net.corda.testing.core.chooseIdentity import net.corda.testing.core.dummyCommand import net.corda.testing.internal.IntegrationTest import net.corda.testing.internal.IntegrationTestSchemas -import net.corda.testing.node.* +import net.corda.testing.node.MockNodeParameters import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties +import net.corda.testing.node.inMemoryH2DataSourceConfig +import net.corda.testing.node.internal.InternalMockNetwork +import net.corda.testing.node.startFlow import org.junit.After import org.junit.Before import org.junit.ClassRule @@ -41,14 +45,14 @@ class MySQLNotaryServiceTests : IntegrationTest() { val databaseSchemas = IntegrationTestSchemas("node_0", "node_1", "node_2") } - private lateinit var mockNet: MockNetwork - private lateinit var node: StartedMockNode + private lateinit var mockNet: InternalMockNetwork + private lateinit var node: StartedNode private lateinit var notaryParty: Party - private lateinit var notaryNode: StartedMockNode + private lateinit var notaryNode: StartedNode @Before fun before() { - mockNet = MockNetwork(cordappPackages = listOf("net.corda.testing.contracts")) + mockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.testing.contracts")) notaryParty = DevIdentityGenerator.installKeyStoreWithNodeIdentity(mockNet.baseDirectory(mockNet.nextNodeId), notaryName) val networkParameters = NetworkParametersCopier(testNetworkParameters(listOf(NotaryInfo(notaryParty, false)))) val notaryNodeUnstarted = createNotaryNode() @@ -123,7 +127,7 @@ class MySQLNotaryServiceTests : IntegrationTest() { checkSignature(signatureRetry) } - private fun createNotaryNode(): UnstartedMockNode { + private fun createNotaryNode(): InternalMockNetwork.MockNode { val dataStoreProperties = makeTestDataSourceProperties(configSupplier = ::inMemoryH2DataSourceConfig).apply { setProperty("autoCommit", "false") } @@ -139,7 +143,7 @@ class MySQLNotaryServiceTests : IntegrationTest() { ) } - private fun issueState(node: StartedMockNode, notary: Party): StateAndRef<*> { + private fun issueState(node: StartedNode, notary: Party): StateAndRef<*> { return node.database.transaction { val builder = DummyContract.generateInitial(Random().nextInt(), notary, node.info.chooseIdentity().ref(0)) val stx = node.services.signInitialTransaction(builder) diff --git a/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt index aebec2e4f9..554a3829aa 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt @@ -11,6 +11,8 @@ import net.corda.testing.internal.IntegrationTestSchemas import net.corda.testing.internal.toDatabaseSchemaName import net.corda.testing.core.* import net.corda.testing.node.internal.NodeBasedTest +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.junit.Before import org.junit.ClassRule import org.junit.Test @@ -41,6 +43,45 @@ class PersistentNetworkMapCacheTest : NodeBasedTest() { } } + @Test + fun `unknown legal name`() { + val alice = startNodesWithPort(listOf(ALICE))[0] + val netMapCache = alice.services.networkMapCache + alice.database.transaction { + assertThat(netMapCache.getNodesByLegalName(DUMMY_NOTARY_NAME)).isEmpty() + assertThat(netMapCache.getNodeByLegalName(DUMMY_NOTARY_NAME)).isNull() + assertThat(netMapCache.getPeerByLegalName(DUMMY_NOTARY_NAME)).isNull() + assertThat(netMapCache.getPeerCertificateByLegalName(DUMMY_NOTARY_NAME)).isNull() + } + } + + @Test + fun `nodes in distributed service`() { + val alice = startNodesWithPort(listOf(ALICE))[0] + val netMapCache = alice.services.networkMapCache + + val distServiceNodeInfos = alice.database.transaction { + val distributedIdentity = TestIdentity(DUMMY_NOTARY_NAME).identity + (1..2).map { + val nodeInfo = NodeInfo( + addresses = listOf(NetworkHostAndPort("localhost", 1000 + it)), + legalIdentitiesAndCerts = listOf(TestIdentity.fresh("Org-$it").identity, distributedIdentity), + platformVersion = 3, + serial = 1 + ) + netMapCache.addNode(nodeInfo) + nodeInfo + } + } + + alice.database.transaction { + assertThat(netMapCache.getNodesByLegalName(DUMMY_NOTARY_NAME)).containsOnlyElementsOf(distServiceNodeInfos) + assertThatExceptionOfType(IllegalArgumentException::class.java) + .isThrownBy { netMapCache.getNodeByLegalName(DUMMY_NOTARY_NAME) } + .withMessageContaining(DUMMY_NOTARY_NAME.toString()) + } + } + @Test fun `get nodes by owning key and by name`() { val alice = startNodesWithPort(listOf(ALICE))[0] diff --git a/node/src/integration-test/kotlin/net/corda/node/services/rpc/RpcSslTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/rpc/RpcSslTest.kt index c555d96f1f..45facffba8 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/rpc/RpcSslTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/rpc/RpcSslTest.kt @@ -1,6 +1,7 @@ package net.corda.node.services.rpc import net.corda.client.rpc.CordaRPCClient +import net.corda.client.rpc.internal.createCordaRPCClientWithSsl import net.corda.core.identity.CordaX500Name import net.corda.core.utilities.getOrThrow import net.corda.node.services.Permissions.Companion.all @@ -45,7 +46,7 @@ class RpcSslTest : IntegrationTest() { var successful = false driver(DriverParameters(isDebug = true, startNodesInProcess = true, portAllocation = PortAllocation.RandomFree)) { startNode(rpcUsers = listOf(user), customOverrides = nodeSslOptions.useSslRpcOverrides()).getOrThrow().use { node -> - CordaRPCClient(node.rpcAddress, sslConfiguration = clientSslOptions).start(user.username, user.password).use { connection -> + createCordaRPCClientWithSsl(node.rpcAddress, sslConfiguration = clientSslOptions).start(user.username, user.password).use { connection -> connection.proxy.apply { nodeInfo() successful = true diff --git a/node/src/integration-test/kotlin/net/corda/node/services/statemachine/LargeTransactionsTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/statemachine/LargeTransactionsTest.kt index e1219f4a80..783d793690 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/statemachine/LargeTransactionsTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/statemachine/LargeTransactionsTest.kt @@ -9,6 +9,8 @@ import net.corda.core.internal.concurrent.transpose import net.corda.core.messaging.startFlow import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.getOrThrow +import net.corda.node.services.config.MB +import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.contracts.DummyContract import net.corda.testing.contracts.DummyState import net.corda.testing.core.* @@ -18,8 +20,8 @@ import net.corda.testing.driver.driver import net.corda.testing.internal.IntegrationTest import net.corda.testing.internal.IntegrationTestSchemas import net.corda.testing.internal.toDatabaseSchemaName -import org.junit.ClassRule import net.corda.testing.node.User +import org.junit.ClassRule import org.junit.Test import kotlin.test.assertEquals @@ -75,11 +77,15 @@ class LargeTransactionsTest : IntegrationTest() { fun checkCanSendLargeTransactions() { // These 4 attachments yield a transaction that's got >10mb attached, so it'd push us over the Artemis // max message size. - val bigFile1 = InputStreamAndHash.createInMemoryTestZip(1024 * 1024 * 3, 0) - val bigFile2 = InputStreamAndHash.createInMemoryTestZip(1024 * 1024 * 3, 1) - val bigFile3 = InputStreamAndHash.createInMemoryTestZip(1024 * 1024 * 3, 2) - val bigFile4 = InputStreamAndHash.createInMemoryTestZip(1024 * 1024 * 3, 3) - driver(DriverParameters(startNodesInProcess = true, extraCordappPackagesToScan = listOf("net.corda.testing.contracts"), portAllocation = PortAllocation.RandomFree)) { + val bigFile1 = InputStreamAndHash.createInMemoryTestZip(3.MB.toInt(), 0) + val bigFile2 = InputStreamAndHash.createInMemoryTestZip(3.MB.toInt(), 1) + val bigFile3 = InputStreamAndHash.createInMemoryTestZip(3.MB.toInt(), 2) + val bigFile4 = InputStreamAndHash.createInMemoryTestZip(3.MB.toInt(), 3) + driver(DriverParameters( + startNodesInProcess = true, + extraCordappPackagesToScan = listOf("net.corda.testing.contracts"), + networkParameters = testNetworkParameters(maxTransactionSize = 13.MB.toInt()), + portAllocation = PortAllocation.RandomFree)) { val rpcUser = User("admin", "admin", setOf("ALL")) val (alice, _) = listOf(ALICE_NAME, BOB_NAME).map { startNode(providedName = it, rpcUsers = listOf(rpcUser)) }.transpose().getOrThrow() CordaRPCClient(alice.rpcAddress).use(rpcUser.username, rpcUser.password) { diff --git a/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt b/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt index d1a0e43d54..28a454279f 100644 --- a/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt @@ -16,10 +16,10 @@ import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_CA import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_INTERMEDIATE_CA import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA import net.corda.testing.common.internal.testNetworkParameters -import net.corda.testing.core.DEV_ROOT_CA import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.singleIdentity import net.corda.testing.driver.PortAllocation +import net.corda.testing.internal.DEV_ROOT_CA import net.corda.testing.internal.IntegrationTest import net.corda.testing.internal.IntegrationTestSchemas import net.corda.testing.node.NotarySpec diff --git a/node/src/main/kotlin/net/corda/node/internal/CordaClock.kt b/node/src/main/kotlin/net/corda/node/CordaClock.kt similarity index 98% rename from node/src/main/kotlin/net/corda/node/internal/CordaClock.kt rename to node/src/main/kotlin/net/corda/node/CordaClock.kt index c08961ee77..f838364bf6 100644 --- a/node/src/main/kotlin/net/corda/node/internal/CordaClock.kt +++ b/node/src/main/kotlin/net/corda/node/CordaClock.kt @@ -1,4 +1,4 @@ -package net.corda.node.internal +package net.corda.node import net.corda.core.serialization.SerializeAsToken import net.corda.core.serialization.SerializeAsTokenContext diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 235c88c664..428b317ffa 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -14,7 +14,6 @@ import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate import net.corda.core.internal.FlowStateMachine -import net.corda.core.internal.GlobalProperties import net.corda.core.internal.VisibleForTesting import net.corda.core.internal.concurrent.map import net.corda.core.internal.concurrent.openFuture @@ -30,8 +29,10 @@ import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.debug import net.corda.core.utilities.getOrThrow +import net.corda.node.CordaClock import net.corda.node.VersionInfo import net.corda.node.internal.classloading.requireAnnotation +import net.corda.node.internal.cordapp.CordappConfigFileProvider import net.corda.node.internal.cordapp.CordappLoader import net.corda.node.internal.cordapp.CordappProviderImpl import net.corda.node.internal.cordapp.CordappProviderInternal @@ -57,6 +58,8 @@ import net.corda.node.services.vault.NodeVaultService import net.corda.node.services.vault.VaultSoftLockManager import net.corda.node.shell.InteractiveShell import net.corda.node.utilities.AffinityExecutor +import net.corda.node.utilities.JVMAgentRegistry +import net.corda.node.utilities.NodeBuildProperties import net.corda.nodeapi.internal.DevIdentityGenerator import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.persistence.* @@ -70,6 +73,7 @@ import rx.Scheduler import java.io.IOException import java.lang.management.ManagementFactory import java.lang.reflect.InvocationTargetException +import java.nio.file.Paths import java.security.KeyPair import java.security.KeyStoreException import java.security.PublicKey @@ -174,13 +178,13 @@ abstract class AbstractNode(val configuration: NodeConfiguration, // a code smell. val persistentNetworkMapCache = PersistentNetworkMapCache(database, notaries = emptyList()) persistentNetworkMapCache.start() - val (keyPairs, info) = initNodeInfo(persistentNetworkMapCache, identity, identityKeyPair) - val signedNodeInfo = info.sign { publicKey, serialised -> + val (keyPairs, nodeInfo) = initNodeInfo(persistentNetworkMapCache, identity, identityKeyPair) + val signedNodeInfo = nodeInfo.sign { publicKey, serialised -> val privateKey = keyPairs.single { it.public == publicKey }.private privateKey.sign(serialised.bytes) } NodeInfoWatcher.saveToFile(configuration.baseDirectory, signedNodeInfo) - info + nodeInfo } } @@ -188,22 +192,32 @@ abstract class AbstractNode(val configuration: NodeConfiguration, check(started == null) { "Node has already been started" } log.info("Node starting up ...") initCertificate() + initialiseJVMAgents() val schemaService = NodeSchemaService(cordappLoader.cordappSchemas, configuration.notary != null) val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null) val identityService = makeIdentityService(identity.certificate) networkMapClient = configuration.compatibilityZoneURL?.let { NetworkMapClient(it, identityService.trustRoot) } - GlobalProperties.networkParameters = NetworkParametersReader(identityService.trustRoot, networkMapClient, configuration.baseDirectory).networkParameters - check(GlobalProperties.networkParameters.minimumPlatformVersion <= versionInfo.platformVersion) { + val networkParameters = NetworkParametersReader(identityService.trustRoot, networkMapClient, configuration.baseDirectory).networkParameters + check(networkParameters.minimumPlatformVersion <= versionInfo.platformVersion) { "Node's platform version is lower than network's required minimumPlatformVersion" } // Do all of this in a database transaction so anything that might need a connection has one. val (startedImpl, schedulerService) = initialiseDatabasePersistence(schemaService, identityService) { database -> - val networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, GlobalProperties.networkParameters.notaries).start(), identityService) - val (keyPairs, info) = initNodeInfo(networkMapCache, identity, identityKeyPair) - identityService.loadIdentities(info.legalIdentitiesAndCerts) + val networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, networkParameters.notaries).start(), identityService) + val (keyPairs, nodeInfo) = initNodeInfo(networkMapCache, identity, identityKeyPair) + identityService.loadIdentities(nodeInfo.legalIdentitiesAndCerts) val transactionStorage = makeTransactionStorage(database, configuration.transactionCacheSizeBytes) val nodeProperties = NodePropertiesPersistentStore(StubbedNodeUniqueIdProvider::value, database) - val nodeServices = makeServices(keyPairs, schemaService, transactionStorage, database, info, identityService, networkMapCache, nodeProperties) + val nodeServices = makeServices( + keyPairs, + schemaService, + transactionStorage, + database, + nodeInfo, + identityService, + networkMapCache, + nodeProperties, + networkParameters) val mutualExclusionConfiguration = configuration.enterpriseConfiguration.mutualExclusionConfiguration if (mutualExclusionConfiguration.on) { RunOnceService(database, mutualExclusionConfiguration.machineName, @@ -232,12 +246,12 @@ abstract class AbstractNode(val configuration: NodeConfiguration, registerCordappFlows(smm) _services.rpcFlows += cordappLoader.cordapps.flatMap { it.rpcFlows } startShell(rpcOps) - Pair(StartedNodeImpl(this, _services, info, checkpointStorage, smm, attachments, network, database, rpcOps, flowStarter, notaryService), schedulerService) + Pair(StartedNodeImpl(this, _services, nodeInfo, checkpointStorage, smm, attachments, network, database, rpcOps, flowStarter, notaryService), schedulerService) } networkMapUpdater = NetworkMapUpdater(services.networkMapCache, NodeInfoWatcher(configuration.baseDirectory, getRxIoScheduler(), Duration.ofMillis(configuration.additionalNodeInfoPollingFrequencyMsec)), networkMapClient, - GlobalProperties.networkParameters.serialize().hash, + networkParameters.serialize().hash, configuration.baseDirectory) runOnStop += networkMapUpdater::close @@ -290,20 +304,22 @@ abstract class AbstractNode(val configuration: NodeConfiguration, } } - var info = NodeInfo( + val nodeInfoWithBlankSerial = NodeInfo( myAddresses(), setOf(identity, myNotaryIdentity).filterNotNull(), versionInfo.platformVersion, - platformClock.instant().toEpochMilli() + serial = 0 ) - // Check if we have already stored a version of 'our own' NodeInfo, this is to avoid regenerating it with - // a different timestamp. - networkMapCache.getNodesByLegalName(configuration.myLegalName).firstOrNull()?.let { - if (info.copy(serial = it.serial) == it) { - info = it - } + + val nodeInfoFromDb = networkMapCache.getNodeByLegalName(identity.name) + + val nodeInfo = if (nodeInfoWithBlankSerial == nodeInfoFromDb?.copy(serial = 0)) { + // The node info hasn't changed. We use the one from the database to preserve the serial. + nodeInfoFromDb + } else { + nodeInfoWithBlankSerial.copy(serial = platformClock.millis()) } - return Pair(keyPairs, info) + return Pair(keyPairs, nodeInfo) } protected abstract fun myAddresses(): List @@ -529,14 +545,15 @@ abstract class AbstractNode(val configuration: NodeConfiguration, schemaService: SchemaService, transactionStorage: WritableTransactionStorage, database: CordaPersistence, - info: NodeInfo, + nodeInfo: NodeInfo, identityService: IdentityServiceInternal, networkMapCache: NetworkMapCacheInternal, - nodeProperties: NodePropertiesStore): MutableList { + nodeProperties: NodePropertiesStore, + networkParameters: NetworkParameters): MutableList { checkpointStorage = DBCheckpointStorage() val metrics = MetricRegistry() attachments = NodeAttachmentService(metrics, configuration.attachmentContentCacheSizeBytes, configuration.attachmentCacheBound) - val cordappProvider = CordappProviderImpl(cordappLoader, attachments) + val cordappProvider = CordappProviderImpl(cordappLoader, CordappConfigFileProvider(), attachments) val keyManagementService = makeKeyManagementService(identityService, keyPairs) _services = ServiceHubInternalImpl( identityService, @@ -546,10 +563,11 @@ abstract class AbstractNode(val configuration: NodeConfiguration, MonitoringService(metrics), cordappProvider, database, - info, + nodeInfo, networkMapCache, - nodeProperties) - network = makeMessagingService(database, info, nodeProperties) + nodeProperties, + networkParameters) + network = makeMessagingService(database, nodeInfo, nodeProperties, networkParameters) val tokenizableServices = mutableListOf(attachments, network, services.vaultService, services.keyManagementService, services.identityService, platformClock, services.auditService, services.monitoringService, services.networkMapCache, services.schemaService, @@ -578,6 +596,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, log.warn("Certificate key store found but key store password does not match configuration.") false } catch (e: IOException) { + log.error("IO exception while trying to validate keystore", e) false } require(containCorrectKeys) { @@ -693,7 +712,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, _started = null } - protected abstract fun makeMessagingService(database: CordaPersistence, info: NodeInfo, nodeProperties: NodePropertiesStore): MessagingService + protected abstract fun makeMessagingService(database: CordaPersistence, info: NodeInfo, nodeProperties: NodePropertiesStore, networkParameters: NetworkParameters): MessagingService protected abstract fun startMessagingService(rpcOps: RPCOps) private fun obtainIdentity(notaryConfig: NotaryConfig?): Pair { @@ -752,6 +771,22 @@ abstract class AbstractNode(val configuration: NodeConfiguration, return NodeVaultService(platformClock, keyManagementService, stateLoader, hibernateConfig) } + /** Load configured JVM agents */ + private fun initialiseJVMAgents() { + configuration.jmxMonitoringHttpPort?.let { port -> + requireNotNull(NodeBuildProperties.JOLOKIA_AGENT_VERSION) { + "'jolokiaAgentVersion' missing from build properties" + } + log.info("Starting Jolokia agent on HTTP port: $port") + val libDir = Paths.get(configuration.baseDirectory.toString(), "drivers") + val jarFilePath = JVMAgentRegistry.resolveAgentJar( + "jolokia-jvm-${NodeBuildProperties.JOLOKIA_AGENT_VERSION}-agent.jar", libDir) ?: + throw Error("Unable to locate agent jar file") + log.info("Agent jar file: $jarFilePath") + JVMAgentRegistry.attach("jolokia", "port=$port", jarFilePath) + } + } + private inner class ServiceHubInternalImpl( override val identityService: IdentityService, // Place the long term identity key in the KMS. Eventually, this is likely going to be separated again because @@ -765,7 +800,8 @@ abstract class AbstractNode(val configuration: NodeConfiguration, override val database: CordaPersistence, override val myInfo: NodeInfo, override val networkMapCache: NetworkMapCacheInternal, - override val nodeProperties: NodePropertiesStore + override val nodeProperties: NodePropertiesStore, + override val networkParameters: NetworkParameters ) : SingletonSerializeAsToken(), ServiceHubInternal, StateLoader by validatedTransactions { override val rpcFlows = ArrayList>>() override val stateMachineRecordedTransactionMapping = DBTransactionMappingStorage() diff --git a/node/src/main/kotlin/net/corda/node/internal/Node.kt b/node/src/main/kotlin/net/corda/node/internal/Node.kt index 1bf7e874a6..71d03b8855 100644 --- a/node/src/main/kotlin/net/corda/node/internal/Node.kt +++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt @@ -2,12 +2,12 @@ package net.corda.node.internal import com.codahale.metrics.JmxReporter import net.corda.core.concurrent.CordaFuture -import net.corda.core.internal.GlobalProperties.networkParameters import net.corda.core.internal.concurrent.openFuture import net.corda.core.internal.concurrent.thenMatch import net.corda.core.internal.div import net.corda.core.internal.uncheckedCast import net.corda.core.messaging.RPCOps +import net.corda.core.node.NetworkParameters import net.corda.core.node.NodeInfo import net.corda.core.node.ServiceHub import net.corda.core.node.services.IdentityService @@ -16,6 +16,8 @@ import net.corda.core.serialization.internal.SerializationEnvironmentImpl import net.corda.core.serialization.internal.nodeSerializationEnv import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.contextLogger +import net.corda.node.CordaClock +import net.corda.node.SimpleClock import net.corda.node.VersionInfo import net.corda.node.internal.artemis.ArtemisBroker import net.corda.node.internal.artemis.BrokerAddresses @@ -146,7 +148,10 @@ open class Node(configuration: NodeConfiguration, private var shutdownHook: ShutdownHook? = null - override fun makeMessagingService(database: CordaPersistence, info: NodeInfo, nodeProperties: NodePropertiesStore): MessagingService { + override fun makeMessagingService(database: CordaPersistence, + info: NodeInfo, + nodeProperties: NodePropertiesStore, + networkParameters: NetworkParameters): MessagingService { // Construct security manager reading users data either from the 'security' config section // if present or from rpcUsers list if the former is missing from config. val securityManagerConfig = configuration.security?.authService ?: @@ -154,9 +159,13 @@ open class Node(configuration: NodeConfiguration, securityManager = RPCSecurityManagerImpl(securityManagerConfig) - val serverAddress = configuration.messagingServerAddress ?: makeLocalMessageBroker() - val rpcServerAddresses = if (configuration.rpcOptions.standAloneBroker) BrokerAddresses(configuration.rpcOptions.address!!, configuration.rpcOptions.adminAddress) else startLocalRpcBroker() - val advertisedAddress = info.addresses.single() + val serverAddress = configuration.messagingServerAddress ?: makeLocalMessageBroker(networkParameters) + val rpcServerAddresses = if (configuration.rpcOptions.standAloneBroker) { + BrokerAddresses(configuration.rpcOptions.address!!, configuration.rpcOptions.adminAddress) + } else { + startLocalRpcBroker(networkParameters) + } + val advertisedAddress = info.addresses[0] bridgeControlListener = BridgeControlListener(configuration, serverAddress, networkParameters.maxMessageSize) printBasicNodeInfo("Incoming connection address", advertisedAddress.toString()) @@ -188,16 +197,31 @@ open class Node(configuration: NodeConfiguration, ) } - private fun startLocalRpcBroker(): BrokerAddresses? { + private fun startLocalRpcBroker(networkParameters: NetworkParameters): BrokerAddresses? { with(configuration) { return rpcOptions.address?.let { require(rpcOptions.address != null) { "RPC address needs to be specified for local RPC broker." } val rpcBrokerDirectory: Path = baseDirectory / "brokers" / "rpc" with(rpcOptions) { rpcBroker = if (useSsl) { - ArtemisRpcBroker.withSsl(this.address!!, sslConfig, securityManager, certificateChainCheckPolicies, networkParameters.maxMessageSize, exportJMXto.isNotEmpty(), rpcBrokerDirectory) + ArtemisRpcBroker.withSsl( + this.address!!, + sslConfig, + securityManager, + certificateChainCheckPolicies, + networkParameters.maxMessageSize, + jmxMonitoringHttpPort != null, + rpcBrokerDirectory) } else { - ArtemisRpcBroker.withoutSsl(this.address!!, adminAddress!!, sslConfig, securityManager, certificateChainCheckPolicies, networkParameters.maxMessageSize, exportJMXto.isNotEmpty(), rpcBrokerDirectory) + ArtemisRpcBroker.withoutSsl( + this.address!!, + adminAddress!!, + sslConfig, + securityManager, + certificateChainCheckPolicies, + networkParameters.maxMessageSize, + jmxMonitoringHttpPort != null, + rpcBrokerDirectory) } } return rpcBroker!!.addresses @@ -205,16 +229,14 @@ open class Node(configuration: NodeConfiguration, } } - private fun makeLocalMessageBroker(): NetworkHostAndPort { + private fun makeLocalMessageBroker(networkParameters: NetworkParameters): NetworkHostAndPort { with(configuration) { messageBroker = ArtemisMessagingServer(this, p2pAddress.port, networkParameters.maxMessageSize) return NetworkHostAndPort("localhost", p2pAddress.port) } } - override fun myAddresses(): List { - return listOf(configuration.messagingServerAddress ?: getAdvertisedAddress()) - } + override fun myAddresses(): List = listOf(getAdvertisedAddress()) private fun getAdvertisedAddress(): NetworkHostAndPort { return with(configuration) { diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappConfigFileProvider.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappConfigFileProvider.kt new file mode 100644 index 0000000000..921b67f98a --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappConfigFileProvider.kt @@ -0,0 +1,35 @@ +package net.corda.node.internal.cordapp + +import com.typesafe.config.Config +import com.typesafe.config.ConfigFactory +import net.corda.core.internal.cordapp.CordappConfigProvider +import net.corda.core.utilities.loggerFor +import java.io.File + +class CordappConfigFileProvider(val configDir: File = DEFAULT_CORDAPP_CONFIG_DIR) : CordappConfigProvider { + companion object { + val DEFAULT_CORDAPP_CONFIG_DIR = File("cordapps/config") + val CONFIG_EXT = ".conf" + val logger = loggerFor() + } + + init { + configDir.mkdirs() + } + + override fun getConfigByName(name: String): Config { + val configFile = File(configDir, name + CONFIG_EXT) + return if (configFile.exists()) { + if (configFile.isDirectory) { + throw IllegalStateException("File at ${configFile.absolutePath} is a directory, expected a config file") + } else { + logger.info("Found config for cordapp $name in ${configFile.absolutePath}") + ConfigFactory.parseFile(configFile) + } + } else { + logger.info("No config found for cordapp $name in ${configFile.absolutePath}") + ConfigFactory.empty() + } + } + +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappConfigProvider.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappConfigProvider.kt new file mode 100644 index 0000000000..f632481d1c --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappConfigProvider.kt @@ -0,0 +1,7 @@ +package net.corda.core.internal.cordapp + +import com.typesafe.config.Config + +interface CordappConfigProvider { + fun getConfigByName(name: String): Config +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderImpl.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderImpl.kt index cfed6d0cd2..a25d872124 100644 --- a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderImpl.kt +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderImpl.kt @@ -5,21 +5,27 @@ import net.corda.core.contracts.ContractClassName import net.corda.core.cordapp.Cordapp import net.corda.core.cordapp.CordappContext import net.corda.core.crypto.SecureHash +import net.corda.core.internal.cordapp.CordappConfigProvider +import net.corda.core.internal.createCordappContext import net.corda.core.node.services.AttachmentId import net.corda.core.node.services.AttachmentStorage import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.utilities.loggerFor import java.net.URL +import java.util.concurrent.ConcurrentHashMap /** * Cordapp provider and store. For querying CorDapps for their attachment and vice versa. */ -open class CordappProviderImpl(private val cordappLoader: CordappLoader, attachmentStorage: AttachmentStorage) : SingletonSerializeAsToken(), CordappProviderInternal { +open class CordappProviderImpl(private val cordappLoader: CordappLoader, private val cordappConfigProvider: CordappConfigProvider, attachmentStorage: AttachmentStorage) : SingletonSerializeAsToken(), CordappProviderInternal { companion object { private val log = loggerFor() } + private val contextCache = ConcurrentHashMap() + + override fun getAppContext(): CordappContext { // TODO: Use better supported APIs in Java 9 Exception().stackTrace.forEach { stackFrame -> @@ -51,7 +57,7 @@ open class CordappProviderImpl(private val cordappLoader: CordappLoader, attachm private fun loadContractsIntoAttachmentStore(attachmentStorage: AttachmentStorage): Map { val cordappsWithAttachments = cordapps.filter { !it.contractClassNames.isEmpty() }.map { it.jarPath } - val attachmentIds = cordappsWithAttachments.map { it.openStream().use { attachmentStorage.importOrGetAttachment(it) }} + val attachmentIds = cordappsWithAttachments.map { it.openStream().use { attachmentStorage.importOrGetAttachment(it) } } return attachmentIds.zip(cordappsWithAttachments).toMap() } @@ -62,7 +68,14 @@ open class CordappProviderImpl(private val cordappLoader: CordappLoader, attachm * @return A cordapp context for the given CorDapp */ fun getAppContext(cordapp: Cordapp): CordappContext { - return CordappContext(cordapp, getCordappAttachmentId(cordapp), cordappLoader.appClassLoader) + return contextCache.computeIfAbsent(cordapp, { + createCordappContext( + cordapp, + getCordappAttachmentId(cordapp), + cordappLoader.appClassLoader, + TypesafeCordappConfig(cordappConfigProvider.getConfigByName(cordapp.name)) + ) + }) } /** diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/TypesafeCordappConfig.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/TypesafeCordappConfig.kt new file mode 100644 index 0000000000..73f5633350 --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/TypesafeCordappConfig.kt @@ -0,0 +1,79 @@ +package net.corda.node.internal.cordapp + +import com.typesafe.config.Config +import com.typesafe.config.ConfigException +import net.corda.core.cordapp.CordappConfig +import net.corda.core.cordapp.CordappConfigException + +/** + * Provides configuration from a typesafe config source + */ +class TypesafeCordappConfig(private val cordappConfig: Config) : CordappConfig { + override fun exists(path: String): Boolean { + return cordappConfig.hasPath(path) + } + + override fun get(path: String): Any { + try { + return cordappConfig.getAnyRef(path) + } catch (e: ConfigException) { + throw CordappConfigException("Cordapp configuration is incorrect due to exception", e) + } + } + + override fun getInt(path: String): Int { + try { + return cordappConfig.getInt(path) + } catch (e: ConfigException) { + throw CordappConfigException("Cordapp configuration is incorrect due to exception", e) + } + } + + override fun getLong(path: String): Long { + try { + return cordappConfig.getLong(path) + } catch (e: ConfigException) { + throw CordappConfigException("Cordapp configuration is incorrect due to exception", e) + } + } + + override fun getFloat(path: String): Float { + try { + return cordappConfig.getDouble(path).toFloat() + } catch (e: ConfigException) { + throw CordappConfigException("Cordapp configuration is incorrect due to exception", e) + } + } + + override fun getDouble(path: String): Double { + try { + return cordappConfig.getDouble(path) + } catch (e: ConfigException) { + throw CordappConfigException("Cordapp configuration is incorrect due to exception", e) + } + } + + override fun getNumber(path: String): Number { + try { + return cordappConfig.getNumber(path) + } catch (e: ConfigException) { + throw CordappConfigException("Cordapp configuration is incorrect due to exception", e) + } + } + + override fun getString(path: String): String { + try { + return cordappConfig.getString(path) + } catch (e: ConfigException) { + throw CordappConfigException("Cordapp configuration is incorrect due to exception", e) + } + } + + override fun getBoolean(path: String): Boolean { + try { + return cordappConfig.getBoolean(path) + } catch (e: ConfigException) { + throw CordappConfigException("Cordapp configuration is incorrect due to exception", e) + } + } +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt index ef0ae2b6fb..a7a4da24de 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt @@ -26,7 +26,7 @@ val Int.MB: Long get() = this * 1024L * 1024L interface NodeConfiguration : NodeSSLConfiguration { val myLegalName: CordaX500Name val emailAddress: String - val exportJMXto: String + val jmxMonitoringHttpPort: Int? val dataSourceProperties: Properties val rpcUsers: List val security: SecurityConfiguration? @@ -140,6 +140,7 @@ data class NodeConfigurationImpl( /** This is not retrieved from the config file but rather from a command line argument. */ override val baseDirectory: Path, override val myLegalName: CordaX500Name, + override val jmxMonitoringHttpPort: Int? = null, override val emailAddress: String, override val keyStorePassword: String, override val trustStorePassword: String, @@ -209,7 +210,6 @@ data class NodeConfigurationImpl( return errors } - override val exportJMXto: String get() = "http" override val transactionCacheSizeBytes: Long get() = transactionCacheSizeMegaBytes?.MB ?: super.transactionCacheSizeBytes override val attachmentContentCacheSizeBytes: Long diff --git a/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt b/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt index 27a653d0d0..f6bf1630fa 100644 --- a/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt +++ b/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt @@ -21,8 +21,8 @@ import net.corda.core.schemas.PersistentStateRef import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.utilities.contextLogger import net.corda.core.utilities.trace -import net.corda.node.internal.CordaClock -import net.corda.node.internal.MutableClock +import net.corda.node.CordaClock +import net.corda.node.MutableClock import net.corda.node.services.api.FlowStarter import net.corda.node.services.api.NodePropertiesStore import net.corda.node.services.api.SchedulerService diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt index 21e72cdcae..a96d7c346c 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt @@ -144,7 +144,7 @@ class ArtemisMessagingServer(private val config: NodeConfiguration, managementNotificationAddress = SimpleString(NOTIFICATIONS_ADDRESS) connectionTtlCheckInterval = config.enterpriseConfiguration.tuning.brokerConnectionTtlCheckIntervalMs // JMX enablement - if (config.exportJMXto.isNotEmpty()) { + if (config.jmxMonitoringHttpPort != null) { isJMXManagementEnabled = true isJMXUseBrokerName = true } diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt b/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt index a25dc5dd45..103eb36476 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt @@ -18,9 +18,7 @@ import net.corda.core.context.InvocationContext import net.corda.core.context.Trace import net.corda.core.context.Trace.InvocationId import net.corda.core.identity.CordaX500Name -import net.corda.core.internal.LazyStickyPool import net.corda.core.internal.LifeCycle -import net.corda.core.internal.join import net.corda.core.messaging.RPCOps import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationDefaults.RPC_SERVER_CONTEXT @@ -29,14 +27,17 @@ import net.corda.core.utilities.* import net.corda.node.internal.security.AuthorizingSubject import net.corda.node.internal.security.RPCSecurityManager import net.corda.node.services.logging.pushToLoggingContext -import net.corda.nodeapi.* +import net.corda.nodeapi.RPCApi +import net.corda.nodeapi.externalTrace +import net.corda.nodeapi.impersonatedActor +import net.corda.nodeapi.internal.DeduplicationChecker +import net.corda.nodeapi.internal.persistence.CordaPersistence +import net.corda.nodeapi.internal.persistence.contextDatabase +import net.corda.nodeapi.internal.persistence.contextDatabaseOrNull import org.apache.activemq.artemis.api.core.Message 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.ClientConsumer -import org.apache.activemq.artemis.api.core.client.ClientMessage -import org.apache.activemq.artemis.api.core.client.ClientSession -import org.apache.activemq.artemis.api.core.client.ServerLocator import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl import org.apache.activemq.artemis.api.core.management.CoreNotificationType import org.apache.activemq.artemis.api.core.management.ManagementHelper @@ -49,24 +50,23 @@ import rx.Subscription import java.lang.reflect.InvocationTargetException import java.lang.reflect.Method import java.time.Duration +import java.util.* import java.util.concurrent.* +import kotlin.concurrent.thread data class RPCServerConfiguration( /** The number of threads to use for handling RPC requests */ val rpcThreadPoolSize: Int, - /** The number of consumers to handle incoming messages */ - val consumerPoolSize: Int, - /** The maximum number of producers to create to handle outgoing messages */ - val producerPoolBound: Int, /** The interval of subscription reaping */ - val reapInterval: Duration + val reapInterval: Duration, + /** The cache expiry of a deduplication watermark per client. */ + val deduplicationCacheExpiry: Duration ) { companion object { val default = RPCServerConfiguration( rpcThreadPoolSize = 4, - consumerPoolSize = 2, - producerPoolBound = 4, - reapInterval = 1.seconds + reapInterval = 1.seconds, + deduplicationCacheExpiry = 1.days ) } } @@ -115,22 +115,24 @@ class RPCServer( /** The scheduled reaper handle. */ private var reaperScheduledFuture: ScheduledFuture<*>? = null - private var observationSendExecutor: ExecutorService? = null + private var senderThread: Thread? = null private var rpcExecutor: ScheduledExecutorService? = null private var reaperExecutor: ScheduledExecutorService? = null - private val sessionAndConsumers = ArrayList(rpcConfiguration.consumerPoolSize) - private val sessionAndProducerPool = LazyStickyPool(rpcConfiguration.producerPoolBound) { - val sessionFactory = serverLocator.createSessionFactory() - val session = sessionFactory.createSession(rpcServerUsername, rpcServerPassword, false, true, true, false, DEFAULT_ACK_BATCH_SIZE) - session.start() - ArtemisProducer(sessionFactory, session, session.createProducer()) - } + 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 var clientBindingRemovalConsumer: ClientConsumer? = null private var clientBindingAdditionConsumer: ClientConsumer? = null private var serverControl: ActiveMQServerControl? = null private val responseMessageBuffer = ConcurrentHashMap() + private val sendJobQueue = LinkedBlockingQueue() + + private val deduplicationChecker = DeduplicationChecker(rpcConfiguration.deduplicationCacheExpiry) + private var deduplicationIdentity: String? = null init { val groupedMethods = ops.javaClass.declaredMethods.groupBy { it.name } @@ -154,16 +156,12 @@ class RPCServer( try { lifeCycle.requireState(State.UNSTARTED) log.info("Starting RPC server with configuration $rpcConfiguration") - observationSendExecutor = Executors.newFixedThreadPool( - 1, - ThreadFactoryBuilder().setNameFormat("rpc-observation-sender-%d").build() - ) + senderThread = startSenderThread() rpcExecutor = Executors.newScheduledThreadPool( rpcConfiguration.rpcThreadPoolSize, ThreadFactoryBuilder().setNameFormat("rpc-server-handler-pool-%d").build() ) - reaperExecutor = Executors.newScheduledThreadPool( - 1, + reaperExecutor = Executors.newSingleThreadScheduledExecutor( ThreadFactoryBuilder().setNameFormat("rpc-server-reaper-%d").build() ) reaperScheduledFuture = reaperExecutor!!.scheduleAtFixedRate( @@ -172,55 +170,85 @@ class RPCServer( rpcConfiguration.reapInterval.toMillis(), TimeUnit.MILLISECONDS ) - val sessions = createConsumerSessions() - createNotificationConsumers() + + sessionFactory = serverLocator.createSessionFactory() + producerSession = sessionFactory!!.createSession(rpcServerUsername, rpcServerPassword, false, true, true, false, DEFAULT_ACK_BATCH_SIZE) + createRpcProducer(producerSession!!) + consumerSession = sessionFactory!!.createSession(rpcServerUsername, rpcServerPassword, false, true, true, false, DEFAULT_ACK_BATCH_SIZE) + createRpcConsumer(consumerSession!!) + createNotificationConsumers(consumerSession!!) serverControl = activeMqServerControl + deduplicationIdentity = UUID.randomUUID().toString() lifeCycle.transition(State.UNSTARTED, State.STARTED) // We delay the consumer session start because Artemis starts delivering messages immediately, so we need to be // fully initialised. - sessions.forEach { - it.start() - } + producerSession!!.start() + consumerSession!!.start() } catch (exception: Throwable) { close() throw exception } } - private fun createConsumerSessions(): ArrayList { - val sessions = ArrayList() - for (i in 1..rpcConfiguration.consumerPoolSize) { - val sessionFactory = serverLocator.createSessionFactory() - val session = sessionFactory.createSession(rpcServerUsername, rpcServerPassword, false, true, true, false, DEFAULT_ACK_BATCH_SIZE) - val consumer = session.createConsumer(RPCApi.RPC_SERVER_QUEUE_NAME) - consumer.setMessageHandler(this@RPCServer::clientArtemisMessageHandler) - sessionAndConsumers.add(ArtemisConsumer(sessionFactory, session, consumer)) - sessions.add(session) - } - return sessions + private fun createRpcProducer(producerSession: ClientSession) { + rpcProducer = producerSession.createProducer() } - private fun createNotificationConsumers() { - clientBindingRemovalConsumer = sessionAndConsumers[0].session.createConsumer(RPCApi.RPC_CLIENT_BINDING_REMOVALS) + private fun createRpcConsumer(consumerSession: ClientSession) { + rpcConsumer = consumerSession.createConsumer(RPCApi.RPC_SERVER_QUEUE_NAME) + rpcConsumer!!.setMessageHandler(this::clientArtemisMessageHandler) + } + + private fun createNotificationConsumers(consumerSession: ClientSession) { + clientBindingRemovalConsumer = consumerSession.createConsumer(RPCApi.RPC_CLIENT_BINDING_REMOVALS) clientBindingRemovalConsumer!!.setMessageHandler(this::bindingRemovalArtemisMessageHandler) - clientBindingAdditionConsumer = sessionAndConsumers[0].session.createConsumer(RPCApi.RPC_CLIENT_BINDING_ADDITIONS) + clientBindingAdditionConsumer = consumerSession.createConsumer(RPCApi.RPC_CLIENT_BINDING_ADDITIONS) clientBindingAdditionConsumer!!.setMessageHandler(this::bindingAdditionArtemisMessageHandler) } + private fun startSenderThread(): Thread { + return thread(name = "rpc-server-sender", isDaemon = true) { + var deduplicationSequenceNumber = 0L + while (true) { + val job = sendJobQueue.poll() + when (job) { + is RpcSendJob.Send -> handleSendJob(deduplicationSequenceNumber++, job) + RpcSendJob.Stop -> return@thread + } + } + } + } + + private fun handleSendJob(sequenceNumber: Long, job: RpcSendJob.Send) { + try { + val artemisMessage = producerSession!!.createMessage(false) + if (job.database != null) { + contextDatabase = job.database + } + // We must do the serialisation here as any encountered Observables may already have events, which would + // trigger more sends. We must make sure that the root of the Observables (e.g. the RPC reply) is sent + // before any child observations. + job.message.writeToClientMessage(job.serializationContext, artemisMessage) + artemisMessage.putLongProperty(RPCApi.DEDUPLICATION_SEQUENCE_NUMBER_FIELD_NAME, sequenceNumber) + rpcProducer!!.send(job.clientAddress, artemisMessage) + log.debug { "<- RPC <- ${job.message}" } + } catch (throwable: Throwable) { + log.error("Failed to send message, kicking client. Message was ${job.message}", throwable) + serverControl!!.closeConsumerConnectionsForAddress(job.clientAddress.toString()) + invalidateClient(job.clientAddress) + } + } + fun close() { - observationSendExecutor?.join() + sendJobQueue.put(RpcSendJob.Stop) + senderThread?.join() reaperScheduledFuture?.cancel(false) rpcExecutor?.shutdownNow() reaperExecutor?.shutdownNow() securityManager.close() - sessionAndConsumers.forEach { - it.sessionFactory.close() - } + sessionFactory?.close() observableMap.invalidateAll() reapSubscriptions() - sessionAndProducerPool.close().forEach { - it.sessionFactory.close() - } lifeCycle.justTransition(State.FINISHED) } @@ -271,32 +299,44 @@ class RPCServer( lifeCycle.requireState(State.STARTED) val clientToServer = RPCApi.ClientToServer.fromClientMessage(artemisMessage) log.debug { "-> RPC -> $clientToServer" } - when (clientToServer) { - is RPCApi.ClientToServer.RpcRequest -> { - val arguments = Try.on { - clientToServer.serialisedArguments.deserialize>(context = RPC_SERVER_CONTEXT) - } - val context = artemisMessage.context(clientToServer.sessionId) - context.invocation.pushToLoggingContext() - when (arguments) { - is Try.Success -> { - rpcExecutor!!.submit { - val result = invokeRpc(context, clientToServer.methodName, arguments.value) - sendReply(clientToServer.replyId, clientToServer.clientAddress, result) + try { + when (clientToServer) { + is RPCApi.ClientToServer.RpcRequest -> { + val deduplicationSequenceNumber = artemisMessage.getLongProperty(RPCApi.DEDUPLICATION_SEQUENCE_NUMBER_FIELD_NAME) + if (deduplicationChecker.checkDuplicateMessageId( + identity = clientToServer.clientAddress, + sequenceNumber = deduplicationSequenceNumber + )) { + log.info("Message duplication detected, discarding message") + return + } + val arguments = Try.on { + clientToServer.serialisedArguments.deserialize>(context = RPC_SERVER_CONTEXT) + } + val context = artemisMessage.context(clientToServer.sessionId) + context.invocation.pushToLoggingContext() + when (arguments) { + is Try.Success -> { + log.info("SUBMITTING") + rpcExecutor!!.submit { + val result = invokeRpc(context, clientToServer.methodName, arguments.value) + sendReply(clientToServer.replyId, clientToServer.clientAddress, result) + } + } + is Try.Failure -> { + // We failed to deserialise the arguments, route back the error + log.warn("Inbound RPC failed", arguments.exception) + sendReply(clientToServer.replyId, clientToServer.clientAddress, arguments) } } - is Try.Failure -> { - // We failed to deserialise the arguments, route back the error - log.warn("Inbound RPC failed", arguments.exception) - sendReply(clientToServer.replyId, clientToServer.clientAddress, arguments) - } + } + is RPCApi.ClientToServer.ObservablesClosed -> { + observableMap.invalidateAll(clientToServer.ids) } } - is RPCApi.ClientToServer.ObservablesClosed -> { - observableMap.invalidateAll(clientToServer.ids) - } + } finally { + artemisMessage.acknowledge() } - artemisMessage.acknowledge() } private fun invokeRpc(context: RpcAuthContext, methodName: String, arguments: List): Try { @@ -316,15 +356,16 @@ class RPCServer( } private fun sendReply(replyId: InvocationId, clientAddress: SimpleString, result: Try) { - val reply = RPCApi.ServerToClient.RpcReply(replyId, result) + val reply = RPCApi.ServerToClient.RpcReply( + id = replyId, + result = result, + deduplicationIdentity = deduplicationIdentity!! + ) val observableContext = ObservableContext( - replyId, observableMap, clientAddressToObservables, - clientAddress, - serverControl!!, - sessionAndProducerPool, - observationSendExecutor!! + deduplicationIdentity!!, + clientAddress ) val buffered = bufferIfQueueNotBound(clientAddress, reply, observableContext) @@ -370,6 +411,36 @@ class RPCServer( val targetLegalIdentity = message.getStringProperty(RPCApi.RPC_TARGET_LEGAL_IDENTITY)?.let(CordaX500Name.Companion::parse) ?: nodeLegalName return Pair(Actor(Id(validatedUser), securityManager.id, targetLegalIdentity), securityManager.buildSubject(validatedUser)) } + + /* + * We construct an observable context on each RPC request. If subsequently a nested Observable is encountered this + * same context is propagated by the instrumented KryoPool. This way all observations rooted in a single RPC will be + * muxed correctly. Note that the context construction itself is quite cheap. + */ + inner class ObservableContext( + val observableMap: ObservableSubscriptionMap, + val clientAddressToObservables: SetMultimap, + val deduplicationIdentity: String, + val clientAddress: SimpleString + ) { + private val serializationContextWithObservableContext = RpcServerObservableSerializer.createContext(this) + + fun sendMessage(serverToClient: RPCApi.ServerToClient) { + sendJobQueue.put(RpcSendJob.Send(contextDatabaseOrNull, clientAddress, serializationContextWithObservableContext, serverToClient)) + } + } + + private sealed class RpcSendJob { + data class Send( + // TODO HACK this is because during serialisation we subscribe to observables that may use + // DatabaseTransactionWrappingSubscriber which tries to access the current database, + val database: CordaPersistence?, + val clientAddress: SimpleString, + val serializationContext: SerializationContext, + val message: RPCApi.ServerToClient + ) : RpcSendJob() + object Stop : RpcSendJob() + } } // TODO replace this by creating a new CordaRPCImpl for each request, passing the context, after we fix Shell and WebServer @@ -417,45 +488,11 @@ class ObservableSubscription( typealias ObservableSubscriptionMap = Cache -// We construct an observable context on each RPC request. If subsequently a nested Observable is -// encountered this same context is propagated by the instrumented KryoPool. This way all -// observations rooted in a single RPC will be muxed correctly. Note that the context construction -// itself is quite cheap. -class ObservableContext( - val invocationId: InvocationId, - val observableMap: ObservableSubscriptionMap, - val clientAddressToObservables: SetMultimap, - val clientAddress: SimpleString, - val serverControl: ActiveMQServerControl, - val sessionAndProducerPool: LazyStickyPool, - val observationSendExecutor: ExecutorService -) { - private companion object { - private val log = contextLogger() - } - - private val serializationContextWithObservableContext = RpcServerObservableSerializer.createContext(this) - - fun sendMessage(serverToClient: RPCApi.ServerToClient) { - try { - sessionAndProducerPool.run(invocationId) { - val artemisMessage = it.session.createMessage(false) - serverToClient.writeToClientMessage(serializationContextWithObservableContext, artemisMessage) - it.producer.send(clientAddress, artemisMessage) - log.debug { "<- RPC <- $serverToClient" } - } - } catch (throwable: Throwable) { - log.error("Failed to send message, kicking client. Message was $serverToClient", throwable) - serverControl.closeConsumerConnectionsForAddress(clientAddress.toString()) - } - } -} - object RpcServerObservableSerializer : Serializer>() { private object RpcObservableContextKey private val log = LoggerFactory.getLogger(javaClass) - fun createContext(observableContext: ObservableContext): SerializationContext { + fun createContext(observableContext: RPCServer.ObservableContext): SerializationContext { return RPC_SERVER_CONTEXT.withProperty(RpcServerObservableSerializer.RpcObservableContextKey, observableContext) } @@ -465,7 +502,7 @@ object RpcServerObservableSerializer : Serializer>() { override fun write(kryo: Kryo, output: Output, observable: Observable<*>) { val observableId = InvocationId.newInstance() - val observableContext = kryo.context[RpcObservableContextKey] as ObservableContext + val observableContext = kryo.context[RpcObservableContextKey] as RPCServer.ObservableContext output.writeInvocationId(observableId) val observableWithSubscription = ObservableSubscription( // We capture [observableContext] in the subscriber. Note that all synchronisation/kryo borrowing @@ -474,9 +511,12 @@ object RpcServerObservableSerializer : Serializer>() { object : Subscriber>() { override fun onNext(observation: Notification<*>) { if (!isUnsubscribed) { - observableContext.observationSendExecutor.submit { - observableContext.sendMessage(RPCApi.ServerToClient.Observation(observableId, observation)) - } + val message = RPCApi.ServerToClient.Observation( + id = observableId, + content = observation, + deduplicationIdentity = observableContext.deduplicationIdentity + ) + observableContext.sendMessage(message) } } diff --git a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt index bfa0350b58..c571835c63 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt @@ -7,12 +7,11 @@ import net.corda.core.identity.AbstractParty import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate -import net.corda.core.node.NotaryInfo import net.corda.core.internal.bufferUntilSubscribed import net.corda.core.internal.concurrent.openFuture -import net.corda.node.internal.schemas.NodeInfoSchemaV1 import net.corda.core.messaging.DataFeed import net.corda.core.node.NodeInfo +import net.corda.core.node.NotaryInfo import net.corda.core.node.services.IdentityService import net.corda.core.node.services.NetworkMapCache.MapChange import net.corda.core.node.services.PartyInfo @@ -21,6 +20,7 @@ import net.corda.core.serialization.serialize import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.contextLogger import net.corda.core.utilities.loggerFor +import net.corda.node.internal.schemas.NodeInfoSchemaV1 import net.corda.node.services.api.NetworkMapCacheBaseInternal import net.corda.node.services.api.NetworkMapCacheInternal import net.corda.node.utilities.NonInvalidatingCache @@ -154,8 +154,17 @@ open class PersistentNetworkMapCache( return null } - override fun getNodeByLegalName(name: CordaX500Name): NodeInfo? = getNodesByLegalName(name).firstOrNull() + override fun getNodeByLegalName(name: CordaX500Name): NodeInfo? { + val nodeInfos = getNodesByLegalName(name) + return when (nodeInfos.size) { + 0 -> null + 1 -> nodeInfos[0] + else -> throw IllegalArgumentException("More than one node found with legal name $name") + } + } + override fun getNodesByLegalName(name: CordaX500Name): List = database.transaction { queryByLegalName(session, name) } + override fun getNodesByLegalIdentityKey(identityKey: PublicKey): List = nodesByKeyCache[identityKey] private val nodesByKeyCache = NonInvalidatingCache>(1024, 8, { key -> database.transaction { queryByIdentityKey(session, key) } }) diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt index 979e2d2ef0..a04ce94d6f 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt @@ -12,13 +12,19 @@ import net.corda.core.flows.NotaryError import net.corda.core.flows.NotaryException import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party +import net.corda.core.flows.NotarisationPayload +import net.corda.core.flows.NotarisationRequest import net.corda.core.node.services.NotaryService import net.corda.core.node.services.UniquenessProvider import net.corda.core.schemas.PersistentStateRef import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize +import net.corda.core.transactions.CoreTransaction import net.corda.core.transactions.FilteredTransaction -import net.corda.core.utilities.* +import net.corda.core.utilities.contextLogger +import net.corda.core.utilities.debug +import net.corda.core.utilities.getOrThrow +import net.corda.core.utilities.unwrap import net.corda.node.services.api.ServiceHubInternal import net.corda.node.services.config.BFTSMaRtConfiguration import net.corda.node.utilities.AppendOnlyPersistentMap @@ -67,25 +73,25 @@ class BFTNonValidatingNotaryService( replicaHolder.getOrThrow() // It's enough to wait for the ServiceReplica constructor to return. } - fun commitTransaction(tx: Any, otherSide: Party) = client.commitTransaction(tx, otherSide) + fun commitTransaction(payload: NotarisationPayload, otherSide: Party) = client.commitTransaction(payload, otherSide) override fun createServiceFlow(otherPartySession: FlowSession): FlowLogic = ServiceFlow(otherPartySession, this) private class ServiceFlow(val otherSideSession: FlowSession, val service: BFTNonValidatingNotaryService) : FlowLogic() { @Suspendable override fun call(): Void? { - val stx = otherSideSession.receive().unwrap { it } - val signatures = commit(stx) + val payload = otherSideSession.receive().unwrap { it } + val signatures = commit(payload) otherSideSession.send(signatures) return null } - private fun commit(stx: FilteredTransaction): List { - val response = service.commitTransaction(stx, otherSideSession.counterparty) + private fun commit(payload: NotarisationPayload): List { + val response = service.commitTransaction(payload, otherSideSession.counterparty) when (response) { is BFTSMaRt.ClusterResponse.Error -> throw NotaryException(response.error) is BFTSMaRt.ClusterResponse.Signatures -> { - log.debug("All input states of transaction ${stx.id} have been committed") + log.debug("All input states of transaction ${payload.coreTransaction.id} have been committed") return response.txSignatures } } @@ -132,28 +138,34 @@ class BFTNonValidatingNotaryService( notaryIdentityKey: PublicKey) : BFTSMaRt.Replica(config, replicaId, createMap, services, notaryIdentityKey) { override fun executeCommand(command: ByteArray): ByteArray { - val request = command.deserialize() - val ftx = request.tx as FilteredTransaction - val response = verifyAndCommitTx(ftx, request.callerIdentity) + val commitRequest = command.deserialize() + verifyRequest(commitRequest) + val response = verifyAndCommitTx(commitRequest.payload.coreTransaction, commitRequest.callerIdentity) return response.serialize().bytes } - fun verifyAndCommitTx(ftx: FilteredTransaction, callerIdentity: Party): BFTSMaRt.ReplicaResponse { + private fun verifyAndCommitTx(transaction: CoreTransaction, callerIdentity: Party): BFTSMaRt.ReplicaResponse { return try { - val id = ftx.id - val inputs = ftx.inputs - val notary = ftx.notary - NotaryService.validateTimeWindow(services.clock, ftx.timeWindow) + val id = transaction.id + val inputs = transaction.inputs + val notary = transaction.notary + if (transaction is FilteredTransaction) NotaryService.validateTimeWindow(services.clock, transaction.timeWindow) if (notary !in services.myInfo.legalIdentities) throw NotaryException(NotaryError.WrongNotary) commitInputStates(inputs, id, callerIdentity) log.debug { "Inputs committed successfully, signing $id" } - BFTSMaRt.ReplicaResponse.Signature(sign(ftx)) + BFTSMaRt.ReplicaResponse.Signature(sign(id)) } catch (e: NotaryException) { log.debug { "Error processing transaction: ${e.error}" } BFTSMaRt.ReplicaResponse.Error(e.error) } } + private fun verifyRequest(commitRequest: BFTSMaRt.CommitRequest) { + val transaction = commitRequest.payload.coreTransaction + val notarisationRequest = NotarisationRequest(transaction.inputs, transaction.id) + notarisationRequest.verifySignature(commitRequest.payload.requestSignature, commitRequest.callerIdentity) + // TODO: persist the signature for traceability. + } } override fun start() { diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRt.kt b/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRt.kt index 84ca283b48..899ecde450 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRt.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRt.kt @@ -17,6 +17,7 @@ import net.corda.core.crypto.* import net.corda.core.flows.NotaryError import net.corda.core.flows.NotaryException import net.corda.core.identity.Party +import net.corda.core.flows.NotarisationPayload import net.corda.core.internal.declaredField import net.corda.core.internal.toTypedArray import net.corda.core.node.services.UniquenessProvider @@ -25,8 +26,6 @@ import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize -import net.corda.core.transactions.FilteredTransaction -import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.contextLogger import net.corda.core.utilities.debug import net.corda.node.services.api.ServiceHubInternal @@ -52,7 +51,7 @@ import java.util.* object BFTSMaRt { /** Sent from [Client] to [Replica]. */ @CordaSerializable - data class CommitRequest(val tx: Any, val callerIdentity: Party) + data class CommitRequest(val payload: NotarisationPayload, val callerIdentity: Party) /** Sent from [Replica] to [Client]. */ @CordaSerializable @@ -101,13 +100,12 @@ object BFTSMaRt { * Sends a transaction commit request to the BFT cluster. The [proxy] will deliver the request to every * replica, and block until a sufficient number of replies are received. */ - fun commitTransaction(transaction: Any, otherSide: Party): ClusterResponse { - require(transaction is FilteredTransaction || transaction is SignedTransaction) { "Unsupported transaction type: ${transaction.javaClass.name}" } + fun commitTransaction(payload: NotarisationPayload, otherSide: Party): ClusterResponse { awaitClientConnectionToCluster() cluster.waitUntilAllReplicasHaveInitialized() - val requestBytes = CommitRequest(transaction, otherSide).serialize().bytes + val requestBytes = CommitRequest(payload, otherSide).serialize().bytes val responseBytes = proxy.invokeOrdered(requestBytes) - return responseBytes.deserialize() + return responseBytes.deserialize() } /** A comparator to check if replies from two replicas are the same. */ @@ -242,12 +240,15 @@ object BFTSMaRt { } } + /** Generates a signature over an arbitrary array of bytes. */ protected fun sign(bytes: ByteArray): DigitalSignature.WithKey { return services.database.transaction { services.keyManagementService.sign(bytes, notaryIdentityKey) } } - protected fun sign(filteredTransaction: FilteredTransaction): TransactionSignature { - return services.database.transaction { services.createSignature(filteredTransaction, notaryIdentityKey) } + /** Generates a transaction signature over the specified transaction [txId]. */ + protected fun sign(txId: SecureHash): TransactionSignature { + val signableData = SignableData(txId, SignatureMetadata(services.myInfo.platformVersion, Crypto.findSignatureScheme(notaryIdentityKey).schemeNumberID)) + return services.keyManagementService.sign(signableData, notaryIdentityKey) } // TODO: diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/NonValidatingNotaryFlow.kt b/node/src/main/kotlin/net/corda/node/services/transactions/NonValidatingNotaryFlow.kt index a12d565d52..5eaaa71c52 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/NonValidatingNotaryFlow.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/NonValidatingNotaryFlow.kt @@ -1,11 +1,15 @@ package net.corda.node.services.transactions import co.paralleluniverse.fibers.Suspendable -import net.corda.core.flows.FlowSession import net.corda.core.contracts.ComponentGroupEnum +import net.corda.core.flows.FlowSession import net.corda.core.flows.NotaryFlow import net.corda.core.flows.TransactionParts +import net.corda.core.flows.NotarisationPayload +import net.corda.core.flows.NotarisationRequest +import net.corda.core.internal.validateRequest import net.corda.core.node.services.TrustedAuthorityNotaryService +import net.corda.core.transactions.CoreTransaction import net.corda.core.transactions.FilteredTransaction import net.corda.core.transactions.NotaryChangeWireTransaction import net.corda.core.utilities.unwrap @@ -21,22 +25,30 @@ class NonValidatingNotaryFlow(otherSideSession: FlowSession, service: TrustedAut */ @Suspendable override fun receiveAndVerifyTx(): TransactionParts { - val parts = otherSideSession.receive().unwrap { - when (it) { - is FilteredTransaction -> { - it.verify() - it.checkAllComponentsVisible(ComponentGroupEnum.INPUTS_GROUP) - it.checkAllComponentsVisible(ComponentGroupEnum.TIMEWINDOW_GROUP) - val notary = it.notary - TransactionParts(it.id, it.inputs, it.timeWindow, notary) - } - is NotaryChangeWireTransaction -> TransactionParts(it.id, it.inputs, null, it.notary) - else -> { - throw IllegalArgumentException("Received unexpected transaction type: ${it::class.java.simpleName}," + - "expected either ${FilteredTransaction::class.java.simpleName} or ${NotaryChangeWireTransaction::class.java.simpleName}") + return otherSideSession.receive().unwrap { payload -> + val transaction = payload.coreTransaction + val request = NotarisationRequest(transaction.inputs, transaction.id) + validateRequest(request, payload.requestSignature) + extractParts(transaction) + } + } + + private fun extractParts(tx: CoreTransaction): TransactionParts { + return when (tx) { + is FilteredTransaction -> { + tx.apply { + verify() + checkAllComponentsVisible(ComponentGroupEnum.INPUTS_GROUP) + checkAllComponentsVisible(ComponentGroupEnum.TIMEWINDOW_GROUP) } + val notary = tx.notary + TransactionParts(tx.id, tx.inputs, tx.timeWindow, notary) + } + is NotaryChangeWireTransaction -> TransactionParts(tx.id, tx.inputs, null, tx.notary) + else -> { + throw IllegalArgumentException("Received unexpected transaction type: ${tx::class.java.simpleName}," + + "expected either ${FilteredTransaction::class.java.simpleName} or ${NotaryChangeWireTransaction::class.java.simpleName}") } } - return parts } } \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryFlow.kt b/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryFlow.kt index 8ce7ba6365..907428429c 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryFlow.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/ValidatingNotaryFlow.kt @@ -4,8 +4,14 @@ import co.paralleluniverse.fibers.Suspendable import net.corda.core.contracts.TimeWindow import net.corda.core.contracts.TransactionVerificationException import net.corda.core.flows.* +import net.corda.core.flows.NotarisationPayload +import net.corda.core.flows.NotarisationRequest +import net.corda.core.internal.ResolveTransactionsFlow +import net.corda.core.internal.validateRequest import net.corda.core.node.services.TrustedAuthorityNotaryService +import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionWithSignatures +import net.corda.core.utilities.unwrap import java.security.SignatureException /** @@ -22,15 +28,15 @@ class ValidatingNotaryFlow(otherSideSession: FlowSession, service: TrustedAuthor @Suspendable override fun receiveAndVerifyTx(): TransactionParts { try { - val stx = subFlow(ReceiveTransactionFlow(otherSideSession, checkSufficientSignatures = false)) + val stx = receiveTransaction() val notary = stx.notary checkNotary(notary) val timeWindow: TimeWindow? = if (stx.isNotaryChangeTransaction()) null else stx.tx.timeWindow - val transactionWithSignatures = stx.resolveTransactionWithSignatures(serviceHub) - checkSignatures(transactionWithSignatures) + resolveAndContractVerify(stx) + verifySignatures(stx) return TransactionParts(stx.id, stx.inputs, timeWindow, notary!!) } catch (e: Exception) { throw when (e) { @@ -41,6 +47,26 @@ class ValidatingNotaryFlow(otherSideSession: FlowSession, service: TrustedAuthor } } + @Suspendable + private fun receiveTransaction(): SignedTransaction { + return otherSideSession.receive().unwrap { + val stx = it.signedTransaction + validateRequest(NotarisationRequest(stx.inputs, stx.id), it.requestSignature) + stx + } + } + + @Suspendable + private fun resolveAndContractVerify(stx: SignedTransaction) { + subFlow(ResolveTransactionsFlow(stx, otherSideSession)) + stx.verify(serviceHub, false) + } + + private fun verifySignatures(stx: SignedTransaction) { + val transactionWithSignatures = stx.resolveTransactionWithSignatures(serviceHub) + checkSignatures(transactionWithSignatures) + } + private fun checkSignatures(tx: TransactionWithSignatures) { try { tx.verifySignaturesExcept(service.notaryIdentityKey) diff --git a/node/src/main/kotlin/net/corda/node/utilities/DemoClock.kt b/node/src/main/kotlin/net/corda/node/utilities/DemoClock.kt index 0d6ab6b48c..ac2e82e962 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/DemoClock.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/DemoClock.kt @@ -1,7 +1,7 @@ package net.corda.node.utilities import net.corda.core.internal.until -import net.corda.node.internal.MutableClock +import net.corda.node.MutableClock import java.time.Clock import java.time.LocalDate import javax.annotation.concurrent.ThreadSafe diff --git a/node/src/main/kotlin/net/corda/node/utilities/JVMAgentRegistry.kt b/node/src/main/kotlin/net/corda/node/utilities/JVMAgentRegistry.kt new file mode 100644 index 0000000000..54850dee10 --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/utilities/JVMAgentRegistry.kt @@ -0,0 +1,50 @@ +package net.corda.node.utilities + +import com.ea.agentloader.AgentLoader +import net.corda.core.internal.exists +import net.corda.core.internal.isRegularFile +import java.net.URLClassLoader +import java.nio.file.Path +import java.nio.file.Paths +import java.util.concurrent.ConcurrentHashMap + +/** + * Helper class for loading JVM agents dynamically + */ +object JVMAgentRegistry { + + /** + * Names and options of loaded agents + */ + val loadedAgents = ConcurrentHashMap() + + /** + * Load and attach agent located at given [jar], unless [loadedAgents] + * indicate that one of its instance has been already loaded. + */ + fun attach(agentName: String, options: String, jar: Path) { + loadedAgents.computeIfAbsent(agentName.toLowerCase()) { + AgentLoader.loadAgent(jar.toString(), options) + options + } + } + + /** + * Attempt finding location of jar for given agent by first searching into + * "drivers" directory of [nodeBaseDirectory] and then falling back to + * classpath. Returns null if no match is found. + */ + fun resolveAgentJar(jarFileName: String, driversDir: Path): Path? { + require(jarFileName.endsWith(".jar")) { "jarFileName does not have .jar suffix" } + + val path = Paths.get(driversDir.toString(), jarFileName) + return if (path.exists() && path.isRegularFile()) { + path + } else { + (this::class.java.classLoader as? URLClassLoader) + ?.urLs + ?.map { Paths.get(it.path) } + ?.firstOrNull { it.fileName.toString() == jarFileName } + } + } +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/utilities/NodeBuildProperties.kt b/node/src/main/kotlin/net/corda/node/utilities/NodeBuildProperties.kt new file mode 100644 index 0000000000..5d7d96cf56 --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/utilities/NodeBuildProperties.kt @@ -0,0 +1,27 @@ +package net.corda.node.utilities + +import java.util.* + +/** + * Expose properties defined in top-level 'constants.properties' file. + */ +object NodeBuildProperties { + + // Note: initialization order is important + private val data by lazy { + Properties().apply { + NodeBuildProperties::class.java.getResourceAsStream("/build.properties") + ?.let { load(it) } + } + } + + /** + * Jolokia dependency version + */ + val JOLOKIA_AGENT_VERSION = get("jolokiaAgentVersion") + + /** + * Get property value by name + */ + fun get(key: String): String? = data.getProperty(key) +} \ No newline at end of file diff --git a/node/src/main/resources/build.properties b/node/src/main/resources/build.properties new file mode 100644 index 0000000000..72577915e7 --- /dev/null +++ b/node/src/main/resources/build.properties @@ -0,0 +1,5 @@ +# Build constants exported as resource file to make them visible in Node program +# Note: sadly, due to present limitation of IntelliJ-IDEA in processing resource files, these constants cannot be +# imported from top-level 'constants.properties' file + +jolokiaAgentVersion=1.3.7 \ No newline at end of file diff --git a/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java b/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java index 1591acff24..1846cd0a45 100644 --- a/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java +++ b/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java @@ -81,7 +81,7 @@ public class VaultQueryJavaTests { identitySvc, MEGA_CORP, DUMMY_NOTARY.getKeyPair()); - issuerServices = new MockServices(cordappPackages, rigorousMock(IdentityServiceInternal.class), DUMMY_CASH_ISSUER_INFO, BOC.getKeyPair()); + issuerServices = new MockServices(cordappPackages, DUMMY_CASH_ISSUER_INFO, rigorousMock(IdentityServiceInternal.class), BOC.getKeyPair()); database = databaseAndServices.getFirst(); MockServices services = databaseAndServices.getSecond(); vaultFiller = new VaultFiller(services, DUMMY_NOTARY); diff --git a/node/src/test/kotlin/net/corda/node/internal/AbstractNodeTests.kt b/node/src/test/kotlin/net/corda/node/internal/AbstractNodeTests.kt index 5dc2f28844..c7494b6f2d 100644 --- a/node/src/test/kotlin/net/corda/node/internal/AbstractNodeTests.kt +++ b/node/src/test/kotlin/net/corda/node/internal/AbstractNodeTests.kt @@ -42,12 +42,22 @@ class AbstractNodeTests { @Test fun `H2 fix is applied`() { - repeat(if (relaxedThoroughness) 1 else 100) { - // Two "nodes" seems to be the magic number to reproduce the problem: - val urls = (0 until 2).map { freshURL() } + val pool = Executors.newFixedThreadPool(5) + val runs = if (relaxedThoroughness) 1 else 100 + (0 until runs).map { + // Four "nodes" seems to be the magic number to reproduce the problem on CI: + val urls = (0 until 4).map { freshURL() } // Haven't been able to reproduce in a warm JVM: - assertEquals(0, startJavaProcess(urls).waitFor()) - } + pool.fork { + assertEquals(0, startJavaProcess(urls).waitFor()) + } + }.transpose().getOrThrow() + pool.shutdown() + // The above will always run all processes, even if the very first fails + // In theory this can be handled better, but + // a) we expect to run all the runs, that's how the test passes + // b) it would require relatively complex handling (futures+threads), which is not worth it + // because of a) } } diff --git a/node/src/test/kotlin/net/corda/node/internal/cordapp/CordappProviderImplTests.kt b/node/src/test/kotlin/net/corda/node/internal/cordapp/CordappProviderImplTests.kt index 8526b36bcd..9d12bffb05 100644 --- a/node/src/test/kotlin/net/corda/node/internal/cordapp/CordappProviderImplTests.kt +++ b/node/src/test/kotlin/net/corda/node/internal/cordapp/CordappProviderImplTests.kt @@ -1,15 +1,27 @@ package net.corda.node.internal.cordapp +import com.typesafe.config.Config +import com.typesafe.config.ConfigFactory +import net.corda.core.internal.cordapp.CordappConfigProvider import net.corda.core.node.services.AttachmentStorage +import net.corda.testing.internal.MockCordappConfigProvider import net.corda.testing.services.MockAttachmentStorage +import org.assertj.core.api.Assertions.assertThat import org.junit.Assert import org.junit.Before import org.junit.Test class CordappProviderImplTests { - companion object { - private val isolatedJAR = this::class.java.getResource("isolated.jar")!! - private val emptyJAR = this::class.java.getResource("empty.jar")!! + private companion object { + val isolatedJAR = this::class.java.getResource("isolated.jar")!! + // TODO: Cordapp name should differ from the JAR name + val isolatedCordappName = "isolated" + val emptyJAR = this::class.java.getResource("empty.jar")!! + val validConfig = ConfigFactory.parseString("key=value") + + val stubConfigProvider = object : CordappConfigProvider { + override fun getConfigByName(name: String): Config = ConfigFactory.empty() + } } private lateinit var attachmentStore: AttachmentStorage @@ -22,7 +34,7 @@ class CordappProviderImplTests { @Test fun `isolated jar is loaded into the attachment store`() { val loader = CordappLoader.createDevMode(listOf(isolatedJAR)) - val provider = CordappProviderImpl(loader, attachmentStore) + val provider = CordappProviderImpl(loader, stubConfigProvider, attachmentStore) val maybeAttachmentId = provider.getCordappAttachmentId(provider.cordapps.first()) Assert.assertNotNull(maybeAttachmentId) @@ -32,14 +44,14 @@ class CordappProviderImplTests { @Test fun `empty jar is not loaded into the attachment store`() { val loader = CordappLoader.createDevMode(listOf(emptyJAR)) - val provider = CordappProviderImpl(loader, attachmentStore) + val provider = CordappProviderImpl(loader, stubConfigProvider, attachmentStore) Assert.assertNull(provider.getCordappAttachmentId(provider.cordapps.first())) } @Test fun `test that we find a cordapp class that is loaded into the store`() { val loader = CordappLoader.createDevMode(listOf(isolatedJAR)) - val provider = CordappProviderImpl(loader, attachmentStore) + val provider = CordappProviderImpl(loader, stubConfigProvider, attachmentStore) val className = "net.corda.finance.contracts.isolated.AnotherDummyContract" val expected = provider.cordapps.first() @@ -50,9 +62,9 @@ class CordappProviderImplTests { } @Test - fun `test that we find an attachment for a cordapp contrat class`() { + fun `test that we find an attachment for a cordapp contract class`() { val loader = CordappLoader.createDevMode(listOf(isolatedJAR)) - val provider = CordappProviderImpl(loader, attachmentStore) + val provider = CordappProviderImpl(loader, stubConfigProvider, attachmentStore) val className = "net.corda.finance.contracts.isolated.AnotherDummyContract" val expected = provider.getAppContext(provider.cordapps.first()).attachmentId val actual = provider.getContractAttachmentID(className) @@ -60,4 +72,16 @@ class CordappProviderImplTests { Assert.assertNotNull(actual) Assert.assertEquals(actual!!, expected) } + + @Test + fun `test cordapp configuration`() { + val configProvider = MockCordappConfigProvider() + configProvider.cordappConfigs.put(isolatedCordappName, validConfig) + val loader = CordappLoader.createDevMode(listOf(isolatedJAR)) + val provider = CordappProviderImpl(loader, configProvider, attachmentStore) + + val expected = provider.getAppContext(provider.cordapps.first()).config + + assertThat(expected.getString("key")).isEqualTo("value") + } } diff --git a/node/src/test/kotlin/net/corda/node/internal/cordapp/TypesafeCordappConfigTests.kt b/node/src/test/kotlin/net/corda/node/internal/cordapp/TypesafeCordappConfigTests.kt new file mode 100644 index 0000000000..6a966ea4b0 --- /dev/null +++ b/node/src/test/kotlin/net/corda/node/internal/cordapp/TypesafeCordappConfigTests.kt @@ -0,0 +1,47 @@ +package net.corda.node.internal.cordapp + +import com.typesafe.config.ConfigFactory +import net.corda.core.cordapp.CordappConfigException +import org.junit.Test +import org.assertj.core.api.Assertions.assertThat + +class TypesafeCordappConfigTests { + @Test + fun `test that all value types can be retrieved`() { + val config = ConfigFactory.parseString("string=string\nint=1\nfloat=1.0\ndouble=1.0\nnumber=2\ndouble=1.01\nbool=false") + val cordappConf = TypesafeCordappConfig(config) + + assertThat(cordappConf.get("string")).isEqualTo("string") + assertThat(cordappConf.getString("string")).isEqualTo("string") + assertThat(cordappConf.getInt("int")).isEqualTo(1) + assertThat(cordappConf.getFloat("float")).isEqualTo(1.0F) + assertThat(cordappConf.getDouble("double")).isEqualTo(1.01) + assertThat(cordappConf.getNumber("number")).isEqualTo(2) + assertThat(cordappConf.getBoolean("bool")).isEqualTo(false) + } + + @Test + fun `test a nested path`() { + val config = ConfigFactory.parseString("outer: { inner: string }") + val cordappConf = TypesafeCordappConfig(config) + + assertThat(cordappConf.getString("outer.inner")).isEqualTo("string") + } + + @Test + fun `test exists determines existence and lack of existence correctly`() { + val config = ConfigFactory.parseString("exists=exists") + val cordappConf = TypesafeCordappConfig(config) + + assertThat(cordappConf.exists("exists")).isTrue() + assertThat(cordappConf.exists("notexists")).isFalse() + } + + @Test(expected = CordappConfigException::class) + fun `test that an exception is thrown when trying to access a non-extant field`() { + val config = ConfigFactory.empty() + val cordappConf = TypesafeCordappConfig(config) + + cordappConf.get("anything") + } +} \ No newline at end of file diff --git a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt index 649fb093cf..15de453b0d 100644 --- a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt @@ -103,9 +103,9 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { // We run this in parallel threads to help catch any race conditions that may exist. The other tests // we run in the unit test thread exclusively to speed things up, ensure deterministic results and // allow interruption half way through. - mockNet = InternalMockNetwork(threadPerNode = true, cordappPackages = cordappPackages) + mockNet = InternalMockNetwork(cordappPackages = cordappPackages, threadPerNode = true) val ledgerIdentityService = rigorousMock() - MockServices(cordappPackages, ledgerIdentityService, MEGA_CORP.name).ledger(DUMMY_NOTARY) { + MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) { val notaryNode = mockNet.defaultNotaryNode val aliceNode = mockNet.createPartyNode(ALICE_NAME) val bobNode = mockNet.createPartyNode(BOB_NAME) @@ -155,9 +155,9 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { @Test(expected = InsufficientBalanceException::class) fun `trade cash for commercial paper fails using soft locking`() { - mockNet = InternalMockNetwork(threadPerNode = true, cordappPackages = cordappPackages) + mockNet = InternalMockNetwork(cordappPackages = cordappPackages, threadPerNode = true) val ledgerIdentityService = rigorousMock() - MockServices(cordappPackages, ledgerIdentityService, MEGA_CORP.name).ledger(DUMMY_NOTARY) { + MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) { val notaryNode = mockNet.defaultNotaryNode val aliceNode = mockNet.createPartyNode(ALICE_NAME) val bobNode = mockNet.createPartyNode(BOB_NAME) @@ -215,7 +215,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { fun `shutdown and restore`() { mockNet = InternalMockNetwork(cordappPackages = cordappPackages) val ledgerIdentityService = rigorousMock() - MockServices(cordappPackages, ledgerIdentityService, MEGA_CORP.name).ledger(DUMMY_NOTARY) { + MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) { val notaryNode = mockNet.defaultNotaryNode val aliceNode = mockNet.createPartyNode(ALICE_NAME) var bobNode = mockNet.createPartyNode(BOB_NAME) @@ -508,7 +508,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { fun `dependency with error on buyer side`() { mockNet = InternalMockNetwork(cordappPackages = cordappPackages) val ledgerIdentityService = rigorousMock() - MockServices(cordappPackages, ledgerIdentityService, MEGA_CORP.name).ledger(DUMMY_NOTARY) { + MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) { runWithError(ledgerIdentityService, true, false, "at least one cash input") } } @@ -517,7 +517,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { fun `dependency with error on seller side`() { mockNet = InternalMockNetwork(cordappPackages = cordappPackages) val ledgerIdentityService = rigorousMock() - MockServices(cordappPackages, ledgerIdentityService, MEGA_CORP.name).ledger(DUMMY_NOTARY) { + MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) { runWithError(ledgerIdentityService, false, true, "Issuances have a time-window") } } diff --git a/node/src/test/kotlin/net/corda/node/modes/draining/ScheduledFlowsDrainingModeTest.kt b/node/src/test/kotlin/net/corda/node/modes/draining/ScheduledFlowsDrainingModeTest.kt index 9e85b92863..eed40b383d 100644 --- a/node/src/test/kotlin/net/corda/node/modes/draining/ScheduledFlowsDrainingModeTest.kt +++ b/node/src/test/kotlin/net/corda/node/modes/draining/ScheduledFlowsDrainingModeTest.kt @@ -52,7 +52,7 @@ class ScheduledFlowsDrainingModeTest { @Before fun setup() { - mockNet = InternalMockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.testing.contracts")) + mockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.testing.contracts"), threadPerNode = true) aliceNode = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME)) bobNode = mockNet.createNode(MockNodeParameters(legalName = BOB_NAME)) notary = mockNet.defaultNotaryIdentity diff --git a/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt b/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt index cc1559cf5b..96a3b63a87 100644 --- a/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt @@ -103,7 +103,7 @@ class ScheduledFlowTests { @Before fun setup() { - mockNet = InternalMockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.testing.contracts")) + mockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.testing.contracts"), threadPerNode = true) aliceNode = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME)) bobNode = mockNet.createNode(MockNodeParameters(legalName = BOB_NAME)) notary = mockNet.defaultNotaryIdentity diff --git a/node/src/test/kotlin/net/corda/node/services/identity/InMemoryIdentityServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/identity/InMemoryIdentityServiceTests.kt index d53c128d5b..f0bbfe8085 100644 --- a/node/src/test/kotlin/net/corda/node/services/identity/InMemoryIdentityServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/identity/InMemoryIdentityServiceTests.kt @@ -11,6 +11,8 @@ import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.x509Certificates import net.corda.testing.core.* +import net.corda.testing.internal.DEV_INTERMEDIATE_CA +import net.corda.testing.internal.DEV_ROOT_CA import org.junit.Rule import org.junit.Test import kotlin.test.assertEquals diff --git a/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt index 0210dcbe56..ea6ddba443 100644 --- a/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt @@ -15,6 +15,8 @@ import net.corda.nodeapi.internal.crypto.x509Certificates import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.testing.core.* +import net.corda.testing.internal.DEV_INTERMEDIATE_CA +import net.corda.testing.internal.DEV_ROOT_CA import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import net.corda.testing.node.makeTestIdentityService import org.junit.After diff --git a/node/src/test/kotlin/net/corda/node/services/keys/KMSUtilsTests.kt b/node/src/test/kotlin/net/corda/node/services/keys/KMSUtilsTests.kt index b76caacba4..deebbf3363 100644 --- a/node/src/test/kotlin/net/corda/node/services/keys/KMSUtilsTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/keys/KMSUtilsTests.kt @@ -19,7 +19,7 @@ class KMSUtilsTests { val alice = getTestPartyAndCertificate(ALICE_NAME, aliceKey.public) val cordappPackages = emptyList() val ledgerIdentityService = makeTestIdentityService(alice) - val mockServices = MockServices(cordappPackages, ledgerIdentityService, alice.name, aliceKey) + val mockServices = MockServices(cordappPackages, alice.name, ledgerIdentityService, aliceKey) val wellKnownIdentity = mockServices.myInfo.singleIdentityAndCert() val confidentialIdentity = mockServices.keyManagementService.freshKeyAndCert(wellKnownIdentity, false) val cert = confidentialIdentity.certificate diff --git a/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTest.kt b/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTest.kt index 5ff6c93121..c6fad024f3 100644 --- a/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTest.kt @@ -68,7 +68,7 @@ class ArtemisMessagingTest { doReturn("trustpass").whenever(it).trustStorePassword doReturn("cordacadevpass").whenever(it).keyStorePassword doReturn(NetworkHostAndPort("0.0.0.0", serverPort)).whenever(it).p2pAddress - doReturn("").whenever(it).exportJMXto + doReturn(null).whenever(it).jmxMonitoringHttpPort doReturn(emptyList()).whenever(it).certificateChainCheckPolicies doReturn(5).whenever(it).messageRedeliveryDelaySeconds doReturn(EnterpriseConfiguration(MutualExclusionConfiguration(false, "", 20000, 40000))).whenever(it).enterpriseConfiguration diff --git a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt index 0eb496fa9a..af0f64ed01 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt @@ -7,8 +7,8 @@ import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.BOB_NAME import net.corda.testing.core.getTestPartyAndCertificate 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 org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Test @@ -18,7 +18,7 @@ import kotlin.test.assertNotNull import kotlin.test.assertNull class NetworkMapCacheTest { - private val mockNet = MockNetwork(emptyList()) + private val mockNet = InternalMockNetwork(emptyList()) @After fun teardown() { diff --git a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt index 1edd35db01..6582196e27 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt @@ -7,10 +7,10 @@ import net.corda.core.serialization.serialize import net.corda.core.utilities.seconds import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.BOB_NAME -import net.corda.testing.core.DEV_ROOT_CA import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.driver.PortAllocation +import net.corda.testing.internal.DEV_ROOT_CA import net.corda.testing.internal.TestNodeInfoBuilder import net.corda.testing.internal.createNodeInfoAndSigned import net.corda.testing.internal.signWith @@ -98,7 +98,7 @@ class NetworkMapClientTest { @Test fun `handle parameters update`() { - val nextParameters = testNetworkParameters(emptyList(), epoch = 2) + val nextParameters = testNetworkParameters(epoch = 2) val originalNetworkParameterHash = server.networkParameters.serialize().hash val nextNetworkParameterHash = nextParameters.serialize().hash val description = "Test parameters" diff --git a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapUpdaterTest.kt b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapUpdaterTest.kt index 8609520c73..fd0214955d 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapUpdaterTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapUpdaterTest.kt @@ -28,6 +28,7 @@ import net.corda.nodeapi.internal.network.ParametersUpdate import net.corda.nodeapi.internal.network.verifiedNetworkMapCert import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.core.* +import net.corda.testing.internal.DEV_ROOT_CA import net.corda.testing.internal.TestNodeInfoBuilder import net.corda.testing.internal.createNodeInfoAndSigned import org.assertj.core.api.Assertions.assertThat @@ -200,7 +201,7 @@ class NetworkMapUpdaterTest { val snapshot = paramsFeed.snapshot val updates = paramsFeed.updates.bufferUntilSubscribed() assertEquals(null, snapshot) - val newParameters = testNetworkParameters(emptyList(), epoch = 2) + val newParameters = testNetworkParameters(epoch = 2) val updateDeadline = Instant.now().plus(1, ChronoUnit.DAYS) scheduleParametersUpdate(newParameters, "Test update", updateDeadline) updater.subscribeToNetworkMap() @@ -218,7 +219,7 @@ class NetworkMapUpdaterTest { @Test fun `ack network parameters update`() { - val newParameters = testNetworkParameters(emptyList(), epoch = 314) + val newParameters = testNetworkParameters(epoch = 314) scheduleParametersUpdate(newParameters, "Test update", Instant.MIN) updater.subscribeToNetworkMap() // TODO: Remove sleep in unit test. diff --git a/node/src/test/kotlin/net/corda/node/services/network/NetworkParametersReaderTest.kt b/node/src/test/kotlin/net/corda/node/services/network/NetworkParametersReaderTest.kt index b6b9e107cd..86fba98581 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/NetworkParametersReaderTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/NetworkParametersReaderTest.kt @@ -12,9 +12,9 @@ import net.corda.nodeapi.internal.network.NETWORK_PARAMS_UPDATE_FILE_NAME import net.corda.nodeapi.internal.network.NetworkParametersCopier import net.corda.nodeapi.internal.network.verifiedNetworkMapCert import net.corda.testing.common.internal.testNetworkParameters -import net.corda.testing.core.DEV_ROOT_CA import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.driver.PortAllocation +import net.corda.testing.internal.DEV_ROOT_CA import net.corda.testing.node.internal.network.NetworkMapServer import org.junit.After import org.junit.Before @@ -50,7 +50,7 @@ class NetworkParametersReaderTest { fun `read correct set of parameters from file`() { val fs = Jimfs.newFileSystem(Configuration.unix()) val baseDirectory = fs.getPath("/node").createDirectories() - val oldParameters = testNetworkParameters(emptyList(), epoch = 1) + val oldParameters = testNetworkParameters(epoch = 1) NetworkParametersCopier(oldParameters).install(baseDirectory) NetworkParametersCopier(server.networkParameters, update = true).install(baseDirectory) // Parameters update file. val parameters = NetworkParametersReader(DEV_ROOT_CA.certificate, networkMapClient, baseDirectory).networkParameters diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt b/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt index db0fff5b10..9faa322728 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt @@ -98,9 +98,9 @@ class HibernateConfigurationTest { @Before fun setUp() { val cordappPackages = listOf("net.corda.testing.internal.vault", "net.corda.finance.contracts.asset") - bankServices = MockServices(cordappPackages, rigorousMock(), BOC.name, BOC_KEY) - issuerServices = MockServices(cordappPackages, rigorousMock(), dummyCashIssuer) - notaryServices = MockServices(cordappPackages, rigorousMock(), dummyNotary) + bankServices = MockServices(cordappPackages, BOC.name, rigorousMock(), BOC_KEY) + issuerServices = MockServices(cordappPackages, dummyCashIssuer, rigorousMock()) + notaryServices = MockServices(cordappPackages, dummyNotary, rigorousMock()) notary = notaryServices.myInfo.singleIdentity() val dataSourceProps = makeTestDataSourceProperties() val identityService = rigorousMock().also { mock -> @@ -116,9 +116,9 @@ class HibernateConfigurationTest { hibernateConfig = database.hibernateConfig // `consumeCash` expects we can self-notarise transactions - services = object : MockServices(cordappPackages, rigorousMock().also { + services = object : MockServices(cordappPackages, BOB_NAME, rigorousMock().also { doNothing().whenever(it).justVerifyAndRegisterIdentity(argThat { name == BOB_NAME }) - }, BOB_NAME, generateKeyPair(), dummyNotary.keyPair) { + }, generateKeyPair(), dummyNotary.keyPair) { override val vaultService = NodeVaultService(Clock.systemUTC(), keyManagementService, validatedTransactions, hibernateConfig) override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable) { for (stx in txs) { diff --git a/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt index e3f09668ff..fdbfdef589 100644 --- a/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt @@ -75,8 +75,8 @@ class FlowFrameworkTests { @Before fun start() { mockNet = InternalMockNetwork( - servicePeerAllocationStrategy = RoundRobin(), - cordappPackages = listOf("net.corda.finance.contracts", "net.corda.testing.contracts") + cordappPackages = listOf("net.corda.finance.contracts", "net.corda.testing.contracts"), + servicePeerAllocationStrategy = RoundRobin() ) aliceNode = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME)) bobNode = mockNet.createNode(MockNodeParameters(legalName = BOB_NAME)) diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/MaxTransactionSizeTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/MaxTransactionSizeTests.kt index 7b6a077b8d..6cb8cdf04e 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/MaxTransactionSizeTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/MaxTransactionSizeTests.kt @@ -8,6 +8,7 @@ import net.corda.core.internal.InputStreamAndHash import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.getOrThrow import net.corda.node.services.api.StartedNodeServices +import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.contracts.DummyContract import net.corda.testing.contracts.DummyState import net.corda.testing.core.ALICE_NAME @@ -34,7 +35,7 @@ class MaxTransactionSizeTests { @Before fun setup() { - mockNet = MockNetwork(listOf("net.corda.testing.contracts", "net.corda.node.services.transactions"), maxTransactionSize = 3_000_000) + mockNet = MockNetwork(listOf("net.corda.testing.contracts", "net.corda.node.services.transactions"), networkParameters = testNetworkParameters(maxTransactionSize = 3_000_000)) val aliceNode = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME)) val bobNode = mockNet.createNode(MockNodeParameters(legalName = BOB_NAME)) notaryServices = mockNet.defaultNotaryNode.services diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt index 5cf3319fbb..a769783cf1 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt @@ -3,24 +3,30 @@ package net.corda.node.services.transactions import net.corda.core.concurrent.CordaFuture import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateRef +import net.corda.core.crypto.Crypto +import net.corda.core.crypto.SecureHash import net.corda.core.crypto.TransactionSignature -import net.corda.core.flows.NotaryError -import net.corda.core.flows.NotaryException -import net.corda.core.flows.NotaryFlow +import net.corda.core.crypto.sign +import net.corda.core.flows.* import net.corda.core.identity.Party +import net.corda.core.internal.generateSignature +import net.corda.core.messaging.MessageRecipients import net.corda.core.node.ServiceHub +import net.corda.core.serialization.deserialize +import net.corda.core.serialization.serialize import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder +import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.seconds import net.corda.node.services.api.StartedNodeServices -import net.corda.testing.core.ALICE_NAME +import net.corda.node.services.messaging.Message +import net.corda.node.services.statemachine.InitialSessionMessage import net.corda.testing.contracts.DummyContract +import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.dummyCommand -import net.corda.testing.node.MockNetwork -import net.corda.testing.node.MockNodeParameters import net.corda.testing.core.singleIdentity -import net.corda.testing.node.startFlow +import net.corda.testing.node.* import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Before @@ -34,6 +40,7 @@ import kotlin.test.assertTrue class NotaryServiceTests { private lateinit var mockNet: MockNetwork private lateinit var notaryServices: StartedNodeServices + private lateinit var aliceNode: StartedMockNode private lateinit var aliceServices: StartedNodeServices private lateinit var notary: Party private lateinit var alice: Party @@ -41,7 +48,8 @@ class NotaryServiceTests { @Before fun setup() { mockNet = MockNetwork(cordappPackages = listOf("net.corda.testing.contracts")) - aliceServices = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME)).services + aliceNode = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME)) + aliceServices = aliceNode.services notaryServices = mockNet.defaultNotaryNode.services //TODO get rid of that notary = mockNet.defaultNotaryIdentity alice = aliceServices.myInfo.singleIdentity() @@ -159,6 +167,70 @@ class NotaryServiceTests { notaryError.conflict.verified() } + @Test + fun `should reject when notarisation request not signed by the requesting party`() { + runNotarisationAndInterceptClientPayload { originalPayload -> + val transaction = originalPayload.signedTransaction + val randomKeyPair = Crypto.generateKeyPair() + val bytesToSign = NotarisationRequest(transaction.inputs, transaction.id).serialize().bytes + val modifiedSignature = NotarisationRequestSignature(randomKeyPair.sign(bytesToSign), aliceServices.myInfo.platformVersion) + originalPayload.copy(requestSignature = modifiedSignature) + } + } + + @Test + fun `should reject when incorrect notarisation request signed - inputs don't match`() { + runNotarisationAndInterceptClientPayload { originalPayload -> + val transaction = originalPayload.signedTransaction + val wrongInputs = listOf(StateRef(SecureHash.randomSHA256(), 0)) + val request = NotarisationRequest(wrongInputs, transaction.id) + val modifiedSignature = request.generateSignature(aliceServices) + originalPayload.copy(requestSignature = modifiedSignature) + } + } + + @Test + fun `should reject when incorrect notarisation request signed - transaction id doesn't match`() { + runNotarisationAndInterceptClientPayload { originalPayload -> + val transaction = originalPayload.signedTransaction + val wrongTransactionId = SecureHash.randomSHA256() + val request = NotarisationRequest(transaction.inputs, wrongTransactionId) + val modifiedSignature = request.generateSignature(aliceServices) + originalPayload.copy(requestSignature = modifiedSignature) + } + } + + private fun runNotarisationAndInterceptClientPayload(payloadModifier: (NotarisationPayload) -> NotarisationPayload) { + aliceNode.setMessagingServiceSpy(object : MessagingServiceSpy(aliceNode.network) { + override fun send(message: Message, target: MessageRecipients, retryId: Long?, sequenceKey: Any) { + val messageData = message.data.deserialize() as? InitialSessionMessage + val payload = messageData?.firstPayload!!.deserialize() + + if (payload is NotarisationPayload) { + val alteredPayload = payloadModifier(payload) + val alteredMessageData = messageData.copy(firstPayload = alteredPayload.serialize()) + val alteredMessage = InMemoryMessagingNetwork.InMemoryMessage(message.topic, OpaqueBytes(alteredMessageData.serialize().bytes), message.uniqueMessageId) + messagingService.send(alteredMessage, target, retryId) + + } else { + messagingService.send(message, target, retryId) + } + } + }) + + val stx = run { + val inputState = issueState(aliceServices, alice) + val tx = TransactionBuilder(notary) + .addInputState(inputState) + .addCommand(dummyCommand(alice.owningKey)) + aliceServices.signInitialTransaction(tx) + } + + val future = runNotaryClient(stx) + val ex = assertFailsWith(NotaryException::class) { future.getOrThrow() } + assertThat(ex.error).isInstanceOf(NotaryError.RequestSignatureInvalid::class.java) + } + private fun runNotaryClient(stx: SignedTransaction): CordaFuture> { val flow = NotaryFlow.Client(stx) val future = aliceServices.startFlow(flow) diff --git a/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt index b7ecaa8fbf..d9aa1248af 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt @@ -96,8 +96,8 @@ class NodeVaultServiceTest { vaultFiller = VaultFiller(services, dummyNotary) // This is safe because MockServices only ever have a single identity identity = services.myInfo.singleIdentityAndCert() - issuerServices = MockServices(cordappPackages, rigorousMock(), dummyCashIssuer) - bocServices = MockServices(cordappPackages, rigorousMock(), bankOfCorda) + issuerServices = MockServices(cordappPackages, dummyCashIssuer, rigorousMock()) + bocServices = MockServices(cordappPackages, bankOfCorda, rigorousMock()) services.identityService.verifyAndRegisterIdentity(DUMMY_CASH_ISSUER_IDENTITY) services.identityService.verifyAndRegisterIdentity(BOC_IDENTITY) } @@ -137,7 +137,7 @@ class NodeVaultServiceTest { assertThat(w1).hasSize(3) val originalVault = vaultService - val services2 = object : MockServices(emptyList(), rigorousMock(), MEGA_CORP.name) { + val services2 = object : MockServices(emptyList(), MEGA_CORP.name, rigorousMock()) { override val vaultService: NodeVaultService get() = originalVault override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable) { for (stx in txs) { @@ -480,7 +480,7 @@ class NodeVaultServiceTest { @Test fun addNoteToTransaction() { - val megaCorpServices = MockServices(cordappPackages, rigorousMock(), MEGA_CORP.name, MEGA_CORP_KEY) + val megaCorpServices = MockServices(cordappPackages, MEGA_CORP.name, rigorousMock(), MEGA_CORP_KEY) database.transaction { val freshKey = identity.owningKey @@ -587,9 +587,9 @@ class NodeVaultServiceTest { val identity = services.myInfo.singleIdentityAndCert() assertEquals(services.identityService.partyFromKey(identity.owningKey), identity.party) val anonymousIdentity = services.keyManagementService.freshKeyAndCert(identity, false) - val thirdPartyServices = MockServices(emptyList(), rigorousMock().also { + val thirdPartyServices = MockServices(emptyList(), MEGA_CORP.name, rigorousMock().also { doNothing().whenever(it).justVerifyAndRegisterIdentity(argThat { name == MEGA_CORP.name }) - }, MEGA_CORP.name) + }) val thirdPartyIdentity = thirdPartyServices.keyManagementService.freshKeyAndCert(thirdPartyServices.myInfo.singleIdentityAndCert(), false) val amount = Amount(1000, Issued(BOC.ref(1), GBP)) diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt index cdf19e3e8a..b17e75314f 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt @@ -108,12 +108,12 @@ open class VaultQueryTests { cordappPackages, makeTestIdentityService(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, dummyCashIssuer.identity, dummyNotary.identity), megaCorp, - DUMMY_NOTARY_KEY) + moreKeys = DUMMY_NOTARY_KEY) database = databaseAndServices.first services = databaseAndServices.second vaultFiller = VaultFiller(services, dummyNotary) vaultFillerCashNotary = VaultFiller(services, dummyNotary, CASH_NOTARY) - notaryServices = MockServices(cordappPackages, rigorousMock(), dummyNotary, dummyCashIssuer.keyPair, BOC_KEY, MEGA_CORP_KEY) + notaryServices = MockServices(cordappPackages, dummyNotary, rigorousMock(), dummyCashIssuer.keyPair, BOC_KEY, MEGA_CORP_KEY) identitySvc = services.identityService // Register all of the identities we're going to use (notaryServices.myInfo.legalIdentitiesAndCerts + BOC_IDENTITY + CASH_NOTARY_IDENTITY + MINI_CORP_IDENTITY + MEGA_CORP_IDENTITY).forEach { identity -> @@ -1342,15 +1342,15 @@ open class VaultQueryTests { fun `unconsumed fungible assets for selected issuer parties`() { // GBP issuer val gbpCashIssuerName = CordaX500Name(organisation = "British Pounds Cash Issuer", locality = "London", country = "GB") - val gbpCashIssuerServices = MockServices(cordappPackages, rigorousMock(), gbpCashIssuerName, generateKeyPair()) + val gbpCashIssuerServices = MockServices(cordappPackages, gbpCashIssuerName, rigorousMock(), generateKeyPair()) val gbpCashIssuer = gbpCashIssuerServices.myInfo.singleIdentityAndCert() // USD issuer val usdCashIssuerName = CordaX500Name(organisation = "US Dollars Cash Issuer", locality = "New York", country = "US") - val usdCashIssuerServices = MockServices(cordappPackages, rigorousMock(), usdCashIssuerName, generateKeyPair()) + val usdCashIssuerServices = MockServices(cordappPackages, usdCashIssuerName, rigorousMock(), generateKeyPair()) val usdCashIssuer = usdCashIssuerServices.myInfo.singleIdentityAndCert() // CHF issuer val chfCashIssuerName = CordaX500Name(organisation = "Swiss Francs Cash Issuer", locality = "Zurich", country = "CH") - val chfCashIssuerServices = MockServices(cordappPackages, rigorousMock(), chfCashIssuerName, generateKeyPair()) + val chfCashIssuerServices = MockServices(cordappPackages, chfCashIssuerName, rigorousMock(), generateKeyPair()) val chfCashIssuer = chfCashIssuerServices.myInfo.singleIdentityAndCert() listOf(gbpCashIssuer, usdCashIssuer, chfCashIssuer).forEach { identity -> services.identityService.verifyAndRegisterIdentity(identity) diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt index 02e5e1c1dd..c3b423cfe1 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt @@ -27,7 +27,6 @@ import net.corda.node.internal.InitiatedFlowFactory import net.corda.node.services.api.VaultServiceInternal import net.corda.nodeapi.internal.persistence.HibernateConfiguration import net.corda.testing.core.chooseIdentity -import net.corda.testing.node.MockNetwork import net.corda.testing.internal.rigorousMock import net.corda.testing.node.MockNodeParameters import net.corda.testing.node.internal.InternalMockNetwork diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultWithCashTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultWithCashTest.kt index 0394844cb5..07d5a45fd2 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/VaultWithCashTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultWithCashTest.kt @@ -78,12 +78,12 @@ class VaultWithCashTest { cordappPackages, makeTestIdentityService(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, dummyCashIssuer.identity, dummyNotary.identity), TestIdentity(MEGA_CORP.name, servicesKey), - dummyNotary.keyPair) + moreKeys = dummyNotary.keyPair) database = databaseAndServices.first services = databaseAndServices.second vaultFiller = VaultFiller(services, dummyNotary) - issuerServices = MockServices(cordappPackages, rigorousMock(), dummyCashIssuer, MEGA_CORP_KEY) - notaryServices = MockServices(cordappPackages, rigorousMock(), dummyNotary) + issuerServices = MockServices(cordappPackages, dummyCashIssuer, rigorousMock(), MEGA_CORP_KEY) + notaryServices = MockServices(cordappPackages, dummyNotary, rigorousMock()) notary = notaryServices.myInfo.legalIdentitiesAndCerts.single().party } @@ -113,7 +113,7 @@ class VaultWithCashTest { @Test fun `issue and spend total correctly and irrelevant ignored`() { - val megaCorpServices = MockServices(cordappPackages, rigorousMock(), MEGA_CORP.name, MEGA_CORP_KEY) + val megaCorpServices = MockServices(cordappPackages, MEGA_CORP.name, rigorousMock(), MEGA_CORP_KEY) val freshKey = services.keyManagementService.freshKey() val usefulTX = diff --git a/node/src/test/kotlin/net/corda/node/utilities/ClockUtilsTest.kt b/node/src/test/kotlin/net/corda/node/utilities/ClockUtilsTest.kt index d8dbfac65f..571dee482b 100644 --- a/node/src/test/kotlin/net/corda/node/utilities/ClockUtilsTest.kt +++ b/node/src/test/kotlin/net/corda/node/utilities/ClockUtilsTest.kt @@ -8,8 +8,8 @@ import com.google.common.util.concurrent.SettableFuture import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.hours import net.corda.core.utilities.minutes -import net.corda.node.internal.CordaClock -import net.corda.node.internal.SimpleClock +import net.corda.node.CordaClock +import net.corda.node.SimpleClock import net.corda.node.services.events.NodeSchedulerService import net.corda.testing.node.TestClock import org.junit.After diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/CommercialPaperTests.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/CommercialPaperTests.kt index b1598f056c..e967e99c7e 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/CommercialPaperTests.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/CommercialPaperTests.kt @@ -96,11 +96,11 @@ class CommercialPaperTestsGeneric { @JvmField val testSerialization = SerializationEnvironmentRule() val issuer = MEGA_CORP.ref(123) - private val ledgerServices = MockServices(emptyList(), rigorousMock().also { + private val ledgerServices = MockServices(emptyList(), MEGA_CORP.name, rigorousMock().also { doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY) doReturn(MINI_CORP).whenever(it).partyFromKey(MINI_CORP_PUBKEY) doReturn(null).whenever(it).partyFromKey(ALICE_PUBKEY) - }, MEGA_CORP.name) + }) @Test @@ -240,8 +240,8 @@ class CommercialPaperTestsGeneric { private lateinit var aliceVaultService: VaultService private lateinit var alicesVault: Vault - private val notaryServices = MockServices(emptyList(), rigorousMock(), MEGA_CORP.name, dummyNotary.keyPair) - private val issuerServices = MockServices(emptyList(), rigorousMock(), MEGA_CORP.name, DUMMY_CASH_ISSUER_KEY) + private val notaryServices = MockServices(emptyList(), MEGA_CORP.name, rigorousMock(), dummyNotary.keyPair) + private val issuerServices = MockServices(emptyList(), MEGA_CORP.name, rigorousMock(), DUMMY_CASH_ISSUER_KEY) private lateinit var moveTX: SignedTransaction } diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashTests.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashTests.kt index 2a15abe870..9498154bf4 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashTests.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashTests.kt @@ -137,11 +137,11 @@ class CashTests { @Before fun setUp() { LogHelper.setLevel(NodeVaultService::class) - megaCorpServices = MockServices(listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas"), rigorousMock(), MEGA_CORP.name, MEGA_CORP_KEY) - miniCorpServices = MockServices(listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas"), rigorousMock().also { + megaCorpServices = MockServices(listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas"), MEGA_CORP.name, rigorousMock(), MEGA_CORP_KEY) + miniCorpServices = MockServices(listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas"), MINI_CORP.name, rigorousMock().also { doNothing().whenever(it).justVerifyAndRegisterIdentity(argThat { name == MINI_CORP.name }) - }, MINI_CORP.name, MINI_CORP_KEY) - val notaryServices = MockServices(listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas"), rigorousMock(), DUMMY_NOTARY.name, DUMMY_NOTARY_KEY) + }, MINI_CORP_KEY) + val notaryServices = MockServices(listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas"), DUMMY_NOTARY.name, rigorousMock(), DUMMY_NOTARY_KEY) val databaseAndServices = makeTestDatabaseAndMockServices( listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas"), makeTestIdentityService(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY), @@ -181,12 +181,12 @@ class CashTests { } private fun transaction(script: TransactionDSL.() -> EnforceVerifyOrFail) = run { - MockServices(emptyList(), rigorousMock().also { + MockServices(emptyList(), MEGA_CORP.name, rigorousMock().also { doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY) doReturn(MINI_CORP).whenever(it).partyFromKey(MINI_CORP_PUBKEY) doReturn(null).whenever(it).partyFromKey(ALICE_PUBKEY) doReturn(null).whenever(it).partyFromKey(BOB_PUBKEY) - }, MEGA_CORP.name).transaction(DUMMY_NOTARY, script) + }).transaction(DUMMY_NOTARY, script) } @Test @@ -853,9 +853,9 @@ class CashTests { @Test fun chainCashDoubleSpendFailsWith() { val mockService = MockServices( - listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas"), rigorousMock().also { + listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas"), MEGA_CORP.name, rigorousMock().also { doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY) - }, MEGA_CORP.name, MEGA_CORP_KEY) + }, MEGA_CORP_KEY) mockService.ledger(DUMMY_NOTARY) { unverifiedTransaction { attachment(Cash.PROGRAM_ID) diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashIssueAndPaymentFlowTests.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashIssueAndPaymentFlowTests.kt index 164b4c3da6..6d0c32e2dd 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashIssueAndPaymentFlowTests.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashIssueAndPaymentFlowTests.kt @@ -9,10 +9,10 @@ import net.corda.core.node.services.trackBy import net.corda.core.node.services.vault.QueryCriteria import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.getOrThrow +import net.corda.node.internal.StartedNode import net.corda.testing.core.* import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin -import net.corda.testing.node.MockNetwork -import net.corda.testing.node.StartedMockNode +import net.corda.testing.node.internal.InternalMockNetwork import net.corda.testing.node.startFlow import org.junit.After import org.junit.Before @@ -20,16 +20,16 @@ import org.junit.Test import kotlin.test.assertEquals class CashIssueAndPaymentFlowTests { - private lateinit var mockNet: MockNetwork + private lateinit var mockNet: InternalMockNetwork private val ref = OpaqueBytes.of(0x01) - private lateinit var bankOfCordaNode: StartedMockNode + private lateinit var bankOfCordaNode: StartedNode private lateinit var bankOfCorda: Party - private lateinit var aliceNode: StartedMockNode + private lateinit var aliceNode: StartedNode private lateinit var notary: Party @Before fun start() { - mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin(), + mockNet = InternalMockNetwork(servicePeerAllocationStrategy = RoundRobin(), cordappPackages = listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas")) bankOfCordaNode = mockNet.createPartyNode(BOC_NAME) aliceNode = mockNet.createPartyNode(ALICE_NAME) diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashIssueAndPaymentNoSelectionFlowTest.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashIssueAndPaymentNoSelectionFlowTest.kt index 0c4c9fe028..7207975eac 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashIssueAndPaymentNoSelectionFlowTest.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashIssueAndPaymentNoSelectionFlowTest.kt @@ -9,10 +9,12 @@ import net.corda.core.node.services.trackBy import net.corda.core.node.services.vault.QueryCriteria import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.getOrThrow +import net.corda.node.internal.StartedNode import net.corda.testing.core.* import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin import net.corda.testing.node.MockNetwork import net.corda.testing.node.StartedMockNode +import net.corda.testing.node.internal.InternalMockNetwork import net.corda.testing.node.startFlow import org.junit.After import org.junit.Before @@ -29,16 +31,16 @@ class CashIssueAndPayNoSelectionTests(private val anonymous: Boolean) { fun data() = listOf(false, true) } - private lateinit var mockNet: MockNetwork + private lateinit var mockNet: InternalMockNetwork private val ref = OpaqueBytes.of(0x01) - private lateinit var bankOfCordaNode: StartedMockNode + private lateinit var bankOfCordaNode: StartedNode private lateinit var bankOfCorda: Party - private lateinit var aliceNode: StartedMockNode + private lateinit var aliceNode: StartedNode private lateinit var notary: Party @Before fun start() { - mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin(), + mockNet = InternalMockNetwork(servicePeerAllocationStrategy = RoundRobin(), cordappPackages = listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas")) bankOfCordaNode = mockNet.createPartyNode(BOC_NAME) aliceNode = mockNet.createPartyNode(ALICE_NAME) diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashPaymentFlowTests.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashPaymentFlowTests.kt index 679f17fee5..be49181d35 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashPaymentFlowTests.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashPaymentFlowTests.kt @@ -9,10 +9,10 @@ import net.corda.core.node.services.trackBy import net.corda.core.node.services.vault.QueryCriteria import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.getOrThrow +import net.corda.node.internal.StartedNode import net.corda.testing.core.* import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin -import net.corda.testing.node.MockNetwork -import net.corda.testing.node.StartedMockNode +import net.corda.testing.node.internal.InternalMockNetwork import net.corda.testing.node.startFlow import org.junit.After import org.junit.Before @@ -21,16 +21,16 @@ import kotlin.test.assertEquals import kotlin.test.assertFailsWith class CashPaymentFlowTests { - private lateinit var mockNet: MockNetwork + private lateinit var mockNet: InternalMockNetwork private val initialBalance = 2000.DOLLARS private val ref = OpaqueBytes.of(0x01) - private lateinit var bankOfCordaNode: StartedMockNode + private lateinit var bankOfCordaNode: StartedNode private lateinit var bankOfCorda: Party - private lateinit var aliceNode: StartedMockNode + private lateinit var aliceNode: StartedNode @Before fun start() { - mockNet = MockNetwork( + mockNet = InternalMockNetwork( servicePeerAllocationStrategy = RoundRobin(), cordappPackages = listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas")) bankOfCordaNode = mockNet.createPartyNode(BOC_NAME) diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlowTest.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlowTest.kt index e58fb3ac4d..d8e6c2afc5 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlowTest.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlowTest.kt @@ -117,7 +117,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { // allow interruption half way through. mockNet = InternalMockNetwork(threadPerNode = true, cordappPackages = cordappPackages) val ledgerIdentityService = rigorousMock() - MockServices(cordappPackages, ledgerIdentityService, MEGA_CORP.name).ledger(DUMMY_NOTARY) { + MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) { val notaryNode = mockNet.defaultNotaryNode val aliceNode = mockNet.createPartyNode(ALICE_NAME) val bobNode = mockNet.createPartyNode(BOB_NAME) @@ -170,7 +170,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { fun `trade cash for commercial paper fails using soft locking`() { mockNet = InternalMockNetwork(threadPerNode = true, cordappPackages = cordappPackages) val ledgerIdentityService = rigorousMock() - MockServices(cordappPackages, ledgerIdentityService, MEGA_CORP.name).ledger(DUMMY_NOTARY) { + MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) { val notaryNode = mockNet.defaultNotaryNode val aliceNode = mockNet.createPartyNode(ALICE_NAME) val bobNode = mockNet.createPartyNode(BOB_NAME) @@ -228,7 +228,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { fun `shutdown and restore`() { mockNet = InternalMockNetwork(cordappPackages = cordappPackages) val ledgerIdentityService = rigorousMock() - MockServices(cordappPackages, ledgerIdentityService, MEGA_CORP.name).ledger(DUMMY_NOTARY) { + MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) { val notaryNode = mockNet.defaultNotaryNode val aliceNode = mockNet.createPartyNode(ALICE_NAME) var bobNode = mockNet.createPartyNode(BOB_NAME) @@ -523,7 +523,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { fun `dependency with error on buyer side`() { mockNet = InternalMockNetwork(cordappPackages = cordappPackages) val ledgerIdentityService = rigorousMock() - MockServices(cordappPackages, ledgerIdentityService, MEGA_CORP.name).ledger(DUMMY_NOTARY) { + MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) { runWithError(ledgerIdentityService, true, false, "at least one cash input") } } @@ -532,7 +532,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { fun `dependency with error on seller side`() { mockNet = InternalMockNetwork(cordappPackages = cordappPackages) val ledgerIdentityService = rigorousMock() - MockServices(cordappPackages, ledgerIdentityService, MEGA_CORP.name).ledger(DUMMY_NOTARY) { + MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) { runWithError(ledgerIdentityService, false, true, "Issuances have a time-window") } } diff --git a/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaCordformTest.kt b/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaCordformTest.kt index 43870199c4..88cfdf9855 100644 --- a/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaCordformTest.kt +++ b/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaCordformTest.kt @@ -4,7 +4,7 @@ import net.corda.finance.DOLLARS import net.corda.finance.POUNDS import net.corda.testing.internal.IntegrationTest import net.corda.testing.internal.IntegrationTestSchemas -import net.corda.testing.node.internal.demorun.deployNodesThen +import net.corda.testing.node.internal.demorun.nodeRunner import org.junit.ClassRule import org.junit.Test @@ -16,7 +16,7 @@ class BankOfCordaCordformTest : IntegrationTest() { @Test fun `run demo`() { - BankOfCordaCordform().deployNodesThen { + BankOfCordaCordform().nodeRunner().scanPackages(listOf("net.corda.finance")).deployAndRunNodesThen { IssueCash.requestWebIssue(30000.POUNDS) IssueCash.requestRpcIssue(20000.DOLLARS) } diff --git a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaCordform.kt b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaCordform.kt index 260d37365f..e750504a8f 100644 --- a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaCordform.kt +++ b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaCordform.kt @@ -25,8 +25,8 @@ private const val BOC_RPC_ADMIN_PORT = 10015 private const val BOC_WEB_PORT = 10007 class BankOfCordaCordform : CordformDefinition() { + // TODO: Readd finance dependency - will fail without it init { - cordappPackages += "net.corda.finance" node { name(NOTARY_NAME) notary(NotaryConfig(validating = true)) @@ -65,7 +65,7 @@ class BankOfCordaCordform : CordformDefinition() { object DeployNodes { @JvmStatic fun main(args: Array) { - BankOfCordaCordform().deployNodes() + BankOfCordaCordform().nodeRunner().scanPackages(listOf("net.corda.finance")).deployAndRunNodes() } } diff --git a/samples/cordapp-configuration/README.md b/samples/cordapp-configuration/README.md new file mode 100644 index 0000000000..651e8debc9 --- /dev/null +++ b/samples/cordapp-configuration/README.md @@ -0,0 +1,23 @@ +# Cordapp Configuration Sample + +This sample shows a simple example of how to use per-cordapp configuration. It includes; + +* A configuration file +* Gradle build file to show how to install your Cordapp configuration +* A flow that consumes the Cordapp configuration + +## Usage + +To run the sample you must first build it from the project root with; + + ./gradlew deployNodes + +This will deploy the node with the configuration installed. +The relevant section is the ``deployNodes`` task. + +## Running + +* Windows: `build\nodes\runnodes` +* Mac/Linux: `./build/nodes/runnodes` + +Once the nodes have started up and show a prompt you can now run your flow. \ No newline at end of file diff --git a/samples/cordapp-configuration/build.gradle b/samples/cordapp-configuration/build.gradle new file mode 100644 index 0000000000..37edc09147 --- /dev/null +++ b/samples/cordapp-configuration/build.gradle @@ -0,0 +1,54 @@ +apply plugin: 'kotlin' +apply plugin: 'java' +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'net.corda.plugins.cordformation' + +dependencies { + cordaCompile project(":core") + cordaCompile project(":node-api") + cordaCompile project(path: ":node:capsule", configuration: 'runtimeArtifacts') + cordaCompile project(path: ":webserver:webcapsule", configuration: 'runtimeArtifacts') +} + +task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { + ext.rpcUsers = [['username': "default", 'password': "default", 'permissions': [ 'ALL' ]]] + + directory "./build/nodes" + node { + name "O=Notary Service,L=Zurich,C=CH" + notary = [validating : true] + p2pPort 10002 + rpcSettings { + port 10003 + adminPort 10004 + } + } + node { + name "O=Bank A,L=London,C=GB" + p2pPort 10005 + cordapps = [] + rpcUsers = ext.rpcUsers + // This configures the default cordapp for this node + projectCordapp { + config "someStringValue=test" + } + rpcSettings { + port 10007 + adminPort 10008 + } + } + node { + name "O=Bank B,L=New York,C=US" + p2pPort 10009 + cordapps = [] + rpcUsers = ext.rpcUsers + // This configures the default cordapp for this node + projectCordapp { + config project.file("src/config.conf") + } + rpcSettings { + port 10011 + adminPort 10012 + } + } +} \ No newline at end of file diff --git a/samples/cordapp-configuration/src/config.conf b/samples/cordapp-configuration/src/config.conf new file mode 100644 index 0000000000..5e2d9fdcd6 --- /dev/null +++ b/samples/cordapp-configuration/src/config.conf @@ -0,0 +1,5 @@ +someStringValue=hello world +someIntValue=1 +nested: { + value: a string +} \ No newline at end of file diff --git a/samples/cordapp-configuration/src/main/kotlin/net/corda/configsample/ConfigSampleFlow.kt b/samples/cordapp-configuration/src/main/kotlin/net/corda/configsample/ConfigSampleFlow.kt new file mode 100644 index 0000000000..251830f538 --- /dev/null +++ b/samples/cordapp-configuration/src/main/kotlin/net/corda/configsample/ConfigSampleFlow.kt @@ -0,0 +1,10 @@ +package net.corda.configsample + +import net.corda.core.flows.FlowLogic + +class ConfigSampleFlow : FlowLogic() { + override fun call(): String { + val config = serviceHub.getAppContext().config + return config.getString("someStringValue") + } +} \ No newline at end of file diff --git a/samples/irs-demo/cordapp/build.gradle b/samples/irs-demo/cordapp/build.gradle index 00cf16c846..5ee75f4ee2 100644 --- a/samples/irs-demo/cordapp/build.gradle +++ b/samples/irs-demo/cordapp/build.gradle @@ -95,10 +95,10 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { } node { name "O=Regulator,L=Moscow,C=RU" - p2pPort 10010 + p2pPort 10011 rpcSettings { - port 10009 - adminPort 10029 + port 10012 + adminPort 10032 } cordapps = ["${project.group}:finance:$corda_release_version"] cordapps = ["${project(":finance").group}:finance:$corda_release_version"] diff --git a/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt b/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt index 5509003328..839b2b229d 100644 --- a/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt +++ b/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt @@ -56,7 +56,7 @@ class NodeInterestRatesTest { EURIBOR 2016-03-15 2M = 0.111 """.trimIndent()) private val dummyCashIssuer = TestIdentity(CordaX500Name("Cash issuer", "London", "GB")) - private val services = MockServices(listOf("net.corda.finance.contracts.asset"), rigorousMock(), dummyCashIssuer, MEGA_CORP_KEY) + private val services = MockServices(listOf("net.corda.finance.contracts.asset"), dummyCashIssuer, rigorousMock(), MEGA_CORP_KEY) // This is safe because MockServices only ever have a single identity private val identity = services.myInfo.singleIdentity() diff --git a/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/contract/IRSTests.kt b/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/contract/IRSTests.kt index 7234ee5d72..9dd8a7debf 100644 --- a/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/contract/IRSTests.kt +++ b/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/contract/IRSTests.kt @@ -237,14 +237,14 @@ class IRSTests { @Rule @JvmField val testSerialization = SerializationEnvironmentRule() - private val megaCorpServices = MockServices(listOf("net.corda.irs.contract"), rigorousMock(), MEGA_CORP.name, MEGA_CORP_KEY) - private val miniCorpServices = MockServices(listOf("net.corda.irs.contract"), rigorousMock(), MINI_CORP.name, MINI_CORP_KEY) - private val notaryServices = MockServices(listOf("net.corda.irs.contract"), rigorousMock(), DUMMY_NOTARY.name, DUMMY_NOTARY_KEY) + private val megaCorpServices = MockServices(listOf("net.corda.irs.contract"), MEGA_CORP.name, rigorousMock(), MEGA_CORP_KEY) + private val miniCorpServices = MockServices(listOf("net.corda.irs.contract"), MINI_CORP.name, rigorousMock(), MINI_CORP_KEY) + private val notaryServices = MockServices(listOf("net.corda.irs.contract"), DUMMY_NOTARY.name, rigorousMock(), DUMMY_NOTARY_KEY) private val ledgerServices - get() = MockServices(emptyList(), rigorousMock().also { + get() = MockServices(emptyList(), MEGA_CORP.name, rigorousMock().also { doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY) doReturn(null).whenever(it).partyFromKey(ORACLE_PUBKEY) - }, MEGA_CORP.name) + }) @Test fun ok() { @@ -341,11 +341,12 @@ class IRSTests { */ @Test fun generateIRSandFixSome() { - val services = MockServices(listOf("net.corda.irs.contract"), rigorousMock().also { - listOf(MEGA_CORP, MINI_CORP).forEach { party -> - doReturn(party).whenever(it).partyFromKey(party.owningKey) - } - }, MEGA_CORP.name) + val services = MockServices(listOf("net.corda.irs.contract"), MEGA_CORP.name, + rigorousMock().also { + listOf(MEGA_CORP, MINI_CORP).forEach { party -> + doReturn(party).whenever(it).partyFromKey(party.owningKey) + } + }) var previousTXN = generateIRSTxn(1) previousTXN.toLedgerTransaction(services).verify() services.recordTransactions(previousTXN) @@ -429,13 +430,13 @@ class IRSTests { input("irs post agreement") val postAgreement = "irs post agreement".output() output(IRS_PROGRAM_ID, "irs post first fixing", - postAgreement.copy( - postAgreement.fixedLeg, - postAgreement.floatingLeg, - postAgreement.calculation.applyFixing(ld, FixedRate(RatioUnit(bd))), - postAgreement.common)) + postAgreement.copy( + postAgreement.fixedLeg, + postAgreement.floatingLeg, + postAgreement.calculation.applyFixing(ld, FixedRate(RatioUnit(bd))), + postAgreement.common)) command(ORACLE_PUBKEY, - InterestRateSwap.Commands.Refix(Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd))) + InterestRateSwap.Commands.Refix(Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd))) timeWindow(TEST_TX_TIME) this.verifies() } @@ -620,7 +621,7 @@ class IRSTests { // Templated tweak for reference. A corrent fixing applied should be ok tweak { command(ORACLE_PUBKEY, - InterestRateSwap.Commands.Refix(Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd))) + InterestRateSwap.Commands.Refix(Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd))) timeWindow(TEST_TX_TIME) output(IRS_PROGRAM_ID, newIRS) this.verifies() @@ -643,12 +644,12 @@ class IRSTests { val firstResetValue = newIRS.calculation.floatingLegPaymentSchedule[firstResetKey] val modifiedFirstResetValue = firstResetValue!!.copy(notional = Amount(firstResetValue.notional.quantity, Currency.getInstance("JPY"))) output(IRS_PROGRAM_ID, - newIRS.copy( - newIRS.fixedLeg, - newIRS.floatingLeg, - newIRS.calculation.copy(floatingLegPaymentSchedule = newIRS.calculation.floatingLegPaymentSchedule.plus( - Pair(firstResetKey, modifiedFirstResetValue))), - newIRS.common)) + newIRS.copy( + newIRS.fixedLeg, + newIRS.floatingLeg, + newIRS.calculation.copy(floatingLegPaymentSchedule = newIRS.calculation.floatingLegPaymentSchedule.plus( + Pair(firstResetKey, modifiedFirstResetValue))), + newIRS.common)) this `fails with` "There is only one change in the IRS floating leg payment schedule" } @@ -660,12 +661,12 @@ class IRSTests { val latestReset = newIRS.calculation.floatingLegPaymentSchedule.filter { it.value.rate is FixedRate }.maxBy { it.key } val modifiedLatestResetValue = latestReset!!.value.copy(notional = Amount(latestReset.value.notional.quantity, Currency.getInstance("JPY"))) output(IRS_PROGRAM_ID, - newIRS.copy( - newIRS.fixedLeg, - newIRS.floatingLeg, - newIRS.calculation.copy(floatingLegPaymentSchedule = newIRS.calculation.floatingLegPaymentSchedule.plus( - Pair(latestReset.key, modifiedLatestResetValue))), - newIRS.common)) + newIRS.copy( + newIRS.fixedLeg, + newIRS.floatingLeg, + newIRS.calculation.copy(floatingLegPaymentSchedule = newIRS.calculation.floatingLegPaymentSchedule.plus( + Pair(latestReset.key, modifiedLatestResetValue))), + newIRS.common)) this `fails with` "The fix payment has the same currency as the notional" } } @@ -687,11 +688,11 @@ class IRSTests { transaction("Agreement") { attachments(IRS_PROGRAM_ID) output(IRS_PROGRAM_ID, "irs post agreement1", - irs.copy( - irs.fixedLeg, - irs.floatingLeg, - irs.calculation, - irs.common.copy(tradeID = "t1"))) + irs.copy( + irs.fixedLeg, + irs.floatingLeg, + irs.calculation, + irs.common.copy(tradeID = "t1"))) command(MEGA_CORP_PUBKEY, InterestRateSwap.Commands.Agree()) timeWindow(TEST_TX_TIME) this.verifies() @@ -700,12 +701,12 @@ class IRSTests { transaction("Agreement") { attachments(IRS_PROGRAM_ID) output(IRS_PROGRAM_ID, "irs post agreement2", - irs.copy( - linearId = UniqueIdentifier("t2"), - fixedLeg = irs.fixedLeg, - floatingLeg = irs.floatingLeg, - calculation = irs.calculation, - common = irs.common.copy(tradeID = "t2"))) + irs.copy( + linearId = UniqueIdentifier("t2"), + fixedLeg = irs.fixedLeg, + floatingLeg = irs.floatingLeg, + calculation = irs.calculation, + common = irs.common.copy(tradeID = "t2"))) command(MEGA_CORP_PUBKEY, InterestRateSwap.Commands.Agree()) timeWindow(TEST_TX_TIME) this.verifies() @@ -717,25 +718,23 @@ class IRSTests { input("irs post agreement2") val postAgreement1 = "irs post agreement1".output() output(IRS_PROGRAM_ID, "irs post first fixing1", - postAgreement1.copy( - postAgreement1.fixedLeg, - postAgreement1.floatingLeg, - postAgreement1.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))), - postAgreement1.common.copy(tradeID = "t1"))) + postAgreement1.copy( + postAgreement1.fixedLeg, + postAgreement1.floatingLeg, + postAgreement1.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))), + postAgreement1.common.copy(tradeID = "t1"))) val postAgreement2 = "irs post agreement2".output() output(IRS_PROGRAM_ID, "irs post first fixing2", - postAgreement2.copy( - postAgreement2.fixedLeg, - postAgreement2.floatingLeg, - postAgreement2.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))), - postAgreement2.common.copy(tradeID = "t2"))) + postAgreement2.copy( + postAgreement2.fixedLeg, + postAgreement2.floatingLeg, + postAgreement2.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))), + postAgreement2.common.copy(tradeID = "t2"))) command(ORACLE_PUBKEY, - InterestRateSwap.Commands.Refix(Fix(FixOf("ICE LIBOR", ld1, Tenor("3M")), bd1))) + InterestRateSwap.Commands.Refix(Fix(FixOf("ICE LIBOR", ld1, Tenor("3M")), bd1))) timeWindow(TEST_TX_TIME) this.verifies() } } } -} - - +} \ No newline at end of file diff --git a/samples/irs-demo/src/system-test/kotlin/IRSDemoDockerTest.kt b/samples/irs-demo/src/system-test/kotlin/IRSDemoDockerTest.kt index 5f0769f669..b40e829c9f 100644 --- a/samples/irs-demo/src/system-test/kotlin/IRSDemoDockerTest.kt +++ b/samples/irs-demo/src/system-test/kotlin/IRSDemoDockerTest.kt @@ -19,16 +19,16 @@ import kotlin.test.assertTrue class IRSDemoDockerTest { companion object { - private fun ensureSystemVariable(variable: String) { - if (System.getProperty(variable) == null) { - throw IllegalStateException("System variable $variable not set. Please refer to README file for proper setup instructions.") + private fun ensureProperty(property: String) { + if (System.getProperty(property) == null) { + throw IllegalStateException("System property $property not set. Please refer to README file for proper setup instructions.") } } init { - ensureSystemVariable("CORDAPP_DOCKER_COMPOSE") - ensureSystemVariable("WEB_DOCKER_COMPOSE") - ensureSystemVariable("phantomjs.binary.path") + ensureProperty("CORDAPP_DOCKER_COMPOSE") + ensureProperty("WEB_DOCKER_COMPOSE") + ensureProperty("phantomjs.binary.path") } @ClassRule @@ -72,9 +72,15 @@ class IRSDemoDockerTest { //Wait for deals to appear in a rows table val dealsList = driverWait.until({ + makeScreenshot(driver, "second") it?.findElement(By.cssSelector("table#deal-list tbody tr")) }) assertNotNull(dealsList) } + + private fun makeScreenshot(driver: PhantomJSDriver, name: String) { + val screenshotAs = driver.getScreenshotAs(OutputType.FILE) + Files.copy(screenshotAs.toPath(), Paths.get("/Users", "maksymilianpawlak", "phantomjs", name + System.currentTimeMillis() + ".png"), StandardCopyOption.REPLACE_EXISTING) + } } diff --git a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt index 66ba4e7cbd..4b0a828e9f 100644 --- a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt +++ b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt @@ -71,9 +71,9 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean, } val mockNet = InternalMockNetwork( + cordappPackages = listOf("net.corda.finance.contract", "net.corda.irs"), networkSendManuallyPumped = networkSendManuallyPumped, - threadPerNode = runAsync, - cordappPackages = listOf("net.corda.finance.contract", "net.corda.irs")) + threadPerNode = runAsync) // TODO: Regulatory nodes don't actually exist properly, this is a last minute demo request. // So we just fire a message at a node that doesn't know how to handle it, and it'll ignore it. // But that's fine for visualisation purposes. diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt index 132636f05a..9f5c8802e4 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt @@ -14,7 +14,7 @@ import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.BOB_NAME import java.nio.file.Paths -fun main(args: Array) = BFTNotaryCordform().deployNodes() +fun main(args: Array) = BFTNotaryCordform().nodeRunner().deployAndRunNodes() private val clusterSize = 4 // Minimum size that tolerates a faulty replica. private val notaryNames = createNotaryNames(clusterSize) diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/Clean.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/Clean.kt index 91aa5ba967..2fbba77b26 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/Clean.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/Clean.kt @@ -1,9 +1,9 @@ package net.corda.notarydemo -import net.corda.testing.node.internal.demorun.clean +import net.corda.testing.node.internal.demorun.nodeRunner fun main(args: Array) { - listOf(SingleNotaryCordform(), RaftNotaryCordform(), BFTNotaryCordform()).forEach { + listOf(SingleNotaryCordform(), RaftNotaryCordform(), BFTNotaryCordform()).map { it.nodeRunner() }.forEach { it.clean() } } diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/CustomNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/CustomNotaryCordform.kt index e9a857b1cb..3aa6b38654 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/CustomNotaryCordform.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/CustomNotaryCordform.kt @@ -9,7 +9,7 @@ import net.corda.testing.core.BOB_NAME import net.corda.testing.core.DUMMY_NOTARY_NAME import java.nio.file.Paths -fun main(args: Array) = CustomNotaryCordform().deployNodes() +fun main(args: Array) = CustomNotaryCordform().nodeRunner().deployAndRunNodes() class CustomNotaryCordform : CordformDefinition() { init { diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/MyCustomNotaryService.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/MyCustomNotaryService.kt index 1dd9cda94b..e79c54e5bf 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/MyCustomNotaryService.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/MyCustomNotaryService.kt @@ -4,12 +4,16 @@ import co.paralleluniverse.fibers.Suspendable import net.corda.core.contracts.TimeWindow import net.corda.core.contracts.TransactionVerificationException import net.corda.core.flows.* +import net.corda.core.flows.NotarisationPayload +import net.corda.core.flows.NotarisationRequest +import net.corda.core.internal.ResolveTransactionsFlow +import net.corda.core.internal.validateRequest import net.corda.core.node.AppServiceHub import net.corda.core.node.services.CordaService -import net.corda.core.node.services.TimeWindowChecker import net.corda.core.node.services.TrustedAuthorityNotaryService -import net.corda.core.transactions.LedgerTransaction +import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionWithSignatures +import net.corda.core.utilities.unwrap import net.corda.node.services.transactions.PersistentUniquenessProvider import java.security.PublicKey import java.security.SignatureException @@ -17,7 +21,8 @@ import java.security.SignatureException /** * A custom notary service should provide a constructor that accepts two parameters of types [AppServiceHub] and [PublicKey]. * - * Note that at present only a single-node notary service can be customised. + * Note that the support for custom notaries is still experimental – at present only a single-node notary service can be customised. + * The notary-related APIs might change in the future. */ // START 1 @CordaService @@ -41,19 +46,15 @@ class MyValidatingNotaryFlow(otherSide: FlowSession, service: MyCustomValidating @Suspendable override fun receiveAndVerifyTx(): TransactionParts { try { - val stx = subFlow(ReceiveTransactionFlow(otherSideSession, checkSufficientSignatures = false)) + val stx = receiveTransaction() val notary = stx.notary checkNotary(notary) - var timeWindow: TimeWindow? = null - val transactionWithSignatures = if (stx.isNotaryChangeTransaction()) { - stx.resolveNotaryChangeTransaction(serviceHub) - } else { - val wtx = stx.tx - customVerify(wtx.toLedgerTransaction(serviceHub)) - timeWindow = wtx.timeWindow - stx - } - checkSignatures(transactionWithSignatures) + val timeWindow: TimeWindow? = if (stx.isNotaryChangeTransaction()) + null + else + stx.tx.timeWindow + resolveAndContractVerify(stx) + verifySignatures(stx) return TransactionParts(stx.id, stx.inputs, timeWindow, notary!!) } catch (e: Exception) { throw when (e) { @@ -64,8 +65,25 @@ class MyValidatingNotaryFlow(otherSide: FlowSession, service: MyCustomValidating } } - private fun customVerify(transaction: LedgerTransaction) { - // Add custom verification logic + @Suspendable + private fun receiveTransaction(): SignedTransaction { + return otherSideSession.receive().unwrap { + val stx = it.signedTransaction + validateRequest(NotarisationRequest(stx.inputs, stx.id), it.requestSignature) + stx + } + } + + @Suspendable + private fun resolveAndContractVerify(stx: SignedTransaction) { + subFlow(ResolveTransactionsFlow(stx, otherSideSession)) + stx.verify(serviceHub, false) + customVerify(stx) + } + + private fun verifySignatures(stx: SignedTransaction) { + val transactionWithSignatures = stx.resolveTransactionWithSignatures(serviceHub) + checkSignatures(transactionWithSignatures) } private fun checkSignatures(tx: TransactionWithSignatures) { @@ -75,5 +93,9 @@ class MyValidatingNotaryFlow(otherSide: FlowSession, service: MyCustomValidating throw NotaryException(NotaryError.TransactionInvalid(e)) } } + + private fun customVerify(stx: SignedTransaction) { + // Add custom verification logic + } } // END 2 diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt index 079b06fa55..2a843d6bd5 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt @@ -13,7 +13,7 @@ import net.corda.testing.core.BOB_NAME import net.corda.testing.node.internal.demorun.* import java.nio.file.Paths -fun main(args: Array) = RaftNotaryCordform().deployNodes() +fun main(args: Array) = RaftNotaryCordform().nodeRunner().deployAndRunNodes() internal fun createNotaryNames(clusterSize: Int) = (0 until clusterSize).map { CordaX500Name("Notary Service $it", "Zurich", "CH") } diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/SingleNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/SingleNotaryCordform.kt index af449ce5cb..d5acdb135c 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/SingleNotaryCordform.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/SingleNotaryCordform.kt @@ -11,7 +11,7 @@ import net.corda.testing.node.User import net.corda.testing.node.internal.demorun.* import java.nio.file.Paths -fun main(args: Array) = SingleNotaryCordform().deployNodes() +fun main(args: Array) = SingleNotaryCordform().nodeRunner().deployAndRunNodes() val notaryDemoUser = User("demou", "demop", setOf(all())) diff --git a/samples/simm-valuation-demo/README.md b/samples/simm-valuation-demo/README.md index 8237872b33..b6160dc5d5 100644 --- a/samples/simm-valuation-demo/README.md +++ b/samples/simm-valuation-demo/README.md @@ -47,8 +47,15 @@ Initial Margin Agreement Process - Agree on the calculation of the above with the other party - In practice, pay (or receive) this margin (omitted for the sake of complexity for this example) -## Demo execution (step by step) +## Requirements +This document assumes you have cURL (curl) installed and ready to use. It is usually installed by default in many Linux +distributions and MacOS. +On Windows, there are numerous ways of installation, including [Cygwin](https://www.cygwin.com), [official distribution](https://curl.haxx.se), +package managers like [Chocolatey](https://chocolatey.org), [NuGet](https://www.nuget.org/), or [Windows Linux subsystem](https://docs.microsoft.com/en-us/windows/wsl/about). +Please refer to installation documents of your chosen source. + +## Demo execution (step by step) **Setting up the Corda infrastructure** @@ -66,7 +73,7 @@ To run from the command line in Windows: From the command line run - curl http://localhost:10005/api/simmvaluationdemo/whoami + curl http://localhost:10005/api/simmvaluationdemo/whoami The response should be something like diff --git a/samples/simm-valuation-demo/build.gradle b/samples/simm-valuation-demo/build.gradle index a8ee9a50ce..0637662f55 100644 --- a/samples/simm-valuation-demo/build.gradle +++ b/samples/simm-valuation-demo/build.gradle @@ -71,7 +71,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { name "O=Notary Service,L=Zurich,C=CH" notary = [validating : true] p2pPort 10002 - cordapps = ["$project.group:finance:$corda_release_version"] + cordapp project(':finance') extraConfig = [ jvmArgs : [ "-Xmx1g"] ] @@ -84,7 +84,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { address("localhost:10016") adminAddress("localhost:10017") } - cordapps = ["$project.group:finance:$corda_release_version"] + cordapp project(':finance') rpcUsers = ext.rpcUsers extraConfig = [ jvmArgs : [ "-Xmx1g"] @@ -98,7 +98,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { address("localhost:10026") adminAddress("localhost:10027") } - cordapps = ["$project.group:finance:$corda_release_version"] + cordapp project(':finance') rpcUsers = ext.rpcUsers extraConfig = [ jvmArgs : [ "-Xmx1g"] @@ -112,7 +112,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { address("localhost:10036") adminAddress("localhost:10037") } - cordapps = ["$project.group:finance:$corda_release_version"] + cordapp project(':finance') rpcUsers = ext.rpcUsers extraConfig = [ jvmArgs : [ "-Xmx1g"] diff --git a/samples/trader-demo/src/test/kotlin/net/corda/traderdemo/TransactionGraphSearchTests.kt b/samples/trader-demo/src/test/kotlin/net/corda/traderdemo/TransactionGraphSearchTests.kt index a7dc44fb28..b501cb7d52 100644 --- a/samples/trader-demo/src/test/kotlin/net/corda/traderdemo/TransactionGraphSearchTests.kt +++ b/samples/trader-demo/src/test/kotlin/net/corda/traderdemo/TransactionGraphSearchTests.kt @@ -46,8 +46,8 @@ class TransactionGraphSearchTests { * @param signer signer for the two transactions and their commands. */ fun buildTransactions(command: CommandData): GraphTransactionStorage { - val megaCorpServices = MockServices(listOf("net.corda.testing.contracts"), rigorousMock(), megaCorp) - val notaryServices = MockServices(listOf("net.corda.testing.contracts"), rigorousMock(), dummyNotary) + val megaCorpServices = MockServices(listOf("net.corda.testing.contracts"), megaCorp, rigorousMock()) + val notaryServices = MockServices(listOf("net.corda.testing.contracts"), dummyNotary, rigorousMock()) val originBuilder = TransactionBuilder(dummyNotary.party) .addOutputState(DummyState(random31BitValue()), DummyContract.PROGRAM_ID) .addCommand(command, megaCorp.publicKey) diff --git a/settings.gradle b/settings.gradle index f9a1119927..5e68e1d854 100644 --- a/settings.gradle +++ b/settings.gradle @@ -16,6 +16,7 @@ include 'client:rpc' include 'webserver' include 'webserver:webcapsule' include 'experimental' +include 'experimental:behave' include 'experimental:sandbox' include 'experimental:quasar-hook' include 'experimental:kryo-hook' @@ -56,6 +57,7 @@ include 'samples:simm-valuation-demo' include 'samples:notary-demo' include 'samples:bank-of-corda-demo' include 'samples:business-network-demo' +include 'samples:cordapp-configuration' include 'cordform-common' include 'verify-enclave' include 'hsm-tool' diff --git a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt index f937da9a40..eec10bdd89 100644 --- a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt +++ b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt @@ -101,10 +101,11 @@ class DriverTests : IntegrationTest() { @Test fun `monitoring mode enables jolokia exporting of JMX metrics via HTTP JSON`() { - driver(DriverParameters(jmxPolicy = JmxPolicy(true))) { + driver(DriverParameters(startNodesInProcess = false, jmxPolicy = JmxPolicy(true))) { // start another node so we gain access to node JMX metrics - startNode(providedName = DUMMY_REGULATOR_NAME).getOrThrow() val webAddress = NetworkHostAndPort("localhost", 7006) + startNode(providedName = DUMMY_REGULATOR_NAME, + customOverrides = mapOf("jmxMonitoringHttpPort" to webAddress.port)).getOrThrow() // request access to some JMX metrics via Jolokia HTTP/JSON val api = HttpApi.fromHostAndPort(webAddress, "/jolokia/") val versionAsJson = api.getJson("/jolokia/version/") diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt index 77329bb765..aaa54796e0 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -102,13 +102,13 @@ data class NodeParameters( val maximumHeapSize: String = "200m", val logLevel: String? = null ) { - fun setProvidedName(providedName: CordaX500Name?): NodeParameters = copy(providedName = providedName) - fun setRpcUsers(rpcUsers: List): NodeParameters = copy(rpcUsers = rpcUsers) - fun setVerifierType(verifierType: VerifierType): NodeParameters = copy(verifierType = verifierType) - fun setCustomerOverrides(customOverrides: Map): NodeParameters = copy(customOverrides = customOverrides) - fun setStartInSameProcess(startInSameProcess: Boolean?): NodeParameters = copy(startInSameProcess = startInSameProcess) - fun setMaximumHeapSize(maximumHeapSize: String): NodeParameters = copy(maximumHeapSize = maximumHeapSize) - fun setLogLevel(logLevel: String?): NodeParameters = copy(logLevel = logLevel) + fun withProvidedName(providedName: CordaX500Name?): NodeParameters = copy(providedName = providedName) + fun withRpcUsers(rpcUsers: List): NodeParameters = copy(rpcUsers = rpcUsers) + fun withVerifierType(verifierType: VerifierType): NodeParameters = copy(verifierType = verifierType) + fun withCustomerOverrides(customOverrides: Map): NodeParameters = copy(customOverrides = customOverrides) + fun withStartInSameProcess(startInSameProcess: Boolean?): NodeParameters = copy(startInSameProcess = startInSameProcess) + fun withMaximumHeapSize(maximumHeapSize: String): NodeParameters = copy(maximumHeapSize = maximumHeapSize) + fun withLogLevel(logLevel: String?): NodeParameters = copy(logLevel = logLevel) } /** @@ -199,17 +199,17 @@ data class DriverParameters( val jmxPolicy: JmxPolicy = JmxPolicy(), val networkParameters: NetworkParameters = testNetworkParameters(notaries = emptyList()) ) { - fun setIsDebug(isDebug: Boolean): DriverParameters = copy(isDebug = isDebug) - fun setDriverDirectory(driverDirectory: Path): DriverParameters = copy(driverDirectory = driverDirectory) - fun setPortAllocation(portAllocation: PortAllocation): DriverParameters = copy(portAllocation = portAllocation) - fun setDebugPortAllocation(debugPortAllocation: PortAllocation): DriverParameters = copy(debugPortAllocation = debugPortAllocation) - fun setSystemProperties(systemProperties: Map): DriverParameters = copy(systemProperties = systemProperties) - fun setUseTestClock(useTestClock: Boolean): DriverParameters = copy(useTestClock = useTestClock) - fun setInitialiseSerialization(initialiseSerialization: Boolean): DriverParameters = copy(initialiseSerialization = initialiseSerialization) - fun setStartNodesInProcess(startNodesInProcess: Boolean): DriverParameters = copy(startNodesInProcess = startNodesInProcess) - fun setWaitForAllNodesToFinish(waitForAllNodesToFinish: Boolean): DriverParameters = copy(waitForAllNodesToFinish = waitForAllNodesToFinish) - fun setNotarySpecs(notarySpecs: List): DriverParameters = copy(notarySpecs = notarySpecs) - fun setExtraCordappPackagesToScan(extraCordappPackagesToScan: List): DriverParameters = copy(extraCordappPackagesToScan = extraCordappPackagesToScan) - fun setJmxPolicy(jmxPolicy: JmxPolicy): DriverParameters = copy(jmxPolicy = jmxPolicy) - fun setNetworkParameters(networkParameters: NetworkParameters): DriverParameters = copy(networkParameters = networkParameters) + fun withIsDebug(isDebug: Boolean): DriverParameters = copy(isDebug = isDebug) + fun withDriverDirectory(driverDirectory: Path): DriverParameters = copy(driverDirectory = driverDirectory) + fun withPortAllocation(portAllocation: PortAllocation): DriverParameters = copy(portAllocation = portAllocation) + fun withDebugPortAllocation(debugPortAllocation: PortAllocation): DriverParameters = copy(debugPortAllocation = debugPortAllocation) + fun withSystemProperties(systemProperties: Map): DriverParameters = copy(systemProperties = systemProperties) + fun withUseTestClock(useTestClock: Boolean): DriverParameters = copy(useTestClock = useTestClock) + fun withInitialiseSerialization(initialiseSerialization: Boolean): DriverParameters = copy(initialiseSerialization = initialiseSerialization) + fun withStartNodesInProcess(startNodesInProcess: Boolean): DriverParameters = copy(startNodesInProcess = startNodesInProcess) + fun withWaitForAllNodesToFinish(waitForAllNodesToFinish: Boolean): DriverParameters = copy(waitForAllNodesToFinish = waitForAllNodesToFinish) + fun withNotarySpecs(notarySpecs: List): DriverParameters = copy(notarySpecs = notarySpecs) + fun withExtraCordappPackagesToScan(extraCordappPackagesToScan: List): DriverParameters = copy(extraCordappPackagesToScan = extraCordappPackagesToScan) + fun withJmxPolicy(jmxPolicy: JmxPolicy): DriverParameters = copy(jmxPolicy = jmxPolicy) + fun withNetworkParameters(networkParameters: NetworkParameters): DriverParameters = copy(networkParameters = networkParameters) } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt index 404742d5e6..5ee3dc18e1 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt @@ -1,5 +1,6 @@ package net.corda.testing.node +import net.corda.core.CordaInternal import net.corda.core.DoNotImplement import net.corda.core.crypto.CompositeKey import net.corda.core.identity.CordaX500Name @@ -46,7 +47,7 @@ import kotlin.concurrent.thread * a service is addressed. */ @ThreadSafe -class InMemoryMessagingNetwork internal constructor( +class InMemoryMessagingNetwork private constructor( private val sendManuallyPumped: Boolean, private val servicePeerAllocationStrategy: ServicePeerAllocationStrategy = InMemoryMessagingNetwork.ServicePeerAllocationStrategy.Random(), private val messagesInFlight: ReusableLatch = ReusableLatch() @@ -54,6 +55,13 @@ class InMemoryMessagingNetwork internal constructor( companion object { private const val MESSAGES_LOG_NAME = "messages" private val log = LoggerFactory.getLogger(MESSAGES_LOG_NAME) + + internal fun create( + sendManuallyPumped: Boolean, + servicePeerAllocationStrategy: ServicePeerAllocationStrategy = InMemoryMessagingNetwork.ServicePeerAllocationStrategy.Random(), + messagesInFlight: ReusableLatch = ReusableLatch()): InMemoryMessagingNetwork { + return InMemoryMessagingNetwork(sendManuallyPumped, servicePeerAllocationStrategy, messagesInFlight) + } } private var counter = 0 // -1 means stopped. @@ -113,7 +121,8 @@ class InMemoryMessagingNetwork internal constructor( val peerHandle = PeerHandle(id, description) peersMapping[peerHandle.description] = peerHandle // Assume that the same name - the same entity in MockNetwork. notaryService?.let { if (it.owningKey !is CompositeKey) peersMapping[it.name] = peerHandle } - val serviceHandles = notaryService?.let { listOf(ServiceHandle(it.party)) } ?: emptyList() //TODO only notary can be distributed? + val serviceHandles = notaryService?.let { listOf(ServiceHandle(it.party)) } + ?: emptyList() //TODO only notary can be distributed? synchronized(this) { val node = InMemoryMessaging(manuallyPumped, peerHandle, executor, database) handleEndpointMap[peerHandle] = node @@ -181,7 +190,8 @@ class InMemoryMessagingNetwork internal constructor( } /** - * Mock service loadbalancing + * How traffic is allocated in the case where multiple nodes share a single identity, which happens for notaries + * in a cluster. You don't normally ever need to change this: it is mostly useful for testing notary implementations. */ @DoNotImplement sealed class ServicePeerAllocationStrategy { @@ -305,7 +315,8 @@ class InMemoryMessagingNetwork internal constructor( override fun getAddressOfParty(partyInfo: PartyInfo): MessageRecipients { return when (partyInfo) { - is PartyInfo.SingleNode -> peersMapping[partyInfo.party.name] ?: throw IllegalArgumentException("No StartedMockNode for party ${partyInfo.party.name}") + is PartyInfo.SingleNode -> peersMapping[partyInfo.party.name] + ?: throw IllegalArgumentException("No StartedMockNode for party ${partyInfo.party.name}") is PartyInfo.DistributedNode -> ServiceHandle(partyInfo.party) } } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetwork.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetwork.kt index 070fec8ef9..23d2f41fd2 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetwork.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetwork.kt @@ -6,13 +6,14 @@ import net.corda.core.crypto.random63BitValue import net.corda.core.flows.FlowLogic import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party +import net.corda.core.node.NetworkParameters import net.corda.core.node.NodeInfo import net.corda.node.VersionInfo import net.corda.node.internal.StartedNode import net.corda.node.services.api.StartedNodeServices import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.messaging.MessagingService -import net.corda.nodeapi.internal.persistence.CordaPersistence +import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.core.DUMMY_NOTARY_NAME import net.corda.testing.node.internal.InternalMockNetwork import net.corda.testing.node.internal.setMessagingServiceSpy @@ -37,35 +38,51 @@ data class MockNodeParameters( val entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), val configOverrides: (NodeConfiguration) -> Any? = {}, val version: VersionInfo = MockServices.MOCK_VERSION_INFO) { - fun setForcedID(forcedID: Int?): MockNodeParameters = copy(forcedID = forcedID) - fun setLegalName(legalName: CordaX500Name?): MockNodeParameters = copy(legalName = legalName) - fun setEntropyRoot(entropyRoot: BigInteger): MockNodeParameters = copy(entropyRoot = entropyRoot) - fun setConfigOverrides(configOverrides: (NodeConfiguration) -> Any?): MockNodeParameters = copy(configOverrides = configOverrides) + fun withForcedID(forcedID: Int?): MockNodeParameters = copy(forcedID = forcedID) + fun withLegalName(legalName: CordaX500Name?): MockNodeParameters = copy(legalName = legalName) + fun withEntropyRoot(entropyRoot: BigInteger): MockNodeParameters = copy(entropyRoot = entropyRoot) + fun withConfigOverrides(configOverrides: (NodeConfiguration) -> Any?): MockNodeParameters = copy(configOverrides = configOverrides) } -/** Helper builder for configuring a [InternalMockNetwork] from Java. */ +/** + * Immutable builder for configuring a [MockNetwork]. Kotlin users can also use named parameters to the constructor + * of [MockNetwork], which is more convenient. + * + * @property networkSendManuallyPumped If true then messages will not be routed from sender to receiver until you use + * the [MockNetwork.runNetwork] method. This is useful for writing single-threaded unit test code that can examine the + * state of the mock network before and after a message is sent, without races and without the receiving node immediately + * sending a response. The default is false, so you must call runNetwork. + * @property threadPerNode If true then each node will be run in its own thread. This can result in race conditions in + * your code if not carefully written, but is more realistic and may help if you have flows in your app that do long + * blocking operations. The default is false. + * @property servicePeerAllocationStrategy How messages are load balanced in the case where a single compound identity + * is used by multiple nodes. You rarely if ever need to change that, it's primarily of interest to people testing + * notary code. + * @property notarySpecs The notaries to use in the mock network. By default you get one mock notary and that is usually sufficient. + * @property networkParameters The network parameters to be used by all the nodes. [NetworkParameters.notaries] must be + * empty as notaries are defined by [notarySpecs]. + */ @Suppress("unused") data class MockNetworkParameters( val networkSendManuallyPumped: Boolean = false, val threadPerNode: Boolean = false, val servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = InMemoryMessagingNetwork.ServicePeerAllocationStrategy.Random(), - val initialiseSerialization: Boolean = true, val notarySpecs: List = listOf(MockNetworkNotarySpec(DUMMY_NOTARY_NAME)), - val maxTransactionSize: Int = Int.MAX_VALUE) { - fun setNetworkSendManuallyPumped(networkSendManuallyPumped: Boolean): MockNetworkParameters = copy(networkSendManuallyPumped = networkSendManuallyPumped) - fun setThreadPerNode(threadPerNode: Boolean): MockNetworkParameters = copy(threadPerNode = threadPerNode) - fun setServicePeerAllocationStrategy(servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy): MockNetworkParameters = copy(servicePeerAllocationStrategy = servicePeerAllocationStrategy) - fun setInitialiseSerialization(initialiseSerialization: Boolean): MockNetworkParameters = copy(initialiseSerialization = initialiseSerialization) - fun setNotarySpecs(notarySpecs: List): MockNetworkParameters = copy(notarySpecs = notarySpecs) - fun setMaxTransactionSize(maxTransactionSize: Int): MockNetworkParameters = copy(maxTransactionSize = maxTransactionSize) + val networkParameters: NetworkParameters = testNetworkParameters() +) { + fun withNetworkParameters(networkParameters: NetworkParameters): MockNetworkParameters = copy(networkParameters = networkParameters) + fun withNetworkSendManuallyPumped(networkSendManuallyPumped: Boolean): MockNetworkParameters = copy(networkSendManuallyPumped = networkSendManuallyPumped) + fun withThreadPerNode(threadPerNode: Boolean): MockNetworkParameters = copy(threadPerNode = threadPerNode) + fun withServicePeerAllocationStrategy(servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy): MockNetworkParameters = copy(servicePeerAllocationStrategy = servicePeerAllocationStrategy) + fun withNotarySpecs(notarySpecs: List): MockNetworkParameters = copy(notarySpecs = notarySpecs) } -/** Represents a node configuration for injection via [MockNetworkParameters] **/ +/** Represents a node configuration for injection via [MockNetworkParameters]. */ data class MockNetworkNotarySpec(val name: CordaX500Name, val validating: Boolean = true) { constructor(name: CordaX500Name) : this(name, validating = true) } -/** A class that represents an unstarted mock node for testing. **/ +/** A class that represents an unstarted mock node for testing. */ class UnstartedMockNode private constructor(private val node: InternalMockNetwork.MockNode) { companion object { internal fun create(node: InternalMockNetwork.MockNode): UnstartedMockNode { @@ -78,7 +95,7 @@ class UnstartedMockNode private constructor(private val node: InternalMockNetwor fun start() = StartedMockNode.create(node.start()) } -/** A class that represents a started mock node for testing. **/ +/** A class that represents a started mock node for testing. */ class StartedMockNode private constructor(private val node: StartedNode) { companion object { internal fun create(node: StartedNode): StartedMockNode { @@ -87,7 +104,6 @@ class StartedMockNode private constructor(private val node: StartedNode> findStateMachines(flowClass: Class): List>> = node.smm.findStateMachines(flowClass) + + fun transaction(statement: () -> T): T { + return node.database.transaction { + statement() + } + } } /** @@ -117,9 +139,14 @@ class StartedMockNode private constructor(private val node: StartedNode, val defaultParameters: MockNetworkParameters = MockNetworkParameters(), val networkSendManuallyPumped: Boolean = defaultParameters.networkSendManuallyPumped, val threadPerNode: Boolean = defaultParameters.threadPerNode, val servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = defaultParameters.servicePeerAllocationStrategy, - val initialiseSerialization: Boolean = defaultParameters.initialiseSerialization, val notarySpecs: List = defaultParameters.notarySpecs, - val maxTransactionSize: Int = defaultParameters.maxTransactionSize) { + val networkParameters: NetworkParameters = defaultParameters.networkParameters) { @JvmOverloads constructor(cordappPackages: List, parameters: MockNetworkParameters = MockNetworkParameters()) : this(cordappPackages, defaultParameters = parameters) - private val internalMockNetwork: InternalMockNetwork = InternalMockNetwork(cordappPackages, defaultParameters, networkSendManuallyPumped, threadPerNode, servicePeerAllocationStrategy, initialiseSerialization, notarySpecs, maxTransactionSize) - val defaultNotaryNode get() : StartedMockNode = StartedMockNode.create(internalMockNetwork.defaultNotaryNode) - val defaultNotaryIdentity get() : Party = internalMockNetwork.defaultNotaryIdentity - val notaryNodes get() : List = internalMockNetwork.notaryNodes.map { StartedMockNode.create(it) } - val nextNodeId get() : Int = internalMockNetwork.nextNodeId + private val internalMockNetwork: InternalMockNetwork = InternalMockNetwork(cordappPackages, defaultParameters, networkSendManuallyPumped, threadPerNode, servicePeerAllocationStrategy, notarySpecs) + + /** Which node will be used as the primary notary during transaction builds. */ + val defaultNotaryNode get(): StartedMockNode = StartedMockNode.create(internalMockNetwork.defaultNotaryNode) + /** The [Party] of the [defaultNotaryNode] */ + val defaultNotaryIdentity get(): Party = internalMockNetwork.defaultNotaryIdentity + /** A list of all notary nodes in the network that have been started. */ + val notaryNodes get(): List = internalMockNetwork.notaryNodes.map { StartedMockNode.create(it) } + /** In a mock network, nodes have an incrementing integer ID. Real networks do not have this. Returns the next ID that will be used. */ + val nextNodeId get(): Int = internalMockNetwork.nextNodeId /** Create a started node with the given identity. **/ fun createPartyNode(legalName: CordaX500Name? = null): StartedMockNode = StartedMockNode.create(internalMockNetwork.createPartyNode(legalName)) @@ -152,9 +184,49 @@ open class MockNetwork( /** Create a started node with the given parameters. **/ fun createNode(parameters: MockNodeParameters = MockNodeParameters()): StartedMockNode = StartedMockNode.create(internalMockNetwork.createNode(parameters)) + /** + * Create a started node with the given parameters. + * + * @param legalName the node's legal name. + * @param forcedID a unique identifier for the node. + * @param entropyRoot the initial entropy value to use when generating keys. Defaults to an (insecure) random value, + * but can be overridden to cause nodes to have stable or colliding identity/service keys. + * @param configOverrides add/override behaviour of the [NodeConfiguration] mock object. + * @param version the mock node's platform, release, revision and vendor versions. + */ + @JvmOverloads + fun createNode(legalName: CordaX500Name? = null, + forcedID: Int? = null, + entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), + configOverrides: (NodeConfiguration) -> Any? = {}, + version: VersionInfo = MockServices.MOCK_VERSION_INFO): StartedMockNode { + val parameters = MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides, version) + return StartedMockNode.create(internalMockNetwork.createNode(parameters)) + } + /** Create an unstarted node with the given parameters. **/ fun createUnstartedNode(parameters: MockNodeParameters = MockNodeParameters()): UnstartedMockNode = UnstartedMockNode.create(internalMockNetwork.createUnstartedNode(parameters)) + /** + * Create an unstarted node with the given parameters. + * + * @param legalName the node's legal name. + * @param forcedID a unique identifier for the node. + * @param entropyRoot the initial entropy value to use when generating keys. Defaults to an (insecure) random value, + * but can be overridden to cause nodes to have stable or colliding identity/service keys. + * @param configOverrides add/override behaviour of the [NodeConfiguration] mock object. + * @param version the mock node's platform, release, revision and vendor versions. + */ + @JvmOverloads + fun createUnstartedNode(legalName: CordaX500Name? = null, + forcedID: Int? = null, + entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), + configOverrides: (NodeConfiguration) -> Any? = {}, + version: VersionInfo = MockServices.MOCK_VERSION_INFO): UnstartedMockNode { + val parameters = MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides, version) + return UnstartedMockNode.create(internalMockNetwork.createUnstartedNode(parameters)) + } + /** Start all nodes that aren't already started. **/ fun startNodes() = internalMockNetwork.startNodes() @@ -175,4 +247,4 @@ open class MockNetwork( /** Get the base directory for the given node id. **/ fun baseDirectory(nodeId: Int): Path = internalMockNetwork.baseDirectory(nodeId) -} \ No newline at end of file +} diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt index 6d57a6af7a..06fd23c188 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt @@ -5,6 +5,7 @@ import com.typesafe.config.Config import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigParseOptions import net.corda.core.concurrent.CordaFuture +import net.corda.core.contracts.ContractClassName import net.corda.core.cordapp.CordappProvider import net.corda.core.crypto.* import net.corda.core.flows.FlowLogic @@ -20,6 +21,7 @@ import net.corda.core.serialization.SerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.toFuture import net.corda.core.transactions.SignedTransaction +import net.corda.core.utilities.NetworkHostAndPort import net.corda.node.VersionInfo import net.corda.node.internal.configureDatabase import net.corda.node.internal.cordapp.CordappLoader @@ -39,7 +41,7 @@ import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.HibernateConfiguration import net.corda.nodeapi.internal.persistence.TransactionIsolationLevel -import net.corda.testing.core.DEV_ROOT_CA +import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.core.TestIdentity import net.corda.testing.database.DatabaseConstants import net.corda.testing.database.DatabaseConstants.DATA_SOURCE_CLASSNAME @@ -48,8 +50,9 @@ import net.corda.testing.database.DatabaseConstants.DATA_SOURCE_URL import net.corda.testing.database.DatabaseConstants.DATA_SOURCE_USER import net.corda.testing.database.DatabaseConstants.SCHEMA import net.corda.testing.database.DatabaseConstants.TRANSACTION_ISOLATION_LEVEL +import net.corda.testing.internal.DEV_ROOT_CA +import net.corda.testing.internal.MockCordappProvider import net.corda.testing.services.MockAttachmentStorage -import net.corda.testing.services.MockCordappProvider import org.bouncycastle.operator.ContentSigner import rx.Observable import rx.subjects.PublishSubject @@ -75,6 +78,7 @@ open class MockServices private constructor( cordappLoader: CordappLoader, override val validatedTransactions: WritableTransactionStorage, override val identityService: IdentityService, + override val networkParameters: NetworkParameters, private val initialIdentity: TestIdentity, private val moreKeys: Array ) : ServiceHub, StateLoader by validatedTransactions { @@ -127,16 +131,18 @@ open class MockServices private constructor( * @return a pair where the first element is the instance of [CordaPersistence] and the second is [MockServices]. */ @JvmStatic + @JvmOverloads fun makeTestDatabaseAndMockServices(cordappPackages: List, identityService: IdentityService, initialIdentity: TestIdentity, + networkParameters: NetworkParameters = testNetworkParameters(), vararg moreKeys: KeyPair): Pair { val cordappLoader = CordappLoader.createWithTestPackages(cordappPackages) val dataSourceProps = makeTestDataSourceProperties(initialIdentity.name.organisation) val schemaService = NodeSchemaService(cordappLoader.cordappSchemas) val database = configureDatabase(dataSourceProps, makeTestDatabaseProperties(initialIdentity.name.organisation), identityService, schemaService) val mockService = database.transaction { - object : MockServices(cordappLoader, identityService, initialIdentity, moreKeys) { + object : MockServices(cordappLoader, identityService, networkParameters, initialIdentity, moreKeys) { override val vaultService: VaultServiceInternal = makeVaultService(database.hibernateConfig, schemaService) override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable) { @@ -160,46 +166,64 @@ open class MockServices private constructor( } } - private constructor(cordappLoader: CordappLoader, identityService: IdentityService, + private constructor(cordappLoader: CordappLoader, identityService: IdentityService, networkParameters: NetworkParameters, initialIdentity: TestIdentity, moreKeys: Array) - : this(cordappLoader, MockTransactionStorage(), identityService, initialIdentity, moreKeys) + : this(cordappLoader, MockTransactionStorage(), identityService, networkParameters, initialIdentity, moreKeys) /** * Create a mock [ServiceHub] that looks for app code in the given package names, uses the provided identity service * (you can get one from [makeTestIdentityService]) and represents the given identity. */ @JvmOverloads - constructor(cordappPackages: List, identityService: IdentityService = makeTestIdentityService(), initialIdentity: TestIdentity, vararg moreKeys: KeyPair) : this(CordappLoader.createWithTestPackages(cordappPackages), identityService, initialIdentity, moreKeys) + constructor(cordappPackages: List, + initialIdentity: TestIdentity, + identityService: IdentityService = makeTestIdentityService(), + vararg moreKeys: KeyPair) : + this(CordappLoader.createWithTestPackages(cordappPackages), identityService, testNetworkParameters(), initialIdentity, moreKeys) + + constructor(cordappPackages: List, + initialIdentity: TestIdentity, + identityService: IdentityService, + networkParameters: NetworkParameters, + vararg moreKeys: KeyPair) : + this(CordappLoader.createWithTestPackages(cordappPackages), identityService, networkParameters, initialIdentity, moreKeys) /** * Create a mock [ServiceHub] that looks for app code in the given package names, uses the provided identity service * (you can get one from [makeTestIdentityService]) and represents the given identity. */ @JvmOverloads - constructor(cordappPackages: List, identityService: IdentityService = makeTestIdentityService(), initialIdentityName: CordaX500Name, key: KeyPair, vararg moreKeys: KeyPair) : this(cordappPackages, identityService, TestIdentity(initialIdentityName, key), *moreKeys) + constructor(cordappPackages: List, initialIdentityName: CordaX500Name, identityService: IdentityService = makeTestIdentityService(), key: KeyPair, vararg moreKeys: KeyPair) : + this(cordappPackages, TestIdentity(initialIdentityName, key), identityService, *moreKeys) /** * Create a mock [ServiceHub] that can't load CorDapp code, which uses the provided identity service * (you can get one from [makeTestIdentityService]) and which represents the given identity. */ @JvmOverloads - constructor(cordappPackages: List, identityService: IdentityService = makeTestIdentityService(), initialIdentityName: CordaX500Name) : this(cordappPackages, identityService, TestIdentity(initialIdentityName)) + constructor(cordappPackages: List, initialIdentityName: CordaX500Name, identityService: IdentityService = makeTestIdentityService()) : + this(cordappPackages, TestIdentity(initialIdentityName), identityService) + + /** + * Create a mock [ServiceHub] that can't load CorDapp code, and which uses a default service identity. + */ + constructor(cordappPackages: List): this(cordappPackages, CordaX500Name("TestIdentity", "", "GB"), makeTestIdentityService()) /** * Create a mock [ServiceHub] which uses the package of the caller to find CorDapp code. It uses the provided identity service * (you can get one from [makeTestIdentityService]) and which represents the given identity. */ @JvmOverloads - constructor(identityService: IdentityService = makeTestIdentityService(), initialIdentityName: CordaX500Name, key: KeyPair, vararg moreKeys: KeyPair) - : this(listOf(getCallerPackage()), identityService, TestIdentity(initialIdentityName, key), *moreKeys) + constructor(initialIdentityName: CordaX500Name, identityService: IdentityService = makeTestIdentityService(), key: KeyPair, vararg moreKeys: KeyPair) + : this(listOf(getCallerPackage()), TestIdentity(initialIdentityName, key), identityService, *moreKeys) /** * Create a mock [ServiceHub] which uses the package of the caller to find CorDapp code. It uses the provided identity service * (you can get one from [makeTestIdentityService]) and which represents the given identity. It has no keys. */ @JvmOverloads - constructor(identityService: IdentityService = makeTestIdentityService(), initialIdentityName: CordaX500Name) - : this(listOf(getCallerPackage()), identityService, TestIdentity(initialIdentityName)) + constructor(initialIdentityName: CordaX500Name, identityService: IdentityService = makeTestIdentityService()) + : this(listOf(getCallerPackage()), TestIdentity(initialIdentityName), identityService) /** * A helper constructor that requires at least one test identity to be registered, and which takes the package of @@ -209,10 +233,17 @@ open class MockServices private constructor( */ constructor(firstIdentity: TestIdentity, vararg moreIdentities: TestIdentity) : this( listOf(getCallerPackage()), + firstIdentity, makeTestIdentityService(*listOf(firstIdentity, *moreIdentities).map { it.identity }.toTypedArray()), - firstIdentity, firstIdentity.keyPair + firstIdentity.keyPair ) + /** + * Create a mock [ServiceHub] which uses the package of the caller to find CorDapp code. It uses a default service + * identity. + */ + constructor(): this(listOf(getCallerPackage()), CordaX500Name("TestIdentity", "", "GB"), makeTestIdentityService()) + override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable) { txs.forEach { validatedTransactions.addTransaction(it) @@ -227,10 +258,10 @@ open class MockServices private constructor( override val clock: Clock get() = Clock.systemUTC() override val myInfo: NodeInfo get() { - return NodeInfo(emptyList(), listOf(initialIdentity.identity), 1, serial = 1L) + return NodeInfo(listOf(NetworkHostAndPort("mock.node.services", 10000)), listOf(initialIdentity.identity), 1, serial = 1L) } override val transactionVerifierService: TransactionVerifierService get() = InMemoryTransactionVerifierService(2) - val mockCordappProvider = MockCordappProvider(cordappLoader, attachments) + private val mockCordappProvider: MockCordappProvider = MockCordappProvider(cordappLoader, attachments) override val cordappProvider: CordappProvider get() = mockCordappProvider internal fun makeVaultService(hibernateConfig: HibernateConfiguration, schemaService: SchemaService): VaultServiceInternal { @@ -242,12 +273,17 @@ open class MockServices private constructor( val cordappServices: MutableClassToInstanceMap = MutableClassToInstanceMap.create() override fun cordaService(type: Class): T { require(type.isAnnotationPresent(CordaService::class.java)) { "${type.name} is not a Corda service" } - return cordappServices.getInstance(type) ?: throw IllegalArgumentException("Corda service ${type.name} does not exist") + return cordappServices.getInstance(type) + ?: throw IllegalArgumentException("Corda service ${type.name} does not exist") } override fun jdbcSession(): Connection = throw UnsupportedOperationException() override fun registerUnloadHandler(runOnStop: () -> Unit) = throw UnsupportedOperationException() + + fun addMockCordapp(contractClassName: ContractClassName) { + mockCordappProvider.addMockCordapp(contractClassName, attachments) + } } class MockKeyManagementService(val identityService: IdentityService, @@ -273,7 +309,8 @@ class MockKeyManagementService(val identityService: IdentityService, private fun getSigner(publicKey: PublicKey): ContentSigner = getSigner(getSigningKeyPair(publicKey)) private fun getSigningKeyPair(publicKey: PublicKey): KeyPair { - val pk = publicKey.keys.firstOrNull { keyStore.containsKey(it) } ?: throw IllegalArgumentException("Public key not found: ${publicKey.toStringShort()}") + val pk = publicKey.keys.firstOrNull { keyStore.containsKey(it) } + ?: throw IllegalArgumentException("Public key not found: ${publicKey.toStringShort()}") return KeyPair(pk, keyStore[pk]!!) } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeTestUtils.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeTestUtils.kt index 008a0c2e81..dcf47f96b3 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeTestUtils.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeTestUtils.kt @@ -9,13 +9,11 @@ import net.corda.core.context.InvocationContext import net.corda.core.flows.FlowLogic import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party -import net.corda.core.internal.GlobalProperties import net.corda.core.node.ServiceHub import net.corda.core.serialization.internal.effectiveSerializationEnv import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.getOrThrow import net.corda.node.services.api.StartedNodeServices -import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.TestIdentity import net.corda.testing.core.chooseIdentity @@ -36,7 +34,6 @@ fun ServiceHub.ledger( false } return LedgerDSL(TestLedgerDSLInterpreter(this), notary).apply { - GlobalProperties.networkParameters = testNetworkParameters(emptyList()) if (serializationExists) { script() } else { diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/TestClock.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/TestClock.kt index 75d6563ae4..c99f1d128d 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/TestClock.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/TestClock.kt @@ -1,7 +1,7 @@ package net.corda.testing.node import net.corda.core.internal.until -import net.corda.node.internal.MutableClock +import net.corda.node.MutableClock import java.time.Clock import java.time.Duration import java.time.Instant diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt index 68cc393305..413c18c903 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt @@ -6,6 +6,7 @@ import com.typesafe.config.Config import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigRenderOptions import net.corda.client.rpc.CordaRPCClient +import net.corda.client.rpc.internal.createCordaRPCClientWithSsl import net.corda.cordform.CordformContext import net.corda.cordform.CordformNode import net.corda.core.concurrent.CordaFuture @@ -42,11 +43,11 @@ import net.corda.nodeapi.internal.network.NodeInfoFilesCopier import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.BOB_NAME import net.corda.testing.core.DUMMY_BANK_A_NAME -import net.corda.testing.core.setGlobalSerialization import net.corda.testing.driver.* import net.corda.testing.driver.internal.InProcessImpl import net.corda.testing.driver.internal.NodeHandleInternal import net.corda.testing.driver.internal.OutOfProcessImpl +import net.corda.testing.internal.setGlobalSerialization import net.corda.testing.node.ClusterSpec import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO import net.corda.testing.node.NotarySpec @@ -150,7 +151,7 @@ class DriverDSLImpl( private fun establishRpc(config: NodeConfig, processDeathFuture: CordaFuture): CordaFuture { val rpcAddress = config.corda.rpcOptions.address!! val client = if (config.corda.rpcOptions.useSsl) { - CordaRPCClient(rpcAddress, sslConfiguration = config.corda.rpcOptions.sslConfig) + createCordaRPCClientWithSsl(rpcAddress, sslConfiguration = config.corda.rpcOptions.sslConfig) } else { CordaRPCClient(rpcAddress) } @@ -1002,7 +1003,7 @@ fun internalDriver( debugPortAllocation: PortAllocation = DriverParameters().debugPortAllocation, systemProperties: Map = DriverParameters().systemProperties, useTestClock: Boolean = DriverParameters().useTestClock, - initialiseSerialization: Boolean = DriverParameters().initialiseSerialization, + initialiseSerialization: Boolean = true, startNodesInProcess: Boolean = DriverParameters().startNodesInProcess, waitForAllNodesToFinish: Boolean = DriverParameters().waitForAllNodesToFinish, notarySpecs: List = DriverParameters().notarySpecs, diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt index 5896ef7622..0a5d250856 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt @@ -18,6 +18,7 @@ import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.MessageRecipients import net.corda.core.messaging.RPCOps import net.corda.core.messaging.SingleMessageRecipient +import net.corda.core.node.NetworkParameters import net.corda.core.node.NodeInfo import net.corda.core.node.NotaryInfo import net.corda.core.node.services.IdentityService @@ -45,8 +46,8 @@ import net.corda.nodeapi.internal.network.NetworkParametersCopier import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.testing.common.internal.testNetworkParameters -import net.corda.testing.core.setGlobalSerialization import net.corda.testing.internal.rigorousMock +import net.corda.testing.internal.setGlobalSerialization import net.corda.testing.internal.testThreadFactory import net.corda.testing.node.* import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO @@ -75,49 +76,33 @@ data class MockNodeArgs( val version: VersionInfo = MOCK_VERSION_INFO ) -/** - * A mock node brings up a suite of in-memory services in a fast manner suitable for unit testing. - * Components that do IO are either swapped out for mocks, or pointed to a [Jimfs] in memory filesystem or an in - * memory H2 database instance. - * - * Mock network nodes require manual pumping by default: they will not run asynchronous. This means that - * for message exchanges to take place (and associated handlers to run), you must call the [runNetwork] - * method. - * - * You can get a printout of every message sent by using code like: - * - * LogHelper.setLevel("+messages") - * - * By default a single notary node is automatically started, which forms part of the network parameters for all the nodes. - * This node is available by calling [defaultNotaryNode]. - */ open class InternalMockNetwork(private val cordappPackages: List, defaultParameters: MockNetworkParameters = MockNetworkParameters(), val networkSendManuallyPumped: Boolean = defaultParameters.networkSendManuallyPumped, val threadPerNode: Boolean = defaultParameters.threadPerNode, servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = defaultParameters.servicePeerAllocationStrategy, - initialiseSerialization: Boolean = defaultParameters.initialiseSerialization, val notarySpecs: List = defaultParameters.notarySpecs, - maxTransactionSize: Int = Int.MAX_VALUE, + networkParameters: NetworkParameters = testNetworkParameters(), val defaultFactory: (MockNodeArgs) -> MockNode = InternalMockNetwork::MockNode) { init { // Apache SSHD for whatever reason registers a SFTP FileSystemProvider - which gets loaded by JimFS. // This SFTP support loads BouncyCastle, which we want to avoid. // Please see https://issues.apache.org/jira/browse/SSHD-736 - it's easier then to create our own fork of SSHD SecurityUtils.setAPrioriDisabledProvider("BC", true) // XXX: Why isn't this static? + require(networkParameters.notaries.isEmpty()) { "Define notaries using notarySpecs" } } var nextNodeId = 0 private set private val filesystem = Jimfs.newFileSystem(unix()) private val busyLatch = ReusableLatch() - val messagingNetwork = InMemoryMessagingNetwork(networkSendManuallyPumped, servicePeerAllocationStrategy, busyLatch) + val messagingNetwork = InMemoryMessagingNetwork.create(networkSendManuallyPumped, servicePeerAllocationStrategy, busyLatch) // A unique identifier for this network to segregate databases with the same nodeID but different networks. private val networkId = random63BitValue() - private val networkParameters: NetworkParametersCopier + private val networkParametersCopier: NetworkParametersCopier private val _nodes = mutableListOf() private val serializationEnv = try { - setGlobalSerialization(initialiseSerialization) + setGlobalSerialization(true) } catch (e: IllegalStateException) { throw IllegalStateException("Using more than one InternalMockNetwork simultaneously is not supported.", e) } @@ -190,7 +175,7 @@ open class InternalMockNetwork(private val cordappPackages: List, filesystem.getPath("/nodes").createDirectory() val notaryInfos = generateNotaryIdentities() // The network parameters must be serialised before starting any of the nodes - networkParameters = NetworkParametersCopier(testNetworkParameters(notaryInfos, maxTransactionSize = maxTransactionSize)) + networkParametersCopier = NetworkParametersCopier(networkParameters.copy(notaries = notaryInfos)) @Suppress("LeakingThis") notaryNodes = createNotaries() } catch (t: Throwable) { @@ -242,7 +227,7 @@ open class InternalMockNetwork(private val cordappPackages: List, override val started: StartedNode? get() = uncheckedCast(super.started) override fun start(): StartedNode { - mockNet.networkParameters.install(configuration.baseDirectory) + mockNet.networkParametersCopier.install(configuration.baseDirectory) val started: StartedNode = uncheckedCast(super.start()) advertiseNodeToNetwork(started) return started @@ -260,7 +245,7 @@ open class InternalMockNetwork(private val cordappPackages: List, // We only need to override the messaging service here, as currently everything that hits disk does so // through the java.nio API which we are already mocking via Jimfs. - override fun makeMessagingService(database: CordaPersistence, info: NodeInfo, nodeProperties: NodePropertiesStore): MessagingService { + override fun makeMessagingService(database: CordaPersistence, info: NodeInfo, nodeProperties: NodePropertiesStore, networkParameters: NetworkParameters): MessagingService { require(id >= 0) { "Node ID must be zero or positive, was passed: " + id } return mockNet.messagingNetwork.createNodeWithID( !mockNet.threadPerNode, @@ -302,13 +287,15 @@ open class InternalMockNetwork(private val cordappPackages: List, override fun makeTransactionVerifierService() = InMemoryTransactionVerifierService(1) - override fun myAddresses(): List = emptyList() + // NodeInfo requires a non-empty addresses list and so we give it a dummy value for mock nodes. + // The non-empty addresses check is important to have and so we tolerate the ugliness here. + override fun myAddresses(): List = listOf(NetworkHostAndPort("mock.node", 1000)) // Allow unit tests to modify the serialization whitelist list before the node start, // so they don't have to ServiceLoad test whitelists into all unit tests. - val testSerializationWhitelists by lazy { super.serializationWhitelists.toMutableList() } + private val _serializationWhitelists by lazy { super.serializationWhitelists.toMutableList() } override val serializationWhitelists: List - get() = testSerializationWhitelists + get() = _serializationWhitelists private var dbCloser: (() -> Any?)? = null override fun initialiseDatabasePersistence(schemaService: SchemaService, identityService: IdentityService, insideTransaction: (CordaPersistence) -> T): T { return super.initialiseDatabasePersistence(schemaService, identityService) { database -> @@ -459,7 +446,7 @@ private fun mockNodeConfiguration(): NodeConfiguration { doReturn(null).whenever(it).notary doReturn(DatabaseConfig()).whenever(it).database doReturn("").whenever(it).emailAddress - doReturn("").whenever(it).exportJMXto + doReturn(null).whenever(it).jmxMonitoringHttpPort doReturn(true).whenever(it).devMode doReturn(null).whenever(it).compatibilityZoneURL doReturn(emptyList()).whenever(it).certificateChainCheckPolicies diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/NodeBasedTest.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/NodeBasedTest.kt index e41f91aafe..15a94fb60d 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/NodeBasedTest.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/NodeBasedTest.kt @@ -55,7 +55,7 @@ abstract class NodeBasedTest(private val cordappPackages: List = emptyLi @Before fun init() { - defaultNetworkParameters = NetworkParametersCopier(testNetworkParameters(emptyList())) + defaultNetworkParameters = NetworkParametersCopier(testNetworkParameters()) } /** diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/demorun/CordformNodeRunner.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/demorun/CordformNodeRunner.kt new file mode 100644 index 0000000000..bbaf5724a5 --- /dev/null +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/demorun/CordformNodeRunner.kt @@ -0,0 +1,77 @@ +package net.corda.testing.node.internal.demorun + +import net.corda.cordform.CordformDefinition +import net.corda.cordform.CordformNode +import net.corda.core.utilities.NetworkHostAndPort +import net.corda.core.utilities.getOrThrow +import net.corda.testing.driver.JmxPolicy +import net.corda.testing.driver.PortAllocation +import net.corda.testing.node.internal.internalDriver + +/** + * Creates a demo runner for this cordform definition + */ +fun CordformDefinition.nodeRunner() = CordformNodeRunner(this) + +/** + * A node runner creates and runs nodes for a given [[CordformDefinition]]. + */ +class CordformNodeRunner(val cordformDefinition: CordformDefinition) { + private var extraPackagesToScan = emptyList() + + /** + * Builder method to sets the extra cordapp scan packages + */ + fun scanPackages(packages: List): CordformNodeRunner { + extraPackagesToScan = packages + return this + } + + fun clean() { + System.err.println("Deleting: ${cordformDefinition.nodesDirectory}") + cordformDefinition.nodesDirectory.toFile().deleteRecursively() + } + + /** + * Deploy the nodes specified in the given [CordformDefinition]. This will block until all the nodes and webservers + * have terminated. + */ + fun deployAndRunNodes() { + runNodes(waitForAllNodesToFinish = true) { } + } + + /** + * Deploy the nodes specified in the given [CordformDefinition] and then execute the given [block] once all the nodes + * and webservers are up. After execution all these processes will be terminated. + */ + fun deployAndRunNodesThen(block: () -> Unit) { + runNodes(waitForAllNodesToFinish = false, block = block) + } + + private fun runNodes(waitForAllNodesToFinish: Boolean, block: () -> Unit) { + clean() + val nodes = cordformDefinition.nodeConfigurers.map { configurer -> CordformNode().also { configurer.accept(it) } } + val maxPort = nodes + .flatMap { listOf(it.p2pAddress, it.rpcAddress, it.webAddress) } + .mapNotNull { address -> address?.let { NetworkHostAndPort.parse(it).port } } + .max()!! + internalDriver( + isDebug = true, + jmxPolicy = JmxPolicy(true), + driverDirectory = cordformDefinition.nodesDirectory, + extraCordappPackagesToScan = extraPackagesToScan, + // Notaries are manually specified in Cordform so we don't want the driver automatically starting any + notarySpecs = emptyList(), + // Start from after the largest port used to prevent port clash + portAllocation = PortAllocation.Incremental(maxPort + 1), + waitForAllNodesToFinish = waitForAllNodesToFinish + ) { + cordformDefinition.setup(this) + startCordformNodes(nodes).getOrThrow() // Only proceed once everything is up and running + println("All nodes and webservers are ready...") + block() + } + } +} + + diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/demorun/DemoRunner.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/demorun/DemoRunner.kt deleted file mode 100644 index 185a851e99..0000000000 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/demorun/DemoRunner.kt +++ /dev/null @@ -1,57 +0,0 @@ -@file:JvmName("DemoRunner") - -package net.corda.testing.node.internal.demorun - -import net.corda.cordform.CordformDefinition -import net.corda.cordform.CordformNode -import net.corda.core.utilities.NetworkHostAndPort -import net.corda.core.utilities.getOrThrow -import net.corda.testing.driver.JmxPolicy -import net.corda.testing.driver.PortAllocation -import net.corda.testing.node.internal.internalDriver - -fun CordformDefinition.clean() { - System.err.println("Deleting: $nodesDirectory") - nodesDirectory.toFile().deleteRecursively() -} - -/** - * Deploy the nodes specified in the given [CordformDefinition]. This will block until all the nodes and webservers - * have terminated. - */ -fun CordformDefinition.deployNodes() { - runNodes(waitForAllNodesToFinish = true) { } -} - -/** - * Deploy the nodes specified in the given [CordformDefinition] and then execute the given [block] once all the nodes - * and webservers are up. After execution all these processes will be terminated. - */ -fun CordformDefinition.deployNodesThen(block: () -> Unit) { - runNodes(waitForAllNodesToFinish = false, block = block) -} - -private fun CordformDefinition.runNodes(waitForAllNodesToFinish: Boolean, block: () -> Unit) { - clean() - val nodes = nodeConfigurers.map { configurer -> CordformNode().also { configurer.accept(it) } } - val maxPort = nodes - .flatMap { listOf(it.p2pAddress, it.rpcAddress, it.webAddress) } - .mapNotNull { address -> address?.let { NetworkHostAndPort.parse(it).port } } - .max()!! - internalDriver( - isDebug = true, - jmxPolicy = JmxPolicy(true), - driverDirectory = nodesDirectory, - extraCordappPackagesToScan = cordappPackages, - // Notaries are manually specified in Cordform so we don't want the driver automatically starting any - notarySpecs = emptyList(), - // Start from after the largest port used to prevent port clash - portAllocation = PortAllocation.Incremental(maxPort + 1), - waitForAllNodesToFinish = waitForAllNodesToFinish - ) { - setup(this) - startCordformNodes(nodes).getOrThrow() // Only proceed once everything is up and running - println("All nodes and webservers are ready...") - block() - } -} diff --git a/testing/node-driver/src/test/java/net/corda/testing/node/MockNodeFactoryInJavaTest.java b/testing/node-driver/src/test/java/net/corda/testing/node/MockNodeFactoryInJavaTest.java index fbf6bb01f4..739e5dcf27 100644 --- a/testing/node-driver/src/test/java/net/corda/testing/node/MockNodeFactoryInJavaTest.java +++ b/testing/node-driver/src/test/java/net/corda/testing/node/MockNodeFactoryInJavaTest.java @@ -13,7 +13,7 @@ public class MockNodeFactoryInJavaTest { private static void factoryIsEasyToPassInUsingJava() { //noinspection Convert2MethodRef new MockNetwork(emptyList()); - new MockNetwork(emptyList(), new MockNetworkParameters().setInitialiseSerialization(false)); + new MockNetwork(emptyList(), new MockNetworkParameters().withThreadPerNode(true)); //noinspection Convert2MethodRef new MockNetwork(emptyList()).createNode(new MockNodeParameters()); } diff --git a/testing/node-driver/src/test/kotlin/net/corda/testing/node/internal/InternalMockNetworkTests.kt b/testing/node-driver/src/test/kotlin/net/corda/testing/node/internal/InternalMockNetworkTests.kt index 478585af17..fe2780977a 100644 --- a/testing/node-driver/src/test/kotlin/net/corda/testing/node/internal/InternalMockNetworkTests.kt +++ b/testing/node-driver/src/test/kotlin/net/corda/testing/node/internal/InternalMockNetworkTests.kt @@ -9,7 +9,7 @@ class InternalMockNetworkTests { fun `does not leak serialization env if init fails`() { val e = Exception("didn't work") assertThatThrownBy { - object : InternalMockNetwork(emptyList(), initialiseSerialization = true) { + object : InternalMockNetwork(emptyList()) { override fun createNotaries() = throw e } }.isSameAs(e) diff --git a/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt b/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt index c17cd661a6..d1a344f232 100644 --- a/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt +++ b/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt @@ -10,10 +10,9 @@ import net.corda.core.internal.div import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.contextLogger import net.corda.nodeapi.internal.network.NetworkParametersCopier -import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.common.internal.asContextEnv +import net.corda.testing.common.internal.testNetworkParameters import java.io.File -import java.nio.file.Files.createDirectories import java.net.URL import java.nio.file.Path import java.nio.file.Paths @@ -54,10 +53,10 @@ class NodeProcess( // TODO All use of this factory have duplicate code which is either bundling the calling module or a 3rd party module // as a CorDapp for the nodes. class Factory( - val buildDirectory: Path = Paths.get("build"), - val cordaJarUrl: URL? = this::class.java.getResource("/corda.jar"), - val extraJvmArgs: Array = emptyArray(), - val redirectConsoleTo: File? = null + private val buildDirectory: Path = Paths.get("build"), + private val cordaJarUrl: URL? = this::class.java.getResource("/corda.jar"), + private val extraJvmArgs: Array = emptyArray(), + private val redirectConsoleTo: File? = null ) { val cordaJar: Path by lazy { require(cordaJarUrl != null, { "corda.jar could not be found in classpath" }) @@ -71,7 +70,7 @@ class NodeProcess( KryoClientSerializationScheme.createSerializationEnv().asContextEnv { // There are no notaries in the network parameters for smoke test nodes. If this is required then we would // need to introduce the concept of a "network" which predefines the notaries, like the driver and MockNetwork - NetworkParametersCopier(testNetworkParameters(emptyList())) + NetworkParametersCopier(testNetworkParameters()) } } diff --git a/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt b/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt index ea089257c2..322e9e90b7 100644 --- a/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt +++ b/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt @@ -5,12 +5,12 @@ import net.corda.core.node.NotaryInfo import java.time.Instant fun testNetworkParameters( - notaries: List, + notaries: List = emptyList(), minimumPlatformVersion: Int = 1, modifiedTime: Instant = Instant.now(), maxMessageSize: Int = 10485760, // TODO: Make this configurable and consistence across driver, bootstrapper, demobench and NetworkMapServer - maxTransactionSize: Int = Int.MAX_VALUE, + maxTransactionSize: Int = maxMessageSize, epoch: Int = 1 ): NetworkParameters { return NetworkParameters( diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/core/SerializationTestHelpers.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/core/SerializationTestHelpers.kt index 698003380d..2feefc269b 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/core/SerializationTestHelpers.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/core/SerializationTestHelpers.kt @@ -16,6 +16,8 @@ import net.corda.nodeapi.internal.serialization.* import net.corda.nodeapi.internal.serialization.amqp.AMQPClientSerializationScheme import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme import net.corda.testing.common.internal.asContextEnv +import net.corda.testing.internal.createTestSerializationEnv +import net.corda.testing.internal.inVMExecutors import net.corda.testing.internal.rigorousMock import net.corda.testing.internal.testThreadFactory import org.apache.activemq.artemis.core.remoting.impl.invm.InVMConnector @@ -26,8 +28,6 @@ import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ExecutorService import java.util.concurrent.Executors -private val inVMExecutors = ConcurrentHashMap() - /** @param inheritable whether new threads inherit the environment, use sparingly. */ class SerializationEnvironmentRule(private val inheritable: Boolean = false) : TestRule { companion object { @@ -78,42 +78,3 @@ interface GlobalSerializationEnvironment : SerializationEnvironment { fun unset() } -/** - * Should only be used by Driver and MockNode. - * @param armed true to install, false to do nothing and return a dummy env. - */ -fun setGlobalSerialization(armed: Boolean): GlobalSerializationEnvironment { - return if (armed) { - object : GlobalSerializationEnvironment, SerializationEnvironment by createTestSerializationEnv("") { - override fun unset() { - _globalSerializationEnv.set(null) - inVMExecutors.remove(this) - } - }.also { - _globalSerializationEnv.set(it) - } - } else { - rigorousMock().also { - doNothing().whenever(it).unset() - } - } -} - -private fun createTestSerializationEnv(label: String): SerializationEnvironmentImpl { - val factory = SerializationFactoryImpl().apply { - registerScheme(KryoClientSerializationScheme()) - registerScheme(KryoServerSerializationScheme()) - registerScheme(AMQPClientSerializationScheme(emptyList())) - registerScheme(AMQPServerSerializationScheme(emptyList())) - } - return object : SerializationEnvironmentImpl( - factory, - AMQP_P2P_CONTEXT, - KRYO_RPC_SERVER_CONTEXT, - KRYO_RPC_CLIENT_CONTEXT, - AMQP_STORAGE_CONTEXT, - KRYO_CHECKPOINT_CONTEXT - ) { - override fun toString() = "testSerializationEnv($label)" - } -} diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/core/TestConstants.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/core/TestConstants.kt index 0a87461d8d..6cbecab924 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/core/TestConstants.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/core/TestConstants.kt @@ -30,10 +30,6 @@ val BOB_NAME = CordaX500Name("Bob Plc", "Rome", "IT") @JvmField val CHARLIE_NAME = CordaX500Name("Charlie Ltd", "Athens", "GR") -val DEV_INTERMEDIATE_CA: CertificateAndKeyPair by lazy { net.corda.nodeapi.internal.DEV_INTERMEDIATE_CA } - -val DEV_ROOT_CA: CertificateAndKeyPair by lazy { net.corda.nodeapi.internal.DEV_ROOT_CA } - fun dummyCommand(vararg signers: PublicKey = arrayOf(generateKeyPair().public)) = Command(DummyCommandData, signers.toList()) object DummyCommandData : TypeOnlyCommandData() diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/core/TestUtils.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/core/TestUtils.kt index 762ea036be..5229cde993 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/core/TestUtils.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/core/TestUtils.kt @@ -20,6 +20,8 @@ import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.X509CertificateFactory import net.corda.nodeapi.internal.crypto.X509Utilities +import net.corda.testing.internal.DEV_INTERMEDIATE_CA +import net.corda.testing.internal.DEV_ROOT_CA import java.math.BigInteger import java.security.KeyPair import java.security.PublicKey diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/dsl/TestDSL.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/dsl/TestDSL.kt index 8548ecc1fb..bf2745f8e3 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/dsl/TestDSL.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/dsl/TestDSL.kt @@ -14,7 +14,7 @@ import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.WireTransaction import net.corda.testing.services.MockAttachmentStorage -import net.corda.testing.services.MockCordappProvider +import net.corda.testing.internal.MockCordappProvider import net.corda.testing.core.dummyCommand import java.io.InputStream import java.security.PublicKey diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalSerializationTestHelpers.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalSerializationTestHelpers.kt index f11693d527..aaa9e3a15d 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalSerializationTestHelpers.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalSerializationTestHelpers.kt @@ -1,8 +1,19 @@ package net.corda.testing.internal -import net.corda.core.serialization.internal._contextSerializationEnv -import net.corda.core.serialization.internal._inheritableContextSerializationEnv +import com.nhaarman.mockito_kotlin.doNothing +import com.nhaarman.mockito_kotlin.whenever +import net.corda.client.rpc.internal.KryoClientSerializationScheme +import net.corda.core.serialization.internal.* +import net.corda.node.serialization.KryoServerSerializationScheme +import net.corda.nodeapi.internal.serialization.* +import net.corda.nodeapi.internal.serialization.amqp.AMQPClientSerializationScheme +import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme +import net.corda.testing.core.GlobalSerializationEnvironment import net.corda.testing.core.SerializationEnvironmentRule +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.ExecutorService + +val inVMExecutors = ConcurrentHashMap() /** * For example your test class uses [SerializationEnvironmentRule] but you want to turn it off for one method. @@ -17,3 +28,43 @@ fun withoutTestSerialization(callable: () -> T): T { // TODO: Delete this, s property.set(env) } } + +internal fun createTestSerializationEnv(label: String): SerializationEnvironmentImpl { + val factory = SerializationFactoryImpl().apply { + registerScheme(KryoClientSerializationScheme()) + registerScheme(KryoServerSerializationScheme()) + registerScheme(AMQPClientSerializationScheme(emptyList())) + registerScheme(AMQPServerSerializationScheme(emptyList())) + } + return object : SerializationEnvironmentImpl( + factory, + AMQP_P2P_CONTEXT, + KRYO_RPC_SERVER_CONTEXT, + KRYO_RPC_CLIENT_CONTEXT, + AMQP_STORAGE_CONTEXT, + KRYO_CHECKPOINT_CONTEXT + ) { + override fun toString() = "testSerializationEnv($label)" + } +} + +/** + * Should only be used by Driver and MockNode. + * @param armed true to install, false to do nothing and return a dummy env. + */ +fun setGlobalSerialization(armed: Boolean): GlobalSerializationEnvironment { + return if (armed) { + object : GlobalSerializationEnvironment, SerializationEnvironment by createTestSerializationEnv("") { + override fun unset() { + _globalSerializationEnv.set(null) + inVMExecutors.remove(this) + } + }.also { + _globalSerializationEnv.set(it) + } + } else { + rigorousMock().also { + doNothing().whenever(it).unset() + } + } +} \ No newline at end of file diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalTestConstants.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalTestConstants.kt new file mode 100644 index 0000000000..d9165951a8 --- /dev/null +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalTestConstants.kt @@ -0,0 +1,7 @@ +package net.corda.testing.internal + +import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair + +val DEV_INTERMEDIATE_CA: CertificateAndKeyPair by lazy { net.corda.nodeapi.internal.DEV_INTERMEDIATE_CA } + +val DEV_ROOT_CA: CertificateAndKeyPair by lazy { net.corda.nodeapi.internal.DEV_ROOT_CA } diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/MockCordappConfigProvider.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/MockCordappConfigProvider.kt new file mode 100644 index 0000000000..6f2f99cfa8 --- /dev/null +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/MockCordappConfigProvider.kt @@ -0,0 +1,17 @@ +package net.corda.testing.internal + +import com.typesafe.config.Config +import com.typesafe.config.ConfigFactory +import net.corda.core.internal.cordapp.CordappConfigProvider + +class MockCordappConfigProvider : CordappConfigProvider { + val cordappConfigs = mutableMapOf () + + override fun getConfigByName(name: String): Config { + return if(cordappConfigs.containsKey(name)) { + cordappConfigs[name]!! + } else { + ConfigFactory.empty() + } + } +} \ No newline at end of file diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/services/MockCordappProvider.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/MockCordappProvider.kt similarity index 76% rename from testing/test-utils/src/main/kotlin/net/corda/testing/services/MockCordappProvider.kt rename to testing/test-utils/src/main/kotlin/net/corda/testing/internal/MockCordappProvider.kt index dba07b73be..4638d73fb3 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/services/MockCordappProvider.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/MockCordappProvider.kt @@ -1,4 +1,4 @@ -package net.corda.testing.services +package net.corda.testing.internal import net.corda.core.contracts.ContractClassName import net.corda.core.cordapp.Cordapp @@ -7,10 +7,17 @@ import net.corda.core.node.services.AttachmentId import net.corda.core.node.services.AttachmentStorage import net.corda.node.internal.cordapp.CordappLoader import net.corda.node.internal.cordapp.CordappProviderImpl +import net.corda.testing.services.MockAttachmentStorage import java.nio.file.Paths import java.util.* -class MockCordappProvider(cordappLoader: CordappLoader, attachmentStorage: AttachmentStorage) : CordappProviderImpl(cordappLoader, attachmentStorage) { +class MockCordappProvider( + cordappLoader: CordappLoader, + attachmentStorage: AttachmentStorage, + val cordappConfigProvider: MockCordappConfigProvider = MockCordappConfigProvider() +) : CordappProviderImpl(cordappLoader, cordappConfigProvider, attachmentStorage) { + constructor(cordappLoader: CordappLoader, attachmentStorage: AttachmentStorage) : this(cordappLoader, attachmentStorage, MockCordappConfigProvider()) + val cordappRegistry = mutableListOf>() fun addMockCordapp(contractClassName: ContractClassName, attachments: MockAttachmentStorage) { @@ -24,7 +31,7 @@ class MockCordappProvider(cordappLoader: CordappLoader, attachmentStorage: Attac serializationWhitelists = emptyList(), serializationCustomSerializers = emptyList(), customSchemas = emptySet(), - jarPath = Paths.get(".").toUri().toURL()) + jarPath = Paths.get("").toUri().toURL()) if (cordappRegistry.none { it.first.contractClassNames.contains(contractClassName) }) { cordappRegistry.add(Pair(cordapp, findOrImportAttachment(contractClassName.toByteArray(), attachments))) } diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/TestNodeInfoBuilder.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/TestNodeInfoBuilder.kt index c99ca190cf..8757adde2b 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/TestNodeInfoBuilder.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/TestNodeInfoBuilder.kt @@ -12,8 +12,6 @@ import net.corda.nodeapi.internal.createDevNodeCa import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.X509Utilities -import net.corda.testing.core.DEV_INTERMEDIATE_CA -import net.corda.testing.core.DEV_ROOT_CA import java.security.KeyPair import java.security.PrivateKey import java.security.cert.X509Certificate diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/services/MockAttachmentStorage.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/services/MockAttachmentStorage.kt index 8cf1a9ccf2..f827efb39d 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/services/MockAttachmentStorage.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/services/MockAttachmentStorage.kt @@ -43,11 +43,11 @@ class MockAttachmentStorage : AttachmentStorage, SingletonSerializeAsToken() { val files = HashMap() + private class MockAttachment(dataLoader: () -> ByteArray, override val id: SecureHash) : AbstractAttachment(dataLoader) + override fun openAttachment(id: SecureHash): Attachment? { val f = files[id] ?: return null - return object : AbstractAttachment({ f }) { - override val id = id - } + return MockAttachment({ f }, id) } override fun queryAttachments(criteria: AttachmentQueryCriteria, sorting: AttachmentSort?): List { @@ -64,10 +64,8 @@ class MockAttachmentStorage : AttachmentStorage, SingletonSerializeAsToken() { override fun importOrGetAttachment(jar: InputStream): AttachmentId { try { return importAttachment(jar) - } - catch (faee: java.nio.file.FileAlreadyExistsException) { + } catch (faee: java.nio.file.FileAlreadyExistsException) { return AttachmentId.parse(faee.message!!) } } - } diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeConfig.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeConfig.kt index 7b94e95058..5012c99908 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeConfig.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeConfig.kt @@ -1,6 +1,9 @@ package net.corda.demobench.model +import com.typesafe.config.Config +import com.typesafe.config.ConfigFactory.empty import com.typesafe.config.ConfigRenderOptions +import com.typesafe.config.ConfigValueFactory import net.corda.core.identity.CordaX500Name import net.corda.core.internal.copyToDirectory import net.corda.core.internal.createDirectories @@ -20,6 +23,7 @@ data class NodeConfig( val myLegalName: CordaX500Name, val p2pAddress: NetworkHostAndPort, val rpcAddress: NetworkHostAndPort, + val rpcAdminAddress: NetworkHostAndPort, /** This is not used by the node but by the webserver which looks at node.conf. */ val webAddress: NetworkHostAndPort, val notary: NotaryService?, @@ -42,7 +46,17 @@ data class NodeConfig( @Suppress("unused") private val useTestClock = true - fun toText(): String = toConfig().root().render(renderOptions) + private fun asConfig(): Config { + + val config = toConfig() + val rpcSettings = empty() + .withValue("address", ConfigValueFactory.fromAnyRef(rpcAddress.toString())) + .withValue("adminAddress", ConfigValueFactory.fromAnyRef(rpcAdminAddress.toString())) + .root() + return config.withoutPath("rpcAddress").withoutPath("rpcAdminAddress").withValue("rpcSettings", rpcSettings) + } + + fun toText(): String = asConfig().root().render(renderOptions) } /** diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt index 56190f9543..5ecb970fce 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt @@ -75,6 +75,7 @@ class NodeController(check: atRuntime = ::checkExists) : Controller() { ), p2pAddress = nodeData.p2pPort.toLocalAddress(), rpcAddress = nodeData.rpcPort.toLocalAddress(), + rpcAdminAddress = nodeData.rpcAdminPort.toLocalAddress(), webAddress = nodeData.webPort.toLocalAddress(), notary = notary, h2port = nodeData.h2Port.value, diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeData.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeData.kt index 7abdd09775..b8af55e5ff 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeData.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeData.kt @@ -35,6 +35,7 @@ class NodeData { val nearestCity = SimpleObjectProperty(CityDatabase["London"]!!) val p2pPort = SimpleIntegerProperty() val rpcPort = SimpleIntegerProperty() + val rpcAdminPort = SimpleIntegerProperty() val webPort = SimpleIntegerProperty() val h2Port = SimpleIntegerProperty() val extraServices = SimpleListProperty(observableArrayList()) @@ -45,6 +46,7 @@ class NodeDataModel : ItemViewModel(NodeData()) { val nearestCity = bind { item?.nearestCity } val p2pPort = bind { item?.p2pPort } val rpcPort = bind { item?.rpcPort } + val rpcAdminPort = bind { item?.rpcAdminPort } val webPort = bind { item?.webPort } val h2Port = bind { item?.h2Port } } diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTabView.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTabView.kt index 5fb32aa9e6..8cded0186d 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTabView.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTabView.kt @@ -188,6 +188,7 @@ class NodeTabView : Fragment() { model.p2pPort.value = nodeController.nextPort model.rpcPort.value = nodeController.nextPort + model.rpcAdminPort.value = nodeController.nextPort model.webPort.value = nodeController.nextPort model.h2Port.value = nodeController.nextPort diff --git a/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeConfigTest.kt b/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeConfigTest.kt index 1933fb0902..048b24ce7d 100644 --- a/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeConfigTest.kt +++ b/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeConfigTest.kt @@ -29,6 +29,7 @@ class NodeConfigTest { legalName = myLegalName, p2pPort = 10001, rpcPort = 40002, + rpcAdminPort = 40003, webPort = 20001, h2port = 30001, notary = NotaryService(validating = false), @@ -56,6 +57,7 @@ class NodeConfigTest { legalName = myLegalName, p2pPort = 10001, rpcPort = 40002, + rpcAdminPort = 40003, webPort = 20001, h2port = 30001, notary = NotaryService(validating = false), @@ -78,6 +80,7 @@ class NodeConfigTest { legalName: CordaX500Name = CordaX500Name(organisation = "Unknown", locality = "Nowhere", country = "GB"), p2pPort: Int = -1, rpcPort: Int = -1, + rpcAdminPort: Int = -1, webPort: Int = -1, h2port: Int = -1, notary: NotaryService?, @@ -87,6 +90,7 @@ class NodeConfigTest { myLegalName = legalName, p2pAddress = localPort(p2pPort), rpcAddress = localPort(rpcPort), + rpcAdminAddress = localPort(rpcAdminPort), webAddress = localPort(webPort), h2port = h2port, notary = notary, diff --git a/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeControllerTest.kt b/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeControllerTest.kt index 18db310a5b..e6b5454144 100644 --- a/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeControllerTest.kt +++ b/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeControllerTest.kt @@ -150,6 +150,7 @@ class NodeControllerTest { organisation: String = "Unknown", p2pPort: Int = 0, rpcPort: Int = 0, + rpcAdminPort: Int = 0, webPort: Int = 0, h2port: Int = 0, notary: NotaryService? = null, @@ -163,6 +164,7 @@ class NodeControllerTest { ), p2pAddress = localPort(p2pPort), rpcAddress = localPort(rpcPort), + rpcAdminAddress = localPort(rpcAdminPort), webAddress = localPort(webPort), h2port = h2port, notary = notary, diff --git a/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/NotaryTest.kt b/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/NotaryTest.kt index 87a0b2589c..a8cff6abb9 100644 --- a/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/NotaryTest.kt +++ b/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/NotaryTest.kt @@ -28,7 +28,7 @@ data class NotariseCommand(val issueTx: SignedTransaction, val moveTx: SignedTra val dummyNotarisationTest = LoadTest( "Notarising dummy transactions", generate = { _, _ -> - val issuerServices = MockServices(emptyList(), makeTestIdentityService(megaCorp.identity, miniCorp.identity, dummyCashIssuer.identity, dummyNotary.identity), megaCorp.name, dummyCashIssuer.keyPair) + val issuerServices = MockServices(emptyList(), megaCorp.name, makeTestIdentityService(megaCorp.identity, miniCorp.identity, dummyCashIssuer.identity, dummyNotary.identity), dummyCashIssuer.keyPair) val generateTx = Generator.pickOne(simpleNodes).flatMap { node -> Generator.int().map { val issueBuilder = DummyContract.generateInitial(it, notary.info.legalIdentities[0], DUMMY_CASH_ISSUER) // TODO notary choice diff --git a/tools/notaryhealthcheck/src/main/kotlin/net/corda/notaryhealthcheck/HealthCheckCordform.kt b/tools/notaryhealthcheck/src/main/kotlin/net/corda/notaryhealthcheck/HealthCheckCordform.kt index 8f7bb5a4ef..5358b1e55c 100644 --- a/tools/notaryhealthcheck/src/main/kotlin/net/corda/notaryhealthcheck/HealthCheckCordform.kt +++ b/tools/notaryhealthcheck/src/main/kotlin/net/corda/notaryhealthcheck/HealthCheckCordform.kt @@ -13,7 +13,7 @@ import net.corda.testing.node.User import net.corda.testing.node.internal.demorun.* import java.nio.file.Paths -fun main(args: Array) = HealthCheckCordform().deployNodes() +fun main(args: Array) = HealthCheckCordform().nodeRunner().deployAndRunNodes() class HealthCheckCordform : CordformDefinition() { private fun createNotaryNames(clusterSize: Int) = (0 until clusterSize).map { CordaX500Name("Notary Service $it", "Zurich", "CH") } diff --git a/tools/notaryhealthcheck/src/main/kotlin/net/corda/notaryhealthcheck/flows/HealthCheckNotaryClientFlow.kt b/tools/notaryhealthcheck/src/main/kotlin/net/corda/notaryhealthcheck/flows/HealthCheckNotaryClientFlow.kt index e36f24995a..28fea93083 100644 --- a/tools/notaryhealthcheck/src/main/kotlin/net/corda/notaryhealthcheck/flows/HealthCheckNotaryClientFlow.kt +++ b/tools/notaryhealthcheck/src/main/kotlin/net/corda/notaryhealthcheck/flows/HealthCheckNotaryClientFlow.kt @@ -2,9 +2,11 @@ package net.corda.notaryhealthcheck.flows import co.paralleluniverse.fibers.Suspendable import net.corda.core.crypto.TransactionSignature +import net.corda.core.flows.NotarisationRequest import net.corda.core.flows.NotaryError import net.corda.core.flows.NotaryException import net.corda.core.flows.NotaryFlow +import net.corda.core.internal.generateSignature import net.corda.core.transactions.SignedTransaction /** @@ -35,19 +37,7 @@ class HealthCheckNotaryClientFlow( var signatures: List = emptyList() parties.forEach { nodeLegalIdentity -> logger.info("Sending notarisation request to: $nodeLegalIdentity") - val response = try { - val session = initiateFlow(nodeLegalIdentity) - if (serviceHub.networkMapCache.isValidatingNotary(notaryParty)) { - sendAndReceiveValidating(session) - } else { - sendAndReceiveNonValidating(nodeLegalIdentity, session) - } - } catch (e: NotaryException) { - if (e.error is NotaryError.Conflict) { - (e.error as NotaryError.Conflict).conflict.verified() - } - throw e - } + val response = notarise(nodeLegalIdentity) signatures = validateResponse(response, notaryParty) logger.info("Received a valid signature from $nodeLegalIdentity, signed by $notaryParty") } diff --git a/tools/scripts/upgrade-test-packages.ps1 b/tools/scripts/upgrade-test-packages.ps1 new file mode 100644 index 0000000000..79028809a2 --- /dev/null +++ b/tools/scripts/upgrade-test-packages.ps1 @@ -0,0 +1,4 @@ +Get-ChildItem $args[0] -Include *.kt, *.java -Recurse | +Foreach-Object { + (Get-Content $_.FullName) -replace 'net.corda.testing.(\*|generateStateRef|freeLocalHostAndPort|getFreeLocalPorts|getTestPartyAndCertificate|TestIdentity|chooseIdentity|singleIdentity|TEST_TX_TIME|DUMMY_NOTARY_NAME|DUMMY_BANK_A_NAME|DUMMY_BANK_B_NAME|DUMMY_BANK_C_NAME|BOC_NAME|ALICE_NAME|BOB_NAME|CHARLIE_NAME|DEV_INTERMEDIATE_CA|DEV_ROOT_CA|dummyCommand|DummyCommandData|MAX_MESSAGE_SIZE|SerializationEnvironmentRule|setGlobalSerialization|expect|sequence|parallel|replicate|genericExpectEvents)', 'net.corda.testing.core.$1' -replace 'net.corda.testing.FlowStackSnapshotFactoryImpl', 'net.corda.testing.services.FlowStackSnapshotFactoryImpl' -join "`r`n" | Set-Content -NoNewline -Force $_.FullName +} \ No newline at end of file diff --git a/verifier/src/integration-test/kotlin/net/corda/verifier/GeneratedLedger.kt b/verifier/src/integration-test/kotlin/net/corda/verifier/GeneratedLedger.kt index afc019ed9a..a07bc0c1ea 100644 --- a/verifier/src/integration-test/kotlin/net/corda/verifier/GeneratedLedger.kt +++ b/verifier/src/integration-test/kotlin/net/corda/verifier/GeneratedLedger.kt @@ -1,17 +1,27 @@ package net.corda.verifier +import com.nhaarman.mockito_kotlin.any +import com.nhaarman.mockito_kotlin.doAnswer +import com.nhaarman.mockito_kotlin.whenever import net.corda.client.mock.Generator import net.corda.core.contracts.* +import net.corda.core.cordapp.CordappProvider import net.corda.core.crypto.SecureHash import net.corda.core.crypto.entropyToKeyPair import net.corda.core.identity.AbstractParty import net.corda.core.identity.AnonymousParty import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party +import net.corda.core.node.NetworkParameters +import net.corda.core.node.ServicesForResolution +import net.corda.core.node.services.AttachmentStorage +import net.corda.core.node.services.IdentityService import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.WireTransaction import net.corda.nodeapi.internal.serialization.GeneratedAttachment +import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.contracts.DummyContract +import net.corda.testing.internal.rigorousMock import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY import java.math.BigInteger import java.security.PublicKey @@ -29,25 +39,36 @@ data class GeneratedLedger( val attachments: Set, val identities: Set ) { - val hashTransactionMap: Map by lazy { transactions.associateBy(WireTransaction::id) } - val attachmentMap: Map by lazy { attachments.associateBy(Attachment::id) } - val identityMap: Map by lazy { identities.associateBy(Party::owningKey) } - val contractAttachmentMap: Map by lazy { + private val hashTransactionMap: Map by lazy { transactions.associateBy(WireTransaction::id) } + private val attachmentMap: Map by lazy { attachments.associateBy(Attachment::id) } + private val identityMap: Map by lazy { identities.associateBy(Party::owningKey) } + private val contractAttachmentMap: Map by lazy { attachments.mapNotNull { it as? ContractAttachment }.associateBy { it.contract } } + private val services = object : ServicesForResolution { + override fun loadState(stateRef: StateRef): TransactionState<*> { + return hashTransactionMap[stateRef.txhash]?.outputs?.get(stateRef.index) ?: throw TransactionResolutionException(stateRef.txhash) + } + override val identityService = rigorousMock().apply { + doAnswer { identityMap[it.arguments[0]] }.whenever(this).partyFromKey(any()) + } + override val attachments = rigorousMock().apply { + doAnswer { attachmentMap[it.arguments[0]] }.whenever(this).openAttachment(any()) + } + override val cordappProvider = rigorousMock().apply { + doAnswer { contractAttachmentMap[it.arguments[0]]?.id }.whenever(this).getContractAttachmentID(any()) + } + override val networkParameters = testNetworkParameters() + } + companion object { val empty = GeneratedLedger(emptyList(), emptyMap(), emptySet(), emptySet()) val contractAttachment = ContractAttachment(GeneratedAttachment(EMPTY_BYTE_ARRAY), DummyContract.PROGRAM_ID) } fun resolveWireTransaction(transaction: WireTransaction): LedgerTransaction { - return transaction.toLedgerTransaction( - resolveIdentity = { identityMap[it] }, - resolveAttachment = { attachmentMap[it] }, - resolveStateRef = { hashTransactionMap[it.txhash]?.outputs?.get(it.index) }, - resolveContractAttachment = { contractAttachmentMap[it.contract]?.id } - ) + return transaction.toLedgerTransaction(services) } val attachmentsGenerator: Generator> by lazy { diff --git a/verify-enclave/src/integration-test/kotlin/com/r3/enclaves/verify/NativeSgxApiTest.kt b/verify-enclave/src/integration-test/kotlin/com/r3/enclaves/verify/NativeSgxApiTest.kt index 70b28c4aa0..ac42cfb50e 100644 --- a/verify-enclave/src/integration-test/kotlin/com/r3/enclaves/verify/NativeSgxApiTest.kt +++ b/verify-enclave/src/integration-test/kotlin/com/r3/enclaves/verify/NativeSgxApiTest.kt @@ -38,9 +38,9 @@ class NativeSgxApiTest { val MINI_CORP_PUBKEY = TestIdentity(CordaX500Name("MiniCorp", "London", "GB")).keyPair.public } - private val ledgerServices = MockServices(emptyList(), rigorousMock().also { + private val ledgerServices = MockServices(emptyList(), NativeSgxApiTest.MEGA_CORP.name, rigorousMock().also { doReturn(NativeSgxApiTest.MEGA_CORP).whenever(it).partyFromKey(NativeSgxApiTest.MEGA_CORP_PUBKEY) - }, NativeSgxApiTest.MEGA_CORP.name) + }) @Ignore("The SGX code is not part of the standard build yet") @Test diff --git a/verify-enclave/src/test/kotlin/com/r3/enclaves/txverify/EnclaveletTest.kt b/verify-enclave/src/test/kotlin/com/r3/enclaves/txverify/EnclaveletTest.kt index 4b3062f1d6..c444635595 100644 --- a/verify-enclave/src/test/kotlin/com/r3/enclaves/txverify/EnclaveletTest.kt +++ b/verify-enclave/src/test/kotlin/com/r3/enclaves/txverify/EnclaveletTest.kt @@ -39,9 +39,9 @@ class EnclaveletTest { @JvmField val testSerialization = SerializationEnvironmentRule() - private val ledgerServices = MockServices(emptyList(), rigorousMock().also { + private val ledgerServices = MockServices(emptyList(), MEGA_CORP.name, rigorousMock().also { doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY) - }, MEGA_CORP.name) + }) @Ignore("Pending Gradle bug: https://github.com/gradle/gradle/issues/2657") @Test diff --git a/webserver/build.gradle b/webserver/build.gradle index e0f66a360c..3715cfaf67 100644 --- a/webserver/build.gradle +++ b/webserver/build.gradle @@ -38,7 +38,6 @@ dependencies { compile "org.eclipse.jetty:jetty-servlet:$jetty_version" compile "org.eclipse.jetty:jetty-webapp:$jetty_version" compile "javax.servlet:javax.servlet-api:3.1.0" - compile "org.jolokia:jolokia-war:$jolokia_version" compile "commons-fileupload:commons-fileupload:$fileupload_version" // Log4J: logging framework (with SLF4J bindings) diff --git a/webserver/src/main/kotlin/net/corda/webserver/WebServerConfig.kt b/webserver/src/main/kotlin/net/corda/webserver/WebServerConfig.kt index 1f74da48ac..157b5f3cdd 100644 --- a/webserver/src/main/kotlin/net/corda/webserver/WebServerConfig.kt +++ b/webserver/src/main/kotlin/net/corda/webserver/WebServerConfig.kt @@ -13,7 +13,6 @@ import java.nio.file.Path class WebServerConfig(override val baseDirectory: Path, val config: Config) : NodeSSLConfiguration { override val keyStorePassword: String by config override val trustStorePassword: String by config - val exportJMXto: String get() = "http" val useHTTPS: Boolean by config val myLegalName: String by config val rpcAddress: NetworkHostAndPort by lazy {