diff --git a/.ci/api-current.txt b/.ci/api-current.txt index 5fcde86cee..03b214c40d 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -2344,66 +2344,6 @@ public class net.corda.core.schemas.MappedSchema extends java.lang.Object public final int getVersion() @org.jetbrains.annotations.NotNull public String toString() ## -public final class net.corda.core.schemas.NodeInfoSchema extends java.lang.Object - public static final net.corda.core.schemas.NodeInfoSchema INSTANCE -## -public final class net.corda.core.schemas.NodeInfoSchemaV1 extends net.corda.core.schemas.MappedSchema - public static final net.corda.core.schemas.NodeInfoSchemaV1 INSTANCE -## -@javax.persistence.Entity public static final class net.corda.core.schemas.NodeInfoSchemaV1$DBHostAndPort extends java.lang.Object - public () - public (net.corda.core.schemas.NodeInfoSchemaV1$PKHostAndPort) - @org.jetbrains.annotations.NotNull public final net.corda.core.schemas.NodeInfoSchemaV1$DBHostAndPort copy(net.corda.core.schemas.NodeInfoSchemaV1$PKHostAndPort) - public boolean equals(Object) - public int hashCode() - @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.NetworkHostAndPort toHostAndPort() - public String toString() - public static final net.corda.core.schemas.NodeInfoSchemaV1$DBHostAndPort$Companion Companion -## -public static final class net.corda.core.schemas.NodeInfoSchemaV1$DBHostAndPort$Companion extends java.lang.Object - @org.jetbrains.annotations.NotNull public final net.corda.core.schemas.NodeInfoSchemaV1$DBHostAndPort fromHostAndPort(net.corda.core.utilities.NetworkHostAndPort) -## -@javax.persistence.Entity @javax.persistence.Table public static final class net.corda.core.schemas.NodeInfoSchemaV1$DBPartyAndCertificate extends java.lang.Object - public () - public (String, String, byte[], boolean, Set) - public (net.corda.core.identity.PartyAndCertificate, boolean) - @org.jetbrains.annotations.NotNull public final String component1() - @org.jetbrains.annotations.NotNull public final String component2() - @org.jetbrains.annotations.NotNull public final byte[] component3() - public final boolean component4() - @org.jetbrains.annotations.NotNull public final net.corda.core.schemas.NodeInfoSchemaV1$DBPartyAndCertificate copy(String, String, byte[], boolean, Set) - public boolean equals(Object) - @org.jetbrains.annotations.NotNull public final String getName() - @org.jetbrains.annotations.NotNull public final String getOwningKeyHash() - @org.jetbrains.annotations.NotNull public final byte[] getPartyCertBinary() - public int hashCode() - public final boolean isMain() - @org.jetbrains.annotations.NotNull public final net.corda.core.identity.PartyAndCertificate toLegalIdentityAndCert() - public String toString() -## -@javax.persistence.Embeddable public static final class net.corda.core.schemas.NodeInfoSchemaV1$PKHostAndPort extends java.lang.Object implements java.io.Serializable - public () - public (String, Integer) - @org.jetbrains.annotations.Nullable public final String component1() - @org.jetbrains.annotations.Nullable public final Integer component2() - @org.jetbrains.annotations.NotNull public final net.corda.core.schemas.NodeInfoSchemaV1$PKHostAndPort copy(String, Integer) - public boolean equals(Object) - @org.jetbrains.annotations.Nullable public final String getHost() - @org.jetbrains.annotations.Nullable public final Integer getPort() - public int hashCode() - public String toString() -## -@javax.persistence.Entity @javax.persistence.Table public static final class net.corda.core.schemas.NodeInfoSchemaV1$PersistentNodeInfo extends java.lang.Object - public () - public (int, List, List, int, long) - @org.jetbrains.annotations.NotNull public final List getAddresses() - public final int getId() - @org.jetbrains.annotations.NotNull public final List getLegalIdentitiesAndCerts() - public final int getPlatformVersion() - public final long getSerial() - public final void setId(int) - @org.jetbrains.annotations.NotNull public final net.corda.core.node.NodeInfo toNodeInfo() -## @javax.persistence.MappedSuperclass @net.corda.core.serialization.CordaSerializable public class net.corda.core.schemas.PersistentState extends java.lang.Object implements net.corda.core.schemas.StatePersistable public () public (net.corda.core.schemas.PersistentStateRef) diff --git a/.ci/check-api-changes.sh b/.ci/check-api-changes.sh index 72ae5f9d80..987d0ced3c 100755 --- a/.ci/check-api-changes.sh +++ b/.ci/check-api-changes.sh @@ -52,6 +52,10 @@ if [ $abstractCount -gt 0 ]; then fi badChanges=$(($removalCount + $abstractCount)) +if [ $badChanges -gt 255 ]; then + echo "OVERFLOW! Number of bad API changes: $badChanges" + badChanges=255 +fi echo "Exiting with exit code" $badChanges exit $badChanges diff --git a/.idea/compiler.xml b/.idea/compiler.xml index e437f00cb7..0e0d668d6d 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -1,7 +1,9 @@ - + + + @@ -21,7 +23,12 @@ + + + + + diff --git a/build.gradle b/build.gradle index 2edbbb0cd9..3bc11e414c 100644 --- a/build.gradle +++ b/build.gradle @@ -267,9 +267,9 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { } node { name "O=Bank B,OU=corda,L=London,C=GB" - p2pPort 10007 - rpcPort 10008 - webPort 10009 + p2pAddress "localhost:10007" + rpcAddress "localhost:10008" + webAddress "localhost:10009" cordapps = [] } } diff --git a/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt b/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt index e0131e6e99..238d6fd05a 100644 --- a/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt +++ b/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt @@ -26,64 +26,71 @@ import net.corda.finance.USD import net.corda.finance.flows.CashExitFlow import net.corda.finance.flows.CashIssueFlow import net.corda.finance.flows.CashPaymentFlow -import net.corda.node.services.FlowPermissions.Companion.startFlowPermission +import net.corda.node.services.Permissions.Companion.invokeRpc +import net.corda.node.services.Permissions.Companion.startFlow import net.corda.nodeapi.User import net.corda.testing.* import net.corda.testing.driver.driver -import net.corda.testing.node.DriverBasedTest import org.junit.Test import rx.Observable -class NodeMonitorModelTest : DriverBasedTest() { - lateinit var aliceNode: NodeInfo - lateinit var bobNode: NodeInfo - lateinit var notaryParty: Party +class NodeMonitorModelTest { + private lateinit var aliceNode: NodeInfo + private lateinit var bobNode: NodeInfo + private lateinit var notaryParty: Party - lateinit var rpc: CordaRPCOps - lateinit var rpcBob: CordaRPCOps - lateinit var stateMachineTransactionMapping: Observable - lateinit var stateMachineUpdates: Observable - lateinit var stateMachineUpdatesBob: Observable - lateinit var progressTracking: Observable - lateinit var transactions: Observable - lateinit var vaultUpdates: Observable> - lateinit var networkMapUpdates: Observable - lateinit var newNode: (CordaX500Name) -> NodeInfo + private lateinit var rpc: CordaRPCOps + private lateinit var rpcBob: CordaRPCOps + private lateinit var stateMachineTransactionMapping: Observable + private lateinit var stateMachineUpdates: Observable + private lateinit var stateMachineUpdatesBob: Observable + private lateinit var progressTracking: Observable + private lateinit var transactions: Observable + private lateinit var vaultUpdates: Observable> + private lateinit var networkMapUpdates: Observable + private lateinit var newNode: (CordaX500Name) -> NodeInfo - override fun setup() = driver(extraCordappPackagesToScan = listOf("net.corda.finance")) { - val cashUser = User("user1", "test", permissions = setOf( - startFlowPermission(), - startFlowPermission(), - startFlowPermission()) - ) - val aliceNodeFuture = startNode(providedName = ALICE.name, rpcUsers = listOf(cashUser)) - val notaryHandle = startNotaryNode(DUMMY_NOTARY.name, validating = false).getOrThrow() - val aliceNodeHandle = aliceNodeFuture.getOrThrow() - aliceNode = aliceNodeHandle.nodeInfo - newNode = { nodeName -> startNode(providedName = nodeName).getOrThrow().nodeInfo } - val monitor = NodeMonitorModel() - stateMachineTransactionMapping = monitor.stateMachineTransactionMapping.bufferUntilSubscribed() - stateMachineUpdates = monitor.stateMachineUpdates.bufferUntilSubscribed() - progressTracking = monitor.progressTracking.bufferUntilSubscribed() - transactions = monitor.transactions.bufferUntilSubscribed() - vaultUpdates = monitor.vaultUpdates.bufferUntilSubscribed() - networkMapUpdates = monitor.networkMap.bufferUntilSubscribed() + private fun setup(runTest: () -> Unit) { + driver(extraCordappPackagesToScan = listOf("net.corda.finance")) { + val cashUser = User("user1", "test", permissions = setOf( + startFlow(), + startFlow(), + startFlow(), + invokeRpc(CordaRPCOps::notaryIdentities), + invokeRpc("vaultTrackBy"), + invokeRpc("vaultQueryBy"), + invokeRpc(CordaRPCOps::internalVerifiedTransactionsFeed), + invokeRpc(CordaRPCOps::stateMachineRecordedTransactionMappingFeed), + invokeRpc(CordaRPCOps::stateMachinesFeed), + invokeRpc(CordaRPCOps::networkMapFeed)) + ) + val aliceNodeHandle = startNode(providedName = ALICE.name, rpcUsers = listOf(cashUser)).getOrThrow() + aliceNode = aliceNodeHandle.nodeInfo + newNode = { nodeName -> startNode(providedName = nodeName).getOrThrow().nodeInfo } + val monitor = NodeMonitorModel() + stateMachineTransactionMapping = monitor.stateMachineTransactionMapping.bufferUntilSubscribed() + stateMachineUpdates = monitor.stateMachineUpdates.bufferUntilSubscribed() + progressTracking = monitor.progressTracking.bufferUntilSubscribed() + transactions = monitor.transactions.bufferUntilSubscribed() + vaultUpdates = monitor.vaultUpdates.bufferUntilSubscribed() + networkMapUpdates = monitor.networkMap.bufferUntilSubscribed() - monitor.register(aliceNodeHandle.configuration.rpcAddress!!, cashUser.username, cashUser.password) - rpc = monitor.proxyObservable.value!! - notaryParty = notaryHandle.nodeInfo.legalIdentities[1] + monitor.register(aliceNodeHandle.configuration.rpcAddress!!, cashUser.username, cashUser.password) + rpc = monitor.proxyObservable.value!! + notaryParty = defaultNotaryIdentity - val bobNodeHandle = startNode(providedName = BOB.name, rpcUsers = listOf(cashUser)).getOrThrow() - bobNode = bobNodeHandle.nodeInfo - val monitorBob = NodeMonitorModel() - stateMachineUpdatesBob = monitorBob.stateMachineUpdates.bufferUntilSubscribed() - monitorBob.register(bobNodeHandle.configuration.rpcAddress!!, cashUser.username, cashUser.password) - rpcBob = monitorBob.proxyObservable.value!! - runTest() + val bobNodeHandle = startNode(providedName = BOB.name, rpcUsers = listOf(cashUser)).getOrThrow() + bobNode = bobNodeHandle.nodeInfo + val monitorBob = NodeMonitorModel() + stateMachineUpdatesBob = monitorBob.stateMachineUpdates.bufferUntilSubscribed() + monitorBob.register(bobNodeHandle.configuration.rpcAddress!!, cashUser.username, cashUser.password) + rpcBob = monitorBob.proxyObservable.value!! + runTest() + } } @Test - fun `network map update`() { + fun `network map update`() = setup { val charlieNode = newNode(CHARLIE.name) val nonServiceIdentities = aliceNode.legalIdentitiesAndCerts + bobNode.legalIdentitiesAndCerts + charlieNode.legalIdentitiesAndCerts networkMapUpdates.filter { it.node.legalIdentitiesAndCerts.any { it in nonServiceIdentities } } @@ -104,7 +111,7 @@ class NodeMonitorModelTest : DriverBasedTest() { } @Test - fun `cash issue works end to end`() { + fun `cash issue works end to end`() = setup { rpc.startFlow(::CashIssueFlow, Amount(100, USD), OpaqueBytes(ByteArray(1, { 1 })), @@ -128,7 +135,7 @@ class NodeMonitorModelTest : DriverBasedTest() { } @Test - fun `cash issue and move`() { + fun `cash issue and move`() = setup { val (_, issueIdentity) = rpc.startFlow(::CashIssueFlow, 100.DOLLARS, OpaqueBytes.of(1), notaryParty).returnValue.getOrThrow() rpc.startFlow(::CashPaymentFlow, 100.DOLLARS, bobNode.chooseIdentity()).returnValue.getOrThrow() diff --git a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/ContractStateModel.kt b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/ContractStateModel.kt index e62f531dcf..85a37acafb 100644 --- a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/ContractStateModel.kt +++ b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/ContractStateModel.kt @@ -40,7 +40,7 @@ class ContractStateModel { return this.map { stateAndRef -> if (stateAndRef.state.data is Cash.State) { // Kotlin doesn't unify here for some reason - uncheckedCast(stateAndRef) + uncheckedCast, StateAndRef>(stateAndRef) } else { null } diff --git a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NetworkIdentityModel.kt b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NetworkIdentityModel.kt index 2213d40bbc..5451f2acce 100644 --- a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NetworkIdentityModel.kt +++ b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NetworkIdentityModel.kt @@ -5,6 +5,7 @@ import com.google.common.cache.CacheLoader import javafx.beans.value.ObservableValue import javafx.collections.FXCollections import javafx.collections.ObservableList +import net.corda.client.jfx.utils.ChosenList import net.corda.client.jfx.utils.filterNotNull import net.corda.client.jfx.utils.fold import net.corda.client.jfx.utils.map @@ -12,7 +13,6 @@ import net.corda.core.identity.AnonymousParty import net.corda.core.identity.Party import net.corda.core.node.NodeInfo import net.corda.core.node.services.NetworkMapCache.MapChange -import net.corda.nodeapi.internal.ServiceType import java.security.PublicKey class NetworkIdentityModel { @@ -36,16 +36,10 @@ class NetworkIdentityModel { .build>(CacheLoader.from { publicKey -> publicKey?.let { rpcProxy.map { it?.nodeInfoFromParty(AnonymousParty(publicKey)) } } }) - - val notaries: ObservableList = networkIdentities.map { - it.legalIdentitiesAndCerts.find { it.name.commonName?.let { ServiceType.parse(it).isNotary() } == true } - }.map { it?.party }.filterNotNull() - + val notaries = ChosenList(rpcProxy.map { FXCollections.observableList(it?.notaryIdentities() ?: emptyList()) }) val notaryNodes: ObservableList = notaries.map { rpcProxy.value?.nodeInfoFromParty(it) }.filterNotNull() val parties: ObservableList = networkIdentities .filtered { it.legalIdentities.all { it !in notaries } } - // TODO: REMOVE THIS HACK WHEN NETWORK MAP REDESIGN WORK IS COMPLETED. - .filtered { it.legalIdentities.all { it.name.organisation != "Network Map Service" } } val myIdentity = rpcProxy.map { it?.nodeInfo()?.legalIdentitiesAndCerts?.first()?.party } fun partyFromPublicKey(publicKey: PublicKey): ObservableValue = identityCache[publicKey] diff --git a/client/rpc/src/integration-test/java/net/corda/client/rpc/CordaRPCJavaClientTest.java b/client/rpc/src/integration-test/java/net/corda/client/rpc/CordaRPCJavaClientTest.java index d46afb11b9..698aca77c9 100644 --- a/client/rpc/src/integration-test/java/net/corda/client/rpc/CordaRPCJavaClientTest.java +++ b/client/rpc/src/integration-test/java/net/corda/client/rpc/CordaRPCJavaClientTest.java @@ -26,7 +26,8 @@ import static java.util.Objects.requireNonNull; import static kotlin.test.AssertionsKt.assertEquals; import static net.corda.finance.Currencies.DOLLARS; import static net.corda.finance.contracts.GetBalances.getCashBalance; -import static net.corda.node.services.FlowPermissions.startFlowPermission; +import static net.corda.node.services.Permissions.invokeRpc; +import static net.corda.node.services.Permissions.startFlow; import static net.corda.testing.TestConstants.getALICE; public class CordaRPCJavaClientTest extends NodeBasedTest { @@ -34,7 +35,12 @@ public class CordaRPCJavaClientTest extends NodeBasedTest { super(Arrays.asList("net.corda.finance.contracts", CashSchemaV1.class.getPackage().getName())); } - private List perms = Arrays.asList(startFlowPermission(CashPaymentFlow.class), startFlowPermission(CashIssueFlow.class)); + private List perms = Arrays.asList( + startFlow(CashPaymentFlow.class), + startFlow(CashIssueFlow.class), + invokeRpc("nodeInfo"), + invokeRpc("vaultQueryBy"), + invokeRpc("vaultQueryByCriteria")); private Set permSet = new HashSet<>(perms); private User rpcUser = new User("user1", "test", permSet); diff --git a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt index e5b412b596..4d67be16d8 100644 --- a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt +++ b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt @@ -4,10 +4,7 @@ import net.corda.core.crypto.random63BitValue import net.corda.core.flows.FlowInitiator import net.corda.core.internal.concurrent.flatMap import net.corda.core.internal.packageName -import net.corda.core.messaging.FlowProgressHandle -import net.corda.core.messaging.StateMachineUpdate -import net.corda.core.messaging.startFlow -import net.corda.core.messaging.startTrackedFlow +import net.corda.core.messaging.* import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.getOrThrow import net.corda.finance.DOLLARS @@ -20,7 +17,8 @@ import net.corda.finance.flows.CashPaymentFlow import net.corda.finance.schemas.CashSchemaV1 import net.corda.node.internal.Node import net.corda.node.internal.StartedNode -import net.corda.node.services.FlowPermissions.Companion.startFlowPermission +import net.corda.node.services.Permissions.Companion.invokeRpc +import net.corda.node.services.Permissions.Companion.startFlow import net.corda.nodeapi.User import net.corda.testing.ALICE import net.corda.testing.chooseIdentity @@ -36,9 +34,12 @@ import kotlin.test.assertTrue class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", CashSchemaV1::class.packageName)) { private val rpcUser = User("user1", "test", permissions = setOf( - startFlowPermission(), - startFlowPermission() - )) + startFlow(), + startFlow(), + invokeRpc("vaultQueryBy"), + invokeRpc(CordaRPCOps::stateMachinesFeed), + invokeRpc("vaultQueryByCriteria")) + ) private lateinit var node: StartedNode private lateinit var client: CordaRPCClient private var connection: CordaRPCConnection? = null diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt index 2def3fbf20..1ef330d3e1 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt @@ -7,6 +7,7 @@ import net.corda.core.messaging.CordaRPCOps import net.corda.core.utilities.NetworkHostAndPort import net.corda.nodeapi.ArtemisTcpTransport.Companion.tcpTransport import net.corda.nodeapi.ConnectionDirection +import net.corda.nodeapi.internal.serialization.AMQP_RPC_CLIENT_CONTEXT import net.corda.nodeapi.internal.serialization.KRYO_RPC_CLIENT_CONTEXT import java.time.Duration diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/KryoClientSerializationScheme.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/KryoClientSerializationScheme.kt index 2275cd0d3f..e044117699 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/KryoClientSerializationScheme.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/KryoClientSerializationScheme.kt @@ -5,6 +5,11 @@ import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationDefaults import net.corda.core.utilities.ByteSequence import net.corda.nodeapi.internal.serialization.* +import net.corda.nodeapi.internal.serialization.amqp.AMQPClientSerializationScheme +import net.corda.nodeapi.internal.serialization.kryo.AbstractKryoSerializationScheme +import net.corda.nodeapi.internal.serialization.kryo.DefaultKryoCustomizer +import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1 +import net.corda.nodeapi.internal.serialization.kryo.RPCKryo import java.util.concurrent.atomic.AtomicBoolean class KryoClientSerializationScheme : AbstractKryoSerializationScheme() { diff --git a/client/rpc/src/test/kotlin/net/corda/client/rpc/ClientRPCInfrastructureTests.kt b/client/rpc/src/test/kotlin/net/corda/client/rpc/ClientRPCInfrastructureTests.kt index 3502145ae0..0eaa7fbacc 100644 --- a/client/rpc/src/test/kotlin/net/corda/client/rpc/ClientRPCInfrastructureTests.kt +++ b/client/rpc/src/test/kotlin/net/corda/client/rpc/ClientRPCInfrastructureTests.kt @@ -6,7 +6,7 @@ import net.corda.core.internal.concurrent.openFuture import net.corda.core.internal.concurrent.thenMatch import net.corda.core.messaging.RPCOps import net.corda.core.utilities.getOrThrow -import net.corda.node.services.messaging.getRpcContext +import net.corda.node.services.messaging.rpcContext import net.corda.testing.RPCDriverExposedDSLInterface import net.corda.testing.rpcDriver import net.corda.testing.rpcTestUser @@ -65,7 +65,7 @@ class ClientRPCInfrastructureTests : AbstractRPCTest() { override fun makeComplicatedObservable() = complicatedObservable override fun makeComplicatedListenableFuture() = complicatedListenableFuturee override fun addedLater(): Unit = throw IllegalStateException() - override fun captureUser(): String = getRpcContext().currentUser.username + override fun captureUser(): String = rpcContext().currentUser.username } @Test diff --git a/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPermissionsTests.kt b/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPermissionsTests.kt index cee75881aa..00392eb37a 100644 --- a/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPermissionsTests.kt +++ b/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPermissionsTests.kt @@ -1,7 +1,9 @@ package net.corda.client.rpc +import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.RPCOps -import net.corda.node.services.messaging.getRpcContext +import net.corda.node.services.Permissions.Companion.invokeRpc +import net.corda.node.services.messaging.rpcContext import net.corda.node.services.messaging.requirePermission import net.corda.nodeapi.User import net.corda.testing.RPCDriverExposedDSLInterface @@ -9,6 +11,8 @@ import net.corda.testing.rpcDriver import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized +import kotlin.reflect.KVisibility +import kotlin.reflect.full.declaredMemberFunctions import kotlin.test.assertFailsWith @RunWith(Parameterized::class) @@ -28,7 +32,7 @@ class RPCPermissionsTests : AbstractRPCTest() { class TestOpsImpl : TestOps { override val protocolVersion = 1 - override fun validatePermission(str: String) = getRpcContext().requirePermission(str) + override fun validatePermission(str: String) { rpcContext().requirePermission(str) } } /** @@ -89,4 +93,19 @@ class RPCPermissionsTests : AbstractRPCTest() { } } + @Test + fun `fine grained permissions are enforced`() { + val allPermissions = CordaRPCOps::class.declaredMemberFunctions.filter { it.visibility == KVisibility.PUBLIC }.map { invokeRpc(it) } + allPermissions.forEach { permission -> + rpcDriver { + val user = userOf("Mark", setOf(permission)) + val proxy = testProxyFor(user) + + proxy.validatePermission(permission) + (allPermissions - permission).forEach { notOwnedPermission -> + assertFailsWith(PermissionException::class, { proxy.validatePermission(notOwnedPermission) }) + } + } + } + } } diff --git a/confidential-identities/src/test/kotlin/net/corda/confidential/IdentitySyncFlowTests.kt b/confidential-identities/src/test/kotlin/net/corda/confidential/IdentitySyncFlowTests.kt index b760817afe..798a954708 100644 --- a/confidential-identities/src/test/kotlin/net/corda/confidential/IdentitySyncFlowTests.kt +++ b/confidential-identities/src/test/kotlin/net/corda/confidential/IdentitySyncFlowTests.kt @@ -14,8 +14,11 @@ import net.corda.finance.DOLLARS import net.corda.finance.contracts.asset.Cash import net.corda.finance.flows.CashIssueAndPaymentFlow import net.corda.finance.flows.CashPaymentFlow -import net.corda.testing.* +import net.corda.testing.ALICE_NAME +import net.corda.testing.BOB_NAME +import net.corda.testing.CHARLIE_NAME import net.corda.testing.node.MockNetwork +import net.corda.testing.singleIdentity import org.junit.After import org.junit.Before import org.junit.Test @@ -24,12 +27,16 @@ import kotlin.test.assertNotNull import kotlin.test.assertNull class IdentitySyncFlowTests { - lateinit var mockNet: MockNetwork + private lateinit var mockNet: MockNetwork @Before fun before() { // We run this in parallel threads to help catch any race conditions that may exist. - mockNet = MockNetwork(networkSendManuallyPumped = false, threadPerNode = true, cordappPackages = listOf("net.corda.finance.contracts.asset")) + mockNet = MockNetwork( + networkSendManuallyPumped = false, + threadPerNode = true, + cordappPackages = listOf("net.corda.finance.contracts.asset") + ) } @After @@ -40,12 +47,11 @@ class IdentitySyncFlowTests { @Test fun `sync confidential identities`() { // Set up values we'll need - val notaryNode = mockNet.createNotaryNode() val aliceNode = mockNet.createPartyNode(ALICE_NAME) val bobNode = mockNet.createPartyNode(BOB_NAME) val alice: Party = aliceNode.info.singleIdentity() val bob: Party = bobNode.info.singleIdentity() - val notary = notaryNode.services.getDefaultNotary() + val notary = mockNet.defaultNotaryIdentity bobNode.internals.registerInitiatedFlow(Receive::class.java) // Alice issues then pays some cash to a new confidential identity that Bob doesn't know about @@ -70,14 +76,13 @@ class IdentitySyncFlowTests { @Test fun `don't offer other's identities confidential identities`() { // Set up values we'll need - val notaryNode = mockNet.createNotaryNode() val aliceNode = mockNet.createPartyNode(ALICE_NAME) val bobNode = mockNet.createPartyNode(BOB_NAME) val charlieNode = mockNet.createPartyNode(CHARLIE_NAME) val alice: Party = aliceNode.info.singleIdentity() val bob: Party = bobNode.info.singleIdentity() val charlie: Party = charlieNode.info.singleIdentity() - val notary = notaryNode.services.getDefaultNotary() + val notary = mockNet.defaultNotaryIdentity bobNode.internals.registerInitiatedFlow(Receive::class.java) // Charlie issues then pays some cash to a new confidential identity @@ -105,7 +110,7 @@ class IdentitySyncFlowTests { * Very lightweight wrapping flow to trigger the counterparty flow that receives the identities. */ @InitiatingFlow - class Initiator(val otherSide: Party, val tx: WireTransaction): FlowLogic() { + class Initiator(private val otherSide: Party, private val tx: WireTransaction): FlowLogic() { @Suspendable override fun call(): Boolean { val session = initiateFlow(otherSide) @@ -116,7 +121,7 @@ class IdentitySyncFlowTests { } @InitiatedBy(IdentitySyncFlowTests.Initiator::class) - class Receive(val otherSideSession: FlowSession): FlowLogic() { + class Receive(private val otherSideSession: FlowSession): FlowLogic() { @Suspendable override fun call() { subFlow(IdentitySyncFlow.Receive(otherSideSession)) diff --git a/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt b/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt index d323b38b30..6fc8270b2d 100644 --- a/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt +++ b/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt @@ -6,17 +6,22 @@ import net.corda.core.identity.Party import net.corda.core.utilities.getOrThrow import net.corda.testing.* import net.corda.testing.node.MockNetwork +import org.junit.Before import org.junit.Test import kotlin.test.* class SwapIdentitiesFlowTests { + private lateinit var mockNet: MockNetwork + + @Before + fun setup() { + // We run this in parallel threads to help catch any race conditions that may exist. + mockNet = MockNetwork(networkSendManuallyPumped = false, threadPerNode = true) + } + @Test fun `issue key`() { - // We run this in parallel threads to help catch any race conditions that may exist. - val mockNet = MockNetwork(threadPerNode = true) - // Set up values we'll need - mockNet.createNotaryNode() val aliceNode = mockNet.createPartyNode(ALICE.name) val bobNode = mockNet.createPartyNode(BOB.name) val alice = aliceNode.info.singleIdentity() @@ -52,19 +57,16 @@ class SwapIdentitiesFlowTests { */ @Test fun `verifies identity name`() { - // We run this in parallel threads to help catch any race conditions that may exist. - val mockNet = MockNetwork(threadPerNode = true) - // Set up values we'll need - val notaryNode = mockNet.createNotaryNode() val aliceNode = mockNet.createPartyNode(ALICE.name) val bobNode = mockNet.createPartyNode(BOB.name) + val charlieNode = mockNet.createPartyNode(CHARLIE.name) val bob: Party = bobNode.services.myInfo.singleIdentity() - val notBob = notaryNode.database.transaction { - notaryNode.services.keyManagementService.freshKeyAndCert(notaryNode.services.myInfo.chooseIdentityAndCert(), false) + val notBob = charlieNode.database.transaction { + charlieNode.services.keyManagementService.freshKeyAndCert(charlieNode.services.myInfo.chooseIdentityAndCert(), false) } val sigData = SwapIdentitiesFlow.buildDataToSign(notBob) - val signature = notaryNode.services.keyManagementService.sign(sigData, notBob.owningKey) + val signature = charlieNode.services.keyManagementService.sign(sigData, notBob.owningKey) assertFailsWith("Certificate subject must match counterparty's well known identity.") { SwapIdentitiesFlow.validateAndRegisterIdentity(aliceNode.services.identityService, bob, notBob, signature.withoutKey()) } @@ -77,11 +79,8 @@ class SwapIdentitiesFlowTests { */ @Test fun `verifies signature`() { - // We run this in parallel threads to help catch any race conditions that may exist. - val mockNet = MockNetwork(threadPerNode = true) - // Set up values we'll need - val notaryNode = mockNet.createNotaryNode() + val notaryNode = mockNet.defaultNotaryNode val aliceNode = mockNet.createPartyNode(ALICE.name) val bobNode = mockNet.createPartyNode(BOB.name) val bob: Party = bobNode.services.myInfo.singleIdentity() diff --git a/config/dev/generalnodeb.conf b/config/dev/generalnodeb.conf index af4e26cc27..55ebb1e690 100644 --- a/config/dev/generalnodeb.conf +++ b/config/dev/generalnodeb.conf @@ -4,8 +4,4 @@ trustStorePassword : "trustpass" p2pAddress : "localhost:10005" rpcAddress : "localhost:10006" webAddress : "localhost:10007" -networkMapService : { - address : "localhost:10000" - legalName : "O=Network Map Service,OU=corda,L=London,C=GB" -} useHTTPS : false diff --git a/constants.properties b/constants.properties index c85d51a756..a780da9214 100644 --- a/constants.properties +++ b/constants.properties @@ -1,5 +1,5 @@ -gradlePluginsVersion=2.0.6 +gradlePluginsVersion=2.0.8 kotlinVersion=1.1.50 guavaVersion=21.0 bouncycastleVersion=1.57 -typesafeConfigVersion=1.3.1 +typesafeConfigVersion=1.3.1 \ No newline at end of file diff --git a/core/build.gradle b/core/build.gradle index d821ec7d37..34d77bf8fc 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -7,6 +7,52 @@ apply plugin: 'com.jfrog.artifactory' description 'Corda core' +evaluationDependsOn(':node:capsule') + +configurations { + integrationTestCompile.extendsFrom testCompile + integrationTestRuntime.extendsFrom testRuntime + + smokeTestCompile.extendsFrom compile + smokeTestRuntime.extendsFrom runtime +} + +sourceSets { + integrationTest { + kotlin { + compileClasspath += main.output + test.output + runtimeClasspath += main.output + test.output + srcDir file('src/integration-test/kotlin') + } + java { + compileClasspath += main.output + test.output + runtimeClasspath += main.output + test.output + srcDir file('src/integration-test/java') + } + } + smokeTest { + kotlin { + // We must NOT have any Node code on the classpath, so do NOT + // include the test or integrationTest dependencies here. + compileClasspath += main.output + runtimeClasspath += main.output + srcDir file('src/smoke-test/kotlin') + } + java { + compileClasspath += main.output + runtimeClasspath += main.output + srcDir file('src/smoke-test/java') + } + } +} + +processSmokeTestResources { + // Bring in the fully built corda.jar for use by NodeFactory in the smoke tests + from(project(':node:capsule').tasks['buildCordaJAR']) { + rename 'corda-(.*)', 'corda.jar' + } +} + buildscript { repositories { mavenCentral() @@ -14,7 +60,6 @@ buildscript { } dependencies { - testCompile "junit:junit:$junit_version" testCompile "commons-fileupload:commons-fileupload:$fileupload_version" @@ -48,6 +93,11 @@ dependencies { // Guava: Google utilities library. testCompile "com.google.guava:guava:$guava_version" + // Smoke tests do NOT have any Node code on the classpath! + smokeTestCompile project(':smoke-test-utils') + smokeTestCompile "org.assertj:assertj-core:${assertj_version}" + smokeTestCompile "junit:junit:$junit_version" + // RxJava: observable streams of events. compile "io.reactivex:rxjava:$rxjava_version" @@ -87,6 +137,22 @@ task testJar(type: Jar) { from sourceSets.test.output } +task integrationTest(type: Test) { + testClassesDirs = sourceSets.integrationTest.output.classesDirs + classpath = sourceSets.integrationTest.runtimeClasspath +} + +task smokeTestJar(type: Jar) { + classifier 'smokeTests' + from sourceSets.smokeTest.output +} + +task smokeTest(type: Test) { + dependsOn smokeTestJar + testClassesDirs = sourceSets.smokeTest.output.classesDirs + classpath = sourceSets.smokeTest.runtimeClasspath +} + artifacts { testArtifacts testJar } diff --git a/core/src/main/kotlin/net/corda/core/concurrent/CordaFuture.kt b/core/src/main/kotlin/net/corda/core/concurrent/CordaFuture.kt index 3ecab2e395..4977f3a34b 100644 --- a/core/src/main/kotlin/net/corda/core/concurrent/CordaFuture.kt +++ b/core/src/main/kotlin/net/corda/core/concurrent/CordaFuture.kt @@ -13,7 +13,7 @@ interface CordaFuture : Future { * If the completion thread is problematic for you e.g. deadlock, you can submit to an executor manually. * If callback fails, its throwable is logged. */ - fun then(callback: (CordaFuture) -> W): Unit + fun then(callback: (CordaFuture) -> W) /** * @return a new [CompletableFuture] with the same outcome as this Future. diff --git a/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt b/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt index 5bc47ed6fb..c02ef10bb6 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt @@ -34,7 +34,9 @@ fun PrivateKey.sign(bytesToSign: ByteArray): DigitalSignature = DigitalSignature * @throws SignatureException if signing is not possible due to malformed data or private key. */ @Throws(InvalidKeyException::class, SignatureException::class) -fun PrivateKey.sign(bytesToSign: ByteArray, publicKey: PublicKey) = DigitalSignature.WithKey(publicKey, this.sign(bytesToSign).bytes) +fun PrivateKey.sign(bytesToSign: ByteArray, publicKey: PublicKey): DigitalSignature.WithKey { + return DigitalSignature.WithKey(publicKey, this.sign(bytesToSign).bytes) +} /** * Helper function to sign with a key pair. @@ -45,11 +47,11 @@ fun PrivateKey.sign(bytesToSign: ByteArray, publicKey: PublicKey) = DigitalSigna * @throws SignatureException if signing is not possible due to malformed data or private key. */ @Throws(InvalidKeyException::class, SignatureException::class) -fun KeyPair.sign(bytesToSign: ByteArray) = private.sign(bytesToSign, public) +fun KeyPair.sign(bytesToSign: ByteArray): DigitalSignature.WithKey = private.sign(bytesToSign, public) /** Helper function to sign the bytes of [bytesToSign] with a key pair. */ @Throws(InvalidKeyException::class, SignatureException::class) -fun KeyPair.sign(bytesToSign: OpaqueBytes) = sign(bytesToSign.bytes) +fun KeyPair.sign(bytesToSign: OpaqueBytes): DigitalSignature.WithKey = sign(bytesToSign.bytes) /** * Helper function for signing a [SignableData] object. diff --git a/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt b/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt index c96070fba7..0fccf0cb4c 100644 --- a/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt @@ -49,6 +49,7 @@ class NotaryFlow { progressTracker.currentStep = REQUESTING val notaryParty = stx.notary ?: throw IllegalStateException("Transaction does not specify a Notary") + check(serviceHub.networkMapCache.isNotary(notaryParty)) { "$notaryParty is not a notary on the network" } check(stx.inputs.all { stateRef -> serviceHub.loadState(stateRef).notary == notaryParty }) { "Input states must have the same Notary" } @@ -115,6 +116,9 @@ class NotaryFlow { @Suspendable override fun call(): Void? { + check(serviceHub.myInfo.legalIdentities.any { serviceHub.networkMapCache.isNotary(it) }) { + "We are not a notary on the network" + } val (id, inputs, timeWindow, notary) = receiveAndVerifyTx() checkNotary(notary) service.validateTimeWindow(timeWindow) @@ -135,7 +139,7 @@ class NotaryFlow { protected fun checkNotary(notary: Party?) { // TODO This check implies that it's OK to use the node's main identity. Shouldn't it be just limited to the // notary identities? - if (notary !in serviceHub.myInfo.legalIdentities) { + if (notary == null || !serviceHub.myInfo.isLegalIdentity(notary)) { throw NotaryException(NotaryError.WrongNotary) } } diff --git a/core/src/main/kotlin/net/corda/core/schemas/NodeInfoSchema.kt b/core/src/main/kotlin/net/corda/core/internal/schemas/NodeInfoSchema.kt similarity index 98% rename from core/src/main/kotlin/net/corda/core/schemas/NodeInfoSchema.kt rename to core/src/main/kotlin/net/corda/core/internal/schemas/NodeInfoSchema.kt index 80c0d842ae..219ee2cbae 100644 --- a/core/src/main/kotlin/net/corda/core/schemas/NodeInfoSchema.kt +++ b/core/src/main/kotlin/net/corda/core/internal/schemas/NodeInfoSchema.kt @@ -1,8 +1,9 @@ -package net.corda.core.schemas +package net.corda.core.internal.schemas import net.corda.core.crypto.toStringShort import net.corda.core.identity.PartyAndCertificate import net.corda.core.node.NodeInfo +import net.corda.core.schemas.MappedSchema import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize diff --git a/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt b/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt index 3e87d4ba39..9556bb0a4f 100644 --- a/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt +++ b/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt @@ -100,21 +100,13 @@ interface CordaRPCOps : RPCOps { // Java Helpers // DOCSTART VaultQueryAPIHelpers - fun vaultQuery(contractStateType: Class): Vault.Page { - return vaultQueryBy(QueryCriteria.VaultQueryCriteria(), PageSpecification(), Sort(emptySet()), contractStateType) - } + fun vaultQuery(contractStateType: Class): Vault.Page - fun vaultQueryByCriteria(criteria: QueryCriteria, contractStateType: Class): Vault.Page { - return vaultQueryBy(criteria, PageSpecification(), Sort(emptySet()), contractStateType) - } + fun vaultQueryByCriteria(criteria: QueryCriteria, contractStateType: Class): Vault.Page - fun vaultQueryByWithPagingSpec(contractStateType: Class, criteria: QueryCriteria, paging: PageSpecification): Vault.Page { - return vaultQueryBy(criteria, paging, Sort(emptySet()), contractStateType) - } + fun vaultQueryByWithPagingSpec(contractStateType: Class, criteria: QueryCriteria, paging: PageSpecification): Vault.Page - fun vaultQueryByWithSorting(contractStateType: Class, criteria: QueryCriteria, sorting: Sort): Vault.Page { - return vaultQueryBy(criteria, PageSpecification(), sorting, contractStateType) - } + fun vaultQueryByWithSorting(contractStateType: Class, criteria: QueryCriteria, sorting: Sort): Vault.Page // DOCEND VaultQueryAPIHelpers /** @@ -141,21 +133,13 @@ interface CordaRPCOps : RPCOps { // Java Helpers // DOCSTART VaultTrackAPIHelpers - fun vaultTrack(contractStateType: Class): DataFeed, Vault.Update> { - return vaultTrackBy(QueryCriteria.VaultQueryCriteria(), PageSpecification(), Sort(emptySet()), contractStateType) - } + fun vaultTrack(contractStateType: Class): DataFeed, Vault.Update> - fun vaultTrackByCriteria(contractStateType: Class, criteria: QueryCriteria): DataFeed, Vault.Update> { - return vaultTrackBy(criteria, PageSpecification(), Sort(emptySet()), contractStateType) - } + fun vaultTrackByCriteria(contractStateType: Class, criteria: QueryCriteria): DataFeed, Vault.Update> - fun vaultTrackByWithPagingSpec(contractStateType: Class, criteria: QueryCriteria, paging: PageSpecification): DataFeed, Vault.Update> { - return vaultTrackBy(criteria, paging, Sort(emptySet()), contractStateType) - } + fun vaultTrackByWithPagingSpec(contractStateType: Class, criteria: QueryCriteria, paging: PageSpecification): DataFeed, Vault.Update> - fun vaultTrackByWithSorting(contractStateType: Class, criteria: QueryCriteria, sorting: Sort): DataFeed, Vault.Update> { - return vaultTrackBy(criteria, PageSpecification(), sorting, contractStateType) - } + fun vaultTrackByWithSorting(contractStateType: Class, criteria: QueryCriteria, sorting: Sort): DataFeed, Vault.Update> // DOCEND VaultTrackAPIHelpers /** diff --git a/core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt b/core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt new file mode 100644 index 0000000000..d5a035175a --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt @@ -0,0 +1,40 @@ +package net.corda.core.node + +import net.corda.core.identity.Party +import net.corda.core.serialization.CordaSerializable +import java.time.Duration +import java.time.Instant + +/** + * @property minimumPlatformVersion + * @property notaries + * @property eventHorizon + * @property maxMessageSize Maximum P2P message sent over the wire in bytes. + * @property maxTransactionSize Maximum permitted transaction size in bytes. + * @property modifiedTime + * @property epoch Version number of the network parameters. Starting from 1, this will always increment on each new set + * of parameters. + */ +// TODO Wire up the parameters +@CordaSerializable +data class NetworkParameters( + val minimumPlatformVersion: Int, + val notaries: List, + val eventHorizon: Duration, + val maxMessageSize: Int, + val maxTransactionSize: Int, + val modifiedTime: Instant, + val epoch: Int +) { + init { + require(minimumPlatformVersion > 0) { "minimumPlatformVersion must be at least 1" } + require(notaries.distinctBy { it.identity } == notaries) { "Duplicate notary identities" } + require(epoch > 0) { "epoch must be at least 1" } + } +} + +/** + * + */ +@CordaSerializable +data class NotaryInfo(val identity: Party, val validating: Boolean) diff --git a/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt b/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt index b5c415c578..69ab1a7307 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt @@ -52,7 +52,6 @@ interface NetworkMapCacheBase { * * Note that the identities are sorted based on legal name, and the ordering might change once new notaries are introduced. */ - // TODO this list will be taken from NetworkParameters distributed by NetworkMap. val notaryIdentities: List // DOCEND 1 @@ -116,12 +115,15 @@ interface NetworkMapCacheBase { fun getNotary(name: CordaX500Name): Party? = notaryIdentities.firstOrNull { it.name == name } // DOCEND 2 - /** Checks whether a given party is an advertised notary identity. */ + /** Returns true if and only if the given [Party] is a notary, which is defined by the network parameters. */ fun isNotary(party: Party): Boolean = party in notaryIdentities - /** Checks whether a given party is an validating notary identity. */ - // TODO This implementation will change after introducing of NetworkParameters. - fun isValidatingNotary(party: Party): Boolean = isNotary(party) && "validating" in party.name.commonName!! + /** + * Returns true if and only if the given [Party] is validating notary. For every party that is a validating notary, + * [isNotary] is also true. + * @see isNotary + */ + fun isValidatingNotary(party: Party): Boolean /** Clear all network map data from local node cache. */ fun clearNetworkMapCache() diff --git a/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt index ba7cb8de1f..be7cdf0d7b 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt @@ -108,7 +108,7 @@ abstract class TraversableTransaction(open val componentGroups: List, val groupHashes: List diff --git a/node/src/smoke-test/kotlin/net/corda/node/CordappSmokeTest.kt b/core/src/smoke-test/kotlin/net/corda/core/cordapp/CordappSmokeTest.kt similarity index 94% rename from node/src/smoke-test/kotlin/net/corda/node/CordappSmokeTest.kt rename to core/src/smoke-test/kotlin/net/corda/core/cordapp/CordappSmokeTest.kt index 82c48bf82e..2f1991af12 100644 --- a/node/src/smoke-test/kotlin/net/corda/node/CordappSmokeTest.kt +++ b/core/src/smoke-test/kotlin/net/corda/core/cordapp/CordappSmokeTest.kt @@ -1,4 +1,4 @@ -package net.corda.node +package net.corda.core.cordapp import co.paralleluniverse.fibers.Suspendable import net.corda.core.flows.* @@ -11,7 +11,6 @@ import net.corda.core.internal.list import net.corda.core.messaging.startFlow import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.unwrap -import net.corda.node.internal.cordapp.CordappLoader import net.corda.nodeapi.User import net.corda.smoketesting.NodeConfig import net.corda.smoketesting.NodeProcess @@ -23,6 +22,7 @@ import kotlin.streams.toList class CordappSmokeTest { private companion object { + private const val CORDAPPS_DIR_NAME = "cordapps" val user = User("user1", "test", permissions = setOf("ALL")) val port = AtomicInteger(15100) } @@ -38,9 +38,10 @@ class CordappSmokeTest { users = listOf(user) ) + @Test fun `FlowContent appName returns the filename of the CorDapp jar`() { - val cordappsDir = (factory.baseDirectory(aliceConfig) / CordappLoader.CORDAPPS_DIR_NAME).createDirectories() + val cordappsDir = (factory.baseDirectory(aliceConfig) / CORDAPPS_DIR_NAME).createDirectories() // Find the jar file for the smoke tests of this module val selfCordapp = Paths.get("build", "libs").list { it.filter { "-smokeTests" in it.toString() }.toList().single() @@ -61,7 +62,7 @@ class CordappSmokeTest { @Test fun `empty cordapps directory`() { - (factory.baseDirectory(aliceConfig) / CordappLoader.CORDAPPS_DIR_NAME).createDirectories() + (factory.baseDirectory(aliceConfig) / CORDAPPS_DIR_NAME).createDirectories() factory.create(aliceConfig).close() } diff --git a/core/src/test/java/net/corda/core/flows/FlowsInJavaTest.java b/core/src/test/java/net/corda/core/flows/FlowsInJavaTest.java index 6e836dbaba..ab03890365 100644 --- a/core/src/test/java/net/corda/core/flows/FlowsInJavaTest.java +++ b/core/src/test/java/net/corda/core/flows/FlowsInJavaTest.java @@ -18,14 +18,12 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.junit.Assert.fail; public class FlowsInJavaTest { - private final MockNetwork mockNet = new MockNetwork(); private StartedNode aliceNode; private StartedNode bobNode; @Before public void setUp() throws Exception { - mockNet.createNotaryNode(); aliceNode = mockNet.createPartyNode(TestConstants.getALICE().getName()); bobNode = mockNet.createPartyNode(TestConstants.getBOB().getName()); mockNet.runNetwork(); diff --git a/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt b/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt index 81e67b3b6e..f634c2fd8b 100644 --- a/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt @@ -14,7 +14,6 @@ import net.corda.testing.ALICE import net.corda.testing.ALICE_NAME import net.corda.testing.BOB import net.corda.testing.node.MockNetwork -import net.corda.testing.node.MockNodeArgs import net.corda.testing.node.MockNodeParameters import net.corda.testing.singleIdentity import org.junit.After @@ -109,15 +108,13 @@ class AttachmentTests { } @Test - fun `malicious response`() { + fun maliciousResponse() { // Make a node that doesn't do sanity checking at load time. - val aliceNode = mockNet.createNotaryNode(MockNodeParameters(legalName = ALICE.name), nodeFactory = object : MockNetwork.Factory { - override fun create(args: MockNodeArgs): MockNetwork.MockNode { - return object : MockNetwork.MockNode(args) { - override fun start() = super.start().apply { attachments.checkAttachmentsOnLoad = false } - } + val aliceNode = mockNet.createNode(MockNodeParameters(legalName = ALICE.name), nodeFactory = { args -> + object : MockNetwork.MockNode(args) { + override fun start() = super.start().apply { attachments.checkAttachmentsOnLoad = false } } - }, validating = false) + }) val bobNode = mockNet.createNode(MockNodeParameters(legalName = BOB.name)) mockNet.runNetwork() val alice = aliceNode.services.myInfo.identityFromX500Name(ALICE_NAME) diff --git a/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt b/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt index 9ecea5940b..75e14ecff0 100644 --- a/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt @@ -27,19 +27,18 @@ class CollectSignaturesFlowTests { private val cordappPackages = listOf("net.corda.testing.contracts") } - lateinit var mockNet: MockNetwork - lateinit var aliceNode: StartedNode - lateinit var bobNode: StartedNode - lateinit var charlieNode: StartedNode - lateinit var alice: Party - lateinit var bob: Party - lateinit var charlie: Party - lateinit var notary: Party + private lateinit var mockNet: MockNetwork + private lateinit var aliceNode: StartedNode + private lateinit var bobNode: StartedNode + private lateinit var charlieNode: StartedNode + private lateinit var alice: Party + private lateinit var bob: Party + private lateinit var charlie: Party + private lateinit var notary: Party @Before fun setup() { mockNet = MockNetwork(cordappPackages = cordappPackages) - val notaryNode = mockNet.createNotaryNode() aliceNode = mockNet.createPartyNode(ALICE.name) bobNode = mockNet.createPartyNode(BOB.name) charlieNode = mockNet.createPartyNode(CHARLIE.name) @@ -47,7 +46,7 @@ class CollectSignaturesFlowTests { alice = aliceNode.info.singleIdentity() bob = bobNode.info.singleIdentity() charlie = charlieNode.info.singleIdentity() - notary = notaryNode.services.getDefaultNotary() + notary = mockNet.defaultNotaryIdentity } @After diff --git a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt index 19b93ba663..e2ec9b68d5 100644 --- a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt +++ b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt @@ -16,9 +16,9 @@ import net.corda.finance.USD import net.corda.finance.`issued by` import net.corda.finance.contracts.asset.Cash import net.corda.finance.flows.CashIssueFlow -import net.corda.node.internal.CordaRPCOpsImpl +import net.corda.node.internal.SecureCordaRPCOps import net.corda.node.internal.StartedNode -import net.corda.node.services.FlowPermissions.Companion.startFlowPermission +import net.corda.node.services.Permissions.Companion.startFlow import net.corda.nodeapi.User import net.corda.testing.* import net.corda.testing.contracts.DummyContract @@ -41,14 +41,13 @@ class ContractUpgradeFlowTest { @Before fun setup() { mockNet = MockNetwork(cordappPackages = listOf("net.corda.testing.contracts", "net.corda.finance.contracts.asset", "net.corda.core.flows")) - val notaryNode = mockNet.createNotaryNode() aliceNode = mockNet.createPartyNode(ALICE.name) bobNode = mockNet.createPartyNode(BOB.name) // Process registration mockNet.runNetwork() - notary = notaryNode.services.getDefaultNotary() + notary = mockNet.defaultNotaryIdentity } @After @@ -118,7 +117,7 @@ class ContractUpgradeFlowTest { return startRpcClient( rpcAddress = startRpcServer( rpcUser = user, - ops = CordaRPCOpsImpl(node.services, node.smm, node.database, node.services) + ops = SecureCordaRPCOps(node.services, node.smm, node.database, node.services) ).get().broker.hostAndPort!!, username = user.username, password = user.password @@ -134,10 +133,10 @@ class ContractUpgradeFlowTest { val stx = bobNode.services.addSignature(signedByA) val user = rpcTestUser.copy(permissions = setOf( - startFlowPermission(), - startFlowPermission>(), - startFlowPermission(), - startFlowPermission() + startFlow(), + startFlow>(), + startFlow(), + startFlow() )) val rpcA = startProxy(aliceNode, user) val rpcB = startProxy(bobNode, user) diff --git a/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt b/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt index 1bc496db6c..167c6a968f 100644 --- a/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt @@ -26,7 +26,6 @@ class FinalityFlowTests { @Before fun setup() { mockNet = MockNetwork(cordappPackages = listOf("net.corda.finance.contracts.asset")) - val notaryNode = mockNet.createNotaryNode() val aliceNode = mockNet.createPartyNode(ALICE_NAME) val bobNode = mockNet.createPartyNode(BOB_NAME) mockNet.runNetwork() @@ -34,7 +33,7 @@ class FinalityFlowTests { bobServices = bobNode.services alice = aliceNode.info.singleIdentity() bob = bobNode.info.singleIdentity() - notary = notaryNode.services.getDefaultNotary() + notary = aliceServices.getDefaultNotary() } @After diff --git a/core/src/test/kotlin/net/corda/core/flows/ReceiveAllFlowTests.kt b/core/src/test/kotlin/net/corda/core/flows/ReceiveAllFlowTests.kt index cfadcf4bf5..9b1ee8161c 100644 --- a/core/src/test/kotlin/net/corda/core/flows/ReceiveAllFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/ReceiveAllFlowTests.kt @@ -13,7 +13,7 @@ import org.junit.Test class ReceiveMultipleFlowTests { @Test fun `receive all messages in parallel using map style`() { - network(3) { nodes, _ -> + network(3) { nodes -> val doubleValue = 5.0 nodes[1].registerAnswer(AlgorithmDefinition::class, doubleValue) val stringValue = "Thriller" @@ -30,7 +30,7 @@ class ReceiveMultipleFlowTests { @Test fun `receive all messages in parallel using list style`() { - network(3) { nodes, _ -> + network(3) { nodes -> val value1 = 5.0 nodes[1].registerAnswer(ParallelAlgorithmList::class, value1) val value2 = 6.0 diff --git a/core/src/test/kotlin/net/corda/core/internal/ResolveTransactionsFlowTest.kt b/core/src/test/kotlin/net/corda/core/internal/ResolveTransactionsFlowTest.kt index e7f43c3096..e5e67fff0b 100644 --- a/core/src/test/kotlin/net/corda/core/internal/ResolveTransactionsFlowTest.kt +++ b/core/src/test/kotlin/net/corda/core/internal/ResolveTransactionsFlowTest.kt @@ -8,7 +8,9 @@ import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.sequence import net.corda.node.internal.StartedNode -import net.corda.testing.* +import net.corda.testing.MEGA_CORP +import net.corda.testing.MINI_CORP +import net.corda.testing.chooseIdentity import net.corda.testing.contracts.DummyContract import net.corda.testing.getDefaultNotary import net.corda.testing.node.MockNetwork @@ -26,18 +28,18 @@ import kotlin.test.assertNull // DOCSTART 3 class ResolveTransactionsFlowTest { - lateinit var mockNet: MockNetwork - lateinit var notaryNode: StartedNode - lateinit var megaCorpNode: StartedNode - lateinit var miniCorpNode: StartedNode - lateinit var megaCorp: Party - lateinit var miniCorp: Party - lateinit var notary: Party + private lateinit var mockNet: MockNetwork + private lateinit var notaryNode: StartedNode + private lateinit var megaCorpNode: StartedNode + private lateinit var miniCorpNode: StartedNode + private lateinit var megaCorp: Party + private lateinit var miniCorp: Party + private lateinit var notary: Party @Before fun setup() { mockNet = MockNetwork(cordappPackages = listOf("net.corda.testing.contracts")) - notaryNode = mockNet.createNotaryNode() + notaryNode = mockNet.defaultNotaryNode megaCorpNode = mockNet.createPartyNode(MEGA_CORP.name) miniCorpNode = mockNet.createPartyNode(MINI_CORP.name) megaCorpNode.internals.registerInitiatedFlow(TestResponseFlow::class.java) diff --git a/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt b/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt index 8057164bcf..2828bb31b9 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt @@ -17,7 +17,6 @@ import net.corda.node.services.persistence.NodeAttachmentService import net.corda.node.utilities.currentDBSession import net.corda.testing.chooseIdentity import net.corda.testing.node.MockNetwork -import net.corda.testing.node.MockNodeArgs import net.corda.testing.node.MockNodeParameters import org.junit.After import org.junit.Before @@ -156,11 +155,9 @@ class AttachmentSerializationTest { private fun rebootClientAndGetAttachmentContent(checkAttachmentsOnLoad: Boolean = true): String { client.dispose() - client = mockNet.createNode(MockNodeParameters(client.internals.id), object : MockNetwork.Factory { - override fun create(args: MockNodeArgs): MockNetwork.MockNode { - return object : MockNetwork.MockNode(args) { - override fun start() = super.start().apply { attachments.checkAttachmentsOnLoad = checkAttachmentsOnLoad } - } + client = mockNet.createNode(MockNodeParameters(client.internals.id), { args -> + object : MockNetwork.MockNode(args) { + override fun start() = super.start().apply { attachments.checkAttachmentsOnLoad = checkAttachmentsOnLoad } } }) return (client.smm.allStateMachines[0].stateMachine.resultFuture.apply { mockNet.runNetwork() }.getOrThrow() as ClientResult).attachmentContent diff --git a/core/src/test/kotlin/net/corda/core/contracts/CompatibleTransactionTests.kt b/core/src/test/kotlin/net/corda/core/transactions/CompatibleTransactionTests.kt similarity index 95% rename from core/src/test/kotlin/net/corda/core/contracts/CompatibleTransactionTests.kt rename to core/src/test/kotlin/net/corda/core/transactions/CompatibleTransactionTests.kt index 70995cd8cd..7464126983 100644 --- a/core/src/test/kotlin/net/corda/core/contracts/CompatibleTransactionTests.kt +++ b/core/src/test/kotlin/net/corda/core/transactions/CompatibleTransactionTests.kt @@ -1,9 +1,9 @@ -package net.corda.core.contracts +package net.corda.core.transactions +import net.corda.core.contracts.* import net.corda.core.contracts.ComponentGroupEnum.* import net.corda.core.crypto.* import net.corda.core.serialization.serialize -import net.corda.core.transactions.* import net.corda.core.utilities.OpaqueBytes import net.corda.testing.* import net.corda.testing.contracts.DummyContract @@ -399,8 +399,7 @@ class CompatibleTransactionTests { @Test fun `FilteredTransaction signer manipulation tests`() { // Required to call the private constructor. - val ftxConstructor = FilteredTransaction::class.java.declaredConstructors[1] - ftxConstructor.isAccessible = true + val ftxConstructor = ::FilteredTransaction // 1st and 3rd commands require a signature from KEY_1. val twoCommandsforKey1 = listOf(dummyCommand(DUMMY_KEY_1.public, DUMMY_KEY_2.public), dummyCommand(DUMMY_KEY_2.public), dummyCommand(DUMMY_KEY_1.public)) @@ -479,12 +478,12 @@ class CompatibleTransactionTests { // A command with no corresponding signer detected // because the pointer of CommandData (3rd leaf) cannot find a corresponding (3rd) signer. val updatedFilteredComponentsNoSignersKey1SamePMT = listOf(key1CommandsFtx.filteredComponentGroups[0], noLastSignerGroupSamePartialTree) - assertFails { ftxConstructor.newInstance(key1CommandsFtx.id, updatedFilteredComponentsNoSignersKey1SamePMT, key1CommandsFtx.groupHashes) } + assertFails { ftxConstructor.invoke(key1CommandsFtx.id, updatedFilteredComponentsNoSignersKey1SamePMT, key1CommandsFtx.groupHashes) } // Remove both last signer (KEY1) and related command. // Update partial Merkle tree for signers. val updatedFilteredComponentsNoLastCommandAndSigners = listOf(noLastCommandDataGroup, noLastSignerGroup) - val ftxNoLastCommandAndSigners = ftxConstructor.newInstance(key1CommandsFtx.id, updatedFilteredComponentsNoLastCommandAndSigners, key1CommandsFtx.groupHashes) as FilteredTransaction + val ftxNoLastCommandAndSigners = ftxConstructor.invoke(key1CommandsFtx.id, updatedFilteredComponentsNoLastCommandAndSigners, key1CommandsFtx.groupHashes) as FilteredTransaction // verify() will pass as the transaction is well-formed. ftxNoLastCommandAndSigners.verify() // checkCommandVisibility() will not pass, because checkAllComponentsVisible(ComponentGroupEnum.SIGNERS_GROUP) will fail. @@ -493,7 +492,7 @@ class CompatibleTransactionTests { // Remove last signer for which there is no pointer from a visible commandData. This is the case of Key2. // Do not change partial Merkle tree for signers. // This time the object can be constructed as there is no pointer mismatch. - val ftxNoLastSigner = ftxConstructor.newInstance(key2CommandsFtx.id, updatedFilteredComponentsNoSignersKey2SamePMT, key2CommandsFtx.groupHashes) as FilteredTransaction + val ftxNoLastSigner = ftxConstructor.invoke(key2CommandsFtx.id, updatedFilteredComponentsNoSignersKey2SamePMT, key2CommandsFtx.groupHashes) as FilteredTransaction // verify() will fail as we didn't change the partial Merkle tree. assertFailsWith { ftxNoLastSigner.verify() } // checkCommandVisibility() will not pass. @@ -501,7 +500,7 @@ class CompatibleTransactionTests { // Remove last signer for which there is no pointer from a visible commandData. This is the case of Key2. // Update partial Merkle tree for signers. - val ftxNoLastSignerB = ftxConstructor.newInstance(key2CommandsFtx.id, updatedFilteredComponentsNoSignersKey2, key2CommandsFtx.groupHashes) as FilteredTransaction + val ftxNoLastSignerB = ftxConstructor.invoke(key2CommandsFtx.id, updatedFilteredComponentsNoSignersKey2, key2CommandsFtx.groupHashes) as FilteredTransaction // verify() will pass, the transaction is well-formed. ftxNoLastSignerB.verify() // But, checkAllComponentsVisible() will not pass. @@ -526,20 +525,18 @@ class CompatibleTransactionTests { val alterFilteredComponents = listOf(key1CommandsFtx.filteredComponentGroups[0], alterSignerGroup) // Do not update groupHashes. - val ftxAlterSigner = ftxConstructor.newInstance(key1CommandsFtx.id, alterFilteredComponents, key1CommandsFtx.groupHashes) as FilteredTransaction + val ftxAlterSigner = ftxConstructor.invoke(key1CommandsFtx.id, alterFilteredComponents, key1CommandsFtx.groupHashes) as FilteredTransaction // Visible components in signers group cannot be verified against their partial Merkle tree. assertFailsWith { ftxAlterSigner.verify() } // Also, checkAllComponentsVisible() will not pass (groupHash matching will fail). assertFailsWith { ftxAlterSigner.checkCommandVisibility(DUMMY_KEY_1.public) } // Update groupHashes. - val ftxAlterSignerB = ftxConstructor.newInstance(key1CommandsFtx.id, alterFilteredComponents, key1CommandsFtx.groupHashes.subList(0, 6) + alterMTree.hash) as FilteredTransaction + val ftxAlterSignerB = ftxConstructor.invoke(key1CommandsFtx.id, alterFilteredComponents, key1CommandsFtx.groupHashes.subList(0, 6) + alterMTree.hash) as FilteredTransaction // Visible components in signers group cannot be verified against their partial Merkle tree. assertFailsWith { ftxAlterSignerB.verify() } // Also, checkAllComponentsVisible() will not pass (top level Merkle tree cannot be verified against transaction's id). assertFailsWith { ftxAlterSignerB.checkCommandVisibility(DUMMY_KEY_1.public) } - - ftxConstructor.isAccessible = false } } diff --git a/core/src/test/kotlin/net/corda/core/contracts/LedgerTransactionQueryTests.kt b/core/src/test/kotlin/net/corda/core/transactions/LedgerTransactionQueryTests.kt similarity index 98% rename from core/src/test/kotlin/net/corda/core/contracts/LedgerTransactionQueryTests.kt rename to core/src/test/kotlin/net/corda/core/transactions/LedgerTransactionQueryTests.kt index 6e835d5209..28d7b6615c 100644 --- a/core/src/test/kotlin/net/corda/core/contracts/LedgerTransactionQueryTests.kt +++ b/core/src/test/kotlin/net/corda/core/transactions/LedgerTransactionQueryTests.kt @@ -1,8 +1,7 @@ -package net.corda.core.contracts +package net.corda.core.transactions +import net.corda.core.contracts.* import net.corda.core.identity.AbstractParty -import net.corda.core.transactions.LedgerTransaction -import net.corda.core.transactions.TransactionBuilder import net.corda.testing.* import net.corda.testing.contracts.DummyContract import net.corda.testing.node.MockServices diff --git a/core/src/test/kotlin/net/corda/core/contracts/TransactionEncumbranceTests.kt b/core/src/test/kotlin/net/corda/core/transactions/TransactionEncumbranceTests.kt similarity index 96% rename from core/src/test/kotlin/net/corda/core/contracts/TransactionEncumbranceTests.kt rename to core/src/test/kotlin/net/corda/core/transactions/TransactionEncumbranceTests.kt index 8988d74a42..f96519dfec 100644 --- a/core/src/test/kotlin/net/corda/core/contracts/TransactionEncumbranceTests.kt +++ b/core/src/test/kotlin/net/corda/core/transactions/TransactionEncumbranceTests.kt @@ -1,7 +1,9 @@ -package net.corda.core.contracts +package net.corda.core.transactions +import net.corda.core.contracts.Contract +import net.corda.core.contracts.ContractState +import net.corda.core.contracts.requireThat import net.corda.core.identity.AbstractParty -import net.corda.core.transactions.LedgerTransaction import net.corda.finance.DOLLARS import net.corda.finance.`issued by` import net.corda.finance.contracts.asset.Cash @@ -12,7 +14,7 @@ import org.junit.Test import java.time.Instant import java.time.temporal.ChronoUnit -val TEST_TIMELOCK_ID = "net.corda.core.contracts.TransactionEncumbranceTests\$DummyTimeLock" +val TEST_TIMELOCK_ID = "net.corda.core.transactions.TransactionEncumbranceTests\$DummyTimeLock" class TransactionEncumbranceTests { val defaultIssuer = MEGA_CORP.ref(1) diff --git a/core/src/test/kotlin/net/corda/core/contracts/TransactionTests.kt b/core/src/test/kotlin/net/corda/core/transactions/TransactionTests.kt similarity index 97% rename from core/src/test/kotlin/net/corda/core/contracts/TransactionTests.kt rename to core/src/test/kotlin/net/corda/core/transactions/TransactionTests.kt index 26878fea9c..17cc1bb769 100644 --- a/core/src/test/kotlin/net/corda/core/contracts/TransactionTests.kt +++ b/core/src/test/kotlin/net/corda/core/transactions/TransactionTests.kt @@ -1,11 +1,9 @@ -package net.corda.core.contracts +package net.corda.core.transactions +import net.corda.core.contracts.* import net.corda.core.crypto.* import net.corda.core.crypto.CompositeKey import net.corda.core.identity.Party -import net.corda.core.transactions.LedgerTransaction -import net.corda.core.transactions.SignedTransaction -import net.corda.core.transactions.WireTransaction import net.corda.finance.contracts.asset.DUMMY_CASH_ISSUER_KEY import net.corda.testing.* import net.corda.testing.contracts.DummyContract diff --git a/docs/source/azure-vm.rst b/docs/source/azure-vm.rst index 7f94f0fdcb..ac8923bed1 100644 --- a/docs/source/azure-vm.rst +++ b/docs/source/azure-vm.rst @@ -3,7 +3,7 @@ Building a Corda Network on Azure Marketplace To help you design, build and test applications on Corda, called CorDapps, a Corda network can be deployed on the `Microsoft Azure Marketplace `_ -This Corda network offering builds a pre-configured network of Corda nodes as Ubuntu virtual machines (VM). The network comprises of a Network Map Service node, a Notary node and up to nine Corda nodes using a version of Corda of your choosing. The following guide will also show you how to load a simple Yo! CorDapp which demonstrates the basic principles of Corda. When you are ready to go further with developing on Corda and start making contributions to the project head over to the `Corda.net `_. +This Corda network offering builds a pre-configured network of Corda nodes as Ubuntu virtual machines (VM). The network comprises of a Notary node and up to nine Corda nodes using a version of Corda of your choosing. The following guide will also show you how to load a simple Yo! CorDapp which demonstrates the basic principles of Corda. When you are ready to go further with developing on Corda and start making contributions to the project head over to the `Corda.net `_. Pre-requisites -------------- diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 1da092f055..08cb43e02a 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -8,6 +8,8 @@ UNRELEASED ---------- * ``ConfigUtilities`` now read system properties for a node. This allow to specify data source properties at runtime. +* ``CordaRPCOps`` implementation now checks permissions for any function invocation, rather than just when starting flows. + * ``OpaqueBytes.bytes`` now returns a clone of its underlying ``ByteArray``, and has been redeclared as ``final``. This is a minor change to the public API, but is required to ensure that classes like ``SecureHash`` are immutable. @@ -72,6 +74,9 @@ UNRELEASED * The ``ReceiveTransactionFlow`` can now be told to record the transaction at the same time as receiving it. Using this feature, better support for observer/regulator nodes has been added. See :doc:`tutorial-observer-nodes`. +* Moved ``NodeInfoSchema`` to internal package as the node info's database schema is not part of the public API. This is + needed to allow new ``node_info_hash`` column to be added for the network map redesign work. + .. _changelog_v1: Release 1.0 diff --git a/docs/source/clientrpc.rst b/docs/source/clientrpc.rst index 14695b7943..14d29ba9b9 100644 --- a/docs/source/clientrpc.rst +++ b/docs/source/clientrpc.rst @@ -41,7 +41,7 @@ The syntax for adding an RPC user is: ... ] -Currently, users need special permissions to start flows via RPC. These permissions are added as follows: +Users need permissions to invoke any RPC call. By default, nothing is allowed. These permissions are specified as follows: .. container:: codeset @@ -62,6 +62,15 @@ Currently, users need special permissions to start flows via RPC. These permissi .. note:: Currently, the node's web server has super-user access, meaning that it can run any RPC operation without logging in. This will be changed in a future release. +Permissions Syntax +^^^^^^^^^^^^^^^^^^ + +Fine grained permissions allow a user to invoke a specific RPC operation, or to start a specific flow. The syntax is: + +- to start a specific flow: ``StartFlow.`` e.g., ``StartFlow.net.corda.flows.ExampleFlow1``. +- to invoke a RPC operation: ``InvokeRpc.`` e.g., ``InvokeRpc.nodeInfo``. +.. note:: Permission ``InvokeRpc.startFlow`` allows a user to initiate all flows. + Observables ----------- The RPC system handles observables in a special way. When a method returns an observable, whether directly or diff --git a/docs/source/corda-configuration-file.rst b/docs/source/corda-configuration-file.rst index 74b476b0a0..c28a39742c 100644 --- a/docs/source/corda-configuration-file.rst +++ b/docs/source/corda-configuration-file.rst @@ -118,10 +118,6 @@ path to the node's base directory. Only one of ``raft``, ``bftSMaRt`` or ``custom`` configuration values may be specified. -:minimumPlatformVersion: Used by the node if it's running the network map service to enforce a minimum version requirement - on registrations - any node on a Platform Version lower than this value will have their registration rejected. - Defaults to 1 if absent. - :useHTTPS: If false the node's web server will be plain HTTP. If true the node will use the same certificate and private key from the ``/certificates/sslkeystore.jks`` file as the ArtemisMQ port for HTTPS. If HTTPS is enabled then unencrypted HTTP traffic to the node's **webAddress** port is not supported. diff --git a/docs/source/demobench.rst b/docs/source/demobench.rst index 374f12234a..24b2948806 100644 --- a/docs/source/demobench.rst +++ b/docs/source/demobench.rst @@ -16,7 +16,7 @@ Running DemoBench Configuring a Node Each node must have a unique name to identify it to the network map service. DemoBench will suggest node names, nearest cities and local port numbers to use. - The first node will host the network map service, and we are forcing that node also to be a notary. Hence only notary services will be available to be selected in the ``Services`` list. For subsequent nodes you may also select any of Corda's other built-in services. + The first node will be a notary. Hence only notary services will be available to be selected in the ``Services`` list. For subsequent nodes you may also select any of Corda's other built-in services. Press the ``Start node`` button to launch the Corda node with your configuration. diff --git a/docs/source/deploying-a-node.rst b/docs/source/deploying-a-node.rst index 74f8bdac65..a9187ed6ed 100644 --- a/docs/source/deploying-a-node.rst +++ b/docs/source/deploying-a-node.rst @@ -41,11 +41,12 @@ notary node: cordapps = [] rpcUsers = [[ user: "user1", "password": "test", "permissions": []]] } + // Example of explicit addresses being used. node { name "CN=NodeC,O=NodeC,L=Paris,C=FR" - p2pPort 10011 - rpcPort 10012 - webPort 10013 + p2pAddress "localhost:10011" + rpcAddress "localhost:10012" + webAddress "localhost:10013" cordapps = [] rpcUsers = [[ user: "user1", "password": "test", "permissions": []]] } diff --git a/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt b/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt index c665a8cb67..9b192038bf 100644 --- a/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt +++ b/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt @@ -1,6 +1,7 @@ package net.corda.docs import net.corda.core.internal.concurrent.transpose +import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.startFlow import net.corda.core.messaging.vaultTrackBy import net.corda.core.node.services.Vault @@ -10,7 +11,8 @@ import net.corda.finance.DOLLARS import net.corda.finance.contracts.asset.Cash import net.corda.finance.flows.CashIssueFlow import net.corda.finance.flows.CashPaymentFlow -import net.corda.node.services.FlowPermissions.Companion.startFlowPermission +import net.corda.node.services.Permissions.Companion.invokeRpc +import net.corda.node.services.Permissions.Companion.startFlow import net.corda.nodeapi.User import net.corda.testing.* import net.corda.testing.driver.driver @@ -24,17 +26,22 @@ class IntegrationTestingTutorial { driver(startNodesInProcess = true, extraCordappPackagesToScan = listOf("net.corda.finance.contracts.asset")) { val aliceUser = User("aliceUser", "testPassword1", permissions = setOf( - startFlowPermission(), - startFlowPermission() + startFlow(), + startFlow(), + invokeRpc("vaultTrackBy"), + invokeRpc(CordaRPCOps::notaryIdentities), + invokeRpc(CordaRPCOps::networkMapFeed) )) val bobUser = User("bobUser", "testPassword2", permissions = setOf( - startFlowPermission() + startFlow(), + invokeRpc("vaultTrackBy"), + invokeRpc(CordaRPCOps::networkMapFeed) )) val (alice, bob) = listOf( startNode(providedName = ALICE.name, rpcUsers = listOf(aliceUser)), - startNode(providedName = BOB.name, rpcUsers = listOf(bobUser)), - startNotaryNode(DUMMY_NOTARY.name) + startNode(providedName = BOB.name, rpcUsers = listOf(bobUser)) ).transpose().getOrThrow() + // END 1 // START 2 @@ -43,9 +50,6 @@ class IntegrationTestingTutorial { val bobClient = bob.rpcClientToNode() val bobProxy = bobClient.start("bobUser", "testPassword2").proxy - - aliceProxy.waitUntilNetworkReady().getOrThrow() - bobProxy.waitUntilNetworkReady().getOrThrow() // END 2 // START 3 diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/ClientRpcTutorial.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/ClientRpcTutorial.kt index 5b11d79549..1f20376fbd 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/ClientRpcTutorial.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/ClientRpcTutorial.kt @@ -1,3 +1,5 @@ +@file:Suppress("unused") + package net.corda.docs import net.corda.core.contracts.Amount @@ -8,16 +10,15 @@ import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.SerializationWhitelist import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.OpaqueBytes -import net.corda.core.utilities.getOrThrow import net.corda.finance.USD import net.corda.finance.contracts.asset.Cash import net.corda.finance.flows.CashExitFlow import net.corda.finance.flows.CashIssueFlow import net.corda.finance.flows.CashPaymentFlow -import net.corda.node.services.FlowPermissions.Companion.startFlowPermission +import net.corda.node.services.Permissions.Companion.invokeRpc +import net.corda.node.services.Permissions.Companion.startFlow import net.corda.nodeapi.User import net.corda.testing.ALICE -import net.corda.testing.DUMMY_NOTARY import net.corda.testing.driver.driver import org.graphstream.graph.Edge import org.graphstream.graph.Node @@ -42,19 +43,18 @@ fun main(args: Array) { val printOrVisualise = PrintOrVisualise.valueOf(args[0]) val baseDirectory = Paths.get("build/rpc-api-tutorial") - val user = User("user", "password", permissions = setOf(startFlowPermission(), - startFlowPermission(), - startFlowPermission())) - + val user = User("user", "password", permissions = setOf(startFlow(), + startFlow(), + startFlow(), + invokeRpc(CordaRPCOps::nodeInfo) + )) driver(driverDirectory = baseDirectory, extraCordappPackagesToScan = listOf("net.corda.finance")) { - startNotaryNode(DUMMY_NOTARY.name) val node = startNode(providedName = ALICE.name, rpcUsers = listOf(user)).get() // END 1 // START 2 val client = node.rpcClientToNode() val proxy = client.start("user", "password").proxy - proxy.waitUntilNetworkReady().getOrThrow() thread { generateTransactions(proxy) @@ -143,4 +143,4 @@ class ExampleRPCSerializationWhitelist : SerializationWhitelist { // Add classes like this. override val whitelist = listOf(ExampleRPCValue::class.java) } -// END 7 +// END 7 \ No newline at end of file diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/mocknetwork/TutorialMockNetwork.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/mocknetwork/TutorialMockNetwork.kt index 9e86b57aab..56d33e5676 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/mocknetwork/TutorialMockNetwork.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/mocknetwork/TutorialMockNetwork.kt @@ -55,7 +55,6 @@ class TutorialMockNetwork { } lateinit private var mockNet: MockNetwork - lateinit private var notary: StartedNode lateinit private var nodeA: StartedNode lateinit private var nodeB: StartedNode @@ -66,7 +65,6 @@ class TutorialMockNetwork { @Before fun setUp() { mockNet = MockNetwork() - notary = mockNet.createNotaryNode() nodeA = mockNet.createPartyNode() nodeB = mockNet.createPartyNode() diff --git a/docs/source/example-code/src/main/resources/example-out-of-process-verifier-node.conf b/docs/source/example-code/src/main/resources/example-out-of-process-verifier-node.conf index 8d43f51a9f..27bf43a1ab 100644 --- a/docs/source/example-code/src/main/resources/example-out-of-process-verifier-node.conf +++ b/docs/source/example-code/src/main/resources/example-out-of-process-verifier-node.conf @@ -1,8 +1,4 @@ myLegalName : "O=Bank A,L=London,C=GB" p2pAddress : "my-corda-node:10002" webAddress : "localhost:10003" -networkMapService : { - address : "my-network-map:10000" - legalName : "O=Network Map Service,OU=corda,L=London,C=GB" -} verifierType: "OutOfProcess" diff --git a/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt b/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt index a4639256f3..0973b3541b 100644 --- a/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt +++ b/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt @@ -10,7 +10,8 @@ import net.corda.finance.contracts.getCashBalances import net.corda.finance.flows.CashIssueFlow import net.corda.finance.schemas.CashSchemaV1 import net.corda.node.internal.StartedNode -import net.corda.testing.* +import net.corda.testing.chooseIdentity +import net.corda.testing.getDefaultNotary import net.corda.testing.node.MockNetwork import org.junit.After import org.junit.Assert @@ -19,23 +20,20 @@ import org.junit.Test import java.util.* class CustomVaultQueryTest { - - lateinit var mockNet: MockNetwork - lateinit var nodeA: StartedNode - lateinit var nodeB: StartedNode - lateinit var notary: Party + private lateinit var mockNet: MockNetwork + private lateinit var nodeA: StartedNode + private lateinit var nodeB: StartedNode + private lateinit var notary: Party @Before fun setup() { - mockNet = MockNetwork( - threadPerNode = true, + mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf( "net.corda.finance.contracts.asset", CashSchemaV1::class.packageName, "net.corda.docs" ) ) - mockNet.createNotaryNode() nodeA = mockNet.createPartyNode() nodeB = mockNet.createPartyNode() nodeA.internals.registerInitiatedFlow(TopupIssuerFlow.TopupIssuer::class.java) diff --git a/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt b/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt index 5e31ac39ba..cb2a8f29eb 100644 --- a/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt +++ b/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt @@ -10,7 +10,8 @@ import net.corda.finance.contracts.getCashBalances import net.corda.finance.flows.CashIssueFlow import net.corda.finance.schemas.CashSchemaV1 import net.corda.node.internal.StartedNode -import net.corda.testing.* +import net.corda.testing.chooseIdentity +import net.corda.testing.getDefaultNotary import net.corda.testing.node.MockNetwork import org.junit.After import org.junit.Before @@ -18,15 +19,14 @@ import org.junit.Test import kotlin.test.assertEquals class FxTransactionBuildTutorialTest { - lateinit var mockNet: MockNetwork - lateinit var nodeA: StartedNode - lateinit var nodeB: StartedNode - lateinit var notary: Party + private lateinit var mockNet: MockNetwork + private lateinit var nodeA: StartedNode + private lateinit var nodeB: StartedNode + private lateinit var notary: Party @Before fun setup() { mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.finance.contracts.asset", CashSchemaV1::class.packageName)) - mockNet.createNotaryNode() nodeA = mockNet.createPartyNode() nodeB = mockNet.createPartyNode() nodeB.internals.registerInitiatedFlow(ForeignExchangeRemoteFlow::class.java) diff --git a/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt b/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt index 9f98082730..1cbd83a282 100644 --- a/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt +++ b/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt @@ -10,7 +10,8 @@ import net.corda.core.node.services.vault.QueryCriteria import net.corda.core.toFuture import net.corda.core.utilities.getOrThrow import net.corda.node.services.api.StartedNodeServices -import net.corda.testing.* +import net.corda.testing.ALICE_NAME +import net.corda.testing.BOB_NAME import net.corda.testing.node.MockNetwork import org.junit.After import org.junit.Before @@ -18,11 +19,11 @@ import org.junit.Test import kotlin.test.assertEquals class WorkflowTransactionBuildTutorialTest { - lateinit var mockNet: MockNetwork - lateinit var aliceServices: StartedNodeServices - lateinit var bobServices: StartedNodeServices - lateinit var alice: Party - lateinit var bob: Party + private lateinit var mockNet: MockNetwork + private lateinit var aliceServices: StartedNodeServices + private lateinit var bobServices: StartedNodeServices + private lateinit var alice: Party + private lateinit var bob: Party // Helper method to locate the latest Vault version of a LinearState private inline fun ServiceHub.latest(ref: UniqueIdentifier): StateAndRef { @@ -33,8 +34,6 @@ class WorkflowTransactionBuildTutorialTest { @Before fun setup() { mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.docs")) - // While we don't use the notary, we need there to be one on the network - mockNet.createNotaryNode() val aliceNode = mockNet.createPartyNode(ALICE_NAME) val bobNode = mockNet.createPartyNode(BOB_NAME) aliceNode.internals.registerInitiatedFlow(RecordCompletionFlow::class.java) diff --git a/docs/source/messaging.rst b/docs/source/messaging.rst index 2bd301d3bc..55f48418d3 100644 --- a/docs/source/messaging.rst +++ b/docs/source/messaging.rst @@ -65,11 +65,6 @@ for maintenance and other minor purposes. corresponding bridge is used to forward the message to an advertising peer's p2p queue. Once a peer is picked the session continues on as normal. -:``internal.networkmap``: - This is another private queue just for the node which functions in a similar manner to the ``internal.peers.*`` queues - except this is used to form a connection to the network map node. The node running the network map service is treated - differently as it provides information about the rest of the network. - :``rpc.requests``: RPC clients send their requests here, and it's only open for sending by clients authenticated as RPC users. diff --git a/docs/source/node-services.rst b/docs/source/node-services.rst index b63dbfc942..edcf20b391 100644 --- a/docs/source/node-services.rst +++ b/docs/source/node-services.rst @@ -139,28 +139,17 @@ physical host and port information required for the physical ``ArtemisMQ`` messaging layer. -PersistentNetworkMapService and NetworkMapService -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +PersistentNetworkMapService +~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The ``NetworkMapService`` is a node internal component responsible for -managing and communicating the directory of authenticated registered -nodes and advertised services in the Corda network. Only a single node -in the network (in future this will be a clustered service) should host -the NetworkMapService implementation. All other Corda nodes initiate -their remote connection to the ``NetworkMapService`` early in the -start-up sequence and wait to synchronise their local -``NetworkMapCache`` before activating any flows. For the -``PersistentNetworkMapService`` registered ``NodeInfo`` data is -persisted and will include nodes that are not currently active. The -networking layer will persist any messages directed at such inactive +The ``PersistentNetworkMapService`` keeps track of ``NodeInfo`` and +persists it to the database. It and will include nodes that are not currently active. +The networking layer will persist any messages directed at such inactive nodes with the expectation that they will be delivered eventually, or else that the source flow will be terminated by admin intervention. An ``InMemoryNetworkMapService`` is also available for unit tests without a database. -The ``NetworkMapService`` should not be used by any flows, or -contracts. Instead they should access the NetworkMapCache service to -access this data. Storage and persistence related services ---------------------------------------- diff --git a/docs/source/setting-up-a-corda-network.rst b/docs/source/setting-up-a-corda-network.rst index 18a2172058..41a44d300f 100644 --- a/docs/source/setting-up-a-corda-network.rst +++ b/docs/source/setting-up-a-corda-network.rst @@ -3,8 +3,7 @@ Creating a Corda network ======================== -A Corda network consists of a number of machines running nodes, including a single node operating as the network map -service. These nodes communicate using persistent protocols in order to create and validate transactions. +A Corda network consists of a number of machines running nodes. These nodes communicate using persistent protocols in order to create and validate transactions. There are four broader categories of functionality one such node may have. These pieces of functionality are provided as services, and one node may run several of them. diff --git a/docs/source/tutorial-clientrpc-api.rst b/docs/source/tutorial-clientrpc-api.rst index aa49889b4f..66c9846dae 100644 --- a/docs/source/tutorial-clientrpc-api.rst +++ b/docs/source/tutorial-clientrpc-api.rst @@ -122,7 +122,7 @@ In the instructions above the server node permissions are configured programmati .. code-block:: text driver(driverDirectory = baseDirectory) { - val user = User("user", "password", permissions = setOf(startFlowPermission())) + val user = User("user", "password", permissions = setOf(startFlow())) val node = startNode("CN=Alice Corp,O=Alice Corp,L=London,C=GB", rpcUsers = listOf(user)).get() When starting a standalone node using a configuration file we must supply the RPC credentials as follows: diff --git a/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashSelectionH2Test.kt b/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashSelectionH2Test.kt index 2a69ac0631..5dcde25664 100644 --- a/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashSelectionH2Test.kt +++ b/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashSelectionH2Test.kt @@ -1,41 +1,38 @@ package net.corda.finance.contracts.asset -import net.corda.core.internal.packageName import net.corda.core.utilities.getOrThrow import net.corda.finance.DOLLARS import net.corda.finance.flows.CashException import net.corda.finance.flows.CashPaymentFlow -import net.corda.finance.schemas.CashSchemaV1 -import net.corda.testing.chooseIdentity import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNodeParameters import org.assertj.core.api.Assertions.assertThatThrownBy +import org.junit.After import org.junit.Test - class CashSelectionH2Test { + private val mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.finance")) + + @After + fun cleanUp() { + mockNet.stopNodes() + } @Test fun `check does not hold connection over retries`() { - val mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.finance.contracts.asset", CashSchemaV1::class.packageName)) - try { - val notaryNode = mockNet.createNotaryNode() - val bankA = mockNet.createNode(MockNodeParameters(configOverrides = { existingConfig -> - // Tweak connections to be minimal to make this easier (1 results in a hung node during start up, so use 2 connections). - existingConfig.dataSourceProperties.setProperty("maximumPoolSize", "2") - })) - mockNet.startNodes() + val bankA = mockNet.createNode(MockNodeParameters(configOverrides = { + // Tweak connections to be minimal to make this easier (1 results in a hung node during start up, so use 2 connections). + it.dataSourceProperties.setProperty("maximumPoolSize", "2") + })) + val notary = mockNet.defaultNotaryIdentity - // Start more cash spends than we have connections. If spend leaks a connection on retry, we will run out of connections. - val flow1 = bankA.services.startFlow(CashPaymentFlow(amount = 100.DOLLARS, anonymous = false, recipient = notaryNode.info.chooseIdentity())) - val flow2 = bankA.services.startFlow(CashPaymentFlow(amount = 100.DOLLARS, anonymous = false, recipient = notaryNode.info.chooseIdentity())) - val flow3 = bankA.services.startFlow(CashPaymentFlow(amount = 100.DOLLARS, anonymous = false, recipient = notaryNode.info.chooseIdentity())) + // Start more cash spends than we have connections. If spend leaks a connection on retry, we will run out of connections. + val flow1 = bankA.services.startFlow(CashPaymentFlow(amount = 100.DOLLARS, anonymous = false, recipient = notary)) + val flow2 = bankA.services.startFlow(CashPaymentFlow(amount = 100.DOLLARS, anonymous = false, recipient = notary)) + val flow3 = bankA.services.startFlow(CashPaymentFlow(amount = 100.DOLLARS, anonymous = false, recipient = notary)) - assertThatThrownBy { flow1.resultFuture.getOrThrow() }.isInstanceOf(CashException::class.java) - assertThatThrownBy { flow2.resultFuture.getOrThrow() }.isInstanceOf(CashException::class.java) - assertThatThrownBy { flow3.resultFuture.getOrThrow() }.isInstanceOf(CashException::class.java) - } finally { - mockNet.stopNodes() - } + assertThatThrownBy { flow1.resultFuture.getOrThrow() }.isInstanceOf(CashException::class.java) + assertThatThrownBy { flow2.resultFuture.getOrThrow() }.isInstanceOf(CashException::class.java) + assertThatThrownBy { flow3.resultFuture.getOrThrow() }.isInstanceOf(CashException::class.java) } } \ No newline at end of file diff --git a/finance/src/test/kotlin/net/corda/finance/flows/CashExitFlowTests.kt b/finance/src/test/kotlin/net/corda/finance/flows/CashExitFlowTests.kt index 94b3f83658..6483229909 100644 --- a/finance/src/test/kotlin/net/corda/finance/flows/CashExitFlowTests.kt +++ b/finance/src/test/kotlin/net/corda/finance/flows/CashExitFlowTests.kt @@ -7,7 +7,9 @@ import net.corda.finance.DOLLARS import net.corda.finance.`issued by` import net.corda.finance.contracts.asset.Cash import net.corda.node.internal.StartedNode -import net.corda.testing.* +import net.corda.testing.BOC +import net.corda.testing.chooseIdentity +import net.corda.testing.getDefaultNotary import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNetwork.MockNode @@ -23,15 +25,13 @@ class CashExitFlowTests { private val ref = OpaqueBytes.of(0x01) private lateinit var bankOfCordaNode: StartedNode private lateinit var bankOfCorda: Party - private lateinit var notaryNode: StartedNode private lateinit var notary: Party @Before fun start() { - mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin(), cordappPackages = listOf("net.corda.finance.contracts.asset")) - notaryNode = mockNet.createNotaryNode() + mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin(), + cordappPackages = listOf("net.corda.finance.contracts.asset")) bankOfCordaNode = mockNet.createPartyNode(BOC.name) - notary = notaryNode.services.getDefaultNotary() bankOfCorda = bankOfCordaNode.info.chooseIdentity() mockNet.runNetwork() diff --git a/finance/src/test/kotlin/net/corda/finance/flows/CashIssueFlowTests.kt b/finance/src/test/kotlin/net/corda/finance/flows/CashIssueFlowTests.kt index 911a014d5f..552ebe0ebd 100644 --- a/finance/src/test/kotlin/net/corda/finance/flows/CashIssueFlowTests.kt +++ b/finance/src/test/kotlin/net/corda/finance/flows/CashIssueFlowTests.kt @@ -7,9 +7,8 @@ import net.corda.finance.DOLLARS import net.corda.finance.`issued by` import net.corda.finance.contracts.asset.Cash import net.corda.node.internal.StartedNode -import net.corda.testing.chooseIdentity -import net.corda.testing.getDefaultNotary import net.corda.testing.BOC +import net.corda.testing.chooseIdentity import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNetwork.MockNode @@ -23,16 +22,14 @@ class CashIssueFlowTests { private lateinit var mockNet: MockNetwork private lateinit var bankOfCordaNode: StartedNode private lateinit var bankOfCorda: Party - private lateinit var notaryNode: StartedNode private lateinit var notary: Party @Before fun start() { mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin(), cordappPackages = listOf("net.corda.finance.contracts.asset")) - notaryNode = mockNet.createNotaryNode() bankOfCordaNode = mockNet.createPartyNode(BOC.name) bankOfCorda = bankOfCordaNode.info.chooseIdentity() - notary = notaryNode.services.getDefaultNotary() + notary = mockNet.defaultNotaryIdentity mockNet.runNetwork() } diff --git a/finance/src/test/kotlin/net/corda/finance/flows/CashPaymentFlowTests.kt b/finance/src/test/kotlin/net/corda/finance/flows/CashPaymentFlowTests.kt index 0c5b1e5efa..c515c886ed 100644 --- a/finance/src/test/kotlin/net/corda/finance/flows/CashPaymentFlowTests.kt +++ b/finance/src/test/kotlin/net/corda/finance/flows/CashPaymentFlowTests.kt @@ -26,18 +26,16 @@ class CashPaymentFlowTests { private val ref = OpaqueBytes.of(0x01) private lateinit var bankOfCordaNode: StartedNode private lateinit var bankOfCorda: Party - private lateinit var notaryNode: StartedNode - private lateinit var notary: Party + private lateinit var aliceNode: StartedNode @Before fun start() { mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin(), cordappPackages = listOf("net.corda.finance.contracts.asset")) - notaryNode = mockNet.createNotaryNode() bankOfCordaNode = mockNet.createPartyNode(BOC.name) + aliceNode = mockNet.createPartyNode(ALICE.name) bankOfCorda = bankOfCordaNode.info.chooseIdentity() - notary = notaryNode.services.getDefaultNotary() - val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance, ref, notary)).resultFuture mockNet.runNetwork() + val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance, ref, mockNet.defaultNotaryIdentity)).resultFuture future.getOrThrow() } @@ -48,7 +46,7 @@ class CashPaymentFlowTests { @Test fun `pay some cash`() { - val payTo = notaryNode.info.chooseIdentity() + val payTo = aliceNode.info.chooseIdentity() val expectedPayment = 500.DOLLARS val expectedChange = 1500.DOLLARS @@ -56,7 +54,7 @@ class CashPaymentFlowTests { // Register for vault updates val criteria = QueryCriteria.VaultQueryCriteria(status = Vault.StateStatus.ALL) val (_, vaultUpdatesBoc) = bankOfCordaNode.services.vaultService.trackBy(criteria) - val (_, vaultUpdatesBankClient) = notaryNode.services.vaultService.trackBy(criteria) + val (_, vaultUpdatesBankClient) = aliceNode.services.vaultService.trackBy(criteria) val future = bankOfCordaNode.services.startFlow(CashPaymentFlow(expectedPayment, payTo)).resultFuture @@ -88,7 +86,7 @@ class CashPaymentFlowTests { @Test fun `pay more than we have`() { - val payTo = notaryNode.info.chooseIdentity() + val payTo = aliceNode.info.chooseIdentity() val expected = 4000.DOLLARS val future = bankOfCordaNode.services.startFlow(CashPaymentFlow(expected, payTo)).resultFuture @@ -100,7 +98,7 @@ class CashPaymentFlowTests { @Test fun `pay zero cash`() { - val payTo = notaryNode.info.chooseIdentity() + val payTo = aliceNode.info.chooseIdentity() val expected = 0.DOLLARS val future = bankOfCordaNode.services.startFlow(CashPaymentFlow(expected, payTo)).resultFuture diff --git a/gradle-plugins/cordapp/src/main/kotlin/net/corda/plugins/CordappPlugin.kt b/gradle-plugins/cordapp/src/main/kotlin/net/corda/plugins/CordappPlugin.kt index e307a7b162..624194fd32 100644 --- a/gradle-plugins/cordapp/src/main/kotlin/net/corda/plugins/CordappPlugin.kt +++ b/gradle-plugins/cordapp/src/main/kotlin/net/corda/plugins/CordappPlugin.kt @@ -61,7 +61,8 @@ class CordappPlugin : Plugin { } filteredDeps.forEach { // net.corda or com.r3.corda.enterprise may be a core dependency which shouldn't be included in this cordapp so give a warning - if ((it.group.startsWith("net.corda.") || it.group.startsWith("com.r3.corda.enterprise."))) { + val group = it.group?.toString() ?: "" + if (group.startsWith("net.corda.") || group.startsWith("com.r3.corda.enterprise.")) { project.logger.warn("You appear to have included a Corda platform component ($it) using a 'compile' or 'runtime' dependency." + "This can cause node stability problems. Please use 'corda' instead." + "See http://docs.corda.net/cordapp-build-systems.html") diff --git a/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/CordformNode.java b/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/CordformNode.java index 7421fee251..f8c4248f99 100644 --- a/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/CordformNode.java +++ b/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/CordformNode.java @@ -58,7 +58,16 @@ public class CordformNode implements NodeDefinition { } /** - * Set the Artemis P2P port for this node. + * Get the artemis address for this node. + * + * @return This node's P2P address. + */ + public String getP2pAddress() { + return config.getString("p2pAddress"); + } + + /** + * Set the Artemis P2P port for this node on localhost. * * @param p2pPort The Artemis messaging queue port. */ @@ -67,7 +76,16 @@ public class CordformNode implements NodeDefinition { } /** - * Set the Artemis RPC port for this node. + * Set the Artemis P2P address for this node. + * + * @param p2pAddress The Artemis messaging queue host and port. + */ + public void p2pAddress(String p2pAddress) { + config = config.withValue("p2pAddress", ConfigValueFactory.fromAnyRef(p2pAddress)); + } + + /** + * Set the Artemis RPC port for this node on localhost. * * @param rpcPort The Artemis RPC queue port. */ @@ -75,6 +93,15 @@ public class CordformNode implements NodeDefinition { config = config.withValue("rpcAddress", ConfigValueFactory.fromAnyRef(DEFAULT_HOST + ':' + rpcPort)); } + /** + * Set the Artemis RPC address for this node. + * + * @param rpcAddress The Artemis RPC queue host and port. + */ + public void rpcAddress(String rpcAddress) { + config = config.withValue("rpcAddress", ConfigValueFactory.fromAnyRef(rpcAddress)); + } + /** * Set the path to a file with optional properties, which are appended to the generated node.conf file. * diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt index d08016bf3c..266ae6434c 100644 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt @@ -61,7 +61,7 @@ class Node(private val project: Project) : CordformNode() { } /** - * Set the HTTP web server port for this node. + * Set the HTTP web server port for this node. Will use localhost as the address. * * @param webPort The web port number for this node. */ @@ -70,6 +70,16 @@ class Node(private val project: Project) : CordformNode() { ConfigValueFactory.fromAnyRef("$DEFAULT_HOST:$webPort")) } + /** + * Set the HTTP web server address and port for this node. + * + * @param webAddress The web address for this node. + */ + fun webAddress(webAddress: String) { + config = config.withValue("webAddress", + ConfigValueFactory.fromAnyRef(webAddress)) + } + /** * Set the network map address for this node. * @@ -107,15 +117,6 @@ class Node(private val project: Project) : CordformNode() { appendOptionalConfig() } - /** - * Get the artemis address for this node. - * - * @return This node's P2P address. - */ - fun getP2PAddress(): String { - return config.getString("p2pAddress") - } - internal fun rootDir(rootDir: Path) { if(name == null) { project.logger.error("Node has a null name - cannot create node") diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7a3265ee94..27768f1bba 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 383fa73d26..b05efff0b2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu Aug 24 12:32:31 BST 2017 +#Mon Nov 06 15:05:49 GMT 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-all.zip diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/ServiceInfo.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/ServiceInfo.kt deleted file mode 100644 index 25e3734165..0000000000 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/ServiceInfo.kt +++ /dev/null @@ -1,27 +0,0 @@ -package net.corda.nodeapi.internal - -import net.corda.core.identity.CordaX500Name -import net.corda.core.serialization.CordaSerializable - -/** - * A container for additional information for an advertised service. - * - * @param type the ServiceType identifier - * @param name the service name, used for differentiating multiple services of the same type. Can also be used as a - * grouping identifier for nodes collectively running a distributed service. - */ -@CordaSerializable -data class ServiceInfo(val type: ServiceType, val name: CordaX500Name? = null) { - companion object { - fun parse(encoded: String): ServiceInfo { - val parts = encoded.split("|") - require(parts.size in 1..2) { "Invalid number of elements found" } - val type = ServiceType.parse(parts[0]) - val name = parts.getOrNull(1) - val principal = name?.let { CordaX500Name.parse(it) } - return ServiceInfo(type, principal) - } - } - - override fun toString() = if (name != null) "$type|$name" else type.toString() -} diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/ServiceType.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/ServiceType.kt deleted file mode 100644 index 7ec0a1ce8f..0000000000 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/ServiceType.kt +++ /dev/null @@ -1,44 +0,0 @@ -package net.corda.nodeapi.internal - -import net.corda.core.serialization.CordaSerializable - -/** - * Identifier for service types a node can expose over the network to other peers. These types are placed into network - * map advertisements. Services that are purely local and are not providing functionality to other parts of the network - * don't need a declared service type. - */ -@CordaSerializable -class ServiceType private constructor(val id: String) { - init { - // Enforce: - // - // * IDs must start with a lower case letter - // * IDs can only contain alphanumeric, full stop and underscore ASCII characters - require(id.matches(Regex("[a-z][a-zA-Z0-9._]+"))) { id } - } - - companion object { - val corda: ServiceType - get() { - val stack = Throwable().stackTrace - val caller = stack.first().className - require(caller.startsWith("net.corda.")) { "Corda ServiceType namespace is reserved for Corda core components" } - return ServiceType("corda") - } - - val notary: ServiceType = corda.getSubType("notary") - - fun parse(id: String): ServiceType = ServiceType(id) - - private fun baseWithSubType(baseId: String, subTypeId: String) = ServiceType("$baseId.$subTypeId") - } - - fun getSubType(subTypeId: String): ServiceType = baseWithSubType(id, subTypeId) - - fun isSubTypeOf(superType: ServiceType) = (id == superType.id) || id.startsWith(superType.id + ".") - fun isNotary() = isSubTypeOf(notary) - - override fun equals(other: Any?): Boolean = other === this || other is ServiceType && other.id == this.id - override fun hashCode(): Int = id.hashCode() - override fun toString(): String = id -} diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/ClientContexts.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/ClientContexts.kt index bd6135522a..dc3712d56e 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/ClientContexts.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/ClientContexts.kt @@ -4,15 +4,25 @@ package net.corda.nodeapi.internal.serialization import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationDefaults +import net.corda.nodeapi.internal.serialization.amqp.AmqpHeaderV1_0 +import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1 /* * Serialisation contexts for the client. * These have been refactored into a separate file to prevent * servers from trying to instantiate any of them. */ + val KRYO_RPC_CLIENT_CONTEXT = SerializationContextImpl(KryoHeaderV0_1, SerializationDefaults.javaClass.classLoader, GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()), emptyMap(), true, SerializationContext.UseCase.RPCClient) + +val AMQP_RPC_CLIENT_CONTEXT = SerializationContextImpl(AmqpHeaderV1_0, + SerializationDefaults.javaClass.classLoader, + GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()), + emptyMap(), + true, + SerializationContext.UseCase.RPCClient) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolver.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolver.kt index 97c50dcd67..52690a1780 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolver.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolver.kt @@ -12,6 +12,7 @@ import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.SerializationContext import net.corda.core.utilities.loggerFor import net.corda.nodeapi.internal.serialization.amqp.hasAnnotationInHierarchy +import net.corda.nodeapi.internal.serialization.kryo.ThrowableSerializer import java.io.PrintWriter import java.lang.reflect.Modifier.isAbstract import java.nio.charset.StandardCharsets diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationScheme.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationScheme.kt index a6be235f88..304340f342 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationScheme.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationScheme.kt @@ -1,26 +1,13 @@ package net.corda.nodeapi.internal.serialization -import co.paralleluniverse.fibers.Fiber -import co.paralleluniverse.io.serialization.kryo.KryoSerializer -import com.esotericsoftware.kryo.Kryo -import com.esotericsoftware.kryo.KryoException -import com.esotericsoftware.kryo.Serializer -import com.esotericsoftware.kryo.io.Input -import com.esotericsoftware.kryo.io.Output -import com.esotericsoftware.kryo.pool.KryoPool -import com.esotericsoftware.kryo.serializers.ClosureSerializer import com.google.common.cache.Cache import com.google.common.cache.CacheBuilder import net.corda.core.contracts.Attachment import net.corda.core.crypto.SecureHash -import net.corda.core.internal.LazyPool -import net.corda.core.internal.uncheckedCast import net.corda.core.serialization.* import net.corda.core.utilities.ByteSequence -import net.corda.core.utilities.OpaqueBytes import net.corda.nodeapi.internal.AttachmentsClassLoader import org.slf4j.LoggerFactory -import java.io.ByteArrayOutputStream import java.io.NotSerializableException import java.util.* import java.util.concurrent.ConcurrentHashMap @@ -158,124 +145,8 @@ open class SerializationFactoryImpl : SerializationFactory() { override fun hashCode(): Int = registeredSchemes.hashCode() } -private object AutoCloseableSerialisationDetector : Serializer() { - override fun write(kryo: Kryo, output: Output, closeable: AutoCloseable) { - val message = "${closeable.javaClass.name}, which is a closeable resource, has been detected during flow checkpointing. " + - "Restoring such resources across node restarts is not supported. Make sure code accessing it is " + - "confined to a private method or the reference is nulled out." - throw UnsupportedOperationException(message) - } - - override fun read(kryo: Kryo, input: Input, type: Class) = throw IllegalStateException("Should not reach here!") -} - -abstract class AbstractKryoSerializationScheme : SerializationScheme { - private val kryoPoolsForContexts = ConcurrentHashMap, KryoPool>() - - protected abstract fun rpcClientKryoPool(context: SerializationContext): KryoPool - protected abstract fun rpcServerKryoPool(context: SerializationContext): KryoPool - - private fun getPool(context: SerializationContext): KryoPool { - return kryoPoolsForContexts.computeIfAbsent(Pair(context.whitelist, context.deserializationClassLoader)) { - when (context.useCase) { - SerializationContext.UseCase.Checkpoint -> - KryoPool.Builder { - val serializer = Fiber.getFiberSerializer(false) as KryoSerializer - val classResolver = CordaClassResolver(context).apply { setKryo(serializer.kryo) } - // TODO The ClassResolver can only be set in the Kryo constructor and Quasar doesn't provide us with a way of doing that - val field = Kryo::class.java.getDeclaredField("classResolver").apply { isAccessible = true } - serializer.kryo.apply { - field.set(this, classResolver) - DefaultKryoCustomizer.customize(this) - addDefaultSerializer(AutoCloseable::class.java, AutoCloseableSerialisationDetector) - register(ClosureSerializer.Closure::class.java, CordaClosureSerializer) - classLoader = it.second - } - }.build() - SerializationContext.UseCase.RPCClient -> - rpcClientKryoPool(context) - SerializationContext.UseCase.RPCServer -> - rpcServerKryoPool(context) - else -> - KryoPool.Builder { - DefaultKryoCustomizer.customize(CordaKryo(CordaClassResolver(context))).apply { classLoader = it.second } - }.build() - } - } - } - - private fun withContext(kryo: Kryo, context: SerializationContext, block: (Kryo) -> T): T { - kryo.context.ensureCapacity(context.properties.size) - context.properties.forEach { kryo.context.put(it.key, it.value) } - try { - return block(kryo) - } finally { - kryo.context.clear() - } - } - - override fun deserialize(byteSequence: ByteSequence, clazz: Class, context: SerializationContext): T { - val pool = getPool(context) - val headerSize = KryoHeaderV0_1.size - val header = byteSequence.take(headerSize) - if (header != KryoHeaderV0_1) { - throw KryoException("Serialized bytes header does not match expected format.") - } - Input(byteSequence.bytes, byteSequence.offset + headerSize, byteSequence.size - headerSize).use { input -> - return pool.run { kryo -> - withContext(kryo, context) { - if (context.objectReferencesEnabled) { - uncheckedCast(kryo.readClassAndObject(input)) - } else { - kryo.withoutReferences { uncheckedCast(kryo.readClassAndObject(input)) } - } - } - } - } - } - - override fun serialize(obj: T, context: SerializationContext): SerializedBytes { - val pool = getPool(context) - return pool.run { kryo -> - withContext(kryo, context) { - serializeOutputStreamPool.run { stream -> - serializeBufferPool.run { buffer -> - Output(buffer).use { - it.outputStream = stream - it.writeBytes(KryoHeaderV0_1.bytes) - if (context.objectReferencesEnabled) { - kryo.writeClassAndObject(it, obj) - } else { - kryo.withoutReferences { kryo.writeClassAndObject(it, obj) } - } - } - SerializedBytes(stream.toByteArray()) - } - } - } - } - } -} - -private val serializeBufferPool = LazyPool( - newInstance = { ByteArray(64 * 1024) } -) -private val serializeOutputStreamPool = LazyPool( - clear = ByteArrayOutputStream::reset, - shouldReturnToPool = { it.size() < 256 * 1024 }, // Discard if it grew too large - newInstance = { ByteArrayOutputStream(64 * 1024) } -) - -// "corda" + majorVersionByte + minorVersionMSB + minorVersionLSB -val KryoHeaderV0_1: OpaqueBytes = OpaqueBytes("corda\u0000\u0000\u0001".toByteArray(Charsets.UTF_8)) -val KRYO_P2P_CONTEXT = SerializationContextImpl(KryoHeaderV0_1, - SerializationDefaults.javaClass.classLoader, - GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()), - emptyMap(), - true, - SerializationContext.UseCase.P2P) interface SerializationScheme { // byteSequence expected to just be the 8 bytes necessary for versioning diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/ServerContexts.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/ServerContexts.kt index baab649669..7380cfdd0e 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/ServerContexts.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/ServerContexts.kt @@ -6,6 +6,11 @@ import net.corda.core.serialization.ClassWhitelist import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationDefaults import net.corda.nodeapi.internal.serialization.amqp.AmqpHeaderV1_0 +import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1 + +object QuasarWhitelist : ClassWhitelist { + override fun hasListed(type: Class<*>): Boolean = true +} /* * Serialisation contexts for the server. @@ -16,18 +21,28 @@ import net.corda.nodeapi.internal.serialization.amqp.AmqpHeaderV1_0 * CANNOT always be instantiated outside of the server and so * MUST be kept separate! */ + val KRYO_RPC_SERVER_CONTEXT = SerializationContextImpl(KryoHeaderV0_1, SerializationDefaults.javaClass.classLoader, GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()), emptyMap(), true, SerializationContext.UseCase.RPCServer) + val KRYO_STORAGE_CONTEXT = SerializationContextImpl(KryoHeaderV0_1, SerializationDefaults.javaClass.classLoader, AllButBlacklisted, emptyMap(), true, SerializationContext.UseCase.Storage) + +val KRYO_P2P_CONTEXT = SerializationContextImpl(KryoHeaderV0_1, + SerializationDefaults.javaClass.classLoader, + GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()), + emptyMap(), + true, + SerializationContext.UseCase.P2P) + val KRYO_CHECKPOINT_CONTEXT = SerializationContextImpl(KryoHeaderV0_1, SerializationDefaults.javaClass.classLoader, QuasarWhitelist, @@ -35,9 +50,6 @@ val KRYO_CHECKPOINT_CONTEXT = SerializationContextImpl(KryoHeaderV0_1, true, SerializationContext.UseCase.Checkpoint) -object QuasarWhitelist : ClassWhitelist { - override fun hasListed(type: Class<*>): Boolean = true -} val AMQP_STORAGE_CONTEXT = SerializationContextImpl(AmqpHeaderV1_0, SerializationDefaults.javaClass.classLoader, @@ -45,3 +57,18 @@ val AMQP_STORAGE_CONTEXT = SerializationContextImpl(AmqpHeaderV1_0, emptyMap(), true, SerializationContext.UseCase.Storage) + +val AMQP_P2P_CONTEXT = SerializationContextImpl(AmqpHeaderV1_0, + SerializationDefaults.javaClass.classLoader, + GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()), + emptyMap(), + true, + SerializationContext.UseCase.P2P) + +val AMQP_RPC_SERVER_CONTEXT = SerializationContextImpl(AmqpHeaderV1_0, + SerializationDefaults.javaClass.classLoader, + GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()), + emptyMap(), + true, + SerializationContext.UseCase.RPCServer) + diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/AMQPSerializationScheme.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializationScheme.kt similarity index 92% rename from node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/AMQPSerializationScheme.kt rename to node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializationScheme.kt index 501840e3df..2f888c70fe 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/AMQPSerializationScheme.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializationScheme.kt @@ -1,13 +1,12 @@ @file:JvmName("AMQPSerializationScheme") -package net.corda.nodeapi.internal.serialization +package net.corda.nodeapi.internal.serialization.amqp import net.corda.core.serialization.* import net.corda.core.utilities.ByteSequence -import net.corda.nodeapi.internal.serialization.amqp.AmqpHeaderV1_0 -import net.corda.nodeapi.internal.serialization.amqp.DeserializationInput -import net.corda.nodeapi.internal.serialization.amqp.SerializationOutput -import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory +import net.corda.nodeapi.internal.serialization.DefaultWhitelist +import net.corda.nodeapi.internal.serialization.MutableClassWhitelist +import net.corda.nodeapi.internal.serialization.SerializationScheme import java.util.* import java.util.concurrent.ConcurrentHashMap @@ -25,7 +24,7 @@ fun SerializerFactory.addToWhitelist(vararg types: Class<*>) { } abstract class AbstractAMQPSerializationScheme : SerializationScheme { - internal companion object { + companion object { private val serializationWhitelists: List by lazy { ServiceLoader.load(SerializationWhitelist::class.java, this::class.java.classLoader).toList() + DefaultWhitelist } @@ -132,9 +131,3 @@ class AMQPClientSerializationScheme : AbstractAMQPSerializationScheme() { } -val AMQP_P2P_CONTEXT = SerializationContextImpl(AmqpHeaderV1_0, - SerializationDefaults.javaClass.classLoader, - GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()), - emptyMap(), - true, - SerializationContext.UseCase.P2P) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/CordaClosureSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/CordaClosureSerializer.kt similarity index 94% rename from node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/CordaClosureSerializer.kt rename to node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/CordaClosureSerializer.kt index ec032a8d63..c821509b84 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/CordaClosureSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/CordaClosureSerializer.kt @@ -1,4 +1,4 @@ -package net.corda.nodeapi.internal.serialization +package net.corda.nodeapi.internal.serialization.kryo import com.esotericsoftware.kryo.Kryo import com.esotericsoftware.kryo.io.Output diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/DefaultKryoCustomizer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/DefaultKryoCustomizer.kt similarity index 97% rename from node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/DefaultKryoCustomizer.kt rename to node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/DefaultKryoCustomizer.kt index 7532ecffe9..e19fd49604 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/DefaultKryoCustomizer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/DefaultKryoCustomizer.kt @@ -1,4 +1,4 @@ -package net.corda.nodeapi.internal.serialization +package net.corda.nodeapi.internal.serialization.kryo import com.esotericsoftware.kryo.Kryo import com.esotericsoftware.kryo.Serializer @@ -23,6 +23,10 @@ import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.WireTransaction import net.corda.core.utilities.NonEmptySet import net.corda.core.utilities.toNonEmptySet +import net.corda.nodeapi.internal.serialization.CordaClassResolver +import net.corda.nodeapi.internal.serialization.DefaultWhitelist +import net.corda.nodeapi.internal.serialization.GeneratedAttachment +import net.corda.nodeapi.internal.serialization.MutableClassWhitelist import net.i2p.crypto.eddsa.EdDSAPrivateKey import net.i2p.crypto.eddsa.EdDSAPublicKey import org.bouncycastle.asn1.x500.X500Name diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/Kryo.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/Kryo.kt similarity index 98% rename from node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/Kryo.kt rename to node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/Kryo.kt index 4a82fe8d85..c3a859b084 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/Kryo.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/Kryo.kt @@ -1,4 +1,4 @@ -package net.corda.nodeapi.internal.serialization +package net.corda.nodeapi.internal.serialization.kryo import com.esotericsoftware.kryo.* import com.esotericsoftware.kryo.factories.ReflectionSerializerFactory @@ -21,11 +21,9 @@ import net.corda.core.serialization.SerializedBytes import net.corda.core.toFuture import net.corda.core.toObservable import net.corda.core.transactions.* -import net.corda.core.transactions.CoreTransaction -import net.corda.core.transactions.NotaryChangeWireTransaction -import net.corda.core.transactions.SignedTransaction -import net.corda.core.transactions.WireTransaction import net.corda.core.utilities.SgxSupport +import net.corda.nodeapi.internal.serialization.CordaClassResolver +import net.corda.nodeapi.internal.serialization.serializationContextKey import org.bouncycastle.asn1.ASN1InputStream import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.cert.X509CertificateHolder @@ -288,7 +286,7 @@ object SignedTransactionSerializer : Serializer() { sealed class UseCaseSerializer(private val allowedUseCases: EnumSet) : Serializer() { protected fun checkUseCase() { - checkUseCase(allowedUseCases) + net.corda.nodeapi.internal.serialization.checkUseCase(allowedUseCases) } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoSerializationScheme.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoSerializationScheme.kt new file mode 100644 index 0000000000..cff437dd59 --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoSerializationScheme.kt @@ -0,0 +1,132 @@ +package net.corda.nodeapi.internal.serialization.kryo + +import java.util.concurrent.ConcurrentHashMap +import java.io.ByteArrayOutputStream +import co.paralleluniverse.fibers.Fiber +import co.paralleluniverse.io.serialization.kryo.KryoSerializer +import com.esotericsoftware.kryo.Kryo +import com.esotericsoftware.kryo.KryoException +import com.esotericsoftware.kryo.Serializer +import com.esotericsoftware.kryo.io.Input +import com.esotericsoftware.kryo.io.Output +import com.esotericsoftware.kryo.pool.KryoPool +import com.esotericsoftware.kryo.serializers.ClosureSerializer +import net.corda.core.internal.uncheckedCast +import net.corda.core.utilities.OpaqueBytes +import net.corda.core.utilities.ByteSequence +import net.corda.core.serialization.* +import net.corda.core.internal.LazyPool +import net.corda.nodeapi.internal.serialization.CordaClassResolver +import net.corda.nodeapi.internal.serialization.SerializationScheme + +// "corda" + majorVersionByte + minorVersionMSB + minorVersionLSB +val KryoHeaderV0_1: OpaqueBytes = OpaqueBytes("corda\u0000\u0000\u0001".toByteArray(Charsets.UTF_8)) + +private object AutoCloseableSerialisationDetector : Serializer() { + override fun write(kryo: Kryo, output: Output, closeable: AutoCloseable) { + val message = "${closeable.javaClass.name}, which is a closeable resource, has been detected during flow checkpointing. " + + "Restoring such resources across node restarts is not supported. Make sure code accessing it is " + + "confined to a private method or the reference is nulled out." + throw UnsupportedOperationException(message) + } + + override fun read(kryo: Kryo, input: Input, type: Class) = throw IllegalStateException("Should not reach here!") +} + +abstract class AbstractKryoSerializationScheme : SerializationScheme { + private val kryoPoolsForContexts = ConcurrentHashMap, KryoPool>() + + protected abstract fun rpcClientKryoPool(context: SerializationContext): KryoPool + protected abstract fun rpcServerKryoPool(context: SerializationContext): KryoPool + + private fun getPool(context: SerializationContext): KryoPool { + return kryoPoolsForContexts.computeIfAbsent(Pair(context.whitelist, context.deserializationClassLoader)) { + when (context.useCase) { + SerializationContext.UseCase.Checkpoint -> + KryoPool.Builder { + val serializer = Fiber.getFiberSerializer(false) as KryoSerializer + val classResolver = CordaClassResolver(context).apply { setKryo(serializer.kryo) } + // TODO The ClassResolver can only be set in the Kryo constructor and Quasar doesn't provide us with a way of doing that + val field = Kryo::class.java.getDeclaredField("classResolver").apply { isAccessible = true } + serializer.kryo.apply { + field.set(this, classResolver) + DefaultKryoCustomizer.customize(this) + addDefaultSerializer(AutoCloseable::class.java, AutoCloseableSerialisationDetector) + register(ClosureSerializer.Closure::class.java, CordaClosureSerializer) + classLoader = it.second + } + }.build() + SerializationContext.UseCase.RPCClient -> + rpcClientKryoPool(context) + SerializationContext.UseCase.RPCServer -> + rpcServerKryoPool(context) + else -> + KryoPool.Builder { + DefaultKryoCustomizer.customize(CordaKryo(CordaClassResolver(context))).apply { classLoader = it.second } + }.build() + } + } + } + + private fun withContext(kryo: Kryo, context: SerializationContext, block: (Kryo) -> T): T { + kryo.context.ensureCapacity(context.properties.size) + context.properties.forEach { kryo.context.put(it.key, it.value) } + try { + return block(kryo) + } finally { + kryo.context.clear() + } + } + + override fun deserialize(byteSequence: ByteSequence, clazz: Class, context: SerializationContext): T { + val pool = getPool(context) + val headerSize = KryoHeaderV0_1.size + val header = byteSequence.take(headerSize) + if (header != KryoHeaderV0_1) { + throw KryoException("Serialized bytes header does not match expected format.") + } + Input(byteSequence.bytes, byteSequence.offset + headerSize, byteSequence.size - headerSize).use { input -> + return pool.run { kryo -> + withContext(kryo, context) { + if (context.objectReferencesEnabled) { + uncheckedCast(kryo.readClassAndObject(input)) + } else { + kryo.withoutReferences { uncheckedCast(kryo.readClassAndObject(input)) } + } + } + } + } + } + + override fun serialize(obj: T, context: SerializationContext): SerializedBytes { + val pool = getPool(context) + return pool.run { kryo -> + withContext(kryo, context) { + serializeOutputStreamPool.run { stream -> + serializeBufferPool.run { buffer -> + Output(buffer).use { + it.outputStream = stream + it.writeBytes(KryoHeaderV0_1.bytes) + if (context.objectReferencesEnabled) { + kryo.writeClassAndObject(it, obj) + } else { + kryo.withoutReferences { kryo.writeClassAndObject(it, obj) } + } + } + SerializedBytes(stream.toByteArray()) + } + } + } + } + } +} + +private val serializeBufferPool = LazyPool( + newInstance = { ByteArray(64 * 1024) } +) + +private val serializeOutputStreamPool = LazyPool( + clear = ByteArrayOutputStream::reset, + shouldReturnToPool = { it.size() < 256 * 1024 }, // Discard if it grew too large + newInstance = { ByteArrayOutputStream(64 * 1024) } +) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializeAsTokenSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/SerializeAsTokenSerializer.kt similarity index 96% rename from node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializeAsTokenSerializer.kt rename to node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/SerializeAsTokenSerializer.kt index 118eb299fb..eebe87a099 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializeAsTokenSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/SerializeAsTokenSerializer.kt @@ -1,4 +1,4 @@ -package net.corda.nodeapi.internal.serialization +package net.corda.nodeapi.internal.serialization.kryo import com.esotericsoftware.kryo.Kryo import com.esotericsoftware.kryo.KryoException diff --git a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/ForbiddenLambdaSerializationTests.java b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/ForbiddenLambdaSerializationTests.java index 26ef012df3..ba452d138c 100644 --- a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/ForbiddenLambdaSerializationTests.java +++ b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/ForbiddenLambdaSerializationTests.java @@ -5,6 +5,8 @@ import net.corda.core.serialization.SerializationContext; import net.corda.core.serialization.SerializationFactory; import net.corda.core.serialization.SerializedBytes; import net.corda.testing.SerializationEnvironmentRule; +import net.corda.nodeapi.internal.serialization.kryo.CordaClosureBlacklistSerializer; +import net.corda.nodeapi.internal.serialization.kryo.KryoSerializationSchemeKt; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -31,7 +33,7 @@ public final class ForbiddenLambdaSerializationTests { EnumSet contexts = EnumSet.complementOf(EnumSet.of(SerializationContext.UseCase.Checkpoint)); contexts.forEach(ctx -> { - SerializationContext context = new SerializationContextImpl(SerializationSchemeKt.getKryoHeaderV0_1(), this.getClass().getClassLoader(), AllWhitelist.INSTANCE, Maps.newHashMap(), true, ctx); + SerializationContext context = new SerializationContextImpl(KryoSerializationSchemeKt.getKryoHeaderV0_1(), this.getClass().getClassLoader(), AllWhitelist.INSTANCE, Maps.newHashMap(), true, ctx); String value = "Hey"; Callable target = (Callable & Serializable) () -> value; @@ -54,7 +56,7 @@ public final class ForbiddenLambdaSerializationTests { EnumSet contexts = EnumSet.complementOf(EnumSet.of(SerializationContext.UseCase.Checkpoint)); contexts.forEach(ctx -> { - SerializationContext context = new SerializationContextImpl(SerializationSchemeKt.getKryoHeaderV0_1(), this.getClass().getClassLoader(), AllWhitelist.INSTANCE, Maps.newHashMap(), true, ctx); + SerializationContext context = new SerializationContextImpl(KryoSerializationSchemeKt.getKryoHeaderV0_1(), this.getClass().getClassLoader(), AllWhitelist.INSTANCE, Maps.newHashMap(), true, ctx); String value = "Hey"; Callable target = () -> value; diff --git a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/LambdaCheckpointSerializationTest.java b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/LambdaCheckpointSerializationTest.java index c3230be211..93156d1073 100644 --- a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/LambdaCheckpointSerializationTest.java +++ b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/LambdaCheckpointSerializationTest.java @@ -5,6 +5,8 @@ import net.corda.core.serialization.SerializationContext; import net.corda.core.serialization.SerializationFactory; import net.corda.core.serialization.SerializedBytes; import net.corda.testing.SerializationEnvironmentRule; +import net.corda.nodeapi.internal.serialization.kryo.CordaClosureSerializer; +import net.corda.nodeapi.internal.serialization.kryo.KryoSerializationSchemeKt; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -24,7 +26,7 @@ public final class LambdaCheckpointSerializationTest { @Before public void setup() { factory = testSerialization.env.getSERIALIZATION_FACTORY(); - context = new SerializationContextImpl(SerializationSchemeKt.getKryoHeaderV0_1(), this.getClass().getClassLoader(), AllWhitelist.INSTANCE, Maps.newHashMap(), true, SerializationContext.UseCase.Checkpoint); + context = new SerializationContextImpl(KryoSerializationSchemeKt.getKryoHeaderV0_1(), this.getClass().getClassLoader(), AllWhitelist.INSTANCE, Maps.newHashMap(), true, SerializationContext.UseCase.Checkpoint); } @Test diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolverTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolverTests.kt index 04f4c69122..155229a5bc 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolverTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolverTests.kt @@ -10,6 +10,8 @@ import net.corda.core.node.services.AttachmentStorage import net.corda.core.serialization.* import net.corda.nodeapi.internal.AttachmentsClassLoader import net.corda.nodeapi.internal.AttachmentsClassLoaderTests +import net.corda.nodeapi.internal.serialization.kryo.CordaKryo +import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1 import net.corda.testing.node.MockAttachmentStorage import net.corda.testing.rigorousMock import org.junit.Rule diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/KryoTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/KryoTests.kt index 07593368a7..cafeb14dd0 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/KryoTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/KryoTests.kt @@ -13,6 +13,7 @@ import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.sequence import net.corda.node.serialization.KryoServerSerializationScheme import net.corda.node.services.persistence.NodeAttachmentService +import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1 import net.corda.testing.ALICE_PUBKEY import net.corda.testing.SerializationEnvironmentRule import org.assertj.core.api.Assertions.assertThat diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/ListsSerializationTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/ListsSerializationTest.kt index c59d26b72d..5663a78beb 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/ListsSerializationTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/ListsSerializationTest.kt @@ -7,6 +7,7 @@ import net.corda.node.services.statemachine.SessionData import net.corda.nodeapi.internal.serialization.amqp.DeserializationInput import net.corda.nodeapi.internal.serialization.amqp.Envelope import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory +import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1 import net.corda.testing.amqpSpecific import net.corda.testing.kryoSpecific import net.corda.testing.SerializationEnvironmentRule diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/MapsSerializationTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/MapsSerializationTest.kt index 5cc64a5b64..ed45e63b68 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/MapsSerializationTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/MapsSerializationTest.kt @@ -6,6 +6,7 @@ import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.node.services.statemachine.SessionData +import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1 import net.corda.testing.amqpSpecific import net.corda.testing.kryoSpecific import net.corda.testing.SerializationEnvironmentRule @@ -84,4 +85,4 @@ class MapsSerializationTest { } assertArrayEquals(output.toByteArray(), serializedForm.bytes) } -} \ No newline at end of file +} diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SerializationTokenTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SerializationTokenTest.kt index 6fc74fba55..bcca06f6de 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SerializationTokenTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SerializationTokenTest.kt @@ -5,6 +5,10 @@ import com.esotericsoftware.kryo.KryoException import com.esotericsoftware.kryo.io.Output import net.corda.core.serialization.* import net.corda.core.utilities.OpaqueBytes +import net.corda.nodeapi.internal.serialization.kryo.CordaKryo +import net.corda.nodeapi.internal.serialization.kryo.DefaultKryoCustomizer +import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1 +import net.corda.testing.TestDependencyInjectionBase import net.corda.testing.rigorousMock import net.corda.testing.SerializationEnvironmentRule import org.assertj.core.api.Assertions.assertThat diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SetsSerializationTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SetsSerializationTest.kt index 57cee0e7e5..243b73a803 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SetsSerializationTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SetsSerializationTest.kt @@ -5,6 +5,7 @@ import com.esotericsoftware.kryo.util.DefaultClassResolver import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.node.services.statemachine.SessionData +import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1 import net.corda.testing.kryoSpecific import net.corda.testing.SerializationEnvironmentRule import org.junit.Assert.assertArrayEquals diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt index f3496a9f74..d6527fd816 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt @@ -13,7 +13,6 @@ import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.SerializationFactory import net.corda.core.transactions.LedgerTransaction import net.corda.client.rpc.RPCException -import net.corda.nodeapi.internal.serialization.AbstractAMQPSerializationScheme import net.corda.nodeapi.internal.serialization.AllWhitelist import net.corda.nodeapi.internal.serialization.EmptyWhitelist import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory.Companion.isPrimitive diff --git a/node/build.gradle b/node/build.gradle index 169593924b..f9a65e4104 100644 --- a/node/build.gradle +++ b/node/build.gradle @@ -19,9 +19,6 @@ configurations { integrationTestCompile.extendsFrom testCompile integrationTestRuntime.extendsFrom testRuntime - - smokeTestCompile.extendsFrom compile - smokeTestRuntime.extendsFrom runtime } sourceSets { @@ -40,20 +37,6 @@ sourceSets { srcDir file('src/integration-test/resources') } } - smokeTest { - kotlin { - // We must NOT have any Node code on the classpath, so do NOT - // include the test or integrationTest dependencies here. - compileClasspath += main.output - runtimeClasspath += main.output - srcDir file('src/smoke-test/kotlin') - } - java { - compileClasspath += main.output - runtimeClasspath += main.output - srcDir file('src/smoke-test/java') - } - } } // Use manual resource copying of log4j2.xml rather than source sets. @@ -62,13 +45,6 @@ processResources { from file("$rootDir/config/dev/log4j2.xml") } -processSmokeTestResources { - // Bring in the fully built corda.jar for use by NodeFactory in the smoke tests - from(project(':node:capsule').tasks.buildCordaJAR) { - rename 'corda-(.*)', 'corda.jar' - } -} - // To find potential version conflicts, run "gradle htmlDependencyReport" and then look in // build/reports/project/dependencies/index.html for green highlighted parts of the tree. @@ -184,11 +160,6 @@ dependencies { integrationTestCompile "junit:junit:$junit_version" integrationTestCompile "org.assertj:assertj-core:${assertj_version}" - // Smoke tests do NOT have any Node code on the classpath! - smokeTestCompile project(':smoke-test-utils') - smokeTestCompile "org.assertj:assertj-core:${assertj_version}" - smokeTestCompile "junit:junit:$junit_version" - // Jetty dependencies for NetworkMapClient test. // Web stuff: for HTTP[S] servlets testCompile "org.eclipse.jetty:jetty-servlet:${jetty_version}" @@ -206,17 +177,6 @@ task integrationTest(type: Test) { classpath = sourceSets.integrationTest.runtimeClasspath } -task smokeTestJar(type: Jar) { - classifier 'smokeTests' - from sourceSets.smokeTest.output -} - -task smokeTest(type: Test) { - dependsOn smokeTestJar - testClassesDirs = sourceSets.smokeTest.output.classesDirs - classpath = sourceSets.smokeTest.runtimeClasspath -} - jar { baseName 'corda-node' } diff --git a/node/src/integration-test/kotlin/net/corda/node/BootTests.kt b/node/src/integration-test/kotlin/net/corda/node/BootTests.kt index e402d00849..6c2c3d3ae7 100644 --- a/node/src/integration-test/kotlin/net/corda/node/BootTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/BootTests.kt @@ -7,29 +7,24 @@ import net.corda.core.internal.div import net.corda.core.messaging.startFlow import net.corda.core.utilities.getOrThrow import net.corda.node.internal.NodeStartup -import net.corda.node.services.FlowPermissions.Companion.startFlowPermission +import net.corda.node.services.Permissions.Companion.startFlow import net.corda.nodeapi.User -import net.corda.nodeapi.internal.ServiceInfo -import net.corda.nodeapi.internal.ServiceType import net.corda.testing.ALICE import net.corda.testing.ProjectStructure.projectRootDir -import net.corda.testing.driver.ListenProcessDeathException import net.corda.testing.driver.driver import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy -import org.junit.Ignore import org.junit.Test import java.io.* import java.nio.file.Files import kotlin.test.assertEquals -import kotlin.test.assertFailsWith class BootTests { @Test fun `java deserialization is disabled`() { driver { - val user = User("u", "p", setOf(startFlowPermission())) + val user = User("u", "p", setOf(startFlow())) val future = startNode(rpcUsers = listOf(user)).getOrThrow().rpcClientToNode(). start(user.username, user.password).proxy.startFlow(::ObjectInputStreamFlow).returnValue assertThatThrownBy { future.getOrThrow() }.isInstanceOf(InvalidClassException::class.java).hasMessage("filter status: REJECTED") @@ -53,16 +48,6 @@ class BootTests { assertEquals(1, numberOfNodesThatLogged) } } - - @Ignore("Need rewriting to produce too big network map registration (adverticed services trick doesn't work after services removal).") - @Test - fun `node quits on failure to register with network map`() { - val tooManyAdvertisedServices = (1..100).map { ServiceInfo(ServiceType.notary.getSubType("$it")) }.toSet() - driver { - val future = startNode(providedName = ALICE.name) - assertFailsWith(ListenProcessDeathException::class) { future.getOrThrow() } - } - } } @StartableByRPC diff --git a/node/src/integration-test/kotlin/net/corda/node/CordappScanningDriverTest.kt b/node/src/integration-test/kotlin/net/corda/node/CordappScanningDriverTest.kt index 50788582b5..8515b9b4ec 100644 --- a/node/src/integration-test/kotlin/net/corda/node/CordappScanningDriverTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/CordappScanningDriverTest.kt @@ -7,7 +7,7 @@ import net.corda.core.internal.concurrent.transpose import net.corda.core.messaging.startFlow import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.unwrap -import net.corda.node.services.FlowPermissions.Companion.startFlowPermission +import net.corda.node.services.Permissions.Companion.startFlow import net.corda.nodeapi.User import net.corda.testing.ALICE import net.corda.testing.BOB @@ -19,7 +19,7 @@ import org.junit.Test class CordappScanningDriverTest { @Test fun `sub-classed initiated flow pointing to the same initiating flow as its super-class`() { - val user = User("u", "p", setOf(startFlowPermission())) + val user = User("u", "p", setOf(startFlow())) // The driver will automatically pick up the annotated flows below driver { val (alice, bob) = listOf( diff --git a/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt b/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt index 6cddc95bed..abf4b1a06b 100644 --- a/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt @@ -12,12 +12,12 @@ import net.corda.core.utilities.minutes import net.corda.finance.DOLLARS import net.corda.finance.flows.CashIssueFlow import net.corda.finance.flows.CashPaymentFlow -import net.corda.node.services.FlowPermissions.Companion.startFlowPermission +import net.corda.node.services.Permissions.Companion.startFlow import net.corda.nodeapi.User import net.corda.testing.DUMMY_NOTARY -import net.corda.testing.chooseIdentity import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.driver +import net.corda.testing.node.NotarySpec import net.corda.testing.performance.div import net.corda.testing.performance.startPublishingFixedRateInjector import net.corda.testing.performance.startReporter @@ -59,7 +59,7 @@ class NodePerformanceTests { @Test fun `empty flow per second`() { driver(startNodesInProcess = true) { - val a = startNode(rpcUsers = listOf(User("A", "A", setOf(startFlowPermission())))).get() + val a = startNode(rpcUsers = listOf(User("A", "A", setOf(startFlow())))).get() a.rpcClientToNode().use("A", "A") { connection -> val timings = Collections.synchronizedList(ArrayList()) @@ -89,7 +89,7 @@ class NodePerformanceTests { @Test fun `empty flow rate`() { driver(startNodesInProcess = true) { - val a = startNode(rpcUsers = listOf(User("A", "A", setOf(startFlowPermission())))).get() + val a = startNode(rpcUsers = listOf(User("A", "A", setOf(startFlow())))).get() a as NodeHandle.InProcess val metricRegistry = startReporter(shutdownManager, a.node.services.monitoringService.metrics) a.rpcClientToNode().use("A", "A") { connection -> @@ -102,26 +102,25 @@ class NodePerformanceTests { @Test fun `self pay rate`() { - driver(startNodesInProcess = true, extraCordappPackagesToScan = listOf("net.corda.finance")) { - val a = startNotaryNode( - DUMMY_NOTARY.name, - rpcUsers = listOf(User("A", "A", setOf(startFlowPermission(), startFlowPermission()))) - ).getOrThrow() - a as NodeHandle.InProcess - val metricRegistry = startReporter(shutdownManager, a.node.services.monitoringService.metrics) - a.rpcClientToNode().use("A", "A") { connection -> - val notary = connection.proxy.notaryIdentities().first() + val user = User("A", "A", setOf(startFlow(), startFlow())) + driver( + notarySpecs = listOf(NotarySpec(DUMMY_NOTARY.name, rpcUsers = listOf(user))), + startNodesInProcess = true, + extraCordappPackagesToScan = listOf("net.corda.finance") + ) { + val notary = defaultNotaryNode.getOrThrow() as NodeHandle.InProcess + val metricRegistry = startReporter(shutdownManager, notary.node.services.monitoringService.metrics) + notary.rpcClientToNode().use("A", "A") { connection -> println("ISSUING") val doneFutures = (1..100).toList().parallelStream().map { - connection.proxy.startFlow(::CashIssueFlow, 1.DOLLARS, OpaqueBytes.of(0), notary).returnValue + connection.proxy.startFlow(::CashIssueFlow, 1.DOLLARS, OpaqueBytes.of(0), defaultNotaryIdentity).returnValue }.toList() doneFutures.transpose().get() println("STARTING PAYMENT") startPublishingFixedRateInjector(metricRegistry, 8, 5.minutes, 100L / TimeUnit.SECONDS) { - connection.proxy.startFlow(::CashPaymentFlow, 1.DOLLARS, a.nodeInfo.chooseIdentity()).returnValue.get() + connection.proxy.startFlow(::CashPaymentFlow, 1.DOLLARS, defaultNotaryIdentity).returnValue.get() } } - } } } \ No newline at end of file diff --git a/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt index 2065c7d350..0b233b9fb3 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt @@ -13,14 +13,13 @@ import net.corda.core.internal.div import net.corda.core.internal.toLedgerTransaction import net.corda.core.serialization.SerializationFactory import net.corda.core.transactions.TransactionBuilder -import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.loggerFor -import net.corda.core.utilities.seconds import net.corda.node.internal.cordapp.CordappLoader import net.corda.node.internal.cordapp.CordappProviderImpl -import net.corda.nodeapi.User -import net.corda.testing.* +import net.corda.testing.DUMMY_BANK_A +import net.corda.testing.DUMMY_NOTARY +import net.corda.testing.SerializationEnvironmentRule import net.corda.testing.driver.DriverDSLExposedInterface import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.driver @@ -53,20 +52,15 @@ class AttachmentLoadingTests { val bankAName = CordaX500Name("BankA", "Zurich", "CH") val bankBName = CordaX500Name("BankB", "Zurich", "CH") - val notaryName = CordaX500Name("Notary", "Zurich", "CH") - val flowInitiatorClass = + val flowInitiatorClass: Class> = Class.forName("net.corda.finance.contracts.isolated.IsolatedDummyFlow\$Initiator", true, URLClassLoader(arrayOf(isolatedJAR))) .asSubclass(FlowLogic::class.java) - private fun DriverDSLExposedInterface.createTwoNodesAndNotary(): List { - val adminUser = User("admin", "admin", permissions = setOf("ALL")) - val nodes = listOf( - startNode(providedName = bankAName, rpcUsers = listOf(adminUser)), - startNode(providedName = bankBName, rpcUsers = listOf(adminUser)), - startNotaryNode(providedName = notaryName, rpcUsers = listOf(adminUser), validating = false) - ).transpose().getOrThrow() // Wait for all nodes to start up. - nodes.forEach { it.rpc.waitUntilNetworkReady().getOrThrow() } - return nodes + private fun DriverDSLExposedInterface.createTwoNodes(): List { + return listOf( + startNode(providedName = bankAName), + startNode(providedName = bankBName) + ).transpose().getOrThrow() } private fun DriverDSLExposedInterface.installIsolatedCordappTo(nodeName: CordaX500Name) { @@ -79,15 +73,6 @@ class AttachmentLoadingTests { } } } - - // Due to cluster instability after nodes been started it may take some time to all the nodes to become available - // *and* discover each other to reliably communicate. Hence, eventual nature of the test. - // TODO: Remove this method and usages of it once NetworkMap service been re-worked - private fun eventuallyPassingTest(block: () -> Unit) { - eventually(30.seconds) { - block() - } - } } private lateinit var services: Services @@ -103,9 +88,8 @@ class AttachmentLoadingTests { val contractClass = appClassLoader.loadClass(ISOLATED_CONTRACT_ID).asSubclass(Contract::class.java) val generateInitialMethod = contractClass.getDeclaredMethod("generateInitial", PartyAndReference::class.java, Integer.TYPE, Party::class.java) val contract = contractClass.newInstance() - val txBuilder = generateInitialMethod.invoke(contract, PartyAndReference(DUMMY_BANK_A, OpaqueBytes(kotlin.ByteArray(1))), 1, DUMMY_NOTARY) as TransactionBuilder - val context = SerializationFactory.defaultFactory.defaultContext - .withClassLoader(appClassLoader) + val txBuilder = generateInitialMethod.invoke(contract, DUMMY_BANK_A.ref(1), 1, DUMMY_NOTARY) as TransactionBuilder + val context = SerializationFactory.defaultFactory.defaultContext.withClassLoader(appClassLoader) val ledgerTx = txBuilder.toLedgerTransaction(services, context) contract.verify(ledgerTx) @@ -119,11 +103,9 @@ class AttachmentLoadingTests { fun `test that attachments retrieved over the network are not used for code`() { driver(initialiseSerialization = false) { installIsolatedCordappTo(bankAName) - val (bankA, bankB, _) = createTwoNodesAndNotary() - eventuallyPassingTest { - assertFailsWith("Party C=CH,L=Zurich,O=BankB rejected session request: Don't know net.corda.finance.contracts.isolated.IsolatedDummyFlow\$Initiator") { - bankA.rpc.startFlowDynamic(flowInitiatorClass, bankB.nodeInfo.legalIdentities.first()).returnValue.getOrThrow() - } + val (bankA, bankB) = createTwoNodes() + assertFailsWith("Party C=CH,L=Zurich,O=BankB rejected session request: Don't know net.corda.finance.contracts.isolated.IsolatedDummyFlow\$Initiator") { + bankA.rpc.startFlowDynamic(flowInitiatorClass, bankB.nodeInfo.legalIdentities.first()).returnValue.getOrThrow() } } } @@ -133,10 +115,8 @@ class AttachmentLoadingTests { driver(initialiseSerialization = false) { installIsolatedCordappTo(bankAName) installIsolatedCordappTo(bankBName) - val (bankA, bankB, _) = createTwoNodesAndNotary() - eventuallyPassingTest { - bankA.rpc.startFlowDynamic(flowInitiatorClass, bankB.nodeInfo.legalIdentities.first()).returnValue.getOrThrow() - } + val (bankA, bankB) = createTwoNodes() + bankA.rpc.startFlowDynamic(flowInitiatorClass, bankB.nodeInfo.legalIdentities.first()).returnValue.getOrThrow() } } } diff --git a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt index 3605ab14a4..dafdb08c7f 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt @@ -13,6 +13,8 @@ import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.internal.deleteIfExists import net.corda.core.internal.div +import net.corda.core.node.NotaryInfo +import net.corda.core.node.services.NotaryService import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.NetworkHostAndPort @@ -21,15 +23,16 @@ import net.corda.core.utilities.getOrThrow import net.corda.node.internal.StartedNode import net.corda.node.services.config.BFTSMaRtConfiguration import net.corda.node.services.config.NotaryConfig -import net.corda.node.services.transactions.BFTNonValidatingNotaryService import net.corda.node.services.transactions.minClusterSize import net.corda.node.services.transactions.minCorrectReplicas import net.corda.node.utilities.ServiceIdentityGenerator import net.corda.testing.chooseIdentity +import net.corda.testing.common.internal.NetworkParametersCopier +import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.contracts.DummyContract import net.corda.testing.dummyCommand -import net.corda.testing.getDefaultNotary import net.corda.testing.node.MockNetwork +import net.corda.testing.node.MockNetwork.MockNode import net.corda.testing.node.MockNodeParameters import org.junit.After import org.junit.Test @@ -38,39 +41,47 @@ import kotlin.test.assertEquals import kotlin.test.assertTrue class BFTNotaryServiceTests { - companion object { - private val clusterName = CordaX500Name(BFTNonValidatingNotaryService.id, "BFT", "Zurich", "CH") - } - private val mockNet = MockNetwork() - private val node = mockNet.createNode() + private lateinit var notary: Party + private lateinit var node: StartedNode @After fun stopNodes() { mockNet.stopNodes() } - private fun bftNotaryCluster(clusterSize: Int, exposeRaces: Boolean = false) { + private fun startBftClusterAndNode(clusterSize: Int, exposeRaces: Boolean = false) { (Paths.get("config") / "currentView").deleteIfExists() // XXX: Make config object warn if this exists? val replicaIds = (0 until clusterSize) - ServiceIdentityGenerator.generateToDisk( + + notary = ServiceIdentityGenerator.generateToDisk( replicaIds.map { mockNet.baseDirectory(mockNet.nextNodeId + it) }, - clusterName) + CordaX500Name("BFT", "Zurich", "CH"), + NotaryService.constructId(validating = false, bft = true)) + + val networkParameters = NetworkParametersCopier(testNetworkParameters(listOf(NotaryInfo(notary, false)))) + val clusterAddresses = replicaIds.map { NetworkHostAndPort("localhost", 11000 + it * 10) } - replicaIds.forEach { replicaId -> - mockNet.createNode(MockNodeParameters(configOverrides = { + + val nodes = replicaIds.map { replicaId -> + mockNet.createUnstartedNode(MockNodeParameters(configOverrides = { val notary = NotaryConfig(validating = false, bftSMaRt = BFTSMaRtConfiguration(replicaId, clusterAddresses, exposeRaces = exposeRaces)) doReturn(notary).whenever(it).notary })) - } - mockNet.runNetwork() // Exchange initial network map registration messages. + } + mockNet.createUnstartedNode() + + // MockNetwork doesn't support BFT clusters, so we create all the nodes we need unstarted, and then install the + // network-parameters in their directories before they're started. + node = nodes.map { node -> + networkParameters.install(mockNet.baseDirectory(node.id)) + node.start() + }.last() } /** Failure mode is the redundant replica gets stuck in startup, so we can't dispose it cleanly at the end. */ @Test fun `all replicas start even if there is a new consensus during startup`() { - bftNotaryCluster(minClusterSize(1), true) // This true adds a sleep to expose the race. - val notary = node.services.getDefaultNotary() + startBftClusterAndNode(minClusterSize(1), exposeRaces = true) // This true adds a sleep to expose the race. val f = node.run { val trivialTx = signInitialTransaction(notary) { addOutputState(DummyContract.SingleOwnerState(owner = info.chooseIdentity()), DummyContract.PROGRAM_ID, AlwaysAcceptAttachmentConstraint) @@ -94,8 +105,7 @@ class BFTNotaryServiceTests { private fun detectDoubleSpend(faultyReplicas: Int) { val clusterSize = minClusterSize(faultyReplicas) - bftNotaryCluster(clusterSize) - val notary = node.services.getDefaultNotary() + startBftClusterAndNode(clusterSize) node.run { val issueTx = signInitialTransaction(notary) { addOutputState(DummyContract.SingleOwnerState(owner = info.chooseIdentity()), DummyContract.PROGRAM_ID, AlwaysAcceptAttachmentConstraint) @@ -138,15 +148,13 @@ class BFTNotaryServiceTests { } } } -} -private fun StartedNode<*>.signInitialTransaction( - notary: Party, - block: TransactionBuilder.() -> Any? -): SignedTransaction { - return services.signInitialTransaction( - TransactionBuilder(notary).apply { - addCommand(dummyCommand(services.myInfo.chooseIdentity().owningKey)) - block() - }) + private fun StartedNode<*>.signInitialTransaction(notary: Party, block: TransactionBuilder.() -> Any?): SignedTransaction { + return services.signInitialTransaction( + TransactionBuilder(notary).apply { + addCommand(dummyCommand(services.myInfo.chooseIdentity().owningKey)) + block() + } + ) + } } diff --git a/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt index 06ed089e16..15a8914e35 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/DistributedServiceTests.kt @@ -11,65 +11,70 @@ import net.corda.core.utilities.getOrThrow import net.corda.finance.POUNDS import net.corda.finance.flows.CashIssueFlow import net.corda.finance.flows.CashPaymentFlow -import net.corda.node.services.FlowPermissions.Companion.startFlowPermission -import net.corda.node.services.transactions.RaftValidatingNotaryService +import net.corda.node.services.Permissions.Companion.invokeRpc +import net.corda.node.services.Permissions.Companion.startFlow import net.corda.nodeapi.User import net.corda.testing.* import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.driver -import net.corda.testing.node.DriverBasedTest +import net.corda.testing.node.ClusterSpec +import net.corda.testing.node.NotarySpec +import org.assertj.core.api.Assertions.assertThat import org.junit.Test import rx.Observable import java.util.* -import kotlin.test.assertEquals -class DistributedServiceTests : DriverBasedTest() { - lateinit var alice: NodeHandle - lateinit var notaries: List - lateinit var aliceProxy: CordaRPCOps - lateinit var raftNotaryIdentity: Party - lateinit var notaryStateMachines: Observable> +class DistributedServiceTests { + private lateinit var alice: NodeHandle + private lateinit var notaryNodes: List + private lateinit var aliceProxy: CordaRPCOps + private lateinit var raftNotaryIdentity: Party + private lateinit var notaryStateMachines: Observable> - override fun setup() = driver(extraCordappPackagesToScan = listOf("net.corda.finance.contracts")) { - // Start Alice and 3 notaries in a RAFT cluster - val clusterSize = 3 + private fun setup(testBlock: () -> Unit) { val testUser = User("test", "test", permissions = setOf( - startFlowPermission(), - startFlowPermission()) - ) - val aliceFuture = startNode(providedName = ALICE.name, rpcUsers = listOf(testUser)) - val notariesFuture = startNotaryCluster( - DUMMY_NOTARY.name.copy(commonName = RaftValidatingNotaryService.id), - rpcUsers = listOf(testUser), - clusterSize = clusterSize + startFlow(), + startFlow(), + invokeRpc(CordaRPCOps::nodeInfo), + invokeRpc(CordaRPCOps::stateMachinesFeed)) ) - alice = aliceFuture.get() - val (notaryIdentity, notaryNodes) = notariesFuture.get() - raftNotaryIdentity = notaryIdentity - notaries = notaryNodes.map { it as NodeHandle.OutOfProcess } + driver( + extraCordappPackagesToScan = listOf("net.corda.finance.contracts"), + notarySpecs = listOf(NotarySpec(DUMMY_NOTARY.name, rpcUsers = listOf(testUser), cluster = ClusterSpec.Raft(clusterSize = 3)))) + { + alice = startNode(providedName = ALICE.name, rpcUsers = listOf(testUser)).getOrThrow() + raftNotaryIdentity = defaultNotaryIdentity + notaryNodes = defaultNotaryHandle.nodeHandles.getOrThrow().map { it as NodeHandle.OutOfProcess } - assertEquals(notaries.size, clusterSize) - // Check that each notary has different identity as a node. - assertEquals(notaries.size, notaries.map { it.nodeInfo.chooseIdentity() }.toSet().size) - // Connect to Alice and the notaries - fun connectRpc(node: NodeHandle): CordaRPCOps { - val client = node.rpcClientToNode() - return client.start("test", "test").proxy + assertThat(notaryNodes).hasSize(3) + + for (notaryNode in notaryNodes) { + assertThat(notaryNode.nodeInfo.legalIdentities).contains(raftNotaryIdentity) + } + + // Check that each notary has different identity as a node. + assertThat(notaryNodes.flatMap { it.nodeInfo.legalIdentities - raftNotaryIdentity }.toSet()).hasSameSizeAs(notaryNodes) + + // Connect to Alice and the notaries + fun connectRpc(node: NodeHandle): CordaRPCOps { + val client = node.rpcClientToNode() + return client.start("test", "test").proxy + } + aliceProxy = connectRpc(alice) + val rpcClientsToNotaries = notaryNodes.map(::connectRpc) + notaryStateMachines = Observable.from(rpcClientsToNotaries.map { proxy -> + proxy.stateMachinesFeed().updates.map { Pair(proxy.nodeInfo().chooseIdentity(), it) } + }).flatMap { it.onErrorResumeNext(Observable.empty()) }.bufferUntilSubscribed() + + testBlock() } - aliceProxy = connectRpc(alice) - val rpcClientsToNotaries = notaries.map(::connectRpc) - notaryStateMachines = Observable.from(rpcClientsToNotaries.map { proxy -> - proxy.stateMachinesFeed().updates.map { Pair(proxy.nodeInfo().chooseIdentity(), it) } - }).flatMap { it.onErrorResumeNext(Observable.empty()) }.bufferUntilSubscribed() - - runTest() } // TODO Use a dummy distributed service rather than a Raft Notary Service as this test is only about Artemis' ability // to handle distributed services @Test - fun `requests are distributed evenly amongst the nodes`() { + fun `requests are distributed evenly amongst the nodes`() = setup { // Issue 100 pounds, then pay ourselves 50x2 pounds issueCash(100.POUNDS) @@ -97,7 +102,7 @@ class DistributedServiceTests : DriverBasedTest() { // TODO This should be in RaftNotaryServiceTests @Test - fun `cluster survives if a notary is killed`() { + fun `cluster survives if a notary is killed`() = setup { // Issue 100 pounds, then pay ourselves 10x5 pounds issueCash(100.POUNDS) @@ -105,8 +110,8 @@ class DistributedServiceTests : DriverBasedTest() { paySelf(5.POUNDS) } - // Now kill a notary - with(notaries[0].process) { + // Now kill a notary node + with(notaryNodes[0].process) { destroy() waitFor() } @@ -137,4 +142,4 @@ class DistributedServiceTests : DriverBasedTest() { private fun paySelf(amount: Amount) { aliceProxy.startFlow(::CashPaymentFlow, amount, alice.nodeInfo.chooseIdentity()).returnValue.getOrThrow() } -} +} \ No newline at end of file diff --git a/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt index 745b763e9e..b9e95db5bd 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt @@ -18,6 +18,8 @@ import net.corda.testing.contracts.DummyContract import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.driver import net.corda.testing.dummyCommand +import net.corda.testing.node.ClusterSpec +import net.corda.testing.node.NotarySpec import org.junit.Test import java.util.* import kotlin.test.assertEquals @@ -28,13 +30,16 @@ class RaftNotaryServiceTests { @Test fun `detect double spend`() { - driver(startNodesInProcess = true, extraCordappPackagesToScan = listOf("net.corda.testing.contracts")) { - val (notaryParty) = startNotaryCluster(notaryName, 3).getOrThrow() + driver( + startNodesInProcess = true, + extraCordappPackagesToScan = listOf("net.corda.testing.contracts"), + notarySpecs = listOf(NotarySpec(notaryName, cluster = ClusterSpec.Raft(clusterSize = 3)))) + { val bankA = startNode(providedName = DUMMY_BANK_A.name).map { (it as NodeHandle.InProcess).node }.getOrThrow() - val inputState = issueState(bankA, notaryParty) + val inputState = issueState(bankA, defaultNotaryIdentity) - val firstTxBuilder = TransactionBuilder(notaryParty) + val firstTxBuilder = TransactionBuilder(defaultNotaryIdentity) .addInputState(inputState) .addCommand(dummyCommand(bankA.services.myInfo.chooseIdentity().owningKey)) val firstSpendTx = bankA.services.signInitialTransaction(firstTxBuilder) @@ -42,7 +47,7 @@ class RaftNotaryServiceTests { val firstSpend = bankA.services.startFlow(NotaryFlow.Client(firstSpendTx)) firstSpend.resultFuture.getOrThrow() - val secondSpendBuilder = TransactionBuilder(notaryParty).withItems(inputState).run { + val secondSpendBuilder = TransactionBuilder(defaultNotaryIdentity).withItems(inputState).run { val dummyState = DummyContract.SingleOwnerState(0, bankA.info.chooseIdentity()) addOutputState(dummyState, DummyContract.PROGRAM_ID) addCommand(dummyCommand(bankA.services.myInfo.chooseIdentity().owningKey)) diff --git a/node/src/integration-test/kotlin/net/corda/node/services/network/NodeInfoWatcherTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/network/NodeInfoWatcherTest.kt index 8658de9091..897504d62d 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/network/NodeInfoWatcherTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/network/NodeInfoWatcherTest.kt @@ -6,15 +6,11 @@ import net.corda.cordform.CordformNode import net.corda.core.internal.createDirectories import net.corda.core.internal.div import net.corda.core.node.NodeInfo -import net.corda.core.node.services.KeyManagementService -import net.corda.node.services.identity.InMemoryIdentityService import net.corda.nodeapi.NodeInfoFilesCopier import net.corda.testing.ALICE import net.corda.testing.ALICE_KEY -import net.corda.testing.DEV_TRUST_ROOT import net.corda.testing.getTestPartyAndCertificate import net.corda.testing.internal.NodeBasedTest -import net.corda.testing.node.MockKeyManagementService import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.contentOf import org.junit.Before @@ -31,7 +27,6 @@ class NodeInfoWatcherTest : NodeBasedTest() { val nodeInfo = NodeInfo(listOf(), listOf(getTestPartyAndCertificate(ALICE)), 0, 0) } - private lateinit var keyManagementService: KeyManagementService private lateinit var nodeInfoPath: Path private val scheduler = TestScheduler() private val testSubscriber = TestSubscriber() @@ -41,16 +36,15 @@ class NodeInfoWatcherTest : NodeBasedTest() { @Before fun start() { - val identityService = InMemoryIdentityService(trustRoot = DEV_TRUST_ROOT) - keyManagementService = MockKeyManagementService(identityService, ALICE_KEY) nodeInfoWatcher = NodeInfoWatcher(tempFolder.root.toPath(), scheduler = scheduler) nodeInfoPath = tempFolder.root.toPath() / CordformNode.NODE_INFO_DIRECTORY } @Test fun `save a NodeInfo`() { - assertEquals(0, tempFolder.root.list().filter { it.startsWith(NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX) }.size) - NodeInfoWatcher.saveToFile(tempFolder.root.toPath(), nodeInfo, keyManagementService) + assertEquals(0, + tempFolder.root.list().filter { it.startsWith(NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX) }.size) + NodeInfoWatcher.saveToFile(tempFolder.root.toPath(), nodeInfo, ALICE_KEY) val nodeInfoFiles = tempFolder.root.list().filter { it.startsWith(NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX) } assertEquals(1, nodeInfoFiles.size) @@ -65,7 +59,7 @@ class NodeInfoWatcherTest : NodeBasedTest() { fun `save a NodeInfo to JimFs`() { val jimFs = Jimfs.newFileSystem(Configuration.unix()) val jimFolder = jimFs.getPath("/nodeInfo") - NodeInfoWatcher.saveToFile(jimFolder, nodeInfo, keyManagementService) + NodeInfoWatcher.saveToFile(jimFolder, nodeInfo, ALICE_KEY) } @Test @@ -134,6 +128,6 @@ class NodeInfoWatcherTest : NodeBasedTest() { // Write a nodeInfo under the right path. private fun createNodeInfoFileInPath(nodeInfo: NodeInfo) { - NodeInfoWatcher.saveToFile(nodeInfoPath, nodeInfo, keyManagementService) + NodeInfoWatcher.saveToFile(nodeInfoPath, nodeInfo, ALICE_KEY) } } diff --git a/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt index 30ac56d490..d4bdec79b7 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt @@ -8,7 +8,7 @@ import net.corda.node.internal.Node import net.corda.node.internal.StartedNode import net.corda.testing.ALICE import net.corda.testing.BOB -import net.corda.testing.DUMMY_NOTARY +import net.corda.testing.DUMMY_REGULATOR import net.corda.testing.chooseIdentity import net.corda.testing.internal.NodeBasedTest import org.junit.Before @@ -16,9 +16,9 @@ import org.junit.Test import kotlin.test.assertEquals class PersistentNetworkMapCacheTest : NodeBasedTest() { - private val partiesList = listOf(DUMMY_NOTARY, ALICE, BOB) + private val partiesList = listOf(DUMMY_REGULATOR, ALICE, BOB) private val addressesMap = HashMap() - private val infos: MutableSet = HashSet() + private val infos = HashSet() @Before fun start() { @@ -37,8 +37,8 @@ class PersistentNetworkMapCacheTest : NodeBasedTest() { alice.database.transaction { val res = netCache.getNodeByLegalIdentity(alice.info.chooseIdentity()) assertEquals(alice.info, res) - val res2 = netCache.getNodeByLegalName(DUMMY_NOTARY.name) - assertEquals(infos.singleOrNull { DUMMY_NOTARY.name in it.legalIdentitiesAndCerts.map { it.name } }, res2) + val res2 = netCache.getNodeByLegalName(DUMMY_REGULATOR.name) + assertEquals(infos.singleOrNull { DUMMY_REGULATOR.name in it.legalIdentities.map { it.name } }, res2) } } diff --git a/node/src/integration-test/kotlin/net/corda/node/services/statemachine/LargeTransactionsTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/statemachine/LargeTransactionsTest.kt index 46ac098355..9f0212d2cb 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/statemachine/LargeTransactionsTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/statemachine/LargeTransactionsTest.kt @@ -8,7 +8,7 @@ import net.corda.core.messaging.startFlow import net.corda.core.transactions.TransactionBuilder import net.corda.testing.BOB import net.corda.testing.DUMMY_NOTARY -import net.corda.testing.aliceBobAndNotary +import net.corda.testing.aliceAndBob import net.corda.testing.contracts.DummyContract import net.corda.testing.contracts.DummyState import net.corda.testing.driver.driver @@ -65,7 +65,7 @@ class LargeTransactionsTest { val bigFile3 = InputStreamAndHash.createInMemoryTestZip(1024 * 1024 * 3, 2) val bigFile4 = InputStreamAndHash.createInMemoryTestZip(1024 * 1024 * 3, 3) driver(startNodesInProcess = true, extraCordappPackagesToScan = listOf("net.corda.testing.contracts")) { - val (alice, _, _) = aliceBobAndNotary() + val (alice, _) = aliceAndBob() alice.useRPC { val hash1 = it.uploadAttachment(bigFile1.inputStream) val hash2 = it.uploadAttachment(bigFile2.inputStream) diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt index b795962f87..3f27c4e2b2 100644 --- a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt +++ b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt @@ -18,7 +18,6 @@ import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.bouncycastle.asn1.x509.GeneralName import org.bouncycastle.asn1.x509.GeneralSubtree import org.bouncycastle.asn1.x509.NameConstraints -import org.bouncycastle.cert.X509CertificateHolder import org.junit.Test import java.nio.file.Files diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/P2PMessagingTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/P2PMessagingTest.kt index c43185bdeb..6d480bdf87 100644 --- a/node/src/integration-test/kotlin/net/corda/services/messaging/P2PMessagingTest.kt +++ b/node/src/integration-test/kotlin/net/corda/services/messaging/P2PMessagingTest.kt @@ -21,6 +21,8 @@ import net.corda.testing.chooseIdentity import net.corda.testing.driver.DriverDSLExposedInterface import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.driver +import net.corda.testing.node.ClusterSpec +import net.corda.testing.node.NotarySpec import org.assertj.core.api.Assertions.assertThat import org.junit.Test import java.util.* @@ -35,16 +37,14 @@ class P2PMessagingTest { @Test fun `communicating with a distributed service which we're part of`() { - driver(startNodesInProcess = true) { - val distributedService = startDistributedService() + startDriverWithDistributedService { distributedService -> assertAllNodesAreUsed(distributedService, DISTRIBUTED_SERVICE_NAME, distributedService[0]) } } @Test fun `distributed service requests are retried if one of the nodes in the cluster goes down without sending a response`() { - driver(startNodesInProcess = true) { - val distributedServiceNodes = startDistributedService() + startDriverWithDistributedService { distributedServiceNodes -> val alice = startAlice() val serviceAddress = alice.services.networkMapCache.run { val notaryParty = notaryIdentities.randomOrNull()!! @@ -77,8 +77,7 @@ class P2PMessagingTest { @Test fun `distributed service request retries are persisted across client node restarts`() { - driver(startNodesInProcess = true) { - val distributedServiceNodes = startDistributedService() + startDriverWithDistributedService { distributedServiceNodes -> val alice = startAlice() val serviceAddress = alice.services.networkMapCache.run { val notaryParty = notaryIdentities.randomOrNull()!! @@ -117,11 +116,10 @@ class P2PMessagingTest { } } - private fun DriverDSLExposedInterface.startDistributedService(): List> { - return startNotaryCluster(DISTRIBUTED_SERVICE_NAME, 2) - .getOrThrow() - .second - .map { (it as NodeHandle.InProcess).node } + private fun startDriverWithDistributedService(dsl: DriverDSLExposedInterface.(List>) -> Unit) { + driver(startNodesInProcess = true, notarySpecs = listOf(NotarySpec(DISTRIBUTED_SERVICE_NAME, cluster = ClusterSpec.Raft(clusterSize = 2)))) { + dsl(defaultNotaryHandle.nodeHandles.getOrThrow().map { (it as NodeHandle.InProcess).node }) + } } private fun DriverDSLExposedInterface.startAlice(): StartedNode { diff --git a/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt b/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt index 9a71e5c60f..c2355f7723 100644 --- a/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt @@ -7,7 +7,6 @@ import net.corda.core.flows.FlowLogic import net.corda.core.flows.StartableByRPC import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party -import net.corda.core.internal.concurrent.transpose import net.corda.core.messaging.startFlow import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentState @@ -18,14 +17,12 @@ import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.getOrThrow -import net.corda.node.services.FlowPermissions +import net.corda.node.services.Permissions.Companion.invokeRpc +import net.corda.node.services.Permissions.Companion.startFlow import net.corda.nodeapi.User -import net.corda.testing.DUMMY_NOTARY import net.corda.testing.chooseIdentity -import net.corda.testing.driver.DriverDSLExposedInterface -import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.driver -import org.junit.Assume +import org.junit.Assume.assumeFalse import org.junit.Test import java.lang.management.ManagementFactory import javax.persistence.Column @@ -35,30 +32,26 @@ import kotlin.test.assertEquals import kotlin.test.assertNotNull class NodeStatePersistenceTests { - @Test fun `persistent state survives node restart`() { // Temporary disable this test when executed on Windows. It is known to be sporadically failing. // More investigation is needed to establish why. - Assume.assumeFalse(System.getProperty("os.name").toLowerCase().startsWith("win")) + assumeFalse(System.getProperty("os.name").toLowerCase().startsWith("win")) - val user = User("mark", "dadada", setOf(FlowPermissions.startFlowPermission())) + val user = User("mark", "dadada", setOf(startFlow(), invokeRpc("vaultQuery"))) val message = Message("Hello world!") driver(isDebug = true, startNodesInProcess = isQuasarAgentSpecified()) { - val (nodeName, notaryNodeHandle) = { - val notaryNodeHandle = startNotaryNode(DUMMY_NOTARY.name, validating = false).getOrThrow() + val nodeName = { val nodeHandle = startNode(rpcUsers = listOf(user)).getOrThrow() - ensureAcquainted(notaryNodeHandle, nodeHandle) val nodeName = nodeHandle.nodeInfo.chooseIdentity().name nodeHandle.rpcClientToNode().start(user.username, user.password).use { it.proxy.startFlow(::SendMessageFlow, message).returnValue.getOrThrow() } - nodeHandle.stop().getOrThrow() - nodeName to notaryNodeHandle + nodeHandle.stop() + nodeName }() val nodeHandle = startNode(providedName = nodeName, rpcUsers = listOf(user)).getOrThrow() - ensureAcquainted(notaryNodeHandle, nodeHandle) nodeHandle.rpcClientToNode().start(user.username, user.password).use { val page = it.proxy.vaultQuery(MessageState::class.java) val stateAndRef = page.states.singleOrNull() @@ -68,10 +61,6 @@ class NodeStatePersistenceTests { } } } - - private fun DriverDSLExposedInterface.ensureAcquainted(one: NodeHandle, another: NodeHandle) { - listOf(one.pollUntilKnowsAbout(another), another.pollUntilKnowsAbout(one)).transpose().getOrThrow() - } } fun isQuasarAgentSpecified(): Boolean { diff --git a/node/src/main/kotlin/net/corda/node/Corda.kt b/node/src/main/kotlin/net/corda/node/Corda.kt index e1c743cadb..610cb3ff1f 100644 --- a/node/src/main/kotlin/net/corda/node/Corda.kt +++ b/node/src/main/kotlin/net/corda/node/Corda.kt @@ -1,3 +1,4 @@ +// This class is used by the smoke tests as a check that the node module isn't on their classpath @file:JvmName("Corda") package net.corda.node diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index a9af07e7ad..c3cefe6c8e 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -7,6 +7,7 @@ import net.corda.confidential.SwapIdentitiesFlow import net.corda.confidential.SwapIdentitiesHandler import net.corda.core.CordaException import net.corda.core.concurrent.CordaFuture +import net.corda.core.crypto.SignedData import net.corda.core.flows.* import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party @@ -20,6 +21,7 @@ import net.corda.core.node.services.* import net.corda.core.serialization.SerializationWhitelist import net.corda.core.serialization.SerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken +import net.corda.core.serialization.deserialize import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.debug @@ -42,10 +44,9 @@ import net.corda.node.services.events.ScheduledActivityObserver import net.corda.node.services.identity.PersistentIdentityService import net.corda.node.services.keys.PersistentKeyManagementService import net.corda.node.services.messaging.MessagingService -import net.corda.node.services.messaging.sendRequest -import net.corda.node.services.network.* -import net.corda.node.services.network.NetworkMapService.RegistrationRequest -import net.corda.node.services.network.NetworkMapService.RegistrationResponse +import net.corda.node.services.network.NetworkMapCacheImpl +import net.corda.node.services.network.NodeInfoWatcher +import net.corda.node.services.network.PersistentNetworkMapCache import net.corda.node.services.persistence.DBCheckpointStorage import net.corda.node.services.persistence.DBTransactionMappingStorage import net.corda.node.services.persistence.DBTransactionStorage @@ -58,7 +59,6 @@ import net.corda.node.services.upgrade.ContractUpgradeServiceImpl import net.corda.node.services.vault.NodeVaultService import net.corda.node.services.vault.VaultSoftLockManager import net.corda.node.utilities.* -import net.corda.node.utilities.AddOrRemove.ADD import org.apache.activemq.artemis.utils.ReusableLatch import org.slf4j.Logger import rx.Observable @@ -86,18 +86,15 @@ import net.corda.core.crypto.generateKeyPair as cryptoGenerateKeyPair * Marked as SingletonSerializeAsToken to prevent the invisible reference to AbstractNode in the ServiceHub accidentally * sweeping up the Node into the Kryo checkpoint serialization via any flows holding a reference to ServiceHub. */ +// TODO Log warning if this node is a notary but not one of the ones specified in the network parameters, both for core and custom + // In theory the NodeInfo for the node should be passed in, instead, however currently this is constructed by the // AbstractNode. It should be possible to generate the NodeInfo outside of AbstractNode, so it can be passed in. -abstract class AbstractNode(config: NodeConfiguration, +abstract class AbstractNode(val configuration: NodeConfiguration, val platformClock: Clock, protected val versionInfo: VersionInfo, protected val cordappLoader: CordappLoader, - @VisibleForTesting val busyNodeLatch: ReusableLatch = ReusableLatch()) : SingletonSerializeAsToken() { - open val configuration = config.apply { - require(minimumPlatformVersion <= versionInfo.platformVersion) { - "minimumPlatformVersion cannot be greater than the node's own version" - } - } + private val busyNodeLatch: ReusableLatch = ReusableLatch()) : SingletonSerializeAsToken() { private class StartedNodeImpl( override val internals: N, @@ -120,14 +117,12 @@ abstract class AbstractNode(config: NodeConfiguration, // low-performance prototyping period. protected abstract val serverThread: AffinityExecutor + protected lateinit var networkParameters: NetworkParameters private val cordappServices = MutableClassToInstanceMap.create() private val flowFactories = ConcurrentHashMap>, InitiatedFlowFactory<*>>() - protected val partyKeys = mutableSetOf() protected val services: ServiceHubInternal get() = _services private lateinit var _services: ServiceHubInternalImpl - protected lateinit var legalIdentity: PartyAndCertificate - private lateinit var allIdentities: List protected lateinit var info: NodeInfo protected var myNotaryIdentity: PartyAndCertificate? = null protected lateinit var checkpointStorage: CheckpointStorage @@ -158,11 +153,7 @@ abstract class AbstractNode(config: NodeConfiguration, /** The implementation of the [CordaRPCOps] interface used by this node. */ open fun makeRPCOps(flowStarter: FlowStarter): CordaRPCOps { - return CordaRPCOpsImpl(services, smm, database, flowStarter) - } - - private fun saveOwnNodeInfo() { - NodeInfoWatcher.saveToFile(configuration.baseDirectory, info, services.keyManagementService) + return SecureCordaRPCOps(services, smm, database, flowStarter) } private fun initCertificate() { @@ -173,33 +164,34 @@ abstract class AbstractNode(config: NodeConfiguration, validateKeystore() } - private fun makeSchemaService() = NodeSchemaService(cordappLoader) open fun generateNodeInfo() { check(started == null) { "Node has already been started" } - initCertificate() log.info("Generating nodeInfo ...") - val schemaService = makeSchemaService() - initialiseDatabasePersistence(schemaService) { - val transactionStorage = makeTransactionStorage() - makeServices(schemaService, transactionStorage, StateLoaderImpl(transactionStorage)) - saveOwnNodeInfo() - } + initCertificate() + initNodeInfo() } open fun start(): StartedNode { check(started == null) { "Node has already been started" } - initCertificate() log.info("Node starting up ...") - val schemaService = makeSchemaService() + initCertificate() + val keyPairs = initNodeInfo() + readNetworkParameters() + val schemaService = NodeSchemaService(cordappLoader) // Do all of this in a database transaction so anything that might need a connection has one. val startedImpl = initialiseDatabasePersistence(schemaService) { val transactionStorage = makeTransactionStorage() val stateLoader = StateLoaderImpl(transactionStorage) - val services = makeServices(schemaService, transactionStorage, stateLoader) - saveOwnNodeInfo() + val services = makeServices(keyPairs, schemaService, transactionStorage, stateLoader) smm = makeStateMachineManager() val flowStarter = FlowStarterImpl(serverThread, smm) - val schedulerService = NodeSchedulerService(platformClock, this@AbstractNode.database, flowStarter, stateLoader, unfinishedSchedules = busyNodeLatch, serverThread = serverThread) + val schedulerService = NodeSchedulerService( + platformClock, + this@AbstractNode.database, + flowStarter, + stateLoader, + unfinishedSchedules = busyNodeLatch, + serverThread = serverThread) if (serverThread is ExecutorService) { runOnStop += { // We wait here, even though any in-flight messages should have been drained away because the @@ -236,6 +228,35 @@ abstract class AbstractNode(config: NodeConfiguration, } } + private fun initNodeInfo(): Set { + val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null) + val keyPairs = mutableSetOf(identityKeyPair) + + myNotaryIdentity = configuration.notary?.let { + if (it.isClusterConfig) { + val (notaryIdentity, notaryIdentityKeyPair) = obtainIdentity(it) + keyPairs += notaryIdentityKeyPair + notaryIdentity + } else { + // In case of a single notary service myNotaryIdentity will be the node's single identity. + identity + } + } + + info = NodeInfo( + myAddresses(), + setOf(identity, myNotaryIdentity).filterNotNull(), + versionInfo.platformVersion, + platformClock.instant().toEpochMilli() + ) + + NodeInfoWatcher.saveToFile(configuration.baseDirectory, info, identityKeyPair) + + return keyPairs + } + + protected abstract fun myAddresses(): List + protected open fun makeStateMachineManager(): StateMachineManager { return StateMachineManagerImpl( services, @@ -462,23 +483,25 @@ abstract class AbstractNode(config: NodeConfiguration, * Builds node internal, advertised, and plugin services. * Returns a list of tokenizable services to be added to the serialisation context. */ - private fun makeServices(schemaService: SchemaService, transactionStorage: WritableTransactionStorage, stateLoader: StateLoader): MutableList { + private fun makeServices(keyPairs: Set, schemaService: SchemaService, transactionStorage: WritableTransactionStorage, stateLoader: StateLoader): MutableList { checkpointStorage = DBCheckpointStorage() val metrics = MetricRegistry() attachments = NodeAttachmentService(metrics) val cordappProvider = CordappProviderImpl(cordappLoader, attachments) - _services = ServiceHubInternalImpl(schemaService, transactionStorage, stateLoader, MonitoringService(metrics), cordappProvider) - legalIdentity = obtainIdentity(notaryConfig = null) - // TODO We keep only notary identity as additional legalIdentity if we run it on a node . Multiple identities need more design thinking. - myNotaryIdentity = getNotaryIdentity() - allIdentities = listOf(legalIdentity, myNotaryIdentity).filterNotNull() - network = makeMessagingService(legalIdentity) - val addresses = myAddresses() // TODO There is no support for multiple IP addresses yet. - info = NodeInfo(addresses, allIdentities, versionInfo.platformVersion, platformClock.instant().toEpochMilli()) - val networkMapCache = services.networkMapCache + val identityService = makeIdentityService() + val keyManagementService = makeKeyManagementService(identityService, keyPairs) + _services = ServiceHubInternalImpl( + identityService, + keyManagementService, + schemaService, + transactionStorage, + stateLoader, + MonitoringService(metrics), + cordappProvider) + network = makeMessagingService() val tokenizableServices = mutableListOf(attachments, network, services.vaultService, services.keyManagementService, services.identityService, platformClock, - services.auditService, services.monitoringService, networkMapCache, services.schemaService, + services.auditService, services.monitoringService, services.networkMapCache, services.schemaService, services.transactionVerifierService, services.validatedTransactions, services.contractUpgradeService, services, cordappProvider, this) makeNetworkServices(tokenizableServices) @@ -493,12 +516,6 @@ abstract class AbstractNode(config: NodeConfiguration, HibernateObserver.install(services.vaultService.rawUpdates, database.hibernateConfig) } - /** - * Obtain the node's notary identity if it's configured to be one. If part of a distributed notary then this will be - * the distributed identity shared across all the nodes of the cluster. - */ - protected fun getNotaryIdentity(): PartyAndCertificate? = configuration.notary?.let { obtainIdentity(it) } - @VisibleForTesting protected open fun acceptableLiveFiberCountOnStop(): Int = 0 @@ -553,18 +570,6 @@ abstract class AbstractNode(config: NodeConfiguration, } } - private fun sendNetworkMapRegistration(networkMapAddress: SingleMessageRecipient): CordaFuture { - // Register this node against the network - val instant = platformClock.instant() - val expires = instant + NetworkMapService.DEFAULT_EXPIRATION_PERIOD - val reg = NodeRegistration(info, info.serial, ADD, expires) - val request = RegistrationRequest(reg.toWire(services.keyManagementService, info.legalIdentitiesAndCerts.first().owningKey), network.myAddress) - return network.sendRequest(NetworkMapService.REGISTER_TOPIC, request, networkMapAddress) - } - - /** Return list of node's addresses. It's overridden in MockNetwork as we don't have real addresses for MockNodes. */ - protected abstract fun myAddresses(): List - open protected fun checkNetworkMapIsInitialized() { if (!services.networkMapCache.loadDBSuccess ) { // TODO: There should be a consistent approach to configuration error exceptions. @@ -572,11 +577,16 @@ abstract class AbstractNode(config: NodeConfiguration, } } - protected open fun makeKeyManagementService(identityService: IdentityService): KeyManagementService { - return PersistentKeyManagementService(identityService, partyKeys) + protected open fun makeKeyManagementService(identityService: IdentityService, keyPairs: Set): KeyManagementService { + return PersistentKeyManagementService(identityService, keyPairs) } - abstract protected fun makeNetworkMapService(network: MessagingService, networkMapCache: NetworkMapCacheInternal): NetworkMapService + private fun readNetworkParameters() { + val file = configuration.baseDirectory / "network-parameters" + networkParameters = file.readAll().deserialize>().verified() + log.info(networkParameters.toString()) + check(networkParameters.minimumPlatformVersion <= versionInfo.platformVersion) { "Node is too old for the network" } + } private fun makeCoreNotaryService(notaryConfig: NotaryConfig): NotaryService { val notaryKey = myNotaryIdentity?.owningKey ?: throw IllegalArgumentException("No notary identity initialized when creating a notary service") @@ -608,13 +618,13 @@ abstract class AbstractNode(config: NodeConfiguration, } } - protected open fun makeIdentityService(trustRoot: X509Certificate, - clientCa: CertificateAndKeyPair?, - legalIdentity: PartyAndCertificate): IdentityService { - val caCertificates: Array = listOf(legalIdentity.certificate, clientCa?.certificate?.cert) - .filterNotNull() - .toTypedArray() - return PersistentIdentityService(allIdentities, trustRoot = trustRoot, caCertificates = *caCertificates) + private fun makeIdentityService(): IdentityService { + val trustStore = KeyStoreWrapper(configuration.trustStoreFile, configuration.trustStorePassword) + val caKeyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword) + val trustRoot = trustStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA) + val clientCa = caKeyStore.certificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA) + val caCertificates = arrayOf(info.legalIdentitiesAndCerts[0].certificate, clientCa.certificate.cert) + return PersistentIdentityService(info.legalIdentitiesAndCerts, trustRoot = trustRoot, caCertificates = *caCertificates) } protected abstract fun makeTransactionVerifierService(): TransactionVerifierService @@ -634,29 +644,23 @@ abstract class AbstractNode(config: NodeConfiguration, _started = null } - protected abstract fun makeMessagingService(legalIdentity: PartyAndCertificate): MessagingService + protected abstract fun makeMessagingService(): MessagingService protected abstract fun startMessagingService(rpcOps: RPCOps) - private fun obtainIdentity(notaryConfig: NotaryConfig?): PartyAndCertificate { + private fun obtainIdentity(notaryConfig: NotaryConfig?): Pair { val keyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword) - val (id, singleName) = if (notaryConfig == null) { - // Node's main identity + val (id, singleName) = if (notaryConfig == null || !notaryConfig.isClusterConfig) { + // Node's main identity or if it's a single node notary Pair("identity", myLegalName) } else { val notaryId = notaryConfig.run { NotaryService.constructId(validating, raft != null, bftSMaRt != null, custom) } - if (notaryConfig.bftSMaRt == null && notaryConfig.raft == null) { - // Node's notary identity - Pair(notaryId, myLegalName.copy(commonName = notaryId)) - } else { - // The node is part of a distributed notary whose identity must already be generated beforehand - Pair(notaryId, null) - } + // The node is part of a distributed notary whose identity must already be generated beforehand. + Pair(notaryId, null) } - // TODO: Integrate with Key management service? val privateKeyAlias = "$id-private-key" @@ -668,7 +672,7 @@ abstract class AbstractNode(config: NodeConfiguration, keyStore.signAndSaveNewKeyPair(singleName, privateKeyAlias, generateKeyPair()) } - val (x509Cert, keys) = keyStore.certificateAndKeyPair(privateKeyAlias) + val (x509Cert, keyPair) = keyStore.certificateAndKeyPair(privateKeyAlias) // TODO: Use configuration to indicate composite key should be used instead of public key for the identity. val compositeKeyAlias = "$id-composite-key" @@ -694,8 +698,8 @@ abstract class AbstractNode(config: NodeConfiguration, throw ConfigurationException("The name '$singleName' for $id doesn't match what's in the key store: $subject") } - partyKeys += keys - return PartyAndCertificate(CertificateFactory.getInstance("X509").generateCertPath(certificates)) + val certPath = CertificateFactory.getInstance("X509").generateCertPath(certificates) + return Pair(PartyAndCertificate(certPath), keyPair) } protected open fun generateKeyPair() = cryptoGenerateKeyPair() @@ -704,6 +708,11 @@ abstract class AbstractNode(config: NodeConfiguration, } private inner class ServiceHubInternalImpl( + override val identityService: IdentityService, + // Place the long term identity key in the KMS. Eventually, this is likely going to be separated again because + // the KMS is meant for derived temporary keys used in transactions, and we're not supposed to sign things with + // the identity key. But the infrastructure to make that easy isn't here yet. + override val keyManagementService: KeyManagementService, override val schemaService: SchemaService, override val validatedTransactions: WritableTransactionStorage, private val stateLoader: StateLoader, @@ -714,22 +723,16 @@ abstract class AbstractNode(config: NodeConfiguration, override val stateMachineRecordedTransactionMapping = DBTransactionMappingStorage() override val auditService = DummyAuditService() override val transactionVerifierService by lazy { makeTransactionVerifierService() } - override val networkMapCache by lazy { NetworkMapCacheImpl(PersistentNetworkMapCache(this@AbstractNode.database, this@AbstractNode.configuration), identityService) } + override val networkMapCache by lazy { + NetworkMapCacheImpl( + PersistentNetworkMapCache( + this@AbstractNode.database, + this@AbstractNode.configuration, + networkParameters.notaries), + identityService) + } override val vaultService by lazy { makeVaultService(keyManagementService, stateLoader) } override val contractUpgradeService by lazy { ContractUpgradeServiceImpl() } - - // Place the long term identity key in the KMS. Eventually, this is likely going to be separated again because - // the KMS is meant for derived temporary keys used in transactions, and we're not supposed to sign things with - // the identity key. But the infrastructure to make that easy isn't here yet. - override val keyManagementService by lazy { makeKeyManagementService(identityService) } - override val identityService by lazy { - val trustStore = KeyStoreWrapper(configuration.trustStoreFile, configuration.trustStorePassword) - val caKeyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword) - makeIdentityService( - trustStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA), - caKeyStore.certificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA), - legalIdentity) - } override val attachments: AttachmentStorage get() = this@AbstractNode.attachments override val networkService: MessagingService get() = network override val clock: Clock get() = platformClock diff --git a/node/src/main/kotlin/net/corda/node/internal/CordaClock.kt b/node/src/main/kotlin/net/corda/node/internal/CordaClock.kt new file mode 100644 index 0000000000..cd86529f67 --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/internal/CordaClock.kt @@ -0,0 +1,61 @@ +package net.corda.node.internal + +import net.corda.core.serialization.SerializeAsToken +import net.corda.core.serialization.SerializeAsTokenContext +import net.corda.core.serialization.SingletonSerializationToken +import rx.Observable +import rx.Subscriber +import rx.subscriptions.Subscriptions +import java.time.Clock +import java.time.Instant +import java.time.ZoneId +import java.util.concurrent.CopyOnWriteArraySet +import java.util.concurrent.atomic.AtomicLong +import javax.annotation.concurrent.ThreadSafe + +/** A [Clock] that tokenizes itself when serialized, and delegates to an underlying [Clock] implementation. */ +abstract class CordaClock : Clock(), SerializeAsToken { + protected abstract val delegateClock: Clock + private val token = SingletonSerializationToken.singletonSerializationToken(javaClass) + override fun toToken(context: SerializeAsTokenContext) = token.registerWithContext(context, this) + override fun instant(): Instant = delegateClock.instant() + override fun getZone(): ZoneId = delegateClock.zone + @Deprecated("Do not use this. Instead seek to use ZonedDateTime methods.", level = DeprecationLevel.ERROR) + override fun withZone(zone: ZoneId) = throw UnsupportedOperationException("Tokenized clock does not support withZone()") +} + +@ThreadSafe +class SimpleClock(override val delegateClock: Clock) : CordaClock() + +/** + * An abstract class with helper methods for a type of Clock that might have it's concept of "now" adjusted externally. + * e.g. for testing (so unit tests do not have to wait for timeouts in realtime) or for demos and simulations. + */ +abstract class MutableClock(private var _delegateClock: Clock) : CordaClock() { + override var delegateClock + @Synchronized get() = _delegateClock + @Synchronized set(clock) { + _delegateClock = clock + } + private val _version = AtomicLong(0L) + /** This is an observer on the mutation count of this [Clock], which reflects the occurence of mutations. */ + val mutations: Observable by lazy { + Observable.create { subscriber: Subscriber -> + if (!subscriber.isUnsubscribed) { + mutationObservers.add(subscriber) + // This is not very intuitive, but subscribing to a subscriber observes unsubscribes. + subscriber.add(Subscriptions.create { mutationObservers.remove(subscriber) }) + } + } + } + private val mutationObservers = CopyOnWriteArraySet>() + /** Must be called by subclasses when they mutate (but not just with the passage of time as per the "wall clock"). */ + protected fun notifyMutationObservers() { + val version = _version.incrementAndGet() + for (observer in mutationObservers) { + if (!observer.isUnsubscribed) { + observer.onNext(version) + } + } + } +} diff --git a/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt b/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt index 863835c25a..6871c234ba 100644 --- a/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt +++ b/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt @@ -20,11 +20,9 @@ import net.corda.core.node.services.vault.QueryCriteria import net.corda.core.node.services.vault.Sort import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.getOrThrow -import net.corda.node.services.FlowPermissions.Companion.startFlowPermission import net.corda.node.services.api.FlowStarter import net.corda.node.services.api.ServiceHubInternal -import net.corda.node.services.messaging.getRpcContext -import net.corda.node.services.messaging.requirePermission +import net.corda.node.services.messaging.rpcContext import net.corda.node.services.statemachine.StateMachineManager import net.corda.node.utilities.CordaPersistence import rx.Observable @@ -36,7 +34,7 @@ import java.time.Instant * Server side implementations of RPCs available to MQ based client tools. Execution takes place on the server * thread (i.e. serially). Arguments are serialised and deserialised automatically. */ -class CordaRPCOpsImpl( +internal class CordaRPCOpsImpl( private val services: ServiceHubInternal, private val smm: StateMachineManager, private val database: CordaPersistence, @@ -149,9 +147,7 @@ class CordaRPCOpsImpl( private fun startFlow(logicType: Class>, args: Array): FlowStateMachine { require(logicType.isAnnotationPresent(StartableByRPC::class.java)) { "${logicType.name} was not designed for RPC" } - val rpcContext = getRpcContext() - rpcContext.requirePermission(startFlowPermission(logicType)) - val currentUser = FlowInitiator.RPC(rpcContext.currentUser.username) + val currentUser = FlowInitiator.RPC(rpcContext().currentUser.username) // TODO RPC flows should have mapping user -> identity that should be resolved automatically on starting flow. return flowStarter.invokeFlowAsync(logicType, currentUser, *args).getOrThrow() } @@ -221,6 +217,38 @@ class CordaRPCOpsImpl( } } + override fun vaultQuery(contractStateType: Class): Vault.Page { + return vaultQueryBy(QueryCriteria.VaultQueryCriteria(), PageSpecification(), Sort(emptySet()), contractStateType) + } + + override fun vaultQueryByCriteria(criteria: QueryCriteria, contractStateType: Class): Vault.Page { + return vaultQueryBy(criteria, PageSpecification(), Sort(emptySet()), contractStateType) + } + + override fun vaultQueryByWithPagingSpec(contractStateType: Class, criteria: QueryCriteria, paging: PageSpecification): Vault.Page { + return vaultQueryBy(criteria, paging, Sort(emptySet()), contractStateType) + } + + override fun vaultQueryByWithSorting(contractStateType: Class, criteria: QueryCriteria, sorting: Sort): Vault.Page { + return vaultQueryBy(criteria, PageSpecification(), sorting, contractStateType) + } + + override fun vaultTrack(contractStateType: Class): DataFeed, Vault.Update> { + return vaultTrackBy(QueryCriteria.VaultQueryCriteria(), PageSpecification(), Sort(emptySet()), contractStateType) + } + + override fun vaultTrackByCriteria(contractStateType: Class, criteria: QueryCriteria): DataFeed, Vault.Update> { + return vaultTrackBy(criteria, PageSpecification(), Sort(emptySet()), contractStateType) + } + + override fun vaultTrackByWithPagingSpec(contractStateType: Class, criteria: QueryCriteria, paging: PageSpecification): DataFeed, Vault.Update> { + return vaultTrackBy(criteria, paging, Sort(emptySet()), contractStateType) + } + + override fun vaultTrackByWithSorting(contractStateType: Class, criteria: QueryCriteria, sorting: Sort): DataFeed, Vault.Update> { + return vaultTrackBy(criteria, PageSpecification(), sorting, contractStateType) + } + companion object { private fun stateMachineInfoFromFlowLogic(flowLogic: FlowLogic<*>): StateMachineInfo { return StateMachineInfo(flowLogic.runId, flowLogic.javaClass.name, flowLogic.stateMachine.flowInitiator, flowLogic.track()) @@ -233,6 +261,4 @@ class CordaRPCOpsImpl( } } } - -} - +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/internal/DefaultCordaRpcPermissions.kt b/node/src/main/kotlin/net/corda/node/internal/DefaultCordaRpcPermissions.kt new file mode 100644 index 0000000000..dccec894e7 --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/internal/DefaultCordaRpcPermissions.kt @@ -0,0 +1,25 @@ +package net.corda.node.internal + +import net.corda.core.flows.FlowLogic +import net.corda.core.messaging.CordaRPCOps +import net.corda.node.services.Permissions.Companion.all +import net.corda.node.services.Permissions.Companion.startFlow +import net.corda.node.services.Permissions.Companion.invokeRpc +import kotlin.reflect.KVisibility +import kotlin.reflect.full.declaredMemberFunctions + +object DefaultCordaRpcPermissions { + + private val invokePermissions = CordaRPCOps::class.declaredMemberFunctions.filter { it.visibility == KVisibility.PUBLIC }.associate { it.name to setOf(invokeRpc(it), all()) } + private val startFlowPermissions = setOf("startFlow", "startFlowDynamic", "startTrackedFlow", "startTrackedFlowDynamic").associate { it to this::startFlowPermission } + + fun permissionsAllowing(methodName: String, args: List): Set { + + val invoke = invokePermissions[methodName] ?: emptySet() + val start = startFlowPermissions[methodName]?.invoke(args) + return if (start != null) invoke + start else invoke + } + + @Suppress("UNCHECKED_CAST") + private fun startFlowPermission(args: List): String = if (args[0] is Class<*>) startFlow(args[0] as Class>) else startFlow(args[0] as String) +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/internal/EnterpriseNode.kt b/node/src/main/kotlin/net/corda/node/internal/EnterpriseNode.kt index 00f376726e..685a57afc2 100644 --- a/node/src/main/kotlin/net/corda/node/internal/EnterpriseNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/EnterpriseNode.kt @@ -7,7 +7,6 @@ import net.corda.core.utilities.loggerFor import net.corda.node.VersionInfo import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.RelayConfiguration -import net.corda.nodeapi.internal.ServiceInfo import org.fusesource.jansi.Ansi import org.fusesource.jansi.AnsiConsole import java.io.IOException diff --git a/node/src/main/kotlin/net/corda/node/internal/MutableClock.kt b/node/src/main/kotlin/net/corda/node/internal/MutableClock.kt deleted file mode 100644 index 3a52cf377c..0000000000 --- a/node/src/main/kotlin/net/corda/node/internal/MutableClock.kt +++ /dev/null @@ -1,45 +0,0 @@ -package net.corda.node.internal - -import rx.Observable -import rx.Subscriber -import rx.subscriptions.Subscriptions -import java.time.Clock -import java.util.concurrent.CopyOnWriteArraySet -import java.util.concurrent.atomic.AtomicLong - -/** - * An abstract class with helper methods for a type of Clock that might have it's concept of "now" - * adjusted externally. - * - * e.g. for testing (so unit tests do not have to wait for timeouts in realtime) or for demos and simulations. - */ -abstract class MutableClock : Clock() { - private val _version = AtomicLong(0L) - - /** - * This is an observer on the mutation count of this [Clock], which reflects the occurence of mutations. - */ - val mutations: Observable by lazy { - Observable.create({ subscriber: Subscriber -> - if (!subscriber.isUnsubscribed) { - mutationObservers.add(subscriber) - // This is not very intuitive, but subscribing to a subscriber observes unsubscribes. - subscriber.add(Subscriptions.create { mutationObservers.remove(subscriber) }) - } - }) - } - - private val mutationObservers = CopyOnWriteArraySet>() - - /** - * Must be called by subclasses when they mutate (but not just with the passage of time as per the "wall clock"). - */ - protected fun notifyMutationObservers() { - val version = _version.incrementAndGet() - for (observer in mutationObservers) { - if (!observer.isUnsubscribed) { - observer.onNext(version) - } - } - } -} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/internal/Node.kt b/node/src/main/kotlin/net/corda/node/internal/Node.kt index c75ac90ce0..19566eac4f 100644 --- a/node/src/main/kotlin/net/corda/node/internal/Node.kt +++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt @@ -4,50 +4,35 @@ import com.codahale.metrics.JmxReporter import net.corda.core.CordaException import net.corda.core.concurrent.CordaFuture import net.corda.core.identity.CordaX500Name -import net.corda.core.identity.PartyAndCertificate import net.corda.core.internal.concurrent.openFuture import net.corda.core.internal.concurrent.thenMatch import net.corda.core.internal.uncheckedCast import net.corda.core.messaging.RPCOps import net.corda.core.node.ServiceHub import net.corda.core.serialization.SerializationDefaults -import net.corda.core.utilities.* +import net.corda.core.utilities.NetworkHostAndPort +import net.corda.core.utilities.loggerFor import net.corda.node.VersionInfo import net.corda.node.internal.cordapp.CordappLoader import net.corda.node.serialization.KryoServerSerializationScheme -import net.corda.node.serialization.NodeClock import net.corda.node.services.RPCUserService import net.corda.node.services.RPCUserServiceImpl -import net.corda.node.services.api.NetworkMapCacheInternal import net.corda.node.services.api.SchemaService import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.messaging.ArtemisMessagingServer -import net.corda.node.services.messaging.ArtemisMessagingServer.Companion.ipDetectRequestProperty -import net.corda.node.services.messaging.ArtemisMessagingServer.Companion.ipDetectResponseProperty import net.corda.node.services.messaging.MessagingService import net.corda.node.services.messaging.NodeMessagingClient -import net.corda.node.services.network.NetworkMapService -import net.corda.node.services.network.PersistentNetworkMapService import net.corda.node.utilities.AddressUtils import net.corda.node.utilities.AffinityExecutor -import net.corda.node.utilities.TestClock +import net.corda.node.utilities.DemoClock import net.corda.nodeapi.ArtemisMessagingComponent -import net.corda.nodeapi.ArtemisMessagingComponent.Companion.IP_REQUEST_PREFIX -import net.corda.nodeapi.ArtemisMessagingComponent.Companion.PEER_USER -import net.corda.nodeapi.ArtemisTcpTransport -import net.corda.nodeapi.ConnectionDirection import net.corda.nodeapi.internal.ShutdownHook import net.corda.nodeapi.internal.addShutdownHook import net.corda.nodeapi.internal.serialization.* -import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException -import org.apache.activemq.artemis.api.core.RoutingType -import org.apache.activemq.artemis.api.core.client.ActiveMQClient -import org.apache.activemq.artemis.api.core.client.ClientMessage +import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme import org.slf4j.Logger import org.slf4j.LoggerFactory -import java.io.IOException import java.time.Clock -import java.util.* import java.util.concurrent.atomic.AtomicInteger import javax.management.ObjectName import kotlin.system.exitProcess @@ -58,7 +43,7 @@ import kotlin.system.exitProcess * * @param configuration This is typically loaded from a TypeSafe HOCON configuration file. */ -open class Node(override val configuration: NodeConfiguration, +open class Node(configuration: NodeConfiguration, versionInfo: VersionInfo, val initialiseSerialization: Boolean = true, cordappLoader: CordappLoader = makeCordappLoader(configuration) @@ -81,7 +66,7 @@ open class Node(override val configuration: NodeConfiguration, } private fun createClock(configuration: NodeConfiguration): Clock { - return if (configuration.useTestClock) TestClock() else NodeClock() + return (if (configuration.useTestClock) ::DemoClock else ::SimpleClock)(Clock.systemUTC()) } private val sameVmNodeCounter = AtomicInteger() @@ -144,11 +129,11 @@ open class Node(override val configuration: NodeConfiguration, private lateinit var userService: RPCUserService - override fun makeMessagingService(legalIdentity: PartyAndCertificate): MessagingService { + override fun makeMessagingService(): MessagingService { userService = RPCUserServiceImpl(configuration.rpcUsers) val serverAddress = configuration.messagingServerAddress ?: makeLocalMessageBroker() - val advertisedAddress = configuration.messagingServerAddress ?: getAdvertisedAddress() + val advertisedAddress = info.addresses.single() printBasicNodeInfo("Incoming connection address", advertisedAddress.toString()) @@ -156,10 +141,9 @@ open class Node(override val configuration: NodeConfiguration, configuration, versionInfo, serverAddress, - legalIdentity.owningKey, + info.legalIdentities[0].owningKey, serverThread, database, - nodeReadyFuture, services.monitoringService, advertisedAddress) } @@ -171,17 +155,21 @@ open class Node(override val configuration: NodeConfiguration, } } + override fun myAddresses(): List { + return listOf(configuration.messagingServerAddress ?: getAdvertisedAddress()) + } + private fun getAdvertisedAddress(): NetworkHostAndPort { return with(configuration) { if (relay != null) { NetworkHostAndPort(relay!!.relayHost, relay!!.remoteInboundPort) } else { - val useHost = if (detectPublicIp) { + val host = if (detectPublicIp) { tryDetectIfNotPublicHost(p2pAddress.host) ?: p2pAddress.host } else { p2pAddress.host } - NetworkHostAndPort(useHost, p2pAddress.port) + NetworkHostAndPort(host, p2pAddress.port) } } } @@ -203,53 +191,6 @@ open class Node(override val configuration: NodeConfiguration, return null } - /** - * Asks the network map service to provide this node's public IP address: - * - Connects to the network map service's message broker and creates a special IP request queue with a custom - * request id. Marks the established session with the same request id. - * - On the server side a special post-queue-creation callback is fired. It finds the session matching the request id - * encoded in the queue name. It then extracts the remote IP from the session details and posts a message containing - * it back to the queue. - * - Once the message is received the session is closed and the queue deleted. - */ - private fun discoverPublicHost(serverAddress: NetworkHostAndPort): String? { - log.trace { "Trying to detect public hostname through the Network Map Service at $serverAddress" } - val tcpTransport = ArtemisTcpTransport.tcpTransport(ConnectionDirection.Outbound(), serverAddress, configuration) - val locator = ActiveMQClient.createServerLocatorWithoutHA(tcpTransport).apply { - initialConnectAttempts = 2 // TODO Public host discovery needs rewriting, as we may start nodes without network map, and we don't want to wait that long on startup. - retryInterval = 2.seconds.toMillis() - retryIntervalMultiplier = 1.5 - maxRetryInterval = 3.minutes.toMillis() - } - val clientFactory = try { - locator.createSessionFactory() - } catch (e: ActiveMQNotConnectedException) { - log.warn("Unable to connect to the Network Map Service at $serverAddress for IP address discovery. " + - "Using the provided \"${configuration.p2pAddress.host}\" as the advertised address.") - return null - } - - val session = clientFactory.createSession(PEER_USER, PEER_USER, false, true, true, locator.isPreAcknowledge, ActiveMQClient.DEFAULT_ACK_BATCH_SIZE) - val requestId = UUID.randomUUID().toString() - session.addMetaData(ipDetectRequestProperty, requestId) - session.start() - - val queueName = "$IP_REQUEST_PREFIX$requestId" - session.createQueue(queueName, RoutingType.MULTICAST, queueName, false) - - val consumer = session.createConsumer(queueName) - val artemisMessage: ClientMessage = consumer.receive(10.seconds.toMillis()) ?: - throw IOException("Did not receive a response from the Network Map Service at $serverAddress") - val publicHostAndPort = artemisMessage.getStringProperty(ipDetectResponseProperty) - log.info("Detected public address: $publicHostAndPort") - - consumer.close() - session.deleteQueue(queueName) - clientFactory.close() - - return NetworkHostAndPort.parse(publicHostAndPort.removePrefix("/")).host - } - override fun startMessagingService(rpcOps: RPCOps) { // Start up the embedded MQ server messageBroker?.apply { @@ -261,15 +202,6 @@ open class Node(override val configuration: NodeConfiguration, (network as NodeMessagingClient).start(rpcOps, userService) } - override fun myAddresses(): List { - val address = network.myAddress as ArtemisMessagingComponent.ArtemisPeerAddress - return listOf(address.hostAndPort) - } - - override fun makeNetworkMapService(network: MessagingService, networkMapCache: NetworkMapCacheInternal): NetworkMapService { - return PersistentNetworkMapService(network, networkMapCache, configuration.minimumPlatformVersion) - } - /** * If the node is persisting to an embedded H2 database, then expose this via TCP with a JDBC URL of the form: * jdbc:h2:tcp://:/node diff --git a/node/src/main/kotlin/net/corda/node/internal/RpcAuthorisationProxy.kt b/node/src/main/kotlin/net/corda/node/internal/RpcAuthorisationProxy.kt new file mode 100644 index 0000000000..c99103b06d --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/internal/RpcAuthorisationProxy.kt @@ -0,0 +1,158 @@ +package net.corda.node.internal + +import net.corda.core.contracts.ContractState +import net.corda.core.crypto.SecureHash +import net.corda.core.flows.FlowLogic +import net.corda.core.identity.AbstractParty +import net.corda.core.identity.CordaX500Name +import net.corda.core.identity.Party +import net.corda.core.messaging.CordaRPCOps +import net.corda.core.messaging.DataFeed +import net.corda.core.node.NodeInfo +import net.corda.core.node.services.NetworkMapCache +import net.corda.core.node.services.Vault +import net.corda.core.node.services.vault.PageSpecification +import net.corda.core.node.services.vault.QueryCriteria +import net.corda.core.node.services.vault.Sort +import net.corda.node.services.messaging.RpcContext +import net.corda.node.services.messaging.requireEitherPermission +import java.io.InputStream +import java.security.PublicKey + +// TODO change to KFunction reference after Kotlin fixes https://youtrack.jetbrains.com/issue/KT-12140 +class RpcAuthorisationProxy(private val implementation: CordaRPCOps, private val context: () -> RpcContext, private val permissionsAllowing: (methodName: String, args: List) -> Set) : CordaRPCOps { + + override fun stateMachinesSnapshot() = guard("stateMachinesSnapshot") { + implementation.stateMachinesSnapshot() + } + + override fun stateMachinesFeed() = guard("stateMachinesFeed") { + implementation.stateMachinesFeed() + } + + override fun vaultQueryBy(criteria: QueryCriteria, paging: PageSpecification, sorting: Sort, contractStateType: Class) = guard("vaultQueryBy") { + implementation.vaultQueryBy(criteria, paging, sorting, contractStateType) + } + + override fun vaultTrackBy(criteria: QueryCriteria, paging: PageSpecification, sorting: Sort, contractStateType: Class) = guard("vaultTrackBy") { + implementation.vaultTrackBy(criteria, paging, sorting, contractStateType) + } + + override fun internalVerifiedTransactionsSnapshot() = guard("internalVerifiedTransactionsSnapshot", implementation::internalVerifiedTransactionsSnapshot) + + override fun internalVerifiedTransactionsFeed() = guard("internalVerifiedTransactionsFeed", implementation::internalVerifiedTransactionsFeed) + + override fun stateMachineRecordedTransactionMappingSnapshot() = guard("stateMachineRecordedTransactionMappingSnapshot", implementation::stateMachineRecordedTransactionMappingSnapshot) + + override fun stateMachineRecordedTransactionMappingFeed() = guard("stateMachineRecordedTransactionMappingFeed", implementation::stateMachineRecordedTransactionMappingFeed) + + override fun networkMapSnapshot(): List = guard("networkMapSnapshot", implementation::networkMapSnapshot) + + override fun networkMapFeed(): DataFeed, NetworkMapCache.MapChange> = guard("networkMapFeed", implementation::networkMapFeed) + + override fun startFlowDynamic(logicType: Class>, vararg args: Any?) = guard("startFlowDynamic", listOf(logicType)) { + implementation.startFlowDynamic(logicType, *args) + } + + override fun startTrackedFlowDynamic(logicType: Class>, vararg args: Any?) = guard("startTrackedFlowDynamic", listOf(logicType)) { + implementation.startTrackedFlowDynamic(logicType, *args) + } + + override fun nodeInfo(): NodeInfo = guard("nodeInfo", implementation::nodeInfo) + + override fun notaryIdentities(): List = guard("notaryIdentities", implementation::notaryIdentities) + + override fun addVaultTransactionNote(txnId: SecureHash, txnNote: String) = guard("addVaultTransactionNote") { + implementation.addVaultTransactionNote(txnId, txnNote) + } + + override fun getVaultTransactionNotes(txnId: SecureHash): Iterable = guard("getVaultTransactionNotes") { + implementation.getVaultTransactionNotes(txnId) + } + + override fun attachmentExists(id: SecureHash) = guard("attachmentExists") { + implementation.attachmentExists(id) + } + + override fun openAttachment(id: SecureHash) = guard("openAttachment") { + implementation.openAttachment(id) + } + + override fun uploadAttachment(jar: InputStream) = guard("uploadAttachment") { + implementation.uploadAttachment(jar) + } + + override fun currentNodeTime() = guard("currentNodeTime", implementation::currentNodeTime) + + override fun waitUntilNetworkReady() = guard("waitUntilNetworkReady", implementation::waitUntilNetworkReady) + + override fun wellKnownPartyFromAnonymous(party: AbstractParty) = guard("wellKnownPartyFromAnonymous") { + implementation.wellKnownPartyFromAnonymous(party) + } + + override fun partyFromKey(key: PublicKey) = guard("partyFromKey") { + implementation.partyFromKey(key) + } + + override fun wellKnownPartyFromX500Name(x500Name: CordaX500Name) = guard("wellKnownPartyFromX500Name") { + implementation.wellKnownPartyFromX500Name(x500Name) + } + + override fun notaryPartyFromX500Name(x500Name: CordaX500Name) = guard("notaryPartyFromX500Name") { + implementation.notaryPartyFromX500Name(x500Name) + } + + override fun partiesFromName(query: String, exactMatch: Boolean) = guard("partiesFromName") { + implementation.partiesFromName(query, exactMatch) + } + + override fun registeredFlows() = guard("registeredFlows", implementation::registeredFlows) + + override fun nodeInfoFromParty(party: AbstractParty) = guard("nodeInfoFromParty") { + implementation.nodeInfoFromParty(party) + } + + override fun clearNetworkMapCache() = guard("clearNetworkMapCache", implementation::clearNetworkMapCache) + + override fun vaultQuery(contractStateType: Class): Vault.Page = guard("vaultQuery") { + implementation.vaultQuery(contractStateType) + } + + override fun vaultQueryByCriteria(criteria: QueryCriteria, contractStateType: Class): Vault.Page = guard("vaultQueryByCriteria") { + implementation.vaultQueryByCriteria(criteria, contractStateType) + } + + override fun vaultQueryByWithPagingSpec(contractStateType: Class, criteria: QueryCriteria, paging: PageSpecification): Vault.Page = guard("vaultQueryByWithPagingSpec") { + implementation.vaultQueryByWithPagingSpec(contractStateType, criteria, paging) + } + + override fun vaultQueryByWithSorting(contractStateType: Class, criteria: QueryCriteria, sorting: Sort): Vault.Page = guard("vaultQueryByWithSorting") { + implementation.vaultQueryByWithSorting(contractStateType, criteria, sorting) + } + + override fun vaultTrack(contractStateType: Class): DataFeed, Vault.Update> = guard("vaultTrack") { + implementation.vaultTrack(contractStateType) + } + + override fun vaultTrackByCriteria(contractStateType: Class, criteria: QueryCriteria): DataFeed, Vault.Update> = guard("vaultTrackByCriteria") { + implementation.vaultTrackByCriteria(contractStateType, criteria) + } + + override fun vaultTrackByWithPagingSpec(contractStateType: Class, criteria: QueryCriteria, paging: PageSpecification): DataFeed, Vault.Update> = guard("vaultTrackByWithPagingSpec") { + implementation.vaultTrackByWithPagingSpec(contractStateType, criteria, paging) + } + + override fun vaultTrackByWithSorting(contractStateType: Class, criteria: QueryCriteria, sorting: Sort): DataFeed, Vault.Update> = guard("vaultTrackByWithSorting") { + implementation.vaultTrackByWithSorting(contractStateType, criteria, sorting) + } + + // TODO change to KFunction reference after Kotlin fixes https://youtrack.jetbrains.com/issue/KT-12140 + private inline fun guard(methodName: String, action: () -> RESULT) = guard(methodName, emptyList(), action) + + // TODO change to KFunction reference after Kotlin fixes https://youtrack.jetbrains.com/issue/KT-12140 + private inline fun guard(methodName: String, args: List, action: () -> RESULT): RESULT { + + context.invoke().requireEitherPermission(permissionsAllowing.invoke(methodName, args)) + return action.invoke() + } +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/internal/SecureCordaRPCOps.kt b/node/src/main/kotlin/net/corda/node/internal/SecureCordaRPCOps.kt new file mode 100644 index 0000000000..7083a434d2 --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/internal/SecureCordaRPCOps.kt @@ -0,0 +1,24 @@ +package net.corda.node.internal + +import net.corda.core.messaging.CordaRPCOps +import net.corda.node.services.api.FlowStarter +import net.corda.node.services.api.ServiceHubInternal +import net.corda.node.services.messaging.rpcContext +import net.corda.node.services.statemachine.StateMachineManager +import net.corda.node.utilities.CordaPersistence + +/** + * Implementation of [CordaRPCOps] that checks authorisation. + */ +class SecureCordaRPCOps(services: ServiceHubInternal, + smm: StateMachineManager, + database: CordaPersistence, + flowStarter: FlowStarter, + val unsafe: CordaRPCOps = CordaRPCOpsImpl(services, smm, database, flowStarter)) : CordaRPCOps by RpcAuthorisationProxy(unsafe, ::rpcContext, DefaultCordaRpcPermissions::permissionsAllowing) { + + /** + * Returns the RPC protocol version, which is the same the node's Platform Version. Exists since version 1 so guaranteed + * to be present. + */ + override val protocolVersion: Int get() = unsafe.nodeInfo().platformVersion +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/serialization/KryoServerSerializationScheme.kt b/node/src/main/kotlin/net/corda/node/serialization/KryoServerSerializationScheme.kt index 319312689b..09f876d937 100644 --- a/node/src/main/kotlin/net/corda/node/serialization/KryoServerSerializationScheme.kt +++ b/node/src/main/kotlin/net/corda/node/serialization/KryoServerSerializationScheme.kt @@ -4,10 +4,10 @@ import com.esotericsoftware.kryo.pool.KryoPool import net.corda.core.serialization.SerializationContext import net.corda.core.utilities.ByteSequence import net.corda.node.services.messaging.RpcServerObservableSerializer -import net.corda.nodeapi.internal.serialization.AbstractKryoSerializationScheme -import net.corda.nodeapi.internal.serialization.DefaultKryoCustomizer -import net.corda.nodeapi.internal.serialization.KryoHeaderV0_1 -import net.corda.nodeapi.internal.serialization.RPCKryo +import net.corda.nodeapi.internal.serialization.kryo.AbstractKryoSerializationScheme +import net.corda.nodeapi.internal.serialization.kryo.DefaultKryoCustomizer +import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1 +import net.corda.nodeapi.internal.serialization.kryo.RPCKryo class KryoServerSerializationScheme : AbstractKryoSerializationScheme() { override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean { diff --git a/node/src/main/kotlin/net/corda/node/serialization/NodeClock.kt b/node/src/main/kotlin/net/corda/node/serialization/NodeClock.kt deleted file mode 100644 index 313c3e14bc..0000000000 --- a/node/src/main/kotlin/net/corda/node/serialization/NodeClock.kt +++ /dev/null @@ -1,36 +0,0 @@ -package net.corda.node.serialization - -import net.corda.core.serialization.SerializeAsToken -import net.corda.core.serialization.SerializeAsTokenContext -import net.corda.core.serialization.SingletonSerializationToken -import net.corda.core.serialization.SingletonSerializationToken.Companion.singletonSerializationToken -import java.time.Clock -import java.time.Instant -import java.time.ZoneId -import javax.annotation.concurrent.ThreadSafe - - -/** - * A [Clock] that tokenizes itself when serialized, and delegates to an underlying [Clock] implementation. - */ -@ThreadSafe -class NodeClock(private val delegateClock: Clock = Clock.systemUTC()) : Clock(), SerializeAsToken { - - private val token = singletonSerializationToken(javaClass) - - override fun toToken(context: SerializeAsTokenContext) = token.registerWithContext(context, this) - - override fun instant(): Instant { - return delegateClock.instant() - } - - // Do not use this. Instead seek to use ZonedDateTime methods. - override fun withZone(zone: ZoneId): Clock { - throw UnsupportedOperationException("Tokenized clock does not support withZone()") - } - - override fun getZone(): ZoneId { - return delegateClock.zone - } - -} diff --git a/node/src/main/kotlin/net/corda/node/services/Permissions.kt b/node/src/main/kotlin/net/corda/node/services/Permissions.kt new file mode 100644 index 0000000000..f87060fba1 --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/services/Permissions.kt @@ -0,0 +1,59 @@ +package net.corda.node.services + +import net.corda.core.flows.FlowLogic +import kotlin.reflect.KFunction + +/** + * Helper class for creating permissions. + */ +class Permissions { + + companion object { + + /** + * Global admin permissions. + */ + @JvmStatic + fun all() = "ALL" + + /** + * Creates the flow permission string of the format "StartFlow.{ClassName}". + * + * @param className a flow class name for which permission is created. + */ + @JvmStatic + fun startFlow(className: String) = "StartFlow.$className" + + /** + * An overload for the [startFlow] + * + * @param clazz a class for which permission is created. + */ + @JvmStatic + fun

> startFlow(clazz: Class

) = startFlow(clazz.name) + + /** + * An overload for the [startFlow]. + * + * @param P a class for which permission is created. + */ + @JvmStatic + inline fun > startFlow(): String = startFlow(P::class.java) + + /** + * Creates a permission string with format "InvokeRpc.{MethodName}". + * + * @param methodName a RPC method name for which permission is created. + */ + @JvmStatic + fun invokeRpc(methodName: String) = "InvokeRpc.$methodName" + + /** + * Creates a permission string with format "InvokeRpc.{method.name}". + * + * @param method a RPC [KFunction] for which permission is created. + */ + @JvmStatic + fun invokeRpc(method: KFunction<*>) = invokeRpc(method.name) + } +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/services/RPCUserService.kt b/node/src/main/kotlin/net/corda/node/services/RPCUserService.kt index 58a50dceb7..371815ed94 100644 --- a/node/src/main/kotlin/net/corda/node/services/RPCUserService.kt +++ b/node/src/main/kotlin/net/corda/node/services/RPCUserService.kt @@ -1,6 +1,5 @@ package net.corda.node.services -import net.corda.core.flows.FlowLogic import net.corda.nodeapi.User /** @@ -26,36 +25,3 @@ class RPCUserServiceImpl(override val users: List) : RPCUserService { override fun getUser(username: String): User? = users.find { it.username == username } } - -/** - * Helper class for creating flow class permissions. - */ -class FlowPermissions { - companion object { - - /** - * Creates the flow permission string of the format "StartFlow.{ClassName}". - * - * @param className a flow class name for which permission is created. - */ - @JvmStatic - fun startFlowPermission(className: String) = "StartFlow.$className" - - /** - * An overload for the [startFlowPermission] - * - * @param clazz a class for which permission is created. - * - */ - @JvmStatic - fun

> startFlowPermission(clazz: Class

) = startFlowPermission(clazz.name) - - /** - * An overload for the [startFlowPermission]. - * - * @param P a class for which permission is created. - */ - @JvmStatic - inline fun > startFlowPermission(): String = startFlowPermission(P::class.java) - } -} diff --git a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt index f2d888d54d..d54deb153a 100644 --- a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt +++ b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt @@ -1,6 +1,5 @@ package net.corda.node.services.api -import net.corda.core.CordaException import net.corda.core.concurrent.CordaFuture import net.corda.core.crypto.SecureHash import net.corda.core.flows.FlowInitiator @@ -18,7 +17,6 @@ import net.corda.core.node.StatesToRecord import net.corda.core.node.services.NetworkMapCache import net.corda.core.node.services.NetworkMapCacheBase import net.corda.core.node.services.TransactionStorage -import net.corda.core.serialization.CordaSerializable import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.loggerFor @@ -42,12 +40,6 @@ interface NetworkMapCacheBaseInternal : NetworkMapCacheBase { val loadDBSuccess: Boolean } -@CordaSerializable -sealed class NetworkCacheException : CordaException("Network Cache Error") { - /** Indicates a failure to deregister, because of a rejected request from the remote node */ - class DeregistrationFailed : NetworkCacheException() -} - interface ServiceHubInternal : ServiceHub { companion object { private val log = loggerFor() diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt index bfc0947bd2..db3ae31888 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt @@ -18,7 +18,6 @@ interface NodeConfiguration : NodeSSLConfiguration { // myLegalName should be only used in the initial network registration, we should use the name from the certificate instead of this. // TODO: Remove this so we don't accidentally use this identity in the code? val myLegalName: CordaX500Name - val minimumPlatformVersion: Int val emailAddress: String val exportJMXto: String val dataSourceProperties: Properties @@ -53,15 +52,17 @@ data class NotaryConfig(val validating: Boolean, "raft, bftSMaRt, and custom configs cannot be specified together" } } + val isClusterConfig: Boolean get() = raft != null || bftSMaRt != null } data class RaftConfig(val nodeAddress: NetworkHostAndPort, val clusterAddresses: List) /** @param exposeRaces for testing only, so its default is not in reference.conf but here. */ -data class BFTSMaRtConfiguration constructor(val replicaId: Int, - val clusterAddresses: List, - val debug: Boolean = false, - val exposeRaces: Boolean = false +data class BFTSMaRtConfiguration( + val replicaId: Int, + val clusterAddresses: List, + val debug: Boolean = false, + val exposeRaces: Boolean = false ) { init { require(replicaId >= 0) { "replicaId cannot be negative" } @@ -86,7 +87,6 @@ data class NodeConfigurationImpl( override val dataSourceProperties: Properties, override val database: Properties?, override val certificateSigningService: URL, - override val minimumPlatformVersion: Int = 1, override val rpcUsers: List, override val verifierType: VerifierType, // TODO typesafe config supports the notion of durations. Make use of that by mapping it to java.time.Duration. @@ -115,8 +115,6 @@ data class NodeConfigurationImpl( // This is a sanity feature do not remove. require(!useTestClock || devMode) { "Cannot use test clock outside of dev mode" } require(devModeOptions == null || devMode) { "Cannot use devModeOptions outside of dev mode" } - require(myLegalName.commonName == null) { "Common name must be null: $myLegalName" } - require(minimumPlatformVersion >= 1) { "minimumPlatformVersion cannot be less than 1" } } } diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt index 12951716c9..449b232aec 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt @@ -116,7 +116,7 @@ class ArtemisMessagingServer(override val config: NodeConfiguration, /** * The server will make sure the bridge exists on network map changes, see method [updateBridgesOnNetworkChange] - * We assume network map will be updated accordingly when the client node register with the network map server. + * We assume network map will be updated accordingly when the client node register with the network map. */ @Throws(IOException::class, KeyStoreException::class) fun start() = mutex.locked { diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt b/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt index d9b0aa0f13..39becee90a 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt @@ -1,11 +1,8 @@ package net.corda.node.services.messaging -import net.corda.core.concurrent.CordaFuture import net.corda.core.crypto.random63BitValue import net.corda.core.identity.CordaX500Name import net.corda.core.internal.ThreadBox -import net.corda.core.internal.concurrent.andForget -import net.corda.core.internal.concurrent.thenMatch import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.MessageRecipients import net.corda.core.messaging.RPCOps @@ -16,7 +13,10 @@ import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.transactions.LedgerTransaction -import net.corda.core.utilities.* +import net.corda.core.utilities.NetworkHostAndPort +import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.sequence +import net.corda.core.utilities.trace import net.corda.node.VersionInfo import net.corda.node.services.RPCUserService import net.corda.node.services.api.MonitoringService @@ -65,8 +65,7 @@ import javax.persistence.Lob * CordaRPCClient class. * * @param serverAddress The address of the broker instance to connect to (might be running in the same process). - * @param myIdentity Either the public key to be used as the ArtemisMQ address and queue name for the node globally, or null to indicate - * that this is a NetworkMapService node which will be bound globally to the name "networkmap". + * @param myIdentity The public key to be used as the ArtemisMQ address and queue name for the node. * @param nodeExecutor An executor to run received message tasks upon. * @param advertisedAddress The node address for inbound connections, advertised to the network map service and peers. * If not provided, will default to [serverAddress]. @@ -78,7 +77,6 @@ class NodeMessagingClient(override val config: NodeConfiguration, private val myIdentity: PublicKey, private val nodeExecutor: AffinityExecutor.ServiceAffinityExecutor, val database: CordaPersistence, - private val networkMapRegistrationFuture: CordaFuture, val monitoringService: MonitoringService, advertisedAddress: NetworkHostAndPort = serverAddress ) : ArtemisMessagingComponent(), MessagingService { @@ -234,18 +232,7 @@ class NodeMessagingClient(override val config: NodeConfiguration, this.producer = producer // Create a queue, consumer and producer for handling P2P network messages. - p2pConsumer = makeP2PConsumer(session, true) - networkMapRegistrationFuture.thenMatch({ - state.locked { - log.info("Network map is complete, so removing filter from P2P consumer.") - try { - p2pConsumer!!.close() - } catch (e: ActiveMQObjectClosedException) { - // Ignore it: this can happen if the server has gone away before we do. - } - p2pConsumer = makeP2PConsumer(session, false) - } - }, {}) + p2pConsumer = session.createConsumer(P2P_QUEUE) val myCert = loadKeyStore(config.sslKeystore, config.keyStorePassword).getX509Certificate(X509Utilities.CORDA_CLIENT_TLS) rpcServer = RPCServer(rpcOps, NODE_USER, NODE_USER, locator, userService, CordaX500Name.build(myCert.subjectX500Principal)) @@ -267,20 +254,6 @@ class NodeMessagingClient(override val config: NodeConfiguration, resumeMessageRedelivery() } - /** - * We make the consumer twice, once to filter for just network map messages, and then once that is complete, we close - * the original and make another without a filter. We do this so that there is a network map in place for all other - * message handlers. - */ - private fun makeP2PConsumer(session: ClientSession, networkMapOnly: Boolean): ClientConsumer { - return if (networkMapOnly) { - // Filter for just the network map messages. - val messageFilter = "hyphenated_props:$topicProperty like 'platform.network_map.%'" - session.createConsumer(P2P_QUEUE, messageFilter) - } else - session.createConsumer(P2P_QUEUE) - } - private fun resumeMessageRedelivery() { messagesToRedeliver.forEach { retryId, (message, target) -> send(message, target, retryId) @@ -325,46 +298,22 @@ class NodeMessagingClient(override val config: NodeConfiguration, return true } - private fun runPreNetworkMap(serverControl: ActiveMQServerControl) { - val consumer = state.locked { - check(started) { "start must be called first" } - check(!running) { "run can't be called twice" } - running = true - rpcServer!!.start(serverControl) - (verifierService as? OutOfProcessTransactionVerifierService)?.start(verificationResponseConsumer!!) - p2pConsumer!! - } - - while (!networkMapRegistrationFuture.isDone && processMessage(consumer)) { - } - with(networkMapRegistrationFuture) { - if (isDone) getOrThrow() else andForget(log) // Trigger node shutdown here to avoid deadlock in shutdown hooks. - } - } - - private fun runPostNetworkMap() { - val consumer = state.locked { - // If it's null, it means we already called stop, so return immediately. - p2pConsumer ?: return - } - - while (processMessage(consumer)) { - } - } - /** * Starts the p2p event loop: this method only returns once [stop] has been called. - * - * This actually runs as two sequential loops. The first subscribes for and receives only network map messages until - * we get our network map fetch response. At that point the filtering consumer is closed and we proceed to the second loop and - * consume all messages via a new consumer without a filter applied. */ fun run(serverControl: ActiveMQServerControl) { try { - // Build the network map. - runPreNetworkMap(serverControl) - // Process everything else once we have the network map. - runPostNetworkMap() + val consumer = state.locked { + check(started) { "start must be called first" } + check(!running) { "run can't be called twice" } + running = true + rpcServer!!.start(serverControl) + (verifierService as? OutOfProcessTransactionVerifierService)?.start(verificationResponseConsumer!!) + // If it's null, it means we already called stop, so return immediately. + p2pConsumer ?: return + } + + while (processMessage(consumer)) { } } finally { shutdownLatch.countDown() } diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt b/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt index ae4f328378..d74ffa35c6 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt @@ -373,7 +373,7 @@ internal val CURRENT_RPC_CONTEXT: ThreadLocal = ThreadLocal() * throw. If you'd like to use the context outside of the call (e.g. in another thread) then pass the returned reference * around explicitly. */ -fun getRpcContext(): RpcContext = CURRENT_RPC_CONTEXT.get() +fun rpcContext(): RpcContext = CURRENT_RPC_CONTEXT.get() /** * @param currentUser This is available to RPC implementations to query the validated [User] that is calling it. Each diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/RPCServerStructures.kt b/node/src/main/kotlin/net/corda/node/services/messaging/RPCServerStructures.kt index e8e91d793f..6e68a81687 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/RPCServerStructures.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/RPCServerStructures.kt @@ -3,13 +3,18 @@ package net.corda.node.services.messaging import net.corda.client.rpc.PermissionException +import net.corda.node.services.Permissions.Companion.all import net.corda.nodeapi.ArtemisMessagingComponent /** Helper method which checks that the current RPC user is entitled for the given permission. Throws a [PermissionException] otherwise. */ -fun RpcContext.requirePermission(permission: String) { +fun RpcContext.requirePermission(permission: String): RpcContext = requireEitherPermission(setOf(permission)) + +/** Helper method which checks that the current RPC user is entitled with any of the given permissions. Throws a [PermissionException] otherwise. */ +fun RpcContext.requireEitherPermission(permissions: Set): RpcContext { // TODO remove the NODE_USER condition once webserver doesn't need it val currentUserPermissions = currentUser.permissions - if (currentUser.username != ArtemisMessagingComponent.NODE_USER && currentUserPermissions.intersect(listOf(permission, "ALL")).isEmpty()) { - throw PermissionException("User not permissioned for $permission, permissions are $currentUserPermissions") + if (currentUser.username != ArtemisMessagingComponent.NODE_USER && currentUserPermissions.intersect(permissions + all()).isEmpty()) { + throw PermissionException("User not permissioned with any of $permissions, permissions are $currentUserPermissions") } -} + return this +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapService.kt b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapService.kt deleted file mode 100644 index 06b04e55a7..0000000000 --- a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapService.kt +++ /dev/null @@ -1,368 +0,0 @@ -package net.corda.node.services.network - -import net.corda.core.CordaException -import net.corda.core.crypto.DigitalSignature -import net.corda.core.crypto.SignedData -import net.corda.core.crypto.isFulfilledBy -import net.corda.core.crypto.random63BitValue -import net.corda.core.identity.PartyAndCertificate -import net.corda.core.internal.ThreadBox -import net.corda.core.internal.VisibleForTesting -import net.corda.core.messaging.MessageRecipients -import net.corda.core.messaging.SingleMessageRecipient -import net.corda.core.node.NodeInfo -import net.corda.core.node.services.KeyManagementService -import net.corda.core.node.services.NetworkMapCache -import net.corda.core.serialization.CordaSerializable -import net.corda.core.serialization.SerializedBytes -import net.corda.core.serialization.deserialize -import net.corda.core.serialization.serialize -import net.corda.core.utilities.debug -import net.corda.core.utilities.loggerFor -import net.corda.node.services.api.AbstractNodeService -import net.corda.node.services.api.NetworkMapCacheInternal -import net.corda.node.services.messaging.MessageHandlerRegistration -import net.corda.node.services.messaging.MessagingService -import net.corda.node.services.messaging.ServiceRequestMessage -import net.corda.node.services.messaging.createMessage -import net.corda.node.services.network.NetworkMapService.* -import net.corda.node.services.network.NetworkMapService.Companion.FETCH_TOPIC -import net.corda.node.services.network.NetworkMapService.Companion.PUSH_ACK_TOPIC -import net.corda.node.services.network.NetworkMapService.Companion.PUSH_TOPIC -import net.corda.node.services.network.NetworkMapService.Companion.QUERY_TOPIC -import net.corda.node.services.network.NetworkMapService.Companion.REGISTER_TOPIC -import net.corda.node.services.network.NetworkMapService.Companion.SUBSCRIPTION_TOPIC -import net.corda.node.utilities.AddOrRemove -import net.corda.node.utilities.AddOrRemove.ADD -import net.corda.node.utilities.AddOrRemove.REMOVE -import java.io.IOException -import java.security.PublicKey -import java.security.SignatureException -import java.time.Instant -import java.time.Period -import java.util.* -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.atomic.AtomicInteger -import javax.annotation.concurrent.ThreadSafe - -/** - * A network map contains lists of nodes on the network along with information about their identity keys, services - * they provide and host names or IP addresses where they can be connected to. This information is cached locally within - * nodes, by the [NetworkMapCache]. Currently very basic consensus controls are applied, using signed changes which - * replace each other based on a serial number present in the change. - */ -// TODO: A better architecture for the network map service might be one like the Tor directory authorities, where -// several nodes linked by RAFT or Paxos elect a leader and that leader distributes signed documents describing the -// network layout. Those documents can then be cached by every node and thus a network map can/ be retrieved given only -// a single successful peer connection. -// -// It may also be that this is replaced or merged with the identity management service; for example if the network has -// a concept of identity changes over time, should that include the node for an identity? If so, that is likely to -// replace this service. -interface NetworkMapService { - - companion object { - val DEFAULT_EXPIRATION_PERIOD: Period = Period.ofWeeks(4) - const val FETCH_TOPIC = "platform.network_map.fetch" - const val QUERY_TOPIC = "platform.network_map.query" - const val REGISTER_TOPIC = "platform.network_map.register" - const val SUBSCRIPTION_TOPIC = "platform.network_map.subscribe" - // Base topic used when pushing out updates to the network map. Consumed, for example, by the map cache. - // When subscribing to these updates, remember they must be acknowledged - const val PUSH_TOPIC = "platform.network_map.push" - // Base topic for messages acknowledging pushed updates - const val PUSH_ACK_TOPIC = "platform.network_map.push_ack" - } - - data class FetchMapRequest(val subscribe: Boolean, - val ifChangedSinceVersion: Int?, - override val replyTo: SingleMessageRecipient, - override val sessionID: Long = random63BitValue()) : ServiceRequestMessage - - @CordaSerializable - data class FetchMapResponse(val nodes: List?, val version: Int) - - data class QueryIdentityRequest(val identity: PartyAndCertificate, - override val replyTo: SingleMessageRecipient, - override val sessionID: Long = random63BitValue()) : ServiceRequestMessage - - @CordaSerializable - data class QueryIdentityResponse(val node: NodeInfo?) - - // TODO Rename this RegistractionChangeRequest or similar (and related classes) - data class RegistrationRequest(val wireReg: WireNodeRegistration, - override val replyTo: SingleMessageRecipient, - override val sessionID: Long = random63BitValue()) : ServiceRequestMessage - - /** If [error] is null then the registration was successful. If not null then it wasn't and it explains why */ - @CordaSerializable - data class RegistrationResponse(val error: String?) - - data class SubscribeRequest(val subscribe: Boolean, - override val replyTo: SingleMessageRecipient, - override val sessionID: Long = random63BitValue()) : ServiceRequestMessage - - @CordaSerializable - data class SubscribeResponse(val confirmed: Boolean) - - @CordaSerializable - data class Update(val wireReg: WireNodeRegistration, val mapVersion: Int, val replyTo: MessageRecipients) - - @CordaSerializable - data class UpdateAcknowledge(val mapVersion: Int, val replyTo: MessageRecipients) -} - -object NullNetworkMapService : NetworkMapService - -@ThreadSafe -class InMemoryNetworkMapService(network: MessagingService, networkMapCache: NetworkMapCacheInternal, minimumPlatformVersion: Int) - : AbstractNetworkMapService(network, networkMapCache, minimumPlatformVersion) { - - override val nodeRegistrations: MutableMap = ConcurrentHashMap() - override val subscribers = ThreadBox(mutableMapOf()) - - init { - setup() - } -} - -/** - * Abstracted out core functionality as the basis for a persistent implementation, as well as existing in-memory implementation. - * - * Design is slightly refactored to track time and map version of last acknowledge per subscriber to facilitate - * subscriber clean up and is simpler to persist than the previous implementation based on a set of missing messages acks. - */ -@ThreadSafe -abstract class AbstractNetworkMapService(network: MessagingService, - private val networkMapCache: NetworkMapCacheInternal, - private val minimumPlatformVersion: Int) : NetworkMapService, AbstractNodeService(network) { - companion object { - /** - * Maximum credible size for a registration request. Generally requests are around 2000-6000 bytes, so this gives a - * 10 times overhead. - */ - private const val MAX_SIZE_REGISTRATION_REQUEST_BYTES = 40000 - private val logger = loggerFor() - } - - protected abstract val nodeRegistrations: MutableMap - - // Map from subscriber address, to most recently acknowledged update map version. - protected abstract val subscribers: ThreadBox> - - protected val _mapVersion = AtomicInteger(0) - - @VisibleForTesting - val mapVersion: Int - get() = _mapVersion.get() - - /** Maximum number of unacknowledged updates to send to a node before automatically unregistering them for updates */ - val maxUnacknowledgedUpdates = 10 - - private val handlers = ArrayList() - protected fun setup() { - // Register message handlers - handlers += addMessageHandler(FETCH_TOPIC) { req: FetchMapRequest -> processFetchAllRequest(req) } - handlers += addMessageHandler(QUERY_TOPIC) { req: QueryIdentityRequest -> processQueryRequest(req) } - handlers += addMessageHandler(REGISTER_TOPIC) { req: RegistrationRequest -> processRegistrationRequest(req) } - handlers += addMessageHandler(SUBSCRIPTION_TOPIC) { req: SubscribeRequest -> processSubscriptionRequest(req) } - handlers += network.addMessageHandler(PUSH_ACK_TOPIC) { message, _ -> - val req = message.data.deserialize() - processAcknowledge(req) - } - } - - @VisibleForTesting - fun unregisterNetworkHandlers() { - for (handler in handlers) { - network.removeMessageHandler(handler) - } - handlers.clear() - } - - private fun addSubscriber(subscriber: MessageRecipients) { - if (subscriber !is SingleMessageRecipient) throw NodeMapException.InvalidSubscriber() - subscribers.locked { - if (!containsKey(subscriber)) { - put(subscriber, LastAcknowledgeInfo(mapVersion)) - } - } - } - - private fun removeSubscriber(subscriber: MessageRecipients) { - if (subscriber !is SingleMessageRecipient) throw NodeMapException.InvalidSubscriber() - subscribers.locked { remove(subscriber) } - } - - private fun processAcknowledge(request: UpdateAcknowledge) { - if (request.replyTo !is SingleMessageRecipient) throw NodeMapException.InvalidSubscriber() - subscribers.locked { - val lastVersionAcked = this[request.replyTo]?.mapVersion - if ((lastVersionAcked ?: 0) < request.mapVersion) { - this[request.replyTo] = LastAcknowledgeInfo(request.mapVersion) - } - } - } - - private fun processFetchAllRequest(request: FetchMapRequest): FetchMapResponse { - if (request.subscribe) { - addSubscriber(request.replyTo) - } - val currentVersion = mapVersion - val nodeRegistrations = if (request.ifChangedSinceVersion == null || request.ifChangedSinceVersion < currentVersion) { - // We return back the current state of the entire map including nodes that have been removed - ArrayList(nodeRegistrations.values.map { it.reg }) // Snapshot to avoid attempting to serialise Map internals - } else { - null - } - return FetchMapResponse(nodeRegistrations, currentVersion) - } - - private fun processQueryRequest(request: QueryIdentityRequest): QueryIdentityResponse { - val candidate = nodeRegistrations[request.identity]?.reg - // If the most recent record we have is of the node being removed from the map, then it's considered - // as no match. - val node = if (candidate == null || candidate.type == REMOVE) null else candidate.node - return QueryIdentityResponse(node) - } - - private fun processRegistrationRequest(request: RegistrationRequest): RegistrationResponse { - val requestSize = request.wireReg.raw.size - logger.debug { "Received registration request of size: $requestSize" } - if (requestSize > MAX_SIZE_REGISTRATION_REQUEST_BYTES) { - return RegistrationResponse("Request is too big") - } - - val change = try { - request.wireReg.verified() - } catch (e: SignatureException) { - return RegistrationResponse("Invalid signature on request") - } catch (e: IOException) { - val msg = "Unexpected IO exception: ${e.message}" - logger.error(msg, e) - return RegistrationResponse(msg) - } - val node = change.node - // Get identity from signature on node's registration and use it as an index. - val identity = node.legalIdentitiesAndCerts.singleOrNull { request.wireReg.sig.by == it.owningKey } - identity ?: return RegistrationResponse("Key from signature on the node registration wasn't found in NodeInfo") - - if (node.platformVersion < minimumPlatformVersion) { - return RegistrationResponse("Minimum platform version requirement not met: $minimumPlatformVersion") - } - - // Update the current value atomically, so that if multiple updates come - // in on different threads, there is no risk of a race condition while checking - // sequence numbers. - val registrationInfo = try { - nodeRegistrations.compute(identity) { _, existing: NodeRegistrationInfo? -> - require(!((existing == null || existing.reg.type == REMOVE) && change.type == REMOVE)) { - "Attempting to de-register unknown node" - } - require(existing == null || existing.reg.serial < change.serial) { "Serial value is too small" } - NodeRegistrationInfo(change, _mapVersion.incrementAndGet()) - } - } catch (e: IllegalArgumentException) { - return RegistrationResponse(e.message) - } - - notifySubscribers(request.wireReg, registrationInfo!!.mapVersion) - - // Update the local cache - // TODO: Once local messaging is fixed, this should go over the network layer as it does to other - // subscribers - when (change.type) { - ADD -> { - logger.info("Added node ${node.addresses} to network map") - networkMapCache.addNode(change.node) - } - REMOVE -> { - logger.info("Removed node ${node.addresses} from network map") - networkMapCache.removeNode(change.node) - } - } - - return RegistrationResponse(null) - } - - private fun notifySubscribers(wireReg: WireNodeRegistration, newMapVersion: Int) { - // TODO: Once we have a better established messaging system, we can probably send - // to a MessageRecipientGroup that nodes join/leave, rather than the network map - // service itself managing the group - val update = NetworkMapService.Update(wireReg, newMapVersion, network.myAddress).serialize().bytes - val message = network.createMessage(PUSH_TOPIC, data = update) - - subscribers.locked { - // Remove any stale subscribers - values.removeIf { (mapVersion) -> newMapVersion - mapVersion > maxUnacknowledgedUpdates } - // TODO: introduce some concept of time in the condition to avoid unsubscribes when there's a message burst. - keys.forEach { recipient -> network.send(message, recipient) } - } - } - - private fun processSubscriptionRequest(request: SubscribeRequest): SubscribeResponse { - if (request.subscribe) { - addSubscriber(request.replyTo) - } else { - removeSubscriber(request.replyTo) - } - return SubscribeResponse(true) - } -} - -/** - * A node registration state in the network map. - * - * @param node the node being added/removed. - * @param serial an increasing value which represents the version of this registration. Not expected to be sequential, - * but later versions of the registration must have higher values (or they will be ignored by the map service). - * Similar to the serial number on DNS records. - * @param type add if the node is being added to the map, or remove if a previous node is being removed (indicated as - * going offline). - * @param expires when the registration expires. Only used when adding a node to a map. - */ -// TODO: This might alternatively want to have a node and party, with the node being optional, so registering a node -// involves providing both node and paerty, and deregistering a node involves a request with party but no node. -@CordaSerializable -data class NodeRegistration(val node: NodeInfo, val serial: Long, val type: AddOrRemove, var expires: Instant) { - /** - * Build a node registration in wire format. - */ - fun toWire(keyManager: KeyManagementService, publicKey: PublicKey): WireNodeRegistration { - val regSerialized = this.serialize() - val regSig = keyManager.sign(regSerialized.bytes, publicKey) - - return WireNodeRegistration(regSerialized, regSig) - } - - override fun toString(): String = "$node #$serial ($type)" -} - -/** - * A node registration and its signature as a pair. - */ -@CordaSerializable -class WireNodeRegistration(raw: SerializedBytes, sig: DigitalSignature.WithKey) : SignedData(raw, sig) { - @Throws(IllegalArgumentException::class) - override fun verifyData(data: NodeRegistration) { - // Check that the registration is fulfilled by any of node's identities. - // TODO It may cause some problems with distributed services? We loose node's main identity. Should be all signatures instead of isFulfilledBy? - require(data.node.legalIdentitiesAndCerts.any { it.owningKey.isFulfilledBy(sig.by) }) - } -} - -@CordaSerializable -sealed class NodeMapException : CordaException("Network Map Protocol Error") { - - /** Thrown if the signature on the node info does not match the public key for the identity */ - class InvalidSignature : NodeMapException() - - /** Thrown if the replyTo of a subscription change message is not a single message recipient */ - class InvalidSubscriber : NodeMapException() -} - -@CordaSerializable -data class LastAcknowledgeInfo(val mapVersion: Int) - -@CordaSerializable -data class NodeRegistrationInfo(val reg: NodeRegistration, val mapVersion: Int) diff --git a/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt b/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt index 4bd9c72cb4..32dfecee97 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt @@ -2,9 +2,9 @@ package net.corda.node.services.network import net.corda.cordform.CordformNode import net.corda.core.crypto.SignedData +import net.corda.core.crypto.sign import net.corda.core.internal.* import net.corda.core.node.NodeInfo -import net.corda.core.node.services.KeyManagementService import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.utilities.loggerFor @@ -15,6 +15,7 @@ import rx.Scheduler import rx.schedulers.Schedulers import java.io.IOException import java.nio.file.Path +import java.security.KeyPair import java.util.concurrent.TimeUnit import kotlin.streams.toList @@ -48,13 +49,13 @@ class NodeInfoWatcher(private val nodePath: Path, * * @param path the path where to write the file, if non-existent it will be created. * @param nodeInfo the NodeInfo to serialize. - * @param keyManager a KeyManagementService used to sign the NodeInfo data. + * @param signingKey used to sign the NodeInfo data. */ - fun saveToFile(path: Path, nodeInfo: NodeInfo, keyManager: KeyManagementService) { + fun saveToFile(path: Path, nodeInfo: NodeInfo, signingKey: KeyPair) { try { path.createDirectories() val serializedBytes = nodeInfo.serialize() - val regSig = keyManager.sign(serializedBytes.bytes, nodeInfo.legalIdentities.first().owningKey) + val regSig = signingKey.sign(serializedBytes.bytes) val signedData = SignedData(serializedBytes, regSig) signedData.serialize().open().copyTo( path / "${NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX}${serializedBytes.hash}") diff --git a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt index d632da7242..33f98f7322 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt @@ -8,14 +8,13 @@ import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate import net.corda.core.internal.bufferUntilSubscribed import net.corda.core.internal.concurrent.openFuture +import net.corda.core.internal.schemas.NodeInfoSchemaV1 import net.corda.core.messaging.DataFeed import net.corda.core.node.NodeInfo +import net.corda.core.node.NotaryInfo import net.corda.core.node.services.IdentityService import net.corda.core.node.services.NetworkMapCache.MapChange -import net.corda.core.node.services.NotaryService import net.corda.core.node.services.PartyInfo -import net.corda.core.schemas.NodeInfoSchemaV1 -import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.loggerFor @@ -32,8 +31,12 @@ import java.security.PublicKey import java.util.* import javax.annotation.concurrent.ThreadSafe import kotlin.collections.HashMap +import kotlin.collections.HashSet -class NetworkMapCacheImpl(networkMapCacheBase: NetworkMapCacheBaseInternal, private val identityService: IdentityService) : NetworkMapCacheBaseInternal by networkMapCacheBase, NetworkMapCacheInternal { +class NetworkMapCacheImpl( + networkMapCacheBase: NetworkMapCacheBaseInternal, + private val identityService: IdentityService +) : NetworkMapCacheBaseInternal by networkMapCacheBase, NetworkMapCacheInternal { init { networkMapCacheBase.allNodes.forEach { it.legalIdentitiesAndCerts.forEach { identityService.verifyAndRegisterIdentity(it) } } networkMapCacheBase.changed.subscribe { mapChange -> @@ -58,13 +61,16 @@ class NetworkMapCacheImpl(networkMapCacheBase: NetworkMapCacheBaseInternal, priv * Extremely simple in-memory cache of the network map. */ @ThreadSafe -open class PersistentNetworkMapCache(private val database: CordaPersistence, configuration: NodeConfiguration) : SingletonSerializeAsToken(), NetworkMapCacheBaseInternal { +open class PersistentNetworkMapCache( + private val database: CordaPersistence, + val configuration: NodeConfiguration, + notaries: List +) : SingletonSerializeAsToken(), NetworkMapCacheBaseInternal { companion object { val logger = loggerFor() } - // TODO Small explanation, partyNodes and registeredNodes is left in memory as it was before, because it will be removed in - // next PR that gets rid of services. These maps are used only for queries by service. + // TODO Cleanup registered and party nodes protected val registeredNodes: MutableMap = Collections.synchronizedMap(HashMap()) protected val partyNodes: MutableList get() = registeredNodes.map { it.value }.toMutableList() private val _changed = PublishSubject.create() @@ -78,22 +84,9 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence, con override val nodeReady: CordaFuture get() = _registrationFuture private var _loadDBSuccess: Boolean = false override val loadDBSuccess get() = _loadDBSuccess - // TODO From the NetworkMapService redesign doc: Remove the concept of network services. - // As a temporary hack, just assume for now that every network has a notary service named "Notary Service" that can be looked up in the map. - // This should eliminate the only required usage of services. - // It is ensured on node startup when constructing a notary that the name contains "notary". - override val notaryIdentities: List - get() { - return partyNodes - .flatMap { - // TODO: validate notary identity certificates before loading into network map cache. - // Notary certificates have to be signed by the doorman directly - it.legalIdentities - } - .filter { it.name.commonName?.startsWith(NotaryService.ID_PREFIX) ?: false } - .toSet() // Distinct, because of distributed service nodes - .sortedBy { it.name.toString() } - } + + override val notaryIdentities: List = notaries.map { it.identity } + private val validatingNotaries = notaries.mapNotNullTo(HashSet()) { if (it.validating) it.identity else null } private val nodeInfoSerializer = NodeInfoWatcher(configuration.baseDirectory, configuration.additionalNodeInfoPollingFrequencyMsec) @@ -108,6 +101,8 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence, con nodeInfoSerializer.nodeInfoUpdates().subscribe { node -> addNode(node) } } + override fun isValidatingNotary(party: Party): Boolean = party in validatingNotaries + override fun getPartyInfo(party: Party): PartyInfo? { val nodes = database.transaction { queryByIdentityKey(session, party.owningKey) } if (nodes.size == 1 && nodes[0].isLegalIdentity(party)) { @@ -287,7 +282,6 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence, con return NodeInfoSchemaV1.PersistentNodeInfo( id = 0, addresses = nodeInfo.addresses.map { NodeInfoSchemaV1.DBHostAndPort.fromHostAndPort(it) }, - // TODO Another ugly hack with special first identity... legalIdentitiesAndCerts = nodeInfo.legalIdentitiesAndCerts.mapIndexed { idx, elem -> NodeInfoSchemaV1.DBPartyAndCertificate(elem, isMain = idx == 0) }, diff --git a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapService.kt b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapService.kt deleted file mode 100644 index 1ef1659b7f..0000000000 --- a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapService.kt +++ /dev/null @@ -1,133 +0,0 @@ -package net.corda.node.services.network - -import net.corda.core.crypto.toStringShort -import net.corda.core.identity.PartyAndCertificate -import net.corda.core.internal.ThreadBox -import net.corda.core.messaging.SingleMessageRecipient -import net.corda.core.serialization.SerializationDefaults -import net.corda.core.serialization.deserialize -import net.corda.core.serialization.serialize -import net.corda.core.utilities.MAX_HASH_HEX_SIZE -import net.corda.node.services.api.NetworkMapCacheInternal -import net.corda.node.services.messaging.MessagingService -import net.corda.node.utilities.NODE_DATABASE_PREFIX -import net.corda.node.utilities.PersistentMap -import net.corda.nodeapi.ArtemisMessagingComponent -import java.io.ByteArrayInputStream -import java.security.cert.CertificateFactory -import java.util.* -import javax.persistence.* - -/** - * A network map service backed by a database to survive restarts of the node hosting it. - * - * Majority of the logic is inherited from [AbstractNetworkMapService]. - * - * This class needs database transactions to be in-flight during method calls and init, otherwise it will throw - * exceptions. - */ -class PersistentNetworkMapService(network: MessagingService, networkMapCache: NetworkMapCacheInternal, minimumPlatformVersion: Int) - : AbstractNetworkMapService(network, networkMapCache, minimumPlatformVersion) { - - // Only the node_party_path column is needed to reconstruct a PartyAndCertificate but we have the others for human readability - @Entity - @Table(name = "${NODE_DATABASE_PREFIX}network_map_nodes") - class NetworkNode( - @Id - @Column(name = "node_party_key_hash", length = MAX_HASH_HEX_SIZE) - var publicKeyHash: String, - - @Column - var nodeParty: NodeParty = NodeParty(), - - @Lob @Column - var registrationInfo: ByteArray = ByteArray(0) - ) - - @Embeddable - class NodeParty( - @Column(name = "node_party_name") - var name: String = "", - - @Column(name = "node_party_certificate", length = 4096) - var certificate: ByteArray = ByteArray(0), - - @Column(name = "node_party_path", length = 4096) - var certPath: ByteArray = ByteArray(0) - ) - - private companion object { - private val factory = CertificateFactory.getInstance("X.509") - - fun createNetworkNodesMap(): PersistentMap { - return PersistentMap( - toPersistentEntityKey = { it.owningKey.toStringShort() }, - fromPersistentEntity = { - Pair(PartyAndCertificate(factory.generateCertPath(ByteArrayInputStream(it.nodeParty.certPath))), - it.registrationInfo.deserialize(context = SerializationDefaults.STORAGE_CONTEXT)) - }, - toPersistentEntity = { key: PartyAndCertificate, value: NodeRegistrationInfo -> - NetworkNode( - publicKeyHash = key.owningKey.toStringShort(), - nodeParty = NodeParty( - key.name.toString(), - key.certificate.encoded, - key.certPath.encoded - ), - registrationInfo = value.serialize(context = SerializationDefaults.STORAGE_CONTEXT).bytes - ) - }, - persistentEntityClass = NetworkNode::class.java - ) - } - - fun createNetworkSubscribersMap(): PersistentMap { - return PersistentMap( - toPersistentEntityKey = { it.getPrimaryKeyBasedOnSubType() }, - fromPersistentEntity = { - Pair(it.key.deserialize(context = SerializationDefaults.STORAGE_CONTEXT), - it.value.deserialize(context = SerializationDefaults.STORAGE_CONTEXT)) - }, - toPersistentEntity = { k: SingleMessageRecipient, v: LastAcknowledgeInfo -> - NetworkSubscriber( - id = k.getPrimaryKeyBasedOnSubType(), - key = k.serialize(context = SerializationDefaults.STORAGE_CONTEXT).bytes, - value = v.serialize(context = SerializationDefaults.STORAGE_CONTEXT).bytes - ) - }, - persistentEntityClass = NetworkSubscriber::class.java - ) - } - - fun SingleMessageRecipient.getPrimaryKeyBasedOnSubType() = - if (this is ArtemisMessagingComponent.ArtemisPeerAddress) { - this.hostAndPort.toString() - } else { - this.toString() - } - } - - override val nodeRegistrations: MutableMap = - Collections.synchronizedMap(createNetworkNodesMap()) - - @Entity - @Table(name = "${NODE_DATABASE_PREFIX}network_map_subscribers") - class NetworkSubscriber( - @Id @Column - var id: String = "", - - @Column(length = 4096) - var key: ByteArray = ByteArray(0), - - @Column(length = 4096) - var value: ByteArray = ByteArray(0) - ) - - override val subscribers = ThreadBox(createNetworkSubscribersMap()) - - init { - // Initialise the network map version with the current highest persisted version, or zero if there are no entries. - _mapVersion.set(nodeRegistrations.values.map { it.mapVersion }.max() ?: 0) - setup() - } -} diff --git a/node/src/main/kotlin/net/corda/node/services/schema/NodeSchemaService.kt b/node/src/main/kotlin/net/corda/node/services/schema/NodeSchemaService.kt index 8436f9459b..00cb1b5f79 100644 --- a/node/src/main/kotlin/net/corda/node/services/schema/NodeSchemaService.kt +++ b/node/src/main/kotlin/net/corda/node/services/schema/NodeSchemaService.kt @@ -3,9 +3,9 @@ package net.corda.node.services.schema import net.corda.core.contracts.ContractState import net.corda.core.contracts.FungibleAsset import net.corda.core.contracts.LinearState +import net.corda.core.internal.schemas.NodeInfoSchemaV1 import net.corda.core.schemas.CommonSchemaV1 import net.corda.core.schemas.MappedSchema -import net.corda.core.schemas.NodeInfoSchemaV1 import net.corda.core.schemas.PersistentState import net.corda.core.schemas.QueryableState import net.corda.core.serialization.SingletonSerializeAsToken @@ -15,7 +15,6 @@ import net.corda.node.services.events.NodeSchedulerService import net.corda.node.services.identity.PersistentIdentityService import net.corda.node.services.keys.PersistentKeyManagementService import net.corda.node.services.messaging.NodeMessagingClient -import net.corda.node.services.network.PersistentNetworkMapService import net.corda.node.services.persistence.DBCheckpointStorage import net.corda.node.services.persistence.DBTransactionMappingStorage import net.corda.node.services.persistence.DBTransactionStorage @@ -48,8 +47,6 @@ class NodeSchemaService(cordappLoader: CordappLoader?) : SchemaService, Singleto PersistentUniquenessProvider.PersistentNotaryCommit::class.java, NodeSchedulerService.PersistentScheduledState::class.java, NodeAttachmentService.DBAttachment::class.java, - PersistentNetworkMapService.NetworkNode::class.java, - PersistentNetworkMapService.NetworkSubscriber::class.java, NodeMessagingClient.ProcessedMessage::class.java, NodeMessagingClient.RetryMessage::class.java, NodeAttachmentService.DBAttachment::class.java, diff --git a/node/src/main/kotlin/net/corda/node/utilities/DemoClock.kt b/node/src/main/kotlin/net/corda/node/utilities/DemoClock.kt new file mode 100644 index 0000000000..0d6ab6b48c --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/utilities/DemoClock.kt @@ -0,0 +1,23 @@ +package net.corda.node.utilities + +import net.corda.core.internal.until +import net.corda.node.internal.MutableClock +import java.time.Clock +import java.time.LocalDate +import javax.annotation.concurrent.ThreadSafe + +/** A [Clock] that can have the date advanced for use in demos. */ +@ThreadSafe +class DemoClock(delegateClock: Clock) : MutableClock(delegateClock) { + @Synchronized + fun updateDate(date: LocalDate): Boolean { + val currentDate = LocalDate.now(this) + if (currentDate.isBefore(date)) { + // It's ok to increment + delegateClock = Clock.offset(delegateClock, currentDate.atStartOfDay() until date.atStartOfDay()) + notifyMutationObservers() + return true + } + return false + } +} diff --git a/node/src/main/kotlin/net/corda/node/utilities/ServiceIdentityGenerator.kt b/node/src/main/kotlin/net/corda/node/utilities/ServiceIdentityGenerator.kt index e27a3cc8e4..2ff06f1d70 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/ServiceIdentityGenerator.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/ServiceIdentityGenerator.kt @@ -5,10 +5,10 @@ import net.corda.core.crypto.generateKeyPair import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.internal.cert +import net.corda.core.internal.createDirectories import net.corda.core.internal.div import net.corda.core.utilities.loggerFor import net.corda.core.utilities.trace -import java.nio.file.Files import java.nio.file.Path object ServiceIdentityGenerator { @@ -20,13 +20,12 @@ object ServiceIdentityGenerator { * This method should be called *before* any of the nodes are started. * * @param dirs List of node directories to place the generated identity and key pairs in. - * @param serviceId The service id of the distributed service. - * @param serviceName The legal name of the distributed service, with service id as CN. + * @param serviceName The legal name of the distributed service. * @param threshold The threshold for the generated group [CompositeKey]. */ - // TODO: This needs to write out to the key store, not just files on disk fun generateToDisk(dirs: List, serviceName: CordaX500Name, + serviceId: String, threshold: Int = 1): Party { log.trace { "Generating a group identity \"serviceName\" for nodes: ${dirs.joinToString()}" } val keyPairs = (1..dirs.size).map { generateKeyPair() } @@ -39,9 +38,8 @@ object ServiceIdentityGenerator { keyPairs.zip(dirs) { keyPair, dir -> val serviceKeyCert = X509Utilities.createCertificate(CertificateType.CLIENT_CA, issuer.certificate, issuer.keyPair, serviceName, keyPair.public) val compositeKeyCert = X509Utilities.createCertificate(CertificateType.CLIENT_CA, issuer.certificate, issuer.keyPair, serviceName, notaryKey) - val certPath = Files.createDirectories(dir / "certificates") / "distributedService.jks" + val certPath = (dir / "certificates").createDirectories() / "distributedService.jks" val keystore = loadOrCreateKeyStore(certPath, "cordacadevpass") - val serviceId = serviceName.commonName keystore.setCertificateEntry("$serviceId-composite-key", compositeKeyCert.cert) keystore.setKeyEntry("$serviceId-private-key", keyPair.private, "cordacadevkeypass".toCharArray(), arrayOf(serviceKeyCert.cert, issuer.certificate.cert, rootCert)) keystore.save(certPath, "cordacadevpass") diff --git a/node/src/main/kotlin/net/corda/node/utilities/TestClock.kt b/node/src/main/kotlin/net/corda/node/utilities/TestClock.kt deleted file mode 100644 index 60af87f895..0000000000 --- a/node/src/main/kotlin/net/corda/node/utilities/TestClock.kt +++ /dev/null @@ -1,49 +0,0 @@ -package net.corda.node.utilities - -import net.corda.core.internal.until -import net.corda.core.serialization.SerializeAsToken -import net.corda.core.serialization.SerializeAsTokenContext -import net.corda.core.serialization.SingletonSerializationToken.Companion.singletonSerializationToken -import net.corda.node.internal.MutableClock -import java.time.Clock -import java.time.Instant -import java.time.LocalDate -import java.time.ZoneId -import javax.annotation.concurrent.ThreadSafe - -/** - * A [Clock] that can have the date advanced for use in demos. - */ -@ThreadSafe -class TestClock(private var delegateClock: Clock = Clock.systemUTC()) : MutableClock(), SerializeAsToken { - - private val token = singletonSerializationToken(javaClass) - - override fun toToken(context: SerializeAsTokenContext) = token.registerWithContext(context, this) - - @Synchronized - fun updateDate(date: LocalDate): Boolean { - val currentDate = LocalDate.now(this) - if (currentDate.isBefore(date)) { - // It's ok to increment - delegateClock = Clock.offset(delegateClock, currentDate.atStartOfDay() until date.atStartOfDay()) - notifyMutationObservers() - return true - } - return false - } - - @Synchronized override fun instant(): Instant { - return delegateClock.instant() - } - - // Do not use this. Instead seek to use ZonedDateTime methods. - override fun withZone(zone: ZoneId): Clock { - throw UnsupportedOperationException("Tokenized clock does not support withZone()") - } - - @Synchronized override fun getZone(): ZoneId { - return delegateClock.zone - } - -} diff --git a/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt b/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt index ae39004b31..552d16a484 100644 --- a/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt +++ b/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt @@ -23,9 +23,10 @@ import net.corda.finance.USD import net.corda.finance.contracts.asset.Cash import net.corda.finance.flows.CashIssueFlow import net.corda.finance.flows.CashPaymentFlow -import net.corda.node.internal.CordaRPCOpsImpl +import net.corda.node.internal.SecureCordaRPCOps import net.corda.node.internal.StartedNode -import net.corda.node.services.FlowPermissions.Companion.startFlowPermission +import net.corda.node.services.Permissions.Companion.startFlow +import net.corda.node.services.Permissions.Companion.invokeRpc import net.corda.node.services.messaging.CURRENT_RPC_CONTEXT import net.corda.node.services.messaging.RpcContext import net.corda.nodeapi.User @@ -49,33 +50,31 @@ import kotlin.test.assertNull import kotlin.test.assertTrue class CordaRPCOpsImplTest { - private companion object { val testJar = "net/corda/node/testing/test.jar" } - lateinit var mockNet: MockNetwork - lateinit var aliceNode: StartedNode - lateinit var notaryNode: StartedNode - lateinit var notary: Party - lateinit var rpc: CordaRPCOps - lateinit var stateMachineUpdates: Observable - lateinit var transactions: Observable - lateinit var vaultTrackCash: Observable> + private lateinit var mockNet: MockNetwork + private lateinit var aliceNode: StartedNode + private lateinit var notary: Party + private lateinit var rpc: CordaRPCOps + private lateinit var stateMachineUpdates: Observable + private lateinit var transactions: Observable + private lateinit var vaultTrackCash: Observable> + + private val user = User("user", "pwd", permissions = emptySet()) @Before fun setup() { mockNet = MockNetwork(cordappPackages = listOf("net.corda.finance.contracts.asset")) aliceNode = mockNet.createNode() - notaryNode = mockNet.createNotaryNode(validating = false) - rpc = CordaRPCOpsImpl(aliceNode.services, aliceNode.smm, aliceNode.database, aliceNode.services) - CURRENT_RPC_CONTEXT.set(RpcContext(User("user", "pwd", permissions = setOf( - startFlowPermission(), - startFlowPermission() - )))) + rpc = SecureCordaRPCOps(aliceNode.services, aliceNode.smm, aliceNode.database, aliceNode.services) + CURRENT_RPC_CONTEXT.set(RpcContext(user)) mockNet.runNetwork() - notary = rpc.notaryIdentities().first() + withPermissions(invokeRpc(CordaRPCOps::notaryIdentities)) { + notary = rpc.notaryIdentities().first() + } } @After @@ -85,170 +84,185 @@ class CordaRPCOpsImplTest { @Test fun `cash issue accepted`() { - aliceNode.database.transaction { - stateMachineUpdates = rpc.stateMachinesFeed().updates - vaultTrackCash = rpc.vaultTrackBy().updates - } - val quantity = 1000L - val ref = OpaqueBytes(ByteArray(1) { 1 }) + withPermissions(invokeRpc("vaultTrackBy"), invokeRpc("vaultQueryBy"), invokeRpc(CordaRPCOps::stateMachinesFeed), startFlow()) { - // Check the monitoring service wallet is empty - aliceNode.database.transaction { - assertFalse(aliceNode.services.vaultService.queryBy().totalStatesAvailable > 0) - } + aliceNode.database.transaction { + stateMachineUpdates = rpc.stateMachinesFeed().updates + vaultTrackCash = rpc.vaultTrackBy().updates + } - // Tell the monitoring service node to issue some cash - val result = rpc.startFlow(::CashIssueFlow, Amount(quantity, GBP), ref, notary) - mockNet.runNetwork() + val quantity = 1000L + val ref = OpaqueBytes(ByteArray(1) { 1 }) - var issueSmId: StateMachineRunId? = null - stateMachineUpdates.expectEvents { - sequence( - // ISSUE - expect { add: StateMachineUpdate.Added -> - issueSmId = add.id - }, - expect { remove: StateMachineUpdate.Removed -> - require(remove.id == issueSmId) - } - ) - } + // Check the monitoring service wallet is empty + aliceNode.database.transaction { + assertFalse(aliceNode.services.vaultService.queryBy().totalStatesAvailable > 0) + } - val anonymisedRecipient = result.returnValue.getOrThrow().recipient!! - val expectedState = Cash.State(Amount(quantity, - Issued(aliceNode.info.chooseIdentity().ref(ref), GBP)), - anonymisedRecipient) + // Tell the monitoring service node to issue some cash + val result = rpc.startFlow(::CashIssueFlow, Amount(quantity, GBP), ref, notary) + mockNet.runNetwork() - // Query vault via RPC - val cash = rpc.vaultQueryBy() - assertEquals(expectedState, cash.states.first().state.data) + var issueSmId: StateMachineRunId? = null + stateMachineUpdates.expectEvents { + sequence( + // ISSUE + expect { add: StateMachineUpdate.Added -> + issueSmId = add.id + }, + expect { remove: StateMachineUpdate.Removed -> + require(remove.id == issueSmId) + } + ) + } - vaultTrackCash.expectEvents { - expect { update -> - val actual = update.produced.single().state.data - assertEquals(expectedState, actual) + val anonymisedRecipient = result.returnValue.getOrThrow().recipient!! + val expectedState = Cash.State(Amount(quantity, + Issued(aliceNode.info.chooseIdentity().ref(ref), GBP)), + anonymisedRecipient) + + // Query vault via RPC + val cash = rpc.vaultQueryBy() + assertEquals(expectedState, cash.states.first().state.data) + + vaultTrackCash.expectEvents { + expect { update -> + val actual = update.produced.single().state.data + assertEquals(expectedState, actual) + } } } } @Test fun `issue and move`() { - aliceNode.database.transaction { - stateMachineUpdates = rpc.stateMachinesFeed().updates - transactions = rpc.internalVerifiedTransactionsFeed().updates - vaultTrackCash = rpc.vaultTrackBy().updates - } - val result = rpc.startFlow(::CashIssueFlow, - 100.DOLLARS, - OpaqueBytes(ByteArray(1, { 1 })), - notary - ) + withPermissions(invokeRpc(CordaRPCOps::stateMachinesFeed), + invokeRpc(CordaRPCOps::internalVerifiedTransactionsFeed), + invokeRpc("vaultTrackBy"), + startFlow(), + startFlow()) { + aliceNode.database.transaction { + stateMachineUpdates = rpc.stateMachinesFeed().updates + transactions = rpc.internalVerifiedTransactionsFeed().updates + vaultTrackCash = rpc.vaultTrackBy().updates + } - mockNet.runNetwork() - - rpc.startFlow(::CashPaymentFlow, 100.DOLLARS, aliceNode.info.chooseIdentity()) - - mockNet.runNetwork() - - var issueSmId: StateMachineRunId? = null - var moveSmId: StateMachineRunId? = null - stateMachineUpdates.expectEvents { - sequence( - // ISSUE - expect { add: StateMachineUpdate.Added -> - issueSmId = add.id - }, - expect { remove: StateMachineUpdate.Removed -> - require(remove.id == issueSmId) - }, - // MOVE - expect { add: StateMachineUpdate.Added -> - moveSmId = add.id - }, - expect { remove: StateMachineUpdate.Removed -> - require(remove.id == moveSmId) - } + val result = rpc.startFlow(::CashIssueFlow, + 100.DOLLARS, + OpaqueBytes(ByteArray(1, { 1 })), + notary ) - } - result.returnValue.getOrThrow() - transactions.expectEvents { - sequence( - // ISSUE - expect { stx -> - require(stx.tx.inputs.isEmpty()) - require(stx.tx.outputs.size == 1) - val signaturePubKeys = stx.sigs.map { it.by }.toSet() - // Only Alice signed, as issuer - val aliceKey = aliceNode.info.chooseIdentity().owningKey - require(signaturePubKeys.size <= aliceKey.keys.size) - require(aliceKey.isFulfilledBy(signaturePubKeys)) - }, - // MOVE - expect { stx -> - require(stx.tx.inputs.size == 1) - require(stx.tx.outputs.size == 1) - val signaturePubKeys = stx.sigs.map { it.by }.toSet() - // Alice and Notary signed - require(aliceNode.services.keyManagementService.filterMyKeys(signaturePubKeys).toList().isNotEmpty()) - require(notary.owningKey.isFulfilledBy(signaturePubKeys)) - } - ) - } + mockNet.runNetwork() - vaultTrackCash.expectEvents { - sequence( - // ISSUE - expect { (consumed, produced) -> - require(consumed.isEmpty()) { consumed.size } - require(produced.size == 1) { produced.size } - }, - // MOVE - expect { (consumed, produced) -> - require(consumed.size == 1) { consumed.size } - require(produced.size == 1) { produced.size } - } - ) + rpc.startFlow(::CashPaymentFlow, 100.DOLLARS, aliceNode.info.chooseIdentity()) + + mockNet.runNetwork() + + var issueSmId: StateMachineRunId? = null + var moveSmId: StateMachineRunId? = null + stateMachineUpdates.expectEvents { + sequence( + // ISSUE + expect { add: StateMachineUpdate.Added -> + issueSmId = add.id + }, + expect { remove: StateMachineUpdate.Removed -> + require(remove.id == issueSmId) + }, + // MOVE + expect { add: StateMachineUpdate.Added -> + moveSmId = add.id + }, + expect { remove: StateMachineUpdate.Removed -> + require(remove.id == moveSmId) + } + ) + } + + result.returnValue.getOrThrow() + transactions.expectEvents { + sequence( + // ISSUE + expect { stx -> + require(stx.tx.inputs.isEmpty()) + require(stx.tx.outputs.size == 1) + val signaturePubKeys = stx.sigs.map { it.by }.toSet() + // Only Alice signed, as issuer + val aliceKey = aliceNode.info.chooseIdentity().owningKey + require(signaturePubKeys.size <= aliceKey.keys.size) + require(aliceKey.isFulfilledBy(signaturePubKeys)) + }, + // MOVE + expect { stx -> + require(stx.tx.inputs.size == 1) + require(stx.tx.outputs.size == 1) + val signaturePubKeys = stx.sigs.map { it.by }.toSet() + // Alice and Notary signed + require(aliceNode.services.keyManagementService.filterMyKeys(signaturePubKeys).toList().isNotEmpty()) + require(notary.owningKey.isFulfilledBy(signaturePubKeys)) + } + ) + } + + vaultTrackCash.expectEvents { + sequence( + // ISSUE + expect { (consumed, produced) -> + require(consumed.isEmpty()) { consumed.size } + require(produced.size == 1) { produced.size } + }, + // MOVE + expect { (consumed, produced) -> + require(consumed.size == 1) { consumed.size } + require(produced.size == 1) { produced.size } + } + ) + } } } @Test fun `cash command by user not permissioned for cash`() { - CURRENT_RPC_CONTEXT.set(RpcContext(User("user", "pwd", permissions = emptySet()))) - assertThatExceptionOfType(PermissionException::class.java).isThrownBy { - rpc.startFlow(::CashIssueFlow, Amount(100, USD), OpaqueBytes(ByteArray(1, { 1 })), notary) + withoutAnyPermissions { + assertThatExceptionOfType(PermissionException::class.java).isThrownBy { + rpc.startFlow(::CashIssueFlow, Amount(100, USD), OpaqueBytes(ByteArray(1, { 1 })), notary) + } } } @Test fun `can upload an attachment`() { - val inputJar = Thread.currentThread().contextClassLoader.getResourceAsStream(testJar) - val secureHash = rpc.uploadAttachment(inputJar) - assertTrue(rpc.attachmentExists(secureHash)) + withPermissions(invokeRpc(CordaRPCOps::uploadAttachment), invokeRpc(CordaRPCOps::attachmentExists)) { + val inputJar = Thread.currentThread().contextClassLoader.getResourceAsStream(testJar) + val secureHash = rpc.uploadAttachment(inputJar) + assertTrue(rpc.attachmentExists(secureHash)) + } } @Test fun `can download an uploaded attachment`() { - val inputJar = Thread.currentThread().contextClassLoader.getResourceAsStream(testJar) - val secureHash = rpc.uploadAttachment(inputJar) - val bufferFile = ByteArrayOutputStream() - val bufferRpc = ByteArrayOutputStream() + withPermissions(invokeRpc(CordaRPCOps::uploadAttachment), invokeRpc(CordaRPCOps::openAttachment)) { + val inputJar = Thread.currentThread().contextClassLoader.getResourceAsStream(testJar) + val secureHash = rpc.uploadAttachment(inputJar) + val bufferFile = ByteArrayOutputStream() + val bufferRpc = ByteArrayOutputStream() - IOUtils.copy(Thread.currentThread().contextClassLoader.getResourceAsStream(testJar), bufferFile) - IOUtils.copy(rpc.openAttachment(secureHash), bufferRpc) + IOUtils.copy(Thread.currentThread().contextClassLoader.getResourceAsStream(testJar), bufferFile) + IOUtils.copy(rpc.openAttachment(secureHash), bufferRpc) - assertArrayEquals(bufferFile.toByteArray(), bufferRpc.toByteArray()) + assertArrayEquals(bufferFile.toByteArray(), bufferRpc.toByteArray()) + } } @Test fun `attempt to start non-RPC flow`() { - CURRENT_RPC_CONTEXT.set(RpcContext(User("user", "pwd", permissions = setOf( - startFlowPermission() - )))) - assertThatExceptionOfType(IllegalArgumentException::class.java).isThrownBy { - rpc.startFlow(::NonRPCFlow) + withPermissions(startFlow()) { + assertThatExceptionOfType(IllegalArgumentException::class.java).isThrownBy { + rpc.startFlow(::NonRPCFlow) + } } } @@ -259,12 +273,11 @@ class CordaRPCOpsImplTest { @Test fun `attempt to start RPC flow with void return`() { - CURRENT_RPC_CONTEXT.set(RpcContext(User("user", "pwd", permissions = setOf( - startFlowPermission() - )))) - val result = rpc.startFlow(::VoidRPCFlow) - mockNet.runNetwork() - assertNull(result.returnValue.getOrThrow()) + withPermissions(startFlow()) { + val result = rpc.startFlow(::VoidRPCFlow) + mockNet.runNetwork() + assertNull(result.returnValue.getOrThrow()) + } } @StartableByRPC @@ -272,4 +285,17 @@ class CordaRPCOpsImplTest { @Suspendable override fun call(): Void? = null } -} + + private fun withPermissions(vararg permissions: String, action: () -> Unit) { + + val previous = CURRENT_RPC_CONTEXT.get() + try { + CURRENT_RPC_CONTEXT.set(RpcContext(user.copy(permissions = permissions.toSet()))) + action.invoke() + } finally { + CURRENT_RPC_CONTEXT.set(previous) + } + } + + private fun withoutAnyPermissions(action: () -> Unit) = withPermissions(action = action) +} \ No newline at end of file diff --git a/node/src/test/kotlin/net/corda/node/internal/CordaServiceTest.kt b/node/src/test/kotlin/net/corda/node/internal/CordaServiceTest.kt index 14242aecb3..7e5f99d32a 100644 --- a/node/src/test/kotlin/net/corda/node/internal/CordaServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/internal/CordaServiceTest.kt @@ -74,14 +74,12 @@ class TestCordaService2(val appServiceHub: AppServiceHub): SingletonSerializeAsT class LegacyCordaService(@Suppress("UNUSED_PARAMETER") simpleServiceHub: ServiceHub) : SingletonSerializeAsToken() class CordaServiceTest { - lateinit var mockNet: MockNetwork - lateinit var notaryNode: StartedNode - lateinit var nodeA: StartedNode + private lateinit var mockNet: MockNetwork + private lateinit var nodeA: StartedNode @Before fun start() { mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.node.internal","net.corda.finance")) - notaryNode = mockNet.createNotaryNode() nodeA = mockNet.createNode() mockNet.startNodes() } diff --git a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt index c5f893965e..00e3e4d641 100644 --- a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt @@ -63,7 +63,7 @@ import kotlin.test.assertTrue * We assume that Alice and Bob already found each other via some market, and have agreed the details already. */ @RunWith(Parameterized::class) -class TwoPartyTradeFlowTests(val anonymous: Boolean) { +class TwoPartyTradeFlowTests(private val anonymous: Boolean) { companion object { private val cordappPackages = listOf("net.corda.finance.contracts") @JvmStatic @@ -93,7 +93,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { // allow interruption half way through. mockNet = MockNetwork(threadPerNode = true, cordappPackages = cordappPackages) ledger(MockServices(cordappPackages), initialiseSerialization = false) { - val notaryNode = mockNet.createNotaryNode() + val notaryNode = mockNet.defaultNotaryNode val aliceNode = mockNet.createPartyNode(ALICE_NAME) val bobNode = mockNet.createPartyNode(BOB_NAME) val bankNode = mockNet.createPartyNode(BOC_NAME) @@ -143,7 +143,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { fun `trade cash for commercial paper fails using soft locking`() { mockNet = MockNetwork(threadPerNode = true, cordappPackages = cordappPackages) ledger(MockServices(cordappPackages), initialiseSerialization = false) { - val notaryNode = mockNet.createNotaryNode() + val notaryNode = mockNet.defaultNotaryNode val aliceNode = mockNet.createPartyNode(ALICE_NAME) val bobNode = mockNet.createPartyNode(BOB_NAME) val bankNode = mockNet.createPartyNode(BOC_NAME) @@ -199,7 +199,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { fun `shutdown and restore`() { mockNet = MockNetwork(cordappPackages = cordappPackages) ledger(MockServices(cordappPackages), initialiseSerialization = false) { - val notaryNode = mockNet.createNotaryNode() + val notaryNode = mockNet.defaultNotaryNode val aliceNode = mockNet.createPartyNode(ALICE_NAME) var bobNode = mockNet.createPartyNode(BOB_NAME) val bankNode = mockNet.createPartyNode(BOC_NAME) @@ -292,15 +292,14 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { // Creates a mock node with an overridden storage service that uses a RecordingMap, that lets us test the order // of gets and puts. - private fun makeNodeWithTracking(name: CordaX500Name): StartedNode { + private fun makeNodeWithTracking( + name: CordaX500Name): StartedNode { // Create a node in the mock network ... - return mockNet.createNode(MockNodeParameters(legalName = name), nodeFactory = object : MockNetwork.Factory { - override fun create(args: MockNodeArgs): MockNetwork.MockNode { - return object : MockNetwork.MockNode(args) { - // That constructs a recording tx storage - override fun makeTransactionStorage(): WritableTransactionStorage { - return RecordingTransactionStorage(database, super.makeTransactionStorage()) - } + return mockNet.createNode(MockNodeParameters(legalName = name), nodeFactory = { args -> + object : MockNetwork.MockNode(args) { + // That constructs a recording tx storage + override fun makeTransactionStorage(): WritableTransactionStorage { + return RecordingTransactionStorage(database, super.makeTransactionStorage()) } } }) @@ -309,7 +308,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { @Test fun `check dependencies of sale asset are resolved`() { mockNet = MockNetwork(cordappPackages = cordappPackages) - val notaryNode = mockNet.createNotaryNode() + val notaryNode = mockNet.defaultNotaryNode val aliceNode = makeNodeWithTracking(ALICE_NAME) val bobNode = makeNodeWithTracking(BOB_NAME) val bankNode = makeNodeWithTracking(BOC_NAME) @@ -415,7 +414,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { @Test fun `track works`() { mockNet = MockNetwork(cordappPackages = cordappPackages) - val notaryNode = mockNet.createNotaryNode() + val notaryNode = mockNet.defaultNotaryNode val aliceNode = makeNodeWithTracking(ALICE_NAME) val bobNode = makeNodeWithTracking(BOB_NAME) val bankNode = makeNodeWithTracking(BOC_NAME) @@ -570,7 +569,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { aliceError: Boolean, expectedMessageSubstring: String ) { - val notaryNode = mockNet.createNotaryNode() + val notaryNode = mockNet.defaultNotaryNode val aliceNode = mockNet.createPartyNode(ALICE_NAME) val bobNode = mockNet.createPartyNode(BOB_NAME) val bankNode = mockNet.createPartyNode(BOC_NAME) @@ -617,18 +616,25 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { notaryNode: StartedNode<*>, vararg extraSigningNodes: StartedNode<*>): Map { + val notaryParty = notaryNode.info.legalIdentities[0] val signed = wtxToSign.map { val id = it.id val sigs = mutableListOf() val nodeKey = node.info.chooseIdentity().owningKey - sigs.add(node.services.keyManagementService.sign(SignableData(id, SignatureMetadata(1, Crypto.findSignatureScheme(nodeKey).schemeNumberID)), nodeKey)) - sigs.add(notaryNode.services.keyManagementService.sign(SignableData(id, SignatureMetadata(1, - Crypto.findSignatureScheme(notaryNode.info.legalIdentities[1].owningKey).schemeNumberID)), notaryNode.info.legalIdentities[1].owningKey)) + sigs += node.services.keyManagementService.sign( + SignableData(id, SignatureMetadata(1, Crypto.findSignatureScheme(nodeKey).schemeNumberID)), + nodeKey + ) + sigs += notaryNode.services.keyManagementService.sign( + SignableData(id, SignatureMetadata(1, Crypto.findSignatureScheme(notaryParty.owningKey).schemeNumberID)), + notaryParty.owningKey + ) extraSigningNodes.forEach { currentNode -> - sigs.add(currentNode.services.keyManagementService.sign( - SignableData(id, SignatureMetadata(1, Crypto.findSignatureScheme(currentNode.info.chooseIdentity().owningKey).schemeNumberID)), + sigs += currentNode.services.keyManagementService.sign( + SignableData(id, SignatureMetadata( + 1, + Crypto.findSignatureScheme(currentNode.info.chooseIdentity().owningKey).schemeNumberID)), currentNode.info.chooseIdentity().owningKey) - ) } SignedTransaction(it, sigs) } diff --git a/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt b/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt index ee21427fdd..5f18b85b10 100644 --- a/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt @@ -16,7 +16,7 @@ import net.corda.node.internal.StartedNode import net.corda.testing.* import net.corda.testing.contracts.DummyContract import net.corda.testing.node.MockNetwork -import net.corda.testing.node.MockNodeParameters +import net.corda.testing.node.MockNetwork.NotarySpec import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.junit.After import org.junit.Before @@ -27,24 +27,26 @@ import kotlin.test.assertEquals import kotlin.test.assertTrue class NotaryChangeTests { - lateinit var mockNet: MockNetwork - lateinit var oldNotaryNode: StartedNode - lateinit var newNotaryNode: StartedNode - lateinit var clientNodeA: StartedNode - lateinit var clientNodeB: StartedNode - lateinit var newNotaryParty: Party - lateinit var oldNotaryParty: Party + private lateinit var mockNet: MockNetwork + private lateinit var oldNotaryNode: StartedNode + private lateinit var clientNodeA: StartedNode + private lateinit var clientNodeB: StartedNode + private lateinit var newNotaryParty: Party + private lateinit var oldNotaryParty: Party @Before fun setUp() { - mockNet = MockNetwork(cordappPackages = listOf("net.corda.testing.contracts")) - oldNotaryNode = mockNet.createNotaryNode(MockNodeParameters(legalName = DUMMY_NOTARY.name)) + val oldNotaryName = DUMMY_REGULATOR.name + mockNet = MockNetwork( + notarySpecs = listOf(NotarySpec(DUMMY_NOTARY.name), NotarySpec(oldNotaryName)), + cordappPackages = listOf("net.corda.testing.contracts") + ) clientNodeA = mockNet.createNode() clientNodeB = mockNet.createNode() - newNotaryNode = mockNet.createNotaryNode(MockNodeParameters(legalName = DUMMY_NOTARY.name.copy(organisation = "Dummy Notary 2"))) + oldNotaryNode = mockNet.notaryNodes[1] mockNet.runNetwork() // Clear network map registration messages - oldNotaryParty = newNotaryNode.services.networkMapCache.getNotary(DUMMY_NOTARY_SERVICE_NAME)!! - newNotaryParty = newNotaryNode.services.networkMapCache.getNotary(DUMMY_NOTARY_SERVICE_NAME.copy(organisation = "Dummy Notary 2"))!! + newNotaryParty = clientNodeA.services.networkMapCache.getNotary(DUMMY_NOTARY.name)!! + oldNotaryParty = clientNodeA.services.networkMapCache.getNotary(oldNotaryName)!! } @After diff --git a/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt b/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt index db4b9dde89..a8b924fe53 100644 --- a/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt @@ -35,7 +35,6 @@ class ScheduledFlowTests { } lateinit var mockNet: MockNetwork - lateinit var notaryNode: StartedNode lateinit var nodeA: StartedNode lateinit var nodeB: StartedNode @@ -94,7 +93,6 @@ class ScheduledFlowTests { @Before fun setup() { mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.testing.contracts")) - notaryNode = mockNet.createNotaryNode() val a = mockNet.createUnstartedNode() val b = mockNet.createUnstartedNode() diff --git a/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt b/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt index b18b7a1766..b19125f6e6 100644 --- a/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt @@ -14,7 +14,6 @@ import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.configureWithDevSSLCertificate import net.corda.node.services.network.NetworkMapCacheImpl import net.corda.node.services.network.PersistentNetworkMapCache -import net.corda.node.services.network.NetworkMapService import net.corda.node.services.transactions.PersistentUniquenessProvider import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor import net.corda.node.utilities.CordaPersistence @@ -40,29 +39,33 @@ import kotlin.test.assertNull //TODO This needs to be merged into P2PMessagingTest as that creates a more realistic environment class ArtemisMessagingTests { + companion object { + const val TOPIC = "platform.self" + } + @Rule @JvmField val testSerialization = SerializationEnvironmentRule() + @Rule @JvmField val temporaryFolder = TemporaryFolder() - val serverPort = freePort() - val rpcPort = freePort() - val topic = "platform.self" - val identity = generateKeyPair() + private val serverPort = freePort() + private val rpcPort = freePort() + private val identity = generateKeyPair() - lateinit var config: NodeConfiguration - lateinit var database: CordaPersistence - lateinit var userService: RPCUserService - lateinit var networkMapRegistrationFuture: CordaFuture + private lateinit var config: NodeConfiguration + private lateinit var database: CordaPersistence + private lateinit var userService: RPCUserService + private lateinit var networkMapRegistrationFuture: CordaFuture - var messagingClient: NodeMessagingClient? = null - var messagingServer: ArtemisMessagingServer? = null + private var messagingClient: NodeMessagingClient? = null + private var messagingServer: ArtemisMessagingServer? = null - lateinit var networkMapCache: NetworkMapCacheImpl + private lateinit var networkMapCache: NetworkMapCacheImpl - val rpcOps = object : RPCOps { + private val rpcOps = object : RPCOps { override val protocolVersion: Int get() = throw UnsupportedOperationException() } @@ -76,7 +79,7 @@ class ArtemisMessagingTests { LogHelper.setLevel(PersistentUniquenessProvider::class) database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), ::makeTestIdentityService) networkMapRegistrationFuture = doneFuture(Unit) - networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, config), rigorousMock()) + networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, config, emptyList()), rigorousMock()) } @After @@ -130,7 +133,7 @@ class ArtemisMessagingTests { val receivedMessages = LinkedBlockingQueue() val messagingClient = createAndStartClientAndServer(receivedMessages) - val message = messagingClient.createMessage(topic, data = "first msg".toByteArray()) + val message = messagingClient.createMessage(TOPIC, data = "first msg".toByteArray()) messagingClient.send(message, messagingClient.myAddress) val actual: Message = receivedMessages.take() @@ -146,15 +149,9 @@ class ArtemisMessagingTests { val receivedMessages = LinkedBlockingQueue() val messagingClient = createAndStartClientAndServer(receivedMessages) - val message = messagingClient.createMessage(topic, data = "first msg".toByteArray()) + val message = messagingClient.createMessage(TOPIC, data = "first msg".toByteArray()) messagingClient.send(message, messagingClient.myAddress) - val networkMapMessage = messagingClient.createMessage(NetworkMapService.FETCH_TOPIC, data = "second msg".toByteArray()) - messagingClient.send(networkMapMessage, messagingClient.myAddress) - - val actual: Message = receivedMessages.take() - assertEquals("second msg", String(actual.data)) - assertNull(receivedMessages.poll(200, MILLISECONDS)) settableFuture.set(Unit) val firstActual: Message = receivedMessages.take() assertEquals("first msg", String(firstActual.data)) @@ -171,17 +168,10 @@ class ArtemisMessagingTests { val messagingClient = createAndStartClientAndServer(receivedMessages) for (iter in 1..iterations) { - val message = messagingClient.createMessage(topic, data = "first msg $iter".toByteArray()) + val message = messagingClient.createMessage(TOPIC, data = "first msg $iter".toByteArray()) messagingClient.send(message, messagingClient.myAddress) } - val networkMapMessage = messagingClient.createMessage(NetworkMapService.FETCH_TOPIC, data = "second msg".toByteArray()) - messagingClient.send(networkMapMessage, messagingClient.myAddress) - - val actual: Message = receivedMessages.take() - assertEquals("second msg", String(actual.data)) - assertNull(receivedMessages.poll(200, MILLISECONDS)) - // Stop client and server and create afresh. messagingClient.stop() messagingServer?.stop() @@ -205,10 +195,7 @@ class ArtemisMessagingTests { val messagingClient = createMessagingClient() startNodeMessagingClient() - messagingClient.addMessageHandler(topic) { message, _ -> - receivedMessages.add(message) - } - messagingClient.addMessageHandler(NetworkMapService.FETCH_TOPIC) { message, _ -> + messagingClient.addMessageHandler(TOPIC) { message, _ -> receivedMessages.add(message) } // Run after the handlers are added, otherwise (some of) the messages get delivered and discarded / dead-lettered. @@ -225,7 +212,6 @@ class ArtemisMessagingTests { identity.public, ServiceAffinityExecutor("ArtemisMessagingTests", 1), database, - networkMapRegistrationFuture, MonitoringService(MetricRegistry())).apply { config.configureWithDevSSLCertificate() messagingClient = this diff --git a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt index b493466645..cb4be70ef7 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt @@ -38,13 +38,13 @@ class NetworkMapCacheTest { @Test fun `getNodeByLegalIdentity`() { - val notaryNode = mockNet.createNotaryNode() val aliceNode = mockNet.createPartyNode(ALICE.name) - val notaryCache: NetworkMapCache = notaryNode.services.networkMapCache + val bobNode = mockNet.createPartyNode(BOB.name) + val bobCache: NetworkMapCache = bobNode.services.networkMapCache val expected = aliceNode.info mockNet.runNetwork() - val actual = notaryNode.database.transaction { notaryCache.getNodeByLegalIdentity(aliceNode.info.chooseIdentity()) } + val actual = bobNode.database.transaction { bobCache.getNodeByLegalIdentity(aliceNode.info.chooseIdentity()) } assertEquals(expected, actual) // TODO: Should have a test case with anonymous lookup @@ -52,30 +52,30 @@ class NetworkMapCacheTest { @Test fun `getPeerByLegalName`() { - val notaryNode = mockNet.createNotaryNode() val aliceNode = mockNet.createPartyNode(ALICE.name) - val notaryCache: NetworkMapCache = notaryNode.services.networkMapCache + val bobNode = mockNet.createPartyNode(BOB.name) + val bobCache: NetworkMapCache = bobNode.services.networkMapCache val expected = aliceNode.info.legalIdentities.single() mockNet.runNetwork() - val actual = notaryNode.database.transaction { notaryCache.getPeerByLegalName(ALICE.name) } + val actual = bobNode.database.transaction { bobCache.getPeerByLegalName(ALICE.name) } assertEquals(expected, actual) } @Test fun `remove node from cache`() { - val notaryNode = mockNet.createNotaryNode() val aliceNode = mockNet.createPartyNode(ALICE.name) - val notaryLegalIdentity = notaryNode.info.chooseIdentity() + val bobNode = mockNet.createPartyNode(BOB.name) + val bobLegalIdentity = bobNode.info.chooseIdentity() val alice = aliceNode.info.chooseIdentity() - val notaryCache = notaryNode.services.networkMapCache + val bobCache = bobNode.services.networkMapCache mockNet.runNetwork() - notaryNode.database.transaction { - assertThat(notaryCache.getNodeByLegalIdentity(alice) != null) - notaryCache.removeNode(aliceNode.info) - assertThat(notaryCache.getNodeByLegalIdentity(alice) == null) - assertThat(notaryCache.getNodeByLegalIdentity(notaryLegalIdentity) != null) - assertThat(notaryCache.getNodeByLegalName(alice.name) == null) + bobNode.database.transaction { + assertThat(bobCache.getNodeByLegalIdentity(alice) != null) + bobCache.removeNode(aliceNode.info) + assertThat(bobCache.getNodeByLegalIdentity(alice) == null) + assertThat(bobCache.getNodeByLegalIdentity(bobLegalIdentity) != null) + assertThat(bobCache.getNodeByLegalName(alice.name) == null) } } } diff --git a/node/src/test/kotlin/net/corda/node/services/schema/NodeSchemaServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/schema/NodeSchemaServiceTest.kt index be168e8b98..b17b91928f 100644 --- a/node/src/test/kotlin/net/corda/node/services/schema/NodeSchemaServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/schema/NodeSchemaServiceTest.kt @@ -43,9 +43,7 @@ class NodeSchemaServiceTest { @Test fun `auto scanning of custom schemas for testing with Driver`() { driver(startNodesInProcess = true) { - val node = startNode() - val nodeHandle = node.getOrThrow() - val result = nodeHandle.rpc.startFlow(::MappedSchemasFlow) + val result = defaultNotaryNode.getOrThrow().rpc.startFlow(::MappedSchemasFlow) val mappedSchemas = result.returnValue.getOrThrow() assertTrue(mappedSchemas.contains(TestSchema.name)) } @@ -54,11 +52,12 @@ class NodeSchemaServiceTest { @Test fun `custom schemas are loaded eagerly`() { val expected = setOf("PARENTS", "CHILDREN") - assertEquals>(expected, driver { - (startNode(startInSameProcess = true).getOrThrow() as NodeHandle.InProcess).node.database.transaction { + val tables = driver(startNodesInProcess = true) { + (defaultNotaryNode.getOrThrow() as NodeHandle.InProcess).node.database.transaction { session.createNativeQuery("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES").list() } - }.toMutableSet().apply { retainAll(expected) }) + } + assertEquals>(expected, tables.toMutableSet().apply { retainAll(expected) }) } @StartableByRPC diff --git a/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt index 42229d2f4c..b809ec06e0 100644 --- a/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt @@ -72,23 +72,19 @@ class FlowFrameworkTests { @Before fun start() { - mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin(), cordappPackages = listOf("net.corda.finance.contracts", "net.corda.testing.contracts")) + mockNet = MockNetwork( + servicePeerAllocationStrategy = RoundRobin(), + cordappPackages = listOf("net.corda.finance.contracts", "net.corda.testing.contracts") + ) aliceNode = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME)) bobNode = mockNet.createNode(MockNodeParameters(legalName = BOB_NAME)) - mockNet.runNetwork() - - // We intentionally create our own notary and ignore the one provided by the network - // Note that these notaries don't operate correctly as they don't share their state. They are only used for testing - // service addressing. - val notary = mockNet.createNotaryNode() receivedSessionMessagesObservable().forEach { receivedSessionMessages += it } - mockNet.runNetwork() // Extract identities alice = aliceNode.info.singleIdentity() bob = bobNode.info.singleIdentity() - notaryIdentity = notary.services.getDefaultNotary() + notaryIdentity = aliceNode.services.getDefaultNotary() } @After diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/DistributedImmutableMapTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/DistributedImmutableMapTests.kt index b2a34d127e..f2ca28c8ec 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/DistributedImmutableMapTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/DistributedImmutableMapTests.kt @@ -8,11 +8,12 @@ import io.atomix.copycat.server.storage.Storage import io.atomix.copycat.server.storage.StorageLevel import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.getOrThrow -import net.corda.node.services.network.NetworkMapService import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.DatabaseTransaction import net.corda.node.utilities.configureDatabase -import net.corda.testing.* +import net.corda.testing.LogHelper +import net.corda.testing.SerializationEnvironmentRule +import net.corda.testing.freeLocalHostAndPort import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties import net.corda.testing.node.MockServices.Companion.makeTestIdentityService @@ -37,14 +38,12 @@ class DistributedImmutableMapTests { @Before fun setup() { LogHelper.setLevel("-org.apache.activemq") - LogHelper.setLevel(NetworkMapService::class) cluster = setUpCluster() } @After fun tearDown() { LogHelper.reset("org.apache.activemq") - LogHelper.reset(NetworkMapService::class) cluster.forEach { it.client.close() it.server.shutdown() diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt index 05bf4ecdca..a17b9c68c9 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt @@ -14,10 +14,13 @@ import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.seconds import net.corda.node.services.api.StartedNodeServices -import net.corda.testing.* +import net.corda.testing.ALICE_NAME import net.corda.testing.contracts.DummyContract +import net.corda.testing.dummyCommand +import net.corda.testing.getDefaultNotary import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNodeParameters +import net.corda.testing.singleIdentity import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Before @@ -28,20 +31,19 @@ import kotlin.test.assertEquals import kotlin.test.assertFailsWith class NotaryServiceTests { - lateinit var mockNet: MockNetwork - lateinit var notaryServices: StartedNodeServices - lateinit var aliceServices: StartedNodeServices - lateinit var notary: Party - lateinit var alice: Party + private lateinit var mockNet: MockNetwork + private lateinit var notaryServices: StartedNodeServices + private lateinit var aliceServices: StartedNodeServices + private lateinit var notary: Party + private lateinit var alice: Party @Before fun setup() { mockNet = MockNetwork(cordappPackages = listOf("net.corda.testing.contracts")) - val notaryNode = mockNet.createNotaryNode(validating = false) aliceServices = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME)).services mockNet.runNetwork() // Clear network map registration messages - notaryServices = notaryNode.services - notary = notaryServices.getDefaultNotary() + notaryServices = mockNet.defaultNotaryNode.services //TODO get rid of that + notary = aliceServices.getDefaultNotary() alice = aliceServices.myInfo.singleIdentity() } @@ -155,7 +157,7 @@ class NotaryServiceTests { return future } - fun issueState(services: ServiceHub, identity: Party): StateAndRef<*> { + private fun issueState(services: ServiceHub, identity: Party): StateAndRef<*> { val tx = DummyContract.generateInitial(Random().nextInt(), notary, identity.ref(0)) val signedByNode = services.signInitialTransaction(tx) val stx = notaryServices.addSignature(signedByNode, notary.owningKey) diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt index 4fe3f68a20..112348d74c 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt @@ -15,10 +15,13 @@ import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.getOrThrow import net.corda.node.services.api.StartedNodeServices import net.corda.node.services.issueInvalidState -import net.corda.testing.* +import net.corda.testing.ALICE_NAME +import net.corda.testing.MEGA_CORP_KEY import net.corda.testing.contracts.DummyContract +import net.corda.testing.dummyCommand import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNodeParameters +import net.corda.testing.singleIdentity import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Before @@ -28,21 +31,20 @@ import kotlin.test.assertEquals import kotlin.test.assertFailsWith class ValidatingNotaryServiceTests { - lateinit var mockNet: MockNetwork - lateinit var notaryServices: StartedNodeServices - lateinit var aliceServices: StartedNodeServices - lateinit var notary: Party - lateinit var alice: Party + private lateinit var mockNet: MockNetwork + private lateinit var notaryServices: StartedNodeServices + private lateinit var aliceServices: StartedNodeServices + private lateinit var notary: Party + private lateinit var alice: Party @Before fun setup() { mockNet = MockNetwork(cordappPackages = listOf("net.corda.testing.contracts")) - val notaryNode = mockNet.createNotaryNode() val aliceNode = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME)) mockNet.runNetwork() // Clear network map registration messages - notaryServices = notaryNode.services + notaryServices = mockNet.defaultNotaryNode.services aliceServices = aliceNode.services - notary = notaryServices.getDefaultNotary() + notary = mockNet.defaultNotaryIdentity alice = aliceNode.info.singleIdentity() } @@ -97,7 +99,7 @@ class ValidatingNotaryServiceTests { return future } - fun issueState(serviceHub: ServiceHub, identity: Party): StateAndRef<*> { + private fun issueState(serviceHub: ServiceHub, identity: Party): StateAndRef<*> { val tx = DummyContract.generateInitial(Random().nextInt(), notary, identity.ref(0)) val signedByNode = serviceHub.signInitialTransaction(tx) val stx = notaryServices.addSignature(signedByNode, notary.owningKey) diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt index ae1ac4da8f..16aa63254d 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt @@ -28,7 +28,6 @@ import net.corda.node.services.api.VaultServiceInternal import net.corda.testing.chooseIdentity import net.corda.testing.node.MockNetwork import net.corda.testing.rigorousMock -import net.corda.testing.node.MockNodeArgs import net.corda.testing.node.MockNodeParameters import org.junit.After import org.junit.Test @@ -80,15 +79,13 @@ class VaultSoftLockManagerTest { private val mockVault = rigorousMock().also { doNothing().whenever(it).softLockRelease(any(), anyOrNull()) } - private val mockNet = MockNetwork(cordappPackages = listOf(ContractImpl::class.packageName), defaultFactory = object : MockNetwork.Factory { - override fun create(args: MockNodeArgs): MockNetwork.MockNode { - return object : MockNetwork.MockNode(args) { - override fun makeVaultService(keyManagementService: KeyManagementService, stateLoader: StateLoader): VaultServiceInternal { - val realVault = super.makeVaultService(keyManagementService, stateLoader) - return object : VaultServiceInternal by realVault { - override fun softLockRelease(lockId: UUID, stateRefs: NonEmptySet?) { - mockVault.softLockRelease(lockId, stateRefs) // No need to also call the real one for these tests. - } + private val mockNet = MockNetwork(cordappPackages = listOf(ContractImpl::class.packageName), defaultFactory = { args -> + object : MockNetwork.MockNode(args) { + override fun makeVaultService(keyManagementService: KeyManagementService, stateLoader: StateLoader): VaultServiceInternal { + val realVault = super.makeVaultService(keyManagementService, stateLoader) + return object : VaultServiceInternal by realVault { + override fun softLockRelease(lockId: UUID, stateRefs: NonEmptySet?) { + mockVault.softLockRelease(lockId, stateRefs) // No need to also call the real one for these tests. } } } diff --git a/node/src/test/kotlin/net/corda/node/utilities/X509UtilitiesTest.kt b/node/src/test/kotlin/net/corda/node/utilities/X509UtilitiesTest.kt index a94b2d7d88..df81bef948 100644 --- a/node/src/test/kotlin/net/corda/node/utilities/X509UtilitiesTest.kt +++ b/node/src/test/kotlin/net/corda/node/utilities/X509UtilitiesTest.kt @@ -14,7 +14,7 @@ import net.corda.core.serialization.serialize import net.corda.node.serialization.KryoServerSerializationScheme import net.corda.node.services.config.createKeystoreForCordaNode import net.corda.nodeapi.internal.serialization.AllWhitelist -import net.corda.nodeapi.internal.serialization.KryoHeaderV0_1 +import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1 import net.corda.nodeapi.internal.serialization.SerializationContextImpl import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl import net.corda.testing.ALICE diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashSelectionH2Test.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashSelectionH2Test.kt index 5aa10b3179..36b767c219 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashSelectionH2Test.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashSelectionH2Test.kt @@ -4,38 +4,35 @@ import net.corda.core.utilities.getOrThrow import com.r3.corda.enterprise.perftestcordapp.DOLLARS import com.r3.corda.enterprise.perftestcordapp.flows.CashException import com.r3.corda.enterprise.perftestcordapp.flows.CashPaymentFlow -import net.corda.testing.chooseIdentity import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNodeParameters import org.assertj.core.api.Assertions.assertThatThrownBy +import org.junit.After import org.junit.Test - class CashSelectionH2Test { + private val mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.finance")) + + @After + fun cleanUp() { + mockNet.stopNodes() + } @Test fun `check does not hold connection over retries`() { - val mockNet = MockNetwork(threadPerNode = true) - try { - val notaryNode = mockNet.createNotaryNode() - val bankA = mockNet.createNode(MockNodeParameters(configOverrides = { existingConfig -> - // Tweak connections to be minimal to make this easier (1 results in a hung node during start up, so use 2 connections). - existingConfig.dataSourceProperties.setProperty("maximumPoolSize", "2") - existingConfig - })) + val bankA = mockNet.createNode(MockNodeParameters(configOverrides = { + // Tweak connections to be minimal to make this easier (1 results in a hung node during start up, so use 2 connections). + it.dataSourceProperties.setProperty("maximumPoolSize", "2") + })) + val notary = mockNet.defaultNotaryIdentity - mockNet.startNodes() + // Start more cash spends than we have connections. If spend leaks a connection on retry, we will run out of connections. + val flow1 = bankA.services.startFlow(CashPaymentFlow(amount = 100.DOLLARS, anonymous = false, recipient = notary)) + val flow2 = bankA.services.startFlow(CashPaymentFlow(amount = 100.DOLLARS, anonymous = false, recipient = notary)) + val flow3 = bankA.services.startFlow(CashPaymentFlow(amount = 100.DOLLARS, anonymous = false, recipient = notary)) - // Start more cash spends than we have connections. If spend leaks a connection on retry, we will run out of connections. - val flow1 = bankA.services.startFlow(CashPaymentFlow(amount = 100.DOLLARS, anonymous = false, recipient = notaryNode.info.chooseIdentity())) - val flow2 = bankA.services.startFlow(CashPaymentFlow(amount = 100.DOLLARS, anonymous = false, recipient = notaryNode.info.chooseIdentity())) - val flow3 = bankA.services.startFlow(CashPaymentFlow(amount = 100.DOLLARS, anonymous = false, recipient = notaryNode.info.chooseIdentity())) - - assertThatThrownBy { flow1.resultFuture.getOrThrow() }.isInstanceOf(CashException::class.java) - assertThatThrownBy { flow2.resultFuture.getOrThrow() }.isInstanceOf(CashException::class.java) - assertThatThrownBy { flow3.resultFuture.getOrThrow() }.isInstanceOf(CashException::class.java) - } finally { - mockNet.stopNodes() - } + assertThatThrownBy { flow1.resultFuture.getOrThrow() }.isInstanceOf(CashException::class.java) + assertThatThrownBy { flow2.resultFuture.getOrThrow() }.isInstanceOf(CashException::class.java) + assertThatThrownBy { flow3.resultFuture.getOrThrow() }.isInstanceOf(CashException::class.java) } } \ No newline at end of file diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashExitFlowTests.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashExitFlowTests.kt index fc827ac9c6..eb469fc55d 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashExitFlowTests.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashExitFlowTests.kt @@ -20,20 +20,18 @@ import kotlin.test.assertEquals import kotlin.test.assertFailsWith class CashExitFlowTests { - private lateinit var mockNet : MockNetwork + private lateinit var mockNet: MockNetwork private val initialBalance = 2000.DOLLARS private val ref = OpaqueBytes.of(0x01) private lateinit var bankOfCordaNode: StartedNode private lateinit var bankOfCorda: Party - private lateinit var notaryNode: StartedNode private lateinit var notary: Party @Before fun start() { - mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin(), cordappPackages = listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset")) - notaryNode = mockNet.createNotaryNode() + mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin(), + cordappPackages = listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset")) bankOfCordaNode = mockNet.createPartyNode(BOC.name) - notary = notaryNode.services.getDefaultNotary() bankOfCorda = bankOfCordaNode.info.chooseIdentity() mockNet.runNetwork() diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashIssueFlowTests.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashIssueFlowTests.kt index 9df67f5f29..89287be7a9 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashIssueFlowTests.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashIssueFlowTests.kt @@ -7,9 +7,8 @@ import com.r3.corda.enterprise.perftestcordapp.DOLLARS import com.r3.corda.enterprise.perftestcordapp.`issued by` import com.r3.corda.enterprise.perftestcordapp.contracts.asset.Cash import net.corda.node.internal.StartedNode -import net.corda.testing.chooseIdentity -import net.corda.testing.getDefaultNotary import net.corda.testing.BOC +import net.corda.testing.chooseIdentity import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNetwork.MockNode @@ -20,19 +19,17 @@ import kotlin.test.assertEquals import kotlin.test.assertFailsWith class CashIssueFlowTests { - private lateinit var mockNet : MockNetwork + private lateinit var mockNet: MockNetwork private lateinit var bankOfCordaNode: StartedNode private lateinit var bankOfCorda: Party - private lateinit var notaryNode: StartedNode private lateinit var notary: Party @Before fun start() { mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin(), cordappPackages = listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset")) - notaryNode = mockNet.createNotaryNode() bankOfCordaNode = mockNet.createPartyNode(BOC.name) bankOfCorda = bankOfCordaNode.info.chooseIdentity() - notary = notaryNode.services.getDefaultNotary() + notary = mockNet.defaultNotaryIdentity mockNet.runNetwork() } diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashPaymentFlowTests.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashPaymentFlowTests.kt index 58aba54e43..a43515da84 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashPaymentFlowTests.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashPaymentFlowTests.kt @@ -21,23 +21,21 @@ import kotlin.test.assertEquals import kotlin.test.assertFailsWith class CashPaymentFlowTests { - private lateinit var mockNet : MockNetwork + private lateinit var mockNet: MockNetwork private val initialBalance = 2000.DOLLARS private val ref = OpaqueBytes.of(0x01) private lateinit var bankOfCordaNode: StartedNode private lateinit var bankOfCorda: Party - private lateinit var notaryNode: StartedNode - private lateinit var notary: Party + private lateinit var aliceNode: StartedNode @Before fun start() { mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin(), cordappPackages = listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset")) - notaryNode = mockNet.createNotaryNode() bankOfCordaNode = mockNet.createPartyNode(BOC.name) + aliceNode = mockNet.createPartyNode(ALICE.name) bankOfCorda = bankOfCordaNode.info.chooseIdentity() - notary = notaryNode.services.getDefaultNotary() - val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance, ref, notary)).resultFuture mockNet.runNetwork() + val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance, ref, mockNet.defaultNotaryIdentity)).resultFuture future.getOrThrow() } @@ -48,7 +46,7 @@ class CashPaymentFlowTests { @Test fun `pay some cash`() { - val payTo = notaryNode.info.chooseIdentity() + val payTo = aliceNode.info.chooseIdentity() val expectedPayment = 500.DOLLARS val expectedChange = 1500.DOLLARS @@ -56,7 +54,7 @@ class CashPaymentFlowTests { // Register for vault updates val criteria = QueryCriteria.VaultQueryCriteria(status = Vault.StateStatus.ALL) val (_, vaultUpdatesBoc) = bankOfCordaNode.services.vaultService.trackBy(criteria) - val (_, vaultUpdatesBankClient) = notaryNode.services.vaultService.trackBy(criteria) + val (_, vaultUpdatesBankClient) = aliceNode.services.vaultService.trackBy(criteria) val future = bankOfCordaNode.services.startFlow(CashPaymentFlow(expectedPayment, payTo)).resultFuture @@ -88,7 +86,7 @@ class CashPaymentFlowTests { @Test fun `pay more than we have`() { - val payTo = notaryNode.info.chooseIdentity() + val payTo = aliceNode.info.chooseIdentity() val expected = 4000.DOLLARS val future = bankOfCordaNode.services.startFlow(CashPaymentFlow(expected, payTo)).resultFuture @@ -100,7 +98,7 @@ class CashPaymentFlowTests { @Test fun `pay zero cash`() { - val payTo = notaryNode.info.chooseIdentity() + val payTo = aliceNode.info.chooseIdentity() val expected = 0.DOLLARS val future = bankOfCordaNode.services.startFlow(CashPaymentFlow(expected, payTo)).resultFuture diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlowTest.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlowTest.kt index 23653ad786..c5a863d023 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlowTest.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlowTest.kt @@ -82,7 +82,7 @@ internal fun CheckpointStorage.checkpoints(): List { * We assume that Alice and Bob already found each other via some market, and have agreed the details already. */ @RunWith(Parameterized::class) -class TwoPartyTradeFlowTests(val anonymous: Boolean) { +class TwoPartyTradeFlowTests(private val anonymous: Boolean) { companion object { private val cordappPackages = listOf("com.r3.corda.enterprise.perftestcordapp.contracts") @JvmStatic @@ -112,7 +112,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { // allow interruption half way through. mockNet = MockNetwork(threadPerNode = true, cordappPackages = cordappPackages) ledger(MockServices(cordappPackages), initialiseSerialization = false) { - val notaryNode = mockNet.createNotaryNode() + val notaryNode = mockNet.defaultNotaryNode val aliceNode = mockNet.createPartyNode(ALICE_NAME) val bobNode = mockNet.createPartyNode(BOB_NAME) val bankNode = mockNet.createPartyNode(BOC_NAME) @@ -162,7 +162,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { fun `trade cash for commercial paper fails using soft locking`() { mockNet = MockNetwork(threadPerNode = true, cordappPackages = cordappPackages) ledger(MockServices(cordappPackages), initialiseSerialization = false) { - val notaryNode = mockNet.createNotaryNode() + val notaryNode = mockNet.defaultNotaryNode val aliceNode = mockNet.createPartyNode(ALICE_NAME) val bobNode = mockNet.createPartyNode(BOB_NAME) val bankNode = mockNet.createPartyNode(BOC_NAME) @@ -218,7 +218,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { fun `shutdown and restore`() { mockNet = MockNetwork(cordappPackages = cordappPackages) ledger(MockServices(cordappPackages), initialiseSerialization = false) { - val notaryNode = mockNet.createNotaryNode() + val notaryNode = mockNet.defaultNotaryNode val aliceNode = mockNet.createPartyNode(ALICE_NAME) var bobNode = mockNet.createPartyNode(BOB_NAME) val bankNode = mockNet.createPartyNode(BOC_NAME) @@ -311,15 +311,14 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { // Creates a mock node with an overridden storage service that uses a RecordingMap, that lets us test the order // of gets and puts. - private fun makeNodeWithTracking(name: CordaX500Name): StartedNode { + private fun makeNodeWithTracking( + name: CordaX500Name): StartedNode { // Create a node in the mock network ... - return mockNet.createNode(MockNodeParameters(legalName = name), nodeFactory = object : MockNetwork.Factory { - override fun create(args: MockNodeArgs): MockNetwork.MockNode { - return object : MockNetwork.MockNode(args) { - // That constructs a recording tx storage - override fun makeTransactionStorage(): WritableTransactionStorage { - return RecordingTransactionStorage(database, super.makeTransactionStorage()) - } + return mockNet.createNode(MockNodeParameters(legalName = name), nodeFactory = { args -> + object : MockNetwork.MockNode(args) { + // That constructs a recording tx storage + override fun makeTransactionStorage(): WritableTransactionStorage { + return RecordingTransactionStorage(database, super.makeTransactionStorage()) } } }) @@ -328,7 +327,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { @Test fun `check dependencies of sale asset are resolved`() { mockNet = MockNetwork(cordappPackages = cordappPackages) - val notaryNode = mockNet.createNotaryNode() + val notaryNode = mockNet.defaultNotaryNode val aliceNode = makeNodeWithTracking(ALICE_NAME) val bobNode = makeNodeWithTracking(BOB_NAME) val bankNode = makeNodeWithTracking(BOC_NAME) @@ -434,7 +433,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { @Test fun `track works`() { mockNet = MockNetwork(cordappPackages = cordappPackages) - val notaryNode = mockNet.createNotaryNode() + val notaryNode = mockNet.defaultNotaryNode val aliceNode = makeNodeWithTracking(ALICE_NAME) val bobNode = makeNodeWithTracking(BOB_NAME) val bankNode = makeNodeWithTracking(BOC_NAME) @@ -589,7 +588,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { aliceError: Boolean, expectedMessageSubstring: String ) { - val notaryNode = mockNet.createNotaryNode() + val notaryNode = mockNet.defaultNotaryNode val aliceNode = mockNet.createPartyNode(ALICE_NAME) val bobNode = mockNet.createPartyNode(BOB_NAME) val bankNode = mockNet.createPartyNode(BOC_NAME) @@ -636,18 +635,25 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { notaryNode: StartedNode<*>, vararg extraSigningNodes: StartedNode<*>): Map { + val notaryParty = notaryNode.info.legalIdentities[0] val signed = wtxToSign.map { val id = it.id val sigs = mutableListOf() val nodeKey = node.info.chooseIdentity().owningKey - sigs.add(node.services.keyManagementService.sign(SignableData(id, SignatureMetadata(1, Crypto.findSignatureScheme(nodeKey).schemeNumberID)), nodeKey)) - sigs.add(notaryNode.services.keyManagementService.sign(SignableData(id, SignatureMetadata(1, - Crypto.findSignatureScheme(notaryNode.info.legalIdentities[1].owningKey).schemeNumberID)), notaryNode.info.legalIdentities[1].owningKey)) + sigs += node.services.keyManagementService.sign( + SignableData(id, SignatureMetadata(1, Crypto.findSignatureScheme(nodeKey).schemeNumberID)), + nodeKey + ) + sigs += notaryNode.services.keyManagementService.sign( + SignableData(id, SignatureMetadata(1, Crypto.findSignatureScheme(notaryParty.owningKey).schemeNumberID)), + notaryParty.owningKey + ) extraSigningNodes.forEach { currentNode -> - sigs.add(currentNode.services.keyManagementService.sign( - SignableData(id, SignatureMetadata(1, Crypto.findSignatureScheme(currentNode.info.chooseIdentity().owningKey).schemeNumberID)), + sigs += currentNode.services.keyManagementService.sign( + SignableData(id, SignatureMetadata( + 1, + Crypto.findSignatureScheme(currentNode.info.chooseIdentity().owningKey).schemeNumberID)), currentNode.info.chooseIdentity().owningKey) - ) } SignedTransaction(it, sigs) } diff --git a/samples/attachment-demo/src/integration-test/kotlin/net/corda/attachmentdemo/AttachmentDemoTest.kt b/samples/attachment-demo/src/integration-test/kotlin/net/corda/attachmentdemo/AttachmentDemoTest.kt index 7d6f43a907..42f04aa714 100644 --- a/samples/attachment-demo/src/integration-test/kotlin/net/corda/attachmentdemo/AttachmentDemoTest.kt +++ b/samples/attachment-demo/src/integration-test/kotlin/net/corda/attachmentdemo/AttachmentDemoTest.kt @@ -1,11 +1,12 @@ package net.corda.attachmentdemo +import net.corda.core.messaging.CordaRPCOps import net.corda.core.utilities.getOrThrow -import net.corda.node.services.FlowPermissions.Companion.startFlowPermission +import net.corda.node.services.Permissions.Companion.invokeRpc +import net.corda.node.services.Permissions.Companion.startFlow import net.corda.nodeapi.User import net.corda.testing.DUMMY_BANK_A import net.corda.testing.DUMMY_BANK_B -import net.corda.testing.DUMMY_NOTARY import net.corda.testing.driver.PortAllocation import net.corda.testing.driver.driver import org.junit.Test @@ -18,12 +19,18 @@ class AttachmentDemoTest { fun `attachment demo using a 10MB zip file`() { val numOfExpectedBytes = 10_000_000 driver(isDebug = true, portAllocation = PortAllocation.Incremental(20000)) { - val demoUser = listOf(User("demo", "demo", setOf(startFlowPermission()))) + val demoUser = listOf(User("demo", "demo", setOf( + startFlow(), + invokeRpc(CordaRPCOps::attachmentExists), + invokeRpc(CordaRPCOps::uploadAttachment), + invokeRpc(CordaRPCOps::openAttachment), + invokeRpc(CordaRPCOps::wellKnownPartyFromX500Name), + invokeRpc(CordaRPCOps::internalVerifiedTransactionsFeed) + ))) val (nodeA, nodeB) = listOf( startNode(providedName = DUMMY_BANK_A.name, rpcUsers = demoUser, maximumHeapSize = "1g", logLevel = "INFO"), - startNode(providedName = DUMMY_BANK_B.name, rpcUsers = demoUser, maximumHeapSize = "1g", logLevel = "INFO"), - startNotaryNode(DUMMY_NOTARY.name, validating = false)) - .map { it.getOrThrow() } + startNode(providedName = DUMMY_BANK_B.name, rpcUsers = demoUser, maximumHeapSize = "1g", logLevel = "INFO") + ).map { it.getOrThrow() } startWebserver(nodeB).getOrThrow() val senderThread = supplyAsync { diff --git a/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/Main.kt b/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/Main.kt index f63d1c68a7..ac2c59abac 100644 --- a/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/Main.kt +++ b/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/Main.kt @@ -4,7 +4,6 @@ import net.corda.core.internal.div import net.corda.nodeapi.User import net.corda.testing.DUMMY_BANK_A import net.corda.testing.DUMMY_BANK_B -import net.corda.testing.DUMMY_NOTARY import net.corda.testing.driver.driver /** @@ -14,7 +13,6 @@ import net.corda.testing.driver.driver fun main(args: Array) { val demoUser = listOf(User("demo", "demo", setOf("StartFlow.net.corda.flows.FinalityFlow"))) driver(isDebug = true, driverDirectory = "build" / "attachment-demo-nodes") { - startNotaryNode(DUMMY_NOTARY.name, validating = false) startNode(providedName = DUMMY_BANK_A.name, rpcUsers = demoUser) startNode(providedName = DUMMY_BANK_B.name, rpcUsers = demoUser) waitForAllNodesToFinish() diff --git a/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaHttpAPITest.kt b/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaHttpAPITest.kt index 8077105e41..0f37be7cbc 100644 --- a/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaHttpAPITest.kt +++ b/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaHttpAPITest.kt @@ -5,20 +5,20 @@ import net.corda.bank.api.BankOfCordaWebApi.IssueRequestParams import net.corda.core.utilities.getOrThrow import net.corda.testing.BOC import net.corda.testing.driver.driver -import net.corda.testing.notary import org.junit.Test import kotlin.test.assertTrue class BankOfCordaHttpAPITest { @Test fun `issuer flow via Http`() { - driver(extraCordappPackagesToScan = listOf("net.corda.finance"), dsl = { - val bigCorpNodeFuture = startNode(providedName = BIGCORP_LEGAL_NAME) - val nodeBankOfCordaFuture = startNotaryNode(BOC.name, validating = false) - val (nodeBankOfCorda) = listOf(nodeBankOfCordaFuture, bigCorpNodeFuture).map { it.getOrThrow() } - val nodeBankOfCordaApiAddr = startWebserver(nodeBankOfCorda).getOrThrow().listenAddress - val notaryName = notary().node.nodeInfo.legalIdentities[1].name - assertTrue(BankOfCordaClientApi(nodeBankOfCordaApiAddr).requestWebIssue(IssueRequestParams(1000, "USD", BIGCORP_LEGAL_NAME, "1", BOC.name, notaryName))) - }, isDebug = true) + driver(extraCordappPackagesToScan = listOf("net.corda.finance"), isDebug = true) { + val (bocNode) = listOf( + startNode(providedName = BOC.name), + startNode(providedName = BIGCORP_LEGAL_NAME) + ).map { it.getOrThrow() } + val bocApiAddress = startWebserver(bocNode).getOrThrow().listenAddress + val issueRequestParams = IssueRequestParams(1000, "USD", BIGCORP_LEGAL_NAME, "1", BOC.name, defaultNotaryIdentity.name) + assertTrue(BankOfCordaClientApi(bocApiAddress).requestWebIssue(issueRequestParams)) + } } } diff --git a/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaRPCClientTest.kt b/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaRPCClientTest.kt index 147d6a65e0..4b2e6c2580 100644 --- a/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaRPCClientTest.kt +++ b/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaRPCClientTest.kt @@ -1,5 +1,6 @@ package net.corda.bank +import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.startFlow import net.corda.core.node.services.Vault import net.corda.core.node.services.vault.QueryCriteria @@ -7,7 +8,8 @@ import net.corda.core.utilities.getOrThrow import net.corda.finance.DOLLARS import net.corda.finance.contracts.asset.Cash import net.corda.finance.flows.CashIssueAndPaymentFlow -import net.corda.node.services.FlowPermissions.Companion.startFlowPermission +import net.corda.node.services.Permissions.Companion.invokeRpc +import net.corda.node.services.Permissions.Companion.startFlow import net.corda.nodeapi.User import net.corda.testing.* import net.corda.testing.driver.driver @@ -16,13 +18,19 @@ import org.junit.Test class BankOfCordaRPCClientTest { @Test fun `issuer flow via RPC`() { - driver(extraCordappPackagesToScan = listOf("net.corda.finance"), dsl = { + val commonPermissions = setOf( + invokeRpc("vaultTrackByCriteria"), + invokeRpc(CordaRPCOps::wellKnownPartyFromX500Name), + invokeRpc(CordaRPCOps::notaryIdentities) + ) + driver(extraCordappPackagesToScan = listOf("net.corda.finance"), isDebug = true) { val bocManager = User("bocManager", "password1", permissions = setOf( - startFlowPermission())) - val bigCorpCFO = User("bigCorpCFO", "password2", permissions = emptySet()) - val nodeBankOfCordaFuture = startNotaryNode(BOC.name, rpcUsers = listOf(bocManager), validating = false) - val nodeBigCorporationFuture = startNode(providedName = BIGCORP_LEGAL_NAME, rpcUsers = listOf(bigCorpCFO)) - val (nodeBankOfCorda, nodeBigCorporation) = listOf(nodeBankOfCordaFuture, nodeBigCorporationFuture).map { it.getOrThrow() } + startFlow()) + commonPermissions) + val bigCorpCFO = User("bigCorpCFO", "password2", permissions = emptySet() + commonPermissions) + val (nodeBankOfCorda, nodeBigCorporation) = listOf( + startNode(providedName = BOC.name, rpcUsers = listOf(bocManager)), + startNode(providedName = BIGCORP_LEGAL_NAME, rpcUsers = listOf(bigCorpCFO)) + ).map { it.getOrThrow() } // Bank of Corda RPC Client val bocClient = nodeBankOfCorda.rpcClientToNode() @@ -31,8 +39,6 @@ class BankOfCordaRPCClientTest { // Big Corporation RPC Client val bigCorpClient = nodeBigCorporation.rpcClientToNode() val bigCorpProxy = bigCorpClient.start("bigCorpCFO", "password2").proxy - bocProxy.waitUntilNetworkReady().getOrThrow() - bigCorpProxy.waitUntilNetworkReady().getOrThrow() // Register for Bank of Corda Vault updates val criteria = QueryCriteria.VaultQueryCriteria(status = Vault.StateStatus.ALL) @@ -45,12 +51,11 @@ class BankOfCordaRPCClientTest { // Kick-off actual Issuer Flow val anonymous = true - val notary = bocProxy.notaryIdentities().first() bocProxy.startFlow(::CashIssueAndPaymentFlow, 1000.DOLLARS, BIG_CORP_PARTY_REF, bigCorporation, anonymous, - notary).returnValue.getOrThrow() + defaultNotaryIdentity).returnValue.getOrThrow() // Check Bank of Corda Vault Updates vaultUpdatesBoc.expectEvents { @@ -78,6 +83,6 @@ class BankOfCordaRPCClientTest { } ) } - }, isDebug = true) + } } -} +} \ No newline at end of file diff --git a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt index b526e580fe..a0e7752640 100644 --- a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt +++ b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt @@ -9,8 +9,7 @@ import net.corda.finance.flows.CashConfigDataFlow import net.corda.finance.flows.CashExitFlow import net.corda.finance.flows.CashIssueAndPaymentFlow import net.corda.finance.flows.CashPaymentFlow -import net.corda.node.services.FlowPermissions.Companion.startFlowPermission -import net.corda.node.services.transactions.ValidatingNotaryService +import net.corda.node.services.Permissions.Companion.startFlow import net.corda.nodeapi.User import net.corda.testing.BOC import net.corda.testing.DUMMY_NOTARY @@ -57,33 +56,32 @@ private class BankOfCordaDriver { try { when (role) { Role.ISSUER -> { - driver(dsl = { - startNotaryNode(providedName = DUMMY_NOTARY.name, validating = true) + driver(isDebug = true, extraCordappPackagesToScan = listOf("net.corda.finance.contracts.asset")) { val bankUser = User( BANK_USERNAME, "test", permissions = setOf( - startFlowPermission(), - startFlowPermission(), - startFlowPermission(), - startFlowPermission(), - startFlowPermission() + startFlow(), + startFlow(), + startFlow(), + startFlow(), + startFlow() )) val bankOfCorda = startNode( providedName = BOC.name, rpcUsers = listOf(bankUser)) val bigCorpUser = User(BIGCORP_USERNAME, "test", permissions = setOf( - startFlowPermission(), - startFlowPermission())) + startFlow(), + startFlow())) startNode(providedName = BIGCORP_LEGAL_NAME, rpcUsers = listOf(bigCorpUser)) startWebserver(bankOfCorda.get()) waitForAllNodesToFinish() - }, isDebug = true, extraCordappPackagesToScan = listOf("net.corda.finance.contracts.asset")) + } } else -> { val requestParams = IssueRequestParams(options.valueOf(quantity), options.valueOf(currency), BIGCORP_LEGAL_NAME, - "1", BOC.name, DUMMY_NOTARY.name.copy(commonName = ValidatingNotaryService.id)) + "1", BOC.name, DUMMY_NOTARY.name) when(role) { Role.ISSUE_CASH_RPC -> { println("Requesting Cash via RPC ...") @@ -120,4 +118,3 @@ private class BankOfCordaDriver { } } - diff --git a/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/Main.kt b/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/Main.kt index d1e770b257..64acf2c4ac 100644 --- a/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/Main.kt +++ b/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/Main.kt @@ -3,7 +3,6 @@ package net.corda.irs import net.corda.core.utilities.getOrThrow import net.corda.testing.DUMMY_BANK_A import net.corda.testing.DUMMY_BANK_B -import net.corda.testing.DUMMY_NOTARY import net.corda.testing.driver.driver /** @@ -11,17 +10,17 @@ import net.corda.testing.driver.driver * Do not use in a production environment. */ fun main(args: Array) { - driver(dsl = { - val (controller, nodeA, nodeB) = listOf( - startNotaryNode(DUMMY_NOTARY.name, validating = false), + driver(useTestClock = true, isDebug = true) { + val (nodeA, nodeB) = listOf( startNode(providedName = DUMMY_BANK_A.name), - startNode(providedName = DUMMY_BANK_B.name)) - .map { it.getOrThrow() } + startNode(providedName = DUMMY_BANK_B.name) + ).map { it.getOrThrow() } + val controller = defaultNotaryNode.getOrThrow() startWebserver(controller) startWebserver(nodeA) startWebserver(nodeB) waitForAllNodesToFinish() - }, useTestClock = true, isDebug = true) + } } diff --git a/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt b/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt index 2b286ceaab..193eb8ec08 100644 --- a/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt +++ b/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt @@ -205,7 +205,7 @@ class NodeInterestRatesTest { @Test fun `network tearoff`() { val mockNet = MockNetwork(initialiseSerialization = false, cordappPackages = listOf("net.corda.finance.contracts", "net.corda.irs")) - val n1 = mockNet.createNotaryNode() + val aliceNode = mockNet.createPartyNode(ALICE.name) val oracleNode = mockNet.createNode().apply { internals.registerInitiatedFlow(NodeInterestRates.FixQueryHandler::class.java) internals.registerInitiatedFlow(NodeInterestRates.FixSignHandler::class.java) @@ -218,7 +218,7 @@ class NodeInterestRatesTest { val flow = FilteredRatesFlow(tx, oracleNode.info.chooseIdentity(), fixOf, BigDecimal("0.675"), BigDecimal("0.1")) LogHelper.setLevel("rates") mockNet.runNetwork() - val future = n1.services.startFlow(flow).resultFuture + val future = aliceNode.services.startFlow(flow).resultFuture mockNet.runNetwork() future.getOrThrow() // We should now have a valid fix of our tx from the oracle. diff --git a/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/flows/UpdateBusinessDayFlow.kt b/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/flows/UpdateBusinessDayFlow.kt index 1e36a3bc86..ae2dadab5e 100644 --- a/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/flows/UpdateBusinessDayFlow.kt +++ b/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/flows/UpdateBusinessDayFlow.kt @@ -7,11 +7,10 @@ import net.corda.core.flows.InitiatingFlow import net.corda.core.flows.StartableByRPC import net.corda.core.identity.Party import net.corda.core.flows.* -import net.corda.core.node.NodeInfo import net.corda.core.serialization.CordaSerializable import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.unwrap -import net.corda.node.utilities.TestClock +import net.corda.node.utilities.DemoClock import java.time.LocalDate /** @@ -27,7 +26,7 @@ object UpdateBusinessDayFlow { @Suspendable override fun call() { val message = otherPartySession.receive().unwrap { it } - (serviceHub.clock as TestClock).updateDate(message.date) + (serviceHub.clock as DemoClock).updateDate(message.date) } } diff --git a/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt b/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt index 0fa498891f..ca9b841d9f 100644 --- a/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt +++ b/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt @@ -8,6 +8,7 @@ import com.fasterxml.jackson.databind.JsonDeserializer import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.module.SimpleModule import com.fasterxml.jackson.module.kotlin.readValue +import net.corda.client.jackson.JacksonSupport import net.corda.client.rpc.CordaRPCClient import net.corda.core.contracts.UniqueIdentifier import net.corda.core.identity.Party @@ -24,6 +25,7 @@ import net.corda.nodeapi.User import net.corda.test.spring.springDriver import net.corda.testing.* import net.corda.testing.http.HttpApi +import net.corda.testing.node.NotarySpec import org.apache.commons.io.IOUtils import org.assertj.core.api.Assertions.assertThat import org.junit.Test @@ -31,28 +33,29 @@ import rx.Observable import java.time.Duration import java.time.LocalDate -class IRSDemoTest : IntegrationTestCategory { - +class IRSDemoTest { companion object { val log = loggerFor() } - val rpcUsers = listOf(User("user", "password", - setOf("StartFlow.net.corda.irs.flows.AutoOfferFlow\$Requester", - "StartFlow.net.corda.irs.flows.UpdateBusinessDayFlow\$Broadcast", - "StartFlow.net.corda.irs.api.NodeInterestRates\$UploadFixesFlow"))) - - val currentDate: LocalDate = LocalDate.now() - val futureDate: LocalDate = currentDate.plusMonths(6) - val maxWaitTime: Duration = 60.seconds + private val rpcUsers = listOf(User("user", "password", setOf("ALL"))) + private val currentDate: LocalDate = LocalDate.now() + private val futureDate: LocalDate = currentDate.plusMonths(6) + private val maxWaitTime: Duration = 60.seconds @Test fun `runs IRS demo`() { - springDriver(useTestClock = true, isDebug = true, extraCordappPackagesToScan = listOf("net.corda.irs")) { - val (controller, nodeA, nodeB) = listOf( - startNotaryNode(DUMMY_NOTARY.name, validating = true, rpcUsers = rpcUsers), + springDriver( + useTestClock = true, + notarySpecs = listOf(NotarySpec(DUMMY_NOTARY.name, rpcUsers = rpcUsers)), + isDebug = true, + extraCordappPackagesToScan = listOf("net.corda.irs") + ) { + val (nodeA, nodeB) = listOf( startNode(providedName = DUMMY_BANK_A.name, rpcUsers = rpcUsers), - startNode(providedName = DUMMY_BANK_B.name, rpcUsers = rpcUsers)).map { it.getOrThrow() } + startNode(providedName = DUMMY_BANK_B.name, rpcUsers = rpcUsers) + ).map { it.getOrThrow() } + val controller = defaultNotaryNode.getOrThrow() log.info("All nodes started") @@ -65,7 +68,7 @@ class IRSDemoTest : IntegrationTestCategory { log.info("All webservers started") val (controllerApi, nodeAApi, nodeBApi) = listOf(controller, nodeA, nodeB).zip(listOf(controllerAddr, nodeAAddr, nodeBAddr)).map { - val mapper = net.corda.client.jackson.JacksonSupport.createDefaultMapper(it.first.rpc) + val mapper = JacksonSupport.createDefaultMapper(it.first.rpc) registerFinanceJSONMappers(mapper) registerIRSModule(mapper) HttpApi.fromHostAndPort(it.second, "api/irs", mapper = mapper) @@ -90,7 +93,9 @@ class IRSDemoTest : IntegrationTestCategory { } } - fun getFloatingLegFixCount(nodeApi: HttpApi) = getTrades(nodeApi)[0].calculation.floatingLegPaymentSchedule.count { it.value.rate.ratioUnit != null } + private fun getFloatingLegFixCount(nodeApi: HttpApi): Int { + return getTrades(nodeApi)[0].calculation.floatingLegPaymentSchedule.count { it.value.rate.ratioUnit != null } + } private fun getFixingDateObservable(config: NodeConfiguration): Observable { val client = CordaRPCClient(config.rpcAddress!!) diff --git a/samples/irs-demo/src/integration-test/kotlin/net/corda/test/spring/SpringDriver.kt b/samples/irs-demo/src/integration-test/kotlin/net/corda/test/spring/SpringDriver.kt index 871cc82954..bd1bc7070a 100644 --- a/samples/irs-demo/src/integration-test/kotlin/net/corda/test/spring/SpringDriver.kt +++ b/samples/irs-demo/src/integration-test/kotlin/net/corda/test/spring/SpringDriver.kt @@ -6,6 +6,7 @@ import net.corda.core.internal.concurrent.fork import net.corda.core.internal.concurrent.map import net.corda.core.utilities.loggerFor import net.corda.testing.driver.* +import net.corda.testing.node.NotarySpec import okhttp3.OkHttpClient import okhttp3.Request import java.net.ConnectException @@ -41,6 +42,7 @@ fun springDriver( useTestClock: Boolean = defaultParameters.useTestClock, initialiseSerialization: Boolean = defaultParameters.initialiseSerialization, startNodesInProcess: Boolean = defaultParameters.startNodesInProcess, + notarySpecs: List, extraCordappPackagesToScan: List = defaultParameters.extraCordappPackagesToScan, dsl: SpringDriverExposedDSLInterface.() -> A ) = genericDriver( @@ -54,9 +56,9 @@ fun springDriver( initialiseSerialization = initialiseSerialization, startNodesInProcess = startNodesInProcess, extraCordappPackagesToScan = extraCordappPackagesToScan, + notarySpecs = notarySpecs, driverDslWrapper = { driverDSL:DriverDSL -> SpringBootDriverDSL(driverDSL) }, - coerce = { it }, - dsl = dsl + coerce = { it }, dsl = dsl ) data class SpringBootDriverDSL( diff --git a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/NetworkMapVisualiser.kt b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/NetworkMapVisualiser.kt index 7034e4b4e8..3411eb5d0d 100644 --- a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/NetworkMapVisualiser.kt +++ b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/NetworkMapVisualiser.kt @@ -15,7 +15,6 @@ import net.corda.core.serialization.deserialize import net.corda.core.utilities.ProgressTracker import net.corda.netmap.VisualiserViewModel.Style import net.corda.netmap.simulation.IRSSimulation -import net.corda.node.services.network.NetworkMapService import net.corda.node.services.statemachine.SessionConfirm import net.corda.node.services.statemachine.SessionEnd import net.corda.node.services.statemachine.SessionInit @@ -343,8 +342,6 @@ class NetworkMapVisualiser : Application() { private fun transferIsInteresting(transfer: InMemoryMessagingNetwork.MessageTransfer): Boolean { // Loopback messages are boring. if (transfer.sender == transfer.recipients) return false - // Network map push acknowledgements are boring. - if (NetworkMapService.PUSH_ACK_TOPIC in transfer.message.topicSession.topic) return false val message = transfer.message.data.deserialize() return when (message) { is SessionEnd -> false diff --git a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt index 5b73073155..6a25314b1f 100644 --- a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt +++ b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt @@ -31,7 +31,6 @@ import java.util.* import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture.allOf - /** * A simulation in which banks execute interest rate swaps with each other, including the fixing events. */ @@ -133,15 +132,13 @@ class IRSSimulation(networkSendManuallyPumped: Boolean, runAsync: Boolean, laten val irs = om.readValue(javaClass.classLoader.getResourceAsStream("net/corda/irs/web/simulation/trade.json") .reader() .readText() - .replace("oracleXXX", RatesOracleFactory.RATES_SERVICE_NAME.toString())) + .replace("oracleXXX", RatesOracleNode.RATES_SERVICE_NAME.toString())) irs.fixedLeg.fixedRatePayer = node1.info.chooseIdentity() irs.floatingLeg.floatingRatePayer = node2.info.chooseIdentity() node1.internals.registerInitiatedFlow(FixingFlow.Fixer::class.java) node2.internals.registerInitiatedFlow(FixingFlow.Fixer::class.java) - val notaryId = node1.rpcOps.notaryIdentities().first() - @InitiatingFlow class StartDealFlow(val otherParty: Party, val payload: AutoOffer) : FlowLogic() { @@ -166,7 +163,7 @@ class IRSSimulation(networkSendManuallyPumped: Boolean, runAsync: Boolean, laten val instigator = StartDealFlow( node2.info.chooseIdentity(), - AutoOffer(notaryId, irs)) // TODO Pass notary as parameter to Simulation. + AutoOffer(mockNet.defaultNotaryIdentity, irs)) // TODO Pass notary as parameter to Simulation. val instigatorTxFuture = node1.services.startFlow(instigator).resultFuture return allOf(instigatorTxFuture.toCompletableFuture(), acceptorTxFuture).thenCompose { instigatorTxFuture.toCompletableFuture() } diff --git a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt index 84be5a59ff..21bfc7396c 100644 --- a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt +++ b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt @@ -9,7 +9,6 @@ import net.corda.finance.utils.CityDatabase import net.corda.irs.api.NodeInterestRates import net.corda.node.internal.StartedNode import net.corda.node.services.statemachine.StateMachineManager -import net.corda.testing.DUMMY_NOTARY import net.corda.testing.DUMMY_REGULATOR import net.corda.testing.node.* import net.corda.testing.node.MockNetwork.MockNode @@ -50,20 +49,18 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean, val bankLocations = listOf(Pair("London", "GB"), Pair("Frankfurt", "DE"), Pair("Rome", "IT")) - object RatesOracleFactory : MockNetwork.Factory { - // TODO: Make a more realistic legal name - val RATES_SERVICE_NAME = CordaX500Name(organisation = "Rates Service Provider", locality = "Madrid", country = "ES") + class RatesOracleNode(args: MockNodeArgs) : MockNode(args) { + companion object { + // TODO: Make a more realistic legal name + val RATES_SERVICE_NAME = CordaX500Name(organisation = "Rates Service Provider", locality = "Madrid", country = "ES") + } - override fun create(args: MockNodeArgs): MockNetwork.MockNode { - return object : MockNode(args) { - override fun start() = super.start().apply { - registerInitiatedFlow(NodeInterestRates.FixQueryHandler::class.java) - registerInitiatedFlow(NodeInterestRates.FixSignHandler::class.java) - javaClass.classLoader.getResourceAsStream("net/corda/irs/simulation/example.rates.txt").use { - database.transaction { - findTokenizableService(NodeInterestRates.Oracle::class.java)!!.uploadFixes(it.reader().readText()) - } - } + override fun start() = super.start().apply { + registerInitiatedFlow(NodeInterestRates.FixQueryHandler::class.java) + registerInitiatedFlow(NodeInterestRates.FixSignHandler::class.java) + javaClass.classLoader.getResourceAsStream("net/corda/irs/simulation/example.rates.txt").use { + database.transaction { + findTokenizableService(NodeInterestRates.Oracle::class.java)!!.uploadFixes(it.reader().readText()) } } } @@ -73,14 +70,13 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean, networkSendManuallyPumped = networkSendManuallyPumped, threadPerNode = runAsync, cordappPackages = listOf("net.corda.irs.contract", "net.corda.finance.contract", "net.corda.irs")) - val notary = mockNet.createNotaryNode(defaultParams.copy(legalName = DUMMY_NOTARY.name), false) // TODO: Regulatory nodes don't actually exist properly, this is a last minute demo request. // So we just fire a message at a node that doesn't know how to handle it, and it'll ignore it. // But that's fine for visualisation purposes. val regulators = listOf(mockNet.createUnstartedNode(defaultParams.copy(legalName = DUMMY_REGULATOR.name))) - val ratesOracle = mockNet.createUnstartedNode(defaultParams.copy(legalName = RatesOracleFactory.RATES_SERVICE_NAME), RatesOracleFactory) + val ratesOracle = mockNet.createUnstartedNode(defaultParams.copy(legalName = RatesOracleNode.RATES_SERVICE_NAME), ::RatesOracleNode) // All nodes must be in one of these two lists for the purposes of the visualiser tool. - val serviceProviders: List = listOf(notary.internals, ratesOracle) + val serviceProviders: List = listOf(mockNet.defaultNotaryNode.internals, ratesOracle) val banks: List = bankLocations.mapIndexed { i, (city, country) -> val legalName = CordaX500Name(organisation = "Bank ${'A' + i}", locality = city, country = country) // Use deterministic seeds so the simulation is stable. Needed so that party owning keys are stable. @@ -114,7 +110,8 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean, init { // Advance node clocks when current time is changed dateChanges.subscribe { - clocks.setTo(currentDateAndTime.toInstant(ZoneOffset.UTC)) + val instant = currentDateAndTime.toInstant(ZoneOffset.UTC) + clocks.forEach { it.setTo(instant) } } } diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt index b3e295d1cb..cb0be14ee0 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt @@ -5,6 +5,7 @@ import net.corda.cordform.CordformDefinition import net.corda.cordform.CordformNode import net.corda.core.identity.CordaX500Name import net.corda.core.internal.div +import net.corda.core.node.services.NotaryService import net.corda.core.utilities.NetworkHostAndPort import net.corda.node.services.config.BFTSMaRtConfiguration import net.corda.node.services.config.NotaryConfig @@ -13,11 +14,7 @@ import net.corda.node.services.transactions.minCorrectReplicas import net.corda.node.utilities.ServiceIdentityGenerator import net.corda.testing.ALICE import net.corda.testing.BOB -import net.corda.testing.internal.demorun.name -import net.corda.testing.internal.demorun.node -import net.corda.testing.internal.demorun.notary -import net.corda.testing.internal.demorun.rpcUsers -import net.corda.testing.internal.demorun.runNodes +import net.corda.testing.internal.demorun.* fun main(args: Array) = BFTNotaryCordform().runNodes() @@ -66,6 +63,10 @@ class BFTNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") { } override fun setup(context: CordformContext) { - ServiceIdentityGenerator.generateToDisk(notaryNames.map { context.baseDirectory(it.toString()) }, clusterName, minCorrectReplicas(clusterSize)) + ServiceIdentityGenerator.generateToDisk( + notaryNames.map { context.baseDirectory(it.toString()) }, + clusterName, + NotaryService.constructId(validating = false, bft = true), + minCorrectReplicas(clusterSize)) } } diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt index ff128c6edf..677ec08a9a 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt @@ -5,6 +5,7 @@ import net.corda.cordform.CordformDefinition import net.corda.cordform.CordformNode import net.corda.core.identity.CordaX500Name import net.corda.core.internal.div +import net.corda.core.node.services.NotaryService import net.corda.core.utilities.NetworkHostAndPort import net.corda.node.services.config.NotaryConfig import net.corda.node.services.config.RaftConfig @@ -58,6 +59,9 @@ class RaftNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") { } override fun setup(context: CordformContext) { - ServiceIdentityGenerator.generateToDisk(notaryNames.map { context.baseDirectory(it.toString()) }, clusterName) + ServiceIdentityGenerator.generateToDisk( + notaryNames.map { context.baseDirectory(it.toString()) }, + clusterName, + NotaryService.constructId(validating = true, raft = true)) } } diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/SingleNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/SingleNotaryCordform.kt index 8e349494b8..97168834c0 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/SingleNotaryCordform.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/SingleNotaryCordform.kt @@ -3,7 +3,7 @@ package net.corda.notarydemo import net.corda.cordform.CordformContext import net.corda.cordform.CordformDefinition import net.corda.core.internal.div -import net.corda.node.services.FlowPermissions.Companion.startFlowPermission +import net.corda.node.services.Permissions.Companion.all import net.corda.node.services.config.NotaryConfig import net.corda.nodeapi.User import net.corda.notarydemo.flows.DummyIssueAndMove @@ -19,7 +19,7 @@ import net.corda.testing.internal.demorun.runNodes fun main(args: Array) = SingleNotaryCordform().runNodes() -val notaryDemoUser = User("demou", "demop", setOf(startFlowPermission(), startFlowPermission())) +val notaryDemoUser = User("demou", "demop", setOf(all())) // This is not the intended final design for how to use CordformDefinition, please treat this as experimental and DO // NOT use this as a design to copy. diff --git a/samples/simm-valuation-demo/src/integration-test/kotlin/net/corda/vega/SimmValuationTest.kt b/samples/simm-valuation-demo/src/integration-test/kotlin/net/corda/vega/SimmValuationTest.kt index 87b9fa7839..d09d8f5a8c 100644 --- a/samples/simm-valuation-demo/src/integration-test/kotlin/net/corda/vega/SimmValuationTest.kt +++ b/samples/simm-valuation-demo/src/integration-test/kotlin/net/corda/vega/SimmValuationTest.kt @@ -3,7 +3,8 @@ package net.corda.vega import com.opengamma.strata.product.common.BuySell import net.corda.core.identity.CordaX500Name import net.corda.core.utilities.getOrThrow -import net.corda.testing.* +import net.corda.testing.DUMMY_BANK_A +import net.corda.testing.DUMMY_BANK_B import net.corda.testing.driver.driver import net.corda.testing.http.HttpApi import net.corda.vega.api.PortfolioApi @@ -15,7 +16,7 @@ import org.junit.Test import java.math.BigDecimal import java.time.LocalDate -class SimmValuationTest : IntegrationTestCategory { +class SimmValuationTest { private companion object { // SIMM demo can only currently handle one valuation date due to a lack of market data or a market data source. val valuationDate: LocalDate = LocalDate.parse("2016-06-06") @@ -27,7 +28,6 @@ class SimmValuationTest : IntegrationTestCategory { @Test fun `runs SIMM valuation demo`() { driver(isDebug = true, extraCordappPackagesToScan = listOf("net.corda.vega.contracts")) { - startNotaryNode(DUMMY_NOTARY.name, validating = false).getOrThrow() val nodeAFuture = startNode(providedName = nodeALegalName) val nodeBFuture = startNode(providedName = nodeBLegalName) val (nodeA, nodeB) = listOf(nodeAFuture, nodeBFuture).map { it.getOrThrow() } diff --git a/samples/simm-valuation-demo/src/test/kotlin/net/corda/vega/Main.kt b/samples/simm-valuation-demo/src/test/kotlin/net/corda/vega/Main.kt index 9cc72b5af0..c6bdcb2a2b 100644 --- a/samples/simm-valuation-demo/src/test/kotlin/net/corda/vega/Main.kt +++ b/samples/simm-valuation-demo/src/test/kotlin/net/corda/vega/Main.kt @@ -4,7 +4,6 @@ import net.corda.core.utilities.getOrThrow import net.corda.testing.DUMMY_BANK_A import net.corda.testing.DUMMY_BANK_B import net.corda.testing.DUMMY_BANK_C -import net.corda.testing.DUMMY_NOTARY import net.corda.testing.driver.driver /** @@ -13,17 +12,17 @@ import net.corda.testing.driver.driver * via the web api. */ fun main(args: Array) { - driver(dsl = { - val notaryFuture = startNotaryNode(DUMMY_NOTARY.name, validating = false) - val nodeAFuture = startNode(providedName = DUMMY_BANK_A.name) - val nodeBFuture = startNode(providedName = DUMMY_BANK_B.name) - val nodeCFuture = startNode(providedName = DUMMY_BANK_C.name) - val (nodeA, nodeB, nodeC) = listOf(nodeAFuture, nodeBFuture, nodeCFuture, notaryFuture).map { it.getOrThrow() } + driver(isDebug = true) { + val (nodeA, nodeB, nodeC) = listOf( + startNode(providedName = DUMMY_BANK_A.name), + startNode(providedName = DUMMY_BANK_B.name), + startNode(providedName = DUMMY_BANK_C.name) + ).map { it.getOrThrow() } startWebserver(nodeA) startWebserver(nodeB) startWebserver(nodeC) waitForAllNodesToFinish() - }, isDebug = true) + } } diff --git a/samples/trader-demo/README.md b/samples/trader-demo/README.md index 4d8e4693bf..be47476272 100644 --- a/samples/trader-demo/README.md +++ b/samples/trader-demo/README.md @@ -1,7 +1,7 @@ Trader demo ----------- -This demo brings up four nodes: Bank A, Bank B, Bank Of Corda, and a notary/network map node that they all use. Bank A +This demo brings up four nodes: Bank A, Bank B, Bank Of Corda, and a notary node that they all use. Bank A will be the buyer, and requests some cash from the Bank of Corda in order to acquire commercial paper from Bank B, the seller. diff --git a/samples/trader-demo/build.gradle b/samples/trader-demo/build.gradle index 392ecd60ac..1a00d51bec 100644 --- a/samples/trader-demo/build.gradle +++ b/samples/trader-demo/build.gradle @@ -42,12 +42,7 @@ dependencies { } task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { - ext.rpcUsers = [['username': "demo", 'password': "demo", 'permissions': [ - 'StartFlow.net.corda.finance.flows.CashIssueFlow', - 'StartFlow.net.corda.finance.flows.CashPaymentFlow', - 'StartFlow.net.corda.traderdemo.flow.CommercialPaperIssueFlow', - 'StartFlow.net.corda.traderdemo.flow.SellerFlow' - ]]] + ext.rpcUsers = [['username': "demo", 'password': "demo", 'permissions': ["ALL"]]] directory "./build/nodes" // This name "Notary" is hard-coded into TraderDemoClientApi so if you change it here, change it there too. diff --git a/samples/trader-demo/src/integration-test/kotlin/net/corda/traderdemo/TraderDemoTest.kt b/samples/trader-demo/src/integration-test/kotlin/net/corda/traderdemo/TraderDemoTest.kt index 8e7e639967..b741356548 100644 --- a/samples/trader-demo/src/integration-test/kotlin/net/corda/traderdemo/TraderDemoTest.kt +++ b/samples/trader-demo/src/integration-test/kotlin/net/corda/traderdemo/TraderDemoTest.kt @@ -6,9 +6,13 @@ import net.corda.core.utilities.millis import net.corda.finance.DOLLARS import net.corda.finance.flows.CashIssueFlow import net.corda.finance.flows.CashPaymentFlow -import net.corda.node.services.FlowPermissions +import net.corda.node.services.Permissions.Companion.all +import net.corda.node.services.Permissions.Companion.startFlow import net.corda.nodeapi.User -import net.corda.testing.* +import net.corda.testing.BOC +import net.corda.testing.DUMMY_BANK_A +import net.corda.testing.DUMMY_BANK_B +import net.corda.testing.chooseIdentity import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.driver import net.corda.testing.driver.poll @@ -22,18 +26,18 @@ import java.util.concurrent.Executors class TraderDemoTest { @Test fun `runs trader demo`() { - val demoUser = User("demo", "demo", setOf(FlowPermissions.startFlowPermission())) + val demoUser = User("demo", "demo", setOf(startFlow(), all())) val bankUser = User("user1", "test", permissions = setOf( - FlowPermissions.startFlowPermission(), - FlowPermissions.startFlowPermission(), - FlowPermissions.startFlowPermission())) + startFlow(), + startFlow(), + startFlow(), + all())) driver(startNodesInProcess = true, extraCordappPackagesToScan = listOf("net.corda.finance")) { val (nodeA, nodeB, bankNode) = listOf( startNode(providedName = DUMMY_BANK_A.name, rpcUsers = listOf(demoUser)), startNode(providedName = DUMMY_BANK_B.name, rpcUsers = listOf(demoUser)), - startNode(providedName = BOC.name, rpcUsers = listOf(bankUser)), - startNotaryNode(DUMMY_NOTARY.name, validating = false)) - .map { (it.getOrThrow() as NodeHandle.InProcess).node } + startNode(providedName = BOC.name, rpcUsers = listOf(bankUser)) + ).map { (it.getOrThrow() as NodeHandle.InProcess).node } nodeA.internals.registerInitiatedFlow(BuyerFlow::class.java) val (nodeARpc, nodeBRpc) = listOf(nodeA, nodeB).map { diff --git a/samples/trader-demo/src/test/kotlin/net/corda/traderdemo/Main.kt b/samples/trader-demo/src/test/kotlin/net/corda/traderdemo/Main.kt index 4a032576f5..3413a012f5 100644 --- a/samples/trader-demo/src/test/kotlin/net/corda/traderdemo/Main.kt +++ b/samples/trader-demo/src/test/kotlin/net/corda/traderdemo/Main.kt @@ -2,12 +2,12 @@ package net.corda.traderdemo import net.corda.core.internal.div import net.corda.finance.flows.CashIssueFlow -import net.corda.node.services.FlowPermissions.Companion.startFlowPermission +import net.corda.node.services.Permissions.Companion.all +import net.corda.node.services.Permissions.Companion.startFlow import net.corda.nodeapi.User import net.corda.testing.BOC import net.corda.testing.DUMMY_BANK_A import net.corda.testing.DUMMY_BANK_B -import net.corda.testing.DUMMY_NOTARY import net.corda.testing.driver.driver import net.corda.traderdemo.flow.CommercialPaperIssueFlow import net.corda.traderdemo.flow.SellerFlow @@ -18,14 +18,14 @@ import net.corda.traderdemo.flow.SellerFlow */ fun main(args: Array) { val permissions = setOf( - startFlowPermission(), - startFlowPermission()) + startFlow(), + startFlow(), + all()) val demoUser = listOf(User("demo", "demo", permissions)) driver(driverDirectory = "build" / "trader-demo-nodes", isDebug = true) { - val user = User("user1", "test", permissions = setOf(startFlowPermission(), - startFlowPermission(), - startFlowPermission())) - startNotaryNode(DUMMY_NOTARY.name, validating = false) + val user = User("user1", "test", permissions = setOf(startFlow(), + startFlow(), + startFlow())) startNode(providedName = DUMMY_BANK_A.name, rpcUsers = demoUser) startNode(providedName = DUMMY_BANK_B.name, rpcUsers = demoUser) startNode(providedName = BOC.name, rpcUsers = listOf(user)) diff --git a/settings.gradle b/settings.gradle index 2ef7265d8b..7ce3241252 100644 --- a/settings.gradle +++ b/settings.gradle @@ -19,7 +19,7 @@ include 'experimental' include 'experimental:sandbox' include 'experimental:quasar-hook' include 'experimental:kryo-hook' -include 'experimental:intellij-plugin' +//include 'experimental:intellij-plugin' include 'verifier' include 'test-common' include 'test-utils' diff --git a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/FlowStackSnapshotTest.kt b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/FlowStackSnapshotTest.kt index 8adba7f34d..e824c782f9 100644 --- a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/FlowStackSnapshotTest.kt +++ b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/FlowStackSnapshotTest.kt @@ -8,7 +8,7 @@ import net.corda.core.internal.list import net.corda.core.internal.read import net.corda.core.messaging.startFlow import net.corda.core.serialization.CordaSerializable -import net.corda.node.services.FlowPermissions.Companion.startFlowPermission +import net.corda.node.services.Permissions.Companion.startFlow import net.corda.nodeapi.User import net.corda.testing.driver.driver import net.corda.testing.node.MockNetwork @@ -229,7 +229,7 @@ class FlowStackSnapshotTest { @Test fun `flowStackSnapshot contains full frames when methods with side effects are called`() { driver(startNodesInProcess = true) { - val a = startNode(rpcUsers = listOf(User(Constants.USER, Constants.PASSWORD, setOf(startFlowPermission())))).get() + val a = startNode(rpcUsers = listOf(User(Constants.USER, Constants.PASSWORD, setOf(startFlow())))).get() a.rpcClientToNode().use(Constants.USER, Constants.PASSWORD) { connection -> val stackSnapshotFrames = connection.proxy.startFlow(::SideEffectFlow).returnValue.get() val iterator = stackSnapshotFrames.listIterator() @@ -244,7 +244,7 @@ class FlowStackSnapshotTest { @Test fun `flowStackSnapshot contains empty frames when methods with no side effects are called`() { driver(startNodesInProcess = true) { - val a = startNode(rpcUsers = listOf(User(Constants.USER, Constants.PASSWORD, setOf(startFlowPermission())))).get() + val a = startNode(rpcUsers = listOf(User(Constants.USER, Constants.PASSWORD, setOf(startFlow())))).get() a.rpcClientToNode().use(Constants.USER, Constants.PASSWORD) { connection -> val stackSnapshotFrames = connection.proxy.startFlow(::NoSideEffectFlow).returnValue.get() val iterator = stackSnapshotFrames.listIterator() @@ -259,7 +259,7 @@ class FlowStackSnapshotTest { @Test fun `persistFlowStackSnapshot persists empty frames to a file when methods with no side effects are called`() { driver(startNodesInProcess = true) { - val a = startNode(rpcUsers = listOf(User(Constants.USER, Constants.PASSWORD, setOf(startFlowPermission())))).get() + val a = startNode(rpcUsers = listOf(User(Constants.USER, Constants.PASSWORD, setOf(startFlow())))).get() a.rpcClientToNode().use(Constants.USER, Constants.PASSWORD) { connection -> val flowId = connection.proxy.startFlow(::PersistingNoSideEffectFlow).returnValue.get() @@ -276,7 +276,7 @@ class FlowStackSnapshotTest { @Test fun `persistFlowStackSnapshot persists multiple snapshots in different files`() { driver(startNodesInProcess = true) { - val a = startNode(rpcUsers = listOf(User(Constants.USER, Constants.PASSWORD, setOf(startFlowPermission())))).get() + val a = startNode(rpcUsers = listOf(User(Constants.USER, Constants.PASSWORD, setOf(startFlow())))).get() a.rpcClientToNode().use(Constants.USER, Constants.PASSWORD) { connection -> val numberOfFlowSnapshots = 5 @@ -290,7 +290,6 @@ class FlowStackSnapshotTest { @Test fun `flowStackSnapshot object is serializable`() { val mockNet = MockNetwork(threadPerNode = true) - mockNet.createNotaryNode() val node = mockNet.createPartyNode() node.internals.registerInitiatedFlow(DummyFlow::class.java) node.services.startFlow(FlowStackSnapshotSerializationTestingFlow()).resultFuture.get() @@ -306,7 +305,7 @@ class FlowStackSnapshotTest { @Test fun `persistFlowStackSnapshot stack traces are aligned with stack objects`() { driver(startNodesInProcess = true) { - val a = startNode(rpcUsers = listOf(User(Constants.USER, Constants.PASSWORD, setOf(startFlowPermission())))).get() + val a = startNode(rpcUsers = listOf(User(Constants.USER, Constants.PASSWORD, setOf(startFlow())))).get() a.rpcClientToNode().use(Constants.USER, Constants.PASSWORD) { connection -> val flowId = connection.proxy.startFlow(::PersistingSideEffectFlow).returnValue.get() diff --git a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt index 0e3c9eeec2..12507b3b5e 100644 --- a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt +++ b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/driver/DriverTests.kt @@ -7,7 +7,6 @@ import net.corda.core.internal.readLines import net.corda.core.utilities.getOrThrow import net.corda.node.internal.NodeStartup import net.corda.testing.DUMMY_BANK_A -import net.corda.testing.DUMMY_NOTARY import net.corda.testing.DUMMY_REGULATOR import net.corda.testing.ProjectStructure.projectRootDir import org.assertj.core.api.Assertions.assertThat @@ -18,7 +17,6 @@ import java.util.concurrent.ScheduledExecutorService class DriverTests { companion object { - private val executorService: ScheduledExecutorService = Executors.newScheduledThreadPool(2) private fun nodeMustBeUp(handleFuture: CordaFuture) = handleFuture.getOrThrow().apply { @@ -32,26 +30,15 @@ class DriverTests { // Check that the port is bound addressMustNotBeBound(executorService, hostAndPort) } - } @Test fun `simple node startup and shutdown`() { - val handles = driver { - val notary = startNotaryNode(DUMMY_NOTARY.name, validating = false) + val handle = driver { val regulator = startNode(providedName = DUMMY_REGULATOR.name) - listOf(nodeMustBeUp(notary), nodeMustBeUp(regulator)) + nodeMustBeUp(regulator) } - handles.map { nodeMustBeDown(it) } - } - - @Test - fun `starting node with no services`() { - val noService = driver { - val noService = startNode(providedName = DUMMY_BANK_A.name) - nodeMustBeUp(noService) - } - nodeMustBeDown(noService) + nodeMustBeDown(handle) } @Test diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/DriverConstants.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/DriverConstants.kt index 73e36aba5e..6c050f24b8 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/DriverConstants.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/DriverConstants.kt @@ -15,17 +15,11 @@ import net.corda.testing.driver.DriverDSLExposedInterface /** * A simple wrapper for objects provided by the integration test driver DSL. The fields are lazy so * node construction won't start until you access the members. You can get one of these from the - * [alice], [bob] and [aliceBobAndNotary] functions. + * [alice], [bob] and [aliceAndBob] functions. */ -class PredefinedTestNode internal constructor(party: Party, driver: DriverDSLExposedInterface, ifNotaryIsValidating: Boolean?) { +class PredefinedTestNode internal constructor(party: Party, driver: DriverDSLExposedInterface) { val rpcUsers = listOf(User("admin", "admin", setOf("ALL"))) // TODO: Randomize? - val nodeFuture by lazy { - if (ifNotaryIsValidating != null) { - driver.startNotaryNode(providedName = party.name, rpcUsers = rpcUsers, validating = ifNotaryIsValidating) - } else { - driver.startNode(providedName = party.name, rpcUsers = rpcUsers) - } - } + val nodeFuture by lazy { driver.startNode(providedName = party.name, rpcUsers = rpcUsers) } val node by lazy { nodeFuture.get()!! } val rpc by lazy { node.rpcClientToNode() } @@ -38,28 +32,21 @@ class PredefinedTestNode internal constructor(party: Party, driver: DriverDSLExp * Returns a plain, entirely stock node pre-configured with the [ALICE] identity. Note that a random key will be generated * for it: you won't have [ALICE_KEY]. */ -fun DriverDSLExposedInterface.alice(): PredefinedTestNode = PredefinedTestNode(ALICE, this, null) +fun DriverDSLExposedInterface.alice(): PredefinedTestNode = PredefinedTestNode(ALICE, this) /** * Returns a plain, entirely stock node pre-configured with the [BOB] identity. Note that a random key will be generated * for it: you won't have [BOB_KEY]. */ -fun DriverDSLExposedInterface.bob(): PredefinedTestNode = PredefinedTestNode(BOB, this, null) +fun DriverDSLExposedInterface.bob(): PredefinedTestNode = PredefinedTestNode(BOB, this) /** - * Returns a plain single node notary pre-configured with the [DUMMY_NOTARY] identity. Note that a random key will be generated - * for it: you won't have [DUMMY_NOTARY_KEY]. + * Returns plain, entirely stock nodes pre-configured with the [ALICE] and [BOB] X.500 names in that order. They have been + * started up in parallel and are now ready to use. */ -fun DriverDSLExposedInterface.notary(): PredefinedTestNode = PredefinedTestNode(DUMMY_NOTARY, this, true) - -/** - * Returns plain, entirely stock nodes pre-configured with the [ALICE], [BOB] and [DUMMY_NOTARY] X.500 names in that - * order. They have been started up in parallel and are now ready to use. - */ -fun DriverDSLExposedInterface.aliceBobAndNotary(): List { +fun DriverDSLExposedInterface.aliceAndBob(): List { val alice = alice() val bob = bob() - val notary = notary() - listOf(alice.nodeFuture, bob.nodeFuture, notary.nodeFuture).transpose().get() - return listOf(alice, bob, notary) + listOf(alice.nodeFuture, bob.nodeFuture).transpose().get() + return listOf(alice, bob) } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt index eac60d7e03..24ee8e0341 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt @@ -61,7 +61,6 @@ fun testNodeConfiguration( return rigorousMock().also { doReturn(baseDirectory).whenever(it).baseDirectory doReturn(myLegalName).whenever(it).myLegalName - doReturn(1).whenever(it).minimumPlatformVersion doReturn("cordacadevpass").whenever(it).keyStorePassword doReturn("trustpass").whenever(it).trustStorePassword doReturn(emptyList()).whenever(it).rpcUsers diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/RPCDriver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/RPCDriver.kt index aeddb369df..91d28d9f4e 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/RPCDriver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/RPCDriver.kt @@ -23,6 +23,7 @@ import net.corda.nodeapi.RPCApi import net.corda.nodeapi.User import net.corda.nodeapi.internal.serialization.KRYO_RPC_CLIENT_CONTEXT import net.corda.testing.driver.* +import net.corda.testing.node.NotarySpec import org.apache.activemq.artemis.api.core.SimpleString import org.apache.activemq.artemis.api.core.TransportConfiguration import org.apache.activemq.artemis.api.core.client.ActiveMQClient @@ -230,6 +231,7 @@ fun rpcDriver( initialiseSerialization: Boolean = true, startNodesInProcess: Boolean = false, extraCordappPackagesToScan: List = emptyList(), + notarySpecs: List = emptyList(), dsl: RPCDriverExposedDSLInterface.() -> A ) = genericDriver( driverDsl = RPCDriverDSL( @@ -241,7 +243,8 @@ fun rpcDriver( useTestClock = useTestClock, isDebug = isDebug, startNodesInProcess = startNodesInProcess, - extraCordappPackagesToScan = extraCordappPackagesToScan + extraCordappPackagesToScan = extraCordappPackagesToScan, + notarySpecs = notarySpecs ) ), coerce = { it }, diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt index 7e68cfa998..ecf0f1c846 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -13,37 +13,42 @@ import net.corda.core.concurrent.CordaFuture import net.corda.core.concurrent.firstOf import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party -import net.corda.core.internal.ThreadBox +import net.corda.core.internal.* import net.corda.core.internal.concurrent.* -import net.corda.core.internal.div -import net.corda.core.internal.times import net.corda.core.messaging.CordaRPCOps +import net.corda.core.node.NetworkParameters import net.corda.core.node.NodeInfo +import net.corda.core.node.NotaryInfo import net.corda.core.node.services.NetworkMapCache +import net.corda.core.node.services.NotaryService import net.corda.core.toFuture import net.corda.core.utilities.* import net.corda.node.internal.Node import net.corda.node.internal.NodeStartup import net.corda.node.internal.StartedNode import net.corda.node.internal.cordapp.CordappLoader +import net.corda.node.services.Permissions.Companion.invokeRpc import net.corda.node.services.config.* -import net.corda.node.services.network.NetworkMapService import net.corda.node.utilities.ServiceIdentityGenerator import net.corda.nodeapi.NodeInfoFilesCopier import net.corda.nodeapi.User import net.corda.nodeapi.config.toConfig import net.corda.nodeapi.internal.addShutdownHook import net.corda.testing.* +import net.corda.testing.common.internal.NetworkParametersCopier +import net.corda.testing.common.internal.testNetworkParameters +import net.corda.testing.node.ClusterSpec import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO +import net.corda.testing.node.NotarySpec import okhttp3.OkHttpClient import okhttp3.Request import org.slf4j.Logger import rx.Observable import rx.observables.ConnectableObservable -import java.io.File import java.net.* import java.nio.file.Path import java.nio.file.Paths +import java.nio.file.StandardCopyOption.REPLACE_EXISTING import java.time.Duration import java.time.Instant import java.time.ZoneOffset.UTC @@ -56,30 +61,73 @@ import java.util.concurrent.TimeUnit.MILLISECONDS import java.util.concurrent.TimeUnit.SECONDS import java.util.concurrent.TimeoutException import java.util.concurrent.atomic.AtomicInteger +import kotlin.collections.ArrayList import kotlin.concurrent.thread /** * This file defines a small "Driver" DSL for starting up nodes that is only intended for development, demos and tests. * - * The process the driver is run in behaves as an Artemis client and starts up other processes. Namely it first - * bootstraps a network map service to allow the specified nodes to connect to, then starts up the actual nodes. + * The process the driver is run in behaves as an Artemis client and starts up other processes. * * TODO this file is getting way too big, it should be split into several files. */ - private val log: Logger = loggerFor() private val DEFAULT_POLL_INTERVAL = 500.millis private const val DEFAULT_WARN_COUNT = 120 +private val DRIVER_REQUIRED_PERMISSIONS = setOf( + invokeRpc(CordaRPCOps::nodeInfo), + invokeRpc(CordaRPCOps::networkMapFeed), + invokeRpc(CordaRPCOps::networkMapSnapshot) +) + +/** + * Object ecapsulating a notary started automatically by the driver. + */ +data class NotaryHandle(val identity: Party, val validating: Boolean, val nodeHandles: CordaFuture>) + /** * This is the interface that's exposed to DSL users. */ interface DriverDSLExposedInterface : CordformContext { + /** Returns a list of [NotaryHandle]s matching the list of [NotarySpec]s passed into [driver]. */ + val notaryHandles: List + /** - * Starts a [net.corda.node.internal.Node] in a separate process. + * Returns the [NotaryHandle] for the single notary on the network. Throws if there are none or more than one. + * @see notaryHandles + */ + val defaultNotaryHandle: NotaryHandle get() { + return when (notaryHandles.size) { + 0 -> throw IllegalStateException("There are no notaries defined on the network") + 1 -> notaryHandles[0] + else -> throw IllegalStateException("There is more than one notary defined on the network") + } + } + + /** + * Returns the identity of the single notary on the network. Throws if there are none or more than one. + * @see defaultNotaryHandle + */ + val defaultNotaryIdentity: Party get() = defaultNotaryHandle.identity + + /** + * Returns a [CordaFuture] on the [NodeHandle] for the single-node notary on the network. Throws if there + * are no notaries or more than one, or if the notary is a distributed cluster. + * @see defaultNotaryHandle + * @see notaryHandles + */ + val defaultNotaryNode: CordaFuture get() { + return defaultNotaryHandle.nodeHandles.map { + it.singleOrNull() ?: throw IllegalStateException("Default notary is not a single node") + } + } + + /** + * Start a node. * * @param defaultParameters The default parameters for the node. Allows the node to be configured in builder style * when called from Java code. @@ -89,7 +137,7 @@ interface DriverDSLExposedInterface : CordformContext { * @param rpcUsers List of users who are authorised to use the RPC system. Defaults to empty list. * @param startInSameProcess Determines if the node should be started inside the same process the Driver is running * in. If null the Driver-level value will be used. - * @return The [NodeInfo] of the started up node retrieved from the network map service. + * @return A [CordaFuture] on the [NodeHandle] to the node. The future will complete when the node is available. */ fun startNode( defaultParameters: NodeParameters = NodeParameters(), @@ -101,23 +149,13 @@ interface DriverDSLExposedInterface : CordformContext { maximumHeapSize: String = defaultParameters.maximumHeapSize, logLevel: String? = defaultParameters.logLevel): CordaFuture - // TODO This method has been added temporarily, to be deleted once the set of notaries is defined at the network level. - fun startNotaryNode(providedName: CordaX500Name, - rpcUsers: List = emptyList(), - verifierType: VerifierType = VerifierType.InMemory, - customOverrides: Map = emptyMap(), - //TODO Switch the default value - validating: Boolean = true): CordaFuture - /** * Helper function for starting a [Node] with custom parameters from Java. * * @param parameters The default parameters for the driver. * @return The value returned in the [dsl] closure. */ - fun startNode(parameters: NodeParameters): CordaFuture { - return startNode(defaultParameters = parameters) - } + fun startNode(parameters: NodeParameters): CordaFuture = startNode(defaultParameters = parameters) fun startNodes( nodes: List, @@ -125,24 +163,6 @@ interface DriverDSLExposedInterface : CordformContext { maximumHeapSize: String = "200m" ): List> - /** - * Starts a distributed notary cluster. - * - * @param notaryName The legal name of the advertised distributed notary service. - * @param clusterSize Number of nodes to create for the cluster. - * @param verifierType The type of transaction verifier to use. See: [VerifierType] - * @param rpcUsers List of users who are authorised to use the RPC system. Defaults to empty list. - * @param startInSameProcess Determines if the node should be started inside the same process the Driver is running - * in. If null the Driver-level value will be used. - * @return The [Party] identity of the distributed notary service, and the [NodeInfo]s of the notaries in the cluster. - */ - fun startNotaryCluster( - notaryName: CordaX500Name, - clusterSize: Int = 3, - verifierType: VerifierType = VerifierType.InMemory, - rpcUsers: List = emptyList(), - startInSameProcess: Boolean? = null): CordaFuture>> - /** Call [startWebserver] with a default maximumHeapSize. */ fun startWebserver(handle: NodeHandle): CordaFuture = startWebserver(handle, "200m") @@ -174,15 +194,6 @@ interface DriverDSLExposedInterface : CordformContext { return pollUntilNonNull(pollName, pollInterval, warnCount) { if (check()) Unit else null } } - /** - * Polls until a given node knows about presence of another node via its own NetworkMap - */ - fun NodeHandle.pollUntilKnowsAbout(another: NodeHandle, pollInterval: Duration = DEFAULT_POLL_INTERVAL, warnCount: Int = DEFAULT_WARN_COUNT): CordaFuture { - return pollUntilTrue("${nodeInfo.legalIdentities} knows about ${another.nodeInfo.legalIdentities}", pollInterval, warnCount) { - another.nodeInfo in rpc.networkMapSnapshot() - } - } - val shutdownManager: ShutdownManager } @@ -201,6 +212,11 @@ sealed class NodeHandle { abstract val configuration: NodeConfiguration abstract val webAddress: NetworkHostAndPort + /** + * Stops the referenced node. + */ + abstract fun stop() + data class OutOfProcess( override val nodeInfo: NodeInfo, override val rpc: CordaRPCOps, @@ -210,13 +226,12 @@ sealed class NodeHandle { val process: Process, private val onStopCallback: () -> Unit ) : NodeHandle() { - override fun stop(): CordaFuture { + override fun stop() { with(process) { destroy() waitFor() } onStopCallback() - return doneFuture(Unit) } } @@ -229,23 +244,17 @@ sealed class NodeHandle { val nodeThread: Thread, private val onStopCallback: () -> Unit ) : NodeHandle() { - override fun stop(): CordaFuture { + override fun stop() { node.dispose() with(nodeThread) { interrupt() join() } onStopCallback() - return doneFuture(Unit) } } fun rpcClientToNode(): CordaRPCClient = CordaRPCClient(configuration.rpcAddress!!) - - /** - * Stops the referenced node. - */ - abstract fun stop(): CordaFuture } data class WebserverHandle( @@ -305,8 +314,6 @@ data class NodeParameters( * of the [NodeInfo] that may be waited on, which completes when the new node registered with the network map service or * loaded node data from database. * - * The driver implicitly bootstraps a [NetworkMapService]. - * * @param defaultParameters The default parameters for the driver. Allows the driver to be configured in builder style * when called from Java code. * @param isDebug Indicates whether the spawned nodes should start in jdwt debug mode and have debug level logging. @@ -319,6 +326,8 @@ data class NodeParameters( * @param useTestClock If true the test clock will be used in Node. * @param startNodesInProcess Provides the default behaviour of whether new nodes should start inside this process or * not. Note that this may be overridden in [DriverDSLExposedInterface.startNode]. + * @param notarySpecs The notaries advertised in the [NetworkParameters] for this network. These nodes will be started + * automatically and will be available from [DriverDSLExposedInterface.notaryHandles]. Defaults to a simple validating notary. * @param dsl The dsl itself. * @return The value returned in the [dsl] closure. */ @@ -331,8 +340,8 @@ fun driver( extraSystemProperties: Map = defaultParameters.extraSystemProperties, useTestClock: Boolean = defaultParameters.useTestClock, initialiseSerialization: Boolean = defaultParameters.initialiseSerialization, - startNodesInProcess: Boolean = defaultParameters.startNodesInProcess, + notarySpecs: List = defaultParameters.notarySpecs, extraCordappPackagesToScan: List = defaultParameters.extraCordappPackagesToScan, dsl: DriverDSLExposedInterface.() -> A ): A { @@ -345,6 +354,7 @@ fun driver( useTestClock = useTestClock, isDebug = isDebug, startNodesInProcess = startNodesInProcess, + notarySpecs = notarySpecs, extraCordappPackagesToScan = extraCordappPackagesToScan ), coerce = { it }, @@ -378,6 +388,7 @@ data class DriverParameters( val useTestClock: Boolean = false, val initialiseSerialization: Boolean = true, val startNodesInProcess: Boolean = false, + val notarySpecs: List = listOf(NotarySpec(DUMMY_NOTARY.name)), val extraCordappPackagesToScan: List = emptyList() ) { fun setIsDebug(isDebug: Boolean) = copy(isDebug = isDebug) @@ -389,6 +400,7 @@ data class DriverParameters( fun setInitialiseSerialization(initialiseSerialization: Boolean) = copy(initialiseSerialization = initialiseSerialization) fun setStartNodesInProcess(startNodesInProcess: Boolean) = copy(startNodesInProcess = startNodesInProcess) fun setExtraCordappPackagesToScan(extraCordappPackagesToScan: List) = copy(extraCordappPackagesToScan = extraCordappPackagesToScan) + fun setNotarySpecs(notarySpecs: List) = copy(notarySpecs = notarySpecs) } /** @@ -438,10 +450,10 @@ fun genericD useTestClock: Boolean = defaultParameters.useTestClock, initialiseSerialization: Boolean = defaultParameters.initialiseSerialization, startNodesInProcess: Boolean = defaultParameters.startNodesInProcess, + notarySpecs: List, extraCordappPackagesToScan: List = defaultParameters.extraCordappPackagesToScan, driverDslWrapper: (DriverDSL) -> D, - coerce: (D) -> DI, - dsl: DI.() -> A + coerce: (D) -> DI, dsl: DI.() -> A ): A { val serializationEnv = initialiseTestSerialization(initialiseSerialization) val driverDsl = driverDslWrapper( @@ -453,7 +465,8 @@ fun genericD useTestClock = useTestClock, isDebug = isDebug, startNodesInProcess = startNodesInProcess, - extraCordappPackagesToScan = extraCordappPackagesToScan + extraCordappPackagesToScan = extraCordappPackagesToScan, + notarySpecs = notarySpecs ) ) val shutdownHook = addShutdownHook(driverDsl::shutdown) @@ -474,7 +487,8 @@ fun getTimestampAsDirectoryName(): String { return DateTimeFormatter.ofPattern("yyyyMMddHHmmss").withZone(UTC).format(Instant.now()) } -class ListenProcessDeathException(hostAndPort: NetworkHostAndPort, listenProcess: Process) : CordaException("The process that was expected to listen on $hostAndPort has died with status: ${listenProcess.exitValue()}") +class ListenProcessDeathException(hostAndPort: NetworkHostAndPort, listenProcess: Process) : + CordaException("The process that was expected to listen on $hostAndPort has died with status: ${listenProcess.exitValue()}") /** * @throws ListenProcessDeathException if [listenProcess] dies before the check succeeds, i.e. the check can't succeed as intended. @@ -499,7 +513,7 @@ fun addressMustBeBoundFuture(executorService: ScheduledExecutorService, hostAndP /* * The default timeout value of 40 seconds have been chosen based on previous node shutdown time estimate. - * It's been observed that nodes can take up to 30 seconds to shut down, so just to stay on the safe side the 40 seconds + * It's been observed that nodes can take up to 30 seconds to shut down, so just to stay on the safe side the 60 seconds * timeout has been chosen. */ fun addressMustNotBeBound(executorService: ScheduledExecutorService, hostAndPort: NetworkHostAndPort, timeout: Duration = 40.seconds) { @@ -652,7 +666,8 @@ class DriverDSL( val useTestClock: Boolean, val isDebug: Boolean, val startNodesInProcess: Boolean, - extraCordappPackagesToScan: List + extraCordappPackagesToScan: List, + val notarySpecs: List ) : DriverDSLInternalInterface { private var _executorService: ScheduledExecutorService? = null val executorService get() = _executorService!! @@ -667,6 +682,9 @@ class DriverDSL( private val nodeInfoFilesCopier = NodeInfoFilesCopier() // Map from a nodes legal name to an observable emitting the number of nodes in its network map. private val countObservables = mutableMapOf>() + private lateinit var _notaries: List + override val notaryHandles: List get() = _notaries + private lateinit var networkParameters: NetworkParametersCopier class State { val processes = ArrayList>() @@ -738,6 +756,7 @@ class DriverDSL( val webAddress = portAllocation.nextHostAndPort() // TODO: Derive name from the full picked name, don't just wrap the common name val name = providedName ?: CordaX500Name(organisation = "${oneOf(names).organisation}-${p2pAddress.port}", locality = "London", country = "GB") + val users = rpcUsers.map { it.copy(permissions = it.permissions + DRIVER_REQUIRED_PERMISSIONS) } val config = ConfigHelper.loadConfig( baseDirectory = baseDirectory(name), allowMissingConfig = true, @@ -747,23 +766,13 @@ class DriverDSL( "rpcAddress" to rpcAddress.toString(), "webAddress" to webAddress.toString(), "useTestClock" to useTestClock, - "rpcUsers" to if (rpcUsers.isEmpty()) defaultRpcUserList else rpcUsers.map { it.toConfig().root().unwrapped() }, - "verifierType" to verifierType.name, - "noNetworkMapServiceMode" to true + "rpcUsers" to if (users.isEmpty()) defaultRpcUserList else users.map { it.toConfig().root().unwrapped() }, + "verifierType" to verifierType.name ) + customOverrides ) return startNodeInternal(config, webAddress, startInSameProcess, maximumHeapSize) } - override fun startNotaryNode(providedName: CordaX500Name, - rpcUsers: List, - verifierType: VerifierType, - customOverrides: Map, - validating: Boolean): CordaFuture { - val config = customOverrides + NotaryConfig(validating).toConfigMap() - return startNode(providedName = providedName, rpcUsers = rpcUsers, verifierType = verifierType, customOverrides = config) - } - override fun startNodes(nodes: List, startInSameProcess: Boolean?, maximumHeapSize: String): List> { return nodes.map { node -> portAllocation.nextHostAndPort() // rpcAddress @@ -775,66 +784,13 @@ class DriverDSL( baseDirectory = baseDirectory(name), allowMissingConfig = true, configOverrides = node.config + notary + mapOf( - "rpcUsers" to if (rpcUsers.isEmpty()) defaultRpcUserList else rpcUsers, - "noNetworkMapServiceMode" to true + "rpcUsers" to if (rpcUsers.isEmpty()) defaultRpcUserList else rpcUsers ) ) startNodeInternal(config, webAddress, startInSameProcess, maximumHeapSize) } } - // TODO This mapping is done is several plaecs including the gradle plugin. In general we need a better way of - // generating the configs for the nodes, probably making use of Any.toConfig() - private fun NotaryConfig.toConfigMap(): Map = mapOf("notary" to toConfig().root().unwrapped()) - - override fun startNotaryCluster( - notaryName: CordaX500Name, - clusterSize: Int, - verifierType: VerifierType, - rpcUsers: List, - startInSameProcess: Boolean? - ): CordaFuture>> { - fun notaryConfig(nodeAddress: NetworkHostAndPort, clusterAddress: NetworkHostAndPort? = null): Map { - val clusterAddresses = if (clusterAddress != null) listOf(clusterAddress) else emptyList() - val config = NotaryConfig(validating = true, raft = RaftConfig(nodeAddress = nodeAddress, clusterAddresses = clusterAddresses)) - return config.toConfigMap() - } - - val nodeNames = (0 until clusterSize).map { CordaX500Name("Notary Service $it", "Zurich", "CH") } - val paths = nodeNames.map { baseDirectory(it) } - ServiceIdentityGenerator.generateToDisk(paths, notaryName) - val clusterAddress = portAllocation.nextHostAndPort() - - // Start the first node that will bootstrap the cluster - val firstNotaryFuture = startNode( - providedName = nodeNames.first(), - rpcUsers = rpcUsers, - verifierType = verifierType, - customOverrides = notaryConfig(clusterAddress) + mapOf( - "database.serverNameTablePrefix" to if (nodeNames.isNotEmpty()) nodeNames.first().toString().replace(Regex("[^0-9A-Za-z]+"), "") else "" - ), - startInSameProcess = startInSameProcess - ) - // All other nodes will join the cluster - val restNotaryFutures = nodeNames.drop(1).map { - val nodeAddress = portAllocation.nextHostAndPort() - startNode( - providedName = it, - rpcUsers = rpcUsers, - verifierType = verifierType, - customOverrides = notaryConfig(nodeAddress, clusterAddress) + mapOf( - "database.serverNameTablePrefix" to it.toString().replace(Regex("[^0-9A-Za-z]+"), "") - )) - } - - return firstNotaryFuture.flatMap { firstNotary -> - val notaryParty = firstNotary.nodeInfo.legalIdentities[1] // TODO For now the second identity is notary identity. - restNotaryFutures.transpose().map { restNotaries -> - Pair(notaryParty, listOf(firstNotary) + restNotaries) - } - } - } - private fun queryWebserver(handle: NodeHandle, process: Process): WebserverHandle { val protocol = if (handle.configuration.useHTTPS) "https://" else "http://" val url = URL("$protocol${handle.webAddress}/api/status") @@ -863,12 +819,102 @@ class DriverDSL( _executorService = Executors.newScheduledThreadPool(2, ThreadFactoryBuilder().setNameFormat("driver-pool-thread-%d").build()) _shutdownManager = ShutdownManager(executorService) shutdownManager.registerShutdown { nodeInfoFilesCopier.close() } + val notaryInfos = generateNotaryIdentities() + // The network parameters must be serialised before starting any of the nodes + networkParameters = NetworkParametersCopier(testNetworkParameters(notaryInfos)) + val nodeHandles = startNotaries() + _notaries = notaryInfos.zip(nodeHandles) { (identity, validating), nodes -> NotaryHandle(identity, validating, nodes) } + } + + private fun generateNotaryIdentities(): List { + return notarySpecs.map { spec -> + val identity = if (spec.cluster == null) { + ServiceIdentityGenerator.generateToDisk( + dirs = listOf(baseDirectory(spec.name)), + serviceName = spec.name, + serviceId = "identity") + } else { + ServiceIdentityGenerator.generateToDisk( + dirs = generateNodeNames(spec).map { baseDirectory(it) }, + serviceName = spec.name, + serviceId = NotaryService.constructId( + validating = spec.validating, + raft = spec.cluster is ClusterSpec.Raft)) + } + NotaryInfo(identity, spec.validating) + } + } + + private fun generateNodeNames(spec: NotarySpec): List { + return (0 until spec.cluster!!.clusterSize).map { spec.name.copy(organisation = "${spec.name.organisation}-$it") } + } + + private fun startNotaries(): List>> { + return notarySpecs.map { + when { + it.cluster == null -> startSingleNotary(it) + it.cluster is ClusterSpec.Raft -> startRaftNotaryCluster(it) + else -> throw IllegalArgumentException("BFT-SMaRt not supported") + } + } + } + + // TODO This mapping is done is several places including the gradle plugin. In general we need a better way of + // generating the configs for the nodes, probably making use of Any.toConfig() + private fun NotaryConfig.toConfigMap(): Map = mapOf("notary" to toConfig().root().unwrapped()) + + private fun startSingleNotary(spec: NotarySpec): CordaFuture> { + return startNode( + providedName = spec.name, + rpcUsers = spec.rpcUsers, + verifierType = spec.verifierType, + customOverrides = NotaryConfig(spec.validating).toConfigMap() + ).map { listOf(it) } + } + + private fun startRaftNotaryCluster(spec: NotarySpec): CordaFuture> { + fun notaryConfig(nodeAddress: NetworkHostAndPort, clusterAddress: NetworkHostAndPort? = null): Map { + val clusterAddresses = if (clusterAddress != null) listOf(clusterAddress) else emptyList() + val config = NotaryConfig( + validating = spec.validating, + raft = RaftConfig(nodeAddress = nodeAddress, clusterAddresses = clusterAddresses)) + return config.toConfigMap() + } + + val nodeNames = generateNodeNames(spec) + val clusterAddress = portAllocation.nextHostAndPort() + + // Start the first node that will bootstrap the cluster + val firstNodeFuture = startNode( + providedName = nodeNames[0], + rpcUsers = spec.rpcUsers, + verifierType = spec.verifierType, + customOverrides = notaryConfig(clusterAddress) + mapOf( + "database.serverNameTablePrefix" to nodeNames[0].toString().replace(Regex("[^0-9A-Za-z]+"), "") + ) + ) + + // All other nodes will join the cluster + val restNodeFutures = nodeNames.drop(1).map { + val nodeAddress = portAllocation.nextHostAndPort() + startNode( + providedName = it, + rpcUsers = spec.rpcUsers, + verifierType = spec.verifierType, + customOverrides = notaryConfig(nodeAddress, clusterAddress) + mapOf( + "database.serverNameTablePrefix" to it.toString().replace(Regex("[^0-9A-Za-z]+"), "") + ) + ) + } + + return firstNodeFuture.flatMap { first -> + restNodeFutures.transpose().map { rest -> listOf(first) + rest } + } } fun baseDirectory(nodeName: CordaX500Name): Path { - val nodeDirectoryName = String(nodeName.organisation.filter { !it.isWhitespace() }.toCharArray()) + val nodeDirectoryName = nodeName.organisation.filter { !it.isWhitespace() } return driverDirectory / nodeDirectoryName - } override fun baseDirectory(nodeName: String): Path = baseDirectory(CordaX500Name.parse(nodeName)) @@ -899,7 +945,7 @@ class DriverDSL( private fun allNodesConnected(rpc: CordaRPCOps): CordaFuture { val (snapshot, updates) = rpc.networkMapFeed() val counterObservable = nodeCountObservable(snapshot.size, updates) - countObservables.put(rpc.nodeInfo().legalIdentities.first().name, counterObservable) + countObservables[rpc.nodeInfo().legalIdentities[0].name] = counterObservable /* TODO: this might not always be the exact number of nodes one has to wait for, * for example in the following sequence * 1 start 3 nodes in order, A, B, C. @@ -909,7 +955,7 @@ class DriverDSL( val requiredNodes = countObservables.size // This is an observable which yield the minimum number of nodes in each node network map. - val smallestSeenNetworkMapSize = Observable.combineLatest(countObservables.values.toList()) { args : Array -> + val smallestSeenNetworkMapSize = Observable.combineLatest(countObservables.values.toList()) { args: Array -> args.map { it as Int }.min() ?: 0 } val future = smallestSeenNetworkMapSize.filter { it >= requiredNodes }.toFuture() @@ -917,15 +963,20 @@ class DriverDSL( return future } - private fun startNodeInternal(config: Config, webAddress: NetworkHostAndPort, startInProcess: Boolean?, maximumHeapSize: String): CordaFuture { - val nodeConfiguration = config.parseAsNodeConfiguration() - nodeInfoFilesCopier.addConfig(nodeConfiguration.baseDirectory) + private fun startNodeInternal(config: Config, + webAddress: NetworkHostAndPort, + startInProcess: Boolean?, + maximumHeapSize: String): CordaFuture { + val configuration = config.parseAsNodeConfiguration() + val baseDirectory = configuration.baseDirectory.createDirectories() + networkParameters.install(baseDirectory) + nodeInfoFilesCopier.addConfig(baseDirectory) val onNodeExit: () -> Unit = { - nodeInfoFilesCopier.removeConfig(nodeConfiguration.baseDirectory) - countObservables.remove(nodeConfiguration.myLegalName) + nodeInfoFilesCopier.removeConfig(baseDirectory) + countObservables.remove(configuration.myLegalName) } if (startInProcess ?: startNodesInProcess) { - val nodeAndThreadFuture = startInProcessNode(executorService, nodeConfiguration, config, cordappPackages) + val nodeAndThreadFuture = startInProcessNode(executorService, configuration, config, cordappPackages) shutdownManager.registerShutdown( nodeAndThreadFuture.map { (node, thread) -> { @@ -935,33 +986,33 @@ class DriverDSL( } ) return nodeAndThreadFuture.flatMap { (node, thread) -> - establishRpc(nodeConfiguration, openFuture()).flatMap { rpc -> + establishRpc(configuration, openFuture()).flatMap { rpc -> allNodesConnected(rpc).map { - NodeHandle.InProcess(rpc.nodeInfo(), rpc, nodeConfiguration, webAddress, node, thread, onNodeExit) + NodeHandle.InProcess(rpc.nodeInfo(), rpc, configuration, webAddress, node, thread, onNodeExit) } } } } else { val debugPort = if (isDebug) debugPortAllocation.nextPort() else null - val processFuture = startOutOfProcessNode(executorService, nodeConfiguration, config, quasarJarPath, debugPort, systemProperties, cordappPackages, maximumHeapSize) + val processFuture = startOutOfProcessNode(executorService, configuration, config, quasarJarPath, debugPort, systemProperties, cordappPackages, maximumHeapSize) registerProcess(processFuture) return processFuture.flatMap { process -> val processDeathFuture = poll(executorService, "process death") { if (process.isAlive) null else process } - establishRpc(nodeConfiguration, processDeathFuture).flatMap { rpc -> - // Call waitUntilNetworkReady in background in case RPC is failing over: + establishRpc(configuration, processDeathFuture).flatMap { rpc -> + // Check for all nodes to have all other nodes in background in case RPC is failing over: val forked = executorService.fork { allNodesConnected(rpc) } val networkMapFuture = forked.flatMap { it } firstOf(processDeathFuture, networkMapFuture) { if (it == processDeathFuture) { - throw ListenProcessDeathException(nodeConfiguration.p2pAddress, process) + throw ListenProcessDeathException(configuration.p2pAddress, process) } processDeathFuture.cancel(false) - log.info("Node handle is ready. NodeInfo: ${rpc.nodeInfo()}, WebAddress: ${webAddress}") - NodeHandle.OutOfProcess(rpc.nodeInfo(), rpc, nodeConfiguration, webAddress, debugPort, process, + log.info("Node handle is ready. NodeInfo: ${rpc.nodeInfo()}, WebAddress: $webAddress") + NodeHandle.OutOfProcess(rpc.nodeInfo(), rpc, configuration, webAddress, debugPort, process, onNodeExit) } } @@ -997,12 +1048,19 @@ class DriverDSL( // Write node.conf writeConfig(nodeConf.baseDirectory, "node.conf", config) // TODO pass the version in? - val node = Node(nodeConf, MOCK_VERSION_INFO, initialiseSerialization = false, cordappLoader = CordappLoader.createDefaultWithTestPackages(nodeConf, cordappPackages)).start() + val node = Node( + nodeConf, + MOCK_VERSION_INFO, + initialiseSerialization = false, + cordappLoader = CordappLoader.createDefaultWithTestPackages(nodeConf, cordappPackages)) + .start() val nodeThread = thread(name = nodeConf.myLegalName.organisation) { node.internals.run() } node to nodeThread - }.flatMap { nodeAndThread -> addressMustBeBoundFuture(executorService, nodeConf.p2pAddress).map { nodeAndThread } } + }.flatMap { + nodeAndThread -> addressMustBeBoundFuture(executorService, nodeConf.p2pAddress).map { nodeAndThread } + } } private fun startOutOfProcessNode( @@ -1017,7 +1075,7 @@ class DriverDSL( logLevel: String? = null ): CordaFuture { val processFuture = executorService.fork { - log.info("Starting out-of-process Node ${nodeConf.myLegalName.organisation}, debug port is " + debugPort ?: "not enabled") + log.info("Starting out-of-process Node ${nodeConf.myLegalName.organisation}, debug port is " + (debugPort ?: "not enabled")) // Write node.conf writeConfig(nodeConf.baseDirectory, "node.conf", config) @@ -1029,7 +1087,13 @@ class DriverDSL( "user.dir" to nodeConf.baseDirectory ) // See experimental/quasar-hook/README.md for how to generate. - val excludePattern = "x(antlr**;bftsmart**;ch**;co.paralleluniverse**;com.codahale**;com.esotericsoftware**;com.fasterxml**;com.google**;com.ibm**;com.intellij**;com.jcabi**;com.nhaarman**;com.opengamma**;com.typesafe**;com.zaxxer**;de.javakaffee**;groovy**;groovyjarjarantlr**;groovyjarjarasm**;io.atomix**;io.github**;io.netty**;jdk**;joptsimple**;junit**;kotlin**;net.bytebuddy**;net.i2p**;org.apache**;org.assertj**;org.bouncycastle**;org.codehaus**;org.crsh**;org.dom4j**;org.fusesource**;org.h2**;org.hamcrest**;org.hibernate**;org.jboss**;org.jcp**;org.joda**;org.junit**;org.mockito**;org.objectweb**;org.objenesis**;org.slf4j**;org.w3c**;org.xml**;org.yaml**;reflectasm**;rx**)" + val excludePattern = "x(antlr**;bftsmart**;ch**;co.paralleluniverse**;com.codahale**;com.esotericsoftware**;" + + "com.fasterxml**;com.google**;com.ibm**;com.intellij**;com.jcabi**;com.nhaarman**;com.opengamma**;" + + "com.typesafe**;com.zaxxer**;de.javakaffee**;groovy**;groovyjarjarantlr**;groovyjarjarasm**;io.atomix**;" + + "io.github**;io.netty**;jdk**;joptsimple**;junit**;kotlin**;net.bytebuddy**;net.i2p**;org.apache**;" + + "org.assertj**;org.bouncycastle**;org.codehaus**;org.crsh**;org.dom4j**;org.fusesource**;org.h2**;" + + "org.hamcrest**;org.hibernate**;org.jboss**;org.jcp**;org.joda**;org.junit**;org.mockito**;org.objectweb**;" + + "org.objenesis**;org.slf4j**;org.w3c**;org.xml**;org.yaml**;reflectasm**;rx**)" val extraJvmArguments = systemProperties.removeResolvedClasspath().map { "-D${it.key}=${it.value}" } + "-javaagent:$quasarJarPath=$excludePattern" val loggingLevel = logLevel ?: if (debugPort == null) "INFO" else "DEBUG" @@ -1095,7 +1159,7 @@ class DriverDSL( } fun writeConfig(path: Path, filename: String, config: Config) { - path.toFile().mkdirs() - File("$path/$filename").writeText(config.root().render(ConfigRenderOptions.defaults())) + val configString = config.root().render(ConfigRenderOptions.defaults()) + configString.byteInputStream().copyTo(path / filename, REPLACE_EXISTING) } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/NodeBasedTest.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/NodeBasedTest.kt index a3449d9417..3c0c0da6e0 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/NodeBasedTest.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/NodeBasedTest.kt @@ -16,11 +16,14 @@ import net.corda.node.services.config.parseAsNodeConfiguration import net.corda.node.services.config.plus import net.corda.nodeapi.User import net.corda.testing.SerializationEnvironmentRule +import net.corda.testing.common.internal.NetworkParametersCopier +import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.driver.addressMustNotBeBoundFuture import net.corda.testing.getFreeLocalPorts import net.corda.testing.node.MockServices import org.apache.logging.log4j.Level import org.junit.After +import org.junit.Before import org.junit.Rule import org.junit.rules.TemporaryFolder import java.nio.file.Path @@ -40,6 +43,7 @@ abstract class NodeBasedTest(private val cordappPackages: List = emptyLi @JvmField val tempFolder = TemporaryFolder() + private lateinit var defaultNetworkParameters: NetworkParametersCopier private val nodes = mutableListOf>() private val nodeInfos = mutableListOf() @@ -47,6 +51,11 @@ abstract class NodeBasedTest(private val cordappPackages: List = emptyLi System.setProperty("consoleLogLevel", Level.DEBUG.name().toLowerCase()) } + @Before + fun init() { + defaultNetworkParameters = NetworkParametersCopier(testNetworkParameters(emptyList())) + } + /** * Stops the network map node and all the nodes started by [startNode]. This is called automatically after each test * but can also be called manually within a test. @@ -86,6 +95,7 @@ abstract class NodeBasedTest(private val cordappPackages: List = emptyLi ) val parsedConfig = config.parseAsNodeConfiguration() + defaultNetworkParameters.install(baseDirectory) val node = Node( parsedConfig, MockServices.MOCK_VERSION_INFO.copy(platformVersion = platformVersion), diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/demorun/DemoRunner.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/demorun/DemoRunner.kt index cf452a9116..a38a5d78f1 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/demorun/DemoRunner.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/demorun/DemoRunner.kt @@ -15,6 +15,7 @@ fun CordformDefinition.clean() { /** * Creates and starts all nodes required for the demo. */ +// TODO add notaries to cordform! fun CordformDefinition.runNodes() = driver( isDebug = true, driverDirectory = driverDirectory, diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/DriverBasedTest.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/DriverBasedTest.kt deleted file mode 100644 index d9b4807845..0000000000 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/DriverBasedTest.kt +++ /dev/null @@ -1,46 +0,0 @@ -package net.corda.testing.node - -import com.google.common.util.concurrent.SettableFuture -import net.corda.core.utilities.getOrThrow -import net.corda.testing.driver.DriverDSLExposedInterface -import org.junit.After -import org.junit.Before -import java.util.concurrent.CountDownLatch -import kotlin.concurrent.thread - -abstract class DriverBasedTest { - private val stopDriver = CountDownLatch(1) - private var driverThread: Thread? = null - private lateinit var driverStarted: SettableFuture - - protected sealed class RunTestToken { - internal object Token : RunTestToken() - } - - protected abstract fun setup(): RunTestToken - - protected fun DriverDSLExposedInterface.runTest(): RunTestToken { - driverStarted.set(Unit) - stopDriver.await() - return RunTestToken.Token - } - - @Before - fun start() { - driverStarted = SettableFuture.create() - driverThread = thread { - try { - setup() - } catch (t: Throwable) { - driverStarted.setException(t) - } - } - driverStarted.getOrThrow() - } - - @After - fun stop() { - stopDriver.countDown() - driverThread?.join() - } -} \ No newline at end of file diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetworkMapCache.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetworkMapCache.kt index 71ee228f1b..6e10455fee 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetworkMapCache.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetworkMapCache.kt @@ -18,7 +18,10 @@ import java.math.BigInteger /** * Network map cache with no backing map service. */ -class MockNetworkMapCache(database: CordaPersistence, configuration: NodeConfiguration) : PersistentNetworkMapCache(database, configuration) { +class MockNetworkMapCache( + database: CordaPersistence, + configuration: NodeConfiguration +) : PersistentNetworkMapCache(database, configuration, emptyList()) { private companion object { val BANK_C = getTestPartyAndCertificate(CordaX500Name(organisation = "Bank C", locality = "London", country = "GB"), entropyToKeyPair(BigInteger.valueOf(1000)).public) val BANK_D = getTestPartyAndCertificate(CordaX500Name(organisation = "Bank D", locality = "London", country = "GB"), entropyToKeyPair(BigInteger.valueOf(2000)).public) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt index 3c6918e9ec..9988dc16b3 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt @@ -7,13 +7,14 @@ import com.nhaarman.mockito_kotlin.whenever import net.corda.core.crypto.entropyToKeyPair import net.corda.core.crypto.random63BitValue import net.corda.core.identity.CordaX500Name -import net.corda.core.identity.PartyAndCertificate +import net.corda.core.identity.Party import net.corda.core.internal.createDirectories import net.corda.core.internal.createDirectory import net.corda.core.internal.uncheckedCast import net.corda.core.messaging.MessageRecipients import net.corda.core.messaging.RPCOps import net.corda.core.messaging.SingleMessageRecipient +import net.corda.core.node.NotaryInfo import net.corda.core.node.services.IdentityService import net.corda.core.node.services.KeyManagementService import net.corda.core.serialization.SerializationWhitelist @@ -23,33 +24,34 @@ import net.corda.core.utilities.loggerFor import net.corda.node.internal.AbstractNode import net.corda.node.internal.StartedNode import net.corda.node.internal.cordapp.CordappLoader -import net.corda.node.services.api.NetworkMapCacheInternal import net.corda.node.services.api.SchemaService import net.corda.node.services.config.BFTSMaRtConfiguration import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.NotaryConfig import net.corda.node.services.keys.E2ETestKeyManagementService import net.corda.node.services.messaging.MessagingService -import net.corda.node.services.network.InMemoryNetworkMapService -import net.corda.node.services.network.NetworkMapService import net.corda.node.services.transactions.BFTNonValidatingNotaryService import net.corda.node.services.transactions.BFTSMaRt import net.corda.node.services.transactions.InMemoryTransactionVerifierService import net.corda.node.utilities.AffinityExecutor import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor -import net.corda.nodeapi.internal.ServiceInfo +import net.corda.node.utilities.ServiceIdentityGenerator import net.corda.testing.DUMMY_NOTARY +import net.corda.testing.common.internal.NetworkParametersCopier +import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.initialiseTestSerialization import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import net.corda.testing.testNodeConfiguration import org.apache.activemq.artemis.utils.ReusableLatch import org.slf4j.Logger +import sun.plugin.dom.DOMObjectFactory.createNode import java.io.Closeable import java.math.BigInteger import java.nio.file.Path import java.security.KeyPair import java.security.PublicKey +import java.time.Clock import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicInteger import net.corda.testing.testNodeConfiguration @@ -64,20 +66,18 @@ data class MockNetworkParameters( val networkSendManuallyPumped: Boolean = false, val threadPerNode: Boolean = false, val servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = InMemoryMessagingNetwork.ServicePeerAllocationStrategy.Random(), - val defaultFactory: MockNetwork.Factory<*> = MockNetwork.DefaultFactory, + val defaultFactory: (MockNodeArgs) -> MockNetwork.MockNode = MockNetwork::MockNode, val initialiseSerialization: Boolean = true, val cordappPackages: List = emptyList()) { fun setNetworkSendManuallyPumped(networkSendManuallyPumped: Boolean) = copy(networkSendManuallyPumped = networkSendManuallyPumped) fun setThreadPerNode(threadPerNode: Boolean) = copy(threadPerNode = threadPerNode) fun setServicePeerAllocationStrategy(servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy) = copy(servicePeerAllocationStrategy = servicePeerAllocationStrategy) - fun setDefaultFactory(defaultFactory: MockNetwork.Factory<*>) = copy(defaultFactory = defaultFactory) + fun setDefaultFactory(defaultFactory: (MockNodeArgs) -> MockNetwork.MockNode) = copy(defaultFactory = defaultFactory) fun setInitialiseSerialization(initialiseSerialization: Boolean) = copy(initialiseSerialization = initialiseSerialization) fun setCordappPackages(cordappPackages: List) = copy(cordappPackages = cordappPackages) } /** - * @param notaryIdentity a set of service entries to use in place of the node's default service entries, - * for example where a node's service is part of a cluster. * @param entropyRoot the initial entropy value to use when generating keys. Defaults to an (insecure) random value, * but can be overridden to cause nodes to have stable or colliding identity/service keys. * @param configOverrides add/override behaviour of the [NodeConfiguration] mock object. @@ -86,12 +86,10 @@ data class MockNetworkParameters( data class MockNodeParameters( val forcedID: Int? = null, val legalName: CordaX500Name? = null, - val notaryIdentity: Pair? = null, val entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), val configOverrides: (NodeConfiguration) -> Any? = {}) { fun setForcedID(forcedID: Int?) = copy(forcedID = forcedID) fun setLegalName(legalName: CordaX500Name?) = copy(legalName = legalName) - fun setNotaryIdentity(notaryIdentity: Pair?) = copy(notaryIdentity = notaryIdentity) fun setEntropyRoot(entropyRoot: BigInteger) = copy(entropyRoot = entropyRoot) fun setConfigOverrides(configOverrides: (NodeConfiguration) -> Any?) = copy(configOverrides = configOverrides) } @@ -100,8 +98,8 @@ data class MockNodeArgs( val config: NodeConfiguration, val network: MockNetwork, val id: Int, - val notaryIdentity: Pair?, - val entropyRoot: BigInteger) + val entropyRoot: BigInteger +) /** * A mock node brings up a suite of in-memory services in a fast manner suitable for unit testing. @@ -115,13 +113,17 @@ data class MockNodeArgs( * You can get a printout of every message sent by using code like: * * LogHelper.setLevel("+messages") + * + * By default a single notary node is automatically started, which forms part of the network parameters for all the nodes. + * This node is available by calling [defaultNotaryNode]. */ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParameters(), private val networkSendManuallyPumped: Boolean = defaultParameters.networkSendManuallyPumped, private val threadPerNode: Boolean = defaultParameters.threadPerNode, servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = defaultParameters.servicePeerAllocationStrategy, - private val defaultFactory: Factory<*> = defaultParameters.defaultFactory, + private val defaultFactory: (MockNodeArgs) -> MockNode = defaultParameters.defaultFactory, initialiseSerialization: Boolean = defaultParameters.initialiseSerialization, + private val notarySpecs: List = listOf(NotarySpec(DUMMY_NOTARY.name)), private val cordappPackages: List = defaultParameters.cordappPackages) : Closeable { /** Helper constructor for creating a [MockNetwork] with custom parameters from Java. */ constructor(parameters: MockNetworkParameters) : this(defaultParameters = parameters) @@ -132,28 +134,43 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete val messagingNetwork = InMemoryMessagingNetwork(networkSendManuallyPumped, servicePeerAllocationStrategy, busyLatch) // A unique identifier for this network to segregate databases with the same nodeID but different networks. private val networkId = random63BitValue() + private val networkParameters: NetworkParametersCopier private val _nodes = mutableListOf() + private val serializationEnv = initialiseTestSerialization(initialiseSerialization) + private val sharedUserCount = AtomicInteger(0) + /** A read only view of the current set of executing nodes. */ val nodes: List get() = _nodes - private val serializationEnv = initialiseTestSerialization(initialiseSerialization) - init { - filesystem.getPath("/nodes").createDirectory() + /** + * Returns the list of nodes started by the network. Each notary specified when the network is constructed ([notarySpecs] + * parameter) maps 1:1 to the notaries returned by this list. + */ + val notaryNodes: List> + + /** + * Returns the single notary node on the network. Throws if there are none or more than one. + * @see notaryNodes + */ + val defaultNotaryNode: StartedNode get() { + return when (notaryNodes.size) { + 0 -> throw IllegalStateException("There are no notaries defined on the network") + 1 -> notaryNodes[0] + else -> throw IllegalStateException("There is more than one notary defined on the network") + } } - /** Allows customisation of how nodes are created. */ - interface Factory { - fun create(args: MockNodeArgs): N - } - - object DefaultFactory : Factory { - override fun create(args: MockNodeArgs) = MockNode(args) + /** + * Return the identity of the default notary node. + * @see defaultNotaryNode + */ + val defaultNotaryIdentity: Party get() { + return defaultNotaryNode.info.legalIdentities.singleOrNull() ?: throw IllegalStateException("Default notary has multiple identities") } /** * Because this executor is shared, we need to be careful about nodes shutting it down. */ - private val sharedUserCount = AtomicInteger(0) private val sharedServerThread = object : ServiceAffinityExecutor("Mock network", 1) { override fun shutdown() { // We don't actually allow the shutdown of the network-wide shared thread pool until all references to @@ -164,68 +181,107 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete } override fun awaitTermination(timeout: Long, unit: TimeUnit): Boolean { - if (!isShutdown) { + return if (!isShutdown) { flush() - return true + true } else { - return super.awaitTermination(timeout, unit) + super.awaitTermination(timeout, unit) } } } + init { + filesystem.getPath("/nodes").createDirectory() + val notaryInfos = generateNotaryIdentities() + // The network parameters must be serialised before starting any of the nodes + networkParameters = NetworkParametersCopier(testNetworkParameters(notaryInfos)) + notaryNodes = createNotaries() + } + + private fun generateNotaryIdentities(): List { + return notarySpecs.mapIndexed { index, spec -> + val identity = ServiceIdentityGenerator.generateToDisk( + dirs = listOf(baseDirectory(nextNodeId + index)), + serviceName = spec.name, + serviceId = "identity") + NotaryInfo(identity, spec.validating) + } + } + + private fun createNotaries(): List> { + return notarySpecs.map { spec -> + createNode(MockNodeParameters(legalName = spec.name, configOverrides = { + doReturn(NotaryConfig(spec.validating)).whenever(it).notary + })) + } + } + open class MockNode(args: MockNodeArgs) : AbstractNode( args.config, - TestClock(), + TestClock(Clock.systemUTC()), MOCK_VERSION_INFO, CordappLoader.createDefaultWithTestPackages(args.config, args.network.cordappPackages), - args.network.busyLatch) { + args.network.busyLatch + ) { val mockNet = args.network val id = args.id - internal val notaryIdentity = args.notaryIdentity - val entropyRoot = args.entropyRoot + private val entropyRoot = args.entropyRoot var counter = entropyRoot + override val log: Logger = loggerFor() + override val serverThread: AffinityExecutor = - if (mockNet.threadPerNode) + if (mockNet.threadPerNode) { ServiceAffinityExecutor("Mock node $id thread", 1) - else { + } else { mockNet.sharedUserCount.incrementAndGet() mockNet.sharedServerThread } + override val started: StartedNode? get() = uncheckedCast(super.started) - override fun start(): StartedNode = uncheckedCast(super.start()) + + override fun start(): StartedNode { + mockNet.networkParameters.install(configuration.baseDirectory) + val started: StartedNode = uncheckedCast(super.start()) + advertiseNodeToNetwork(started) + return started + } + + private fun advertiseNodeToNetwork(newNode: StartedNode) { + mockNet.nodes + .mapNotNull { it.started } + .forEach { existingNode -> + newNode.services.networkMapCache.addNode(existingNode.info) + existingNode.services.networkMapCache.addNode(newNode.info) + } + } // We only need to override the messaging service here, as currently everything that hits disk does so // through the java.nio API which we are already mocking via Jimfs. - override fun makeMessagingService(legalIdentity: PartyAndCertificate): MessagingService { + override fun makeMessagingService(): MessagingService { require(id >= 0) { "Node ID must be zero or positive, was passed: " + id } return mockNet.messagingNetwork.createNodeWithID( !mockNet.threadPerNode, id, serverThread, - getNotaryIdentity(), + myNotaryIdentity, myLegalName, - database) - .start() - .getOrThrow() + database + ).start().getOrThrow() } fun setMessagingServiceSpy(messagingServiceSpy: MessagingServiceSpy) { network = messagingServiceSpy } - override fun makeKeyManagementService(identityService: IdentityService): KeyManagementService { - return E2ETestKeyManagementService(identityService, partyKeys + (notaryIdentity?.let { setOf(it.second) } ?: emptySet())) + override fun makeKeyManagementService(identityService: IdentityService, keyPairs: Set): KeyManagementService { + return E2ETestKeyManagementService(identityService, keyPairs) } override fun startMessagingService(rpcOps: RPCOps) { // Nothing to do } - override fun makeNetworkMapService(network: MessagingService, networkMapCache: NetworkMapCacheInternal): NetworkMapService { - return InMemoryNetworkMapService(network, networkMapCache, 1) - } - // This is not thread safe, but node construction is done on a single thread, so that should always be fine override fun generateKeyPair(): KeyPair { counter = counter.add(BigInteger.ONE) @@ -240,7 +296,7 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete override fun makeTransactionVerifierService() = InMemoryTransactionVerifierService(1) - override fun myAddresses() = emptyList() + override fun myAddresses(): List = emptyList() // Allow unit tests to modify the serialization whitelist list before the node start, // so they don't have to ServiceLoad test whitelists into all unit tests. @@ -262,9 +318,6 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete dbCloser = null } - fun hasDBConnection() = dbCloser != null - - // You can change this from zero if you have custom [FlowLogic] that park themselves. e.g. [StateMachineManagerTests] var acceptableLiveFiberCountOnStop: Int = 0 override fun acceptableLiveFiberCountOnStop(): Int = acceptableLiveFiberCountOnStop @@ -285,20 +338,24 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete } } - fun createUnstartedNode(parameters: MockNodeParameters = MockNodeParameters()) = createUnstartedNode(parameters, defaultFactory) - fun createUnstartedNode(parameters: MockNodeParameters = MockNodeParameters(), nodeFactory: Factory): N { + fun createUnstartedNode(parameters: MockNodeParameters = MockNodeParameters()): MockNode { + return createUnstartedNode(parameters, defaultFactory) + } + + fun createUnstartedNode(parameters: MockNodeParameters = MockNodeParameters(), nodeFactory: (MockNodeArgs) -> N): N { return createNodeImpl(parameters, nodeFactory, false) } - fun createNode(parameters: MockNodeParameters = MockNodeParameters()): StartedNode = createNode(parameters, defaultFactory) - /** Like the other [createNode] but takes a [Factory] and propagates its [MockNode] subtype. */ - fun createNode(parameters: MockNodeParameters = MockNodeParameters(), nodeFactory: Factory): StartedNode { - val node: StartedNode = uncheckedCast(createNodeImpl(parameters, nodeFactory, true).started)!! - ensureAllNetworkMapCachesHaveAllNodeInfos() - return node + fun createNode(parameters: MockNodeParameters = MockNodeParameters()): StartedNode { + return createNode(parameters, defaultFactory) } - private fun createNodeImpl(parameters: MockNodeParameters, nodeFactory: Factory, start: Boolean): N { + /** Like the other [createNode] but takes a [nodeFactory] and propagates its [MockNode] subtype. */ + fun createNode(parameters: MockNodeParameters = MockNodeParameters(), nodeFactory: (MockNodeArgs) -> N): StartedNode { + return uncheckedCast(createNodeImpl(parameters, nodeFactory, true).started)!! + } + + private fun createNodeImpl(parameters: MockNodeParameters, nodeFactory: (MockNodeArgs) -> N, start: Boolean): N { val id = parameters.forcedID ?: nextNodeId++ val config = testNodeConfiguration( baseDirectory = baseDirectory(id).createDirectories(), @@ -306,14 +363,12 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete doReturn(makeTestDataSourceProperties("node_${id}_net_$networkId")).whenever(it).dataSourceProperties parameters.configOverrides(it) } - return nodeFactory.create(MockNodeArgs(config, this, id, parameters.notaryIdentity, parameters.entropyRoot)).apply { - if (start) { - start() - if (threadPerNode) nodeReadyFuture.getOrThrow() // XXX: What about manually-started nodes? - ensureAllNetworkMapCachesHaveAllNodeInfos() - } - _nodes.add(this) + val node = nodeFactory(MockNodeArgs(config, this, id, parameters.entropyRoot)) + _nodes += node + if (start) { + node.start() } + return node } fun baseDirectory(nodeId: Int): Path = filesystem.getPath("/nodes/$nodeId") @@ -326,7 +381,6 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete */ @JvmOverloads fun runNetwork(rounds: Int = -1) { - ensureAllNetworkMapCachesHaveAllNodeInfos() check(!networkSendManuallyPumped) fun pumpAll() = messagingNetwork.endpoints.map { it.pumpReceive(false) } @@ -341,23 +395,9 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete } @JvmOverloads - fun createNotaryNode(parameters: MockNodeParameters = MockNodeParameters(legalName = DUMMY_NOTARY.name), validating: Boolean = true): StartedNode { - return createNotaryNode(parameters, validating, defaultFactory) - } - fun createNotaryNode(parameters: MockNodeParameters = MockNodeParameters(legalName = DUMMY_NOTARY.name), - validating: Boolean = true, - nodeFactory: Factory): StartedNode { - return createNode(parameters.copy(configOverrides = { - doReturn(NotaryConfig(validating)).whenever(it).notary - parameters.configOverrides(it) - }), nodeFactory) - } - - @JvmOverloads - fun createPartyNode(legalName: CordaX500Name? = null, - notaryIdentity: Pair? = null): StartedNode { - return createNode(MockNodeParameters(legalName = legalName, notaryIdentity = notaryIdentity)) + fun createPartyNode(legalName: CordaX500Name? = null): StartedNode { + return createNode(MockNodeParameters(legalName = legalName)) } @Suppress("unused") // This is used from the network visualiser tool. @@ -372,21 +412,9 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete } } - private fun ensureAllNetworkMapCachesHaveAllNodeInfos() { - val infos = nodes.mapNotNull { it.started?.info } - nodes.filter { it.hasDBConnection() } - .mapNotNull { it.started?.services?.networkMapCache } - .forEach { - for (nodeInfo in infos) { - it.addNode(nodeInfo) - } - } - } - fun startNodes() { require(nodes.isNotEmpty()) nodes.forEach { it.started ?: it.start() } - ensureAllNetworkMapCachesHaveAllNodeInfos() } fun stopNodes() { @@ -403,14 +431,16 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete override fun close() { stopNodes() } + + data class NotarySpec(val name: CordaX500Name, val validating: Boolean = true) { + constructor(name: CordaX500Name) : this(name, validating = true) + } } -fun network(nodesCount: Int, action: MockNetwork.(nodes: List>, notary: StartedNode) -> Unit) { - MockNetwork().use { - it.runNetwork() - val notary = it.createNotaryNode() - val nodes = (1..nodesCount).map { _ -> it.createPartyNode() } - action(it, nodes, notary) +fun network(nodesCount: Int, action: MockNetwork.(List>) -> Unit) { + MockNetwork().use { mockNet -> + val nodes = (1..nodesCount).map { mockNet.createPartyNode() } + mockNet.action(nodes) } } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/NotarySpec.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/NotarySpec.kt new file mode 100644 index 0000000000..336d6bfc8d --- /dev/null +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/NotarySpec.kt @@ -0,0 +1,23 @@ +package net.corda.testing.node + +import net.corda.core.identity.CordaX500Name +import net.corda.node.services.config.VerifierType +import net.corda.nodeapi.User + +data class NotarySpec( + val name: CordaX500Name, + val validating: Boolean = true, + val rpcUsers: List = emptyList(), + val verifierType: VerifierType = VerifierType.InMemory, + val cluster: ClusterSpec? = null +) + +sealed class ClusterSpec { + abstract val clusterSize: Int + + data class Raft(override val clusterSize: Int) : ClusterSpec() { + init { + require(clusterSize > 0) + } + } +} \ No newline at end of file diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/SimpleNode.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/SimpleNode.kt deleted file mode 100644 index 6dad146916..0000000000 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/SimpleNode.kt +++ /dev/null @@ -1,77 +0,0 @@ -package net.corda.testing.node - -import com.codahale.metrics.MetricRegistry -import net.corda.core.crypto.generateKeyPair -import net.corda.core.internal.concurrent.openFuture -import net.corda.core.messaging.RPCOps -import net.corda.core.node.services.IdentityService -import net.corda.core.node.services.KeyManagementService -import net.corda.core.utilities.NetworkHostAndPort -import net.corda.node.services.RPCUserServiceImpl -import net.corda.node.services.api.MonitoringService -import net.corda.node.services.config.NodeConfiguration -import net.corda.node.services.identity.InMemoryIdentityService -import net.corda.node.services.keys.E2ETestKeyManagementService -import net.corda.node.services.messaging.ArtemisMessagingServer -import net.corda.node.services.messaging.NodeMessagingClient -import net.corda.node.services.network.NetworkMapCacheImpl -import net.corda.node.services.schema.NodeSchemaService -import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor -import net.corda.node.utilities.CordaPersistence -import net.corda.node.utilities.configureDatabase -import net.corda.testing.freeLocalHostAndPort -import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO -import java.security.KeyPair -import java.security.cert.X509Certificate -import kotlin.concurrent.thread - -/** - * This is a bare-bones node which can only send and receive messages. It doesn't register with a network map service or - * any other such task that would make it functional in a network and thus left to the user to do so manually. - */ -class SimpleNode(val config: NodeConfiguration, val address: NetworkHostAndPort = freeLocalHostAndPort(), - rpcAddress: NetworkHostAndPort = freeLocalHostAndPort(), - trustRoot: X509Certificate) : AutoCloseable { - - val userService = RPCUserServiceImpl(config.rpcUsers) - val monitoringService = MonitoringService(MetricRegistry()) - val identity: KeyPair = generateKeyPair() - val identityService: IdentityService = InMemoryIdentityService(trustRoot = trustRoot) - val database: CordaPersistence = configureDatabase(config.dataSourceProperties, config.database, { InMemoryIdentityService(trustRoot = trustRoot) }) - val keyService: KeyManagementService = E2ETestKeyManagementService(identityService, setOf(identity)) - val executor = ServiceAffinityExecutor(config.myLegalName.organisation, 1) - // TODO: We should have a dummy service hub rather than change behaviour in tests - val broker = ArtemisMessagingServer(config, address.port, rpcAddress.port, - NetworkMapCacheImpl(MockNetworkMapCache(database, config), identityService), userService) - val networkMapRegistrationFuture = openFuture() - val network = database.transaction { - NodeMessagingClient( - config, - MOCK_VERSION_INFO, - address, - identity.public, - executor, - database, - networkMapRegistrationFuture, - monitoringService) - } - - fun start() { - broker.start() - network.start( - object : RPCOps { - override val protocolVersion = 0 - }, - userService) - thread(name = config.myLegalName.organisation) { - network.run(broker.serverControl) - } - } - - override fun close() { - network.stop() - broker.stop() - database.close() - executor.shutdownNow() - } -} diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/TestClock.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/TestClock.kt index e1149b1fda..75d6563ae4 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/TestClock.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/TestClock.kt @@ -1,30 +1,16 @@ package net.corda.testing.node import net.corda.core.internal.until -import net.corda.core.serialization.SerializeAsToken -import net.corda.core.serialization.SerializeAsTokenContext -import net.corda.core.serialization.SingletonSerializationToken.Companion.singletonSerializationToken import net.corda.node.internal.MutableClock import java.time.Clock import java.time.Duration import java.time.Instant -import java.time.ZoneId import javax.annotation.concurrent.ThreadSafe - -/** - * A [Clock] that can have the time advanced for use in testing. - */ +/** A [Clock] that can have the time advanced for use in testing. */ @ThreadSafe -class TestClock(private var delegateClock: Clock = Clock.systemUTC()) : MutableClock(), SerializeAsToken { - - private val token = singletonSerializationToken(javaClass) - - override fun toToken(context: SerializeAsTokenContext) = token.registerWithContext(context, this) - - /** - * Advance this [Clock] by the specified [Duration] for testing purposes. - */ +class TestClock(delegateClock: Clock) : MutableClock(delegateClock) { + /** Advance this [Clock] by the specified [Duration] for testing purposes. */ @Synchronized fun advanceBy(duration: Duration) { delegateClock = offset(delegateClock, duration) @@ -33,28 +19,8 @@ class TestClock(private var delegateClock: Clock = Clock.systemUTC()) : MutableC /** * Move this [Clock] to the specified [Instant] for testing purposes. - * * This will only be approximate due to the time ticking away, but will be some time shortly after the requested [Instant]. */ @Synchronized fun setTo(newInstant: Instant) = advanceBy(instant() until newInstant) - - @Synchronized override fun instant(): Instant { - return delegateClock.instant() - } - - @Deprecated("Do not use this. Instead seek to use ZonedDateTime methods.", level = DeprecationLevel.ERROR) - override fun withZone(zone: ZoneId): Clock { - throw UnsupportedOperationException("Tokenized clock does not support withZone()") - } - - @Synchronized override fun getZone(): ZoneId { - return delegateClock.zone - } } - -/** - * A helper method to set several [TestClock]s to approximately the same time. The clocks may drift by the time it - * takes for each [TestClock] to have it's time set and any observers to execute. - */ -fun Iterable.setTo(instant: Instant) = this.forEach { it.setTo(instant) } diff --git a/testing/node-driver/src/test/java/net/corda/testing/node/MockNodeFactoryInJavaTest.java b/testing/node-driver/src/test/java/net/corda/testing/node/MockNodeFactoryInJavaTest.java new file mode 100644 index 0000000000..4e6b9215ca --- /dev/null +++ b/testing/node-driver/src/test/java/net/corda/testing/node/MockNodeFactoryInJavaTest.java @@ -0,0 +1,25 @@ +package net.corda.testing.node; + +import org.jetbrains.annotations.NotNull; + +@SuppressWarnings("unused") +public class MockNodeFactoryInJavaTest { + private static class CustomNode extends MockNetwork.MockNode { + private CustomNode(@NotNull MockNodeArgs args) { + super(args); + } + } + + /** + * Does not need to run, only compile. + */ + @SuppressWarnings("unused") + private static void factoryIsEasyToPassInUsingJava() { + //noinspection Convert2MethodRef + new MockNetwork(new MockNetworkParameters().setDefaultFactory(args -> new CustomNode(args))); + new MockNetwork(new MockNetworkParameters().setDefaultFactory(CustomNode::new)); + //noinspection Convert2MethodRef + new MockNetwork().createNode(new MockNodeParameters(), args -> new CustomNode(args)); + new MockNetwork().createNode(new MockNodeParameters(), CustomNode::new); + } +} diff --git a/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt b/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt index 28d1afb9bf..f2a9196eac 100644 --- a/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt +++ b/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt @@ -2,10 +2,14 @@ package net.corda.smoketesting import net.corda.client.rpc.CordaRPCClient import net.corda.client.rpc.CordaRPCConnection +import net.corda.client.rpc.internal.KryoClientSerializationScheme +import net.corda.core.internal.copyTo import net.corda.core.internal.createDirectories import net.corda.core.internal.div import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.loggerFor +import net.corda.testing.common.internal.NetworkParametersCopier +import net.corda.testing.common.internal.testNetworkParameters import java.nio.file.Path import java.nio.file.Paths import java.time.Instant @@ -15,15 +19,13 @@ import java.util.concurrent.Executors import java.util.concurrent.TimeUnit.SECONDS class NodeProcess( - val config: NodeConfig, - val nodeDir: Path, + private val config: NodeConfig, + private val nodeDir: Path, private val node: Process, private val client: CordaRPCClient ) : AutoCloseable { private companion object { val log = loggerFor() - val javaPath: Path = Paths.get(System.getProperty("java.home"), "bin", "java") - val formatter: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss").withZone(systemDefault()) } fun connect(): CordaRPCConnection { @@ -43,22 +45,41 @@ class NodeProcess( (nodeDir / "artemis").toFile().deleteRecursively() } - class Factory(val buildDirectory: Path = Paths.get("build"), - val cordaJar: Path = Paths.get(this::class.java.getResource("/corda.jar").toURI())) { - val nodesDirectory = buildDirectory / formatter.format(Instant.now()) + class Factory( + private val buildDirectory: Path = Paths.get("build"), + private val cordaJar: Path = Paths.get(this::class.java.getResource("/corda.jar").toURI()) + ) { + private companion object { + val javaPath: Path = Paths.get(System.getProperty("java.home"), "bin", "java") + val formatter: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss").withZone(systemDefault()) + val defaultNetworkParameters = run { + // TODO withTestSerialization is in test-utils, which we don't have access to + KryoClientSerializationScheme.initialiseSerialization() + // There are no notaries in the network parameters for smoke test nodes. If this is required then we would + // need to introduce the concept of a "network" which predefines the notaries, like the driver and MockNetwork + NetworkParametersCopier(testNetworkParameters(emptyList())) + } - init { - nodesDirectory.createDirectories() + init { + try { + Class.forName("net.corda.node.Corda") + throw Error("Smoke test has the node in its classpath. Please remove the offending dependency.") + } catch (e: ClassNotFoundException) { + // If the class can't be found then we're good! + } + } } + private val nodesDirectory = (buildDirectory / formatter.format(Instant.now())).createDirectories() + fun baseDirectory(config: NodeConfig): Path = nodesDirectory / config.commonName fun create(config: NodeConfig): NodeProcess { val nodeDir = baseDirectory(config).createDirectories() log.info("Node directory: {}", nodeDir) - val confFile = nodeDir.resolve("node.conf").toFile() - confFile.writeText(config.toText()) + config.toText().byteInputStream().copyTo(nodeDir / "node.conf") + defaultNetworkParameters.install(nodeDir) val process = startNode(nodeDir) val client = CordaRPCClient(NetworkHostAndPort("localhost", config.rpcPort)) diff --git a/testing/test-common/build.gradle b/testing/test-common/build.gradle index 05c6d36b20..dd2e2285b5 100644 --- a/testing/test-common/build.gradle +++ b/testing/test-common/build.gradle @@ -1,6 +1,10 @@ apply plugin: 'net.corda.plugins.publish-utils' apply plugin: 'com.jfrog.artifactory' +dependencies { + compile project(':core') +} + jar { baseName 'corda-test-common' } diff --git a/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/NetworkParametersCopier.kt b/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/NetworkParametersCopier.kt new file mode 100644 index 0000000000..25ba327a73 --- /dev/null +++ b/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/NetworkParametersCopier.kt @@ -0,0 +1,32 @@ +package net.corda.testing.common.internal + +import net.corda.core.crypto.SignedData +import net.corda.core.crypto.entropyToKeyPair +import net.corda.core.crypto.sign +import net.corda.core.internal.copyTo +import net.corda.core.internal.div +import net.corda.core.node.NetworkParameters +import net.corda.core.serialization.serialize +import java.math.BigInteger +import java.nio.file.FileAlreadyExistsException +import java.nio.file.Path + +class NetworkParametersCopier(networkParameters: NetworkParameters) { + private companion object { + val DUMMY_MAP_KEY = entropyToKeyPair(BigInteger.valueOf(123)) + } + + private val serializedNetworkParameters = networkParameters.let { + val serialize = it.serialize() + val signature = DUMMY_MAP_KEY.sign(serialize) + SignedData(serialize, signature).serialize() + } + + fun install(dir: Path) { + try { + serializedNetworkParameters.open().copyTo(dir / "network-parameters") + } catch (e: FileAlreadyExistsException) { + // Leave the file untouched if it already exists + } + } +} \ No newline at end of file diff --git a/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt b/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt new file mode 100644 index 0000000000..da40ba6ac3 --- /dev/null +++ b/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt @@ -0,0 +1,18 @@ +package net.corda.testing.common.internal + +import net.corda.core.node.NetworkParameters +import net.corda.core.node.NotaryInfo +import net.corda.core.utilities.days +import java.time.Instant + +fun testNetworkParameters(notaries: List): NetworkParameters { + return NetworkParameters( + minimumPlatformVersion = 1, + notaries = notaries, + modifiedTime = Instant.now(), + eventHorizon = 10000.days, + maxMessageSize = 40000, + maxTransactionSize = 40000, + epoch = 1 + ) +} \ No newline at end of file diff --git a/testing/test-utils/build.gradle b/testing/test-utils/build.gradle index f2995b396a..c90b00304e 100644 --- a/testing/test-utils/build.gradle +++ b/testing/test-utils/build.gradle @@ -8,7 +8,6 @@ description 'Testing utilities for Corda' dependencies { compile project(':test-common') - compile project(':core') compile project(':node') compile project(':client:mock') diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt index a1cda49a22..3155e38f27 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt @@ -24,7 +24,7 @@ import net.corda.node.utilities.CertificateAndKeyPair import net.corda.node.utilities.CertificateType import net.corda.node.utilities.X509Utilities import net.corda.nodeapi.config.SSLConfiguration -import net.corda.nodeapi.internal.serialization.AMQP_ENABLED +import net.corda.nodeapi.internal.serialization.amqp.AMQP_ENABLED import org.mockito.internal.stubbing.answers.ThrowsException import org.mockito.stubbing.Answer import java.nio.file.Files diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/IntegrationTestCategory.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/IntegrationTestCategory.kt deleted file mode 100644 index cea4389b47..0000000000 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/IntegrationTestCategory.kt +++ /dev/null @@ -1,6 +0,0 @@ -package net.corda.testing - -/** - * Marker interface for tests which are only run as part of the integration tests. - */ -interface IntegrationTestCategory diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt index 857f9cb0fb..4738175ae4 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt @@ -9,6 +9,8 @@ import net.corda.core.serialization.internal.SerializationEnvironment import net.corda.core.utilities.ByteSequence import net.corda.node.serialization.KryoServerSerializationScheme import net.corda.nodeapi.internal.serialization.* +import net.corda.nodeapi.internal.serialization.amqp.AMQPClientSerializationScheme +import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme import org.junit.rules.TestRule import org.junit.runner.Description import org.junit.runners.model.Statement diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/TestConstants.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/TestConstants.kt index 9e73764f06..d4b2290b40 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/TestConstants.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/TestConstants.kt @@ -18,7 +18,6 @@ import org.bouncycastle.cert.X509CertificateHolder import java.math.BigInteger import java.security.KeyPair import java.security.PublicKey -import java.security.cert.Certificate import java.time.Instant // A dummy time at which we will be pretending test transactions are created. @@ -31,11 +30,6 @@ val DUMMY_NOTARY_KEY: KeyPair by lazy { entropyToKeyPair(BigInteger.valueOf(20)) /** Dummy notary identity for tests and simulations */ val DUMMY_NOTARY_IDENTITY: PartyAndCertificate get() = getTestPartyAndCertificate(DUMMY_NOTARY) val DUMMY_NOTARY: Party get() = Party(CordaX500Name(organisation = "Notary Service", locality = "Zurich", country = "CH"), DUMMY_NOTARY_KEY.public) -val DUMMY_NOTARY_SERVICE_NAME: CordaX500Name = DUMMY_NOTARY.name.copy(commonName = "corda.notary.validating") - -val DUMMY_MAP_KEY: KeyPair by lazy { entropyToKeyPair(BigInteger.valueOf(30)) } -/** Dummy network map service identity for tests and simulations */ -val DUMMY_MAP: Party get() = Party(CordaX500Name(organisation = "Network Map Service", locality = "Amsterdam", country = "NL"), DUMMY_MAP_KEY.public) val DUMMY_BANK_A_KEY: KeyPair by lazy { entropyToKeyPair(BigInteger.valueOf(40)) } /** Dummy bank identity for tests and simulations */ diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/model/InstallFactory.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/model/InstallFactory.kt index d2dba4a687..f7709a7ada 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/model/InstallFactory.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/model/InstallFactory.kt @@ -25,10 +25,6 @@ class InstallFactory : Controller() { val tempDir = Files.createTempDirectory(baseDir, ".node") - if (nodeConfig.isNetworkMap) { - log.info("Node '${nodeConfig.myLegalName}' is the network map") - } - return InstallConfig(tempDir, NodeConfigWrapper(tempDir, nodeConfig)) } } diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeConfig.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeConfig.kt index 504fdfab79..8ac6aeefdf 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeConfig.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeConfig.kt @@ -22,7 +22,6 @@ data class NodeConfig( /** This is not used by the node but by the webserver which looks at node.conf. */ val webAddress: NetworkHostAndPort, val notary: NotaryService?, - val networkMapService: NetworkMapConfig?, val h2port: Int, val rpcUsers: List = listOf(defaultUser), /** This is an extra config used by the Cash app. */ @@ -39,16 +38,9 @@ data class NodeConfig( @Suppress("unused") private val useTestClock = true - val isNetworkMap: Boolean get() = networkMapService == null - fun toText(): String = toConfig().root().render(renderOptions) } -/** - * This is a mirror of NetworkMapInfo. - */ -data class NetworkMapConfig(val legalName: CordaX500Name, val address: NetworkHostAndPort) - /** * This is a subset of NotaryConfig. It implements [ExtraService] to avoid unnecessary copying. */ diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt index 2830c969b8..59237e97a8 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt @@ -37,8 +37,6 @@ class NodeController(check: atRuntime = ::checkExists) : Controller() { private val nodes = LinkedHashMap() private val port = AtomicInteger(firstPort) - private var networkMapConfig: NetworkMapConfig? = null - val activeNodes: List get() = nodes.values.filter { (it.state == NodeState.RUNNING) || (it.state == NodeState.STARTING) @@ -70,7 +68,6 @@ class NodeController(check: atRuntime = ::checkExists) : Controller() { rpcAddress = nodeData.rpcPort.toLocalAddress(), webAddress = nodeData.webPort.toLocalAddress(), notary = nodeData.extraServices.filterIsInstance().noneOrSingle(), - networkMapService = networkMapConfig, // The first node becomes the network map h2port = nodeData.h2Port.value, issuableCurrencies = nodeData.extraServices.filterIsInstance().map { it.currency.toString() } ) @@ -82,11 +79,6 @@ class NodeController(check: atRuntime = ::checkExists) : Controller() { return null } - if (nodeConfig.isNetworkMap) { - networkMapConfig = nodeConfig.let { NetworkMapConfig(it.myLegalName, it.p2pAddress) } - log.info("Network map provided by: ${nodeConfig.myLegalName}") - } - nodeInfoFilesCopier.addConfig(wrapper) return wrapper @@ -96,10 +88,6 @@ class NodeController(check: atRuntime = ::checkExists) : Controller() { config.state = NodeState.DEAD nodeInfoFilesCopier.removeConfig(config) - - if (config.nodeConfig.isNetworkMap) { - log.warning("Network map service (Node '${config.nodeConfig.myLegalName}') has exited.") - } } val nextPort: Int get() = port.andIncrement @@ -110,7 +98,7 @@ class NodeController(check: atRuntime = ::checkExists) : Controller() { fun nameExists(name: String) = keyExists(name.toKey()) - fun hasNetworkMap(): Boolean = networkMapConfig != null + fun hasNotary(): Boolean = activeNodes.any { it.nodeConfig.notary != null } fun runCorda(pty: R3Pty, config: NodeConfigWrapper): Boolean { try { @@ -141,7 +129,6 @@ class NodeController(check: atRuntime = ::checkExists) : Controller() { log.info("Changed base directory: $baseDir") // Wipe out any knowledge of previous nodes. - networkMapConfig = null nodes.clear() nodeInfoFilesCopier.reset() } @@ -157,10 +144,6 @@ class NodeController(check: atRuntime = ::checkExists) : Controller() { updatePort(config.nodeConfig) - if (networkMapConfig == null && config.nodeConfig.isNetworkMap) { - networkMapConfig = config.nodeConfig.let { NetworkMapConfig(it.myLegalName, it.p2pAddress) } - } - return true } diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTabView.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTabView.kt index c9f993a5c2..cc728b4708 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTabView.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTabView.kt @@ -114,7 +114,7 @@ class NodeTabView : Fragment() { fieldset("Additional configuration") { styleClass.addAll("services-panel") - val extraServices = if (nodeController.hasNetworkMap()) { + val extraServices = if (nodeController.hasNotary()) { listOf(USD, GBP, CHF, EUR).map { CurrencyIssuer(it) } } else { listOf(NotaryService(true), NotaryService(false)) @@ -123,7 +123,7 @@ class NodeTabView : Fragment() { val servicesList = CheckListView(extraServices.observable()).apply { vboxConstraints { vGrow = Priority.ALWAYS } model.item.extraServices.set(checkModel.checkedItems) - if (!nodeController.hasNetworkMap()) { + if (!nodeController.hasNotary()) { checkModel.check(0) checkModel.checkedItems.addListener(ListChangeListener { change -> while (change.next()) { diff --git a/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeConfigTest.kt b/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeConfigTest.kt index 92ed477be8..8ebcbaf2cc 100644 --- a/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeConfigTest.kt +++ b/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeConfigTest.kt @@ -7,7 +7,6 @@ import net.corda.core.utilities.NetworkHostAndPort import net.corda.node.services.config.parseAsNodeConfiguration import net.corda.nodeapi.User import net.corda.nodeapi.config.toConfig -import net.corda.testing.DUMMY_NOTARY import net.corda.webserver.WebServerConfig import org.assertj.core.api.Assertions.assertThat import org.junit.Test @@ -32,7 +31,6 @@ class NodeConfigTest { webPort = 20001, h2port = 30001, notary = NotaryService(validating = false), - networkMap = NetworkMapConfig(DUMMY_NOTARY.name, localPort(12345)), users = listOf(user("jenny")) ) @@ -60,7 +58,6 @@ class NodeConfigTest { webPort = 20001, h2port = 30001, notary = NotaryService(validating = false), - networkMap = NetworkMapConfig(DUMMY_NOTARY.name, localPort(12345)), users = listOf(user("jenny")) ) @@ -83,7 +80,6 @@ class NodeConfigTest { webPort: Int = -1, h2port: Int = -1, notary: NotaryService?, - networkMap: NetworkMapConfig?, users: List = listOf(user("guest")) ): NodeConfig { return NodeConfig( @@ -93,7 +89,6 @@ class NodeConfigTest { webAddress = localPort(webPort), h2port = h2port, notary = notary, - networkMapService = networkMap, rpcUsers = users ) } diff --git a/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeControllerTest.kt b/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeControllerTest.kt index bae103a317..21899794d8 100644 --- a/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeControllerTest.kt +++ b/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeControllerTest.kt @@ -3,7 +3,6 @@ package net.corda.demobench.model import net.corda.core.identity.CordaX500Name import net.corda.core.utilities.NetworkHostAndPort import net.corda.nodeapi.User -import net.corda.testing.DUMMY_NOTARY import org.junit.Test import java.nio.file.Path import java.nio.file.Paths @@ -24,6 +23,22 @@ class NodeControllerTest { assertNull(controller.validate(data)) } + @Test + fun `register notary`() { + assertFalse(controller.hasNotary()) + val config = createConfig(organisation = "Name", notary = NotaryService(false)) + controller.register(config) + assertTrue(controller.hasNotary()) + } + + @Test + fun `register non notary`() { + assertFalse(controller.hasNotary()) + val config = createConfig(organisation = "Name") + controller.register(config) + assertFalse(controller.hasNotary()) + } + @Test fun `test unique key after validate`() { val data = NodeData() @@ -48,28 +63,16 @@ class NodeControllerTest { assertTrue(controller.nameExists("organisation 1")) } - @Test - fun `test first validated node becomes network map`() { - val data = NodeData() - data.legalName.value = node1Name - data.p2pPort.value = 10000 - - assertFalse(controller.hasNetworkMap()) - controller.validate(data) - assertTrue(controller.hasNetworkMap()) - } - @Test fun `test register unique nodes`() { - val config = createConfig(commonName = organisation2Name) + val config = createConfig(organisation = organisation2Name) assertTrue(controller.register(config)) assertFalse(controller.register(config)) } @Test fun `test unique key after register`() { - val config = createConfig(commonName = organisation2Name) - + val config = createConfig(organisation = organisation2Name) assertFalse(controller.keyExists("organisation2")) controller.register(config) assertTrue(controller.keyExists("organisation2")) @@ -77,8 +80,7 @@ class NodeControllerTest { @Test fun `test matching name after register`() { - val config = createConfig(commonName = organisation2Name) - + val config = createConfig(organisation = organisation2Name) assertFalse(controller.nameExists("Organisation 2")) assertFalse(controller.nameExists("Organisation2")) assertFalse(controller.nameExists("organisation 2")) @@ -88,28 +90,6 @@ class NodeControllerTest { assertTrue(controller.nameExists("organisation 2")) } - @Test - fun `test register network map node`() { - val config = createConfig(commonName = "Organisation is Network Map") - assertTrue(config.nodeConfig.isNetworkMap) - - assertFalse(controller.hasNetworkMap()) - controller.register(config) - assertTrue(controller.hasNetworkMap()) - } - - @Test - fun `test register non-network-map node`() { - val config = createConfig( - commonName = "Organisation is not Network Map", - networkMap = NetworkMapConfig(DUMMY_NOTARY.name, localPort(10000))) - assertFalse(config.nodeConfig.isNetworkMap) - - assertFalse(controller.hasNetworkMap()) - controller.register(config) - assertFalse(controller.hasNetworkMap()) - } - @Test fun `test valid ports`() { assertFalse(controller.isPortValid(NodeController.minPort - 1)) @@ -156,7 +136,7 @@ class NodeControllerTest { @Test fun `dispose node`() { - val config = createConfig(commonName = "MyName") + val config = createConfig(organisation = "MyName") controller.register(config) assertEquals(NodeState.STARTING, config.state) @@ -167,18 +147,17 @@ class NodeControllerTest { } private fun createConfig( - commonName: String = "Unknown", + organisation: String = "Unknown", p2pPort: Int = 0, rpcPort: Int = 0, webPort: Int = 0, h2port: Int = 0, notary: NotaryService? = null, - networkMap: NetworkMapConfig? = null, users: List = listOf(user("guest")) ): NodeConfigWrapper { val nodeConfig = NodeConfig( myLegalName = CordaX500Name( - organisation = commonName, + organisation = organisation, locality = "New York", country = "US" ), @@ -187,7 +166,6 @@ class NodeControllerTest { webAddress = localPort(webPort), h2port = h2port, notary = notary, - networkMapService = networkMap, rpcUsers = users ) return NodeConfigWrapper(baseDir, nodeConfig) diff --git a/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt b/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt index d22e628596..3bd939d4ab 100644 --- a/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt +++ b/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt @@ -20,27 +20,26 @@ import net.corda.finance.contracts.asset.Cash import net.corda.finance.flows.* import net.corda.finance.flows.CashExitFlow.ExitRequest import net.corda.finance.flows.CashIssueAndPaymentFlow.IssueAndPaymentRequest -import net.corda.node.services.FlowPermissions.Companion.startFlowPermission +import net.corda.node.services.Permissions.Companion.startFlow import net.corda.nodeapi.User import net.corda.testing.ALICE import net.corda.testing.BOB -import net.corda.testing.DUMMY_NOTARY import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.PortAllocation import net.corda.testing.driver.driver import java.time.Instant import java.util.* -class ExplorerSimulation(val options: OptionSet) { +class ExplorerSimulation(private val options: OptionSet) { private val user = User("user1", "test", permissions = setOf( - startFlowPermission(), - startFlowPermission() + startFlow(), + startFlow() )) private val manager = User("manager", "test", permissions = setOf( - startFlowPermission(), - startFlowPermission(), - startFlowPermission(), - startFlowPermission()) + startFlow(), + startFlow(), + startFlow(), + startFlow()) ) private lateinit var notaryNode: NodeHandle @@ -67,23 +66,16 @@ class ExplorerSimulation(val options: OptionSet) { val portAllocation = PortAllocation.Incremental(20000) driver(portAllocation = portAllocation, extraCordappPackagesToScan = listOf("net.corda.finance")) { // TODO : Supported flow should be exposed somehow from the node instead of set of ServiceInfo. - val notary = startNotaryNode(DUMMY_NOTARY.name, customOverrides = mapOf("nearestCity" to "Zurich"), validating = false) - val alice = startNode(providedName = ALICE.name, rpcUsers = arrayListOf(user), - customOverrides = mapOf("nearestCity" to "Milan")) - val bob = startNode(providedName = BOB.name, rpcUsers = arrayListOf(user), - customOverrides = mapOf("nearestCity" to "Madrid")) + val alice = startNode(providedName = ALICE.name, rpcUsers = listOf(user)) + val bob = startNode(providedName = BOB.name, rpcUsers = listOf(user)) val ukBankName = CordaX500Name(organisation = "UK Bank Plc", locality = "London", country = "GB") val usaBankName = CordaX500Name(organisation = "USA Bank Corp", locality = "New York", country = "US") - val issuerGBP = startNode(providedName = ukBankName, rpcUsers = arrayListOf(manager), - customOverrides = mapOf( - "issuableCurrencies" to listOf("GBP"), - "nearestCity" to "London")) - val issuerUSD = startNode(providedName = usaBankName, rpcUsers = arrayListOf(manager), - customOverrides = mapOf( - "issuableCurrencies" to listOf("USD"), - "nearestCity" to "New York")) + val issuerGBP = startNode(providedName = ukBankName, rpcUsers = listOf(manager), + customOverrides = mapOf("issuableCurrencies" to listOf("GBP"))) + val issuerUSD = startNode(providedName = usaBankName, rpcUsers = listOf(manager), + customOverrides = mapOf("issuableCurrencies" to listOf("USD"))) - notaryNode = notary.get() + notaryNode = defaultNotaryNode.get() aliceNode = alice.get() bobNode = bob.get() issuerNodeGBP = issuerGBP.get() @@ -127,10 +119,6 @@ class ExplorerSimulation(val options: OptionSet) { bobNode.nodeInfo.legalIdentities.first() to bobRPC, issuerNodeGBP.nodeInfo.legalIdentities.first() to issuerRPCGBP, issuerNodeUSD.nodeInfo.legalIdentities.first() to issuerRPCUSD)) - - listOf(aliceRPC, bobRPC, issuerRPCGBP, issuerRPCUSD).map { - it.waitUntilNetworkReady().getOrThrow() - } } private fun startSimulation(eventGenerator: EventGenerator, maxIterations: Int) { diff --git a/tools/explorer/src/main/kotlin/net/corda/explorer/views/Network.kt b/tools/explorer/src/main/kotlin/net/corda/explorer/views/Network.kt index de6a41aded..8970e7b1e1 100644 --- a/tools/explorer/src/main/kotlin/net/corda/explorer/views/Network.kt +++ b/tools/explorer/src/main/kotlin/net/corda/explorer/views/Network.kt @@ -36,7 +36,6 @@ import net.corda.explorer.model.CordaView import net.corda.finance.utils.CityDatabase import net.corda.finance.utils.ScreenCoordinate import net.corda.finance.utils.WorldMapLocation -import net.corda.nodeapi.internal.ServiceType import tornadofx.* class Network : CordaView() { @@ -45,6 +44,7 @@ class Network : CordaView() { // Inject data. private val myIdentity by observableValue(NetworkIdentityModel::myIdentity) private val notaries by observableList(NetworkIdentityModel::notaryNodes) + private val notaryIdentities by observableList(NetworkIdentityModel::notaries) private val peers by observableList(NetworkIdentityModel::parties) private val transactions by observableList(TransactionDataModel::partiallyResolvedTransactions) var centralPeer: String? = null @@ -103,7 +103,7 @@ class Network : CordaView() { hgap = 5.0 vgap = 5.0 for (identity in identities) { - val isNotary = identity.name.commonName?.let { ServiceType.parse(it).isNotary() } == true + val isNotary = identity.party in notaryIdentities row("${if (isNotary) "Notary " else ""}Public Key :") { copyableLabel(SimpleObjectProperty(identity.owningKey.toBase58String())) } diff --git a/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/NotaryTest.kt b/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/NotaryTest.kt index b96f6f4f40..88b517b9db 100644 --- a/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/NotaryTest.kt +++ b/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/NotaryTest.kt @@ -24,7 +24,7 @@ val dummyNotarisationTest = LoadTest( val issuerServices = MockServices(DUMMY_CASH_ISSUER_KEY) val generateTx = Generator.pickOne(simpleNodes).flatMap { node -> Generator.int().map { - val issueBuilder = DummyContract.generateInitial(it, notary.info.legalIdentities[1], DUMMY_CASH_ISSUER) // TODO notary choice + val issueBuilder = DummyContract.generateInitial(it, notary.info.legalIdentities[0], DUMMY_CASH_ISSUER) // TODO notary choice val issueTx = issuerServices.signInitialTransaction(issueBuilder) val asset = issueTx.tx.outRef(0) val moveBuilder = DummyContract.move(asset, DUMMY_CASH_ISSUER.party) diff --git a/tools/loadtest/src/main/resources/loadtest-reference.conf b/tools/loadtest/src/main/resources/loadtest-reference.conf index 8b03044650..67ea98c2ae 100644 --- a/tools/loadtest/src/main/resources/loadtest-reference.conf +++ b/tools/loadtest/src/main/resources/loadtest-reference.conf @@ -8,4 +8,4 @@ localTunnelStartingPort = 10000 remoteNodeDirectory = "/opt/corda" rpcPort = 10003 remoteSystemdServiceName = "corda" -rpcUser = {username = corda, password = not_blockchain, permissions = []} +rpcUser = {username = corda, password = not_blockchain, permissions = ["ALL"]} diff --git a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt index e36854d54c..78a5269fa1 100644 --- a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt +++ b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt @@ -6,6 +6,7 @@ import net.corda.core.concurrent.CordaFuture import net.corda.core.crypto.random63BitValue import net.corda.core.identity.CordaX500Name import net.corda.core.internal.concurrent.* +import net.corda.core.internal.createDirectories import net.corda.core.internal.div import net.corda.core.transactions.LedgerTransaction import net.corda.core.utilities.NetworkHostAndPort @@ -18,6 +19,7 @@ import net.corda.nodeapi.VerifierApi import net.corda.nodeapi.config.NodeSSLConfiguration import net.corda.nodeapi.config.SSLConfiguration import net.corda.testing.driver.* +import net.corda.testing.node.NotarySpec 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 @@ -76,6 +78,7 @@ fun verifierDriver( useTestClock: Boolean = false, startNodesInProcess: Boolean = false, extraCordappPackagesToScan: List = emptyList(), + notarySpecs: List = emptyList(), dsl: VerifierExposedDSLInterface.() -> A ) = genericDriver( driverDsl = VerifierDriverDSL( @@ -87,7 +90,8 @@ fun verifierDriver( useTestClock = useTestClock, isDebug = isDebug, startNodesInProcess = startNodesInProcess, - extraCordappPackagesToScan = extraCordappPackagesToScan + extraCordappPackagesToScan = extraCordappPackagesToScan, + notarySpecs = notarySpecs ) ), coerce = { it }, @@ -248,7 +252,7 @@ data class VerifierDriverDSL( val jdwpPort = if (driverDSL.isDebug) driverDSL.debugPortAllocation.nextPort() else null val processFuture = driverDSL.executorService.fork { val verifierName = CordaX500Name(organisation = "Verifier$id", locality = "London", country = "GB") - val baseDirectory = driverDSL.driverDirectory / verifierName.organisation + val baseDirectory = (driverDSL.driverDirectory / verifierName.organisation).createDirectories() val config = createConfiguration(baseDirectory, address) val configFilename = "verifier.conf" writeConfig(baseDirectory, configFilename, config) diff --git a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierTests.kt b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierTests.kt index dc228fd6f6..852caaecda 100644 --- a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierTests.kt +++ b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierTests.kt @@ -15,12 +15,12 @@ import net.corda.node.services.config.VerifierType import net.corda.testing.ALICE import net.corda.testing.ALICE_NAME import net.corda.testing.DUMMY_NOTARY -import net.corda.testing.DUMMY_NOTARY_SERVICE_NAME +import net.corda.testing.node.NotarySpec import org.junit.Test import java.util.* import java.util.concurrent.atomic.AtomicInteger -import kotlin.test.assertTrue import kotlin.test.assertNotNull +import kotlin.test.assertTrue class VerifierTests { private fun generateTransactions(number: Int): List { @@ -129,17 +129,15 @@ class VerifierTests { @Test fun `single verifier works with a node`() { - verifierDriver(extraCordappPackagesToScan = listOf("net.corda.finance.contracts")) { - val aliceFuture = startNode(providedName = ALICE.name) - val notaryFuture = startNotaryNode(DUMMY_NOTARY.name, verifierType = VerifierType.OutOfProcess) - val aliceNode = aliceFuture.get() - val notaryNode = notaryFuture.get() + verifierDriver( + extraCordappPackagesToScan = listOf("net.corda.finance.contracts"), + notarySpecs = listOf(NotarySpec(DUMMY_NOTARY.name, verifierType = VerifierType.OutOfProcess)) + ) { + val aliceNode = startNode(providedName = ALICE.name).getOrThrow() + val notaryNode = defaultNotaryNode.getOrThrow() val alice = aliceNode.rpc.wellKnownPartyFromX500Name(ALICE_NAME)!! - val notary = notaryNode.rpc.notaryPartyFromX500Name(DUMMY_NOTARY_SERVICE_NAME)!! startVerifier(notaryNode) - notaryNode.pollUntilKnowsAbout(aliceNode).getOrThrow() - aliceNode.pollUntilKnowsAbout(notaryNode).getOrThrow() - aliceNode.rpc.startFlow(::CashIssueFlow, 10.DOLLARS, OpaqueBytes.of(0), notary).returnValue.get() + aliceNode.rpc.startFlow(::CashIssueFlow, 10.DOLLARS, OpaqueBytes.of(0), defaultNotaryIdentity).returnValue.get() notaryNode.waitUntilNumberOfVerifiers(1) for (i in 1..10) { val cashFlowResult = aliceNode.rpc.startFlow(::CashPaymentFlow, 10.DOLLARS, alice).returnValue.get() diff --git a/verifier/src/main/kotlin/net/corda/verifier/Verifier.kt b/verifier/src/main/kotlin/net/corda/verifier/Verifier.kt index 22ff4e9f60..ccc9a967db 100644 --- a/verifier/src/main/kotlin/net/corda/verifier/Verifier.kt +++ b/verifier/src/main/kotlin/net/corda/verifier/Verifier.kt @@ -18,8 +18,11 @@ import net.corda.nodeapi.config.NodeSSLConfiguration import net.corda.nodeapi.config.getValue import net.corda.nodeapi.internal.addShutdownHook import net.corda.nodeapi.internal.serialization.* +import net.corda.nodeapi.internal.serialization.amqp.AbstractAMQPSerializationScheme import net.corda.nodeapi.internal.serialization.amqp.AmqpHeaderV1_0 import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory +import net.corda.nodeapi.internal.serialization.kryo.AbstractKryoSerializationScheme +import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1 import org.apache.activemq.artemis.api.core.client.ActiveMQClient import java.nio.file.Path import java.nio.file.Paths diff --git a/verify-enclave/src/main/kotlin/com/r3/enclaves/txverify/KryoVerifierSerializationScheme.kt b/verify-enclave/src/main/kotlin/com/r3/enclaves/txverify/KryoVerifierSerializationScheme.kt index b81352281d..455be7ae40 100644 --- a/verify-enclave/src/main/kotlin/com/r3/enclaves/txverify/KryoVerifierSerializationScheme.kt +++ b/verify-enclave/src/main/kotlin/com/r3/enclaves/txverify/KryoVerifierSerializationScheme.kt @@ -3,10 +3,10 @@ package com.r3.enclaves.txverify import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationDefaults import net.corda.core.utilities.ByteSequence -import net.corda.nodeapi.internal.serialization.AbstractKryoSerializationScheme import net.corda.nodeapi.internal.serialization.KRYO_P2P_CONTEXT -import net.corda.nodeapi.internal.serialization.KryoHeaderV0_1 import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl +import net.corda.nodeapi.internal.serialization.kryo.AbstractKryoSerializationScheme +import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1 @Suppress("UNUSED") private class KryoVerifierSerializationScheme : AbstractKryoSerializationScheme() { diff --git a/webserver/src/integration-test/kotlin/net/corda/webserver/WebserverDriverTests.kt b/webserver/src/integration-test/kotlin/net/corda/webserver/WebserverDriverTests.kt index cd4f35577d..ee85cef6fd 100644 --- a/webserver/src/integration-test/kotlin/net/corda/webserver/WebserverDriverTests.kt +++ b/webserver/src/integration-test/kotlin/net/corda/webserver/WebserverDriverTests.kt @@ -11,7 +11,7 @@ import org.junit.Test import java.util.concurrent.Executors import java.util.concurrent.ScheduledExecutorService -class DriverTests { +class WebserverDriverTests { companion object { val executorService: ScheduledExecutorService = Executors.newScheduledThreadPool(2)