mirror of
https://github.com/corda/corda.git
synced 2025-06-15 21:58:17 +00:00
Merge branch 'master' into colljos-merge-230418
This commit is contained in:
@ -881,6 +881,9 @@ public final class net.corda.core.crypto.CryptoUtils extends java.lang.Object
|
||||
public final boolean verify(byte[])
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.crypto.DigitalSignature withoutKey()
|
||||
##
|
||||
public final class net.corda.core.crypto.DummySecureRandom extends java.security.SecureRandom
|
||||
public static final net.corda.core.crypto.DummySecureRandom INSTANCE
|
||||
##
|
||||
public abstract class net.corda.core.crypto.MerkleTree extends java.lang.Object
|
||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.SecureHash getHash()
|
||||
public static final net.corda.core.crypto.MerkleTree$Companion Companion
|
||||
@ -1200,11 +1203,14 @@ public static final class net.corda.core.flows.FinalityFlow$Companion extends ja
|
||||
@org.jetbrains.annotations.NotNull public net.corda.core.utilities.ProgressTracker childProgressTracker()
|
||||
public static final net.corda.core.flows.FinalityFlow$Companion$NOTARISING INSTANCE
|
||||
##
|
||||
@net.corda.core.serialization.CordaSerializable public class net.corda.core.flows.FlowException extends net.corda.core.CordaException
|
||||
@net.corda.core.serialization.CordaSerializable public class net.corda.core.flows.FlowException extends net.corda.core.CordaException implements net.corda.core.flows.IdentifiableException
|
||||
public <init>()
|
||||
public <init>(String)
|
||||
public <init>(String, Throwable)
|
||||
public <init>(Throwable)
|
||||
@org.jetbrains.annotations.Nullable public Long getErrorId()
|
||||
@org.jetbrains.annotations.Nullable public final Long getOriginalErrorId()
|
||||
public final void setOriginalErrorId(Long)
|
||||
##
|
||||
@net.corda.core.serialization.CordaSerializable public final class net.corda.core.flows.FlowInfo extends java.lang.Object
|
||||
public <init>(int, String)
|
||||
@ -1270,7 +1276,6 @@ public abstract class net.corda.core.flows.FlowLogic extends java.lang.Object
|
||||
public final void checkFlowPermission(String, Map)
|
||||
@co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.Nullable public final net.corda.core.flows.FlowStackSnapshot flowStackSnapshot()
|
||||
@org.jetbrains.annotations.Nullable public static final net.corda.core.flows.FlowLogic getCurrentTopLevel()
|
||||
@co.paralleluniverse.fibers.Suspendable @kotlin.Deprecated @org.jetbrains.annotations.NotNull public final net.corda.core.flows.FlowInfo getFlowInfo(net.corda.core.identity.Party)
|
||||
@org.jetbrains.annotations.NotNull public final org.slf4j.Logger getLogger()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party getOurIdentity()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.identity.PartyAndCertificate getOurIdentityAndCert()
|
||||
@ -1279,13 +1284,13 @@ public abstract class net.corda.core.flows.FlowLogic extends java.lang.Object
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.node.ServiceHub getServiceHub()
|
||||
@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 @kotlin.Deprecated @org.jetbrains.annotations.NotNull public net.corda.core.utilities.UntrustworthyData receive(Class, net.corda.core.identity.Party)
|
||||
@co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public List receiveAll(Class, List)
|
||||
@co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public List receiveAll(Class, List, boolean)
|
||||
@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)
|
||||
@co.paralleluniverse.fibers.Suspendable @kotlin.Deprecated public void send(net.corda.core.identity.Party, Object)
|
||||
@co.paralleluniverse.fibers.Suspendable @kotlin.Deprecated @org.jetbrains.annotations.NotNull public net.corda.core.utilities.UntrustworthyData sendAndReceive(Class, net.corda.core.identity.Party, Object)
|
||||
@co.paralleluniverse.fibers.Suspendable public static final void sleep(java.time.Duration)
|
||||
@co.paralleluniverse.fibers.Suspendable public static final void sleep(java.time.Duration, boolean)
|
||||
@co.paralleluniverse.fibers.Suspendable public Object subFlow(net.corda.core.flows.FlowLogic)
|
||||
@org.jetbrains.annotations.Nullable public final net.corda.core.messaging.DataFeed track()
|
||||
@org.jetbrains.annotations.Nullable public final net.corda.core.messaging.DataFeed trackStepsTree()
|
||||
@ -1297,6 +1302,7 @@ public abstract class net.corda.core.flows.FlowLogic extends java.lang.Object
|
||||
public static final class net.corda.core.flows.FlowLogic$Companion extends java.lang.Object
|
||||
@org.jetbrains.annotations.Nullable public final net.corda.core.flows.FlowLogic getCurrentTopLevel()
|
||||
@co.paralleluniverse.fibers.Suspendable public final void sleep(java.time.Duration)
|
||||
@co.paralleluniverse.fibers.Suspendable public final void sleep(java.time.Duration, boolean)
|
||||
##
|
||||
@net.corda.core.DoNotImplement @net.corda.core.serialization.CordaSerializable public interface net.corda.core.flows.FlowLogicRef
|
||||
##
|
||||
@ -1341,6 +1347,9 @@ public static final class net.corda.core.flows.FlowStackSnapshot$Frame extends j
|
||||
public int hashCode()
|
||||
@org.jetbrains.annotations.NotNull public String toString()
|
||||
##
|
||||
public interface net.corda.core.flows.IdentifiableException
|
||||
@javax.annotation.Nullable public Long getErrorId()
|
||||
##
|
||||
@net.corda.core.serialization.CordaSerializable public final class net.corda.core.flows.IllegalFlowLogicException extends java.lang.IllegalArgumentException
|
||||
public <init>(Class, String)
|
||||
public <init>(String, String)
|
||||
@ -1589,9 +1598,10 @@ public final class net.corda.core.flows.TransactionParts extends java.lang.Objec
|
||||
public int hashCode()
|
||||
public String toString()
|
||||
##
|
||||
@net.corda.core.serialization.CordaSerializable public final class net.corda.core.flows.UnexpectedFlowEndException extends net.corda.core.CordaRuntimeException
|
||||
public <init>(String)
|
||||
public <init>(String, Throwable)
|
||||
@net.corda.core.serialization.CordaSerializable public final class net.corda.core.flows.UnexpectedFlowEndException extends net.corda.core.CordaRuntimeException implements net.corda.core.flows.IdentifiableException
|
||||
public <init>(String, Throwable, long)
|
||||
@org.jetbrains.annotations.NotNull public Long getErrorId()
|
||||
public final long getOriginalErrorId()
|
||||
##
|
||||
@net.corda.core.DoNotImplement @net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.identity.AbstractParty extends java.lang.Object
|
||||
public <init>(java.security.PublicKey)
|
||||
@ -1687,6 +1697,7 @@ public final class net.corda.core.identity.IdentityUtils extends java.lang.Objec
|
||||
@kotlin.Deprecated @net.corda.core.messaging.RPCReturnsObservables @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.DataFeed internalVerifiedTransactionsFeed()
|
||||
@kotlin.Deprecated @org.jetbrains.annotations.NotNull public abstract List internalVerifiedTransactionsSnapshot()
|
||||
public abstract boolean isFlowsDrainingModeEnabled()
|
||||
public abstract boolean killFlow(net.corda.core.flows.StateMachineRunId)
|
||||
@net.corda.core.messaging.RPCReturnsObservables @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.DataFeed networkMapFeed()
|
||||
@org.jetbrains.annotations.NotNull public abstract List networkMapSnapshot()
|
||||
@net.corda.core.messaging.RPCReturnsObservables @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.DataFeed networkParametersFeed()
|
||||
@ -2105,6 +2116,7 @@ public final class net.corda.core.node.services.TimeWindowChecker extends java.l
|
||||
@org.jetbrains.annotations.Nullable public abstract net.corda.core.transactions.SignedTransaction getTransaction(net.corda.core.crypto.SecureHash)
|
||||
@org.jetbrains.annotations.NotNull public abstract rx.Observable getUpdates()
|
||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.DataFeed track()
|
||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.concurrent.CordaFuture trackTransaction(net.corda.core.crypto.SecureHash)
|
||||
##
|
||||
@net.corda.core.DoNotImplement public interface net.corda.core.node.services.TransactionVerifierService
|
||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.concurrent.CordaFuture verify(net.corda.core.transactions.LedgerTransaction)
|
||||
@ -2837,6 +2849,7 @@ public final class net.corda.core.schemas.CommonSchema extends java.lang.Object
|
||||
public static final net.corda.core.schemas.CommonSchema INSTANCE
|
||||
##
|
||||
public final class net.corda.core.schemas.CommonSchemaV1 extends net.corda.core.schemas.MappedSchema
|
||||
@org.jetbrains.annotations.NotNull protected String getMigrationResource()
|
||||
public static final net.corda.core.schemas.CommonSchemaV1 INSTANCE
|
||||
##
|
||||
@javax.persistence.MappedSuperclass @net.corda.core.serialization.CordaSerializable public static class net.corda.core.schemas.CommonSchemaV1$FungibleState extends net.corda.core.schemas.PersistentState
|
||||
@ -2867,6 +2880,7 @@ public final class net.corda.core.schemas.CommonSchemaV1 extends net.corda.core.
|
||||
public class net.corda.core.schemas.MappedSchema extends java.lang.Object
|
||||
public <init>(Class, int, Iterable)
|
||||
@org.jetbrains.annotations.NotNull public final Iterable getMappedTypes()
|
||||
@org.jetbrains.annotations.Nullable protected String getMigrationResource()
|
||||
@org.jetbrains.annotations.NotNull public final String getName()
|
||||
public final int getVersion()
|
||||
@org.jetbrains.annotations.NotNull public String toString()
|
||||
@ -3640,6 +3654,10 @@ public static final class net.corda.core.utilities.OpaqueBytes$Companion extends
|
||||
public interface net.corda.core.utilities.PropertyDelegate
|
||||
public abstract Object getValue(Object, kotlin.reflect.KProperty)
|
||||
##
|
||||
public final class net.corda.core.utilities.SgxSupport extends java.lang.Object
|
||||
public static final boolean isInsideEnclave()
|
||||
public static final net.corda.core.utilities.SgxSupport INSTANCE
|
||||
##
|
||||
@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.utilities.Try extends java.lang.Object
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.utilities.Try combine(net.corda.core.utilities.Try, kotlin.jvm.functions.Function2)
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.utilities.Try flatMap(kotlin.jvm.functions.Function1)
|
||||
@ -3686,6 +3704,7 @@ public static interface net.corda.core.utilities.UntrustworthyData$Validator ext
|
||||
@co.paralleluniverse.fibers.Suspendable public abstract Object validate(Object)
|
||||
##
|
||||
public final class net.corda.core.utilities.UntrustworthyDataKt extends java.lang.Object
|
||||
@org.jetbrains.annotations.NotNull public static final net.corda.core.utilities.UntrustworthyData checkPayloadIs(net.corda.core.serialization.SerializedBytes, Class)
|
||||
public static final Object unwrap(net.corda.core.utilities.UntrustworthyData, kotlin.jvm.functions.Function1)
|
||||
##
|
||||
public final class net.corda.core.utilities.UuidGenerator extends java.lang.Object
|
||||
@ -3882,11 +3901,12 @@ public final class net.corda.testing.driver.Driver extends java.lang.Object
|
||||
##
|
||||
public final class net.corda.testing.driver.DriverParameters extends java.lang.Object
|
||||
public <init>()
|
||||
public <init>(boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, Map, boolean, boolean, boolean, List, List, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters)
|
||||
public <init>(boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, Map, boolean, boolean, boolean, boolean, List, List, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters)
|
||||
public final boolean component1()
|
||||
@org.jetbrains.annotations.NotNull public final List component10()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.JmxPolicy component11()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.node.NetworkParameters component12()
|
||||
@org.jetbrains.annotations.NotNull public final List component11()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.JmxPolicy component12()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.node.NetworkParameters component13()
|
||||
@org.jetbrains.annotations.NotNull public final java.nio.file.Path component2()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.PortAllocation component3()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.PortAllocation component4()
|
||||
@ -3894,12 +3914,13 @@ public final class net.corda.testing.driver.DriverParameters extends java.lang.O
|
||||
public final boolean component6()
|
||||
public final boolean component7()
|
||||
public final boolean component8()
|
||||
@org.jetbrains.annotations.NotNull public final List component9()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters copy(boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, Map, boolean, boolean, boolean, List, List, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters)
|
||||
public final boolean component9()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters copy(boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, Map, boolean, boolean, boolean, boolean, List, List, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters)
|
||||
public boolean equals(Object)
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.PortAllocation getDebugPortAllocation()
|
||||
@org.jetbrains.annotations.NotNull public final java.nio.file.Path getDriverDirectory()
|
||||
@org.jetbrains.annotations.NotNull public final List getExtraCordappPackagesToScan()
|
||||
public final boolean getInitialiseSerialization()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.JmxPolicy getJmxPolicy()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.core.node.NetworkParameters getNetworkParameters()
|
||||
@org.jetbrains.annotations.NotNull public final List getNotarySpecs()
|
||||
@ -3914,6 +3935,7 @@ public final class net.corda.testing.driver.DriverParameters extends java.lang.O
|
||||
@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)
|
||||
@ -3952,16 +3974,18 @@ public final class net.corda.testing.driver.JmxPolicy extends java.lang.Object
|
||||
##
|
||||
public final class net.corda.testing.driver.NodeParameters extends java.lang.Object
|
||||
public <init>()
|
||||
public <init>(net.corda.core.identity.CordaX500Name, List, net.corda.testing.driver.VerifierType, Map, Boolean, String)
|
||||
public <init>(net.corda.core.identity.CordaX500Name, List, net.corda.testing.driver.VerifierType, Map, Boolean, String, String)
|
||||
@org.jetbrains.annotations.Nullable public final net.corda.core.identity.CordaX500Name component1()
|
||||
@org.jetbrains.annotations.NotNull public final List component2()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.VerifierType component3()
|
||||
@org.jetbrains.annotations.NotNull public final Map component4()
|
||||
@org.jetbrains.annotations.Nullable public final Boolean component5()
|
||||
@org.jetbrains.annotations.NotNull public final String component6()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.NodeParameters copy(net.corda.core.identity.CordaX500Name, List, net.corda.testing.driver.VerifierType, Map, Boolean, String)
|
||||
@org.jetbrains.annotations.Nullable public final String component7()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.NodeParameters copy(net.corda.core.identity.CordaX500Name, List, net.corda.testing.driver.VerifierType, Map, Boolean, String, String)
|
||||
public boolean equals(Object)
|
||||
@org.jetbrains.annotations.NotNull public final Map getCustomOverrides()
|
||||
@org.jetbrains.annotations.Nullable public final String getLogLevel()
|
||||
@org.jetbrains.annotations.NotNull public final String getMaximumHeapSize()
|
||||
@org.jetbrains.annotations.Nullable public final net.corda.core.identity.CordaX500Name getProvidedName()
|
||||
@org.jetbrains.annotations.NotNull public final List getRpcUsers()
|
||||
@ -3970,6 +3994,7 @@ public final class net.corda.testing.driver.NodeParameters extends java.lang.Obj
|
||||
public int hashCode()
|
||||
public String toString()
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.NodeParameters withCustomOverrides(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)
|
||||
@ -4222,9 +4247,10 @@ public class net.corda.testing.node.MockServices extends java.lang.Object implem
|
||||
@org.jetbrains.annotations.NotNull public java.sql.Connection jdbcSession()
|
||||
@org.jetbrains.annotations.NotNull public net.corda.core.contracts.TransactionState loadState(net.corda.core.contracts.StateRef)
|
||||
@org.jetbrains.annotations.NotNull public Set loadStates(Set)
|
||||
@org.jetbrains.annotations.NotNull public static final Properties makeTestDataSourceProperties(String)
|
||||
@org.jetbrains.annotations.NotNull public static final Properties makeTestDataSourceProperties(String, String, kotlin.jvm.functions.Function2)
|
||||
@org.jetbrains.annotations.NotNull public static final kotlin.Pair makeTestDatabaseAndMockServices(List, net.corda.core.node.services.IdentityService, net.corda.testing.core.TestIdentity, net.corda.core.node.NetworkParameters, java.security.KeyPair...)
|
||||
@org.jetbrains.annotations.NotNull public static final kotlin.Pair makeTestDatabaseAndMockServices(List, net.corda.core.node.services.IdentityService, net.corda.testing.core.TestIdentity, java.security.KeyPair...)
|
||||
@org.jetbrains.annotations.NotNull public static final net.corda.nodeapi.internal.persistence.DatabaseConfig makeTestDatabaseProperties(String)
|
||||
public void recordTransactions(Iterable)
|
||||
public void recordTransactions(net.corda.core.node.StatesToRecord, Iterable)
|
||||
public void recordTransactions(net.corda.core.transactions.SignedTransaction, net.corda.core.transactions.SignedTransaction...)
|
||||
@ -4238,9 +4264,10 @@ public class net.corda.testing.node.MockServices extends java.lang.Object implem
|
||||
public static final net.corda.testing.node.MockServices$Companion Companion
|
||||
##
|
||||
public static final class net.corda.testing.node.MockServices$Companion extends java.lang.Object
|
||||
@org.jetbrains.annotations.NotNull public final Properties makeTestDataSourceProperties(String)
|
||||
@org.jetbrains.annotations.NotNull public final Properties makeTestDataSourceProperties(String, String, kotlin.jvm.functions.Function2)
|
||||
@org.jetbrains.annotations.NotNull public final kotlin.Pair makeTestDatabaseAndMockServices(List, net.corda.core.node.services.IdentityService, net.corda.testing.core.TestIdentity, net.corda.core.node.NetworkParameters, java.security.KeyPair...)
|
||||
@org.jetbrains.annotations.NotNull public final kotlin.Pair makeTestDatabaseAndMockServices(List, net.corda.core.node.services.IdentityService, net.corda.testing.core.TestIdentity, java.security.KeyPair...)
|
||||
@org.jetbrains.annotations.NotNull public final net.corda.nodeapi.internal.persistence.DatabaseConfig makeTestDatabaseProperties(String)
|
||||
##
|
||||
public static final class net.corda.testing.node.MockServices$Companion$makeTestDatabaseAndMockServices$mockService$1$1 extends net.corda.testing.node.MockServices
|
||||
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.VaultService getVaultService()
|
||||
@ -4249,6 +4276,8 @@ public static final class net.corda.testing.node.MockServices$Companion$makeTest
|
||||
##
|
||||
public final class net.corda.testing.node.MockServicesKt extends java.lang.Object
|
||||
@org.jetbrains.annotations.NotNull public static final net.corda.core.serialization.SerializeAsToken createMockCordaService(net.corda.testing.node.MockServices, kotlin.jvm.functions.Function1)
|
||||
@org.jetbrains.annotations.NotNull public static final com.typesafe.config.Config databaseProviderDataSourceConfig(String, String)
|
||||
@org.jetbrains.annotations.NotNull public static final com.typesafe.config.Config inMemoryH2DataSourceConfig(String, String)
|
||||
@org.jetbrains.annotations.NotNull public static final net.corda.node.services.identity.InMemoryIdentityService makeTestIdentityService(net.corda.core.identity.PartyAndCertificate...)
|
||||
##
|
||||
public static final class net.corda.testing.node.MockServicesKt$createMockCordaService$MockAppServiceHubImpl extends java.lang.Object implements net.corda.core.node.AppServiceHub, net.corda.core.node.ServiceHub
|
||||
@ -4632,6 +4661,25 @@ public final class net.corda.testing.core.TestUtils extends java.lang.Object
|
||||
@org.jetbrains.annotations.NotNull public static final net.corda.core.identity.Party singleIdentity(net.corda.core.node.NodeInfo)
|
||||
@org.jetbrains.annotations.NotNull public static final net.corda.core.identity.PartyAndCertificate singleIdentityAndCert(net.corda.core.node.NodeInfo)
|
||||
##
|
||||
public final class net.corda.testing.database.DatabaseConstants extends java.lang.Object
|
||||
@org.jetbrains.annotations.NotNull public static final String DATA_SOURCE_CLASSNAME = "dataSourceProperties.dataSourceClassName"
|
||||
@org.jetbrains.annotations.NotNull public static final String DATA_SOURCE_PASSWORD = "dataSourceProperties.dataSource.password"
|
||||
@org.jetbrains.annotations.NotNull public static final String DATA_SOURCE_URL = "dataSourceProperties.dataSource.url"
|
||||
@org.jetbrains.annotations.NotNull public static final String DATA_SOURCE_USER = "dataSourceProperties.dataSource.user"
|
||||
public static final net.corda.testing.database.DatabaseConstants INSTANCE
|
||||
@org.jetbrains.annotations.NotNull public static final String SCHEMA = "database.schema"
|
||||
@org.jetbrains.annotations.NotNull public static final String TRANSACTION_ISOLATION_LEVEL = "database.transactionIsolationLevel"
|
||||
##
|
||||
public final class net.corda.testing.database.DbScriptRunner extends java.lang.Object
|
||||
@org.jetbrains.annotations.NotNull public final List merge(List, String)
|
||||
@org.jetbrains.annotations.NotNull public final List merge(List, List)
|
||||
public final void runDbScript(String, String, List)
|
||||
public static final net.corda.testing.database.DbScriptRunner INSTANCE
|
||||
##
|
||||
public final class net.corda.testing.database.ListPopulator extends java.lang.Object implements org.springframework.jdbc.datasource.init.DatabasePopulator
|
||||
public <init>(boolean, boolean, List)
|
||||
public void populate(java.sql.Connection)
|
||||
##
|
||||
public final class net.corda.testing.dsl.AttachmentResolutionException extends net.corda.core.flows.FlowException
|
||||
public <init>(net.corda.core.crypto.SecureHash)
|
||||
##
|
||||
@ -4767,6 +4815,7 @@ public static final class net.corda.testing.dsl.TestTransactionDSLInterpreter$se
|
||||
@org.jetbrains.annotations.NotNull public net.corda.testing.dsl.EnforceVerifyOrFail failsWith(String)
|
||||
@org.jetbrains.annotations.NotNull public net.corda.testing.dsl.LedgerDSLInterpreter getLedgerInterpreter()
|
||||
public final void input(String)
|
||||
public final void input(String, String)
|
||||
public final void input(String, net.corda.core.contracts.ContractState)
|
||||
public void input(net.corda.core.contracts.StateRef)
|
||||
public final void output(String, int, net.corda.core.contracts.ContractState)
|
||||
|
@ -1,4 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!--
|
||||
~ R3 Proprietary and Confidential
|
||||
~
|
||||
~ Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
~
|
||||
~ The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
~
|
||||
~ Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
-->
|
||||
|
||||
<suppressions xmlns="https://jeremylong.github.io/DependencyCheck/dependency-suppression.1.1.xsd">
|
||||
<!-- Example of a suppressed library -->
|
||||
<!-- The suppress node can be generated from the HTML report by using the 'suppress' option for each vulnerability found
|
||||
|
9
.gitignore
vendored
9
.gitignore
vendored
@ -107,3 +107,12 @@ virtualenv/
|
||||
PLAN
|
||||
NOTES
|
||||
TODO
|
||||
|
||||
# SGX
|
||||
/sgx-jvm/jdk8u/
|
||||
/sgx-jvm/avian/
|
||||
/sgx-jvm/linux-sgx/
|
||||
/sgx-jvm/jvm-enclave/proguard.jar
|
||||
|
||||
# HSM keys
|
||||
*.key
|
||||
|
81
.idea/compiler.xml
generated
81
.idea/compiler.xml
generated
@ -15,22 +15,33 @@
|
||||
<module name="behave_test" target="1.8" />
|
||||
<module name="bootstrapper_main" target="1.8" />
|
||||
<module name="bootstrapper_test" target="1.8" />
|
||||
<module name="bridge_integrationTest" target="1.8" />
|
||||
<module name="bridge_main" target="1.8" />
|
||||
<module name="bridge_test" target="1.8" />
|
||||
<module name="bridgecapsule_main" target="1.6" />
|
||||
<module name="bridgecapsule_test" target="1.6" />
|
||||
<module name="bridges_integrationTest" target="1.8" />
|
||||
<module name="bridges_main" target="1.8" />
|
||||
<module name="bridges_test" target="1.8" />
|
||||
<module name="buildSrc_main" target="1.8" />
|
||||
<module name="buildSrc_test" target="1.8" />
|
||||
<module name="business-network-demo_integrationTest" target="1.8" />
|
||||
<module name="business-network-demo_main" target="1.8" />
|
||||
<module name="business-network-demo_test" target="1.8" />
|
||||
<module name="canonicalizer_main" target="1.8" />
|
||||
<module name="canonicalizer_test" target="1.8" />
|
||||
<module name="capsule-crr-submission_main" target="1.8" />
|
||||
<module name="capsule-crr-submission_test" target="1.8" />
|
||||
<module name="capsule-hsm-cert-generator_main" target="1.8" />
|
||||
<module name="capsule-hsm-cert-generator_test" target="1.8" />
|
||||
<module name="capsule-hsm_main" target="1.8" />
|
||||
<module name="capsule-hsm_test" target="1.8" />
|
||||
<module name="client_main" target="1.8" />
|
||||
<module name="client_test" target="1.8" />
|
||||
<module name="confidential-identities_main" target="1.8" />
|
||||
<module name="confidential-identities_test" target="1.8" />
|
||||
<module name="corda-core_integrationTest" target="1.8" />
|
||||
<module name="corda-core_smokeTest" target="1.8" />
|
||||
<module name="corda-finance_integrationTest" target="1.8" />
|
||||
<module name="corda-project_main" target="1.8" />
|
||||
<module name="corda-project_test" target="1.8" />
|
||||
<module name="corda-webserver_integrationTest" target="1.8" />
|
||||
<module name="corda-webserver_main" target="1.8" />
|
||||
<module name="corda-webserver_test" target="1.8" />
|
||||
<module name="cordapp-configuration_main" target="1.8" />
|
||||
<module name="cordapp-configuration_test" target="1.8" />
|
||||
<module name="cordapp_integrationTest" target="1.8" />
|
||||
@ -38,20 +49,16 @@
|
||||
<module name="cordapp_test" target="1.8" />
|
||||
<module name="cordform-common_main" target="1.8" />
|
||||
<module name="cordform-common_test" target="1.8" />
|
||||
<module name="cordformation_main" target="1.8" />
|
||||
<module name="cordformation_runnodes" target="1.8" />
|
||||
<module name="cordformation_test" target="1.8" />
|
||||
<module name="core_extraResource" target="1.8" />
|
||||
<module name="core_integrationTest" target="1.8" />
|
||||
<module name="core_main" target="1.8" />
|
||||
<module name="core_smokeTest" target="1.8" />
|
||||
<module name="core_smokeTestPlugins" target="1.8" />
|
||||
<module name="core_test" target="1.8" />
|
||||
<module name="dbmigration_main" target="1.8" />
|
||||
<module name="dbmigration_test" target="1.8" />
|
||||
<module name="demobench_main" target="1.8" />
|
||||
<module name="demobench_test" target="1.8" />
|
||||
<module name="docs_main" target="1.8" />
|
||||
<module name="docs_source_example-code_integrationTest" target="1.8" />
|
||||
<module name="docs_source_example-code_main" target="1.8" />
|
||||
<module name="docs_source_example-code_test" target="1.8" />
|
||||
<module name="docs_test" target="1.8" />
|
||||
<module name="example-code_integrationTest" target="1.8" />
|
||||
<module name="example-code_main" target="1.8" />
|
||||
@ -67,17 +74,12 @@
|
||||
<module name="finance_integrationTest" target="1.8" />
|
||||
<module name="finance_main" target="1.8" />
|
||||
<module name="finance_test" target="1.8" />
|
||||
<module name="gradle-plugins-cordapp_main" target="1.8" />
|
||||
<module name="gradle-plugins-cordapp_test" target="1.8" />
|
||||
<module name="flow-hook_main" target="1.8" />
|
||||
<module name="flow-hook_test" target="1.8" />
|
||||
<module name="graphs_main" target="1.8" />
|
||||
<module name="graphs_test" target="1.8" />
|
||||
<module name="irs-demo-cordapp_integrationTest" target="1.8" />
|
||||
<module name="irs-demo-cordapp_main" target="1.8" />
|
||||
<module name="irs-demo-cordapp_main~1" target="1.8" />
|
||||
<module name="irs-demo-cordapp_test" target="1.8" />
|
||||
<module name="irs-demo-cordapp_test~1" target="1.8" />
|
||||
<module name="irs-demo-web_main" target="1.8" />
|
||||
<module name="irs-demo-web_test" target="1.8" />
|
||||
<module name="intellij-plugin_main" target="1.8" />
|
||||
<module name="intellij-plugin_test" target="1.8" />
|
||||
<module name="irs-demo_integrationTest" target="1.8" />
|
||||
<module name="irs-demo_main" target="1.8" />
|
||||
<module name="irs-demo_systemTest" target="1.8" />
|
||||
@ -89,12 +91,19 @@
|
||||
<module name="jfx_integrationTest" target="1.8" />
|
||||
<module name="jfx_main" target="1.8" />
|
||||
<module name="jfx_test" target="1.8" />
|
||||
<module name="jmeter_main" target="1.8" />
|
||||
<module name="jmeter_test" target="1.8" />
|
||||
<module name="kryo-hook_main" target="1.8" />
|
||||
<module name="kryo-hook_test" target="1.8" />
|
||||
<module name="loadtest_main" target="1.8" />
|
||||
<module name="loadtest_test" target="1.8" />
|
||||
<module name="mock_main" target="1.8" />
|
||||
<module name="mock_test" target="1.8" />
|
||||
<module name="network-management-capsule_main" target="1.8" />
|
||||
<module name="network-management-capsule_test" target="1.8" />
|
||||
<module name="network-management_integrationTest" target="1.8" />
|
||||
<module name="network-management_main" target="1.8" />
|
||||
<module name="network-management_test" target="1.8" />
|
||||
<module name="network-visualiser_main" target="1.8" />
|
||||
<module name="network-visualiser_test" target="1.8" />
|
||||
<module name="node-api_main" target="1.8" />
|
||||
@ -110,20 +119,32 @@
|
||||
<module name="node_test" target="1.8" />
|
||||
<module name="notary-demo_main" target="1.8" />
|
||||
<module name="notary-demo_test" target="1.8" />
|
||||
<module name="publish-utils_main" target="1.8" />
|
||||
<module name="publish-utils_test" target="1.8" />
|
||||
<module name="notaryhealthcheck_main" target="1.8" />
|
||||
<module name="notaryhealthcheck_test" target="1.8" />
|
||||
<module name="perftestcordapp_integrationTest" target="1.8" />
|
||||
<module name="perftestcordapp_main" target="1.8" />
|
||||
<module name="perftestcordapp_test" target="1.8" />
|
||||
<module name="quasar-hook_main" target="1.8" />
|
||||
<module name="quasar-hook_test" target="1.8" />
|
||||
<module name="quasar-utils_main" target="1.8" />
|
||||
<module name="quasar-utils_test" target="1.8" />
|
||||
<module name="registration-tool_integrationTest" target="1.8" />
|
||||
<module name="registration-tool_main" target="1.8" />
|
||||
<module name="registration-tool_test" target="1.8" />
|
||||
<module name="rpc_integrationTest" target="1.8" />
|
||||
<module name="rpc_main" target="1.8" />
|
||||
<module name="rpc_smokeTest" target="1.8" />
|
||||
<module name="rpc_test" target="1.8" />
|
||||
<module name="samples-business-network-demo_main" target="1.8" />
|
||||
<module name="samples-business-network-demo_test" target="1.8" />
|
||||
<module name="samples_main" target="1.8" />
|
||||
<module name="samples_test" target="1.8" />
|
||||
<module name="sandbox_main" target="1.8" />
|
||||
<module name="sandbox_test" target="1.8" />
|
||||
<module name="sgx-hsm-tool_main" target="1.8" />
|
||||
<module name="sgx-hsm-tool_test" target="1.8" />
|
||||
<module name="sgx-jvm_hsm-tool_main" target="1.8" />
|
||||
<module name="sgx-jvm_hsm-tool_test" target="1.8" />
|
||||
<module name="shell_integrationTest" target="1.8" />
|
||||
<module name="shell_main" target="1.8" />
|
||||
<module name="shell_test" target="1.8" />
|
||||
@ -132,17 +153,10 @@
|
||||
<module name="simm-valuation-demo_test" target="1.8" />
|
||||
<module name="smoke-test-utils_main" target="1.8" />
|
||||
<module name="smoke-test-utils_test" target="1.8" />
|
||||
<module name="source-example-code_integrationTest" target="1.8" />
|
||||
<module name="source-example-code_main" target="1.8" />
|
||||
<module name="source-example-code_test" target="1.8" />
|
||||
<module name="test-common_main" target="1.8" />
|
||||
<module name="test-common_test" target="1.8" />
|
||||
<module name="test-utils_integrationTest" target="1.8" />
|
||||
<module name="test-utils_main" target="1.8" />
|
||||
<module name="test-utils_test" target="1.8" />
|
||||
<module name="testing-node-driver_integrationTest" target="1.8" />
|
||||
<module name="testing-node-driver_main" target="1.8" />
|
||||
<module name="testing-node-driver_test" target="1.8" />
|
||||
<module name="testing-smoke-test-utils_main" target="1.8" />
|
||||
<module name="testing-smoke-test-utils_test" target="1.8" />
|
||||
<module name="testing-test-common_main" target="1.8" />
|
||||
@ -157,12 +171,13 @@
|
||||
<module name="verifier_integrationTest" target="1.8" />
|
||||
<module name="verifier_main" target="1.8" />
|
||||
<module name="verifier_test" target="1.8" />
|
||||
<module name="verify-enclave_integrationTest" target="1.8" />
|
||||
<module name="verify-enclave_main" target="1.8" />
|
||||
<module name="verify-enclave_test" target="1.8" />
|
||||
<module name="web_main" target="1.8" />
|
||||
<module name="web_test" target="1.8" />
|
||||
<module name="webcapsule_main" target="1.6" />
|
||||
<module name="webcapsule_test" target="1.6" />
|
||||
<module name="webserver-webcapsule_main" target="1.8" />
|
||||
<module name="webserver-webcapsule_test" target="1.8" />
|
||||
<module name="webserver_integrationTest" target="1.8" />
|
||||
<module name="webserver_main" target="1.8" />
|
||||
<module name="webserver_test" target="1.8" />
|
||||
|
21
.idea/runConfigurations/CordaPlugin.xml
generated
Normal file
21
.idea/runConfigurations/CordaPlugin.xml
generated
Normal file
@ -0,0 +1,21 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="CordaPlugin" type="GradleRunConfiguration" factoryName="Gradle" singleton="true">
|
||||
<log_file path="$PROJECT_DIR$/experimental/intellij-plugin/build/idea-sandbox/system/log/idea.log" checked="true" skipped="true" show_all="false" alias="idea.log" />
|
||||
<ExternalSystemSettings>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" value="" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list>
|
||||
<option value="runIde" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" value="" />
|
||||
</ExternalSystemSettings>
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
15
.idea/runConfigurations/Explorer___demo_nodes__flow_triage_.xml
generated
Normal file
15
.idea/runConfigurations/Explorer___demo_nodes__flow_triage_.xml
generated
Normal file
@ -0,0 +1,15 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Explorer - demo nodes (flow triage)" type="JetRunConfigurationType" factoryName="Kotlin" singleton="true">
|
||||
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
||||
<option name="VM_PARAMETERS" value="-DAMQ_DELIVERY_DELAY_MS=15000" />
|
||||
<option name="PROGRAM_PARAMETERS" value="-F" />
|
||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
|
||||
<option name="ALTERNATIVE_JRE_PATH" value="1.8" />
|
||||
<option name="PASS_PARENT_ENVS" value="true" />
|
||||
<option name="MAIN_CLASS_NAME" value="net.corda.explorer.MainKt" />
|
||||
<option name="WORKING_DIRECTORY" value="" />
|
||||
<module name="explorer_main" />
|
||||
<envs />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
@ -1,52 +0,0 @@
|
||||
# Contributing to Corda
|
||||
|
||||
To start contributing you can fork our repo and begin making pull requests. Please use
|
||||
descriptive commit messages and follow our [coding style guidelines](https://docs.corda.net/codestyle.html).
|
||||
|
||||
## Community Locations
|
||||
|
||||
* [GitHub](https://github.com/corda/corda)
|
||||
* [Forums](https://discourse.corda.net)
|
||||
* [Chat](https://slack.corda.net)
|
||||
|
||||
## Developer Certificate of Origin
|
||||
|
||||
All contributions to this project are subject to the terms of the Developer Certificate of Origin, below:
|
||||
|
||||
Developer Certificate of Origin
|
||||
Version 1.1
|
||||
|
||||
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
|
||||
1 Letterman Drive
|
||||
Suite D4700
|
||||
San Francisco, CA, 94129
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this
|
||||
license document, but changing it is not allowed.
|
||||
|
||||
|
||||
Developer's Certificate of Origin 1.1
|
||||
|
||||
By making a contribution to this project, I certify that:
|
||||
|
||||
(a) The contribution was created in whole or in part by me and I
|
||||
have the right to submit it under the open source license
|
||||
indicated in the file; or
|
||||
|
||||
(b) The contribution is based upon previous work that, to the best
|
||||
of my knowledge, is covered under an appropriate open source
|
||||
license and I have the right under that license to submit that
|
||||
work with modifications, whether created in whole or in part
|
||||
by me, under the same open source license (unless I am
|
||||
permitted to submit under a different license), as indicated
|
||||
in the file; or
|
||||
|
||||
(c) The contribution was provided directly to me by some other
|
||||
person who certified (a), (b) or (c) and I have not modified
|
||||
it.
|
||||
|
||||
(d) I understand and agree that this project and the contribution
|
||||
are public and that a record of the contribution (including all
|
||||
personal information I submit with it, including my sign-off) is
|
||||
maintained indefinitely and may be redistributed consistent with
|
||||
this project or the open source license(s) involved.
|
13
LICENSE
13
LICENSE
@ -1,13 +0,0 @@
|
||||
Copyright 2016 - 2017, R3 Limited.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
15
README.md
15
README.md
@ -1,8 +1,11 @@
|
||||

|
||||
|
||||
<a href="https://ci-master.corda.r3cev.com/viewType.html?buildTypeId=Corda_CordaBuild&tab=buildTypeStatusDiv&guest=1"><img src="https://ci.corda.r3cev.com/app/rest/builds/buildType:Corda_CordaBuild/statusIcon"/></a>
|
||||
<a href="https://ci-master.corda.r3cev.com/viewType.html?buildTypeId=CordaEnterprise_Build&tab=buildTypeStatusDiv"><img src="https://ci.corda.r3cev.com/app/rest/builds/buildType:Corda_CordaBuild/statusIcon"/></a>
|
||||
|
||||
# Corda
|
||||
# Corda Enterprise
|
||||
|
||||
Corda Enterprise is R3's closed source patch set on top of Corda Open Source. It adds features and improvements that we
|
||||
plan to charge for.
|
||||
|
||||
Corda is a decentralised database system in which nodes trust each other as little as possible.
|
||||
|
||||
@ -15,6 +18,14 @@ Corda is a decentralised database system in which nodes trust each other as litt
|
||||
* Enables the development and deployment of distributed apps called CorDapps
|
||||
* Written in [Kotlin](https://kotlinlang.org), targeting the JVM
|
||||
|
||||
## Extra features
|
||||
|
||||
* Doorman
|
||||
* SOCKS relaying
|
||||
* Flow triage screen in Explorer
|
||||
* No stupid jokes at startup
|
||||
* SGX
|
||||
|
||||
## Getting started
|
||||
|
||||
1. Read the [Getting Started](https://docs.corda.net/getting-set-up.html) documentation
|
||||
|
@ -1,4 +0,0 @@
|
||||
Corda and the Corda logo are trademarks of R3 HoldCo LLC and its affiliates.
|
||||
All rights reserved.
|
||||
|
||||
For R3 HoldCo LLC's trademark and logo usage information, please consult our Trademark Usage Policy available at https://www.r3.com/trademark-policy
|
78
bridge/bridgecapsule/build.gradle
Normal file
78
bridge/bridgecapsule/build.gradle
Normal file
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*
|
||||
* This build.gradle exists to publish our capsule (executable fat jar) to maven. It cannot be placed in the
|
||||
* bridges project because the bintray plugin cannot publish two modules from one project.
|
||||
*/
|
||||
apply plugin: 'net.corda.plugins.publish-utils'
|
||||
apply plugin: 'us.kirchmeier.capsule'
|
||||
apply plugin: 'com.jfrog.artifactory'
|
||||
|
||||
description 'Corda bridge server capsule'
|
||||
|
||||
configurations {
|
||||
runtimeArtifacts
|
||||
capsuleRuntime
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// TypeSafe Config: for simple and human friendly config files.
|
||||
capsuleRuntime "com.typesafe:config:$typesafe_config_version"
|
||||
}
|
||||
|
||||
// Force the Caplet to target Java 6. This ensures that running 'java -jar corda.jar' on any Java 6 VM upwards
|
||||
// will get as far as the Capsule version checks, meaning that if your JVM is too old, you will at least get
|
||||
// a sensible error message telling you what to do rather than a bytecode version exception that doesn't.
|
||||
// If we introduce .java files into this module that need Java 8+ then we will have to push the caplet into
|
||||
// its own module so its target can be controlled individually, but for now this suffices.
|
||||
sourceCompatibility = 1.6
|
||||
targetCompatibility = 1.6
|
||||
|
||||
jar {
|
||||
baseName 'corda-bridgeserver'
|
||||
}
|
||||
|
||||
task buildBridgeServerJar(type: FatCapsule, dependsOn: project(':bridge').jar) {
|
||||
applicationClass 'net.corda.bridge.Bridge'
|
||||
archiveName "corda-bridgeserver-${corda_release_version}.jar"
|
||||
applicationSource = files(
|
||||
project(':bridge').configurations.runtime,
|
||||
project(':bridge').jar,
|
||||
"$rootDir/config/dev/log4j2.xml",
|
||||
"$rootDir/bridge/build/resources/main/reference.conf"
|
||||
)
|
||||
from 'NOTICE' // Copy CDDL notice
|
||||
from configurations.capsuleRuntime.files.collect { zipTree(it) }
|
||||
|
||||
capsuleManifest {
|
||||
applicationVersion = corda_release_version
|
||||
javaAgents = []
|
||||
systemProperties['visualvm.display.name'] = 'Corda Bridge Server'
|
||||
minJavaVersion = '1.8.0'
|
||||
minUpdateVersion['1.8'] = java8_minUpdateVersion
|
||||
caplets = []
|
||||
|
||||
// JVM configuration:
|
||||
// - Constrain to small heap sizes to ease development on low end devices.
|
||||
// - Switch to the G1 GC which is going to be the default in Java 9 and gives low pause times/string dedup.
|
||||
jvmArgs = ['-Xmx200m', '-XX:+UseG1GC']
|
||||
}
|
||||
}
|
||||
|
||||
artifacts {
|
||||
runtimeArtifacts buildBridgeServerJar
|
||||
publish buildBridgeServerJar {
|
||||
classifier ""
|
||||
}
|
||||
}
|
||||
|
||||
publish {
|
||||
disableDefaultJar = true
|
||||
name jar.baseName
|
||||
}
|
67
bridge/build.gradle
Normal file
67
bridge/build.gradle
Normal file
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'net.corda.plugins.publish-utils'
|
||||
|
||||
description 'Corda peer bridging components'
|
||||
|
||||
configurations {
|
||||
integrationTestCompile.extendsFrom testCompile
|
||||
integrationTestRuntime.extendsFrom testRuntime
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
integrationTest {
|
||||
kotlin {
|
||||
compileClasspath += main.output + test.output
|
||||
runtimeClasspath += main.output + test.output
|
||||
srcDir file('src/integration-test/kotlin')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
processResources {
|
||||
from file("$rootDir/config/dev/log4j2.xml")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':node-api')
|
||||
|
||||
// Log4J: logging framework (with SLF4J bindings)
|
||||
compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
|
||||
compile "org.apache.logging.log4j:log4j-core:$log4j_version"
|
||||
compile "org.slf4j:jul-to-slf4j:$slf4j_version"
|
||||
|
||||
// JOpt: for command line flags.
|
||||
compile "net.sf.jopt-simple:jopt-simple:$jopt_simple_version"
|
||||
|
||||
// Manifests: for reading stuff from the manifest file
|
||||
compile "com.jcabi:jcabi-manifests:1.1"
|
||||
|
||||
integrationTestCompile project(':node-driver')
|
||||
integrationTestCompile "org.apache.curator:curator-test:${curator_version}"
|
||||
testCompile "junit:junit:$junit_version"
|
||||
testCompile project(':test-utils')
|
||||
}
|
||||
|
||||
task integrationTest(type: Test) {
|
||||
testClassesDirs = sourceSets.integrationTest.output.classesDirs
|
||||
classpath = sourceSets.integrationTest.runtimeClasspath
|
||||
}
|
||||
|
||||
jar {
|
||||
baseName 'corda-bridge-impl'
|
||||
}
|
||||
|
||||
publish {
|
||||
name jar.baseName
|
||||
}
|
@ -0,0 +1,294 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.bridge
|
||||
|
||||
import com.nhaarman.mockito_kotlin.doReturn
|
||||
import com.nhaarman.mockito_kotlin.whenever
|
||||
import net.corda.bridge.internal.BridgeInstance
|
||||
import net.corda.bridge.services.api.BridgeMode
|
||||
import net.corda.bridge.services.config.BridgeHAConfigImpl
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.serialization.SerializationDefaults
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.node.services.config.CertChainPolicyConfig
|
||||
import net.corda.node.services.config.EnterpriseConfiguration
|
||||
import net.corda.node.services.config.MutualExclusionConfiguration
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.node.services.messaging.ArtemisMessagingServer
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingClient
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.BRIDGE_CONTROL
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.BRIDGE_NOTIFY
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_PREFIX
|
||||
import net.corda.nodeapi.internal.bridging.BridgeControl
|
||||
import net.corda.nodeapi.internal.zookeeper.ZkClient
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.DUMMY_BANK_A_NAME
|
||||
import net.corda.testing.core.MAX_MESSAGE_SIZE
|
||||
import net.corda.testing.core.SerializationEnvironmentRule
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import org.apache.activemq.artemis.api.core.RoutingType
|
||||
import org.apache.activemq.artemis.api.core.SimpleString
|
||||
import org.apache.curator.test.TestingServer
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
|
||||
class BridgeIntegrationTest {
|
||||
@Rule
|
||||
@JvmField
|
||||
val tempFolder = TemporaryFolder()
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val serializationEnvironment = SerializationEnvironmentRule(true)
|
||||
|
||||
private abstract class AbstractNodeConfiguration : NodeConfiguration
|
||||
|
||||
@Test
|
||||
fun `Load simple all in one bridge and stand it up`() {
|
||||
val configResource = "/net/corda/bridge/singleprocess/bridge.conf"
|
||||
createNetworkParams(tempFolder.root.toPath())
|
||||
val config = createAndLoadConfigFromResource(tempFolder.root.toPath(), configResource)
|
||||
assertEquals(BridgeMode.SenderReceiver, config.bridgeMode)
|
||||
assertEquals(NetworkHostAndPort("localhost", 11005), config.outboundConfig!!.artemisBrokerAddress)
|
||||
assertEquals(NetworkHostAndPort("0.0.0.0", 10005), config.inboundConfig!!.listeningAddress)
|
||||
assertNull(config.floatInnerConfig)
|
||||
assertNull(config.floatOuterConfig)
|
||||
config.createBridgeKeyStores(DUMMY_BANK_A_NAME)
|
||||
val (artemisServer, artemisClient) = createArtemis()
|
||||
try {
|
||||
installBridgeControlResponder(artemisClient)
|
||||
val bridge = BridgeInstance(config, BridgeVersionInfo(1, "1.1", "Dummy", "Test"))
|
||||
val stateFollower = bridge.activeChange.toBlocking().iterator
|
||||
assertEquals(false, stateFollower.next())
|
||||
assertEquals(false, bridge.active)
|
||||
bridge.start()
|
||||
assertEquals(true, stateFollower.next())
|
||||
assertEquals(true, bridge.active)
|
||||
assertEquals(true, serverListening("localhost", 10005))
|
||||
bridge.stop()
|
||||
assertEquals(false, stateFollower.next())
|
||||
assertEquals(false, bridge.active)
|
||||
assertEquals(false, serverListening("localhost", 10005))
|
||||
} finally {
|
||||
artemisClient.stop()
|
||||
artemisServer.stop()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Load bridge (float inner) and float outer and stand them up`() {
|
||||
val bridgeFolder = tempFolder.root.toPath()
|
||||
val bridgeConfigResource = "/net/corda/bridge/withfloat/bridge/bridge.conf"
|
||||
val bridgeConfig = createAndLoadConfigFromResource(bridgeFolder, bridgeConfigResource)
|
||||
bridgeConfig.createBridgeKeyStores(DUMMY_BANK_A_NAME)
|
||||
createNetworkParams(bridgeFolder)
|
||||
assertEquals(BridgeMode.FloatInner, bridgeConfig.bridgeMode)
|
||||
assertEquals(NetworkHostAndPort("localhost", 11005), bridgeConfig.outboundConfig!!.artemisBrokerAddress)
|
||||
val floatFolder = tempFolder.root.toPath() / "float"
|
||||
val floatConfigResource = "/net/corda/bridge/withfloat/float/bridge.conf"
|
||||
val floatConfig = createAndLoadConfigFromResource(floatFolder, floatConfigResource)
|
||||
floatConfig.createBridgeKeyStores(DUMMY_BANK_A_NAME)
|
||||
createNetworkParams(floatFolder)
|
||||
assertEquals(BridgeMode.FloatOuter, floatConfig.bridgeMode)
|
||||
assertEquals(NetworkHostAndPort("0.0.0.0", 10005), floatConfig.inboundConfig!!.listeningAddress)
|
||||
val (artemisServer, artemisClient) = createArtemis()
|
||||
try {
|
||||
installBridgeControlResponder(artemisClient)
|
||||
val bridge = BridgeInstance(bridgeConfig, BridgeVersionInfo(1, "1.1", "Dummy", "Test"))
|
||||
val bridgeStateFollower = bridge.activeChange.toBlocking().iterator
|
||||
val float = BridgeInstance(floatConfig, BridgeVersionInfo(1, "1.1", "Dummy", "Test"))
|
||||
val floatStateFollower = float.activeChange.toBlocking().iterator
|
||||
assertEquals(false, floatStateFollower.next())
|
||||
float.start()
|
||||
assertEquals(true, floatStateFollower.next())
|
||||
assertEquals(true, float.active) // float is running
|
||||
assertEquals(false, serverListening("localhost", 10005)) // but not activated
|
||||
assertEquals(false, bridgeStateFollower.next())
|
||||
bridge.start()
|
||||
assertEquals(true, bridgeStateFollower.next())
|
||||
assertEquals(true, bridge.active)
|
||||
assertEquals(true, float.active)
|
||||
assertEquals(true, serverListening("localhost", 10005)) // now activated
|
||||
bridge.stop()
|
||||
assertEquals(false, bridgeStateFollower.next())
|
||||
assertEquals(false, bridge.active)
|
||||
assertEquals(true, float.active)
|
||||
assertEquals(false, serverListening("localhost", 10005)) // now de-activated
|
||||
float.stop()
|
||||
assertEquals(false, floatStateFollower.next())
|
||||
assertEquals(false, bridge.active)
|
||||
assertEquals(false, float.active)
|
||||
} finally {
|
||||
artemisClient.stop()
|
||||
artemisServer.stop()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Run HA all in one mode`() {
|
||||
val configResource = "/net/corda/bridge/hasingleprocess/bridge.conf"
|
||||
createNetworkParams(tempFolder.root.toPath())
|
||||
val config = createAndLoadConfigFromResource(tempFolder.root.toPath(), configResource)
|
||||
assertEquals(BridgeHAConfigImpl("zk//:localhost:11105", 10), config.haConfig)
|
||||
config.createBridgeKeyStores(DUMMY_BANK_A_NAME)
|
||||
val (artemisServer, artemisClient) = createArtemis()
|
||||
val zkServer = TestingServer(11105, false)
|
||||
try {
|
||||
installBridgeControlResponder(artemisClient)
|
||||
val bridge = BridgeInstance(config, BridgeVersionInfo(1, "1.1", "Dummy", "Test"))
|
||||
val stateFollower = bridge.activeChange.toBlocking().iterator
|
||||
assertEquals(false, stateFollower.next())
|
||||
assertEquals(false, bridge.active)
|
||||
bridge.start()
|
||||
assertEquals(false, bridge.active) // Starting the bridge insufficient to go active
|
||||
zkServer.start() // Now start zookeeper and we should be able to become active
|
||||
assertEquals(true, stateFollower.next())
|
||||
assertEquals(true, bridge.active)
|
||||
assertEquals(true, serverListening("localhost", 10005))
|
||||
val higherPriorityClient = ZkClient("localhost:11105", "/bridge/ha", "Test", 5)
|
||||
higherPriorityClient.start()
|
||||
higherPriorityClient.requestLeadership() // should win leadership and kick out our bridge
|
||||
assertEquals(false, stateFollower.next())
|
||||
assertEquals(false, bridge.active)
|
||||
var socketState = true
|
||||
for (i in 0 until 5) { // The event signalling bridge down is pretty immediate, but the cascade of events leading to socket close can take a while
|
||||
socketState = serverListening("localhost", 10005)
|
||||
if (!socketState) break
|
||||
Thread.sleep(100)
|
||||
}
|
||||
assertEquals(false, socketState)
|
||||
higherPriorityClient.relinquishLeadership() // let our bridge back as leader
|
||||
higherPriorityClient.close()
|
||||
assertEquals(true, stateFollower.next())
|
||||
assertEquals(true, bridge.active)
|
||||
assertEquals(true, serverListening("localhost", 10005))
|
||||
bridge.stop() // Finally check shutdown
|
||||
assertEquals(false, stateFollower.next())
|
||||
assertEquals(false, bridge.active)
|
||||
assertEquals(false, serverListening("localhost", 10005))
|
||||
} finally {
|
||||
artemisClient.stop()
|
||||
artemisServer.stop()
|
||||
zkServer.stop()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Run HA float and bridge mode`() {
|
||||
val bridgeFolder = tempFolder.root.toPath()
|
||||
val bridgeConfigResource = "/net/corda/bridge/hawithfloat/bridge/bridge.conf"
|
||||
val bridgeConfig = createAndLoadConfigFromResource(bridgeFolder, bridgeConfigResource)
|
||||
assertEquals(BridgeHAConfigImpl("zk//:localhost:11105", 10), bridgeConfig.haConfig)
|
||||
bridgeConfig.createBridgeKeyStores(DUMMY_BANK_A_NAME)
|
||||
createNetworkParams(bridgeFolder)
|
||||
val floatFolder = tempFolder.root.toPath() / "float"
|
||||
val floatConfigResource = "/net/corda/bridge/hawithfloat/float/bridge.conf"
|
||||
val floatConfig = createAndLoadConfigFromResource(floatFolder, floatConfigResource)
|
||||
assertNull(floatConfig.haConfig)
|
||||
floatConfig.createBridgeKeyStores(DUMMY_BANK_A_NAME)
|
||||
createNetworkParams(floatFolder)
|
||||
val (artemisServer, artemisClient) = createArtemis()
|
||||
val zkServer = TestingServer(11105, false)
|
||||
try {
|
||||
installBridgeControlResponder(artemisClient)
|
||||
val bridge = BridgeInstance(bridgeConfig, BridgeVersionInfo(1, "1.1", "Dummy", "Test"))
|
||||
val bridgeStateFollower = bridge.activeChange.toBlocking().iterator
|
||||
val float = BridgeInstance(floatConfig, BridgeVersionInfo(1, "1.1", "Dummy", "Test"))
|
||||
val floatStateFollower = float.activeChange.toBlocking().iterator
|
||||
assertEquals(false, bridgeStateFollower.next())
|
||||
assertEquals(false, bridge.active)
|
||||
assertEquals(false, floatStateFollower.next())
|
||||
assertEquals(false, float.active)
|
||||
float.start()
|
||||
assertEquals(true, floatStateFollower.next()) // float goes active, but not listening
|
||||
assertEquals(true, float.active)
|
||||
assertEquals(false, serverListening("localhost", 10005))
|
||||
bridge.start()
|
||||
assertEquals(false, bridge.active) // Starting the bridge/float insufficient to go active
|
||||
assertEquals(true, float.active) // still active, but not listening
|
||||
assertEquals(false, serverListening("localhost", 10005))
|
||||
zkServer.start() // Now start zookeeper and we should be able to become active on float listener
|
||||
assertEquals(true, bridgeStateFollower.next())
|
||||
assertEquals(true, bridge.active)
|
||||
assertEquals(true, float.active)
|
||||
assertEquals(true, serverListening("localhost", 10005))
|
||||
val higherPriorityClient = ZkClient("localhost:11105", "/bridge/ha", "Test", 5)
|
||||
higherPriorityClient.start()
|
||||
higherPriorityClient.requestLeadership() // should win leadership and kick out our bridge
|
||||
assertEquals(false, bridgeStateFollower.next())
|
||||
assertEquals(false, bridge.active)
|
||||
assertEquals(true, float.active)
|
||||
var socketState = true
|
||||
for (i in 0 until 5) { // The event signalling bridge down is pretty immediate, but the cascade of events leading to socket close can take a while
|
||||
socketState = serverListening("localhost", 10005)
|
||||
if (!socketState) break
|
||||
Thread.sleep(100)
|
||||
}
|
||||
assertEquals(false, socketState)
|
||||
higherPriorityClient.relinquishLeadership() // let our bridge back as leader
|
||||
higherPriorityClient.close()
|
||||
assertEquals(true, bridgeStateFollower.next())
|
||||
assertEquals(true, bridge.active)
|
||||
assertEquals(true, float.active)
|
||||
assertEquals(true, serverListening("localhost", 10005))
|
||||
bridge.stop() // Finally check shutdown
|
||||
float.stop()
|
||||
assertEquals(false, bridgeStateFollower.next())
|
||||
assertEquals(false, bridge.active)
|
||||
assertEquals(false, floatStateFollower.next())
|
||||
assertEquals(false, float.active)
|
||||
assertEquals(false, serverListening("localhost", 10005))
|
||||
} finally {
|
||||
artemisClient.stop()
|
||||
artemisServer.stop()
|
||||
zkServer.stop()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createArtemis(): Pair<ArtemisMessagingServer, ArtemisMessagingClient> {
|
||||
val artemisConfig = rigorousMock<AbstractNodeConfiguration>().also {
|
||||
doReturn(tempFolder.root.toPath()).whenever(it).baseDirectory
|
||||
doReturn(ALICE_NAME).whenever(it).myLegalName
|
||||
doReturn("trustpass").whenever(it).trustStorePassword
|
||||
doReturn("cordacadevpass").whenever(it).keyStorePassword
|
||||
doReturn(NetworkHostAndPort("localhost", 11005)).whenever(it).p2pAddress
|
||||
doReturn(null).whenever(it).jmxMonitoringHttpPort
|
||||
doReturn(emptyList<CertChainPolicyConfig>()).whenever(it).certificateChainCheckPolicies
|
||||
doReturn(EnterpriseConfiguration(MutualExclusionConfiguration(false, "", 20000, 40000), externalBridge = true)).whenever(it).enterpriseConfiguration
|
||||
}
|
||||
val artemisServer = ArtemisMessagingServer(artemisConfig, NetworkHostAndPort("0.0.0.0", 11005), MAX_MESSAGE_SIZE)
|
||||
val artemisClient = ArtemisMessagingClient(artemisConfig, NetworkHostAndPort("localhost", 11005), MAX_MESSAGE_SIZE)
|
||||
artemisServer.start()
|
||||
artemisClient.start()
|
||||
return Pair(artemisServer, artemisClient)
|
||||
}
|
||||
|
||||
private fun installBridgeControlResponder(artemisClient: ArtemisMessagingClient) {
|
||||
val artemis = artemisClient.started!!
|
||||
val inboxAddress = SimpleString("${P2P_PREFIX}Test")
|
||||
artemis.session.createQueue(inboxAddress, RoutingType.ANYCAST, inboxAddress, true)
|
||||
artemis.session.createQueue(BRIDGE_NOTIFY, RoutingType.ANYCAST, BRIDGE_NOTIFY, false)
|
||||
val controlConsumer = artemis.session.createConsumer(BRIDGE_NOTIFY)
|
||||
controlConsumer.setMessageHandler { msg ->
|
||||
val bridgeControl = BridgeControl.NodeToBridgeSnapshot("Test", listOf(inboxAddress.toString()), emptyList())
|
||||
val controlPacket = bridgeControl.serialize(context = SerializationDefaults.P2P_CONTEXT).bytes
|
||||
val artemisMessage = artemis.session.createMessage(false)
|
||||
artemisMessage.writeBodyBufferBytes(controlPacket)
|
||||
artemis.producer.send(BRIDGE_CONTROL, artemisMessage)
|
||||
msg.acknowledge()
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,176 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.bridge.services
|
||||
|
||||
import net.corda.bridge.createAndLoadConfigFromResource
|
||||
import net.corda.bridge.createBridgeKeyStores
|
||||
import net.corda.bridge.createNetworkParams
|
||||
import net.corda.bridge.serverListening
|
||||
import net.corda.bridge.services.receiver.BridgeAMQPListenerServiceImpl
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.Crypto.ECDSA_SECP256R1_SHA256
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.readAll
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEERS_PREFIX
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER
|
||||
import net.corda.nodeapi.internal.crypto.X509KeyStore
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import net.corda.nodeapi.internal.protonwrapper.messages.MessageStatus
|
||||
import net.corda.nodeapi.internal.protonwrapper.netty.AMQPClient
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.DUMMY_BANK_A_NAME
|
||||
import net.corda.testing.core.DUMMY_BANK_B_NAME
|
||||
import net.corda.testing.core.SerializationEnvironmentRule
|
||||
import org.junit.Assert.assertArrayEquals
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class AMQPListenerTest {
|
||||
@Rule
|
||||
@JvmField
|
||||
val tempFolder = TemporaryFolder()
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val serializationEnvironment = SerializationEnvironmentRule(true)
|
||||
|
||||
@Test
|
||||
fun `Basic AMPQListenerService lifecycle test`() {
|
||||
val configResource = "/net/corda/bridge/singleprocess/bridge.conf"
|
||||
createNetworkParams(tempFolder.root.toPath())
|
||||
val bridgeConfig = createAndLoadConfigFromResource(tempFolder.root.toPath() / "listener", configResource)
|
||||
bridgeConfig.createBridgeKeyStores(DUMMY_BANK_A_NAME)
|
||||
val auditService = TestAuditService()
|
||||
val amqpListenerService = BridgeAMQPListenerServiceImpl(bridgeConfig, auditService)
|
||||
val stateFollower = amqpListenerService.activeChange.toBlocking().iterator
|
||||
val connectionFollower = amqpListenerService.onConnection.toBlocking().iterator
|
||||
val auditFollower = auditService.onAuditEvent.toBlocking().iterator
|
||||
// Listener doesn't come up yet as not started
|
||||
assertEquals(false, stateFollower.next())
|
||||
amqpListenerService.start()
|
||||
// Listener still not up as audit not ready
|
||||
assertEquals(false, amqpListenerService.active)
|
||||
auditService.start()
|
||||
// Service 'active', but no listening activity yet
|
||||
assertEquals(true, stateFollower.next())
|
||||
assertEquals(true, amqpListenerService.active)
|
||||
assertEquals(false, serverListening("localhost", 10005))
|
||||
val keyStoreBytes = bridgeConfig.sslKeystore.readAll()
|
||||
val trustStoreBytes = bridgeConfig.trustStoreFile.readAll()
|
||||
// start listening
|
||||
amqpListenerService.provisionKeysAndActivate(keyStoreBytes,
|
||||
bridgeConfig.keyStorePassword.toCharArray(),
|
||||
bridgeConfig.keyStorePassword.toCharArray(),
|
||||
trustStoreBytes,
|
||||
bridgeConfig.trustStorePassword.toCharArray())
|
||||
// Fire lots of activity to prove we are good
|
||||
assertEquals(TestAuditService.AuditEvent.STATUS_CHANGE, auditFollower.next())
|
||||
assertEquals(true, amqpListenerService.active)
|
||||
// Definitely a socket tehre
|
||||
assertEquals(true, serverListening("localhost", 10005))
|
||||
// But not a valid SSL link
|
||||
assertEquals(false, connectionFollower.next().connected)
|
||||
assertEquals(TestAuditService.AuditEvent.FAILED_CONNECTION, auditFollower.next())
|
||||
val clientConfig = createAndLoadConfigFromResource(tempFolder.root.toPath() / "client", configResource)
|
||||
clientConfig.createBridgeKeyStores(DUMMY_BANK_B_NAME)
|
||||
val clientKeyStore = clientConfig.loadSslKeyStore().internal
|
||||
val clientTrustStore = clientConfig.loadTrustStore().internal
|
||||
// create and connect a real client
|
||||
val amqpClient = AMQPClient(listOf(NetworkHostAndPort("localhost", 10005)),
|
||||
setOf(DUMMY_BANK_A_NAME),
|
||||
PEER_USER,
|
||||
PEER_USER,
|
||||
clientKeyStore,
|
||||
clientConfig.keyStorePassword,
|
||||
clientTrustStore)
|
||||
|
||||
amqpClient.start()
|
||||
// Should see events to show we got a valid connection
|
||||
val connectedEvent = connectionFollower.next()
|
||||
assertEquals(true, connectedEvent.connected)
|
||||
assertEquals(DUMMY_BANK_B_NAME, CordaX500Name.build(connectedEvent.remoteCert!!.subjectX500Principal))
|
||||
assertEquals(TestAuditService.AuditEvent.SUCCESSFUL_CONNECTION, auditFollower.next())
|
||||
val receiver = amqpListenerService.onReceive.toBlocking().iterator
|
||||
// Send a test message
|
||||
val testMsg = "A test".toByteArray()
|
||||
val msg = amqpClient.createMessage(testMsg, "${PEERS_PREFIX}fake", DUMMY_BANK_A_NAME.toString(), emptyMap())
|
||||
amqpClient.write(msg)
|
||||
val receivedMessage = receiver.next()
|
||||
// confirm details match
|
||||
assertEquals(DUMMY_BANK_B_NAME, CordaX500Name.parse(receivedMessage.sourceLegalName))
|
||||
assertArrayEquals(testMsg, receivedMessage.payload)
|
||||
receivedMessage.complete(true)
|
||||
assertEquals(MessageStatus.Acknowledged, msg.onComplete.get())
|
||||
// Shutdown link
|
||||
amqpClient.stop()
|
||||
// verify audit events for disconnect
|
||||
val disconnectedEvent = connectionFollower.next()
|
||||
assertEquals(false, disconnectedEvent.connected)
|
||||
assertEquals(DUMMY_BANK_B_NAME, CordaX500Name.build(disconnectedEvent.remoteCert!!.subjectX500Principal))
|
||||
assertEquals(TestAuditService.AuditEvent.FAILED_CONNECTION, auditFollower.next())
|
||||
// tear down listener
|
||||
amqpListenerService.wipeKeysAndDeactivate()
|
||||
assertEquals(true, amqpListenerService.active)
|
||||
assertEquals(false, serverListening("localhost", 10005))
|
||||
amqpListenerService.stop()
|
||||
assertEquals(false, stateFollower.next())
|
||||
assertEquals(false, amqpListenerService.active)
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun `Bad certificate audit check`() {
|
||||
val configResource = "/net/corda/bridge/singleprocess/bridge.conf"
|
||||
createNetworkParams(tempFolder.root.toPath())
|
||||
val bridgeConfig = createAndLoadConfigFromResource(tempFolder.root.toPath() / "listener", configResource)
|
||||
bridgeConfig.createBridgeKeyStores(DUMMY_BANK_A_NAME)
|
||||
val auditService = TestAuditService()
|
||||
val amqpListenerService = BridgeAMQPListenerServiceImpl(bridgeConfig, auditService)
|
||||
amqpListenerService.start()
|
||||
auditService.start()
|
||||
val keyStoreBytes = bridgeConfig.sslKeystore.readAll()
|
||||
val trustStoreBytes = bridgeConfig.trustStoreFile.readAll()
|
||||
// start listening
|
||||
amqpListenerService.provisionKeysAndActivate(keyStoreBytes,
|
||||
bridgeConfig.keyStorePassword.toCharArray(),
|
||||
bridgeConfig.keyStorePassword.toCharArray(),
|
||||
trustStoreBytes,
|
||||
bridgeConfig.trustStorePassword.toCharArray())
|
||||
val connectionFollower = amqpListenerService.onConnection.toBlocking().iterator
|
||||
val auditFollower = auditService.onAuditEvent.toBlocking().iterator
|
||||
val clientKeys = Crypto.generateKeyPair(ECDSA_SECP256R1_SHA256)
|
||||
val clientCert = X509Utilities.createSelfSignedCACertificate(ALICE_NAME.x500Principal, clientKeys)
|
||||
val clientKeyStore = X509KeyStore("password")
|
||||
clientKeyStore.setPrivateKey("TLS_CERT", clientKeys.private, listOf(clientCert))
|
||||
val clientTrustStore = X509KeyStore("password")
|
||||
clientTrustStore.setCertificate("TLS_ROOT", clientCert)
|
||||
// create and connect a real client
|
||||
val amqpClient = AMQPClient(listOf(NetworkHostAndPort("localhost", 10005)),
|
||||
setOf(DUMMY_BANK_A_NAME),
|
||||
PEER_USER,
|
||||
PEER_USER,
|
||||
clientKeyStore.internal,
|
||||
"password",
|
||||
clientTrustStore.internal)
|
||||
amqpClient.start()
|
||||
val connectionEvent = connectionFollower.next()
|
||||
assertEquals(false, connectionEvent.connected)
|
||||
assertEquals(TestAuditService.AuditEvent.FAILED_CONNECTION, auditFollower.next())
|
||||
amqpClient.stop()
|
||||
amqpListenerService.wipeKeysAndDeactivate()
|
||||
amqpListenerService.stop()
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.bridge.services
|
||||
|
||||
import com.nhaarman.mockito_kotlin.doReturn
|
||||
import com.nhaarman.mockito_kotlin.whenever
|
||||
import net.corda.bridge.createAndLoadConfigFromResource
|
||||
import net.corda.bridge.createBridgeKeyStores
|
||||
import net.corda.bridge.createNetworkParams
|
||||
import net.corda.bridge.services.artemis.BridgeArtemisConnectionServiceImpl
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.node.services.config.CertChainPolicyConfig
|
||||
import net.corda.node.services.config.EnterpriseConfiguration
|
||||
import net.corda.node.services.config.MutualExclusionConfiguration
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.node.services.messaging.ArtemisMessagingServer
|
||||
import net.corda.testing.core.DUMMY_BANK_A_NAME
|
||||
import net.corda.testing.core.MAX_MESSAGE_SIZE
|
||||
import net.corda.testing.core.SerializationEnvironmentRule
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertNull
|
||||
|
||||
class ArtemisConnectionTest {
|
||||
@Rule
|
||||
@JvmField
|
||||
val tempFolder = TemporaryFolder()
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val serializationEnvironment = SerializationEnvironmentRule(true)
|
||||
|
||||
private abstract class AbstractNodeConfiguration : NodeConfiguration
|
||||
|
||||
@Test
|
||||
fun `Basic lifecycle test`() {
|
||||
val configResource = "/net/corda/bridge/singleprocess/bridge.conf"
|
||||
createNetworkParams(tempFolder.root.toPath())
|
||||
val bridgeConfig = createAndLoadConfigFromResource(tempFolder.root.toPath(), configResource)
|
||||
bridgeConfig.createBridgeKeyStores(DUMMY_BANK_A_NAME)
|
||||
val auditService = TestAuditService()
|
||||
val artemisService = BridgeArtemisConnectionServiceImpl(bridgeConfig, MAX_MESSAGE_SIZE, auditService)
|
||||
val stateFollower = artemisService.activeChange.toBlocking().iterator
|
||||
artemisService.start()
|
||||
assertEquals(false, stateFollower.next())
|
||||
assertEquals(false, artemisService.active)
|
||||
assertNull(artemisService.started)
|
||||
auditService.start()
|
||||
assertEquals(false, artemisService.active)
|
||||
assertNull(artemisService.started)
|
||||
var artemisServer = createArtemis()
|
||||
try {
|
||||
assertEquals(true, stateFollower.next())
|
||||
assertEquals(true, artemisService.active)
|
||||
assertNotNull(artemisService.started)
|
||||
auditService.stop()
|
||||
assertEquals(false, stateFollower.next())
|
||||
assertEquals(false, artemisService.active)
|
||||
assertNull(artemisService.started)
|
||||
auditService.start()
|
||||
assertEquals(true, stateFollower.next())
|
||||
assertEquals(true, artemisService.active)
|
||||
assertNotNull(artemisService.started)
|
||||
} finally {
|
||||
artemisServer.stop()
|
||||
}
|
||||
assertEquals(false, stateFollower.next())
|
||||
assertEquals(false, artemisService.active)
|
||||
assertNull(artemisService.started)
|
||||
artemisServer = createArtemis()
|
||||
try {
|
||||
assertEquals(true, stateFollower.next())
|
||||
assertEquals(true, artemisService.active)
|
||||
assertNotNull(artemisService.started)
|
||||
} finally {
|
||||
artemisServer.stop()
|
||||
}
|
||||
assertEquals(false, stateFollower.next())
|
||||
assertEquals(false, artemisService.active)
|
||||
assertNull(artemisService.started)
|
||||
artemisService.stop()
|
||||
}
|
||||
|
||||
|
||||
private fun createArtemis(): ArtemisMessagingServer {
|
||||
val artemisConfig = rigorousMock<AbstractNodeConfiguration>().also {
|
||||
doReturn(tempFolder.root.toPath()).whenever(it).baseDirectory
|
||||
doReturn(DUMMY_BANK_A_NAME).whenever(it).myLegalName
|
||||
doReturn("trustpass").whenever(it).trustStorePassword
|
||||
doReturn("cordacadevpass").whenever(it).keyStorePassword
|
||||
doReturn(NetworkHostAndPort("localhost", 11005)).whenever(it).p2pAddress
|
||||
doReturn(null).whenever(it).jmxMonitoringHttpPort
|
||||
doReturn(emptyList<CertChainPolicyConfig>()).whenever(it).certificateChainCheckPolicies
|
||||
doReturn(EnterpriseConfiguration(MutualExclusionConfiguration(false, "", 20000, 40000), externalBridge = true)).whenever(it).enterpriseConfiguration
|
||||
}
|
||||
val artemisServer = ArtemisMessagingServer(artemisConfig, NetworkHostAndPort("0.0.0.0", 11005), MAX_MESSAGE_SIZE)
|
||||
artemisServer.start()
|
||||
return artemisServer
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,261 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.bridge.services
|
||||
|
||||
import com.nhaarman.mockito_kotlin.*
|
||||
import net.corda.bridge.*
|
||||
import net.corda.bridge.services.api.BridgeAMQPListenerService
|
||||
import net.corda.bridge.services.api.IncomingMessageFilterService
|
||||
import net.corda.bridge.services.ha.SingleInstanceMasterService
|
||||
import net.corda.bridge.services.receiver.FloatControlListenerService
|
||||
import net.corda.bridge.services.receiver.TunnelingBridgeReceiverService
|
||||
import net.corda.core.internal.createDirectories
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_PREFIX
|
||||
import net.corda.nodeapi.internal.protonwrapper.messages.ReceivedMessage
|
||||
import net.corda.nodeapi.internal.protonwrapper.netty.ConnectionChange
|
||||
import net.corda.testing.core.DUMMY_BANK_A_NAME
|
||||
import net.corda.testing.core.DUMMY_BANK_B_NAME
|
||||
import net.corda.testing.core.SerializationEnvironmentRule
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import org.junit.Assert.assertArrayEquals
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import rx.Observable
|
||||
import rx.subjects.PublishSubject
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class TunnelControlTest {
|
||||
companion object {
|
||||
val inboxTopic = "${P2P_PREFIX}test"
|
||||
}
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val tempFolder = TemporaryFolder()
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val serializationEnvironment = SerializationEnvironmentRule(true)
|
||||
|
||||
private abstract class TestBridgeAMQPListenerService : BridgeAMQPListenerService, TestServiceBase() {
|
||||
private var _running: Boolean = false
|
||||
override val running: Boolean
|
||||
get() = _running
|
||||
|
||||
override fun provisionKeysAndActivate(keyStoreBytes: ByteArray, keyStorePassword: CharArray, keyStorePrivateKeyPassword: CharArray, trustStoreBytes: ByteArray, trustStorePassword: CharArray) {
|
||||
_running = true
|
||||
}
|
||||
|
||||
override fun wipeKeysAndDeactivate() {
|
||||
_running = false
|
||||
}
|
||||
}
|
||||
|
||||
private abstract class TestIncomingMessageFilterService : IncomingMessageFilterService, TestServiceBase()
|
||||
|
||||
@Test
|
||||
fun `Basic tunnel life cycle test`() {
|
||||
val bridgeConfigResource = "/net/corda/bridge/withfloat/bridge/bridge.conf"
|
||||
val bridgePath = tempFolder.root.toPath() / "bridge"
|
||||
bridgePath.createDirectories()
|
||||
createNetworkParams(bridgePath)
|
||||
val bridgeConfig = createAndLoadConfigFromResource(bridgePath, bridgeConfigResource)
|
||||
bridgeConfig.createBridgeKeyStores(DUMMY_BANK_A_NAME)
|
||||
val bridgeAuditService = TestAuditService()
|
||||
val haService = SingleInstanceMasterService(bridgeConfig, bridgeAuditService)
|
||||
val filterService = createPartialMock<TestIncomingMessageFilterService>()
|
||||
val bridgeProxiedReceiverService = TunnelingBridgeReceiverService(bridgeConfig, bridgeAuditService, haService, filterService)
|
||||
val bridgeStateFollower = bridgeProxiedReceiverService.activeChange.toBlocking().iterator
|
||||
bridgeProxiedReceiverService.start()
|
||||
assertEquals(false, bridgeStateFollower.next())
|
||||
assertEquals(false, bridgeProxiedReceiverService.active)
|
||||
bridgeAuditService.start()
|
||||
assertEquals(false, bridgeProxiedReceiverService.active)
|
||||
filterService.start()
|
||||
assertEquals(false, bridgeProxiedReceiverService.active)
|
||||
haService.start()
|
||||
assertEquals(false, bridgeProxiedReceiverService.active)
|
||||
|
||||
val floatConfigResource = "/net/corda/bridge/withfloat/float/bridge.conf"
|
||||
val floatPath = tempFolder.root.toPath() / "float"
|
||||
floatPath.createDirectories()
|
||||
createNetworkParams(floatPath)
|
||||
val floatConfig = createAndLoadConfigFromResource(floatPath, floatConfigResource)
|
||||
floatConfig.createBridgeKeyStores(DUMMY_BANK_A_NAME)
|
||||
|
||||
val floatAuditService = TestAuditService()
|
||||
val amqpListenerService = createPartialMock<TestBridgeAMQPListenerService>().also {
|
||||
doReturn(Observable.never<ConnectionChange>()).whenever(it).onConnection
|
||||
doReturn(Observable.never<ReceivedMessage>()).whenever(it).onReceive
|
||||
}
|
||||
val floatControlListener = FloatControlListenerService(floatConfig, floatAuditService, amqpListenerService)
|
||||
val floatStateFollower = floatControlListener.activeChange.toBlocking().iterator
|
||||
assertEquals(false, floatStateFollower.next())
|
||||
assertEquals(false, floatControlListener.active)
|
||||
floatControlListener.start()
|
||||
assertEquals(false, floatControlListener.active)
|
||||
floatAuditService.start()
|
||||
assertEquals(false, floatControlListener.active)
|
||||
verify(amqpListenerService, times(0)).wipeKeysAndDeactivate()
|
||||
verify(amqpListenerService, times(0)).provisionKeysAndActivate(any(), any(), any(), any(), any())
|
||||
assertEquals(false, serverListening("localhost", 12005))
|
||||
amqpListenerService.start()
|
||||
assertEquals(true, floatStateFollower.next())
|
||||
assertEquals(true, floatControlListener.active)
|
||||
assertEquals(true, serverListening("localhost", 12005))
|
||||
|
||||
assertEquals(true, bridgeStateFollower.next())
|
||||
assertEquals(true, bridgeProxiedReceiverService.active)
|
||||
verify(amqpListenerService, times(0)).wipeKeysAndDeactivate()
|
||||
verify(amqpListenerService, times(1)).provisionKeysAndActivate(any(), any(), any(), any(), any())
|
||||
|
||||
haService.stop()
|
||||
assertEquals(false, bridgeStateFollower.next())
|
||||
assertEquals(false, bridgeProxiedReceiverService.active)
|
||||
assertEquals(true, floatControlListener.active)
|
||||
verify(amqpListenerService, times(1)).wipeKeysAndDeactivate()
|
||||
verify(amqpListenerService, times(1)).provisionKeysAndActivate(any(), any(), any(), any(), any())
|
||||
assertEquals(true, serverListening("localhost", 12005))
|
||||
|
||||
haService.start()
|
||||
assertEquals(true, bridgeStateFollower.next())
|
||||
assertEquals(true, bridgeProxiedReceiverService.active)
|
||||
assertEquals(true, floatControlListener.active)
|
||||
verify(amqpListenerService, times(1)).wipeKeysAndDeactivate()
|
||||
verify(amqpListenerService, times(2)).provisionKeysAndActivate(any(), any(), any(), any(), any())
|
||||
|
||||
floatControlListener.stop()
|
||||
assertEquals(false, floatControlListener.active)
|
||||
bridgeProxiedReceiverService.stop()
|
||||
assertEquals(false, bridgeStateFollower.next())
|
||||
assertEquals(false, bridgeProxiedReceiverService.active)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Inbound message test`() {
|
||||
val bridgeConfigResource = "/net/corda/bridge/withfloat/bridge/bridge.conf"
|
||||
val bridgePath = tempFolder.root.toPath() / "bridge"
|
||||
bridgePath.createDirectories()
|
||||
createNetworkParams(bridgePath)
|
||||
val bridgeConfig = createAndLoadConfigFromResource(bridgePath, bridgeConfigResource)
|
||||
bridgeConfig.createBridgeKeyStores(DUMMY_BANK_A_NAME)
|
||||
val bridgeAuditService = TestAuditService()
|
||||
val haService = SingleInstanceMasterService(bridgeConfig, bridgeAuditService)
|
||||
val forwardedMessages = PublishSubject.create<ReceivedMessage>()
|
||||
val filterService = createPartialMock<TestIncomingMessageFilterService>().also {
|
||||
doAnswer {
|
||||
val msg = it.arguments[0] as ReceivedMessage
|
||||
forwardedMessages.onNext(msg)
|
||||
Unit
|
||||
}.whenever(it).sendMessageToLocalBroker(any())
|
||||
}
|
||||
val bridgeProxiedReceiverService = TunnelingBridgeReceiverService(bridgeConfig, bridgeAuditService, haService, filterService)
|
||||
val bridgeStateFollower = bridgeProxiedReceiverService.activeChange.toBlocking().iterator
|
||||
bridgeProxiedReceiverService.start()
|
||||
bridgeAuditService.start()
|
||||
filterService.start()
|
||||
haService.start()
|
||||
assertEquals(false, bridgeStateFollower.next())
|
||||
|
||||
val floatConfigResource = "/net/corda/bridge/withfloat/float/bridge.conf"
|
||||
val floatPath = tempFolder.root.toPath() / "float"
|
||||
floatPath.createDirectories()
|
||||
createNetworkParams(floatPath)
|
||||
val floatConfig = createAndLoadConfigFromResource(floatPath, floatConfigResource)
|
||||
floatConfig.createBridgeKeyStores(DUMMY_BANK_A_NAME)
|
||||
|
||||
val floatAuditService = TestAuditService()
|
||||
val receiveObserver = PublishSubject.create<ReceivedMessage>()
|
||||
val amqpListenerService = createPartialMock<TestBridgeAMQPListenerService>().also {
|
||||
doReturn(Observable.never<ConnectionChange>()).whenever(it).onConnection
|
||||
doReturn(receiveObserver).whenever(it).onReceive
|
||||
}
|
||||
val floatControlListener = FloatControlListenerService(floatConfig, floatAuditService, amqpListenerService)
|
||||
floatControlListener.start()
|
||||
floatAuditService.start()
|
||||
amqpListenerService.start()
|
||||
assertEquals(true, bridgeStateFollower.next())
|
||||
|
||||
// Message flows back fine from float to bridge and is then forwarded to the filter service
|
||||
val receiver = forwardedMessages.toBlocking().iterator
|
||||
val testPayload = ByteArray(1) { 0x11 }
|
||||
val receivedMessage = rigorousMock<ReceivedMessage>().also {
|
||||
doNothing().whenever(it).complete(true) // ACK was called
|
||||
doReturn(DUMMY_BANK_B_NAME.toString()).whenever(it).sourceLegalName
|
||||
doReturn(NetworkHostAndPort("localhost", 12345)).whenever(it).sourceLink
|
||||
doReturn(inboxTopic).whenever(it).topic
|
||||
doReturn(testPayload).whenever(it).payload
|
||||
doReturn(emptyMap<Any?, Any?>()).whenever(it).applicationProperties
|
||||
doReturn(DUMMY_BANK_A_NAME.toString()).whenever(it).destinationLegalName
|
||||
doReturn(NetworkHostAndPort("localhost", 6789)).whenever(it).destinationLink
|
||||
}
|
||||
receiveObserver.onNext(receivedMessage)
|
||||
val messageReceived = receiver.next()
|
||||
messageReceived.complete(true)
|
||||
assertArrayEquals(testPayload, messageReceived.payload)
|
||||
assertEquals(inboxTopic, messageReceived.topic)
|
||||
assertEquals(DUMMY_BANK_B_NAME.toString(), messageReceived.sourceLegalName)
|
||||
|
||||
// Message NAK is propagated backwards
|
||||
val testPayload2 = ByteArray(1) { 0x22 }
|
||||
val ackLatch = CountDownLatch(1)
|
||||
val receivedMessage2 = rigorousMock<ReceivedMessage>().also {
|
||||
doAnswer {
|
||||
ackLatch.countDown()
|
||||
Unit
|
||||
}.whenever(it).complete(false) // NAK was called
|
||||
doReturn(DUMMY_BANK_B_NAME.toString()).whenever(it).sourceLegalName
|
||||
doReturn(NetworkHostAndPort("localhost", 12345)).whenever(it).sourceLink
|
||||
doReturn(inboxTopic).whenever(it).topic
|
||||
doReturn(testPayload2).whenever(it).payload
|
||||
doReturn(emptyMap<Any?, Any?>()).whenever(it).applicationProperties
|
||||
doReturn(DUMMY_BANK_A_NAME.toString()).whenever(it).destinationLegalName
|
||||
doReturn(NetworkHostAndPort("localhost", 6789)).whenever(it).destinationLink
|
||||
}
|
||||
receiveObserver.onNext(receivedMessage2)
|
||||
val messageReceived2 = receiver.next()
|
||||
messageReceived2.complete(false) // cause NAK to be called
|
||||
assertArrayEquals(testPayload2, messageReceived2.payload)
|
||||
assertEquals(inboxTopic, messageReceived2.topic)
|
||||
assertEquals(DUMMY_BANK_B_NAME.toString(), messageReceived2.sourceLegalName)
|
||||
ackLatch.await(1, TimeUnit.SECONDS)
|
||||
verify(receivedMessage2, times(1)).complete(false)
|
||||
|
||||
// Message NAK if connection dies, without message acceptance
|
||||
val ackLatch2 = CountDownLatch(1)
|
||||
val receivedMessage3 = rigorousMock<ReceivedMessage>().also {
|
||||
doAnswer {
|
||||
ackLatch2.countDown()
|
||||
Unit
|
||||
}.whenever(it).complete(false) // NAK was called
|
||||
doReturn(DUMMY_BANK_B_NAME.toString()).whenever(it).sourceLegalName
|
||||
doReturn(NetworkHostAndPort("localhost", 12345)).whenever(it).sourceLink
|
||||
doReturn(inboxTopic).whenever(it).topic
|
||||
doReturn(testPayload2).whenever(it).payload
|
||||
doReturn(emptyMap<Any?, Any?>()).whenever(it).applicationProperties
|
||||
doReturn(DUMMY_BANK_A_NAME.toString()).whenever(it).destinationLegalName
|
||||
doReturn(NetworkHostAndPort("localhost", 6789)).whenever(it).destinationLink
|
||||
}
|
||||
receiveObserver.onNext(receivedMessage3)
|
||||
receiver.next() // wait message on bridge
|
||||
bridgeProxiedReceiverService.stop() // drop control link
|
||||
ackLatch.await(1, TimeUnit.SECONDS)
|
||||
verify(receivedMessage3, times(1)).complete(false)
|
||||
|
||||
floatControlListener.stop()
|
||||
}
|
||||
|
||||
}
|
20
bridge/src/main/kotlin/net/corda/bridge/Bridge.kt
Normal file
20
bridge/src/main/kotlin/net/corda/bridge/Bridge.kt
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
@file:JvmName("Bridge")
|
||||
|
||||
package net.corda.bridge
|
||||
|
||||
import net.corda.bridge.internal.BridgeStartup
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
exitProcess(if (BridgeStartup(args).run()) 0 else 1)
|
||||
}
|
78
bridge/src/main/kotlin/net/corda/bridge/BridgeArgsParser.kt
Normal file
78
bridge/src/main/kotlin/net/corda/bridge/BridgeArgsParser.kt
Normal file
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.bridge
|
||||
|
||||
import joptsimple.OptionParser
|
||||
import joptsimple.util.EnumConverter
|
||||
import net.corda.bridge.services.api.BridgeConfiguration
|
||||
import net.corda.bridge.services.config.BridgeConfigHelper
|
||||
import net.corda.bridge.services.config.parseAsBridgeConfiguration
|
||||
import net.corda.core.internal.div
|
||||
import org.slf4j.event.Level
|
||||
import java.io.PrintStream
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
|
||||
// NOTE: Do not use any logger in this class as args parsing is done before the logger is setup.
|
||||
class ArgsParser {
|
||||
private val optionParser = OptionParser()
|
||||
// The intent of allowing a command line configurable directory and config path is to allow deployment flexibility.
|
||||
// Other general configuration should live inside the config file unless we regularly need temporary overrides on the command line
|
||||
private val baseDirectoryArg = optionParser
|
||||
.accepts("base-directory", "The bridge working directory where all the files are kept")
|
||||
.withRequiredArg()
|
||||
.defaultsTo(".")
|
||||
private val configFileArg = optionParser
|
||||
.accepts("config-file", "The path to the config file")
|
||||
.withRequiredArg()
|
||||
.defaultsTo("bridge.conf")
|
||||
private val loggerLevel = optionParser
|
||||
.accepts("logging-level", "Enable logging at this level and higher")
|
||||
.withRequiredArg()
|
||||
.withValuesConvertedBy(object : EnumConverter<Level>(Level::class.java) {})
|
||||
.defaultsTo(Level.INFO)
|
||||
private val logToConsoleArg = optionParser.accepts("log-to-console", "If set, prints logging to the console as well as to a file.")
|
||||
private val isVersionArg = optionParser.accepts("version", "Print the version and exit")
|
||||
private val helpArg = optionParser.accepts("help").forHelp()
|
||||
|
||||
fun parse(vararg args: String): CmdLineOptions {
|
||||
val optionSet = optionParser.parse(*args)
|
||||
require(!optionSet.has(baseDirectoryArg) || !optionSet.has(configFileArg)) {
|
||||
"${baseDirectoryArg.options()[0]} and ${configFileArg.options()[0]} cannot be specified together"
|
||||
}
|
||||
val baseDirectory = Paths.get(optionSet.valueOf(baseDirectoryArg)).normalize().toAbsolutePath()
|
||||
val configFile = baseDirectory / optionSet.valueOf(configFileArg)
|
||||
val help = optionSet.has(helpArg)
|
||||
val loggingLevel = optionSet.valueOf(loggerLevel)
|
||||
val logToConsole = optionSet.has(logToConsoleArg)
|
||||
val isVersion = optionSet.has(isVersionArg)
|
||||
return CmdLineOptions(baseDirectory,
|
||||
configFile,
|
||||
help,
|
||||
loggingLevel,
|
||||
logToConsole,
|
||||
isVersion)
|
||||
}
|
||||
|
||||
fun printHelp(sink: PrintStream) = optionParser.printHelpOn(sink)
|
||||
}
|
||||
|
||||
data class CmdLineOptions(val baseDirectory: Path,
|
||||
val configFile: Path,
|
||||
val help: Boolean,
|
||||
val loggingLevel: Level,
|
||||
val logToConsole: Boolean,
|
||||
val isVersion: Boolean) {
|
||||
fun loadConfig(): BridgeConfiguration {
|
||||
val config = BridgeConfigHelper.loadConfig(baseDirectory, configFile).parseAsBridgeConfiguration()
|
||||
return config
|
||||
}
|
||||
}
|
28
bridge/src/main/kotlin/net/corda/bridge/BridgeVersionInfo.kt
Normal file
28
bridge/src/main/kotlin/net/corda/bridge/BridgeVersionInfo.kt
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.bridge
|
||||
|
||||
|
||||
/**
|
||||
* Encapsulates various pieces of version information of the bridge.
|
||||
*/
|
||||
data class BridgeVersionInfo(
|
||||
/**
|
||||
* Platform version of the bridge which is an integer value which increments on any release where any of the public
|
||||
* API of the entire Corda platform changes. This includes messaging, serialisation, bridge APIs, etc.
|
||||
*/
|
||||
val platformVersion: Int,
|
||||
/** Release version string of the bridge. */
|
||||
val releaseVersion: String,
|
||||
/** The exact version control commit ID of the bridge build. */
|
||||
val revision: String,
|
||||
/** The bridge vendor */
|
||||
val vendor: String)
|
@ -0,0 +1,179 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.bridge.internal
|
||||
|
||||
import net.corda.bridge.BridgeVersionInfo
|
||||
import net.corda.bridge.services.api.*
|
||||
import net.corda.bridge.services.audit.LoggingBridgeAuditService
|
||||
import net.corda.bridge.services.supervisors.BridgeSupervisorServiceImpl
|
||||
import net.corda.bridge.services.supervisors.FloatSupervisorServiceImpl
|
||||
import net.corda.bridge.services.util.ServiceStateCombiner
|
||||
import net.corda.bridge.services.util.ServiceStateHelper
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.internal.concurrent.openFuture
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.exists
|
||||
import net.corda.core.internal.readObject
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.internal.SerializationEnvironmentImpl
|
||||
import net.corda.core.serialization.internal.effectiveSerializationEnv
|
||||
import net.corda.core.serialization.internal.nodeSerializationEnv
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.nodeapi.internal.ShutdownHook
|
||||
import net.corda.nodeapi.internal.addShutdownHook
|
||||
import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME
|
||||
import net.corda.nodeapi.internal.network.SignedNetworkParameters
|
||||
import net.corda.nodeapi.internal.serialization.AMQP_P2P_CONTEXT
|
||||
import net.corda.nodeapi.internal.serialization.AMQP_STORAGE_CONTEXT
|
||||
import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl
|
||||
import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme
|
||||
import rx.Subscription
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
class BridgeInstance(val conf: BridgeConfiguration,
|
||||
val versionInfo: BridgeVersionInfo,
|
||||
private val stateHelper: ServiceStateHelper = ServiceStateHelper(log)) : ServiceLifecycleSupport, ServiceStateSupport by stateHelper {
|
||||
companion object {
|
||||
val log = contextLogger()
|
||||
}
|
||||
|
||||
private val shutdown = AtomicBoolean(false)
|
||||
private var shutdownHook: ShutdownHook? = null
|
||||
|
||||
private var maxMessageSize: Int = -1
|
||||
private lateinit var bridgeAuditService: BridgeAuditService
|
||||
private var bridgeSupervisorService: BridgeSupervisorService? = null
|
||||
private var floatSupervisorService: FloatSupervisorService? = null
|
||||
private var statusFollower: ServiceStateCombiner? = null
|
||||
private var statusSubscriber: Subscription? = null
|
||||
|
||||
|
||||
init {
|
||||
initialiseSerialization()
|
||||
}
|
||||
|
||||
private fun initialiseSerialization() {
|
||||
val serializationExists = try {
|
||||
effectiveSerializationEnv
|
||||
true
|
||||
} catch (e: IllegalStateException) {
|
||||
false
|
||||
}
|
||||
if (!serializationExists) {
|
||||
val classloader = this.javaClass.classLoader
|
||||
nodeSerializationEnv = SerializationEnvironmentImpl(
|
||||
SerializationFactoryImpl().apply {
|
||||
registerScheme(AMQPServerSerializationScheme(emptyList()))
|
||||
},
|
||||
p2pContext = AMQP_P2P_CONTEXT.withClassLoader(classloader),
|
||||
rpcServerContext = AMQP_P2P_CONTEXT.withClassLoader(classloader),
|
||||
storageContext = AMQP_STORAGE_CONTEXT.withClassLoader(classloader),
|
||||
checkpointContext = AMQP_P2P_CONTEXT.withClassLoader(classloader))
|
||||
}
|
||||
}
|
||||
|
||||
override fun start() {
|
||||
val wasRunning = shutdown.getAndSet(true)
|
||||
require(!wasRunning) { "Already running" }
|
||||
shutdownHook = addShutdownHook {
|
||||
stop()
|
||||
}
|
||||
retrieveNetworkParameters()
|
||||
createServices()
|
||||
startServices()
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
val wasRunning = shutdown.getAndSet(false)
|
||||
if (!wasRunning) {
|
||||
return
|
||||
}
|
||||
shutdownHook?.cancel()
|
||||
shutdownHook = null
|
||||
log.info("Shutting down ...")
|
||||
|
||||
stopServices()
|
||||
|
||||
_exitFuture.set(this)
|
||||
log.info("Shutdown complete")
|
||||
}
|
||||
|
||||
private val _exitFuture = openFuture<BridgeInstance>()
|
||||
val onExit: CordaFuture<BridgeInstance> get() = _exitFuture
|
||||
|
||||
private fun retrieveNetworkParameters() {
|
||||
val networkParamsFile = conf.baseDirectory / NETWORK_PARAMS_FILE_NAME
|
||||
require(networkParamsFile.exists()) { "No network-parameters file found." }
|
||||
val networkParameters = networkParamsFile.readObject<SignedNetworkParameters>().raw.deserialize()
|
||||
maxMessageSize = networkParameters.maxMessageSize
|
||||
log.info("Loaded maxMessageSize from network-parameters file: $maxMessageSize")
|
||||
}
|
||||
|
||||
private fun createServices() {
|
||||
require(maxMessageSize > 0) { "maxMessageSize not initialised" }
|
||||
bridgeAuditService = LoggingBridgeAuditService(conf)
|
||||
when (conf.bridgeMode) {
|
||||
// In the SenderReceiver mode the inbound and outbound message paths are run from within a single bridge process.
|
||||
// The process thus contains components that listen for bridge control messages on Artemis.
|
||||
// The process can then initiates TLS/AMQP 1.0 connections to remote peers and transfers the outbound messages.
|
||||
// The process also runs a TLS/AMQP 1.0 server socket, which is can receive connections and messages from peers,
|
||||
// validate the messages and then forwards the packets to the Artemis inbox queue of the node.
|
||||
BridgeMode.SenderReceiver -> {
|
||||
floatSupervisorService = FloatSupervisorServiceImpl(conf, maxMessageSize, bridgeAuditService)
|
||||
bridgeSupervisorService = BridgeSupervisorServiceImpl(conf, maxMessageSize, bridgeAuditService, floatSupervisorService!!.amqpListenerService)
|
||||
}
|
||||
// In the FloatInner mode the process runs the full outbound message path as in the SenderReceiver mode, but the inbound path is split.
|
||||
// This 'Float Inner/Bridge Controller' process runs the more trusted portion of the inbound path.
|
||||
// In particular the 'Float Inner/Bridge Controller' has access to the persisted TLS KeyStore, which it provisions dynamically into the 'Float Outer'.
|
||||
// Also the the 'Float Inner' does more complete validation of inbound messages and ensures that they correspond to legitimate
|
||||
// node inboxes, before transferring the message to Artemis. Potentially it might carry out deeper checks of received packets.
|
||||
// However, the 'Float Inner' is not directly exposed to the internet, or peers and does not host the TLS/AMQP 1.0 server socket.
|
||||
BridgeMode.FloatInner -> {
|
||||
bridgeSupervisorService = BridgeSupervisorServiceImpl(conf, maxMessageSize, bridgeAuditService, null)
|
||||
}
|
||||
// In the FloatOuter mode this process runs a minimal AMQP proxy that is designed to run in a DMZ zone.
|
||||
// The process holds the minimum data necessary to act as the TLS/AMQP 1.0 receiver socket and tries
|
||||
// to minimise any state. It specifically does not persist the Node TLS keys anywhere, nor does it hold network map information on peers.
|
||||
// The 'Float Outer' does not initiate socket connection anywhere, so that attackers can be easily blocked by firewalls
|
||||
// if they try to invade the system from a compromised 'Float Outer' machine. The 'Float Outer' hosts a control TLS/AMQP 1.0 server socket,
|
||||
// which receives a connection from the 'Float Inner/Bridge controller' in the trusted zone of the organisation.
|
||||
// The control channel is ideally authenticated using server/client certificates that are not related to the Corda PKI hierarchy.
|
||||
// Once the control channel is formed it is used to RPC the methods of the BridgeAMQPListenerService to start the publicly visible
|
||||
// TLS/AMQP 1.0 server socket of the Corda node. Thus peer connections will directly terminate onto the activate listener socket and
|
||||
// be validated against the keys/certificates sent across the control tunnel. Inbound messages are given basic checks that do not require
|
||||
// holding potentially sensitive information and are then forwarded across the control tunnel to the 'Float Inner' process for more
|
||||
// complete validation checks.
|
||||
BridgeMode.FloatOuter -> {
|
||||
floatSupervisorService = FloatSupervisorServiceImpl(conf, maxMessageSize, bridgeAuditService)
|
||||
}
|
||||
}
|
||||
statusFollower = ServiceStateCombiner(listOf(bridgeAuditService, floatSupervisorService, bridgeSupervisorService).filterNotNull())
|
||||
statusSubscriber = statusFollower!!.activeChange.subscribe {
|
||||
stateHelper.active = it
|
||||
}
|
||||
}
|
||||
|
||||
private fun startServices() {
|
||||
bridgeAuditService.start()
|
||||
bridgeSupervisorService?.start()
|
||||
floatSupervisorService?.start()
|
||||
}
|
||||
|
||||
private fun stopServices() {
|
||||
stateHelper.active = false
|
||||
floatSupervisorService?.stop()
|
||||
bridgeSupervisorService?.stop()
|
||||
bridgeAuditService.stop()
|
||||
statusSubscriber?.unsubscribe()
|
||||
statusSubscriber = null
|
||||
statusFollower = null
|
||||
}
|
||||
}
|
@ -0,0 +1,214 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.bridge.internal
|
||||
|
||||
import com.jcabi.manifests.Manifests
|
||||
import joptsimple.OptionException
|
||||
import net.corda.bridge.ArgsParser
|
||||
import net.corda.bridge.BridgeVersionInfo
|
||||
import net.corda.bridge.CmdLineOptions
|
||||
import net.corda.bridge.services.api.BridgeConfiguration
|
||||
import net.corda.core.internal.createDirectories
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.nodeapi.internal.addShutdownHook
|
||||
import org.slf4j.bridge.SLF4JBridgeHandler
|
||||
import sun.misc.VMSupport
|
||||
import java.io.RandomAccessFile
|
||||
import java.lang.management.ManagementFactory
|
||||
import java.net.InetAddress
|
||||
import java.nio.file.Path
|
||||
import java.util.*
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
class BridgeStartup(val args: Array<String>) {
|
||||
companion object {
|
||||
// lazy init the logging, because the logging levels aren't configured until we have parsed some options.
|
||||
private val log by lazy { contextLogger() }
|
||||
val LOGS_DIRECTORY_NAME = "logs"
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the bridge startup was successful. This value is intended to be the exit code of the process.
|
||||
*/
|
||||
fun run(): Boolean {
|
||||
val startTime = System.currentTimeMillis()
|
||||
val (argsParser, cmdlineOptions) = parseArguments()
|
||||
|
||||
// We do the single bridge check before we initialise logging so that in case of a double-bridge start it
|
||||
// doesn't mess with the running bridge's logs.
|
||||
enforceSingleBridgeIsRunning(cmdlineOptions.baseDirectory)
|
||||
|
||||
initLogging(cmdlineOptions)
|
||||
|
||||
val versionInfo = getVersionInfo()
|
||||
|
||||
if (cmdlineOptions.isVersion) {
|
||||
println("${versionInfo.vendor} ${versionInfo.releaseVersion}")
|
||||
println("Revision ${versionInfo.revision}")
|
||||
println("Platform Version ${versionInfo.platformVersion}")
|
||||
return true
|
||||
}
|
||||
|
||||
// Maybe render command line help.
|
||||
if (cmdlineOptions.help) {
|
||||
argsParser.printHelp(System.out)
|
||||
return true
|
||||
}
|
||||
val conf = try {
|
||||
loadConfigFile(cmdlineOptions)
|
||||
} catch (e: Exception) {
|
||||
log.error("Exception during bridge configuration", e)
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
logStartupInfo(versionInfo, cmdlineOptions, conf)
|
||||
} catch (e: Exception) {
|
||||
log.error("Exception during bridge registration", e)
|
||||
return false
|
||||
}
|
||||
|
||||
val bridge = try {
|
||||
cmdlineOptions.baseDirectory.createDirectories()
|
||||
startBridge(conf, versionInfo, startTime)
|
||||
} catch (e: Exception) {
|
||||
if (e.message?.startsWith("Unknown named curve:") == true) {
|
||||
log.error("Exception during bridge startup - ${e.message}. " +
|
||||
"This is a known OpenJDK issue on some Linux distributions, please use OpenJDK from zulu.org or Oracle JDK.")
|
||||
} else {
|
||||
log.error("Exception during bridge startup", e)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if (System.getProperties().containsKey("WAIT_KEY_FOR_EXIT")) {
|
||||
System.`in`.read() // Inside IntelliJ we can't forward CTRL-C, so debugging shutdown is a nightmare. So allow -DWAIT_KEY_FOR_EXIT flag for key based quit.
|
||||
} else {
|
||||
bridge.onExit.get()
|
||||
}
|
||||
|
||||
log.info("bridge shutting down")
|
||||
bridge.stop()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
fun logStartupInfo(versionInfo: BridgeVersionInfo, cmdlineOptions: CmdLineOptions, conf: BridgeConfiguration) {
|
||||
log.info("Vendor: ${versionInfo.vendor}")
|
||||
log.info("Release: ${versionInfo.releaseVersion}")
|
||||
log.info("Platform Version: ${versionInfo.platformVersion}")
|
||||
log.info("Revision: ${versionInfo.revision}")
|
||||
val info = ManagementFactory.getRuntimeMXBean()
|
||||
log.info("PID: ${info.name.split("@").firstOrNull()}") // TODO Java 9 has better support for this
|
||||
log.info("Main class: ${BridgeStartup::class.java.protectionDomain.codeSource.location.toURI().path}")
|
||||
log.info("CommandLine Args: ${info.inputArguments.joinToString(" ")}")
|
||||
log.info("Application Args: ${args.joinToString(" ")}")
|
||||
log.info("bootclasspath: ${info.bootClassPath}")
|
||||
log.info("classpath: ${info.classPath}")
|
||||
log.info("VM ${info.vmName} ${info.vmVendor} ${info.vmVersion}")
|
||||
log.info("Machine: ${lookupMachineNameAndMaybeWarn()}")
|
||||
log.info("Working Directory: ${cmdlineOptions.baseDirectory}")
|
||||
val agentProperties = VMSupport.getAgentProperties()
|
||||
if (agentProperties.containsKey("sun.jdwp.listenerAddress")) {
|
||||
log.info("Debug port: ${agentProperties.getProperty("sun.jdwp.listenerAddress")}")
|
||||
}
|
||||
log.info("Starting as bridge mode of ${conf.bridgeMode}")
|
||||
}
|
||||
|
||||
protected fun loadConfigFile(cmdlineOptions: CmdLineOptions): BridgeConfiguration = cmdlineOptions.loadConfig()
|
||||
|
||||
protected fun getVersionInfo(): BridgeVersionInfo {
|
||||
// Manifest properties are only available if running from the corda jar
|
||||
fun manifestValue(name: String): String? = if (Manifests.exists(name)) Manifests.read(name) else null
|
||||
|
||||
return BridgeVersionInfo(
|
||||
manifestValue("Corda-Platform-Version")?.toInt() ?: 1,
|
||||
manifestValue("Corda-Release-Version") ?: "Unknown",
|
||||
manifestValue("Corda-Revision") ?: "Unknown",
|
||||
manifestValue("Corda-Vendor") ?: "Unknown"
|
||||
)
|
||||
}
|
||||
|
||||
private fun enforceSingleBridgeIsRunning(baseDirectory: Path) {
|
||||
// Write out our process ID (which may or may not resemble a UNIX process id - to us it's just a string) to a
|
||||
// file that we'll do our best to delete on exit. But if we don't, it'll be overwritten next time. If it already
|
||||
// exists, we try to take the file lock first before replacing it and if that fails it means we're being started
|
||||
// twice with the same directory: that's a user error and we should bail out.
|
||||
val pidFile = (baseDirectory / "bridge-process-id").toFile()
|
||||
pidFile.createNewFile()
|
||||
pidFile.deleteOnExit()
|
||||
val pidFileRw = RandomAccessFile(pidFile, "rw")
|
||||
val pidFileLock = pidFileRw.channel.tryLock()
|
||||
if (pidFileLock == null) {
|
||||
println("It appears there is already a bridge running with the specified data directory $baseDirectory")
|
||||
println("Shut that other bridge down and try again. It may have process ID ${pidFile.readText()}")
|
||||
System.exit(1)
|
||||
}
|
||||
// Avoid the lock being garbage collected. We don't really need to release it as the OS will do so for us
|
||||
// when our process shuts down, but we try in stop() anyway just to be nice.
|
||||
addShutdownHook {
|
||||
pidFileLock.release()
|
||||
}
|
||||
val ourProcessID: String = ManagementFactory.getRuntimeMXBean().name.split("@")[0]
|
||||
pidFileRw.setLength(0)
|
||||
pidFileRw.write(ourProcessID.toByteArray())
|
||||
}
|
||||
|
||||
private fun lookupMachineNameAndMaybeWarn(): String {
|
||||
val start = System.currentTimeMillis()
|
||||
val hostName: String = InetAddress.getLocalHost().hostName
|
||||
val elapsed = System.currentTimeMillis() - start
|
||||
if (elapsed > 1000 && hostName.endsWith(".local")) {
|
||||
// User is probably on macOS and experiencing this problem: http://stackoverflow.com/questions/10064581/how-can-i-eliminate-slow-resolving-loading-of-localhost-virtualhost-a-2-3-secon
|
||||
//
|
||||
// Also see https://bugs.openjdk.java.net/browse/JDK-8143378
|
||||
val messages = listOf(
|
||||
"Your computer took over a second to resolve localhost due an incorrect configuration. Corda will work but start very slowly until this is fixed. ",
|
||||
"Please see https://docs.corda.net/troubleshooting.html#slow-localhost-resolution for information on how to fix this. ",
|
||||
"It will only take a few seconds for you to resolve."
|
||||
)
|
||||
log.warn(messages.joinToString(""))
|
||||
}
|
||||
return hostName
|
||||
}
|
||||
|
||||
private fun parseArguments(): Pair<ArgsParser, CmdLineOptions> {
|
||||
val argsParser = ArgsParser()
|
||||
val cmdlineOptions = try {
|
||||
argsParser.parse(*args)
|
||||
} catch (ex: OptionException) {
|
||||
println("Invalid command line arguments: ${ex.message}")
|
||||
argsParser.printHelp(System.out)
|
||||
exitProcess(1)
|
||||
}
|
||||
return Pair(argsParser, cmdlineOptions)
|
||||
}
|
||||
|
||||
fun initLogging(cmdlineOptions: CmdLineOptions) {
|
||||
val loggingLevel = cmdlineOptions.loggingLevel.name.toLowerCase(Locale.ENGLISH)
|
||||
System.setProperty("defaultLogLevel", loggingLevel) // These properties are referenced from the XML config file.
|
||||
if (cmdlineOptions.logToConsole) {
|
||||
System.setProperty("consoleLogLevel", loggingLevel)
|
||||
}
|
||||
System.setProperty("log-path", (cmdlineOptions.baseDirectory / LOGS_DIRECTORY_NAME).toString())
|
||||
SLF4JBridgeHandler.removeHandlersForRootLogger() // The default j.u.l config adds a ConsoleHandler.
|
||||
SLF4JBridgeHandler.install()
|
||||
}
|
||||
|
||||
fun startBridge(conf: BridgeConfiguration, versionInfo: BridgeVersionInfo, startTime: Long): BridgeInstance {
|
||||
val bridge = BridgeInstance(conf, versionInfo)
|
||||
bridge.start()
|
||||
val elapsed = (System.currentTimeMillis() - startTime) / 10 / 100.0
|
||||
log.info("Bridge started up and registered in $elapsed sec")
|
||||
return bridge
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.bridge.services.api
|
||||
|
||||
import net.corda.nodeapi.internal.protonwrapper.messages.ReceivedMessage
|
||||
import net.corda.nodeapi.internal.protonwrapper.netty.ConnectionChange
|
||||
import rx.Observable
|
||||
import java.security.KeyStore
|
||||
|
||||
/**
|
||||
* This service when activated via [provisionKeysAndActivate] installs an AMQP listening socket,
|
||||
* which listens on the port specified in the [BridgeConfiguration.inboundConfig] section.
|
||||
* The service technically runs inside the 'float' portion of the bridge, so that it can be run remotely inside the DMZ.
|
||||
* As a result it reports as active, whilst not actually listening. Only when the TLS [KeyStore]s are passed to it
|
||||
* does the service become [running].
|
||||
*/
|
||||
interface BridgeAMQPListenerService : ServiceLifecycleSupport {
|
||||
/**
|
||||
* Passes in the [KeyStore]s containing the TLS keys and certificates. This data is only to be held in memory
|
||||
* and will be wiped on close.
|
||||
*/
|
||||
fun provisionKeysAndActivate(keyStoreBytes: ByteArray,
|
||||
keyStorePassword: CharArray,
|
||||
keyStorePrivateKeyPassword: CharArray,
|
||||
trustStoreBytes: ByteArray,
|
||||
trustStorePassword: CharArray)
|
||||
|
||||
/**
|
||||
* Stop listening on the socket and cleanup any private data/keys.
|
||||
*/
|
||||
fun wipeKeysAndDeactivate()
|
||||
|
||||
/**
|
||||
* If the service is [running] the AMQP listener is active.
|
||||
*/
|
||||
val running: Boolean
|
||||
|
||||
/**
|
||||
* Incoming AMQP packets from remote peers are available on this [Observable].
|
||||
*/
|
||||
val onReceive: Observable<ReceivedMessage>
|
||||
|
||||
/**
|
||||
* Any connection, disconnection, or authentication failure is available on this [Observable].
|
||||
*/
|
||||
val onConnection: Observable<ConnectionChange>
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.bridge.services.api
|
||||
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingClient
|
||||
|
||||
/**
|
||||
* This provides a service to manage connection to the local broker as defined in the [BridgeConfiguration.outboundConfig] section.
|
||||
* Once started the service will repeatedly attempt to connect to the bus, signalling success by changing to the [active] state.
|
||||
*/
|
||||
interface BridgeArtemisConnectionService : ServiceLifecycleSupport {
|
||||
/**
|
||||
* When the service becomes [active] this will be non-null and provides access to Artemis management objects.
|
||||
*/
|
||||
val started: ArtemisMessagingClient.Started?
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.bridge.services.api
|
||||
|
||||
import net.corda.nodeapi.internal.protonwrapper.messages.ReceivedMessage
|
||||
import java.net.InetSocketAddress
|
||||
|
||||
/**
|
||||
* This service provides centralised facilities for recording business critical events in the bridge.
|
||||
* Currently the simple implementation just records events to log file, but future implementations may need to post
|
||||
* security data to an enterprise service.
|
||||
*/
|
||||
interface BridgeAuditService : ServiceLifecycleSupport {
|
||||
fun successfulConnectionEvent(inbound: Boolean, sourceIP: InetSocketAddress, certificateSubject: String, msg: String)
|
||||
fun failedConnectionEvent(inbound: Boolean, sourceIP: InetSocketAddress?, certificateSubject: String?, msg: String)
|
||||
fun packetDropEvent(packet: ReceivedMessage?, msg: String)
|
||||
fun packetAcceptedEvent(packet: ReceivedMessage)
|
||||
fun statusChangeEvent(msg: String)
|
||||
}
|
@ -0,0 +1,118 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.bridge.services.api
|
||||
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.nodeapi.internal.config.NodeSSLConfiguration
|
||||
import net.corda.nodeapi.internal.config.SSLConfiguration
|
||||
import net.corda.nodeapi.internal.protonwrapper.netty.SocksProxyConfig
|
||||
import java.nio.file.Path
|
||||
|
||||
enum class BridgeMode {
|
||||
/**
|
||||
* The Bridge/Float is run as a single process with both AMQP sending and receiving functionality.
|
||||
*/
|
||||
SenderReceiver,
|
||||
/**
|
||||
* Runs only the trusted bridge side of the system, which has direct TLS access to Artemis.
|
||||
* The components handles all outgoing aspects of AMQP bridges directly.
|
||||
* The inbound messages are initially received onto a different [FloatOuter] process and a
|
||||
* separate AMQP tunnel is used to ship back the inbound data to this [FloatInner] process.
|
||||
*/
|
||||
FloatInner,
|
||||
/**
|
||||
* A minimal process designed to be run inside a DMZ, which acts an AMQP receiver of inbound peer messages.
|
||||
* The component carries out basic validation of the TLS sources and AMQP packets, before forwarding to the [FloatInner].
|
||||
* No keys are stored on disk for the component, but must instead be provisioned from the [FloatInner] using a
|
||||
* separate AMQP link initiated from the [FloatInner] to the [FloatOuter].
|
||||
*/
|
||||
FloatOuter
|
||||
}
|
||||
|
||||
interface BridgeSSLConfiguration : SSLConfiguration {
|
||||
override val keyStorePassword: String
|
||||
override val trustStorePassword: String
|
||||
override val sslKeystore: Path
|
||||
override val trustStoreFile: Path
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Details of the local Artemis broker.
|
||||
* Required in SenderReceiver and FloatInner modes.
|
||||
*/
|
||||
interface BridgeOutboundConfiguration {
|
||||
val artemisBrokerAddress: NetworkHostAndPort
|
||||
// Allows override of [KeyStore] details for the artemis connection, otherwise the general top level details are used.
|
||||
val customSSLConfiguration: BridgeSSLConfiguration?
|
||||
// Allows use of a SOCKS 4/5 proxy
|
||||
val socksProxyConfig: SocksProxyConfig?
|
||||
}
|
||||
|
||||
/**
|
||||
* Details of the inbound socket binding address, which should be where external peers
|
||||
* using the node's network map advertised data should route links and directly terminate their TLS connections.
|
||||
* This configuration is required in SenderReceiver and FloatOuter modes.
|
||||
*/
|
||||
interface BridgeInboundConfiguration {
|
||||
val listeningAddress: NetworkHostAndPort
|
||||
// Allows override of [KeyStore] details for the AMQP listener port, otherwise the general top level details are used.
|
||||
val customSSLConfiguration: BridgeSSLConfiguration?
|
||||
}
|
||||
|
||||
/**
|
||||
* Details of the target control ports of available [BridgeMode.FloatOuter] processes from the perspective of the [BridgeMode.FloatInner] process.
|
||||
* Required for [BridgeMode.FloatInner] mode.
|
||||
*/
|
||||
interface FloatInnerConfiguration {
|
||||
val floatAddresses: List<NetworkHostAndPort>
|
||||
val expectedCertificateSubject: CordaX500Name
|
||||
// Allows override of [KeyStore] details for the control port, otherwise the general top level details are used.
|
||||
// Used for connection to Float in DMZ
|
||||
val customSSLConfiguration: BridgeSSLConfiguration?
|
||||
// The SSL keystores to provision into the Float in DMZ
|
||||
val customFloatOuterSSLConfiguration: BridgeSSLConfiguration?
|
||||
}
|
||||
|
||||
interface BridgeHAConfig {
|
||||
val haConnectionString: String
|
||||
val haPriority: Int
|
||||
}
|
||||
|
||||
/**
|
||||
* Details of the listening port for a [BridgeMode.FloatOuter] process and of the certificate that the [BridgeMode.FloatInner] should present.
|
||||
* Required for [BridgeMode.FloatOuter] mode.
|
||||
*/
|
||||
interface FloatOuterConfiguration {
|
||||
val floatAddress: NetworkHostAndPort
|
||||
val expectedCertificateSubject: CordaX500Name
|
||||
// Allows override of [KeyStore] details for the control port, otherwise the general top level details are used.
|
||||
val customSSLConfiguration: BridgeSSLConfiguration?
|
||||
}
|
||||
|
||||
interface BridgeConfiguration : NodeSSLConfiguration {
|
||||
val bridgeMode: BridgeMode
|
||||
val outboundConfig: BridgeOutboundConfiguration?
|
||||
val inboundConfig: BridgeInboundConfiguration?
|
||||
val floatInnerConfig: FloatInnerConfiguration?
|
||||
val floatOuterConfig: FloatOuterConfiguration?
|
||||
val haConfig: BridgeHAConfig?
|
||||
val networkParametersPath: Path
|
||||
val enableAMQPPacketTrace: Boolean
|
||||
// Reconnect to artemis after [artemisReconnectionInterval] ms the default value is 5000 ms.
|
||||
val artemisReconnectionInterval: Int
|
||||
// The period to wait for clean shutdown of remote components
|
||||
// e.g links to the Float Outer, or Artemis sessions, before the process continues shutting down anyway.
|
||||
// Default value is 1000 ms.
|
||||
val politeShutdownPeriod: Int
|
||||
val whitelistedHeaders: List<String>
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.bridge.services.api
|
||||
|
||||
/**
|
||||
* This service controls when a bridge may become active and start relaying messages to/from the artemis broker.
|
||||
* The active flag is the used to gate dependent services, which should hold off connecting to the bus until this service
|
||||
* has been able to become active.
|
||||
*/
|
||||
interface BridgeMasterService : ServiceLifecycleSupport {
|
||||
// An echo of the active flag that can be used to make the intention of active status checks clearer.
|
||||
val isMaster: Boolean get() = active
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.bridge.services.api
|
||||
|
||||
/**
|
||||
* The [BridgeReceiverService] is the service responsible for joining together the perhaps remote [BridgeAMQPListenerService]
|
||||
* and the outgoing [IncomingMessageFilterService] that provides the validation and filtering path into the local Artemis broker.
|
||||
* It should not become active, or transmit messages until all of the dependencies are themselves active.
|
||||
*/
|
||||
interface BridgeReceiverService : ServiceLifecycleSupport {
|
||||
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.bridge.services.api
|
||||
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.nodeapi.internal.bridging.BridgeControlListener
|
||||
|
||||
/**
|
||||
* This service is responsible for the outgoing path of messages from the local Artemis broker
|
||||
* to the remote peer using AMQP. It should not become active until the connection to the local Artemis broker is stable
|
||||
* and the [BridgeMasterService] has allowed this bridge instance to become activated.
|
||||
* In practice the actual AMQP bridging logic is carried out using an instance of the [BridgeControlListener] class with
|
||||
* lifecycle support coming from the service.
|
||||
*/
|
||||
interface BridgeSenderService : ServiceLifecycleSupport {
|
||||
/**
|
||||
* This method is used to check inbound packets against the list of valid inbox addresses registered from the nodes
|
||||
* via the local Bridge Control Protocol. They may optionally also check this against the source legal name.
|
||||
*/
|
||||
fun validateReceiveTopic(topic: String, sourceLegalName: CordaX500Name): Boolean
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.bridge.services.api
|
||||
|
||||
/**
|
||||
* This is the top level service representing the [BridgeMode.FloatInner] service stack. The primary role of this component is to
|
||||
* create and wire up concrete implementations of the relevant services according to the [BridgeConfiguration] details.
|
||||
* The possibly proxied path to the [BridgeAMQPListenerService] is typically a constructor input
|
||||
* as that is a [BridgeMode.FloatOuter] component.
|
||||
*/
|
||||
interface BridgeSupervisorService : ServiceLifecycleSupport
|
@ -0,0 +1,17 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.bridge.services.api
|
||||
|
||||
/**
|
||||
* This service represent an AMQP socket listener that awaits a remote initiated connection from the [BridgeMode.FloatInner].
|
||||
* Only one active connection is allowed at a time and it must match the configured requirements in the [BridgeConfiguration.floatInnerConfig].
|
||||
*/
|
||||
interface FloatControlService : ServiceLifecycleSupport
|
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.bridge.services.api
|
||||
|
||||
/**
|
||||
* This is the top level service responsible for creating and managing the [BridgeMode.FloatOuter] portions of the bridge.
|
||||
* It exposes a possibly proxied [BridgeAMQPListenerService] component that is used in the [BridgeSupervisorService]
|
||||
* to wire up the internal portions of the AMQP peer inbound message path.
|
||||
*/
|
||||
interface FloatSupervisorService : ServiceLifecycleSupport {
|
||||
val amqpListenerService: BridgeAMQPListenerService
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.bridge.services.api
|
||||
|
||||
import net.corda.nodeapi.internal.protonwrapper.messages.ReceivedMessage
|
||||
|
||||
/**
|
||||
* This service is responsible for security checking the incoming packets to ensure they are for a legitimate node inbox and
|
||||
* potentially for any other security related aspects. If the message is badly formed then it will be dropped and an audit event logged.
|
||||
* Otherwise the message is forwarded to the appropriate node inbox on the local Artemis Broker.
|
||||
* The service will not be active until the underlying [BridgeArtemisConnectionService] is active.
|
||||
*/
|
||||
interface IncomingMessageFilterService : ServiceLifecycleSupport {
|
||||
fun sendMessageToLocalBroker(inboundMessage: ReceivedMessage)
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.bridge.services.api
|
||||
|
||||
import rx.Observable
|
||||
|
||||
/**
|
||||
* Basic interface to represent the dynamic life cycles of services that may be running, but may have to await external dependencies,
|
||||
* or for HA master state.
|
||||
* Implementations of this should be implemented in a thread safe fashion.
|
||||
*/
|
||||
interface ServiceStateSupport {
|
||||
/**
|
||||
* Reads the current dynamic status of the service, which should only become true after the service has been started,
|
||||
* any dynamic resources have been started/registered and any network connections have been completed.
|
||||
* Failure to acquire a resource, or manual stop of the service, should return this to false.
|
||||
*/
|
||||
val active: Boolean
|
||||
|
||||
/**
|
||||
* This Observer signals changes in the [active] variable, it should not be triggered for events that don't flip the [active] state.
|
||||
*/
|
||||
val activeChange: Observable<Boolean>
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple interface for generic start/stop service lifecycle and the [active] flag indicating runtime ready state.
|
||||
*/
|
||||
interface ServiceLifecycleSupport : ServiceStateSupport, AutoCloseable {
|
||||
/**
|
||||
* Manual call to allow the service to start the process towards becoming active.
|
||||
* Note wiring up service dependencies should happen in the constructor phase, unless this is to avoid a circular reference.
|
||||
* Also, resources allocated as a result of start should be cleaned up as much as possible by stop.
|
||||
* The [start] method should allow multiple reuse, assuming a [stop] call was made to clear the state.
|
||||
*/
|
||||
fun start()
|
||||
|
||||
/**
|
||||
* Release the resources created by [start] and drops the [active] state to false.
|
||||
*/
|
||||
fun stop()
|
||||
|
||||
override fun close() = stop()
|
||||
|
||||
}
|
@ -0,0 +1,178 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.bridge.services.artemis
|
||||
|
||||
import net.corda.bridge.services.api.*
|
||||
import net.corda.bridge.services.config.BridgeSSLConfigurationImpl
|
||||
import net.corda.bridge.services.util.ServiceStateCombiner
|
||||
import net.corda.bridge.services.util.ServiceStateHelper
|
||||
import net.corda.core.internal.ThreadBox
|
||||
import net.corda.core.serialization.internal.nodeSerializationEnv
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.nodeapi.ArtemisTcpTransport
|
||||
import net.corda.nodeapi.ConnectionDirection
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingClient
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent
|
||||
import org.apache.activemq.artemis.api.core.client.ActiveMQClient
|
||||
import org.apache.activemq.artemis.api.core.client.FailoverEventType
|
||||
import org.apache.activemq.artemis.api.core.client.ServerLocator
|
||||
import rx.Subscription
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
class BridgeArtemisConnectionServiceImpl(val conf: BridgeConfiguration,
|
||||
val maxMessageSize: Int,
|
||||
val auditService: BridgeAuditService,
|
||||
private val stateHelper: ServiceStateHelper = ServiceStateHelper(log)) : BridgeArtemisConnectionService, ServiceStateSupport by stateHelper {
|
||||
companion object {
|
||||
val log = contextLogger()
|
||||
}
|
||||
|
||||
private class InnerState {
|
||||
var running = false
|
||||
var locator: ServerLocator? = null
|
||||
var started: ArtemisMessagingClient.Started? = null
|
||||
var connectThread: Thread? = null
|
||||
}
|
||||
|
||||
private val state = ThreadBox(InnerState())
|
||||
private val sslConfiguration: BridgeSSLConfiguration
|
||||
private val statusFollower: ServiceStateCombiner
|
||||
private var statusSubscriber: Subscription? = null
|
||||
|
||||
init {
|
||||
statusFollower = ServiceStateCombiner(listOf(auditService))
|
||||
sslConfiguration = conf.outboundConfig?.customSSLConfiguration ?: BridgeSSLConfigurationImpl(conf)
|
||||
}
|
||||
|
||||
override fun start() {
|
||||
statusSubscriber = statusFollower.activeChange.subscribe {
|
||||
if (it) {
|
||||
startArtemisConnection()
|
||||
} else {
|
||||
stopArtemisConnection()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startArtemisConnection() {
|
||||
state.locked {
|
||||
check(!running) { "start can't be called twice" }
|
||||
running = true
|
||||
log.info("Connecting to message broker: ${conf.outboundConfig!!.artemisBrokerAddress}")
|
||||
// TODO Add broker CN to config for host verification in case the embedded broker isn't used
|
||||
val tcpTransport = ArtemisTcpTransport.tcpTransport(ConnectionDirection.Outbound(), conf.outboundConfig!!.artemisBrokerAddress, sslConfiguration)
|
||||
locator = ActiveMQClient.createServerLocatorWithoutHA(tcpTransport).apply {
|
||||
// Never time out on our loopback Artemis connections. If we switch back to using the InVM transport this
|
||||
// would be the default and the two lines below can be deleted.
|
||||
connectionTTL = -1
|
||||
clientFailureCheckPeriod = -1
|
||||
minLargeMessageSize = maxMessageSize
|
||||
isUseGlobalPools = nodeSerializationEnv != null
|
||||
}
|
||||
connectThread = Thread({ artemisReconnectionLoop() }, "Artemis Connector Thread").apply {
|
||||
isDaemon = true
|
||||
}
|
||||
connectThread!!.start()
|
||||
}
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
stopArtemisConnection()
|
||||
statusSubscriber?.unsubscribe()
|
||||
statusSubscriber = null
|
||||
}
|
||||
|
||||
private fun stopArtemisConnection() {
|
||||
stateHelper.active = false
|
||||
val connectThread = state.locked {
|
||||
if (running) {
|
||||
log.info("Shutdown artemis")
|
||||
running = false
|
||||
started?.apply {
|
||||
producer.close()
|
||||
session.close()
|
||||
sessionFactory.close()
|
||||
}
|
||||
started = null
|
||||
locator?.close()
|
||||
locator = null
|
||||
val thread = connectThread
|
||||
connectThread = null
|
||||
thread
|
||||
} else null
|
||||
}
|
||||
connectThread?.interrupt()
|
||||
connectThread?.join(conf.politeShutdownPeriod.toLong())
|
||||
}
|
||||
|
||||
override val started: ArtemisMessagingClient.Started?
|
||||
get() = state.locked { started }
|
||||
|
||||
private fun artemisReconnectionLoop() {
|
||||
while (state.locked { running }) {
|
||||
val locator = state.locked { locator }
|
||||
if (locator == null) {
|
||||
break
|
||||
}
|
||||
try {
|
||||
log.info("Try create session factory")
|
||||
val newSessionFactory = locator.createSessionFactory()
|
||||
log.info("Got session factory")
|
||||
val latch = CountDownLatch(1)
|
||||
newSessionFactory.connection.addCloseListener {
|
||||
log.info("Connection close event")
|
||||
latch.countDown()
|
||||
}
|
||||
newSessionFactory.addFailoverListener { evt: FailoverEventType ->
|
||||
log.info("Session failover Event $evt")
|
||||
if (evt == FailoverEventType.FAILOVER_FAILED) {
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
val newSession = newSessionFactory.createSession(ArtemisMessagingComponent.NODE_USER,
|
||||
ArtemisMessagingComponent.NODE_USER,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
locator.isPreAcknowledge,
|
||||
ActiveMQClient.DEFAULT_ACK_BATCH_SIZE)
|
||||
newSession.start()
|
||||
log.info("Session created")
|
||||
val newProducer = newSession.createProducer()
|
||||
state.locked {
|
||||
started = ArtemisMessagingClient.Started(locator, newSessionFactory, newSession, newProducer)
|
||||
}
|
||||
stateHelper.active = true
|
||||
latch.await()
|
||||
stateHelper.active = false
|
||||
state.locked {
|
||||
started?.apply {
|
||||
producer.close()
|
||||
session.close()
|
||||
sessionFactory.close()
|
||||
}
|
||||
started = null
|
||||
}
|
||||
log.info("Session closed")
|
||||
} catch (ex: Exception) {
|
||||
log.trace("Caught exception", ex)
|
||||
}
|
||||
|
||||
try {
|
||||
// Sleep for a short while before attempting reconnect
|
||||
Thread.sleep(conf.artemisReconnectionInterval.toLong())
|
||||
} catch (ex: InterruptedException) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
log.info("Ended Artemis Connector Thread")
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.bridge.services.audit
|
||||
|
||||
import net.corda.bridge.services.api.BridgeAuditService
|
||||
import net.corda.bridge.services.api.BridgeConfiguration
|
||||
import net.corda.bridge.services.api.ServiceStateSupport
|
||||
import net.corda.bridge.services.util.ServiceStateHelper
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.core.utilities.trace
|
||||
import net.corda.nodeapi.internal.protonwrapper.messages.ReceivedMessage
|
||||
import java.net.InetSocketAddress
|
||||
|
||||
class LoggingBridgeAuditService(val conf: BridgeConfiguration,
|
||||
private val stateHelper: ServiceStateHelper = ServiceStateHelper(log)) : BridgeAuditService, ServiceStateSupport by stateHelper {
|
||||
companion object {
|
||||
val log = contextLogger()
|
||||
}
|
||||
|
||||
override fun start() {
|
||||
stateHelper.active = true
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
stateHelper.active = false
|
||||
}
|
||||
|
||||
override fun successfulConnectionEvent(inbound: Boolean, sourceIP: InetSocketAddress, certificateSubject: String, msg: String) {
|
||||
log.info(msg)
|
||||
}
|
||||
|
||||
override fun failedConnectionEvent(inbound: Boolean, sourceIP: InetSocketAddress?, certificateSubject: String?, msg: String) {
|
||||
log.warn(msg)
|
||||
}
|
||||
|
||||
override fun packetDropEvent(packet: ReceivedMessage?, msg: String) {
|
||||
log.info(msg)
|
||||
}
|
||||
|
||||
override fun packetAcceptedEvent(packet: ReceivedMessage) {
|
||||
log.trace { "Packet received from ${packet.sourceLegalName} uuid: ${packet.applicationProperties["_AMQ_DUPL_ID"]}" }
|
||||
}
|
||||
|
||||
override fun statusChangeEvent(msg: String) {
|
||||
log.info(msg)
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.bridge.services.config
|
||||
|
||||
import com.typesafe.config.Config
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import com.typesafe.config.ConfigFactory.systemEnvironment
|
||||
import com.typesafe.config.ConfigFactory.systemProperties
|
||||
import com.typesafe.config.ConfigParseOptions
|
||||
import com.typesafe.config.ConfigRenderOptions
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.nodeapi.internal.config.toProperties
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
fun configOf(vararg pairs: Pair<String, Any?>): Config = ConfigFactory.parseMap(mapOf(*pairs))
|
||||
operator fun Config.plus(overrides: Map<String, Any?>): Config = ConfigFactory.parseMap(overrides).withFallback(this)
|
||||
|
||||
object BridgeConfigHelper {
|
||||
const val BRIDGE_PROPERTY_PREFIX = "bridge."
|
||||
|
||||
private val log = LoggerFactory.getLogger(javaClass)
|
||||
fun loadConfig(baseDirectory: Path,
|
||||
configFile: Path = baseDirectory / "bridge.conf",
|
||||
allowMissingConfig: Boolean = false,
|
||||
configOverrides: Config = ConfigFactory.empty()): Config {
|
||||
val parseOptions = ConfigParseOptions.defaults()
|
||||
val defaultConfig = ConfigFactory.parseResources("bridgedefault.conf", parseOptions.setAllowMissing(false))
|
||||
val appConfig = ConfigFactory.parseFile(configFile.toFile(), parseOptions.setAllowMissing(allowMissingConfig))
|
||||
val systemOverrides = systemProperties().bridgeEntriesOnly()
|
||||
val environmentOverrides = systemEnvironment().bridgeEntriesOnly()
|
||||
|
||||
val finalConfig = configOverrides
|
||||
// Add substitution values here
|
||||
.withFallback(systemOverrides) //for database integration tests
|
||||
.withFallback(environmentOverrides) //for database integration tests
|
||||
.withFallback(configOf("baseDirectory" to baseDirectory.toString()))
|
||||
.withFallback(appConfig)
|
||||
.withFallback(defaultConfig)
|
||||
.resolve()
|
||||
log.info("Config:\n${finalConfig.root().render(ConfigRenderOptions.defaults())}")
|
||||
return finalConfig
|
||||
}
|
||||
|
||||
private fun Config.bridgeEntriesOnly(): Config {
|
||||
return ConfigFactory.parseMap(toProperties().filterKeys { (it as String).startsWith(BRIDGE_PROPERTY_PREFIX) }.mapKeys { (it.key as String).removePrefix(BRIDGE_PROPERTY_PREFIX) })
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.bridge.services.config
|
||||
|
||||
import com.typesafe.config.Config
|
||||
import net.corda.bridge.services.api.*
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent
|
||||
import net.corda.nodeapi.internal.config.NodeSSLConfiguration
|
||||
import net.corda.nodeapi.internal.config.parseAs
|
||||
import net.corda.nodeapi.internal.protonwrapper.netty.SocksProxyConfig
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
|
||||
|
||||
fun Config.parseAsBridgeConfiguration(): BridgeConfiguration = parseAs<BridgeConfigurationImpl>()
|
||||
|
||||
data class BridgeSSLConfigurationImpl(override val keyStorePassword: String,
|
||||
override val trustStorePassword: String,
|
||||
override val certificatesDirectory: Path = Paths.get("certificates"),
|
||||
override val sslKeystore: Path = certificatesDirectory / "sslkeystore.jks",
|
||||
override val trustStoreFile: Path = certificatesDirectory / "truststore.jks") : BridgeSSLConfiguration {
|
||||
constructor(config: NodeSSLConfiguration) : this(config.keyStorePassword, config.trustStorePassword, config.certificatesDirectory, config.sslKeystore, config.trustStoreFile)
|
||||
}
|
||||
|
||||
data class BridgeOutboundConfigurationImpl(override val artemisBrokerAddress: NetworkHostAndPort,
|
||||
override val customSSLConfiguration: BridgeSSLConfigurationImpl?,
|
||||
override val socksProxyConfig: SocksProxyConfig? = null) : BridgeOutboundConfiguration
|
||||
|
||||
data class BridgeInboundConfigurationImpl(override val listeningAddress: NetworkHostAndPort,
|
||||
override val customSSLConfiguration: BridgeSSLConfigurationImpl?) : BridgeInboundConfiguration
|
||||
|
||||
data class FloatInnerConfigurationImpl(override val floatAddresses: List<NetworkHostAndPort>,
|
||||
override val expectedCertificateSubject: CordaX500Name,
|
||||
override val customSSLConfiguration: BridgeSSLConfigurationImpl?,
|
||||
override val customFloatOuterSSLConfiguration: BridgeSSLConfigurationImpl?) : FloatInnerConfiguration
|
||||
|
||||
data class FloatOuterConfigurationImpl(override val floatAddress: NetworkHostAndPort,
|
||||
override val expectedCertificateSubject: CordaX500Name,
|
||||
override val customSSLConfiguration: BridgeSSLConfigurationImpl?) : FloatOuterConfiguration
|
||||
|
||||
data class BridgeHAConfigImpl(override val haConnectionString: String, override val haPriority: Int = 10) : BridgeHAConfig
|
||||
|
||||
data class BridgeConfigurationImpl(
|
||||
override val baseDirectory: Path,
|
||||
override val certificatesDirectory: Path = baseDirectory / "certificates",
|
||||
override val sslKeystore: Path = certificatesDirectory / "sslkeystore.jks",
|
||||
override val trustStoreFile: Path = certificatesDirectory / "truststore.jks",
|
||||
override val keyStorePassword: String,
|
||||
override val trustStorePassword: String,
|
||||
override val bridgeMode: BridgeMode,
|
||||
override val networkParametersPath: Path,
|
||||
override val outboundConfig: BridgeOutboundConfigurationImpl?,
|
||||
override val inboundConfig: BridgeInboundConfigurationImpl?,
|
||||
override val floatInnerConfig: FloatInnerConfigurationImpl?,
|
||||
override val floatOuterConfig: FloatOuterConfigurationImpl?,
|
||||
override val haConfig: BridgeHAConfigImpl?,
|
||||
override val enableAMQPPacketTrace: Boolean,
|
||||
override val artemisReconnectionInterval: Int = 5000,
|
||||
override val politeShutdownPeriod: Int = 1000,
|
||||
override val whitelistedHeaders: List<String> = ArtemisMessagingComponent.Companion.P2PMessagingHeaders.whitelistedHeaders.toList()
|
||||
) : BridgeConfiguration {
|
||||
init {
|
||||
if (bridgeMode == BridgeMode.SenderReceiver) {
|
||||
require(inboundConfig != null && outboundConfig != null) { "Missing required configuration" }
|
||||
} else if (bridgeMode == BridgeMode.FloatInner) {
|
||||
require(floatInnerConfig != null && outboundConfig != null) { "Missing required configuration" }
|
||||
} else if (bridgeMode == BridgeMode.FloatOuter) {
|
||||
require(inboundConfig != null && floatOuterConfig != null) { "Missing required configuration" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,115 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.bridge.services.filter
|
||||
|
||||
import net.corda.bridge.services.api.*
|
||||
import net.corda.bridge.services.util.ServiceStateCombiner
|
||||
import net.corda.bridge.services.util.ServiceStateHelper
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2PMessagingHeaders
|
||||
import net.corda.nodeapi.internal.protonwrapper.messages.ReceivedMessage
|
||||
import org.apache.activemq.artemis.api.core.SimpleString
|
||||
import org.apache.activemq.artemis.api.core.client.ActiveMQClient
|
||||
import org.apache.activemq.artemis.api.core.client.ClientProducer
|
||||
import org.apache.activemq.artemis.api.core.client.ClientSession
|
||||
import rx.Subscription
|
||||
|
||||
class SimpleMessageFilterService(val conf: BridgeConfiguration,
|
||||
val auditService: BridgeAuditService,
|
||||
val artemisConnectionService: BridgeArtemisConnectionService,
|
||||
val bridgeSenderService: BridgeSenderService,
|
||||
private val stateHelper: ServiceStateHelper = ServiceStateHelper(log)) : IncomingMessageFilterService, ServiceStateSupport by stateHelper {
|
||||
companion object {
|
||||
val log = contextLogger()
|
||||
}
|
||||
|
||||
private val statusFollower: ServiceStateCombiner
|
||||
private var statusSubscriber: Subscription? = null
|
||||
private val whiteListedAMQPHeaders: Set<String> = conf.whitelistedHeaders.toSet()
|
||||
private var inboundSession: ClientSession? = null
|
||||
private var inboundProducer: ClientProducer? = null
|
||||
|
||||
init {
|
||||
statusFollower = ServiceStateCombiner(listOf(auditService, artemisConnectionService, bridgeSenderService))
|
||||
}
|
||||
|
||||
override fun start() {
|
||||
statusSubscriber = statusFollower.activeChange.subscribe {
|
||||
if (it) {
|
||||
inboundSession = artemisConnectionService.started!!.sessionFactory.createSession(ArtemisMessagingComponent.NODE_USER, ArtemisMessagingComponent.NODE_USER, false, true, true, false, ActiveMQClient.DEFAULT_ACK_BATCH_SIZE)
|
||||
inboundProducer = inboundSession!!.createProducer()
|
||||
} else {
|
||||
inboundProducer?.close()
|
||||
inboundProducer = null
|
||||
inboundSession?.close()
|
||||
inboundSession = null
|
||||
}
|
||||
stateHelper.active = it
|
||||
}
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
inboundProducer?.close()
|
||||
inboundProducer = null
|
||||
inboundSession?.close()
|
||||
inboundSession = null
|
||||
stateHelper.active = false
|
||||
statusSubscriber?.unsubscribe()
|
||||
statusSubscriber = null
|
||||
}
|
||||
|
||||
private fun validateMessage(inboundMessage: ReceivedMessage) {
|
||||
if (!active) {
|
||||
throw IllegalStateException("Unable to forward message as Service Dependencies down")
|
||||
}
|
||||
val sourceLegalName = try {
|
||||
CordaX500Name.parse(inboundMessage.sourceLegalName)
|
||||
} catch (ex: IllegalArgumentException) {
|
||||
throw SecurityException("Invalid Legal Name ${inboundMessage.sourceLegalName}")
|
||||
}
|
||||
require(inboundMessage.payload.size > 0) { "No valid payload" }
|
||||
val validInboxTopic = bridgeSenderService.validateReceiveTopic(inboundMessage.topic, sourceLegalName)
|
||||
require(validInboxTopic) { "Topic not a legitimate Inbox for a node on this Artemis Broker ${inboundMessage.topic}" }
|
||||
require(inboundMessage.applicationProperties.keys.all { it!!.toString() in whiteListedAMQPHeaders }) { "Disallowed header present in ${inboundMessage.applicationProperties.keys.map { it.toString() }}" }
|
||||
}
|
||||
|
||||
override fun sendMessageToLocalBroker(inboundMessage: ReceivedMessage) {
|
||||
try {
|
||||
validateMessage(inboundMessage)
|
||||
} catch (ex: Exception) {
|
||||
auditService.packetDropEvent(inboundMessage, "Packet Failed validation checks: " + ex.message)
|
||||
inboundMessage.complete(true) // consume the bad message, so that it isn't redelivered forever.
|
||||
return
|
||||
}
|
||||
try {
|
||||
val session = inboundSession
|
||||
val producer = inboundProducer
|
||||
if (session == null || producer == null) {
|
||||
throw IllegalStateException("No artemis connection to forward message over")
|
||||
}
|
||||
val artemisMessage = session.createMessage(true)
|
||||
for (key in whiteListedAMQPHeaders) {
|
||||
if (inboundMessage.applicationProperties.containsKey(key)) {
|
||||
artemisMessage.putObjectProperty(key, inboundMessage.applicationProperties[key])
|
||||
}
|
||||
}
|
||||
artemisMessage.putStringProperty(P2PMessagingHeaders.bridgedCertificateSubject, SimpleString(inboundMessage.sourceLegalName))
|
||||
artemisMessage.writeBodyBufferBytes(inboundMessage.payload)
|
||||
producer.send(SimpleString(inboundMessage.topic), artemisMessage, { _ -> inboundMessage.complete(true) })
|
||||
auditService.packetAcceptedEvent(inboundMessage)
|
||||
} catch (ex: Exception) {
|
||||
log.error("Error trying to forward message", ex)
|
||||
inboundMessage.complete(false) // delivery failure. NAK back to source and await re-delivery attempts
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.bridge.services.ha
|
||||
|
||||
import net.corda.bridge.services.api.BridgeAuditService
|
||||
import net.corda.bridge.services.api.BridgeConfiguration
|
||||
import net.corda.bridge.services.api.BridgeMasterService
|
||||
import net.corda.bridge.services.api.ServiceStateSupport
|
||||
import net.corda.bridge.services.util.ServiceStateHelper
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.nodeapi.internal.zookeeper.CordaLeaderListener
|
||||
import net.corda.nodeapi.internal.zookeeper.ZkClient
|
||||
import net.corda.nodeapi.internal.zookeeper.ZkLeader
|
||||
import java.lang.management.ManagementFactory
|
||||
import java.net.InetAddress
|
||||
|
||||
class ExternalMasterElectionService(val conf: BridgeConfiguration,
|
||||
val auditService: BridgeAuditService,
|
||||
private val stateHelper: ServiceStateHelper = ServiceStateHelper(log)) : BridgeMasterService, ServiceStateSupport by stateHelper {
|
||||
|
||||
private var haElector: ZkLeader? = null
|
||||
private var leaderListener: CordaLeaderListener? = null
|
||||
|
||||
companion object {
|
||||
val log = contextLogger()
|
||||
}
|
||||
|
||||
init {
|
||||
require(conf.haConfig != null) { "Undefined HA Config" }
|
||||
require(conf.haConfig!!.haConnectionString.split(',').all { it.startsWith("zk//:") }) { "Only Zookeeper HA mode 'zk//:IPADDR:PORT supported" }
|
||||
}
|
||||
|
||||
override fun start() {
|
||||
val zkConf = conf.haConfig!!.haConnectionString.split(',').map { it.replace("zk//:", "") }.joinToString(",")
|
||||
val leaderPriority = conf.haConfig!!.haPriority
|
||||
val hostName: String = InetAddress.getLocalHost().hostName
|
||||
val info = ManagementFactory.getRuntimeMXBean()
|
||||
val pid = info.name.split("@").firstOrNull() // TODO Java 9 has better support for this
|
||||
val nodeId = "$hostName:$pid"
|
||||
val leaderElector = ZkClient(zkConf, "/bridge/ha", nodeId, leaderPriority)
|
||||
haElector = leaderElector
|
||||
val listener = object : CordaLeaderListener {
|
||||
override fun notLeader() {
|
||||
auditService.statusChangeEvent("Loss of leadership signalled by Zookeeper")
|
||||
stateHelper.active = false
|
||||
}
|
||||
|
||||
override fun isLeader() {
|
||||
auditService.statusChangeEvent("Acquired leadership from Zookeeper. Going active")
|
||||
stateHelper.active = true
|
||||
}
|
||||
|
||||
}
|
||||
leaderListener = listener
|
||||
leaderElector.addLeadershipListener(listener)
|
||||
leaderElector.start()
|
||||
auditService.statusChangeEvent("Requesting leadership from Zookeeper")
|
||||
leaderElector.requestLeadership()
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
auditService.statusChangeEvent("Stop requested")
|
||||
stateHelper.active = false
|
||||
haElector?.apply {
|
||||
if (leaderListener != null) {
|
||||
removeLeadershipListener(leaderListener!!)
|
||||
}
|
||||
relinquishLeadership()
|
||||
close()
|
||||
}
|
||||
haElector = null
|
||||
leaderListener = null
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.bridge.services.ha
|
||||
|
||||
import net.corda.bridge.services.api.BridgeAuditService
|
||||
import net.corda.bridge.services.api.BridgeConfiguration
|
||||
import net.corda.bridge.services.api.BridgeMasterService
|
||||
import net.corda.bridge.services.api.ServiceStateSupport
|
||||
import net.corda.bridge.services.util.ServiceStateHelper
|
||||
import net.corda.core.utilities.contextLogger
|
||||
|
||||
class SingleInstanceMasterService(val conf: BridgeConfiguration,
|
||||
val auditService: BridgeAuditService,
|
||||
private val stateHelper: ServiceStateHelper = ServiceStateHelper(log)) : BridgeMasterService, ServiceStateSupport by stateHelper {
|
||||
companion object {
|
||||
val log = contextLogger()
|
||||
}
|
||||
|
||||
override fun start() {
|
||||
auditService.statusChangeEvent("Single instance master going active immediately.")
|
||||
stateHelper.active = true
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
auditService.statusChangeEvent("Single instance master stopping")
|
||||
stateHelper.active = false
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,138 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.bridge.services.receiver
|
||||
|
||||
import net.corda.bridge.services.api.BridgeAMQPListenerService
|
||||
import net.corda.bridge.services.api.BridgeAuditService
|
||||
import net.corda.bridge.services.api.BridgeConfiguration
|
||||
import net.corda.bridge.services.api.ServiceStateSupport
|
||||
import net.corda.bridge.services.util.ServiceStateCombiner
|
||||
import net.corda.bridge.services.util.ServiceStateHelper
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER
|
||||
import net.corda.nodeapi.internal.crypto.KEYSTORE_TYPE
|
||||
import net.corda.nodeapi.internal.protonwrapper.messages.ReceivedMessage
|
||||
import net.corda.nodeapi.internal.protonwrapper.netty.AMQPServer
|
||||
import net.corda.nodeapi.internal.protonwrapper.netty.ConnectionChange
|
||||
import org.slf4j.LoggerFactory
|
||||
import rx.Observable
|
||||
import rx.Subscription
|
||||
import rx.subjects.PublishSubject
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.security.KeyStore
|
||||
import java.util.*
|
||||
|
||||
class BridgeAMQPListenerServiceImpl(val conf: BridgeConfiguration,
|
||||
val auditService: BridgeAuditService,
|
||||
private val stateHelper: ServiceStateHelper = ServiceStateHelper(log)) : BridgeAMQPListenerService, ServiceStateSupport by stateHelper {
|
||||
companion object {
|
||||
val log = contextLogger()
|
||||
val consoleLogger = LoggerFactory.getLogger("BasicInfo")
|
||||
}
|
||||
|
||||
private val statusFollower: ServiceStateCombiner
|
||||
private var statusSubscriber: Subscription? = null
|
||||
private var amqpServer: AMQPServer? = null
|
||||
private var keyStorePrivateKeyPassword: CharArray? = null
|
||||
private var onConnectSubscription: Subscription? = null
|
||||
private var onConnectAuditSubscription: Subscription? = null
|
||||
private var onReceiveSubscription: Subscription? = null
|
||||
|
||||
init {
|
||||
statusFollower = ServiceStateCombiner(listOf(auditService))
|
||||
}
|
||||
|
||||
override fun provisionKeysAndActivate(keyStoreBytes: ByteArray,
|
||||
keyStorePassword: CharArray,
|
||||
keyStorePrivateKeyPassword: CharArray,
|
||||
trustStoreBytes: ByteArray,
|
||||
trustStorePassword: CharArray) {
|
||||
require(active) { "AuditService must be active" }
|
||||
require(keyStorePassword !== keyStorePrivateKeyPassword) { "keyStorePassword and keyStorePrivateKeyPassword must reference distinct arrays!" }
|
||||
val keyStore = loadKeyStoreAndWipeKeys(keyStoreBytes, keyStorePassword)
|
||||
val trustStore = loadKeyStoreAndWipeKeys(trustStoreBytes, trustStorePassword)
|
||||
val bindAddress = conf.inboundConfig!!.listeningAddress
|
||||
val server = AMQPServer(bindAddress.host, bindAddress.port, PEER_USER, PEER_USER, keyStore, keyStorePrivateKeyPassword, trustStore, conf.enableAMQPPacketTrace)
|
||||
onConnectSubscription = server.onConnection.subscribe(_onConnection)
|
||||
onConnectAuditSubscription = server.onConnection.subscribe {
|
||||
if (it.connected) {
|
||||
auditService.successfulConnectionEvent(true, it.remoteAddress, it.remoteCert?.subjectDN?.name
|
||||
?: "", "Successful AMQP inbound connection")
|
||||
} else {
|
||||
auditService.failedConnectionEvent(true, it.remoteAddress, it.remoteCert?.subjectDN?.name
|
||||
?: "", "Failed AMQP inbound connection")
|
||||
}
|
||||
}
|
||||
onReceiveSubscription = server.onReceive.subscribe(_onReceive)
|
||||
amqpServer = server
|
||||
server.start()
|
||||
val msg = "Now listening for incoming connections on $bindAddress"
|
||||
auditService.statusChangeEvent(msg)
|
||||
consoleLogger.info(msg)
|
||||
}
|
||||
|
||||
private fun loadKeyStoreAndWipeKeys(keyStoreBytes: ByteArray, keyStorePassword: CharArray): KeyStore {
|
||||
val keyStore = KeyStore.getInstance(KEYSTORE_TYPE)
|
||||
ByteArrayInputStream(keyStoreBytes).use {
|
||||
keyStore.load(it, keyStorePassword)
|
||||
}
|
||||
// We overwrite the keys we don't need anymore
|
||||
Arrays.fill(keyStoreBytes, 0xAA.toByte())
|
||||
Arrays.fill(keyStorePassword, 0xAA55.toChar())
|
||||
return keyStore
|
||||
}
|
||||
|
||||
override fun wipeKeysAndDeactivate() {
|
||||
onReceiveSubscription?.unsubscribe()
|
||||
onReceiveSubscription = null
|
||||
onConnectSubscription?.unsubscribe()
|
||||
onConnectSubscription = null
|
||||
onConnectAuditSubscription?.unsubscribe()
|
||||
onConnectAuditSubscription = null
|
||||
if (running) {
|
||||
val msg = "AMQP Listener shutting down"
|
||||
auditService.statusChangeEvent(msg)
|
||||
consoleLogger.info(msg)
|
||||
}
|
||||
amqpServer?.close()
|
||||
amqpServer = null
|
||||
if (keyStorePrivateKeyPassword != null) {
|
||||
// Wipe the old password
|
||||
Arrays.fill(keyStorePrivateKeyPassword, 0xAA55.toChar())
|
||||
keyStorePrivateKeyPassword = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun start() {
|
||||
statusSubscriber = statusFollower.activeChange.subscribe {
|
||||
stateHelper.active = it
|
||||
}
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
stateHelper.active = false
|
||||
wipeKeysAndDeactivate()
|
||||
statusSubscriber?.unsubscribe()
|
||||
statusSubscriber = null
|
||||
}
|
||||
|
||||
override val running: Boolean
|
||||
get() = amqpServer?.listening ?: false
|
||||
|
||||
private val _onReceive = PublishSubject.create<ReceivedMessage>().toSerialized()
|
||||
override val onReceive: Observable<ReceivedMessage>
|
||||
get() = _onReceive
|
||||
|
||||
private val _onConnection = PublishSubject.create<ConnectionChange>().toSerialized()
|
||||
override val onConnection: Observable<ConnectionChange>
|
||||
get() = _onConnection
|
||||
|
||||
}
|
@ -0,0 +1,239 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.bridge.services.receiver
|
||||
|
||||
import net.corda.bridge.services.api.*
|
||||
import net.corda.bridge.services.config.BridgeSSLConfigurationImpl
|
||||
import net.corda.bridge.services.receiver.FloatControlTopics.FLOAT_DATA_TOPIC
|
||||
import net.corda.bridge.services.util.ServiceStateCombiner
|
||||
import net.corda.bridge.services.util.ServiceStateHelper
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.serialization.SerializationDefaults
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_PREFIX
|
||||
import net.corda.nodeapi.internal.protonwrapper.messages.MessageStatus
|
||||
import net.corda.nodeapi.internal.protonwrapper.messages.ReceivedMessage
|
||||
import net.corda.nodeapi.internal.protonwrapper.netty.AMQPServer
|
||||
import net.corda.nodeapi.internal.protonwrapper.netty.ConnectionChange
|
||||
import rx.Subscription
|
||||
import java.security.KeyStore
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.concurrent.withLock
|
||||
|
||||
|
||||
class FloatControlListenerService(val conf: BridgeConfiguration,
|
||||
val auditService: BridgeAuditService,
|
||||
val amqpListener: BridgeAMQPListenerService,
|
||||
private val stateHelper: ServiceStateHelper = ServiceStateHelper(log)) : FloatControlService, ServiceStateSupport by stateHelper {
|
||||
companion object {
|
||||
val log = contextLogger()
|
||||
}
|
||||
|
||||
private val lock = ReentrantLock()
|
||||
private val statusFollower: ServiceStateCombiner
|
||||
private var statusSubscriber: Subscription? = null
|
||||
private var incomingMessageSubscriber: Subscription? = null
|
||||
private var connectSubscriber: Subscription? = null
|
||||
private var receiveSubscriber: Subscription? = null
|
||||
private var amqpControlServer: AMQPServer? = null
|
||||
private val sslConfiguration: BridgeSSLConfiguration
|
||||
private val keyStore: KeyStore
|
||||
private val keyStorePrivateKeyPassword: String
|
||||
private val trustStore: KeyStore
|
||||
private val floatControlAddress = conf.floatOuterConfig!!.floatAddress
|
||||
private val floatClientName = conf.floatOuterConfig!!.expectedCertificateSubject
|
||||
private var activeConnectionInfo: ConnectionChange? = null
|
||||
private var forwardAddress: NetworkHostAndPort? = null
|
||||
private var forwardLegalName: String? = null
|
||||
|
||||
init {
|
||||
statusFollower = ServiceStateCombiner(listOf(auditService, amqpListener))
|
||||
sslConfiguration = conf.floatOuterConfig?.customSSLConfiguration ?: BridgeSSLConfigurationImpl(conf)
|
||||
keyStore = sslConfiguration.loadSslKeyStore().internal
|
||||
keyStorePrivateKeyPassword = sslConfiguration.keyStorePassword
|
||||
trustStore = sslConfiguration.loadTrustStore().internal
|
||||
}
|
||||
|
||||
|
||||
override fun start() {
|
||||
statusSubscriber = statusFollower.activeChange.subscribe {
|
||||
if (it) {
|
||||
startControlListener()
|
||||
} else {
|
||||
stopControlListener()
|
||||
}
|
||||
stateHelper.active = it
|
||||
}
|
||||
incomingMessageSubscriber = amqpListener.onReceive.subscribe {
|
||||
forwardReceivedMessage(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun startControlListener() {
|
||||
lock.withLock {
|
||||
val controlServer = AMQPServer(floatControlAddress.host, floatControlAddress.port, null, null, keyStore, keyStorePrivateKeyPassword, trustStore, conf.enableAMQPPacketTrace)
|
||||
connectSubscriber = controlServer.onConnection.subscribe { onConnectToControl(it) }
|
||||
receiveSubscriber = controlServer.onReceive.subscribe { onControlMessage(it) }
|
||||
amqpControlServer = controlServer
|
||||
controlServer.start()
|
||||
}
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
lock.withLock {
|
||||
stateHelper.active = false
|
||||
stopControlListener()
|
||||
statusSubscriber?.unsubscribe()
|
||||
statusSubscriber = null
|
||||
}
|
||||
}
|
||||
|
||||
private fun stopControlListener() {
|
||||
lock.withLock {
|
||||
if (amqpListener.running) {
|
||||
amqpListener.wipeKeysAndDeactivate()
|
||||
}
|
||||
connectSubscriber?.unsubscribe()
|
||||
connectSubscriber = null
|
||||
amqpControlServer?.stop()
|
||||
receiveSubscriber?.unsubscribe()
|
||||
receiveSubscriber = null
|
||||
amqpControlServer = null
|
||||
activeConnectionInfo = null
|
||||
forwardAddress = null
|
||||
forwardLegalName = null
|
||||
incomingMessageSubscriber?.unsubscribe()
|
||||
incomingMessageSubscriber = null
|
||||
}
|
||||
}
|
||||
|
||||
private fun onConnectToControl(connectionChange: ConnectionChange) {
|
||||
auditService.statusChangeEvent("Connection change on float control port $connectionChange")
|
||||
lock.withLock {
|
||||
val currentConnection = activeConnectionInfo
|
||||
if (currentConnection != null) {
|
||||
// If there is a new valid TLS connection kill old connection.
|
||||
// Else if this event signals loss of current connection wipe the keys
|
||||
if (connectionChange.connected || (currentConnection.remoteAddress == connectionChange.remoteAddress)) {
|
||||
if (amqpListener.running) {
|
||||
amqpListener.wipeKeysAndDeactivate()
|
||||
}
|
||||
amqpControlServer?.dropConnection(currentConnection.remoteAddress)
|
||||
activeConnectionInfo = null
|
||||
forwardAddress = null
|
||||
forwardLegalName = null
|
||||
}
|
||||
}
|
||||
if (connectionChange.connected) {
|
||||
if (connectionChange.remoteCert != null) {
|
||||
val certificateSubject = CordaX500Name.parse(connectionChange.remoteCert!!.subjectDN.toString())
|
||||
if (certificateSubject == floatClientName) {
|
||||
activeConnectionInfo = connectionChange
|
||||
} else {
|
||||
amqpControlServer?.dropConnection(connectionChange.remoteAddress)
|
||||
}
|
||||
} else {
|
||||
amqpControlServer?.dropConnection(connectionChange.remoteAddress)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onControlMessage(receivedMessage: ReceivedMessage) {
|
||||
if (!receivedMessage.checkTunnelControlTopic()) {
|
||||
auditService.packetDropEvent(receivedMessage, "Invalid control topic packet received on topic ${receivedMessage.topic}!!")
|
||||
receivedMessage.complete(true)
|
||||
return
|
||||
}
|
||||
val controlMessage = try {
|
||||
if (CordaX500Name.parse(receivedMessage.sourceLegalName) != floatClientName) {
|
||||
auditService.packetDropEvent(receivedMessage, "Invalid control source legal name!!")
|
||||
receivedMessage.complete(true)
|
||||
return
|
||||
}
|
||||
receivedMessage.payload.deserialize<TunnelControlMessage>()
|
||||
} catch (ex: Exception) {
|
||||
receivedMessage.complete(true)
|
||||
return
|
||||
}
|
||||
lock.withLock {
|
||||
when (controlMessage) {
|
||||
is ActivateFloat -> {
|
||||
log.info("Received Tunnel Activate message")
|
||||
amqpListener.provisionKeysAndActivate(controlMessage.keyStoreBytes,
|
||||
controlMessage.keyStorePassword,
|
||||
controlMessage.keyStorePrivateKeyPassword,
|
||||
controlMessage.trustStoreBytes,
|
||||
controlMessage.trustStorePassword)
|
||||
forwardAddress = receivedMessage.sourceLink
|
||||
forwardLegalName = receivedMessage.sourceLegalName
|
||||
}
|
||||
is DeactivateFloat -> {
|
||||
log.info("Received Tunnel Deactivate message")
|
||||
if (amqpListener.running) {
|
||||
amqpListener.wipeKeysAndDeactivate()
|
||||
}
|
||||
forwardAddress = null
|
||||
forwardLegalName = null
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
receivedMessage.complete(true)
|
||||
}
|
||||
|
||||
private fun forwardReceivedMessage(message: ReceivedMessage) {
|
||||
val amqpControl = lock.withLock {
|
||||
if (amqpControlServer == null ||
|
||||
activeConnectionInfo == null ||
|
||||
forwardLegalName == null ||
|
||||
forwardAddress == null ||
|
||||
!stateHelper.active) {
|
||||
null
|
||||
} else {
|
||||
amqpControlServer
|
||||
}
|
||||
}
|
||||
if (amqpControl == null) {
|
||||
message.complete(true) // consume message so it isn't resent forever
|
||||
return
|
||||
}
|
||||
if (!message.topic.startsWith(P2P_PREFIX)) {
|
||||
auditService.packetDropEvent(message, "Message topic is not a valid peer namespace ${message.topic}")
|
||||
message.complete(true) // consume message so it isn't resent forever
|
||||
return
|
||||
}
|
||||
val appProperties = message.applicationProperties.map { Pair(it.key!!.toString(), it.value) }.toList()
|
||||
try {
|
||||
val wrappedMessage = FloatDataPacket(message.topic,
|
||||
appProperties,
|
||||
message.payload,
|
||||
CordaX500Name.parse(message.sourceLegalName),
|
||||
message.sourceLink,
|
||||
CordaX500Name.parse(message.destinationLegalName),
|
||||
message.destinationLink)
|
||||
val amqpForwardMessage = amqpControl.createMessage(wrappedMessage.serialize(context = SerializationDefaults.P2P_CONTEXT).bytes,
|
||||
FLOAT_DATA_TOPIC,
|
||||
forwardLegalName!!,
|
||||
forwardAddress!!,
|
||||
emptyMap())
|
||||
amqpForwardMessage.onComplete.then { message.complete(it.get() == MessageStatus.Acknowledged) }
|
||||
amqpControl.write(amqpForwardMessage)
|
||||
} catch (ex: Exception) {
|
||||
log.error("Failed to forward message", ex)
|
||||
message.complete(false)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.bridge.services.receiver
|
||||
|
||||
import net.corda.bridge.services.api.*
|
||||
import net.corda.bridge.services.util.ServiceStateCombiner
|
||||
import net.corda.bridge.services.util.ServiceStateHelper
|
||||
import net.corda.core.internal.readAll
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.nodeapi.internal.config.SSLConfiguration
|
||||
import net.corda.nodeapi.internal.protonwrapper.messages.ReceivedMessage
|
||||
import rx.Subscription
|
||||
|
||||
class InProcessBridgeReceiverService(val conf: BridgeConfiguration,
|
||||
val auditService: BridgeAuditService,
|
||||
haService: BridgeMasterService,
|
||||
val amqpListenerService: BridgeAMQPListenerService,
|
||||
val filterService: IncomingMessageFilterService,
|
||||
private val stateHelper: ServiceStateHelper = ServiceStateHelper(log)) : BridgeReceiverService, ServiceStateSupport by stateHelper {
|
||||
companion object {
|
||||
val log = contextLogger()
|
||||
}
|
||||
|
||||
private val statusFollower: ServiceStateCombiner
|
||||
private var statusSubscriber: Subscription? = null
|
||||
private var receiveSubscriber: Subscription? = null
|
||||
private val sslConfiguration: SSLConfiguration
|
||||
|
||||
init {
|
||||
statusFollower = ServiceStateCombiner(listOf(auditService, haService, amqpListenerService, filterService))
|
||||
sslConfiguration = conf.inboundConfig?.customSSLConfiguration ?: conf
|
||||
}
|
||||
|
||||
override fun start() {
|
||||
statusSubscriber = statusFollower.activeChange.subscribe {
|
||||
if (it) {
|
||||
val keyStoreBytes = sslConfiguration.sslKeystore.readAll()
|
||||
val trustStoreBytes = sslConfiguration.trustStoreFile.readAll()
|
||||
amqpListenerService.provisionKeysAndActivate(keyStoreBytes,
|
||||
sslConfiguration.keyStorePassword.toCharArray(),
|
||||
sslConfiguration.keyStorePassword.toCharArray(),
|
||||
trustStoreBytes,
|
||||
sslConfiguration.trustStorePassword.toCharArray())
|
||||
} else {
|
||||
if (amqpListenerService.running) {
|
||||
amqpListenerService.wipeKeysAndDeactivate()
|
||||
}
|
||||
}
|
||||
stateHelper.active = it
|
||||
}
|
||||
receiveSubscriber = amqpListenerService.onReceive.subscribe {
|
||||
processMessage(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun processMessage(receivedMessage: ReceivedMessage) {
|
||||
filterService.sendMessageToLocalBroker(receivedMessage)
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
stateHelper.active = false
|
||||
if (amqpListenerService.running) {
|
||||
amqpListenerService.wipeKeysAndDeactivate()
|
||||
}
|
||||
receiveSubscriber?.unsubscribe()
|
||||
receiveSubscriber = null
|
||||
statusSubscriber?.unsubscribe()
|
||||
statusSubscriber = null
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.bridge.services.receiver
|
||||
|
||||
import net.corda.bridge.services.receiver.FloatControlTopics.FLOAT_CONTROL_TOPIC
|
||||
import net.corda.bridge.services.receiver.FloatControlTopics.FLOAT_DATA_TOPIC
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.nodeapi.internal.protonwrapper.messages.ReceivedMessage
|
||||
|
||||
@CordaSerializable
|
||||
sealed class TunnelControlMessage
|
||||
|
||||
object FloatControlTopics {
|
||||
const val FLOAT_CONTROL_TOPIC = "float.control"
|
||||
const val FLOAT_DATA_TOPIC = "float.forward"
|
||||
}
|
||||
|
||||
internal class ActivateFloat(val keyStoreBytes: ByteArray,
|
||||
val keyStorePassword: CharArray,
|
||||
val keyStorePrivateKeyPassword: CharArray,
|
||||
val trustStoreBytes: ByteArray,
|
||||
val trustStorePassword: CharArray) : TunnelControlMessage()
|
||||
|
||||
class DeactivateFloat : TunnelControlMessage()
|
||||
|
||||
fun ReceivedMessage.checkTunnelControlTopic() = (topic == FLOAT_CONTROL_TOPIC)
|
||||
|
||||
@CordaSerializable
|
||||
internal class FloatDataPacket(val topic: String,
|
||||
val originalHeaders: List<Pair<String, Any?>>,
|
||||
val originalPayload: ByteArray,
|
||||
val sourceLegalName: CordaX500Name,
|
||||
val sourceLink: NetworkHostAndPort,
|
||||
val destinationLegalName: CordaX500Name,
|
||||
val destinationLink: NetworkHostAndPort)
|
||||
|
||||
fun ReceivedMessage.checkTunnelDataTopic() = (topic == FLOAT_DATA_TOPIC)
|
@ -0,0 +1,204 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.bridge.services.receiver
|
||||
|
||||
import net.corda.bridge.services.api.*
|
||||
import net.corda.bridge.services.receiver.FloatControlTopics.FLOAT_CONTROL_TOPIC
|
||||
import net.corda.bridge.services.util.ServiceStateCombiner
|
||||
import net.corda.bridge.services.util.ServiceStateHelper
|
||||
import net.corda.core.crypto.newSecureRandom
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.readAll
|
||||
import net.corda.core.serialization.SerializationDefaults
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.core.utilities.debug
|
||||
import net.corda.nodeapi.internal.config.SSLConfiguration
|
||||
import net.corda.nodeapi.internal.protonwrapper.messages.MessageStatus
|
||||
import net.corda.nodeapi.internal.protonwrapper.messages.ReceivedMessage
|
||||
import net.corda.nodeapi.internal.protonwrapper.netty.AMQPClient
|
||||
import net.corda.nodeapi.internal.protonwrapper.netty.ConnectionChange
|
||||
import rx.Subscription
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.security.KeyStore
|
||||
import java.security.SecureRandom
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.TimeoutException
|
||||
|
||||
class TunnelingBridgeReceiverService(val conf: BridgeConfiguration,
|
||||
val auditService: BridgeAuditService,
|
||||
haService: BridgeMasterService,
|
||||
val filterService: IncomingMessageFilterService,
|
||||
private val stateHelper: ServiceStateHelper = ServiceStateHelper(log)) : BridgeReceiverService, ServiceStateSupport by stateHelper {
|
||||
companion object {
|
||||
val log = contextLogger()
|
||||
}
|
||||
|
||||
private val statusFollower: ServiceStateCombiner
|
||||
private var statusSubscriber: Subscription? = null
|
||||
private var connectSubscriber: Subscription? = null
|
||||
private var receiveSubscriber: Subscription? = null
|
||||
private var amqpControlClient: AMQPClient? = null
|
||||
private val controlLinkSSLConfiguration: SSLConfiguration
|
||||
private val floatListenerSSLConfiguration: SSLConfiguration
|
||||
private val controlLinkKeyStore: KeyStore
|
||||
private val controLinkKeyStorePrivateKeyPassword: String
|
||||
private val controlLinkTrustStore: KeyStore
|
||||
private val expectedCertificateSubject: CordaX500Name
|
||||
private val secureRandom: SecureRandom = newSecureRandom()
|
||||
|
||||
init {
|
||||
statusFollower = ServiceStateCombiner(listOf(auditService, haService, filterService))
|
||||
controlLinkSSLConfiguration = conf.floatInnerConfig?.customSSLConfiguration ?: conf
|
||||
floatListenerSSLConfiguration = conf.floatInnerConfig?.customFloatOuterSSLConfiguration ?: conf
|
||||
controlLinkKeyStore = controlLinkSSLConfiguration.loadSslKeyStore().internal
|
||||
controLinkKeyStorePrivateKeyPassword = controlLinkSSLConfiguration.keyStorePassword
|
||||
controlLinkTrustStore = controlLinkSSLConfiguration.loadTrustStore().internal
|
||||
expectedCertificateSubject = conf.floatInnerConfig!!.expectedCertificateSubject
|
||||
}
|
||||
|
||||
|
||||
override fun start() {
|
||||
statusSubscriber = statusFollower.activeChange.subscribe {
|
||||
if (it) {
|
||||
val floatAddresses = conf.floatInnerConfig!!.floatAddresses
|
||||
val controlClient = AMQPClient(floatAddresses, setOf(expectedCertificateSubject), null, null, controlLinkKeyStore, controLinkKeyStorePrivateKeyPassword, controlLinkTrustStore, conf.enableAMQPPacketTrace)
|
||||
connectSubscriber = controlClient.onConnection.subscribe { onConnectToControl(it) }
|
||||
receiveSubscriber = controlClient.onReceive.subscribe { onFloatMessage(it) }
|
||||
amqpControlClient = controlClient
|
||||
controlClient.start()
|
||||
} else {
|
||||
stateHelper.active = false
|
||||
closeAMQPClient()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun closeAMQPClient() {
|
||||
connectSubscriber?.unsubscribe()
|
||||
connectSubscriber = null
|
||||
receiveSubscriber?.unsubscribe()
|
||||
receiveSubscriber = null
|
||||
amqpControlClient?.apply {
|
||||
val deactivateMessage = DeactivateFloat()
|
||||
val amqpDeactivateMessage = amqpControlClient!!.createMessage(deactivateMessage.serialize(context = SerializationDefaults.P2P_CONTEXT).bytes,
|
||||
FLOAT_CONTROL_TOPIC,
|
||||
expectedCertificateSubject.toString(),
|
||||
emptyMap())
|
||||
try {
|
||||
amqpControlClient!!.write(amqpDeactivateMessage)
|
||||
} catch (ex: IllegalStateException) {
|
||||
// ignore if channel is already closed
|
||||
}
|
||||
try {
|
||||
// Await acknowledgement of the deactivate message, but don't block our shutdown forever.
|
||||
amqpDeactivateMessage.onComplete.get(conf.politeShutdownPeriod.toLong(), TimeUnit.MILLISECONDS)
|
||||
} catch (ex: TimeoutException) {
|
||||
// Ignore
|
||||
}
|
||||
stop()
|
||||
}
|
||||
amqpControlClient = null
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
stateHelper.active = false
|
||||
closeAMQPClient()
|
||||
statusSubscriber?.unsubscribe()
|
||||
statusSubscriber = null
|
||||
}
|
||||
|
||||
private fun onConnectToControl(connectionChange: ConnectionChange) {
|
||||
auditService.statusChangeEvent("Connection change on float control port $connectionChange")
|
||||
if (connectionChange.connected) {
|
||||
val (freshKeyStorePassword, freshKeyStoreKeyPassword, recodedKeyStore) = recodeKeyStore(floatListenerSSLConfiguration)
|
||||
val trustStoreBytes = floatListenerSSLConfiguration.trustStoreFile.readAll()
|
||||
val activateMessage = ActivateFloat(recodedKeyStore,
|
||||
freshKeyStorePassword,
|
||||
freshKeyStoreKeyPassword,
|
||||
trustStoreBytes,
|
||||
floatListenerSSLConfiguration.trustStorePassword.toCharArray())
|
||||
val amqpActivateMessage = amqpControlClient!!.createMessage(activateMessage.serialize(context = SerializationDefaults.P2P_CONTEXT).bytes,
|
||||
FLOAT_CONTROL_TOPIC,
|
||||
expectedCertificateSubject.toString(),
|
||||
emptyMap())
|
||||
try {
|
||||
amqpControlClient!!.write(amqpActivateMessage)
|
||||
} catch (ex: IllegalStateException) {
|
||||
stateHelper.active = false // lost the channel
|
||||
return
|
||||
}
|
||||
amqpActivateMessage.onComplete.then {
|
||||
stateHelper.active = (it.get() == MessageStatus.Acknowledged)
|
||||
//TODO Retry?
|
||||
}
|
||||
} else {
|
||||
stateHelper.active = false
|
||||
}
|
||||
}
|
||||
|
||||
// Recode KeyStore to use a fresh random password for entries and overall
|
||||
private fun recodeKeyStore(sslConfiguration: SSLConfiguration): Triple<CharArray, CharArray, ByteArray> {
|
||||
val keyStoreOriginal = sslConfiguration.loadSslKeyStore().internal
|
||||
val originalKeyStorePassword = sslConfiguration.keyStorePassword.toCharArray()
|
||||
val freshKeyStorePassword = CharArray(20) { secureRandom.nextInt(0xD800).toChar() } // Stick to single character Unicode range
|
||||
val freshPrivateKeyPassword = CharArray(20) { secureRandom.nextInt(0xD800).toChar() } // Stick to single character Unicode range
|
||||
for (alias in keyStoreOriginal.aliases()) {
|
||||
if (keyStoreOriginal.isKeyEntry(alias)) {
|
||||
// Recode key entries to new password
|
||||
val privateKey = keyStoreOriginal.getKey(alias, originalKeyStorePassword)
|
||||
val certs = keyStoreOriginal.getCertificateChain(alias)
|
||||
keyStoreOriginal.setKeyEntry(alias, privateKey, freshPrivateKeyPassword, certs)
|
||||
}
|
||||
}
|
||||
// Serialize re-keyed KeyStore to ByteArray
|
||||
val recodedKeyStore = ByteArrayOutputStream().use {
|
||||
keyStoreOriginal.store(it, freshKeyStorePassword)
|
||||
it
|
||||
}.toByteArray()
|
||||
|
||||
return Triple(freshKeyStorePassword, freshPrivateKeyPassword, recodedKeyStore)
|
||||
}
|
||||
|
||||
private fun onFloatMessage(receivedMessage: ReceivedMessage) {
|
||||
if (!receivedMessage.checkTunnelDataTopic()) {
|
||||
auditService.packetDropEvent(receivedMessage, "Invalid float inbound topic received ${receivedMessage.topic}!!")
|
||||
receivedMessage.complete(true)
|
||||
return
|
||||
}
|
||||
val innerMessage = try {
|
||||
receivedMessage.payload.deserialize<FloatDataPacket>()
|
||||
} catch (ex: Exception) {
|
||||
auditService.packetDropEvent(receivedMessage, "Unable to decode Float Control message")
|
||||
receivedMessage.complete(true)
|
||||
return
|
||||
}
|
||||
log.debug { "Received message from ${innerMessage.sourceLegalName}" }
|
||||
val onwardMessage = object : ReceivedMessage {
|
||||
override val topic: String = innerMessage.topic
|
||||
override val applicationProperties: Map<Any?, Any?> = innerMessage.originalHeaders.toMap()
|
||||
override val payload: ByteArray = innerMessage.originalPayload
|
||||
override val sourceLegalName: String = innerMessage.sourceLegalName.toString()
|
||||
override val sourceLink: NetworkHostAndPort = receivedMessage.sourceLink
|
||||
|
||||
override fun complete(accepted: Boolean) {
|
||||
receivedMessage.complete(accepted)
|
||||
}
|
||||
|
||||
override val destinationLegalName: String = innerMessage.destinationLegalName.toString()
|
||||
override val destinationLink: NetworkHostAndPort = innerMessage.destinationLink
|
||||
}
|
||||
filterService.sendMessageToLocalBroker(onwardMessage)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.bridge.services.sender
|
||||
|
||||
import net.corda.bridge.services.api.*
|
||||
import net.corda.bridge.services.util.ServiceStateCombiner
|
||||
import net.corda.bridge.services.util.ServiceStateHelper
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingClient
|
||||
import net.corda.nodeapi.internal.ArtemisSessionProvider
|
||||
import net.corda.nodeapi.internal.bridging.BridgeControlListener
|
||||
import rx.Subscription
|
||||
|
||||
class DirectBridgeSenderService(val conf: BridgeConfiguration,
|
||||
val auditService: BridgeAuditService,
|
||||
val haService: BridgeMasterService,
|
||||
val artemisConnectionService: BridgeArtemisConnectionService,
|
||||
private val stateHelper: ServiceStateHelper = ServiceStateHelper(log)) : BridgeSenderService, ServiceStateSupport by stateHelper {
|
||||
companion object {
|
||||
val log = contextLogger()
|
||||
}
|
||||
|
||||
private val statusFollower: ServiceStateCombiner
|
||||
private var statusSubscriber: Subscription? = null
|
||||
private var connectionSubscriber: Subscription? = null
|
||||
private var listenerActiveSubscriber: Subscription? = null
|
||||
private var bridgeControlListener: BridgeControlListener = BridgeControlListener(conf, conf.outboundConfig!!.socksProxyConfig, { ForwardingArtemisMessageClient(artemisConnectionService) })
|
||||
|
||||
init {
|
||||
statusFollower = ServiceStateCombiner(listOf(auditService, artemisConnectionService, haService))
|
||||
}
|
||||
|
||||
private class ForwardingArtemisMessageClient(val artemisConnectionService: BridgeArtemisConnectionService) : ArtemisSessionProvider {
|
||||
override fun start(): ArtemisMessagingClient.Started {
|
||||
// We don't want to start and stop artemis from clients as the lifecycle management is provided by the BridgeArtemisConnectionService
|
||||
return artemisConnectionService.started!!
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
// We don't want to start and stop artemis from clients as the lifecycle management is provided by the BridgeArtemisConnectionService
|
||||
}
|
||||
|
||||
override val started: ArtemisMessagingClient.Started?
|
||||
get() = artemisConnectionService.started
|
||||
|
||||
}
|
||||
|
||||
override fun start() {
|
||||
statusSubscriber = statusFollower.activeChange.subscribe { ready ->
|
||||
if (ready) {
|
||||
listenerActiveSubscriber = bridgeControlListener.activeChange.subscribe {
|
||||
stateHelper.active = it
|
||||
}
|
||||
bridgeControlListener.start()
|
||||
auditService.statusChangeEvent("Waiting for activation by at least one bridge control inbox registration")
|
||||
} else {
|
||||
stateHelper.active = false
|
||||
listenerActiveSubscriber?.unsubscribe()
|
||||
listenerActiveSubscriber = null
|
||||
bridgeControlListener.stop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
stateHelper.active = false
|
||||
listenerActiveSubscriber?.unsubscribe()
|
||||
listenerActiveSubscriber = null
|
||||
bridgeControlListener.stop()
|
||||
connectionSubscriber?.unsubscribe()
|
||||
connectionSubscriber = null
|
||||
statusSubscriber?.unsubscribe()
|
||||
statusSubscriber = null
|
||||
}
|
||||
|
||||
override fun validateReceiveTopic(topic: String, sourceLegalName: CordaX500Name): Boolean = bridgeControlListener.validateReceiveTopic(topic)
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.bridge.services.supervisors
|
||||
|
||||
import net.corda.bridge.services.api.*
|
||||
import net.corda.bridge.services.artemis.BridgeArtemisConnectionServiceImpl
|
||||
import net.corda.bridge.services.filter.SimpleMessageFilterService
|
||||
import net.corda.bridge.services.ha.ExternalMasterElectionService
|
||||
import net.corda.bridge.services.ha.SingleInstanceMasterService
|
||||
import net.corda.bridge.services.receiver.InProcessBridgeReceiverService
|
||||
import net.corda.bridge.services.receiver.TunnelingBridgeReceiverService
|
||||
import net.corda.bridge.services.sender.DirectBridgeSenderService
|
||||
import net.corda.bridge.services.util.ServiceStateCombiner
|
||||
import net.corda.bridge.services.util.ServiceStateHelper
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import org.slf4j.LoggerFactory
|
||||
import rx.Subscription
|
||||
|
||||
class BridgeSupervisorServiceImpl(val conf: BridgeConfiguration,
|
||||
maxMessageSize: Int,
|
||||
val auditService: BridgeAuditService,
|
||||
inProcessAMQPListenerService: BridgeAMQPListenerService?,
|
||||
private val stateHelper: ServiceStateHelper = ServiceStateHelper(log)) : BridgeSupervisorService, ServiceStateSupport by stateHelper {
|
||||
companion object {
|
||||
val log = contextLogger()
|
||||
val consoleLogger = LoggerFactory.getLogger("BasicInfo")
|
||||
}
|
||||
|
||||
private val haService: BridgeMasterService
|
||||
private val artemisService: BridgeArtemisConnectionServiceImpl
|
||||
private val senderService: BridgeSenderService
|
||||
private val receiverService: BridgeReceiverService
|
||||
private val filterService: IncomingMessageFilterService
|
||||
private val statusFollower: ServiceStateCombiner
|
||||
private var statusSubscriber: Subscription? = null
|
||||
|
||||
init {
|
||||
if (conf.haConfig == null) {
|
||||
haService = SingleInstanceMasterService(conf, auditService)
|
||||
} else {
|
||||
haService = ExternalMasterElectionService(conf, auditService)
|
||||
}
|
||||
artemisService = BridgeArtemisConnectionServiceImpl(conf, maxMessageSize, auditService)
|
||||
senderService = DirectBridgeSenderService(conf, auditService, haService, artemisService)
|
||||
filterService = SimpleMessageFilterService(conf, auditService, artemisService, senderService)
|
||||
receiverService = if (conf.bridgeMode == BridgeMode.SenderReceiver) {
|
||||
InProcessBridgeReceiverService(conf, auditService, haService, inProcessAMQPListenerService!!, filterService)
|
||||
} else {
|
||||
require(inProcessAMQPListenerService == null) { "Should not have an in process instance of the AMQPListenerService" }
|
||||
TunnelingBridgeReceiverService(conf, auditService, haService, filterService)
|
||||
}
|
||||
statusFollower = ServiceStateCombiner(listOf(haService, senderService, receiverService, filterService))
|
||||
activeChange.subscribe {
|
||||
consoleLogger.info("BridgeSupervisorService: active = $it")
|
||||
}
|
||||
}
|
||||
|
||||
override fun start() {
|
||||
statusSubscriber = statusFollower.activeChange.subscribe {
|
||||
stateHelper.active = it
|
||||
}
|
||||
artemisService.start()
|
||||
senderService.start()
|
||||
receiverService.start()
|
||||
filterService.start()
|
||||
haService.start()
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
stateHelper.active = false
|
||||
haService.stop()
|
||||
senderService.stop()
|
||||
receiverService.stop()
|
||||
filterService.stop()
|
||||
artemisService.stop()
|
||||
statusSubscriber?.unsubscribe()
|
||||
statusSubscriber = null
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.bridge.services.supervisors
|
||||
|
||||
import net.corda.bridge.services.api.*
|
||||
import net.corda.bridge.services.receiver.BridgeAMQPListenerServiceImpl
|
||||
import net.corda.bridge.services.receiver.FloatControlListenerService
|
||||
import net.corda.bridge.services.util.ServiceStateCombiner
|
||||
import net.corda.bridge.services.util.ServiceStateHelper
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import org.slf4j.LoggerFactory
|
||||
import rx.Subscription
|
||||
|
||||
class FloatSupervisorServiceImpl(val conf: BridgeConfiguration,
|
||||
val maxMessageSize: Int,
|
||||
val auditService: BridgeAuditService,
|
||||
private val stateHelper: ServiceStateHelper = ServiceStateHelper(log)) : FloatSupervisorService, ServiceStateSupport by stateHelper {
|
||||
companion object {
|
||||
val log = contextLogger()
|
||||
val consoleLogger = LoggerFactory.getLogger("BasicInfo")
|
||||
}
|
||||
|
||||
override val amqpListenerService: BridgeAMQPListenerService
|
||||
private val floatControlService: FloatControlService?
|
||||
private val statusFollower: ServiceStateCombiner
|
||||
private var statusSubscriber: Subscription? = null
|
||||
|
||||
init {
|
||||
amqpListenerService = BridgeAMQPListenerServiceImpl(conf, auditService)
|
||||
floatControlService = if (conf.bridgeMode == BridgeMode.FloatOuter) {
|
||||
require(conf.haConfig == null) { "Float process should not have HA config, that is controlled via the bridge." }
|
||||
FloatControlListenerService(conf, auditService, amqpListenerService)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
statusFollower = ServiceStateCombiner(listOf(amqpListenerService, floatControlService).filterNotNull())
|
||||
activeChange.subscribe {
|
||||
consoleLogger.info("FloatSupervisorService: active = $it")
|
||||
}
|
||||
}
|
||||
|
||||
override fun start() {
|
||||
statusSubscriber = statusFollower.activeChange.subscribe {
|
||||
stateHelper.active = it
|
||||
}
|
||||
amqpListenerService.start()
|
||||
floatControlService?.start()
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
stateHelper.active = false
|
||||
floatControlService?.stop()
|
||||
amqpListenerService.stop()
|
||||
statusSubscriber?.unsubscribe()
|
||||
statusSubscriber = null
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.bridge.services.util
|
||||
|
||||
import net.corda.bridge.services.api.ServiceStateSupport
|
||||
import org.slf4j.Logger
|
||||
import rx.Observable
|
||||
import rx.subjects.BehaviorSubject
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.concurrent.withLock
|
||||
|
||||
/**
|
||||
* Simple implementation of [ServiceStateSupport] service domino logic using RxObservables.
|
||||
*/
|
||||
class ServiceStateHelper(val log: Logger) : ServiceStateSupport {
|
||||
val lock = ReentrantLock()
|
||||
private var _active: Boolean = false
|
||||
override var active: Boolean
|
||||
get() = lock.withLock { _active }
|
||||
set(value) {
|
||||
lock.withLock {
|
||||
if (value != _active) {
|
||||
_active = value
|
||||
log.info("Status change to $value")
|
||||
_activeChange.onNext(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val _activeChange: BehaviorSubject<Boolean> = BehaviorSubject.create<Boolean>(false)
|
||||
private val _threadSafeObservable: Observable<Boolean> = _activeChange.serialize().distinctUntilChanged()
|
||||
override val activeChange: Observable<Boolean>
|
||||
get() = _threadSafeObservable
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple implementation of [ServiceStateSupport] where it only reports [active] true when a set of dependencies are all [active] true.
|
||||
*/
|
||||
class ServiceStateCombiner(val services: List<ServiceStateSupport>) : ServiceStateSupport {
|
||||
override val active: Boolean
|
||||
get() = services.all { it.active }
|
||||
|
||||
private val _activeChange = Observable.combineLatest(services.map { it.activeChange }, { x -> x.all { y -> y as Boolean } }).serialize().distinctUntilChanged()
|
||||
override val activeChange: Observable<Boolean>
|
||||
get() = _activeChange
|
||||
}
|
14
bridge/src/main/resources/bridgedefault.conf
Normal file
14
bridge/src/main/resources/bridgedefault.conf
Normal file
@ -0,0 +1,14 @@
|
||||
//
|
||||
// R3 Proprietary and Confidential
|
||||
//
|
||||
// Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
//
|
||||
// The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
//
|
||||
// Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
|
||||
keyStorePassword = "cordacadevpass"
|
||||
trustStorePassword = "trustpass"
|
||||
enableAMQPPacketTrace = false
|
||||
artemisReconnectionInterval = 5000
|
||||
politeShutdownPeriod = 1000
|
104
bridge/src/test/kotlin/net/corda/bridge/BridgeTestHelper.kt
Normal file
104
bridge/src/test/kotlin/net/corda/bridge/BridgeTestHelper.kt
Normal file
@ -0,0 +1,104 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.bridge
|
||||
|
||||
import net.corda.bridge.services.api.BridgeConfiguration
|
||||
import net.corda.core.crypto.Crypto.generateKeyPair
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.createDirectories
|
||||
import net.corda.core.internal.exists
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.node.NotaryInfo
|
||||
import net.corda.core.node.services.AttachmentId
|
||||
import net.corda.nodeapi.internal.*
|
||||
import net.corda.nodeapi.internal.config.SSLConfiguration
|
||||
import net.corda.nodeapi.internal.crypto.*
|
||||
import net.corda.nodeapi.internal.network.NetworkParametersCopier
|
||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||
import net.corda.testing.core.TestIdentity
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.Mockito.CALLS_REAL_METHODS
|
||||
import org.mockito.Mockito.withSettings
|
||||
import java.net.Socket
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.security.cert.X509Certificate
|
||||
import java.time.Instant
|
||||
|
||||
fun createNetworkParams(baseDirectory: Path) {
|
||||
val dummyNotaryParty = TestIdentity(DUMMY_NOTARY_NAME)
|
||||
val notaryInfo = NotaryInfo(dummyNotaryParty.party, false)
|
||||
val copier = NetworkParametersCopier(NetworkParameters(
|
||||
minimumPlatformVersion = 1,
|
||||
notaries = listOf(notaryInfo),
|
||||
modifiedTime = Instant.now(),
|
||||
maxMessageSize = 10485760,
|
||||
maxTransactionSize = 40000,
|
||||
epoch = 1,
|
||||
whitelistedContractImplementations = emptyMap<String, List<AttachmentId>>()
|
||||
), overwriteFile = true)
|
||||
copier.install(baseDirectory)
|
||||
}
|
||||
|
||||
|
||||
fun createAndLoadConfigFromResource(baseDirectory: Path, configResource: String): BridgeConfiguration {
|
||||
val workspaceFolder = baseDirectory.normalize().toAbsolutePath()
|
||||
val args = arrayOf("--base-directory", workspaceFolder.toString())
|
||||
val argsParser = ArgsParser()
|
||||
val cmdlineOptions = argsParser.parse(*args)
|
||||
val configFile = cmdlineOptions.configFile
|
||||
configFile.normalize().parent?.createDirectories()
|
||||
ConfigTest::class.java.getResourceAsStream(configResource).use {
|
||||
Files.copy(it, configFile)
|
||||
}
|
||||
val config = cmdlineOptions.loadConfig()
|
||||
return config
|
||||
}
|
||||
|
||||
fun SSLConfiguration.createBridgeKeyStores(legalName: CordaX500Name,
|
||||
rootCert: X509Certificate = DEV_ROOT_CA.certificate,
|
||||
intermediateCa: CertificateAndKeyPair = DEV_INTERMEDIATE_CA) {
|
||||
|
||||
certificatesDirectory.createDirectories()
|
||||
if (!trustStoreFile.exists()) {
|
||||
loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/${DEV_CA_TRUST_STORE_FILE}"), DEV_CA_TRUST_STORE_PASS).save(trustStoreFile, trustStorePassword)
|
||||
}
|
||||
|
||||
val (nodeCaCert, nodeCaKeyPair) = createDevNodeCa(intermediateCa, legalName)
|
||||
|
||||
val sslKeyStore = loadSslKeyStore(createNew = true)
|
||||
sslKeyStore.update {
|
||||
val tlsKeyPair = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val tlsCert = X509Utilities.createCertificate(CertificateType.TLS, nodeCaCert, nodeCaKeyPair, legalName.x500Principal, tlsKeyPair.public)
|
||||
setPrivateKey(
|
||||
X509Utilities.CORDA_CLIENT_TLS,
|
||||
tlsKeyPair.private,
|
||||
listOf(tlsCert, nodeCaCert, intermediateCa.certificate, rootCert))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun serverListening(host: String, port: Int): Boolean {
|
||||
var s: Socket? = null
|
||||
try {
|
||||
s = Socket(host, port)
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
return false
|
||||
} finally {
|
||||
try {
|
||||
s?.close()
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified T> createPartialMock() = Mockito.mock(T::class.java, withSettings().useConstructor().defaultAnswer(CALLS_REAL_METHODS))
|
107
bridge/src/test/kotlin/net/corda/bridge/ConfigTest.kt
Normal file
107
bridge/src/test/kotlin/net/corda/bridge/ConfigTest.kt
Normal file
@ -0,0 +1,107 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.bridge
|
||||
|
||||
import net.corda.bridge.services.api.BridgeMode
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.testing.core.SerializationEnvironmentRule
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import java.nio.file.Paths
|
||||
|
||||
class ConfigTest {
|
||||
@Rule
|
||||
@JvmField
|
||||
val tempFolder = TemporaryFolder()
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val serializationEnvironment = SerializationEnvironmentRule()
|
||||
|
||||
@Test
|
||||
fun `Load simple config`() {
|
||||
val configResource = "/net/corda/bridge/singleprocess/bridge.conf"
|
||||
val config = createAndLoadConfigFromResource(tempFolder.root.toPath(), configResource)
|
||||
assertEquals(BridgeMode.SenderReceiver, config.bridgeMode)
|
||||
assertEquals(NetworkHostAndPort("localhost", 11005), config.outboundConfig!!.artemisBrokerAddress)
|
||||
assertEquals(NetworkHostAndPort("0.0.0.0", 10005), config.inboundConfig!!.listeningAddress)
|
||||
assertNull(config.floatInnerConfig)
|
||||
assertNull(config.floatOuterConfig)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Load simple bridge config`() {
|
||||
val configResource = "/net/corda/bridge/withfloat/bridge/bridge.conf"
|
||||
val config = createAndLoadConfigFromResource(tempFolder.root.toPath(), configResource)
|
||||
assertEquals(BridgeMode.FloatInner, config.bridgeMode)
|
||||
assertEquals(NetworkHostAndPort("localhost", 11005), config.outboundConfig!!.artemisBrokerAddress)
|
||||
assertNull(config.inboundConfig)
|
||||
assertEquals(listOf(NetworkHostAndPort("localhost", 12005)), config.floatInnerConfig!!.floatAddresses)
|
||||
assertEquals(CordaX500Name.parse("O=Bank A, L=London, C=GB"), config.floatInnerConfig!!.expectedCertificateSubject)
|
||||
assertNull(config.floatOuterConfig)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Load simple float config`() {
|
||||
val configResource = "/net/corda/bridge/withfloat/float/bridge.conf"
|
||||
val config = createAndLoadConfigFromResource(tempFolder.root.toPath(), configResource)
|
||||
assertEquals(BridgeMode.FloatOuter, config.bridgeMode)
|
||||
assertNull(config.outboundConfig)
|
||||
assertEquals(NetworkHostAndPort("0.0.0.0", 10005), config.inboundConfig!!.listeningAddress)
|
||||
assertNull(config.floatInnerConfig)
|
||||
assertEquals(NetworkHostAndPort("localhost", 12005), config.floatOuterConfig!!.floatAddress)
|
||||
assertEquals(CordaX500Name.parse("O=Bank A, L=London, C=GB"), config.floatOuterConfig!!.expectedCertificateSubject)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Load overridden cert config`() {
|
||||
val configResource = "/net/corda/bridge/custombasecerts/bridge.conf"
|
||||
val config = createAndLoadConfigFromResource(tempFolder.root.toPath(), configResource)
|
||||
assertEquals(Paths.get("customcerts/mysslkeystore.jks"), config.sslKeystore)
|
||||
assertEquals(Paths.get("customcerts/mytruststore.jks"), config.trustStoreFile)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Load custom inner certificate config`() {
|
||||
val configResource = "/net/corda/bridge/separatedwithcustomcerts/bridge/bridge.conf"
|
||||
val config = createAndLoadConfigFromResource(tempFolder.root.toPath(), configResource)
|
||||
assertEquals(Paths.get("outboundcerts/outboundkeys.jks"), config.outboundConfig!!.customSSLConfiguration!!.sslKeystore)
|
||||
assertEquals(Paths.get("outboundcerts/outboundtrust.jks"), config.outboundConfig!!.customSSLConfiguration!!.trustStoreFile)
|
||||
assertEquals("outboundkeypassword", config.outboundConfig!!.customSSLConfiguration!!.keyStorePassword)
|
||||
assertEquals("outboundtrustpassword", config.outboundConfig!!.customSSLConfiguration!!.trustStorePassword)
|
||||
assertNull(config.inboundConfig)
|
||||
assertEquals(Paths.get("tunnelcerts/tunnelkeys.jks"), config.floatInnerConfig!!.customSSLConfiguration!!.sslKeystore)
|
||||
assertEquals(Paths.get("tunnelcerts/tunneltrust.jks"), config.floatInnerConfig!!.customSSLConfiguration!!.trustStoreFile)
|
||||
assertEquals("tunnelkeypassword", config.floatInnerConfig!!.customSSLConfiguration!!.keyStorePassword)
|
||||
assertEquals("tunneltrustpassword", config.floatInnerConfig!!.customSSLConfiguration!!.trustStorePassword)
|
||||
assertNull(config.floatOuterConfig)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Load custom outer certificate config`() {
|
||||
val configResource = "/net/corda/bridge/separatedwithcustomcerts/float/bridge.conf"
|
||||
val config = createAndLoadConfigFromResource(tempFolder.root.toPath(), configResource)
|
||||
assertEquals(Paths.get("inboundcerts/inboundkeys.jks"), config.inboundConfig!!.customSSLConfiguration!!.sslKeystore)
|
||||
assertEquals(Paths.get("inboundcerts/inboundtrust.jks"), config.inboundConfig!!.customSSLConfiguration!!.trustStoreFile)
|
||||
assertEquals("inboundkeypassword", config.inboundConfig!!.customSSLConfiguration!!.keyStorePassword)
|
||||
assertEquals("inboundtrustpassword", config.inboundConfig!!.customSSLConfiguration!!.trustStorePassword)
|
||||
assertNull(config.outboundConfig)
|
||||
assertEquals(Paths.get("tunnelcerts/tunnelkeys.jks"), config.floatOuterConfig!!.customSSLConfiguration!!.sslKeystore)
|
||||
assertEquals(Paths.get("tunnelcerts/tunneltrust.jks"), config.floatOuterConfig!!.customSSLConfiguration!!.trustStoreFile)
|
||||
assertEquals("tunnelkeypassword", config.floatOuterConfig!!.customSSLConfiguration!!.keyStorePassword)
|
||||
assertEquals("tunneltrustpassword", config.floatOuterConfig!!.customSSLConfiguration!!.trustStorePassword)
|
||||
assertNull(config.floatInnerConfig)
|
||||
}
|
||||
}
|
237
bridge/src/test/kotlin/net/corda/bridge/ServiceStateTest.kt
Normal file
237
bridge/src/test/kotlin/net/corda/bridge/ServiceStateTest.kt
Normal file
@ -0,0 +1,237 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.bridge
|
||||
|
||||
import net.corda.bridge.services.api.ServiceStateSupport
|
||||
import net.corda.bridge.services.util.ServiceStateCombiner
|
||||
import net.corda.bridge.services.util.ServiceStateHelper
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import rx.Observable
|
||||
|
||||
class ServiceStateTest {
|
||||
interface ServiceA : ServiceStateSupport {
|
||||
fun changeStatus(status: Boolean)
|
||||
}
|
||||
|
||||
|
||||
class ServiceAImpl(private val stateSupport: ServiceStateHelper = ServiceStateHelper(log)) : ServiceA, ServiceStateSupport by stateSupport {
|
||||
companion object {
|
||||
val log = contextLogger()
|
||||
}
|
||||
|
||||
override fun changeStatus(status: Boolean) {
|
||||
stateSupport.active = status
|
||||
}
|
||||
}
|
||||
|
||||
interface ServiceB : ServiceStateSupport {
|
||||
fun changeStatus(status: Boolean)
|
||||
}
|
||||
|
||||
|
||||
class ServiceBImpl(private val stateSupport: ServiceStateHelper = ServiceStateHelper(log)) : ServiceB, ServiceStateSupport by stateSupport {
|
||||
companion object {
|
||||
val log = contextLogger()
|
||||
}
|
||||
|
||||
override fun changeStatus(status: Boolean) {
|
||||
stateSupport.active = status
|
||||
}
|
||||
}
|
||||
|
||||
interface ServiceC : ServiceStateSupport {
|
||||
}
|
||||
|
||||
|
||||
class ServiceCImpl(servA: ServiceA, servB: ServiceB) : ServiceC {
|
||||
private val combiner = ServiceStateCombiner(listOf(servA, servB))
|
||||
|
||||
override val active: Boolean
|
||||
get() = combiner.active
|
||||
|
||||
override val activeChange: Observable<Boolean>
|
||||
get() = combiner.activeChange
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Test state helper`() {
|
||||
val servA = ServiceAImpl()
|
||||
var upA = 0
|
||||
var downA = 0
|
||||
val subsA = servA.activeChange.subscribe {
|
||||
if (it) ++upA else ++downA
|
||||
}
|
||||
assertEquals(0, upA)
|
||||
assertEquals(1, downA)
|
||||
|
||||
servA.changeStatus(true)
|
||||
assertEquals(true, servA.active)
|
||||
assertEquals(1, upA)
|
||||
assertEquals(1, downA)
|
||||
|
||||
servA.changeStatus(true)
|
||||
assertEquals(true, servA.active)
|
||||
assertEquals(1, upA)
|
||||
assertEquals(1, downA)
|
||||
|
||||
servA.changeStatus(false)
|
||||
assertEquals(false, servA.active)
|
||||
assertEquals(1, upA)
|
||||
assertEquals(2, downA)
|
||||
|
||||
servA.changeStatus(false)
|
||||
assertEquals(false, servA.active)
|
||||
assertEquals(1, upA)
|
||||
assertEquals(2, downA)
|
||||
|
||||
// Should stop alerting, but keep functioning after unsubscribe
|
||||
subsA.unsubscribe()
|
||||
|
||||
servA.changeStatus(true)
|
||||
assertEquals(true, servA.active)
|
||||
assertEquals(1, upA)
|
||||
assertEquals(2, downA)
|
||||
|
||||
servA.changeStatus(false)
|
||||
assertEquals(false, servA.active)
|
||||
assertEquals(1, upA)
|
||||
assertEquals(2, downA)
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Test basic domino behaviour of combiner`() {
|
||||
val servA = ServiceAImpl()
|
||||
val servB = ServiceBImpl()
|
||||
val servC = ServiceCImpl(servA, servB)
|
||||
var upA = 0
|
||||
var downA = 0
|
||||
var upB = 0
|
||||
var downB = 0
|
||||
var upC = 0
|
||||
var downC = 0
|
||||
val subsA = servA.activeChange.subscribe {
|
||||
if (it) ++upA else ++downA
|
||||
}
|
||||
val subsB = servB.activeChange.subscribe {
|
||||
if (it) ++upB else ++downB
|
||||
}
|
||||
val subsC = servC.activeChange.subscribe {
|
||||
if (it) ++upC else ++downC
|
||||
}
|
||||
// Get one automatic down event at subscribe
|
||||
assertEquals(false, servA.active)
|
||||
assertEquals(false, servB.active)
|
||||
assertEquals(false, servC.active)
|
||||
assertEquals(0, upA)
|
||||
assertEquals(1, downA)
|
||||
assertEquals(0, upB)
|
||||
assertEquals(1, downB)
|
||||
assertEquals(0, upC)
|
||||
assertEquals(1, downC)
|
||||
|
||||
// Rest of sequence should only signal on change and C should come up if A.active && B.active else it is false
|
||||
servA.changeStatus(true)
|
||||
assertEquals(true, servA.active)
|
||||
assertEquals(false, servB.active)
|
||||
assertEquals(false, servC.active)
|
||||
assertEquals(1, upA)
|
||||
assertEquals(1, downA)
|
||||
assertEquals(0, upB)
|
||||
assertEquals(1, downB)
|
||||
assertEquals(0, upC)
|
||||
assertEquals(1, downC)
|
||||
|
||||
servB.changeStatus(false)
|
||||
assertEquals(true, servA.active)
|
||||
assertEquals(false, servB.active)
|
||||
assertEquals(false, servC.active)
|
||||
assertEquals(1, upA)
|
||||
assertEquals(1, downA)
|
||||
assertEquals(0, upB)
|
||||
assertEquals(1, downB)
|
||||
assertEquals(0, upC)
|
||||
assertEquals(1, downC)
|
||||
|
||||
servB.changeStatus(true)
|
||||
assertEquals(true, servA.active)
|
||||
assertEquals(true, servB.active)
|
||||
assertEquals(true, servC.active)
|
||||
assertEquals(1, upA)
|
||||
assertEquals(1, downA)
|
||||
assertEquals(1, upB)
|
||||
assertEquals(1, downB)
|
||||
assertEquals(1, upC)
|
||||
assertEquals(1, downC)
|
||||
|
||||
servA.changeStatus(false)
|
||||
assertEquals(false, servA.active)
|
||||
assertEquals(true, servB.active)
|
||||
assertEquals(false, servC.active)
|
||||
assertEquals(1, upA)
|
||||
assertEquals(2, downA)
|
||||
assertEquals(1, upB)
|
||||
assertEquals(1, downB)
|
||||
assertEquals(1, upC)
|
||||
assertEquals(2, downC)
|
||||
|
||||
servB.changeStatus(false)
|
||||
assertEquals(false, servA.active)
|
||||
assertEquals(false, servB.active)
|
||||
assertEquals(false, servC.active)
|
||||
assertEquals(1, upA)
|
||||
assertEquals(2, downA)
|
||||
assertEquals(1, upB)
|
||||
assertEquals(2, downB)
|
||||
assertEquals(1, upC)
|
||||
assertEquals(2, downC)
|
||||
|
||||
servB.changeStatus(true)
|
||||
assertEquals(false, servA.active)
|
||||
assertEquals(true, servB.active)
|
||||
assertEquals(false, servC.active)
|
||||
assertEquals(1, upA)
|
||||
assertEquals(2, downA)
|
||||
assertEquals(2, upB)
|
||||
assertEquals(2, downB)
|
||||
assertEquals(1, upC)
|
||||
assertEquals(2, downC)
|
||||
|
||||
servA.changeStatus(true)
|
||||
assertEquals(true, servA.active)
|
||||
assertEquals(true, servB.active)
|
||||
assertEquals(true, servC.active)
|
||||
assertEquals(2, upA)
|
||||
assertEquals(2, downA)
|
||||
assertEquals(2, upB)
|
||||
assertEquals(2, downB)
|
||||
assertEquals(2, upC)
|
||||
assertEquals(2, downC)
|
||||
|
||||
subsC.unsubscribe()
|
||||
subsA.unsubscribe()
|
||||
subsB.unsubscribe()
|
||||
|
||||
servA.changeStatus(false)
|
||||
assertEquals(false, servA.active)
|
||||
assertEquals(true, servB.active)
|
||||
assertEquals(false, servC.active)
|
||||
assertEquals(2, upA)
|
||||
assertEquals(2, downA)
|
||||
assertEquals(2, upB)
|
||||
assertEquals(2, downB)
|
||||
assertEquals(2, upC)
|
||||
assertEquals(2, downC)
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,225 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.bridge.services
|
||||
|
||||
import com.nhaarman.mockito_kotlin.*
|
||||
import net.corda.bridge.createPartialMock
|
||||
import net.corda.bridge.services.api.BridgeArtemisConnectionService
|
||||
import net.corda.bridge.services.api.BridgeConfiguration
|
||||
import net.corda.bridge.services.api.BridgeSenderService
|
||||
import net.corda.bridge.services.filter.SimpleMessageFilterService
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingClient
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_PREFIX
|
||||
import net.corda.nodeapi.internal.protonwrapper.messages.ReceivedMessage
|
||||
import net.corda.testing.core.DUMMY_BANK_B_NAME
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import org.apache.activemq.artemis.api.core.SimpleString
|
||||
import org.apache.activemq.artemis.api.core.client.*
|
||||
import org.junit.Test
|
||||
import org.mockito.ArgumentMatchers
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class FilterServiceTest {
|
||||
private abstract class TestBridgeArtemisConnectionService : BridgeArtemisConnectionService, TestServiceBase()
|
||||
private abstract class TestBridgeSenderService : BridgeSenderService, TestServiceBase()
|
||||
|
||||
companion object {
|
||||
private val inboxTopic = "${P2P_PREFIX}test"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Basic function tests`() {
|
||||
val conf = rigorousMock<BridgeConfiguration>().also {
|
||||
doReturn(ArtemisMessagingComponent.Companion.P2PMessagingHeaders.whitelistedHeaders.toList()).whenever(it).whitelistedHeaders
|
||||
}
|
||||
val auditService = TestAuditService()
|
||||
val dummyMessage = rigorousMock<ClientMessage>().also {
|
||||
doReturn(it).whenever(it).putStringProperty(ArgumentMatchers.anyString(), ArgumentMatchers.anyString())
|
||||
doReturn(it).whenever(it).putStringProperty(ArgumentMatchers.any<SimpleString>(), ArgumentMatchers.any<SimpleString>())
|
||||
doReturn(it).whenever(it).writeBodyBufferBytes(any())
|
||||
}
|
||||
val dummyProducer = rigorousMock<ClientProducer>().also {
|
||||
doNothing().whenever(it).send(any(), eq(dummyMessage), any())
|
||||
doNothing().whenever(it).close()
|
||||
}
|
||||
val dummySession = rigorousMock<ClientSession>().also {
|
||||
doReturn(dummyMessage).whenever(it).createMessage(true)
|
||||
doReturn(dummyProducer).whenever(it).createProducer()
|
||||
doNothing().whenever(it).close()
|
||||
}
|
||||
val artemisStarted = ArtemisMessagingClient.Started(
|
||||
rigorousMock(),
|
||||
rigorousMock<ClientSessionFactory>().also {
|
||||
doReturn(dummySession).whenever(it).createSession(ArtemisMessagingComponent.NODE_USER, ArtemisMessagingComponent.NODE_USER, false, true, true, false, ActiveMQClient.DEFAULT_ACK_BATCH_SIZE)
|
||||
},
|
||||
rigorousMock(),
|
||||
rigorousMock()
|
||||
)
|
||||
val artemisService = createPartialMock<TestBridgeArtemisConnectionService>().also {
|
||||
doReturn(artemisStarted).whenever(it).started
|
||||
}
|
||||
val senderService = createPartialMock<TestBridgeSenderService>().also {
|
||||
doReturn(true).whenever(it).validateReceiveTopic(ArgumentMatchers.anyString(), any())
|
||||
}
|
||||
val filterService = SimpleMessageFilterService(conf, auditService, artemisService, senderService)
|
||||
val stateFollower = filterService.activeChange.toBlocking().iterator
|
||||
val auditFollower = auditService.onAuditEvent.toBlocking().iterator
|
||||
filterService.start()
|
||||
// Not ready so packet dropped
|
||||
val fakeMessage = rigorousMock<ReceivedMessage>().also {
|
||||
doNothing().whenever(it).complete(true) // ACK was called
|
||||
}
|
||||
filterService.sendMessageToLocalBroker(fakeMessage)
|
||||
assertEquals(TestAuditService.AuditEvent.PACKET_DROP, auditFollower.next()) // Dropped as not ready
|
||||
assertEquals(false, stateFollower.next())
|
||||
assertEquals(false, filterService.active)
|
||||
verify(dummyProducer, times(0)).send(ArgumentMatchers.any(), eq(dummyMessage), ArgumentMatchers.any()) // not sent
|
||||
auditService.start()
|
||||
assertEquals(false, filterService.active)
|
||||
artemisService.start()
|
||||
assertEquals(false, filterService.active)
|
||||
senderService.start()
|
||||
assertEquals(true, stateFollower.next())
|
||||
assertEquals(true, filterService.active)
|
||||
// ready so packet forwarded
|
||||
val goodMessage = rigorousMock<ReceivedMessage>().also {
|
||||
doNothing().whenever(it).complete(true) // ACK was called
|
||||
doReturn(DUMMY_BANK_B_NAME.toString()).whenever(it).sourceLegalName
|
||||
doReturn(inboxTopic).whenever(it).topic
|
||||
doReturn(ByteArray(1)).whenever(it).payload
|
||||
doReturn(emptyMap<Any?, Any?>()).whenever(it).applicationProperties
|
||||
}
|
||||
filterService.sendMessageToLocalBroker(goodMessage)
|
||||
assertEquals(TestAuditService.AuditEvent.PACKET_ACCEPT, auditFollower.next()) // Accepted the message
|
||||
verify(dummyProducer, times(1)).send(ArgumentMatchers.any(), eq(dummyMessage), ArgumentMatchers.any()) // message forwarded
|
||||
filterService.stop()
|
||||
assertEquals(false, stateFollower.next())
|
||||
assertEquals(false, filterService.active)
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun `Rejection tests`() {
|
||||
val conf = rigorousMock<BridgeConfiguration>().also {
|
||||
doReturn(ArtemisMessagingComponent.Companion.P2PMessagingHeaders.whitelistedHeaders.toList()).whenever(it).whitelistedHeaders
|
||||
}
|
||||
val auditService = TestAuditService()
|
||||
val dummyMessage = rigorousMock<ClientMessage>().also {
|
||||
doReturn(it).whenever(it).putStringProperty(ArgumentMatchers.anyString(), ArgumentMatchers.anyString())
|
||||
doReturn(it).whenever(it).putStringProperty(ArgumentMatchers.any<SimpleString>(), ArgumentMatchers.any<SimpleString>())
|
||||
doReturn(it).whenever(it).writeBodyBufferBytes(any())
|
||||
}
|
||||
val dummyProducer = rigorousMock<ClientProducer>().also {
|
||||
doNothing().whenever(it).send(any(), eq(dummyMessage), any())
|
||||
doNothing().whenever(it).close()
|
||||
}
|
||||
val dummySession = rigorousMock<ClientSession>().also {
|
||||
doReturn(dummyMessage).whenever(it).createMessage(true)
|
||||
doReturn(dummyProducer).whenever(it).createProducer()
|
||||
doNothing().whenever(it).close()
|
||||
}
|
||||
val artemisStarted = ArtemisMessagingClient.Started(
|
||||
rigorousMock(),
|
||||
rigorousMock<ClientSessionFactory>().also {
|
||||
doReturn(dummySession).whenever(it).createSession(ArtemisMessagingComponent.NODE_USER, ArtemisMessagingComponent.NODE_USER, false, true, true, false, ActiveMQClient.DEFAULT_ACK_BATCH_SIZE)
|
||||
},
|
||||
rigorousMock(),
|
||||
rigorousMock()
|
||||
)
|
||||
val artemisService = createPartialMock<TestBridgeArtemisConnectionService>().also {
|
||||
doReturn(artemisStarted).whenever(it).started
|
||||
}
|
||||
val senderService = createPartialMock<TestBridgeSenderService>().also {
|
||||
doAnswer {
|
||||
val topic = it.arguments[0] as String
|
||||
(topic == inboxTopic)
|
||||
}.whenever(it).validateReceiveTopic(ArgumentMatchers.anyString(), any())
|
||||
}
|
||||
val filterService = SimpleMessageFilterService(conf, auditService, artemisService, senderService)
|
||||
val auditFollower = auditService.onAuditEvent.toBlocking().iterator
|
||||
auditService.start()
|
||||
artemisService.start()
|
||||
senderService.start()
|
||||
filterService.start()
|
||||
assertEquals(true, filterService.active)
|
||||
|
||||
// empty legal name
|
||||
val badMessage1 = rigorousMock<ReceivedMessage>().also {
|
||||
doNothing().whenever(it).complete(true) // ACK was called
|
||||
doReturn("").whenever(it).sourceLegalName
|
||||
doReturn(inboxTopic).whenever(it).topic
|
||||
doReturn(ByteArray(1)).whenever(it).payload
|
||||
doReturn(emptyMap<Any?, Any?>()).whenever(it).applicationProperties
|
||||
}
|
||||
filterService.sendMessageToLocalBroker(badMessage1)
|
||||
assertEquals(TestAuditService.AuditEvent.PACKET_DROP, auditFollower.next())
|
||||
verify(dummyProducer, times(0)).send(ArgumentMatchers.any(), eq(dummyMessage), ArgumentMatchers.any()) // not sent
|
||||
// bad legal name
|
||||
val badMessage2 = rigorousMock<ReceivedMessage>().also {
|
||||
doNothing().whenever(it).complete(true) // ACK was called
|
||||
doReturn("CN=Test").whenever(it).sourceLegalName
|
||||
doReturn(inboxTopic).whenever(it).topic
|
||||
doReturn(ByteArray(1)).whenever(it).payload
|
||||
doReturn(emptyMap<Any?, Any?>()).whenever(it).applicationProperties
|
||||
}
|
||||
filterService.sendMessageToLocalBroker(badMessage2)
|
||||
assertEquals(TestAuditService.AuditEvent.PACKET_DROP, auditFollower.next())
|
||||
verify(dummyProducer, times(0)).send(ArgumentMatchers.any(), eq(dummyMessage), ArgumentMatchers.any()) // not sent
|
||||
// empty payload
|
||||
val badMessage3 = rigorousMock<ReceivedMessage>().also {
|
||||
doNothing().whenever(it).complete(true) // ACK was called
|
||||
doReturn(DUMMY_BANK_B_NAME.toString()).whenever(it).sourceLegalName
|
||||
doReturn(inboxTopic).whenever(it).topic
|
||||
doReturn(ByteArray(0)).whenever(it).payload
|
||||
doReturn(emptyMap<Any?, Any?>()).whenever(it).applicationProperties
|
||||
}
|
||||
filterService.sendMessageToLocalBroker(badMessage3)
|
||||
assertEquals(TestAuditService.AuditEvent.PACKET_DROP, auditFollower.next())
|
||||
verify(dummyProducer, times(0)).send(ArgumentMatchers.any(), eq(dummyMessage), ArgumentMatchers.any()) // not sent
|
||||
// bad topic
|
||||
val badMessage4 = rigorousMock<ReceivedMessage>().also {
|
||||
doNothing().whenever(it).complete(true) // ACK was called
|
||||
doReturn(DUMMY_BANK_B_NAME.toString()).whenever(it).sourceLegalName
|
||||
doReturn("bridge.control").whenever(it).topic
|
||||
doReturn(ByteArray(1)).whenever(it).payload
|
||||
doReturn(emptyMap<Any?, Any?>()).whenever(it).applicationProperties
|
||||
}
|
||||
filterService.sendMessageToLocalBroker(badMessage4)
|
||||
assertEquals(TestAuditService.AuditEvent.PACKET_DROP, auditFollower.next())
|
||||
verify(dummyProducer, times(0)).send(ArgumentMatchers.any(), eq(dummyMessage), ArgumentMatchers.any()) // not sent
|
||||
// Non-whitelist header header
|
||||
val badMessage5 = rigorousMock<ReceivedMessage>().also {
|
||||
doNothing().whenever(it).complete(true) // ACK was called
|
||||
doReturn(DUMMY_BANK_B_NAME.toString()).whenever(it).sourceLegalName
|
||||
doReturn(inboxTopic).whenever(it).topic
|
||||
doReturn(ByteArray(1)).whenever(it).payload
|
||||
doReturn(mapOf<Any?, Any?>("Suspicious" to "Header")).whenever(it).applicationProperties
|
||||
}
|
||||
filterService.sendMessageToLocalBroker(badMessage5)
|
||||
assertEquals(TestAuditService.AuditEvent.PACKET_DROP, auditFollower.next())
|
||||
verify(dummyProducer, times(0)).send(ArgumentMatchers.any(), eq(dummyMessage), ArgumentMatchers.any()) // not sent
|
||||
|
||||
// Valid message sent and completed
|
||||
val goodMessage = rigorousMock<ReceivedMessage>().also {
|
||||
doNothing().whenever(it).complete(true) // ACK was called
|
||||
doReturn(DUMMY_BANK_B_NAME.toString()).whenever(it).sourceLegalName
|
||||
doReturn(inboxTopic).whenever(it).topic
|
||||
doReturn(ByteArray(1)).whenever(it).payload
|
||||
doReturn(emptyMap<Any?, Any?>()).whenever(it).applicationProperties
|
||||
}
|
||||
filterService.sendMessageToLocalBroker(goodMessage)
|
||||
assertEquals(TestAuditService.AuditEvent.PACKET_ACCEPT, auditFollower.next()) // packet was accepted
|
||||
verify(dummyProducer, times(1)).send(ArgumentMatchers.any(), eq(dummyMessage), ArgumentMatchers.any()) // not sent
|
||||
filterService.stop()
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.bridge.services
|
||||
|
||||
import net.corda.bridge.services.api.BridgeAuditService
|
||||
import net.corda.nodeapi.internal.protonwrapper.messages.ReceivedMessage
|
||||
import rx.Observable
|
||||
import rx.subjects.PublishSubject
|
||||
import java.net.InetSocketAddress
|
||||
|
||||
class TestAuditService() : BridgeAuditService, TestServiceBase() {
|
||||
enum class AuditEvent {
|
||||
SUCCESSFUL_CONNECTION,
|
||||
FAILED_CONNECTION,
|
||||
PACKET_DROP,
|
||||
PACKET_ACCEPT,
|
||||
STATUS_CHANGE
|
||||
}
|
||||
|
||||
var eventCount: Int = 0
|
||||
private set
|
||||
|
||||
private val _onAuditEvent = PublishSubject.create<AuditEvent>().toSerialized()
|
||||
val onAuditEvent: Observable<AuditEvent>
|
||||
get() = _onAuditEvent
|
||||
|
||||
override fun successfulConnectionEvent(inbound: Boolean, sourceIP: InetSocketAddress, certificateSubject: String, msg: String) {
|
||||
++eventCount
|
||||
_onAuditEvent.onNext(AuditEvent.SUCCESSFUL_CONNECTION)
|
||||
}
|
||||
|
||||
override fun failedConnectionEvent(inbound: Boolean, sourceIP: InetSocketAddress?, certificateSubject: String?, msg: String) {
|
||||
++eventCount
|
||||
_onAuditEvent.onNext(AuditEvent.FAILED_CONNECTION)
|
||||
}
|
||||
|
||||
override fun packetDropEvent(packet: ReceivedMessage?, msg: String) {
|
||||
++eventCount
|
||||
_onAuditEvent.onNext(AuditEvent.PACKET_DROP)
|
||||
}
|
||||
|
||||
override fun packetAcceptedEvent(packet: ReceivedMessage) {
|
||||
++eventCount
|
||||
_onAuditEvent.onNext(AuditEvent.PACKET_ACCEPT)
|
||||
}
|
||||
|
||||
override fun statusChangeEvent(msg: String) {
|
||||
++eventCount
|
||||
_onAuditEvent.onNext(AuditEvent.STATUS_CHANGE)
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.bridge.services
|
||||
|
||||
import net.corda.bridge.services.api.ServiceLifecycleSupport
|
||||
import net.corda.bridge.services.util.ServiceStateHelper
|
||||
import org.slf4j.helpers.NOPLogger
|
||||
import rx.Observable
|
||||
|
||||
open class TestServiceBase() : ServiceLifecycleSupport {
|
||||
private val stateHelper: ServiceStateHelper = ServiceStateHelper(NOPLogger.NOP_LOGGER)
|
||||
|
||||
override val active: Boolean
|
||||
get() = stateHelper.active
|
||||
|
||||
override val activeChange: Observable<Boolean>
|
||||
get() = stateHelper.activeChange
|
||||
|
||||
override fun start() {
|
||||
stateHelper.active = true
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
stateHelper.active = false
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
//
|
||||
// R3 Proprietary and Confidential
|
||||
//
|
||||
// Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
//
|
||||
// The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
//
|
||||
// Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
|
||||
bridgeMode = SenderReceiver
|
||||
sslKeystore = "customcerts/mysslkeystore.jks"
|
||||
trustStoreFile = "customcerts/mytruststore.jks"
|
||||
outboundConfig : {
|
||||
artemisBrokerAddress = "localhost:11005"
|
||||
}
|
||||
inboundConfig : {
|
||||
listeningAddress = "0.0.0.0:10005"
|
||||
}
|
||||
networkParametersPath = network-parameters
|
@ -0,0 +1,20 @@
|
||||
//
|
||||
// R3 Proprietary and Confidential
|
||||
//
|
||||
// Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
//
|
||||
// The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
//
|
||||
// Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
|
||||
bridgeMode = SenderReceiver
|
||||
outboundConfig : {
|
||||
artemisBrokerAddress = "localhost:11005"
|
||||
}
|
||||
inboundConfig : {
|
||||
listeningAddress = "0.0.0.0:10005"
|
||||
}
|
||||
networkParametersPath = network-parameters
|
||||
haConfig : {
|
||||
haConnectionString = "zk//:localhost:11105"
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
//
|
||||
// R3 Proprietary and Confidential
|
||||
//
|
||||
// Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
//
|
||||
// The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
//
|
||||
// Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
|
||||
bridgeMode = FloatInner
|
||||
outboundConfig : {
|
||||
artemisBrokerAddress = "localhost:11005"
|
||||
}
|
||||
floatInnerConfig : {
|
||||
floatAddresses = [ "localhost:12005" ]
|
||||
expectedCertificateSubject = "O=Bank A, L=London, C=GB"
|
||||
}
|
||||
networkParametersPath = network-parameters
|
||||
haConfig : {
|
||||
haConnectionString = "zk//:localhost:11105"
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
//
|
||||
// R3 Proprietary and Confidential
|
||||
//
|
||||
// Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
//
|
||||
// The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
//
|
||||
// Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
|
||||
bridgeMode = FloatOuter
|
||||
inboundConfig : {
|
||||
listeningAddress = "0.0.0.0:10005"
|
||||
}
|
||||
floatOuterConfig : {
|
||||
floatAddress = "localhost:12005"
|
||||
expectedCertificateSubject = "O=Bank A, L=London, C=GB"
|
||||
}
|
||||
networkParametersPath = network-parameters
|
@ -0,0 +1,30 @@
|
||||
//
|
||||
// R3 Proprietary and Confidential
|
||||
//
|
||||
// Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
//
|
||||
// The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
//
|
||||
// Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
|
||||
bridgeMode = FloatInner
|
||||
outboundConfig : {
|
||||
artemisBrokerAddress = "localhost:11005"
|
||||
customSSLConfiguration : {
|
||||
keyStorePassword = "outboundkeypassword"
|
||||
trustStorePassword = "outboundtrustpassword"
|
||||
sslKeystore = "outboundcerts/outboundkeys.jks"
|
||||
trustStoreFile = "outboundcerts/outboundtrust.jks"
|
||||
}
|
||||
}
|
||||
floatInnerConfig : {
|
||||
floatAddresses = [ "localhost:12005" ]
|
||||
expectedCertificateSubject = "O=Bank A, L=London, C=GB"
|
||||
customSSLConfiguration : {
|
||||
keyStorePassword = "tunnelkeypassword"
|
||||
trustStorePassword = "tunneltrustpassword"
|
||||
sslKeystore = "tunnelcerts/tunnelkeys.jks"
|
||||
trustStoreFile = "tunnelcerts/tunneltrust.jks"
|
||||
}
|
||||
}
|
||||
networkParametersPath = network-parameters
|
@ -0,0 +1,30 @@
|
||||
//
|
||||
// R3 Proprietary and Confidential
|
||||
//
|
||||
// Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
//
|
||||
// The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
//
|
||||
// Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
|
||||
bridgeMode = FloatOuter
|
||||
inboundConfig : {
|
||||
listeningAddress = "0.0.0.0:10005"
|
||||
customSSLConfiguration : {
|
||||
keyStorePassword = "inboundkeypassword"
|
||||
trustStorePassword = "inboundtrustpassword"
|
||||
sslKeystore = "inboundcerts/inboundkeys.jks"
|
||||
trustStoreFile = "inboundcerts/inboundtrust.jks"
|
||||
}
|
||||
}
|
||||
floatOuterConfig : {
|
||||
floatAddress = "localhost:12005"
|
||||
expectedCertificateSubject = "O=Bank A, L=London, C=GB"
|
||||
customSSLConfiguration : {
|
||||
keyStorePassword = "tunnelkeypassword"
|
||||
trustStorePassword = "tunneltrustpassword"
|
||||
sslKeystore = "tunnelcerts/tunnelkeys.jks"
|
||||
trustStoreFile = "tunnelcerts/tunneltrust.jks"
|
||||
}
|
||||
}
|
||||
networkParametersPath = network-parameters
|
@ -0,0 +1,17 @@
|
||||
//
|
||||
// R3 Proprietary and Confidential
|
||||
//
|
||||
// Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
//
|
||||
// The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
//
|
||||
// Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
|
||||
bridgeMode = SenderReceiver
|
||||
outboundConfig : {
|
||||
artemisBrokerAddress = "localhost:11005"
|
||||
}
|
||||
inboundConfig : {
|
||||
listeningAddress = "0.0.0.0:10005"
|
||||
}
|
||||
networkParametersPath = network-parameters
|
@ -0,0 +1,18 @@
|
||||
//
|
||||
// R3 Proprietary and Confidential
|
||||
//
|
||||
// Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
//
|
||||
// The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
//
|
||||
// Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
|
||||
bridgeMode = FloatInner
|
||||
outboundConfig : {
|
||||
artemisBrokerAddress = "localhost:11005"
|
||||
}
|
||||
floatInnerConfig : {
|
||||
floatAddresses = [ "localhost:12005" ]
|
||||
expectedCertificateSubject = "O=Bank A, L=London, C=GB"
|
||||
}
|
||||
networkParametersPath = network-parameters
|
@ -0,0 +1,18 @@
|
||||
//
|
||||
// R3 Proprietary and Confidential
|
||||
//
|
||||
// Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
//
|
||||
// The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
//
|
||||
// Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
|
||||
bridgeMode = FloatOuter
|
||||
inboundConfig : {
|
||||
listeningAddress = "0.0.0.0:10005"
|
||||
}
|
||||
floatOuterConfig : {
|
||||
floatAddress = "localhost:12005"
|
||||
expectedCertificateSubject = "O=Bank A, L=London, C=GB"
|
||||
}
|
||||
networkParametersPath = network-parameters
|
71
build.gradle
71
build.gradle
@ -1,10 +1,22 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
buildscript {
|
||||
// For sharing constants between builds
|
||||
Properties constants = new Properties()
|
||||
file("$projectDir/constants.properties").withInputStream { constants.load(it) }
|
||||
|
||||
// Our version: bump this on release.
|
||||
ext.corda_release_version = "corda-4.0-snapshot"
|
||||
ext.corda_release_version = "R3.CORDA-3.0-SNAPSHOT" // "CORDA-3.0-SNAPSHOT" for Corda (Open Source)
|
||||
ext.corda_release_distribution = "com.r3.corda" // "net.corda" for Corda (Open Source)
|
||||
|
||||
// Increment this on any release that changes public APIs anywhere in the Corda platform
|
||||
ext.corda_platform_version = constants.getProperty("platformVersion")
|
||||
ext.gradle_plugins_version = constants.getProperty("gradlePluginsVersion")
|
||||
@ -47,6 +59,7 @@ buildscript {
|
||||
ext.bouncycastle_version = constants.getProperty("bouncycastleVersion")
|
||||
ext.guava_version = constants.getProperty("guavaVersion")
|
||||
ext.caffeine_version = constants.getProperty("caffeineVersion")
|
||||
ext.metrics_version = constants.getProperty("metricsVersion")
|
||||
ext.okhttp_version = '3.5.0'
|
||||
ext.netty_version = '4.1.9.Final'
|
||||
ext.typesafe_config_version = constants.getProperty("typesafeConfigVersion")
|
||||
@ -66,14 +79,19 @@ buildscript {
|
||||
ext.beanutils_version = '1.9.3'
|
||||
ext.crash_version = 'cadb53544fbb3c0fb901445da614998a6a419488'
|
||||
ext.jsr305_version = constants.getProperty("jsr305Version")
|
||||
ext.spring_jdbc_version ='5.0.0.RELEASE'
|
||||
ext.shiro_version = '1.4.0'
|
||||
ext.artifactory_plugin_version = constants.getProperty('artifactoryPluginVersion')
|
||||
ext.liquibase_version = '3.5.3'
|
||||
ext.shadow_version = '2.0.2'
|
||||
ext.hikari_version = '2.5.1'
|
||||
ext.artifactory_contextUrl = 'https://ci-artifactory.corda.r3cev.com/artifactory'
|
||||
ext.snake_yaml_version = constants.getProperty('snakeYamlVersion')
|
||||
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'
|
||||
ext.curator_version = '4.0.0'
|
||||
ext.jsch_version = '0.1.54'
|
||||
|
||||
// Update 121 is required for ObjectInputFilter and at time of writing 131 was latest:
|
||||
@ -178,7 +196,7 @@ allprojects {
|
||||
attributes('Corda-Release-Version': corda_release_version)
|
||||
attributes('Corda-Platform-Version': corda_platform_version)
|
||||
attributes('Corda-Revision': corda_revision)
|
||||
attributes('Corda-Vendor': 'Corda Open Source')
|
||||
attributes('Corda-Vendor': 'R3 Corda Edition')
|
||||
attributes('Automatic-Module-Name': "net.corda.${task.project.name.replaceAll('-', '.')}")
|
||||
}
|
||||
}
|
||||
@ -187,6 +205,33 @@ allprojects {
|
||||
// Prevent the project from creating temporary files outside of the build directory.
|
||||
systemProperties['java.io.tmpdir'] = buildDir
|
||||
|
||||
// Ensures that "net.corda.testing.amqp.enable" is passed correctly from Gradle command line
|
||||
// down to JVM executing unit test. It looks like we are running unit tests in the forked mode
|
||||
// and all the "-D" parameters passed to Gradle not making it to unit test level
|
||||
// TODO: Remove once we fully switched to AMQP
|
||||
final AMQP_ENABLE_PROP_NAME = "net.corda.testing.amqp.enable"
|
||||
systemProperty(AMQP_ENABLE_PROP_NAME, System.getProperty(AMQP_ENABLE_PROP_NAME))
|
||||
|
||||
// relational database provider to be used by node
|
||||
final DATABASE_PROVIDER = "custom.databaseProvider"
|
||||
final DATASOURCE_URL = "corda.dataSourceProperties.dataSource.url"
|
||||
final DATASOURCE_CLASSNAME = "corda.dataSourceProperties.dataSourceClassName"
|
||||
final DATASOURCE_USER = "corda.dataSourceProperties.dataSource.user"
|
||||
final DATASOURCE_PASSWORD = "corda.dataSourceProperties.dataSource.password"
|
||||
|
||||
// integration testing database configuration (to be used in conjunction with a DATABASE_PROVIDER)
|
||||
final TEST_DB_ADMIN_USER = "test.db.admin.user"
|
||||
final TEST_DB_ADMIN_PASSWORD = "test.db.admin.password"
|
||||
final TEST_DB_SCRIPT_DIR = "test.db.script.dir"
|
||||
|
||||
[DATABASE_PROVIDER,DATASOURCE_URL, DATASOURCE_CLASSNAME, DATASOURCE_USER, DATASOURCE_PASSWORD,
|
||||
TEST_DB_ADMIN_USER, TEST_DB_ADMIN_PASSWORD, TEST_DB_SCRIPT_DIR].forEach {
|
||||
def property = System.getProperty(it)
|
||||
if (property != null) {
|
||||
systemProperty(it, property)
|
||||
}
|
||||
}
|
||||
|
||||
if (System.getProperty("test.maxParallelForks") != null) {
|
||||
maxParallelForks = Integer.valueOf(System.getProperty("test.maxParallelForks"))
|
||||
}
|
||||
@ -196,7 +241,7 @@ allprojects {
|
||||
}
|
||||
}
|
||||
|
||||
group 'net.corda'
|
||||
group 'com.r3.corda'
|
||||
version "$corda_release_version"
|
||||
|
||||
repositories {
|
||||
@ -205,6 +250,18 @@ allprojects {
|
||||
jcenter()
|
||||
maven { url 'https://jitpack.io' }
|
||||
maven { url "$artifactory_contextUrl/corda-releases" } // cordform-common
|
||||
if (project.hasProperty("mavenOracleUsername") && project.hasProperty("mavenOraclePassword")) {
|
||||
maven {
|
||||
// For integrationTest task when running against Oracle database the JDBC driver is in Oracle Maven repository with login access only,
|
||||
// setup an account on https://login.oracle.com/oaam_server/login.do
|
||||
// provide credentials to Gradle task by -PmavenOracleUsername=... -PmavenOraclePassword=...
|
||||
url "https://www.oracle.com/content/secure/maven/content"
|
||||
credentials {
|
||||
username = project.property("mavenOracleUsername")
|
||||
password = project.property("mavenOraclePassword")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configurations {
|
||||
@ -284,8 +341,8 @@ tasks.withType(Test) {
|
||||
}
|
||||
|
||||
bintrayConfig {
|
||||
user = System.getenv('CORDA_BINTRAY_USER')
|
||||
key = System.getenv('CORDA_BINTRAY_KEY')
|
||||
// user = System.getenv('CORDA_BINTRAY_USER')
|
||||
// key = System.getenv('CORDA_BINTRAY_KEY')
|
||||
repo = 'corda'
|
||||
org = 'r3'
|
||||
licenses = ['Apache-2.0']
|
||||
@ -293,7 +350,7 @@ bintrayConfig {
|
||||
projectUrl = 'https://github.com/corda/corda'
|
||||
gpgSign = true
|
||||
gpgPassphrase = System.getenv('CORDA_BINTRAY_GPG_PASSPHRASE')
|
||||
publications = ['corda-jfx', 'corda-mock', 'corda-rpc', 'corda-core', 'corda', 'corda-finance', 'corda-node', 'corda-node-api', 'corda-test-common', 'corda-test-utils', 'corda-jackson', 'corda-verifier', 'corda-webserver-impl', 'corda-webserver', 'corda-node-driver', 'corda-confidential-identities', 'corda-shell']
|
||||
publications = ['corda-jfx', 'corda-mock', 'corda-rpc', 'corda-core', 'corda', 'corda-finance', 'corda-node', 'corda-node-api', 'corda-test-common', 'corda-test-utils', 'corda-jackson', 'corda-verifier', 'corda-webserver-impl', 'corda-webserver', 'corda-node-driver', 'corda-confidential-identities', 'doorman', 'doorman-hsm', 'corda-shell', 'corda-bridgeserver']
|
||||
license {
|
||||
name = 'Apache-2.0'
|
||||
url = 'https://www.apache.org/licenses/LICENSE-2.0'
|
||||
@ -323,7 +380,7 @@ artifactory {
|
||||
publish {
|
||||
contextUrl = artifactory_contextUrl
|
||||
repository {
|
||||
repoKey = 'corda-dev'
|
||||
repoKey = 'enterprise-dev'
|
||||
username = 'teamcity'
|
||||
password = System.getenv('CORDA_ARTIFACTORY_PASSWORD')
|
||||
}
|
||||
|
@ -1,3 +1,13 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
buildscript {
|
||||
Properties constants = new Properties()
|
||||
file("../constants.properties").withInputStream { constants.load(it) }
|
||||
|
@ -1,3 +1,13 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id 'groovy'
|
||||
id 'java-gradle-plugin'
|
||||
|
@ -1,3 +1,13 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
import com.google.common.io.ByteStreams
|
||||
import org.gradle.api.*
|
||||
import java.util.zip.ZipEntry
|
||||
|
@ -1,2 +1,12 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
rootProject.name = 'buildSrc'
|
||||
include 'canonicalizer'
|
||||
|
@ -1,3 +1,13 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'net.corda.plugins.publish-utils'
|
||||
|
@ -1,3 +1,13 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.client.jackson
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||
|
@ -1,3 +1,13 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.client.jackson
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode
|
||||
|
@ -1,3 +1,13 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.client.jackson
|
||||
|
||||
import com.fasterxml.jackson.databind.SerializationFeature
|
||||
|
@ -1,3 +1,13 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.client.jackson
|
||||
|
||||
import net.corda.core.crypto.SecureHash
|
||||
|
@ -1,3 +1,13 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'net.corda.plugins.quasar-utils'
|
||||
apply plugin: 'net.corda.plugins.publish-utils'
|
||||
@ -18,6 +28,9 @@ sourceSets {
|
||||
runtimeClasspath += main.output + test.output
|
||||
srcDir file('src/integration-test/kotlin')
|
||||
}
|
||||
resources {
|
||||
srcDir file('../../testing/test-utils/src/main/resources')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,13 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.client.jfx
|
||||
|
||||
import net.corda.client.jfx.model.NodeMonitorModel
|
||||
@ -31,12 +41,16 @@ 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.internal.IntegrationTest
|
||||
import net.corda.testing.internal.IntegrationTestSchemas
|
||||
import net.corda.testing.internal.toDatabaseSchemaName
|
||||
import net.corda.testing.internal.chooseIdentity
|
||||
import net.corda.testing.node.User
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Test
|
||||
import rx.Observable
|
||||
|
||||
class NodeMonitorModelTest {
|
||||
class NodeMonitorModelTest : IntegrationTest() {
|
||||
private lateinit var aliceNode: NodeInfo
|
||||
private lateinit var bobNode: NodeInfo
|
||||
private lateinit var notaryParty: Party
|
||||
@ -52,6 +66,13 @@ class NodeMonitorModelTest {
|
||||
private lateinit var networkMapUpdates: Observable<NetworkMapCache.MapChange>
|
||||
private lateinit var newNode: (CordaX500Name) -> NodeInfo
|
||||
|
||||
companion object {
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val databaseSchemas = IntegrationTestSchemas(*listOf(ALICE_NAME, BOB_NAME, CHARLIE_NAME, DUMMY_NOTARY_NAME)
|
||||
.map { it.toDatabaseSchemaName() }.toTypedArray())
|
||||
}
|
||||
|
||||
private fun setup(runTest: () -> Unit) {
|
||||
driver(DriverParameters(extraCordappPackagesToScan = listOf("net.corda.finance"))) {
|
||||
val cashUser = User("user1", "test", permissions = setOf(
|
||||
|
@ -1,3 +1,13 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.client.jfx.model
|
||||
|
||||
import javafx.collections.FXCollections
|
||||
|
@ -1,3 +1,13 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.client.jfx.model
|
||||
|
||||
import javafx.beans.property.SimpleObjectProperty
|
||||
|
@ -1,3 +1,13 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.client.jfx.model
|
||||
|
||||
import javafx.beans.property.ObjectProperty
|
||||
|
@ -1,3 +1,13 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
@file:JvmName("ModelsUtils")
|
||||
|
||||
package net.corda.client.jfx.model
|
||||
|
@ -1,3 +1,13 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.client.jfx.model
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Caffeine
|
||||
|
@ -1,3 +1,13 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.client.jfx.model
|
||||
|
||||
import javafx.beans.property.SimpleObjectProperty
|
||||
|
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.client.jfx.model
|
||||
|
||||
import javafx.beans.property.SimpleIntegerProperty
|
||||
import javafx.beans.property.SimpleObjectProperty
|
||||
import javafx.beans.value.ObservableValue
|
||||
import javafx.collections.FXCollections
|
||||
import net.corda.client.jfx.utils.fold
|
||||
import net.corda.client.jfx.utils.map
|
||||
import net.corda.client.jfx.utils.recordAsAssociation
|
||||
import net.corda.core.flows.FlowInitiator
|
||||
import net.corda.core.flows.StateMachineRunId
|
||||
import net.corda.core.messaging.StateMachineUpdate
|
||||
import net.corda.core.utilities.Try
|
||||
import org.fxmisc.easybind.EasyBind
|
||||
|
||||
data class ProgressStatus(val status: String?)
|
||||
|
||||
sealed class StateMachineStatus {
|
||||
data class Added(val id: StateMachineRunId, val stateMachineName: String, val flowInitiator: FlowInitiator) : StateMachineStatus()
|
||||
data class Removed(val id: StateMachineRunId, val result: Try<*>) : StateMachineStatus()
|
||||
}
|
||||
|
||||
data class StateMachineData(
|
||||
val id: StateMachineRunId,
|
||||
val stateMachineName: String,
|
||||
val flowInitiator: FlowInitiator,
|
||||
val smmStatus: Pair<ObservableValue<StateMachineStatus>, ObservableValue<ProgressStatus>>
|
||||
)
|
||||
|
||||
data class Counter(
|
||||
var errored: SimpleIntegerProperty = SimpleIntegerProperty(0),
|
||||
var success: SimpleIntegerProperty = SimpleIntegerProperty(0),
|
||||
var progress: SimpleIntegerProperty = SimpleIntegerProperty(0)
|
||||
) {
|
||||
fun addSmm() { progress.value += 1 }
|
||||
fun removeSmm(result: Try<*>) {
|
||||
progress.value -= 1
|
||||
when (result) {
|
||||
is Try.Success -> success.value += 1
|
||||
is Try.Failure -> errored.value += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class StateMachineDataModel {
|
||||
private val stateMachineUpdates by observable(NodeMonitorModel::stateMachineUpdates)
|
||||
private val progressTracking by observable(NodeMonitorModel::progressTracking)
|
||||
private val progressEvents = progressTracking.recordAsAssociation(ProgressTrackingEvent::stateMachineId)
|
||||
|
||||
val counter = Counter()
|
||||
|
||||
private val stateMachineIndexMap = HashMap<StateMachineRunId, Int>()
|
||||
private val stateMachineStatus = stateMachineUpdates.fold(FXCollections.observableArrayList<SimpleObjectProperty<StateMachineStatus>>()) { list, update ->
|
||||
when (update) {
|
||||
is StateMachineUpdate.Added -> {
|
||||
counter.addSmm()
|
||||
val flowInitiator= update.stateMachineInfo.initiator
|
||||
val added: SimpleObjectProperty<StateMachineStatus> =
|
||||
SimpleObjectProperty(StateMachineStatus.Added(update.id, update.stateMachineInfo.flowLogicClassName, flowInitiator))
|
||||
list.add(added)
|
||||
stateMachineIndexMap[update.id] = list.size - 1
|
||||
}
|
||||
is StateMachineUpdate.Removed -> {
|
||||
val addedIdx = stateMachineIndexMap[update.id]
|
||||
val added = addedIdx?.let { list.getOrNull(addedIdx) }
|
||||
added ?: throw Exception("State machine removed with unknown id ${update.id}")
|
||||
counter.removeSmm(update.result)
|
||||
list[addedIdx].set(StateMachineStatus.Removed(update.id, update.result))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val stateMachineDataList = stateMachineStatus.map {
|
||||
val smStatus = it.value as StateMachineStatus.Added
|
||||
val id = smStatus.id
|
||||
val progress = SimpleObjectProperty(progressEvents.get(id))
|
||||
StateMachineData(id, smStatus.stateMachineName, smStatus.flowInitiator,
|
||||
Pair(it, EasyBind.map(progress) { ProgressStatus(it?.message) }))
|
||||
}
|
||||
|
||||
val stateMachinesAll = stateMachineDataList
|
||||
val error = counter.errored
|
||||
val success = counter.success
|
||||
val progress = counter.progress
|
||||
}
|
@ -1,3 +1,13 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.client.jfx.model
|
||||
|
||||
import javafx.beans.property.ObjectProperty
|
||||
|
@ -1,3 +1,13 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.client.jfx.model
|
||||
|
||||
import javafx.beans.value.ObservableValue
|
||||
|
@ -1,3 +1,13 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.client.jfx.utils
|
||||
|
||||
import javafx.collections.FXCollections
|
||||
|
@ -1,3 +1,13 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.client.jfx.utils
|
||||
|
||||
import javafx.beans.binding.Bindings
|
||||
|
@ -1,3 +1,13 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.client.jfx.utils
|
||||
|
||||
import javafx.collections.ListChangeListener
|
||||
|
@ -1,3 +1,13 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.client.jfx.utils
|
||||
|
||||
import javafx.beans.Observable
|
||||
|
@ -1,3 +1,13 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.client.jfx.utils
|
||||
|
||||
import co.paralleluniverse.common.util.VisibleForTesting
|
||||
|
@ -1,3 +1,13 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.client.jfx.utils
|
||||
|
||||
import javafx.beans.value.ChangeListener
|
||||
|
@ -1,3 +1,13 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.client.jfx.utils
|
||||
|
||||
import javafx.beans.property.SimpleObjectProperty
|
||||
|
@ -1,3 +1,13 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.client.jfx.utils
|
||||
|
||||
import javafx.collections.FXCollections
|
||||
|
@ -1,3 +1,13 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.client.jfx.utils
|
||||
|
||||
import javafx.collections.ListChangeListener
|
||||
|
@ -1,3 +1,13 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
@file:JvmName("ObservableFold")
|
||||
|
||||
package net.corda.client.jfx.utils
|
||||
|
@ -1,3 +1,13 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
@file:JvmName("ObservableUtilities")
|
||||
|
||||
package net.corda.client.jfx.utils
|
||||
|
@ -1,3 +1,13 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.client.jfx.utils
|
||||
|
||||
import com.sun.javafx.collections.MapListenerHelper
|
||||
|
@ -1,3 +1,13 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.client.jfx.utils
|
||||
|
||||
import javafx.collections.ListChangeListener
|
||||
|
@ -1,3 +1,13 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.client.jfx.model
|
||||
|
||||
import net.corda.core.contracts.Amount
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user