Added a method to NodeHandle to simplify using RPC in the Driver

This commit is contained in:
Shams Asari
2017-01-06 11:33:00 +00:00
parent 0867a05ad7
commit 59456cb6b1
17 changed files with 146 additions and 148 deletions

View File

@ -12,28 +12,21 @@ import net.corda.node.driver.DriverBasedTest
import net.corda.node.driver.NodeHandle import net.corda.node.driver.NodeHandle
import net.corda.node.driver.driver import net.corda.node.driver.driver
import net.corda.node.services.User import net.corda.node.services.User
import net.corda.node.services.config.configureTestSSL
import net.corda.node.services.messaging.ArtemisMessagingComponent.Companion.toHostAndPort
import net.corda.node.services.messaging.CordaRPCClient import net.corda.node.services.messaging.CordaRPCClient
import net.corda.node.services.startFlowPermission import net.corda.node.services.startFlowPermission
import net.corda.node.services.transactions.ValidatingNotaryService import net.corda.node.services.transactions.ValidatingNotaryService
import org.apache.activemq.artemis.api.core.ActiveMQSecurityException import org.apache.activemq.artemis.api.core.ActiveMQSecurityException
import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.junit.After
import org.junit.Before
import org.junit.Test import org.junit.Test
import java.util.concurrent.CountDownLatch
import kotlin.concurrent.thread
class CordaRPCClientTest : DriverBasedTest() { class CordaRPCClientTest : DriverBasedTest() {
private val rpcUser = User("user1", "test", permissions = setOf(startFlowPermission<CashFlow>())) private val rpcUser = User("user1", "test", permissions = setOf(startFlowPermission<CashFlow>()))
private lateinit var node: NodeHandle
private lateinit var client: CordaRPCClient private lateinit var client: CordaRPCClient
private lateinit var driverInfo: NodeHandle
override fun setup() = driver(isDebug = true) { override fun setup() = driver(isDebug = true) {
driverInfo = startNode(rpcUsers = listOf(rpcUser), advertisedServices = setOf(ServiceInfo(ValidatingNotaryService.type))).getOrThrow() node = startNode(rpcUsers = listOf(rpcUser), advertisedServices = setOf(ServiceInfo(ValidatingNotaryService.type))).getOrThrow()
client = CordaRPCClient(toHostAndPort(driverInfo.nodeInfo.address), configureTestSSL()) client = node.rpcClientToNode()
runTest() runTest()
} }
@ -63,7 +56,9 @@ class CordaRPCClientTest : DriverBasedTest() {
println("Creating proxy") println("Creating proxy")
val proxy = client.proxy() val proxy = client.proxy()
println("Starting flow") println("Starting flow")
val flowHandle = proxy.startFlow(::CashFlow, CashCommand.IssueCash(20.DOLLARS, OpaqueBytes.of(0), driverInfo.nodeInfo.legalIdentity, driverInfo.nodeInfo.legalIdentity)) val flowHandle = proxy.startFlow(
::CashFlow,
CashCommand.IssueCash(20.DOLLARS, OpaqueBytes.of(0), node.nodeInfo.legalIdentity, node.nodeInfo.legalIdentity))
println("Started flow, waiting on result") println("Started flow, waiting on result")
flowHandle.progress.subscribe { flowHandle.progress.subscribe {
println("PROGRESS $it") println("PROGRESS $it")

View File

@ -3,13 +3,12 @@
Client RPC API tutorial Client RPC API tutorial
======================= =======================
In this tutorial we will build a simple command line utility that In this tutorial we will build a simple command line utility that connects to a node, creates some Cash transactions and
connects to a node, creates some Cash transactions and meanwhile dumps meanwhile dumps the transaction graph to the standard output. We will then put some simple visualisation on top. For an
the transaction graph to the standard output. We will then put some explanation on how the RPC works see :doc:`clientrpc`.
simple visualisation on top. For an explanation on how the RPC works
see :doc:`clientrpc`.
We start off by connecting to the node itself. For the purposes of the tutorial we will use the Driver to start up a notary and a node that issues/exits and moves Cash around for herself. To authenticate we will use the certificates of the nodes directly. We start off by connecting to the node itself. For the purposes of the tutorial we will use the Driver to start up a notary
and a node that issues/exits and moves Cash around for herself.
Note how we configure the node to create a user that has permission to start the CashFlow. Note how we configure the node to create a user that has permission to start the CashFlow.
@ -25,14 +24,16 @@ Now we can connect to the node itself using a valid RPC login. We login using th
:start-after: START 2 :start-after: START 2
:end-before: END 2 :end-before: END 2
We start generating transactions in a different thread (``generateTransactions`` to be defined later) using ``proxy``, which exposes the full RPC interface of the node: We start generating transactions in a different thread (``generateTransactions`` to be defined later) using ``proxy``,
which exposes the full RPC interface of the node:
.. literalinclude:: ../../node/src/main/kotlin/net/corda/node/services/messaging/CordaRPCOps.kt .. literalinclude:: ../../node/src/main/kotlin/net/corda/node/services/messaging/CordaRPCOps.kt
:language: kotlin :language: kotlin
:start-after: interface CordaRPCOps :start-after: interface CordaRPCOps
:end-before: } :end-before: }
.. warning:: This API is evolving and will continue to grow as new functionality and features added to Corda are made available to RPC clients. .. warning:: This API is evolving and will continue to grow as new functionality and features added to Corda are made
available to RPC clients.
The one we need in order to dump the transaction graph is ``verifiedTransactions``. The type signature tells us that the The one we need in order to dump the transaction graph is ``verifiedTransactions``. The type signature tells us that the
RPC will return a list of transactions and an Observable stream. This is a general pattern, we query some data and the RPC will return a list of transactions and an Observable stream. This is a general pattern, we query some data and the
@ -65,9 +66,12 @@ We utilise several RPC functions here to query things like the notaries in the n
Then in a loop we generate randomly either an Issue, a Pay or an Exit transaction. Then in a loop we generate randomly either an Issue, a Pay or an Exit transaction.
The RPC we need to initiate a Cash transaction is ``startFlowDynamic`` which may start an arbitrary flow, given sufficient permissions to do so. We won't use this function directly, but rather a type-safe wrapper around it ``startFlow`` that type-checks the arguments for us. The RPC we need to initiate a Cash transaction is ``startFlowDynamic`` which may start an arbitrary flow, given sufficient
permissions to do so. We won't use this function directly, but rather a type-safe wrapper around it ``startFlow`` that
type-checks the arguments for us.
Finally we have everything in place: we start a couple of nodes, connect to them, and start creating transactions while listening on successfully created ones, which are dumped to the console. We just need to run it!: Finally we have everything in place: we start a couple of nodes, connect to them, and start creating transactions while
listening on successfully created ones, which are dumped to the console. We just need to run it!:
.. code-block:: text .. code-block:: text

View File

@ -1,8 +1,10 @@
package net.corda.docs package net.corda.docs
import com.google.common.util.concurrent.Futures
import net.corda.contracts.asset.Cash import net.corda.contracts.asset.Cash
import net.corda.core.contracts.DOLLARS import net.corda.core.contracts.DOLLARS
import net.corda.core.contracts.issuedBy import net.corda.core.contracts.issuedBy
import net.corda.core.getOrThrow
import net.corda.core.messaging.startFlow import net.corda.core.messaging.startFlow
import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.ServiceInfo
import net.corda.core.node.services.Vault import net.corda.core.node.services.Vault
@ -12,9 +14,6 @@ import net.corda.flows.CashFlow
import net.corda.flows.CashFlowResult import net.corda.flows.CashFlowResult
import net.corda.node.driver.driver import net.corda.node.driver.driver
import net.corda.node.services.User import net.corda.node.services.User
import net.corda.node.services.config.configureTestSSL
import net.corda.node.services.messaging.ArtemisMessagingComponent
import net.corda.node.services.messaging.CordaRPCClient
import net.corda.node.services.startFlowPermission import net.corda.node.services.startFlowPermission
import net.corda.node.services.transactions.ValidatingNotaryService import net.corda.node.services.transactions.ValidatingNotaryService
import net.corda.testing.expect import net.corda.testing.expect
@ -26,31 +25,25 @@ import kotlin.concurrent.thread
import kotlin.test.assertEquals import kotlin.test.assertEquals
class IntegrationTestingTutorial { class IntegrationTestingTutorial {
@Test @Test
fun aliceBobCashExchangeExample() { fun `alice bob cash exchange example`() {
// START 1 // START 1
driver { driver {
val testUser = User("testUser", "testPassword", permissions = setOf(startFlowPermission<CashFlow>())) val testUser = User("testUser", "testPassword", permissions = setOf(startFlowPermission<CashFlow>()))
val aliceFuture = startNode("Alice", rpcUsers = listOf(testUser)) val (alice, bob, notary) = Futures.allAsList(
val bobFuture = startNode("Bob", rpcUsers = listOf(testUser)) startNode("Alice", rpcUsers = listOf(testUser)),
val notaryFuture = startNode("Notary", advertisedServices = setOf(ServiceInfo(ValidatingNotaryService.type))) startNode("Bob", rpcUsers = listOf(testUser)),
val alice = aliceFuture.get() startNode("Notary", advertisedServices = setOf(ServiceInfo(ValidatingNotaryService.type)))
val bob = bobFuture.get() ).getOrThrow()
val notary = notaryFuture.get()
// END 1 // END 1
// START 2 // START 2
val aliceClient = CordaRPCClient( val aliceClient = alice.rpcClientToNode()
host = ArtemisMessagingComponent.toHostAndPort(alice.nodeInfo.address),
config = configureTestSSL()
)
aliceClient.start("testUser", "testPassword") aliceClient.start("testUser", "testPassword")
val aliceProxy = aliceClient.proxy() val aliceProxy = aliceClient.proxy()
val bobClient = CordaRPCClient( val bobClient = bob.rpcClientToNode()
host = ArtemisMessagingComponent.toHostAndPort(bob.nodeInfo.address),
config = configureTestSSL()
)
bobClient.start("testUser", "testPassword") bobClient.start("testUser", "testPassword")
val bobProxy = bobClient.proxy() val bobProxy = bobClient.proxy()
// END 2 // END 2

View File

@ -6,7 +6,6 @@ import net.corda.core.contracts.Amount
import net.corda.core.contracts.Issued import net.corda.core.contracts.Issued
import net.corda.core.contracts.PartyAndReference import net.corda.core.contracts.PartyAndReference
import net.corda.core.contracts.USD import net.corda.core.contracts.USD
import net.corda.core.div
import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.startFlow import net.corda.core.messaging.startFlow
import net.corda.core.node.CordaPluginRegistry import net.corda.core.node.CordaPluginRegistry
@ -17,9 +16,6 @@ import net.corda.flows.CashCommand
import net.corda.flows.CashFlow import net.corda.flows.CashFlow
import net.corda.node.driver.driver import net.corda.node.driver.driver
import net.corda.node.services.User import net.corda.node.services.User
import net.corda.node.services.config.FullNodeConfiguration
import net.corda.node.services.config.NodeSSLConfiguration
import net.corda.node.services.messaging.CordaRPCClient
import net.corda.node.services.startFlowPermission import net.corda.node.services.startFlowPermission
import net.corda.node.services.transactions.ValidatingNotaryService import net.corda.node.services.transactions.ValidatingNotaryService
import org.graphstream.graph.Edge import org.graphstream.graph.Edge
@ -41,9 +37,7 @@ enum class PrintOrVisualise {
} }
fun main(args: Array<String>) { fun main(args: Array<String>) {
if (args.size < 1) { require(args.isNotEmpty()) { "Usage: <binary> [Print|Visualise]" }
throw IllegalArgumentException("Usage: <binary> [Print|Visualise]")
}
val printOrVisualise = PrintOrVisualise.valueOf(args[0]) val printOrVisualise = PrintOrVisualise.valueOf(args[0])
val baseDirectory = Paths.get("build/rpc-api-tutorial") val baseDirectory = Paths.get("build/rpc-api-tutorial")
@ -52,15 +46,10 @@ fun main(args: Array<String>) {
driver(driverDirectory = baseDirectory) { driver(driverDirectory = baseDirectory) {
startNode("Notary", advertisedServices = setOf(ServiceInfo(ValidatingNotaryService.type))) startNode("Notary", advertisedServices = setOf(ServiceInfo(ValidatingNotaryService.type)))
val node = startNode("Alice", rpcUsers = listOf(user)).get() val node = startNode("Alice", rpcUsers = listOf(user)).get()
val sslConfig = object : NodeSSLConfiguration {
override val certificatesPath = baseDirectory / "Alice" / "certificates"
override val keyStorePassword = "cordacadevpass"
override val trustStorePassword = "trustpass"
}
// END 1 // END 1
// START 2 // START 2
val client = CordaRPCClient(FullNodeConfiguration(node.config).artemisAddress, sslConfig) val client = node.rpcClientToNode()
client.start("user", "password") client.start("user", "password")
val proxy = client.proxy() val proxy = client.proxy()

View File

@ -35,7 +35,7 @@ notary directly, so there's no need to pass in the test ``User``.
The ``startNode`` function returns a future that completes once the The ``startNode`` function returns a future that completes once the
node is fully started. This allows starting of the nodes to be node is fully started. This allows starting of the nodes to be
parallel. We wait on these futures as we need the information parallel. We wait on these futures as we need the information
returned; their respective ``NodeInfo`` s. returned; their respective ``NodeHandles`` s.
.. literalinclude:: example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt .. literalinclude:: example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt
:language: kotlin :language: kotlin
@ -46,9 +46,6 @@ Next we connect to Alice and Bob respectively from the test process
using the test user we created. Then we establish RPC links that allow using the test user we created. Then we establish RPC links that allow
us to start flows and query state. us to start flows and query state.
Note that Driver nodes start up with test server certificates, so
it's enough to pass in ``configureTestSSL()`` for the clients.
.. literalinclude:: example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt .. literalinclude:: example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt
:language: kotlin :language: kotlin
:start-after: START 3 :start-after: START 3

View File

@ -53,7 +53,7 @@ class DriverTests {
@Test @Test
fun randomFreePortAllocationWorks() { fun randomFreePortAllocationWorks() {
val nodeInfo = driver(portAllocation = PortAllocation.RandomFree()) { val nodeInfo = driver(portAllocation = PortAllocation.RandomFree) {
val nodeInfo = startNode("NoService") val nodeInfo = startNode("NoService")
nodeMustBeUp(nodeInfo.getOrThrow().nodeInfo) nodeMustBeUp(nodeInfo.getOrThrow().nodeInfo)
nodeInfo.getOrThrow() nodeInfo.getOrThrow()

View File

@ -1,6 +1,7 @@
package net.corda.node.services package net.corda.node.services
import net.corda.core.bufferUntilSubscribed import net.corda.core.bufferUntilSubscribed
import net.corda.core.contracts.Amount
import net.corda.core.contracts.POUNDS import net.corda.core.contracts.POUNDS
import net.corda.core.contracts.issuedBy import net.corda.core.contracts.issuedBy
import net.corda.core.crypto.Party import net.corda.core.crypto.Party
@ -15,9 +16,6 @@ import net.corda.flows.CashFlowResult
import net.corda.node.driver.DriverBasedTest import net.corda.node.driver.DriverBasedTest
import net.corda.node.driver.NodeHandle import net.corda.node.driver.NodeHandle
import net.corda.node.driver.driver import net.corda.node.driver.driver
import net.corda.node.services.config.configureTestSSL
import net.corda.node.services.messaging.ArtemisMessagingComponent
import net.corda.node.services.messaging.CordaRPCClient
import net.corda.node.services.transactions.RaftValidatingNotaryService import net.corda.node.services.transactions.RaftValidatingNotaryService
import net.corda.testing.expect import net.corda.testing.expect
import net.corda.testing.expectEvents import net.corda.testing.expectEvents
@ -28,14 +26,14 @@ import java.util.*
import kotlin.test.assertEquals import kotlin.test.assertEquals
class DistributedServiceTests : DriverBasedTest() { class DistributedServiceTests : DriverBasedTest() {
lateinit var alice: NodeInfo lateinit var alice: NodeHandle
lateinit var notaries: List<NodeHandle> lateinit var notaries: List<NodeHandle>
lateinit var aliceProxy: CordaRPCOps lateinit var aliceProxy: CordaRPCOps
lateinit var raftNotaryIdentity: Party lateinit var raftNotaryIdentity: Party
lateinit var notaryStateMachines: Observable<Pair<NodeInfo, StateMachineUpdate>> lateinit var notaryStateMachines: Observable<Pair<NodeInfo, StateMachineUpdate>>
override fun setup() = driver { override fun setup() = driver {
// Start Alice and 3 raft notaries // Start Alice and 3 notaries in a RAFT cluster
val clusterSize = 3 val clusterSize = 3
val testUser = User("test", "test", permissions = setOf(startFlowPermission<CashFlow>())) val testUser = User("test", "test", permissions = setOf(startFlowPermission<CashFlow>()))
val aliceFuture = startNode("Alice", rpcUsers = listOf(testUser)) val aliceFuture = startNode("Alice", rpcUsers = listOf(testUser))
@ -46,7 +44,7 @@ class DistributedServiceTests : DriverBasedTest() {
type = RaftValidatingNotaryService.type type = RaftValidatingNotaryService.type
) )
alice = aliceFuture.get().nodeInfo alice = aliceFuture.get()
val (notaryIdentity, notaryNodes) = notariesFuture.get() val (notaryIdentity, notaryNodes) = notariesFuture.get()
raftNotaryIdentity = notaryIdentity raftNotaryIdentity = notaryIdentity
notaries = notaryNodes notaries = notaryNodes
@ -55,14 +53,14 @@ class DistributedServiceTests : DriverBasedTest() {
assertEquals(notaries.size, notaries.map { it.nodeInfo.legalIdentity }.toSet().size) assertEquals(notaries.size, notaries.map { it.nodeInfo.legalIdentity }.toSet().size)
// Connect to Alice and the notaries // Connect to Alice and the notaries
fun connectRpc(node: NodeInfo): CordaRPCOps { fun connectRpc(node: NodeHandle): CordaRPCOps {
val client = CordaRPCClient(ArtemisMessagingComponent.toHostAndPort(node.address), configureTestSSL()) val client = node.rpcClientToNode()
client.start("test", "test") client.start("test", "test")
return client.proxy() return client.proxy()
} }
aliceProxy = connectRpc(alice) aliceProxy = connectRpc(alice)
val notaryProxies = notaries.map { connectRpc(it.nodeInfo) } val rpcClientsToNotaries = notaries.map(::connectRpc)
notaryStateMachines = Observable.from(notaryProxies.map { proxy -> notaryStateMachines = Observable.from(rpcClientsToNotaries.map { proxy ->
proxy.stateMachinesAndUpdates().second.map { Pair(proxy.nodeIdentity(), it) } proxy.stateMachinesAndUpdates().second.map { Pair(proxy.nodeIdentity(), it) }
}).flatMap { it.onErrorResumeNext(Observable.empty()) }.bufferUntilSubscribed() }).flatMap { it.onErrorResumeNext(Observable.empty()) }.bufferUntilSubscribed()
@ -74,11 +72,10 @@ class DistributedServiceTests : DriverBasedTest() {
@Test @Test
fun `requests are distributed evenly amongst the nodes`() { fun `requests are distributed evenly amongst the nodes`() {
// Issue 100 pounds, then pay ourselves 50x2 pounds // Issue 100 pounds, then pay ourselves 50x2 pounds
val issueHandle = aliceProxy.startFlow(::CashFlow, CashCommand.IssueCash(100.POUNDS, OpaqueBytes.of(0), alice.legalIdentity, raftNotaryIdentity)) issueCash(100.POUNDS)
require(issueHandle.returnValue.toBlocking().first() is CashFlowResult.Success)
for (i in 1..50) { for (i in 1..50) {
val payHandle = aliceProxy.startFlow(::CashFlow, CashCommand.PayCash(2.POUNDS.issuedBy(alice.legalIdentity.ref(0)), alice.legalIdentity)) paySelf(2.POUNDS)
require(payHandle.returnValue.toBlocking().first() is CashFlowResult.Success)
} }
// The state machines added in the notaries should map one-to-one to notarisation requests // The state machines added in the notaries should map one-to-one to notarisation requests
@ -104,11 +101,10 @@ class DistributedServiceTests : DriverBasedTest() {
@Test @Test
fun `cluster survives if a notary is killed`() { fun `cluster survives if a notary is killed`() {
// Issue 100 pounds, then pay ourselves 10x5 pounds // Issue 100 pounds, then pay ourselves 10x5 pounds
val issueHandle = aliceProxy.startFlow(::CashFlow, CashCommand.IssueCash(100.POUNDS, OpaqueBytes.of(0), alice.legalIdentity, raftNotaryIdentity)) issueCash(100.POUNDS)
require(issueHandle.returnValue.toBlocking().first() is CashFlowResult.Success)
for (i in 1..10) { for (i in 1..10) {
val payHandle = aliceProxy.startFlow(::CashFlow, CashCommand.PayCash(5.POUNDS.issuedBy(alice.legalIdentity.ref(0)), alice.legalIdentity)) paySelf(5.POUNDS)
require(payHandle.returnValue.toBlocking().first() is CashFlowResult.Success)
} }
// Now kill a notary // Now kill a notary
@ -119,8 +115,7 @@ class DistributedServiceTests : DriverBasedTest() {
// Pay ourselves another 20x5 pounds // Pay ourselves another 20x5 pounds
for (i in 1..20) { for (i in 1..20) {
val payHandle = aliceProxy.startFlow(::CashFlow, CashCommand.PayCash(5.POUNDS.issuedBy(alice.legalIdentity.ref(0)), alice.legalIdentity)) paySelf(5.POUNDS)
require(payHandle.returnValue.toBlocking().first() is CashFlowResult.Success)
} }
val notarisationsPerNotary = HashMap<Party, Int>() val notarisationsPerNotary = HashMap<Party, Int>()
@ -137,4 +132,18 @@ class DistributedServiceTests : DriverBasedTest() {
println("Notarisation distribution: $notarisationsPerNotary") println("Notarisation distribution: $notarisationsPerNotary")
require(notarisationsPerNotary.size == 3) require(notarisationsPerNotary.size == 3)
} }
private fun issueCash(amount: Amount<Currency>) {
val issueHandle = aliceProxy.startFlow(
::CashFlow,
CashCommand.IssueCash(amount, OpaqueBytes.of(0), alice.nodeInfo.legalIdentity, raftNotaryIdentity))
require(issueHandle.returnValue.toBlocking().first() is CashFlowResult.Success)
}
private fun paySelf(amount: Amount<Currency>) {
val payHandle = aliceProxy.startFlow(
::CashFlow,
CashCommand.PayCash(amount.issuedBy(alice.nodeInfo.legalIdentity.ref(0)), alice.nodeInfo.legalIdentity))
require(payHandle.returnValue.toBlocking().first() is CashFlowResult.Success)
}
} }

View File

@ -19,6 +19,7 @@ import net.corda.core.utilities.loggerFor
import net.corda.node.services.User import net.corda.node.services.User
import net.corda.node.services.config.ConfigHelper import net.corda.node.services.config.ConfigHelper
import net.corda.node.services.config.FullNodeConfiguration import net.corda.node.services.config.FullNodeConfiguration
import net.corda.node.services.messaging.CordaRPCClient
import net.corda.node.services.messaging.NodeMessagingClient import net.corda.node.services.messaging.NodeMessagingClient
import net.corda.node.services.network.NetworkMapService import net.corda.node.services.network.NetworkMapService
import net.corda.node.services.transactions.RaftValidatingNotaryService import net.corda.node.services.transactions.RaftValidatingNotaryService
@ -93,9 +94,11 @@ interface DriverDSLInternalInterface : DriverDSLExposedInterface {
data class NodeHandle( data class NodeHandle(
val nodeInfo: NodeInfo, val nodeInfo: NodeInfo,
val config: Config, val configuration: FullNodeConfiguration,
val process: Process val process: Process
) ) {
fun rpcClientToNode(): CordaRPCClient = CordaRPCClient(configuration.artemisAddress, configuration)
}
sealed class PortAllocation { sealed class PortAllocation {
abstract fun nextPort(): Int abstract fun nextPort(): Int
@ -106,7 +109,7 @@ sealed class PortAllocation {
override fun nextPort() = portCounter.andIncrement override fun nextPort() = portCounter.andIncrement
} }
class RandomFree() : PortAllocation() { object RandomFree : PortAllocation() {
override fun nextPort(): Int { override fun nextPort(): Int {
return ServerSocket().use { return ServerSocket().use {
it.bind(InetSocketAddress(0)) it.bind(InetSocketAddress(0))
@ -364,16 +367,16 @@ open class DriverDSL(
} }
) + customOverrides ) + customOverrides
val config = ConfigHelper.loadConfig( val configuration = FullNodeConfiguration(ConfigHelper.loadConfig(
baseDirectoryPath = baseDirectory, baseDirectoryPath = baseDirectory,
allowMissingConfig = true, allowMissingConfig = true,
configOverrides = configOverrides configOverrides = configOverrides
) ))
val startNode = startNode(executorService, FullNodeConfiguration(config), quasarJarPath, debugPort) val startNode = startNode(executorService, configuration, quasarJarPath, debugPort)
registerProcess(startNode) registerProcess(startNode)
return startNode.map { return startNode.map {
NodeHandle(queryNodeInfo(apiAddress)!!, config, it) NodeHandle(queryNodeInfo(apiAddress)!!, configuration, it)
} }
} }

View File

@ -61,6 +61,8 @@ class FullNodeConfiguration(val config: Config) : NodeConfiguration {
val useHTTPS: Boolean by config val useHTTPS: Boolean by config
val artemisAddress: HostAndPort by config val artemisAddress: HostAndPort by config
val webAddress: HostAndPort by config val webAddress: HostAndPort by config
// TODO This field is slightly redundant as artemisAddress is sufficient to hold the address of the node's MQ broker.
// Instead this should be a Boolean indicating whether that broker is an internal one started by the node or an external one
val messagingServerAddress: HostAndPort? by config.getOrElse { null } val messagingServerAddress: HostAndPort? by config.getOrElse { null }
val extraAdvertisedServiceIds: String by config val extraAdvertisedServiceIds: String by config
val useTestClock: Boolean by config.getOrElse { false } val useTestClock: Boolean by config.getOrElse { false }

View File

@ -1,31 +1,30 @@
package net.corda.attachmentdemo package net.corda.attachmentdemo
import com.google.common.util.concurrent.Futures
import net.corda.core.getOrThrow import net.corda.core.getOrThrow
import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.ServiceInfo
import net.corda.node.driver.driver import net.corda.node.driver.driver
import net.corda.node.services.transactions.SimpleNotaryService import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.testing.getHostAndPort
import org.junit.Test import org.junit.Test
import kotlin.concurrent.thread import kotlin.concurrent.thread
class AttachmentDemoTest { class AttachmentDemoTest {
@Test fun `runs attachment demo`() { @Test fun `runs attachment demo`() {
driver(dsl = { driver(dsl = {
val (nodeA, nodeB) = Futures.allAsList(
startNode("Bank A"),
startNode("Bank B"),
startNode("Notary", setOf(ServiceInfo(SimpleNotaryService.Companion.type))) startNode("Notary", setOf(ServiceInfo(SimpleNotaryService.Companion.type)))
val nodeAFuture = startNode("Bank A") ).getOrThrow()
val nodeBApiAddr = startNode("Bank B").getOrThrow().config.getHostAndPort("webAddress")
val nodeA = nodeAFuture.getOrThrow()
val nodeAApiAddr = nodeA.config.getHostAndPort("webAddress")
var recipientReturn: Boolean? = null var recipientReturn: Boolean? = null
var senderReturn: Boolean? = null var senderReturn: Boolean? = null
val recipientThread = thread { val recipientThread = thread {
recipientReturn = AttachmentDemoClientApi(nodeAApiAddr).runRecipient() recipientReturn = AttachmentDemoClientApi(nodeA.configuration.webAddress).runRecipient()
} }
val senderThread = thread { val senderThread = thread {
val counterpartyKey = nodeA.nodeInfo.legalIdentity.owningKey.toBase58String() val counterpartyKey = nodeA.nodeInfo.legalIdentity.owningKey.toBase58String()
senderReturn = AttachmentDemoClientApi(nodeBApiAddr).runSender(counterpartyKey) senderReturn = AttachmentDemoClientApi(nodeB.configuration.webAddress).runSender(counterpartyKey)
} }
recipientThread.join() recipientThread.join()
senderThread.join() senderThread.join()

View File

@ -1,19 +1,23 @@
package net.corda.bank package net.corda.bank
import com.google.common.util.concurrent.Futures
import net.corda.bank.api.BankOfCordaClientApi import net.corda.bank.api.BankOfCordaClientApi
import net.corda.bank.api.BankOfCordaWebApi.IssueRequestParams import net.corda.bank.api.BankOfCordaWebApi.IssueRequestParams
import net.corda.core.getOrThrow
import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.ServiceInfo
import net.corda.node.driver.driver import net.corda.node.driver.driver
import net.corda.node.services.transactions.SimpleNotaryService import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.testing.getHostAndPort
import org.junit.Test import org.junit.Test
class BankOfCordaHttpAPITest { class BankOfCordaHttpAPITest {
@Test fun `test issuer flow via Http`() { @Test
fun `issuer flow via Http`() {
driver(dsl = { driver(dsl = {
val nodeBankOfCorda = startNode("BankOfCorda", setOf(ServiceInfo(SimpleNotaryService.type))).get() val (nodeBankOfCorda) = Futures.allAsList(
val nodeBankOfCordaApiAddr = nodeBankOfCorda.config.getHostAndPort("webAddress") startNode("BankOfCorda", setOf(ServiceInfo(SimpleNotaryService.type))),
startNode("BigCorporation").get() startNode("BigCorporation")
).getOrThrow()
val nodeBankOfCordaApiAddr = nodeBankOfCorda.configuration.webAddress
assert(BankOfCordaClientApi(nodeBankOfCordaApiAddr).requestWebIssue(IssueRequestParams(1000, "USD", "BigCorporation", "1", "BankOfCorda"))) assert(BankOfCordaClientApi(nodeBankOfCordaApiAddr).requestWebIssue(IssueRequestParams(1000, "USD", "BigCorporation", "1", "BankOfCorda")))
}, isDebug = true) }, isDebug = true)
} }

View File

@ -1,38 +1,40 @@
package net.corda.bank package net.corda.bank
import com.google.common.util.concurrent.Futures
import net.corda.core.contracts.DOLLARS import net.corda.core.contracts.DOLLARS
import net.corda.core.getOrThrow
import net.corda.core.messaging.startFlow import net.corda.core.messaging.startFlow
import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.ServiceInfo
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.flows.IssuerFlow.IssuanceRequester import net.corda.flows.IssuerFlow.IssuanceRequester
import net.corda.node.driver.driver import net.corda.node.driver.driver
import net.corda.node.services.User import net.corda.node.services.User
import net.corda.node.services.config.configureTestSSL
import net.corda.node.services.messaging.CordaRPCClient
import net.corda.node.services.startFlowPermission import net.corda.node.services.startFlowPermission
import net.corda.node.services.transactions.SimpleNotaryService import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.testing.* import net.corda.testing.BOC_PARTY_REF
import net.corda.testing.expect
import net.corda.testing.expectEvents
import net.corda.testing.sequence
import org.junit.Test import org.junit.Test
import kotlin.test.assertTrue import kotlin.test.assertTrue
class BankOfCordaRPCClientTest { class BankOfCordaRPCClientTest {
@Test
@Test fun `test issuer flow via RPC`() { fun `issuer flow via RPC`() {
driver(dsl = { driver(dsl = {
val user = User("user1", "test", permissions = setOf(startFlowPermission<IssuanceRequester>())) val user = User("user1", "test", permissions = setOf(startFlowPermission<IssuanceRequester>()))
val nodeBankOfCorda = startNode("BankOfCorda", setOf(ServiceInfo(SimpleNotaryService.type)), arrayListOf(user)).get() val (nodeBankOfCorda, nodeBigCorporation) = Futures.allAsList(
val nodeBankOfCordaApiAddr = nodeBankOfCorda.config.getHostAndPort("artemisAddress") startNode("BankOfCorda", setOf(ServiceInfo(SimpleNotaryService.type)), listOf(user)),
val bankOfCordaParty = nodeBankOfCorda.nodeInfo.legalIdentity startNode("BigCorporation", rpcUsers = listOf(user))
val nodeBigCorporation = startNode("BigCorporation", rpcUsers = arrayListOf(user)).get() ).getOrThrow()
val bigCorporationParty = nodeBigCorporation.nodeInfo.legalIdentity
// Bank of Corda RPC Client // Bank of Corda RPC Client
val bocClient = CordaRPCClient(nodeBankOfCordaApiAddr, configureTestSSL()) val bocClient = nodeBankOfCorda.rpcClientToNode()
bocClient.start("user1", "test") bocClient.start("user1", "test")
val bocProxy = bocClient.proxy() val bocProxy = bocClient.proxy()
// Big Corporation RPC Client // Big Corporation RPC Client
val bigCorpClient = CordaRPCClient(nodeBankOfCordaApiAddr, configureTestSSL()) val bigCorpClient = nodeBankOfCorda.rpcClientToNode() // TODO This test is broken as this should be nodeBigCorporation
bigCorpClient.start("user1", "test") bigCorpClient.start("user1", "test")
val bigCorpProxy = bigCorpClient.proxy() val bigCorpProxy = bigCorpClient.proxy()
@ -43,7 +45,12 @@ class BankOfCordaRPCClientTest {
val vaultUpdatesBigCorp = bigCorpProxy.vaultAndUpdates().second val vaultUpdatesBigCorp = bigCorpProxy.vaultAndUpdates().second
// Kick-off actual Issuer Flow // Kick-off actual Issuer Flow
val result = bocProxy.startFlow(::IssuanceRequester, 1000.DOLLARS, bigCorporationParty, BOC_PARTY_REF, bankOfCordaParty).returnValue.toBlocking().first() val result = bocProxy.startFlow(
::IssuanceRequester,
1000.DOLLARS,
nodeBigCorporation.nodeInfo.legalIdentity,
BOC_PARTY_REF,
nodeBankOfCorda.nodeInfo.legalIdentity).returnValue.toBlocking().first()
assertTrue { result is SignedTransaction } assertTrue { result is SignedTransaction }
// Check Bank of Corda Vault Updates // Check Bank of Corda Vault Updates
@ -51,13 +58,13 @@ class BankOfCordaRPCClientTest {
sequence( sequence(
// ISSUE // ISSUE
expect { update -> expect { update ->
require(update.consumed.size == 0) { update.consumed.size } require(update.consumed.isEmpty()) { update.consumed.size }
require(update.produced.size == 1) { update.produced.size } require(update.produced.size == 1) { update.produced.size }
}, },
// MOVE // MOVE
expect { update -> expect { update ->
require(update.consumed.size == 1) { update.consumed.size } require(update.consumed.size == 1) { update.consumed.size }
require(update.produced.size == 0) { update.produced.size } require(update.produced.isEmpty()) { update.produced.size }
} }
) )
} }
@ -67,13 +74,13 @@ class BankOfCordaRPCClientTest {
sequence( sequence(
// ISSUE // ISSUE
expect { update -> expect { update ->
require(update.consumed.size == 0) { update.consumed.size } require(update.consumed.isEmpty()) { update.consumed.size }
require(update.produced.size == 1) { update.produced.size } require(update.produced.size == 1) { update.produced.size }
}, },
// MOVE // MOVE
expect { update -> expect { update ->
require(update.consumed.size == 1) { update.consumed.size } require(update.consumed.size == 1) { update.consumed.size }
require(update.produced.size == 0) { update.produced.size } require(update.produced.isEmpty()) { update.produced.size }
} }
) )
} }

View File

@ -1,7 +1,7 @@
package net.corda.irs package net.corda.irs
import com.google.common.net.HostAndPort import com.google.common.net.HostAndPort
import com.typesafe.config.Config import com.google.common.util.concurrent.Futures
import net.corda.core.getOrThrow import net.corda.core.getOrThrow
import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.ServiceInfo
import net.corda.irs.api.NodeInterestRates import net.corda.irs.api.NodeInterestRates
@ -16,16 +16,17 @@ import org.junit.Test
import java.net.URL import java.net.URL
class IRSDemoTest : IntegrationTestCategory { class IRSDemoTest : IntegrationTestCategory {
fun Config.getHostAndPort(name: String): HostAndPort = HostAndPort.fromString(getString(name))!! @Test
fun `runs IRS demo`() {
@Test fun `runs IRS demo`() {
driver(dsl = { driver(dsl = {
val controller = startNode("Notary", setOf(ServiceInfo(SimpleNotaryService.type), ServiceInfo(NodeInterestRates.type))).getOrThrow() val (controller, nodeA, nodeB) = Futures.allAsList(
val nodeA = startNode("Bank A").getOrThrow() startNode("Notary", setOf(ServiceInfo(SimpleNotaryService.type), ServiceInfo(NodeInterestRates.type))),
val nodeB = startNode("Bank B").getOrThrow() startNode("Bank A"),
runUploadRates(controller.config.getHostAndPort("webAddress")) startNode("Bank B")
runTrade(nodeA.config.getHostAndPort("webAddress")) ).getOrThrow()
runDateChange(nodeB.config.getHostAndPort("webAddress")) runUploadRates(controller.configuration.webAddress)
runTrade(nodeA.configuration.webAddress)
runDateChange(nodeB.configuration.webAddress)
}, useTestClock = true, isDebug = true) }, useTestClock = true, isDebug = true)
} }
} }

View File

@ -7,7 +7,6 @@ import net.corda.node.driver.NodeHandle
import net.corda.node.driver.driver import net.corda.node.driver.driver
import net.corda.node.services.transactions.SimpleNotaryService import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.testing.IntegrationTestCategory import net.corda.testing.IntegrationTestCategory
import net.corda.testing.getHostAndPort
import net.corda.testing.http.HttpApi import net.corda.testing.http.HttpApi
import net.corda.vega.api.PortfolioApi import net.corda.vega.api.PortfolioApi
import net.corda.vega.api.PortfolioApiUtils import net.corda.vega.api.PortfolioApiUtils
@ -45,7 +44,7 @@ class SimmValuationTest: IntegrationTestCategory {
} }
private fun getSimmNodeApi(futureNode: Future<NodeHandle>): HttpApi { private fun getSimmNodeApi(futureNode: Future<NodeHandle>): HttpApi {
val nodeAddr = futureNode.getOrThrow().config.getHostAndPort("webAddress") val nodeAddr = futureNode.getOrThrow().configuration.webAddress
return HttpApi.fromHostAndPort(nodeAddr, "api/simmvaluationdemo") return HttpApi.fromHostAndPort(nodeAddr, "api/simmvaluationdemo")
} }

View File

@ -1,5 +1,6 @@
package net.corda.traderdemo package net.corda.traderdemo
import com.google.common.util.concurrent.Futures
import net.corda.core.getOrThrow import net.corda.core.getOrThrow
import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.ServiceInfo
import net.corda.flows.IssuerFlow import net.corda.flows.IssuerFlow
@ -7,21 +8,21 @@ import net.corda.node.driver.driver
import net.corda.node.services.User import net.corda.node.services.User
import net.corda.node.services.startFlowPermission import net.corda.node.services.startFlowPermission
import net.corda.node.services.transactions.SimpleNotaryService import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.testing.getHostAndPort
import org.junit.Test import org.junit.Test
class TraderDemoTest { class TraderDemoTest {
@Test fun `runs trader demo`() { @Test fun `runs trader demo`() {
driver(dsl = { driver(dsl = {
startNode("Notary", setOf(ServiceInfo(SimpleNotaryService.type)))
val nodeA = startNode("Bank A").getOrThrow()
val nodeAApiAddr = nodeA.config.getHostAndPort("webAddress")
val nodeBApiAddr = startNode("Bank B").getOrThrow().config.getHostAndPort("webAddress")
val user = User("user1", "test", permissions = setOf(startFlowPermission<IssuerFlow.IssuanceRequester>())) val user = User("user1", "test", permissions = setOf(startFlowPermission<IssuerFlow.IssuanceRequester>()))
startNode("BankOfCorda", rpcUsers = listOf(user)).getOrThrow() val (nodeA, nodeB) = Futures.allAsList(
startNode("Bank A"),
startNode("Bank B"),
startNode("BankOfCorda", rpcUsers = listOf(user)),
startNode("Notary", setOf(ServiceInfo(SimpleNotaryService.type)))
).getOrThrow()
assert(TraderDemoClientApi(nodeAApiAddr).runBuyer()) assert(TraderDemoClientApi(nodeA.configuration.webAddress).runBuyer())
assert(TraderDemoClientApi(nodeBApiAddr).runSeller(counterparty = nodeA.nodeInfo.legalIdentity.name)) assert(TraderDemoClientApi(nodeB.configuration.webAddress).runSeller(counterparty = nodeA.nodeInfo.legalIdentity.name))
}, isDebug = true) }, isDebug = true)
} }
} }

View File

@ -5,7 +5,6 @@ package net.corda.testing
import com.google.common.net.HostAndPort import com.google.common.net.HostAndPort
import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.SettableFuture import com.google.common.util.concurrent.SettableFuture
import com.typesafe.config.Config
import net.corda.core.contracts.StateRef import net.corda.core.contracts.StateRef
import net.corda.core.crypto.* import net.corda.core.crypto.*
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
@ -165,8 +164,6 @@ inline fun <reified P : FlowLogic<*>> AbstractNode.initiateSingleShotFlow(
return future return future
} }
fun Config.getHostAndPort(name: String) = HostAndPort.fromString(getString(name))
inline fun elapsedTime(block: () -> Unit): Duration { inline fun elapsedTime(block: () -> Unit): Duration {
val start = System.nanoTime() val start = System.nanoTime()
block() block()

View File

@ -26,9 +26,7 @@ import net.corda.flows.IssuerFlow.IssuanceRequester
import net.corda.node.driver.PortAllocation import net.corda.node.driver.PortAllocation
import net.corda.node.driver.driver import net.corda.node.driver.driver
import net.corda.node.services.User import net.corda.node.services.User
import net.corda.node.services.config.FullNodeConfiguration
import net.corda.node.services.messaging.ArtemisMessagingComponent import net.corda.node.services.messaging.ArtemisMessagingComponent
import net.corda.node.services.messaging.CordaRPCClient
import net.corda.node.services.startFlowPermission import net.corda.node.services.startFlowPermission
import net.corda.node.services.transactions.SimpleNotaryService import net.corda.node.services.transactions.SimpleNotaryService
import org.apache.commons.lang.SystemUtils import org.apache.commons.lang.SystemUtils
@ -143,19 +141,19 @@ fun main(args: Array<String>) {
println("Running simulation mode ...") println("Running simulation mode ...")
// Register with alice to use alice's RPC proxy to create random events. // Register with alice to use alice's RPC proxy to create random events.
val aliceClient = CordaRPCClient(ArtemisMessagingComponent.toHostAndPort(aliceNode.nodeInfo.address), FullNodeConfiguration(aliceNode.config)) val aliceClient = aliceNode.rpcClientToNode()
aliceClient.start(user.username, user.password) aliceClient.start(user.username, user.password)
val aliceRPC = aliceClient.proxy() val aliceRPC = aliceClient.proxy()
val bobClient = CordaRPCClient(ArtemisMessagingComponent.toHostAndPort(bobNode.nodeInfo.address), FullNodeConfiguration(bobNode.config)) val bobClient = bobNode.rpcClientToNode()
bobClient.start(user.username, user.password) bobClient.start(user.username, user.password)
val bobRPC = bobClient.proxy() val bobRPC = bobClient.proxy()
val issuerClientGBP = CordaRPCClient(ArtemisMessagingComponent.toHostAndPort(issuerNodeGBP.nodeInfo.address), FullNodeConfiguration(issuerNodeGBP.config)) val issuerClientGBP = issuerNodeGBP.rpcClientToNode()
issuerClientGBP.start(manager.username, manager.password) issuerClientGBP.start(manager.username, manager.password)
val issuerRPCGBP = issuerClientGBP.proxy() val issuerRPCGBP = issuerClientGBP.proxy()
val issuerClientUSD = CordaRPCClient(ArtemisMessagingComponent.toHostAndPort(issuerNodeGBP.nodeInfo.address), FullNodeConfiguration(issuerNodeUSD.config)) val issuerClientUSD = issuerNodeGBP.rpcClientToNode() // TODO This should be issuerNodeUSD
issuerClientUSD.start(manager.username, manager.password) issuerClientUSD.start(manager.username, manager.password)
val issuerRPCUSD = issuerClientUSD.proxy() val issuerRPCUSD = issuerClientUSD.proxy()