mirror of
https://github.com/corda/corda.git
synced 2025-06-23 09:25:36 +00:00
[CORDA-1035] Testing api KDoc Updates (#2584)
* Testing api KDoc Updates * Update after code review * Update api-current * Revert changes to compiler.xml * Made comment changes from review * Fixing merge conflict * Don't expose net.corda.node through test API (first pass) * Fixing merge conflicts * Update api-current * Addressing review commits * Fix exposure of internal implementation of MessageHandlerRegistration * Make InProcess expose ServiceHub instead of internal StartedNodeServices * Move InternalMockMessaginService interface to internal namespace * Move MOCK_VERSION_INFO to internal namespace to avoid exposing VersionInfo * Don't expose WritableTransactionStorage via testing api * Create public VerifierType enum * Update api-current and modify check-api-changes to check for net.corda.node exposures * Fix merge conflicts * Fixing another merge conflict * Fix accidentally broken unit tests * Make getInternalServices a property * Fix failing unit tests * Add todo to check-api-changes * Fix rpc sender thread busy looping * Fix tests * Fixing tests * Address mike's comments * Fixing tests * Make random port allocation internal * Update api
This commit is contained in:
@ -17,6 +17,7 @@ import net.corda.testing.node.internal.internalDriver
|
||||
import net.corda.testing.core.DUMMY_BANK_A_NAME
|
||||
import net.corda.testing.core.DUMMY_BANK_B_NAME
|
||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||
import net.corda.testing.driver.internal.RandomFree
|
||||
import net.corda.testing.http.HttpApi
|
||||
import net.corda.testing.node.NotarySpec
|
||||
import org.assertj.core.api.Assertions.*
|
||||
@ -71,7 +72,7 @@ class DriverTests {
|
||||
|
||||
@Test
|
||||
fun `random free port allocation`() {
|
||||
val nodeHandle = driver(DriverParameters(portAllocation = PortAllocation.RandomFree)) {
|
||||
val nodeHandle = driver(DriverParameters(portAllocation = RandomFree)) {
|
||||
val nodeInfo = startNode(providedName = DUMMY_BANK_A_NAME)
|
||||
nodeMustBeUp(nodeInfo)
|
||||
}
|
||||
|
@ -291,7 +291,7 @@ class FlowStackSnapshotTest {
|
||||
val mockNet = MockNetwork(emptyList(), threadPerNode = true)
|
||||
val node = mockNet.createPartyNode()
|
||||
node.registerInitiatedFlow(DummyFlow::class.java)
|
||||
node.services.startFlow(FlowStackSnapshotSerializationTestingFlow()).get()
|
||||
node.startFlow(FlowStackSnapshotSerializationTestingFlow()).get()
|
||||
val thrown = try {
|
||||
// Due to the [MockNetwork] implementation, the easiest way to trigger object serialization process is at
|
||||
// the network stopping stage.
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
package net.corda.testing.driver
|
||||
|
||||
import net.corda.core.CordaInternal
|
||||
import net.corda.core.DoNotImplement
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.flows.FlowLogic
|
||||
@ -10,17 +11,19 @@ import net.corda.core.identity.Party
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.node.internal.Node
|
||||
import net.corda.node.services.api.StartedNodeServices
|
||||
import net.corda.node.services.config.VerifierType
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||
import net.corda.testing.driver.internal.internalServices
|
||||
import net.corda.testing.node.NotarySpec
|
||||
import net.corda.testing.node.User
|
||||
import net.corda.testing.node.internal.DriverDSLImpl
|
||||
import net.corda.testing.node.internal.genericDriver
|
||||
import net.corda.testing.node.internal.getTimestampAsDirectoryName
|
||||
import net.corda.testing.node.internal.newContext
|
||||
import rx.Observable
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.ServerSocket
|
||||
@ -33,18 +36,27 @@ import java.util.concurrent.atomic.AtomicInteger
|
||||
*/
|
||||
data class NotaryHandle(val identity: Party, val validating: Boolean, val nodeHandles: CordaFuture<List<NodeHandle>>)
|
||||
|
||||
/**
|
||||
* A base interface which represents a node as part of the [driver] dsl, extended by [InProcess] and [OutOfProcess]
|
||||
*/
|
||||
@DoNotImplement
|
||||
interface NodeHandle : AutoCloseable {
|
||||
/** Get the [NodeInfo] for this node */
|
||||
val nodeInfo: NodeInfo
|
||||
/**
|
||||
* Interface to the node's RPC system. The first RPC user will be used to login if are any, otherwise a default one
|
||||
* will be added and that will be used.
|
||||
*/
|
||||
val rpc: CordaRPCOps
|
||||
/** Get the p2p address for this node **/
|
||||
val p2pAddress: NetworkHostAndPort
|
||||
/** Get the rpc address for this node **/
|
||||
val rpcAddress: NetworkHostAndPort
|
||||
/** Get a [List] of [User]'s for this node **/
|
||||
val rpcUsers: List<User>
|
||||
/** The location of the node's base directory **/
|
||||
val baseDirectory: Path
|
||||
|
||||
/**
|
||||
* Stops the referenced node.
|
||||
*/
|
||||
@ -52,46 +64,79 @@ interface NodeHandle : AutoCloseable {
|
||||
}
|
||||
|
||||
|
||||
/** Interface which represents an out of process node and exposes its process handle. **/
|
||||
@DoNotImplement
|
||||
interface OutOfProcess : NodeHandle {
|
||||
/** The process in which this node is running **/
|
||||
val process: Process
|
||||
}
|
||||
|
||||
/** Interface which represents an in process node and exposes available services. **/
|
||||
@DoNotImplement
|
||||
interface InProcess : NodeHandle {
|
||||
val services: StartedNodeServices
|
||||
/** Services which are available to this node **/
|
||||
val services: ServiceHub
|
||||
|
||||
/**
|
||||
* Register a flow that is initiated by another flow
|
||||
*/
|
||||
fun <T : FlowLogic<*>> registerInitiatedFlow(initiatedFlowClass: Class<T>): Observable<T>
|
||||
|
||||
/**
|
||||
* Starts an already constructed flow. Note that you must be on the server thread to call this method.
|
||||
* @param context indicates who started the flow, see: [InvocationContext].
|
||||
*/
|
||||
fun <T> startFlow(logic: FlowLogic<T>): CordaFuture<T> = internalServices.startFlow(logic, internalServices.newContext()).getOrThrow().resultFuture
|
||||
}
|
||||
|
||||
/**
|
||||
* Class which represents a handle to a webserver process and its [NetworkHostAndPort] for testing purposes.
|
||||
*
|
||||
* @property listenAddress The [NetworkHostAndPort] for communicating with this webserver.
|
||||
* @property process The [Process] in which the websever is running
|
||||
* */
|
||||
@Deprecated("The webserver is for testing purposes only and will be removed soon")
|
||||
data class WebserverHandle(
|
||||
val listenAddress: NetworkHostAndPort,
|
||||
val process: Process
|
||||
)
|
||||
|
||||
/**
|
||||
* An abstract helper class which is used within the driver to allocate unused ports for testing. Use either
|
||||
* the [Incremental] or [RandomFree] concrete implementations.
|
||||
*/
|
||||
@DoNotImplement
|
||||
sealed class PortAllocation {
|
||||
abstract class PortAllocation {
|
||||
/** Get the next available port **/
|
||||
abstract fun nextPort(): Int
|
||||
|
||||
/** Get the next available port via [nextPort] and then return a [NetworkHostAndPort] **/
|
||||
fun nextHostAndPort() = NetworkHostAndPort("localhost", nextPort())
|
||||
|
||||
/**
|
||||
* An implementation of [PortAllocation] which allocates ports sequentially
|
||||
*/
|
||||
class Incremental(startingPort: Int) : PortAllocation() {
|
||||
/** The backing [AtomicInteger] used to keep track of the currently allocated port */
|
||||
val portCounter = AtomicInteger(startingPort)
|
||||
override fun nextPort() = portCounter.andIncrement
|
||||
}
|
||||
|
||||
object RandomFree : PortAllocation() {
|
||||
override fun nextPort(): Int {
|
||||
return ServerSocket().use {
|
||||
it.bind(InetSocketAddress(0))
|
||||
it.localPort
|
||||
}
|
||||
}
|
||||
override fun nextPort() = portCounter.andIncrement
|
||||
}
|
||||
}
|
||||
|
||||
/** Helper builder for configuring a [Node] from Java. */
|
||||
/**
|
||||
* Helper builder for configuring a [Node] from Java.
|
||||
*
|
||||
* @property providedName Optional name of the node, which will be its legal name in [Party]. Defaults to something
|
||||
* random. Note that this must be unique as the driver uses it as a primary key!
|
||||
* @property rpcUsers List of users who are authorised to use the RPC system. Defaults to a single user with
|
||||
* all permissions.
|
||||
* @property verifierType The type of transaction verifier to use. See: [VerifierType]
|
||||
* @property customOverrides A map of custom node configuration overrides.
|
||||
* @property 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.
|
||||
* @property maximumHeapSize The maximum JVM heap size to use for the node.
|
||||
*/
|
||||
@Suppress("unused")
|
||||
data class NodeParameters(
|
||||
val providedName: CordaX500Name? = null,
|
||||
@ -104,12 +149,14 @@ data class NodeParameters(
|
||||
fun withProvidedName(providedName: CordaX500Name?): NodeParameters = copy(providedName = providedName)
|
||||
fun withRpcUsers(rpcUsers: List<User>): NodeParameters = copy(rpcUsers = rpcUsers)
|
||||
fun withVerifierType(verifierType: VerifierType): NodeParameters = copy(verifierType = verifierType)
|
||||
fun withCustomerOverrides(customOverrides: Map<String, Any?>): NodeParameters = copy(customOverrides = customOverrides)
|
||||
fun withCustomOverrides(customOverrides: Map<String, Any?>): NodeParameters = copy(customOverrides = customOverrides)
|
||||
fun withStartInSameProcess(startInSameProcess: Boolean?): NodeParameters = copy(startInSameProcess = startInSameProcess)
|
||||
fun withMaximumHeapSize(maximumHeapSize: String): NodeParameters = copy(maximumHeapSize = maximumHeapSize)
|
||||
}
|
||||
|
||||
/**
|
||||
* A class containing configuration information for Jolokia JMX, to be used when creating a node via the [driver]
|
||||
*
|
||||
* @property startJmxHttpServer Indicates whether the spawned nodes should start with a Jolokia JMX agent to enable remote
|
||||
* JMX monitoring using HTTP/JSON
|
||||
* @property jmxHttpServerPortAllocation The port allocation strategy to use for remote Jolokia/JMX monitoring over HTTP.
|
||||
@ -162,24 +209,28 @@ fun <A> driver(defaultParameters: DriverParameters = DriverParameters(), dsl: Dr
|
||||
|
||||
/**
|
||||
* Builder for configuring a [driver].
|
||||
*
|
||||
* @property isDebug Indicates whether the spawned nodes should start in jdwt debug mode and have debug level logging.
|
||||
* @property driverDirectory The base directory node directories go into, defaults to "build/<timestamp>/". The node
|
||||
* directories themselves are "<baseDirectory>/<legalName>/", where legalName defaults to "<randomName>-<messagingPort>"
|
||||
* and may be specified in [DriverDSL.startNode].
|
||||
* directories themselves are "<baseDirectory>/<legalName>/", where legalName defaults to "<randomName>-<messagingPort>"
|
||||
* and may be specified in [DriverDSL.startNode].
|
||||
* @property portAllocation The port allocation strategy to use for the messaging and the web server addresses. Defaults
|
||||
* to incremental.
|
||||
* to incremental.
|
||||
* @property debugPortAllocation The port allocation strategy to use for jvm debugging. Defaults to incremental.
|
||||
* @property systemProperties A Map of extra system properties which will be given to each new node. Defaults to empty.
|
||||
* @property useTestClock If true the test clock will be used in Node.
|
||||
* @property startNodesInProcess Provides the default behaviour of whether new nodes should start inside this process or
|
||||
* not. Note that this may be overridden in [DriverDSL.startNode].
|
||||
* @property waitForAllNodesToFinish If true, the nodes will not shut down automatically after executing the code in the
|
||||
* driver DSL block. It will wait for them to be shut down externally instead.
|
||||
* driver DSL block. It will wait for them to be shut down externally instead.
|
||||
* @property notarySpecs The notaries advertised for this network. These nodes will be started automatically and will be
|
||||
* available from [DriverDSL.notaryHandles]. Defaults to a simple validating notary.
|
||||
* available from [DriverDSL.notaryHandles], and will be added automatically to the network parameters.
|
||||
* Defaults to a simple validating notary.
|
||||
* @property extraCordappPackagesToScan A [List] of additional cordapp packages to scan for any cordapp code, e.g.
|
||||
* contract verification code, flows and services. The calling package is automatically added.
|
||||
* @property jmxPolicy Used to specify whether to expose JMX metrics via Jolokia HHTP/JSON.
|
||||
* @property networkParameters The network parmeters to be used by all the nodes. [NetworkParameters.notaries] must be
|
||||
* empty as notaries are defined by [notarySpecs].
|
||||
* @property networkParameters The network parameters to be used by all the nodes. [NetworkParameters.notaries] must be
|
||||
* empty as notaries are defined by [notarySpecs].
|
||||
*/
|
||||
@Suppress("unused")
|
||||
data class DriverParameters(
|
||||
|
@ -6,11 +6,18 @@ import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.concurrent.map
|
||||
import net.corda.node.internal.Node
|
||||
import net.corda.node.services.config.VerifierType
|
||||
import net.corda.testing.node.User
|
||||
import net.corda.testing.node.NotarySpec
|
||||
import java.nio.file.Path
|
||||
|
||||
enum class VerifierType {
|
||||
InMemory,
|
||||
OutOfProcess
|
||||
}
|
||||
|
||||
/**
|
||||
* Underlying interface for the driver DSL. Do not instantiate directly, instead use the [driver] function.
|
||||
* */
|
||||
@DoNotImplement
|
||||
interface DriverDSL {
|
||||
/** Returns a list of [NotaryHandle]s matching the list of [NotarySpec]s passed into [driver]. */
|
||||
@ -53,10 +60,14 @@ interface DriverDSL {
|
||||
* when called from Java code.
|
||||
* @param providedName Optional name of the node, which will be its legal name in [Party]. Defaults to something
|
||||
* random. Note that this must be unique as the driver uses it as a primary key!
|
||||
* @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 verifierType The type of transaction verifier to use. See: [VerifierType].
|
||||
* @param customOverrides A map of custom node configuration overrides.
|
||||
* @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.
|
||||
* @param maximumHeapSize The maximum JVM heap size to use for the node as a [String]. By default a number is interpreted
|
||||
* as being in bytes. Append the letter 'k' or 'K' to the value to indicate Kilobytes, 'm' or 'M' to indicate
|
||||
* megabytes, and 'g' or 'G' to indicate gigabytes. The default value is "200m" = 200 megabytes.
|
||||
* @return A [CordaFuture] on the [NodeHandle] to the node. The future will complete when the node is available.
|
||||
*/
|
||||
fun startNode(
|
||||
|
@ -12,10 +12,12 @@ import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.testing.driver.InProcess
|
||||
import net.corda.testing.driver.NodeHandle
|
||||
import net.corda.testing.driver.OutOfProcess
|
||||
import net.corda.testing.driver.PortAllocation
|
||||
import net.corda.testing.node.User
|
||||
import rx.Observable
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.ServerSocket
|
||||
import java.nio.file.Path
|
||||
import java.sql.Connection
|
||||
|
||||
interface NodeHandleInternal : NodeHandle {
|
||||
val configuration: NodeConfiguration
|
||||
@ -73,3 +75,14 @@ data class InProcessImpl(
|
||||
override fun close() = stop()
|
||||
override fun <T : FlowLogic<*>> registerInitiatedFlow(initiatedFlowClass: Class<T>): Observable<T> = node.registerInitiatedFlow(initiatedFlowClass)
|
||||
}
|
||||
|
||||
val InProcess.internalServices: StartedNodeServices get() = services as StartedNodeServices
|
||||
|
||||
object RandomFree : PortAllocation() {
|
||||
override fun nextPort(): Int {
|
||||
return ServerSocket().use {
|
||||
it.bind(InetSocketAddress(0))
|
||||
it.localPort
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
package net.corda.testing.node
|
||||
|
||||
import net.corda.core.CordaInternal
|
||||
import net.corda.core.DoNotImplement
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
@ -25,7 +24,8 @@ import net.corda.node.services.messaging.MessagingService
|
||||
import net.corda.node.services.messaging.ReceivedMessage
|
||||
import net.corda.node.utilities.AffinityExecutor
|
||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.testing.node.InMemoryMessagingNetwork.TestMessagingService
|
||||
import net.corda.testing.node.internal.InMemoryMessage
|
||||
import net.corda.testing.node.internal.InternalMockMessagingService
|
||||
import org.apache.activemq.artemis.utils.ReusableLatch
|
||||
import org.slf4j.LoggerFactory
|
||||
import rx.Observable
|
||||
@ -39,8 +39,8 @@ import kotlin.concurrent.schedule
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
/**
|
||||
* An in-memory network allows you to manufacture [TestMessagingService]s for a set of participants. Each
|
||||
* [TestMessagingService] maintains a queue of messages it has received, and a background thread that dispatches
|
||||
* An in-memory network allows you to manufacture [InternalMockMessagingService]s for a set of participants. Each
|
||||
* [InternalMockMessagingService] maintains a queue of messages it has received, and a background thread that dispatches
|
||||
* messages one by one to registered handlers. Alternatively, a messaging system may be manually pumped, in which
|
||||
* case no thread is created and a caller is expected to force delivery one at a time (this is useful for unit
|
||||
* testing).
|
||||
@ -69,8 +69,16 @@ class InMemoryMessagingNetwork private constructor(
|
||||
private var counter = 0 // -1 means stopped.
|
||||
private val handleEndpointMap = HashMap<PeerHandle, InMemoryMessaging>()
|
||||
|
||||
/** A class which represents a message being transferred from sender to recipients, within the [InMemoryMessageNetwork]. **/
|
||||
@CordaSerializable
|
||||
data class MessageTransfer(val sender: PeerHandle, val message: Message, val recipients: MessageRecipients) {
|
||||
class MessageTransfer private constructor(val sender: PeerHandle, internal val message: Message, val recipients: MessageRecipients) {
|
||||
companion object {
|
||||
internal fun createMessageTransfer(sender: PeerHandle, message: Message, recipients: MessageRecipients): MessageTransfer {
|
||||
return MessageTransfer(sender, message, recipients)
|
||||
}
|
||||
}
|
||||
/** Data contained in this message transfer **/
|
||||
val messageData: ByteSequence get() = message.data
|
||||
override fun toString() = "${message.topic} from '$sender' to '$recipients'"
|
||||
}
|
||||
|
||||
@ -79,7 +87,7 @@ class InMemoryMessagingNetwork private constructor(
|
||||
private val messageSendQueue = LinkedBlockingQueue<MessageTransfer>()
|
||||
private val _sentMessages = PublishSubject.create<MessageTransfer>()
|
||||
@Suppress("unused") // Used by the visualiser tool.
|
||||
/** A stream of (sender, message, recipients) triples */
|
||||
/** A stream of (sender, message, recipients) triples containing messages once they have been sent by [pumpSend]. */
|
||||
val sentMessages: Observable<MessageTransfer>
|
||||
get() = _sentMessages
|
||||
|
||||
@ -92,16 +100,18 @@ class InMemoryMessagingNetwork private constructor(
|
||||
private val _receivedMessages = PublishSubject.create<MessageTransfer>()
|
||||
|
||||
// Holds the mapping from services to peers advertising the service.
|
||||
private val serviceToPeersMapping = HashMap<ServiceHandle, LinkedHashSet<PeerHandle>>()
|
||||
private val serviceToPeersMapping = HashMap<DistributedServiceHandle, LinkedHashSet<PeerHandle>>()
|
||||
// Holds the mapping from node's X.500 name to PeerHandle.
|
||||
private val peersMapping = HashMap<CordaX500Name, PeerHandle>()
|
||||
|
||||
@Suppress("unused") // Used by the visualiser tool.
|
||||
/** A stream of (sender, message, recipients) triples */
|
||||
/** A stream of (sender, message, recipients) triples containing messages once they have been received. */
|
||||
val receivedMessages: Observable<MessageTransfer>
|
||||
get() = _receivedMessages
|
||||
internal val endpoints: List<InternalMockMessagingService> @Synchronized get() = handleEndpointMap.values.toList()
|
||||
/** Get a [List] of all the [MockMessagingService] endpoints **/
|
||||
val endpointsExternal: List<MockMessagingService> @Synchronized get() = handleEndpointMap.values.map{ MockMessagingService.createMockMessagingService(it) }.toList()
|
||||
|
||||
val endpoints: List<TestMessagingService> @Synchronized get() = handleEndpointMap.values.toList()
|
||||
/**
|
||||
* Creates a node at the given address: useful if you want to recreate a node to simulate a restart.
|
||||
*
|
||||
@ -119,11 +129,11 @@ class InMemoryMessagingNetwork private constructor(
|
||||
notaryService: PartyAndCertificate?,
|
||||
description: CordaX500Name = CordaX500Name(organisation = "In memory node $id", locality = "London", country = "UK"),
|
||||
database: CordaPersistence)
|
||||
: TestMessagingService {
|
||||
: InternalMockMessagingService {
|
||||
val peerHandle = PeerHandle(id, description)
|
||||
peersMapping[peerHandle.description] = peerHandle // Assume that the same name - the same entity in MockNetwork.
|
||||
peersMapping[peerHandle.name] = peerHandle // Assume that the same name - the same entity in MockNetwork.
|
||||
notaryService?.let { if (it.owningKey !is CompositeKey) peersMapping[it.name] = peerHandle }
|
||||
val serviceHandles = notaryService?.let { listOf(ServiceHandle(it.party)) }
|
||||
val serviceHandles = notaryService?.let { listOf(DistributedServiceHandle(it.party)) }
|
||||
?: emptyList() //TODO only notary can be distributed?
|
||||
synchronized(this) {
|
||||
val node = InMemoryMessaging(manuallyPumped, peerHandle, executor, database)
|
||||
@ -135,7 +145,8 @@ class InMemoryMessagingNetwork private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
interface LatencyCalculator { // XXX: Used?
|
||||
/** Implement this interface in order to inject artificial latency between sender/recipient pairs. */
|
||||
interface LatencyCalculator {
|
||||
fun between(sender: SingleMessageRecipient, receiver: SingleMessageRecipient): Duration
|
||||
}
|
||||
|
||||
@ -147,7 +158,7 @@ class InMemoryMessagingNetwork private constructor(
|
||||
@Synchronized
|
||||
private fun msgSend(from: InMemoryMessaging, message: Message, recipients: MessageRecipients) {
|
||||
messagesInFlight.countUp()
|
||||
messageSendQueue += MessageTransfer(from.myAddress, message, recipients)
|
||||
messageSendQueue += MessageTransfer.createMessageTransfer(from.myAddress, message, recipients)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
@ -159,12 +170,15 @@ class InMemoryMessagingNetwork private constructor(
|
||||
private fun getQueueForPeerHandle(recipients: PeerHandle) = messageReceiveQueues.getOrPut(recipients) { LinkedBlockingQueue() }
|
||||
|
||||
@Synchronized
|
||||
private fun getQueuesForServiceHandle(recipients: ServiceHandle): List<LinkedBlockingQueue<MessageTransfer>> {
|
||||
private fun getQueuesForServiceHandle(recipients: DistributedServiceHandle): List<LinkedBlockingQueue<MessageTransfer>> {
|
||||
return serviceToPeersMapping[recipients]!!.map {
|
||||
messageReceiveQueues.getOrPut(it) { LinkedBlockingQueue() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop all nodes within the network and clear any buffered messages
|
||||
*/
|
||||
fun stop() {
|
||||
val nodes = synchronized(this) {
|
||||
counter = -1
|
||||
@ -179,15 +193,26 @@ class InMemoryMessagingNetwork private constructor(
|
||||
timer.cancel()
|
||||
}
|
||||
|
||||
/**
|
||||
* A class which represents information about an entity on the [InMemoryMessagingNetwork].
|
||||
*
|
||||
* @property id An integer giving the node an ID on the [InMemoryMessagingNetwork].
|
||||
* @property name The node's [CordaX500Name].
|
||||
*/
|
||||
@CordaSerializable
|
||||
data class PeerHandle(val id: Int, val description: CordaX500Name) : SingleMessageRecipient {
|
||||
override fun toString() = description.toString()
|
||||
data class PeerHandle(val id: Int, val name: CordaX500Name) : SingleMessageRecipient {
|
||||
override fun toString() = name.toString()
|
||||
override fun equals(other: Any?) = other is PeerHandle && other.id == id
|
||||
override fun hashCode() = id.hashCode()
|
||||
}
|
||||
|
||||
/**
|
||||
* A class which represents information about nodes offering the same distributed service on the [InMemoryMessagingNetwork].
|
||||
*
|
||||
* @property party The [Party] offering the service.
|
||||
*/
|
||||
@CordaSerializable
|
||||
data class ServiceHandle(val party: Party) : MessageRecipientGroup {
|
||||
data class DistributedServiceHandle(val party: Party) : MessageRecipientGroup {
|
||||
override fun toString() = "Service($party)"
|
||||
}
|
||||
|
||||
@ -197,16 +222,16 @@ class InMemoryMessagingNetwork private constructor(
|
||||
*/
|
||||
@DoNotImplement
|
||||
sealed class ServicePeerAllocationStrategy {
|
||||
abstract fun <A> pickNext(service: ServiceHandle, pickFrom: List<A>): A
|
||||
abstract fun <A> pickNext(service: DistributedServiceHandle, pickFrom: List<A>): A
|
||||
class Random(val random: SplittableRandom = SplittableRandom()) : ServicePeerAllocationStrategy() {
|
||||
override fun <A> pickNext(service: ServiceHandle, pickFrom: List<A>): A {
|
||||
override fun <A> pickNext(service: DistributedServiceHandle, pickFrom: List<A>): A {
|
||||
return pickFrom[random.nextInt(pickFrom.size)]
|
||||
}
|
||||
}
|
||||
|
||||
class RoundRobin : ServicePeerAllocationStrategy() {
|
||||
private val previousPicks = HashMap<ServiceHandle, Int>()
|
||||
override fun <A> pickNext(service: ServiceHandle, pickFrom: List<A>): A {
|
||||
private val previousPicks = HashMap<DistributedServiceHandle, Int>()
|
||||
override fun <A> pickNext(service: DistributedServiceHandle, pickFrom: List<A>): A {
|
||||
val nextIndex = previousPicks.compute(service) { _, previous ->
|
||||
(previous?.plus(1) ?: 0) % pickFrom.size
|
||||
}!!
|
||||
@ -215,7 +240,12 @@ class InMemoryMessagingNetwork private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
// If block is set to true this function will only return once a message has been pushed onto the recipients' queues
|
||||
/**
|
||||
* Send the next queued message to the requested recipient(s) within the network
|
||||
*
|
||||
* @param block If set to true this function will only return once a message has been pushed onto the recipients'
|
||||
* queues. This is only relevant if a [latencyCalculator] is being used to simulate latency in the network.
|
||||
*/
|
||||
fun pumpSend(block: Boolean): MessageTransfer? {
|
||||
val transfer = (if (block) messageSendQueue.take() else messageSendQueue.poll()) ?: return null
|
||||
|
||||
@ -241,7 +271,7 @@ class InMemoryMessagingNetwork private constructor(
|
||||
private fun pumpSendInternal(transfer: MessageTransfer) {
|
||||
when (transfer.recipients) {
|
||||
is PeerHandle -> getQueueForPeerHandle(transfer.recipients).add(transfer)
|
||||
is ServiceHandle -> {
|
||||
is DistributedServiceHandle -> {
|
||||
val queues = getQueuesForServiceHandle(transfer.recipients)
|
||||
val queue = servicePeerAllocationStrategy.pickNext(transfer.recipients, queues)
|
||||
queue.add(transfer)
|
||||
@ -257,13 +287,6 @@ class InMemoryMessagingNetwork private constructor(
|
||||
_sentMessages.onNext(transfer)
|
||||
}
|
||||
|
||||
data class InMemoryMessage(override val topic: String,
|
||||
override val data: ByteSequence,
|
||||
override val uniqueMessageId: String,
|
||||
override val debugTimestamp: Instant = Instant.now()) : Message {
|
||||
override fun toString() = "$topic#${String(data.bytes)}"
|
||||
}
|
||||
|
||||
private data class InMemoryReceivedMessage(override val topic: String,
|
||||
override val data: ByteSequence,
|
||||
override val platformVersion: Int,
|
||||
@ -272,21 +295,31 @@ class InMemoryMessagingNetwork private constructor(
|
||||
override val peer: CordaX500Name) : ReceivedMessage
|
||||
|
||||
/**
|
||||
* A [TestMessagingService] that provides a [MessagingService] abstraction that also contains the ability to
|
||||
* A class that provides an abstraction over the nodes' messaging service that also contains the ability to
|
||||
* receive messages from the queue for testing purposes.
|
||||
*/
|
||||
@DoNotImplement
|
||||
interface TestMessagingService : MessagingService {
|
||||
fun pumpReceive(block: Boolean): InMemoryMessagingNetwork.MessageTransfer?
|
||||
fun stop()
|
||||
class MockMessagingService private constructor(private val messagingService: InternalMockMessagingService) {
|
||||
companion object {
|
||||
internal fun createMockMessagingService(messagingService: InternalMockMessagingService): MockMessagingService {
|
||||
return MockMessagingService(messagingService)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Delivers a single message from the internal queue. If there are no messages waiting to be delivered and block
|
||||
* is true, waits until one has been provided on a different thread via send. If block is false, the return
|
||||
* result indicates whether a message was delivered or not.
|
||||
*
|
||||
* @return the message that was processed, if any in this round.
|
||||
*/
|
||||
fun pumpReceive(block: Boolean): InMemoryMessagingNetwork.MessageTransfer? = messagingService.pumpReceive(block)
|
||||
}
|
||||
|
||||
@ThreadSafe
|
||||
private inner class InMemoryMessaging(private val manuallyPumped: Boolean,
|
||||
private val peerHandle: PeerHandle,
|
||||
private val executor: AffinityExecutor,
|
||||
private val database: CordaPersistence) : SingletonSerializeAsToken(), TestMessagingService {
|
||||
inner class Handler(val topicSession: String, val callback: (ReceivedMessage, MessageHandlerRegistration) -> Unit) : MessageHandlerRegistration
|
||||
private val database: CordaPersistence) : SingletonSerializeAsToken(), InternalMockMessagingService {
|
||||
private inner class Handler(val topicSession: String, val callback: (ReceivedMessage, MessageHandlerRegistration) -> Unit) : MessageHandlerRegistration
|
||||
|
||||
@Volatile
|
||||
private var running = true
|
||||
@ -316,7 +349,7 @@ class InMemoryMessagingNetwork private constructor(
|
||||
return when (partyInfo) {
|
||||
is PartyInfo.SingleNode -> peersMapping[partyInfo.party.name]
|
||||
?: throw IllegalArgumentException("No StartedMockNode for party ${partyInfo.party.name}")
|
||||
is PartyInfo.DistributedNode -> ServiceHandle(partyInfo.party)
|
||||
is PartyInfo.DistributedNode -> DistributedServiceHandle(partyInfo.party)
|
||||
}
|
||||
}
|
||||
|
||||
@ -454,6 +487,7 @@ class InMemoryMessagingNetwork private constructor(
|
||||
1,
|
||||
message.uniqueMessageId,
|
||||
message.debugTimestamp,
|
||||
sender.description)
|
||||
sender.name)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,36 +8,38 @@ import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.node.VersionInfo
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.node.internal.StartedNode
|
||||
import net.corda.node.services.api.StartedNodeServices
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.node.services.messaging.MessagingService
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||
import net.corda.testing.node.internal.InternalMockMessagingService
|
||||
import net.corda.testing.node.internal.InternalMockNetwork
|
||||
import net.corda.testing.node.internal.setMessagingServiceSpy
|
||||
import net.corda.testing.node.internal.InternalMockNodeParameters
|
||||
import net.corda.testing.node.internal.newContext
|
||||
import rx.Observable
|
||||
import java.math.BigInteger
|
||||
import java.nio.file.Path
|
||||
|
||||
/**
|
||||
* Extend this class in order to intercept and modify messages passing through the [MessagingService] when using the [InMemoryMessagingNetwork].
|
||||
*/
|
||||
open class MessagingServiceSpy(val messagingService: MessagingService) : MessagingService by messagingService
|
||||
|
||||
/**
|
||||
* @param entropyRoot the initial entropy value to use when generating keys. Defaults to an (insecure) random value,
|
||||
* Immutable builder for configuring a [StartedMockNode] or an [UnstartedMockNode] via [MockNetwork.createNode] and
|
||||
* [MockNetwork.createUnstartedNode]. Kotlin users can also use the named parameters overloads of those methods which
|
||||
* are more convenient.
|
||||
*
|
||||
* @property forcedID Override the ID to use for the node. By default node ID's are generated sequentially in a
|
||||
* [MockNetwork]. Specifying the same ID is required if a node is restarted.
|
||||
* @property legalName The [CordaX500Name] name to use for the node.
|
||||
* @property 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.
|
||||
* @property configOverrides Add/override behaviour of the [NodeConfiguration] mock object.
|
||||
*/
|
||||
@Suppress("unused")
|
||||
data class MockNodeParameters(
|
||||
val forcedID: Int? = null,
|
||||
val legalName: CordaX500Name? = null,
|
||||
val entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()),
|
||||
val configOverrides: (NodeConfiguration) -> Any? = {},
|
||||
val version: VersionInfo = MockServices.MOCK_VERSION_INFO) {
|
||||
val configOverrides: (NodeConfiguration) -> Any? = {}) {
|
||||
fun withForcedID(forcedID: Int?): MockNodeParameters = copy(forcedID = forcedID)
|
||||
fun withLegalName(legalName: CordaX500Name?): MockNodeParameters = copy(legalName = legalName)
|
||||
fun withEntropyRoot(entropyRoot: BigInteger): MockNodeParameters = copy(entropyRoot = entropyRoot)
|
||||
@ -77,7 +79,13 @@ data class MockNetworkParameters(
|
||||
fun withNotarySpecs(notarySpecs: List<MockNetworkNotarySpec>): MockNetworkParameters = copy(notarySpecs = notarySpecs)
|
||||
}
|
||||
|
||||
/** Represents a node configuration for injection via [MockNetworkParameters]. */
|
||||
/**
|
||||
* The spec for a notary which will used by the [MockNetwork] to automatically start a notary node. This notary will
|
||||
* become part of the network parameters used by all the nodes.
|
||||
*
|
||||
* @property name The name of the notary node.
|
||||
* @property validating Boolean for whether the notary is validating or non-validating.
|
||||
*/
|
||||
data class MockNetworkNotarySpec(val name: CordaX500Name, val validating: Boolean = true) {
|
||||
constructor(name: CordaX500Name) : this(name, validating = true)
|
||||
}
|
||||
@ -90,9 +98,15 @@ class UnstartedMockNode private constructor(private val node: InternalMockNetwor
|
||||
}
|
||||
}
|
||||
|
||||
/** An identifier for the node. By default this is allocated sequentially in a [MockNetwork] **/
|
||||
val id get() : Int = node.id
|
||||
/** Start the node **/
|
||||
fun start() = StartedMockNode.create(node.start())
|
||||
|
||||
/**
|
||||
* Start the node
|
||||
*
|
||||
* @return A [StartedMockNode] object.
|
||||
*/
|
||||
fun start(): StartedMockNode = StartedMockNode.create(node.start())
|
||||
}
|
||||
|
||||
/** A class that represents a started mock node for testing. */
|
||||
@ -103,30 +117,44 @@ class StartedMockNode private constructor(private val node: StartedNode<Internal
|
||||
}
|
||||
}
|
||||
|
||||
val services get() : StartedNodeServices = node.services
|
||||
val id get() : Int = node.internals.id
|
||||
val info get() : NodeInfo = node.services.myInfo
|
||||
val network get() : MessagingService = node.network
|
||||
/** Register a flow that is initiated by another flow **/
|
||||
fun <F : FlowLogic<*>> registerInitiatedFlow(initiatedFlowClass: Class<F>): Observable<F> = node.registerInitiatedFlow(initiatedFlowClass)
|
||||
/** The [ServiceHub] for the underlying node. **/
|
||||
val services get(): ServiceHub = node.services
|
||||
/** An identifier for the node. By default this is allocated sequentially in a [MockNetwork]. **/
|
||||
val id get(): Int = node.internals.id
|
||||
/** The [NodeInfo] for the underlying node. **/
|
||||
val info get(): NodeInfo = node.services.myInfo
|
||||
|
||||
/**
|
||||
* Attach a [MessagingServiceSpy] to the [InternalMockNetwork.MockNode] allowing
|
||||
* interception and modification of messages.
|
||||
* Starts an already constructed flow. Note that you must be on the server thread to call this method.
|
||||
* @param context indicates who started the flow, see: [InvocationContext].
|
||||
*/
|
||||
fun setMessagingServiceSpy(messagingServiceSpy: MessagingServiceSpy) = node.setMessagingServiceSpy(messagingServiceSpy)
|
||||
fun <T> startFlow(logic: FlowLogic<T>): CordaFuture<T> = node.services.startFlow(logic, node.services.newContext()).getOrThrow().resultFuture
|
||||
|
||||
/** Stop the node **/
|
||||
/** Register a flow that is initiated by another flow .**/
|
||||
fun <F : FlowLogic<*>> registerInitiatedFlow(initiatedFlowClass: Class<F>): Observable<F> = node.registerInitiatedFlow(initiatedFlowClass)
|
||||
|
||||
/** Stop the node. **/
|
||||
fun stop() = node.internals.stop()
|
||||
|
||||
/** Receive a message from the queue. */
|
||||
/**
|
||||
* Delivers a single message from the internal queue. If there are no messages waiting to be delivered and block
|
||||
* is true, waits until one has been provided on a different thread via send. If block is false, the return
|
||||
* result indicates whether a message was delivered or not.
|
||||
*
|
||||
* @return the message that was processed, if any in this round.
|
||||
*/
|
||||
fun pumpReceive(block: Boolean = false): InMemoryMessagingNetwork.MessageTransfer? {
|
||||
return (services.networkService as InMemoryMessagingNetwork.TestMessagingService).pumpReceive(block)
|
||||
return (node.network as InternalMockMessagingService).pumpReceive(block)
|
||||
}
|
||||
|
||||
/** Returns the currently live flows of type [flowClass], and their corresponding result future. */
|
||||
fun <F : FlowLogic<*>> findStateMachines(flowClass: Class<F>): List<Pair<F, CordaFuture<*>>> = node.smm.findStateMachines(flowClass)
|
||||
|
||||
/**
|
||||
* Executes given statement in the scope of transaction.
|
||||
*
|
||||
* @param statement to be executed in the scope of this transaction.
|
||||
*/
|
||||
fun <T> transaction(statement: () -> T): T {
|
||||
return node.database.transaction {
|
||||
statement()
|
||||
@ -154,6 +182,23 @@ class StartedMockNode private constructor(private val node: StartedNode<Internal
|
||||
*
|
||||
* 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].
|
||||
*
|
||||
* @property cordappPackages A [List] of cordapp packages to scan for any cordapp code, e.g. contract verification code, flows and services.
|
||||
* @property defaultParameters A [MockNetworkParameters] object which contains the same parameters as the constructor, provided
|
||||
* as a convenience for Java users.
|
||||
* @property networkSendManuallyPumped If true then messages will not be routed from sender to receiver until you use
|
||||
* the [MockNetwork.runNetwork] method. This is useful for writing single-threaded unit test code that can examine the
|
||||
* state of the mock network before and after a message is sent, without races and without the receiving node immediately
|
||||
* sending a response. The default is false, so you must call runNetwork.
|
||||
* @property threadPerNode If true then each node will be run in its own thread. This can result in race conditions in
|
||||
* your code if not carefully written, but is more realistic and may help if you have flows in your app that do long
|
||||
* blocking operations. The default is false.
|
||||
* @property servicePeerAllocationStrategy How messages are load balanced in the case where a single compound identity
|
||||
* is used by multiple nodes. You rarely if ever need to change that, it's primarily of interest to people testing
|
||||
* notary code.
|
||||
* @property notarySpecs The notaries to use in the mock network. By default you get one mock notary and that is usually sufficient.
|
||||
* @property networkParameters The network parameters to be used by all the nodes. [NetworkParameters.notaries] must be
|
||||
* empty as notaries are defined by [notarySpecs].
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate", "CanBeParameter")
|
||||
open class MockNetwork(
|
||||
@ -169,62 +214,71 @@ open class MockNetwork(
|
||||
|
||||
private val internalMockNetwork: InternalMockNetwork = InternalMockNetwork(cordappPackages, defaultParameters, networkSendManuallyPumped, threadPerNode, servicePeerAllocationStrategy, notarySpecs, networkParameters)
|
||||
|
||||
/** Which node will be used as the primary notary during transaction builds. */
|
||||
val defaultNotaryNode get(): StartedMockNode = StartedMockNode.create(internalMockNetwork.defaultNotaryNode)
|
||||
/** The [Party] of the [defaultNotaryNode] */
|
||||
val defaultNotaryIdentity get(): Party = internalMockNetwork.defaultNotaryIdentity
|
||||
/** A list of all notary nodes in the network that have been started. */
|
||||
val notaryNodes get(): List<StartedMockNode> = internalMockNetwork.notaryNodes.map { StartedMockNode.create(it) }
|
||||
/** In a mock network, nodes have an incrementing integer ID. Real networks do not have this. Returns the next ID that will be used. */
|
||||
val nextNodeId get(): Int = internalMockNetwork.nextNodeId
|
||||
|
||||
/**
|
||||
* Returns the single notary node on the network. Throws an exception if there are none or more than one.
|
||||
* @see notaryNodes
|
||||
*/
|
||||
val defaultNotaryNode get(): StartedMockNode = StartedMockNode.create(internalMockNetwork.defaultNotaryNode)
|
||||
|
||||
/**
|
||||
* Return the identity of the default notary node.
|
||||
* @see defaultNotaryNode
|
||||
*/
|
||||
val defaultNotaryIdentity get(): Party = internalMockNetwork.defaultNotaryIdentity
|
||||
|
||||
/**
|
||||
* Returns the list of notary nodes started by the network.
|
||||
*/
|
||||
val notaryNodes get(): List<StartedMockNode> = internalMockNetwork.notaryNodes.map { StartedMockNode.create(it) }
|
||||
|
||||
/** Create a started node with the given identity. **/
|
||||
fun createPartyNode(legalName: CordaX500Name? = null): StartedMockNode = StartedMockNode.create(internalMockNetwork.createPartyNode(legalName))
|
||||
|
||||
/** Create a started node with the given parameters. **/
|
||||
fun createNode(parameters: MockNodeParameters = MockNodeParameters()): StartedMockNode = StartedMockNode.create(internalMockNetwork.createNode(parameters))
|
||||
fun createNode(parameters: MockNodeParameters = MockNodeParameters()): StartedMockNode = StartedMockNode.create(internalMockNetwork.createNode(InternalMockNodeParameters(parameters)))
|
||||
|
||||
/**
|
||||
* Create a started node with the given parameters.
|
||||
*
|
||||
* @param legalName the node's legal name.
|
||||
* @param forcedID a unique identifier for the node.
|
||||
* @param entropyRoot the initial entropy value to use when generating keys. Defaults to an (insecure) random value,
|
||||
* @param legalName The node's legal name.
|
||||
* @param forcedID A unique identifier for the node.
|
||||
* @param entropyRoot The initial entropy value to use when generating keys. Defaults to an (insecure) random value,
|
||||
* but can be overridden to cause nodes to have stable or colliding identity/service keys.
|
||||
* @param configOverrides add/override behaviour of the [NodeConfiguration] mock object.
|
||||
* @param version the mock node's platform, release, revision and vendor versions.
|
||||
* @param configOverrides Add/override behaviour of the [NodeConfiguration] mock object.
|
||||
* @param version The mock node's platform, release, revision and vendor versions.
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun createNode(legalName: CordaX500Name? = null,
|
||||
forcedID: Int? = null,
|
||||
entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()),
|
||||
configOverrides: (NodeConfiguration) -> Any? = {},
|
||||
version: VersionInfo = MockServices.MOCK_VERSION_INFO): StartedMockNode {
|
||||
val parameters = MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides, version)
|
||||
return StartedMockNode.create(internalMockNetwork.createNode(parameters))
|
||||
configOverrides: (NodeConfiguration) -> Any? = {}): StartedMockNode {
|
||||
val parameters = MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides)
|
||||
return StartedMockNode.create(internalMockNetwork.createNode(InternalMockNodeParameters(parameters)))
|
||||
}
|
||||
|
||||
/** Create an unstarted node with the given parameters. **/
|
||||
fun createUnstartedNode(parameters: MockNodeParameters = MockNodeParameters()): UnstartedMockNode = UnstartedMockNode.create(internalMockNetwork.createUnstartedNode(parameters))
|
||||
fun createUnstartedNode(parameters: MockNodeParameters = MockNodeParameters()): UnstartedMockNode = UnstartedMockNode.create(internalMockNetwork.createUnstartedNode(InternalMockNodeParameters(parameters)))
|
||||
|
||||
/**
|
||||
* Create an unstarted node with the given parameters.
|
||||
*
|
||||
* @param legalName the node's legal name.
|
||||
* @param forcedID a unique identifier for the node.
|
||||
* @param entropyRoot the initial entropy value to use when generating keys. Defaults to an (insecure) random value,
|
||||
* @param legalName The node's legal name.
|
||||
* @param forcedID A unique identifier for the node.
|
||||
* @param entropyRoot The initial entropy value to use when generating keys. Defaults to an (insecure) random value,
|
||||
* but can be overridden to cause nodes to have stable or colliding identity/service keys.
|
||||
* @param configOverrides add/override behaviour of the [NodeConfiguration] mock object.
|
||||
* @param version the mock node's platform, release, revision and vendor versions.
|
||||
* @param configOverrides Add/override behaviour of the [NodeConfiguration] mock object.
|
||||
* @param version The mock node's platform, release, revision and vendor versions.
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun createUnstartedNode(legalName: CordaX500Name? = null,
|
||||
forcedID: Int? = null,
|
||||
entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()),
|
||||
configOverrides: (NodeConfiguration) -> Any? = {},
|
||||
version: VersionInfo = MockServices.MOCK_VERSION_INFO): UnstartedMockNode {
|
||||
val parameters = MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides, version)
|
||||
return UnstartedMockNode.create(internalMockNetwork.createUnstartedNode(parameters))
|
||||
configOverrides: (NodeConfiguration) -> Any? = {}): UnstartedMockNode {
|
||||
val parameters = MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides)
|
||||
return UnstartedMockNode.create(internalMockNetwork.createUnstartedNode(InternalMockNodeParameters(parameters)))
|
||||
}
|
||||
|
||||
/** Start all nodes that aren't already started. **/
|
||||
|
@ -10,13 +10,11 @@ import net.corda.core.crypto.*
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.PartyAndCertificate
|
||||
import net.corda.core.messaging.DataFeed
|
||||
import net.corda.core.messaging.FlowHandle
|
||||
import net.corda.core.messaging.FlowProgressHandle
|
||||
import net.corda.core.node.*
|
||||
import net.corda.core.node.services.*
|
||||
import net.corda.core.serialization.SerializeAsToken
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.node.VersionInfo
|
||||
@ -27,8 +25,6 @@ import net.corda.node.services.api.SchemaService
|
||||
import net.corda.node.services.api.VaultServiceInternal
|
||||
import net.corda.node.services.api.WritableTransactionStorage
|
||||
import net.corda.node.services.identity.InMemoryIdentityService
|
||||
import net.corda.node.services.keys.freshCertificate
|
||||
import net.corda.node.services.keys.getSigner
|
||||
import net.corda.node.services.schema.HibernateObserver
|
||||
import net.corda.node.services.schema.NodeSchemaService
|
||||
import net.corda.node.services.transactions.InMemoryTransactionVerifierService
|
||||
@ -40,17 +36,17 @@ import net.corda.testing.common.internal.testNetworkParameters
|
||||
import net.corda.testing.core.TestIdentity
|
||||
import net.corda.testing.internal.DEV_ROOT_CA
|
||||
import net.corda.testing.internal.MockCordappProvider
|
||||
import net.corda.testing.node.internal.MockKeyManagementService
|
||||
import net.corda.testing.node.internal.MockTransactionStorage
|
||||
import net.corda.testing.services.MockAttachmentStorage
|
||||
import org.bouncycastle.operator.ContentSigner
|
||||
import rx.Observable
|
||||
import rx.subjects.PublishSubject
|
||||
import java.security.KeyPair
|
||||
import java.security.PrivateKey
|
||||
import java.security.PublicKey
|
||||
import java.sql.Connection
|
||||
import java.time.Clock
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Returns a simple [InMemoryIdentityService] containing the supplied [identities].
|
||||
*/
|
||||
fun makeTestIdentityService(vararg identities: PartyAndCertificate) = InMemoryIdentityService(identities, DEV_ROOT_CA.certificate)
|
||||
|
||||
/**
|
||||
@ -64,16 +60,13 @@ fun makeTestIdentityService(vararg identities: PartyAndCertificate) = InMemoryId
|
||||
*/
|
||||
open class MockServices private constructor(
|
||||
cordappLoader: CordappLoader,
|
||||
override val validatedTransactions: WritableTransactionStorage,
|
||||
override val validatedTransactions: TransactionStorage,
|
||||
override val identityService: IdentityService,
|
||||
final override val networkParameters: NetworkParameters,
|
||||
private val initialIdentity: TestIdentity,
|
||||
private val moreKeys: Array<out KeyPair>
|
||||
) : ServiceHub {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
val MOCK_VERSION_INFO = VersionInfo(1, "Mock release", "Mock revision", "Mock Vendor")
|
||||
|
||||
/**
|
||||
* Make properties appropriate for creating a DataSource for unit tests.
|
||||
*
|
||||
@ -93,10 +86,11 @@ open class MockServices private constructor(
|
||||
/**
|
||||
* Makes database and mock services appropriate for unit tests.
|
||||
*
|
||||
* @param moreKeys a list of additional [KeyPair] instances to be used by [MockServices].
|
||||
* @param identityService an instance of [IdentityService], see [makeTestIdentityService].
|
||||
* @param initialIdentity the first (typically sole) identity the services will represent.
|
||||
* @return a pair where the first element is the instance of [CordaPersistence] and the second is [MockServices].
|
||||
* @param cordappPackages A [List] of cordapp packages to scan for any cordapp code, e.g. contract verification code, flows and services.
|
||||
* @param identityService An instance of [IdentityService], see [makeTestIdentityService].
|
||||
* @param initialIdentity The first (typically sole) identity the services will represent.
|
||||
* @param moreKeys A list of additional [KeyPair] instances to be used by [MockServices].
|
||||
* @return A pair where the first element is the instance of [CordaPersistence] and the second is [MockServices].
|
||||
*/
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
@ -111,12 +105,12 @@ open class MockServices private constructor(
|
||||
val database = configureDatabase(dataSourceProps, DatabaseConfig(), identityService, schemaService)
|
||||
val mockService = database.transaction {
|
||||
object : MockServices(cordappLoader, identityService, networkParameters, initialIdentity, moreKeys) {
|
||||
override val vaultService: VaultServiceInternal = makeVaultService(database.hibernateConfig, schemaService)
|
||||
override val vaultService: VaultService = makeVaultService(database.hibernateConfig, schemaService)
|
||||
|
||||
override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable<SignedTransaction>) {
|
||||
super.recordTransactions(statesToRecord, txs)
|
||||
// Refactored to use notifyAll() as we have no other unit test for that method with multiple transactions.
|
||||
vaultService.notifyAll(statesToRecord, txs.map { it.coreTransaction })
|
||||
(vaultService as VaultServiceInternal).notifyAll(statesToRecord, txs.map { it.coreTransaction })
|
||||
}
|
||||
|
||||
override fun jdbcSession(): Connection = database.createSession()
|
||||
@ -212,10 +206,9 @@ open class MockServices private constructor(
|
||||
*/
|
||||
constructor() : this(listOf(getCallerPackage()), CordaX500Name("TestIdentity", "", "GB"), makeTestIdentityService())
|
||||
|
||||
|
||||
override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable<SignedTransaction>) {
|
||||
txs.forEach {
|
||||
validatedTransactions.addTransaction(it)
|
||||
(validatedTransactions as WritableTransactionStorage).addTransaction(it)
|
||||
}
|
||||
}
|
||||
|
||||
@ -241,7 +234,10 @@ open class MockServices private constructor(
|
||||
return vaultService
|
||||
}
|
||||
|
||||
val cordappServices: MutableClassToInstanceMap<SerializeAsToken> = MutableClassToInstanceMap.create<SerializeAsToken>()
|
||||
// This needs to be internal as MutableClassToInstanceMap is a guava type and shouldn't be part of our public API
|
||||
/** A map of available [CordaService] implementations */
|
||||
internal val cordappServices: MutableClassToInstanceMap<SerializeAsToken> = MutableClassToInstanceMap.create<SerializeAsToken>()
|
||||
|
||||
override fun <T : SerializeAsToken> cordaService(type: Class<T>): T {
|
||||
require(type.isAnnotationPresent(CordaService::class.java)) { "${type.name} is not a Corda service" }
|
||||
return cordappServices.getInstance(type)
|
||||
@ -252,6 +248,7 @@ open class MockServices private constructor(
|
||||
|
||||
override fun registerUnloadHandler(runOnStop: () -> Unit) = throw UnsupportedOperationException()
|
||||
|
||||
/** Add the given package name to the list of packages which will be scanned for cordapp contract verification code */
|
||||
fun addMockCordapp(contractClassName: ContractClassName) {
|
||||
mockCordappProvider.addMockCordapp(contractClassName, attachments)
|
||||
}
|
||||
@ -260,70 +257,9 @@ open class MockServices private constructor(
|
||||
override fun loadStates(stateRefs: Set<StateRef>) = servicesForResolution.loadStates(stateRefs)
|
||||
}
|
||||
|
||||
class MockKeyManagementService(val identityService: IdentityService,
|
||||
vararg initialKeys: KeyPair) : SingletonSerializeAsToken(), KeyManagementService {
|
||||
private val keyStore: MutableMap<PublicKey, PrivateKey> = initialKeys.associateByTo(HashMap(), { it.public }, { it.private })
|
||||
|
||||
override val keys: Set<PublicKey> get() = keyStore.keys
|
||||
|
||||
private val nextKeys = LinkedList<KeyPair>()
|
||||
|
||||
override fun freshKey(): PublicKey {
|
||||
val k = nextKeys.poll() ?: generateKeyPair()
|
||||
keyStore[k.public] = k.private
|
||||
return k.public
|
||||
}
|
||||
|
||||
override fun filterMyKeys(candidateKeys: Iterable<PublicKey>): Iterable<PublicKey> = candidateKeys.filter { it in this.keys }
|
||||
|
||||
override fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): PartyAndCertificate {
|
||||
return freshCertificate(identityService, freshKey(), identity, getSigner(identity.owningKey), revocationEnabled)
|
||||
}
|
||||
|
||||
private fun getSigner(publicKey: PublicKey): ContentSigner = getSigner(getSigningKeyPair(publicKey))
|
||||
|
||||
private fun getSigningKeyPair(publicKey: PublicKey): KeyPair {
|
||||
val pk = publicKey.keys.firstOrNull { keyStore.containsKey(it) }
|
||||
?: throw IllegalArgumentException("Public key not found: ${publicKey.toStringShort()}")
|
||||
return KeyPair(pk, keyStore[pk]!!)
|
||||
}
|
||||
|
||||
override fun sign(bytes: ByteArray, publicKey: PublicKey): DigitalSignature.WithKey {
|
||||
val keyPair = getSigningKeyPair(publicKey)
|
||||
return keyPair.sign(bytes)
|
||||
}
|
||||
|
||||
override fun sign(signableData: SignableData, publicKey: PublicKey): TransactionSignature {
|
||||
val keyPair = getSigningKeyPair(publicKey)
|
||||
return keyPair.sign(signableData)
|
||||
}
|
||||
}
|
||||
|
||||
open class MockTransactionStorage : WritableTransactionStorage, SingletonSerializeAsToken() {
|
||||
override fun track(): DataFeed<List<SignedTransaction>, SignedTransaction> {
|
||||
return DataFeed(txns.values.toList(), _updatesPublisher)
|
||||
}
|
||||
|
||||
private val txns = HashMap<SecureHash, SignedTransaction>()
|
||||
|
||||
private val _updatesPublisher = PublishSubject.create<SignedTransaction>()
|
||||
|
||||
override val updates: Observable<SignedTransaction>
|
||||
get() = _updatesPublisher
|
||||
|
||||
private fun notify(transaction: SignedTransaction) = _updatesPublisher.onNext(transaction)
|
||||
|
||||
override fun addTransaction(transaction: SignedTransaction): Boolean {
|
||||
val recorded = txns.putIfAbsent(transaction.id, transaction) == null
|
||||
if (recorded) {
|
||||
notify(transaction)
|
||||
}
|
||||
return recorded
|
||||
}
|
||||
|
||||
override fun getTransaction(id: SecureHash): SignedTransaction? = txns[id]
|
||||
}
|
||||
|
||||
/**
|
||||
* Function which can be used to create a mock [CordaService] for use within testing, such as an Oracle.
|
||||
*/
|
||||
fun <T : SerializeAsToken> createMockCordaService(serviceHub: MockServices, serviceConstructor: (AppServiceHub) -> T): T {
|
||||
class MockAppServiceHubImpl<out T : SerializeAsToken>(val serviceHub: MockServices, serviceConstructor: (AppServiceHub) -> T) : AppServiceHub, ServiceHub by serviceHub {
|
||||
val serviceInstance: T = serviceConstructor(this)
|
||||
|
@ -16,8 +16,8 @@ import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.node.services.api.StartedNodeServices
|
||||
import net.corda.testing.core.SerializationEnvironmentRule
|
||||
import net.corda.testing.core.TestIdentity
|
||||
import net.corda.testing.core.chooseIdentity
|
||||
import net.corda.testing.dsl.*
|
||||
import net.corda.testing.internal.chooseIdentity
|
||||
|
||||
/**
|
||||
* Creates and tests a ledger built by the passed in dsl.
|
||||
@ -55,17 +55,8 @@ fun ServiceHub.transaction(
|
||||
TransactionDSL(TestTransactionDSLInterpreter(interpreter, TransactionBuilder(notary)), notary).script()
|
||||
}
|
||||
|
||||
/** Creates a new [Actor] for use in testing with the given [owningLegalIdentity]. */
|
||||
fun testActor(owningLegalIdentity: CordaX500Name = CordaX500Name("Test Company Inc.", "London", "GB")) = Actor(Actor.Id("Only For Testing"), AuthServiceId("TEST"), owningLegalIdentity)
|
||||
|
||||
/** Creates a new [InvocationContext] for use in testing with the given [owningLegalIdentity]. */
|
||||
fun testContext(owningLegalIdentity: CordaX500Name = CordaX500Name("Test Company Inc.", "London", "GB")) = InvocationContext.rpc(testActor(owningLegalIdentity))
|
||||
|
||||
/**
|
||||
* Creates a new [InvocationContext] for testing purposes.
|
||||
*/
|
||||
fun StartedNodeServices.newContext() = testContext(myInfo.chooseIdentity().name)
|
||||
|
||||
/**
|
||||
* Starts an already constructed flow. Note that you must be on the server thread to call this method. [InvocationContext]
|
||||
* has origin [Origin.RPC] and actor with id "Only For Testing".
|
||||
*/
|
||||
fun <T> StartedNodeServices.startFlow(logic: FlowLogic<T>): CordaFuture<T> = startFlow(logic, newContext()).getOrThrow().resultFuture
|
@ -2,8 +2,18 @@ package net.corda.testing.node
|
||||
|
||||
import net.corda.core.DoNotImplement
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.node.services.config.VerifierType
|
||||
import net.corda.testing.driver.VerifierType
|
||||
|
||||
/**
|
||||
* A notary spec for describing a notary which will be started automatically by the driver and which will be part of
|
||||
* the network parameters used by all the nodes.
|
||||
*
|
||||
* @property name The name of the notary. If this is a notary cluster then each node member will be assigned a name based on this name.
|
||||
* @property validating Boolean for whether the notary is validating or non-validating.
|
||||
* @property rpcUsers A list of users able to instigate RPC for this node or cluster of nodes.
|
||||
* @property verifierType How the notary will verify transactions.
|
||||
* @property cluster [ClusterSpec] if this is a distributed cluster notary. If null then this is a single-node notary.
|
||||
*/
|
||||
data class NotarySpec(
|
||||
val name: CordaX500Name,
|
||||
val validating: Boolean = true,
|
||||
@ -12,10 +22,15 @@ data class NotarySpec(
|
||||
val cluster: ClusterSpec? = null
|
||||
)
|
||||
|
||||
/**
|
||||
* Abstract class specifying information about the consensus algorithm used for a cluster of nodes.
|
||||
*/
|
||||
@DoNotImplement
|
||||
abstract class ClusterSpec {
|
||||
/** The number of nodes within the cluster. **/
|
||||
abstract val clusterSize: Int
|
||||
|
||||
/** A class representing the configuration of a raft consensus algorithm used for a cluster of nodes. **/
|
||||
data class Raft(
|
||||
override val clusterSize: Int
|
||||
) : ClusterSpec() {
|
||||
|
@ -1,6 +1,12 @@
|
||||
package net.corda.testing.node
|
||||
|
||||
/** Object encapsulating a node rpc user and their associated permissions for use when testing */
|
||||
/**
|
||||
* Object encapsulating a node rpc user and their associated permissions for use when testing using the [driver]
|
||||
*
|
||||
* @property username The rpc user's username
|
||||
* @property password The rpc user's password
|
||||
* @property permissions A [List] of [String] detailing the [User]'s permissions
|
||||
* */
|
||||
data class User(
|
||||
val username: String,
|
||||
val password: String,
|
||||
|
@ -44,12 +44,12 @@ import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.BOB_NAME
|
||||
import net.corda.testing.core.DUMMY_BANK_A_NAME
|
||||
import net.corda.testing.driver.*
|
||||
import net.corda.testing.driver.VerifierType
|
||||
import net.corda.testing.driver.internal.InProcessImpl
|
||||
import net.corda.testing.driver.internal.NodeHandleInternal
|
||||
import net.corda.testing.driver.internal.OutOfProcessImpl
|
||||
import net.corda.testing.internal.setGlobalSerialization
|
||||
import net.corda.testing.node.ClusterSpec
|
||||
import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO
|
||||
import net.corda.testing.node.NotarySpec
|
||||
import net.corda.testing.node.User
|
||||
import net.corda.testing.node.internal.DriverDSLImpl.ClusterType.NON_VALIDATING_RAFT
|
||||
|
@ -0,0 +1,15 @@
|
||||
package net.corda.testing.node.internal
|
||||
|
||||
import net.corda.core.utilities.ByteSequence
|
||||
import net.corda.node.services.messaging.Message
|
||||
import java.time.Instant
|
||||
|
||||
/**
|
||||
* An implementation of [Message] for in memory messaging by the test [InMemoryMessagingNetwork].
|
||||
*/
|
||||
data class InMemoryMessage(override val topic: String,
|
||||
override val data: ByteSequence,
|
||||
override val uniqueMessageId: String,
|
||||
override val debugTimestamp: Instant = Instant.now()) : Message {
|
||||
override fun toString() = "$topic#${String(data.bytes)}"
|
||||
}
|
@ -51,7 +51,6 @@ import net.corda.testing.internal.rigorousMock
|
||||
import net.corda.testing.internal.setGlobalSerialization
|
||||
import net.corda.testing.internal.testThreadFactory
|
||||
import net.corda.testing.node.*
|
||||
import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
||||
import org.apache.activemq.artemis.utils.ReusableLatch
|
||||
import org.apache.sshd.common.util.security.SecurityUtils
|
||||
@ -64,8 +63,10 @@ import java.time.Clock
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
val MOCK_VERSION_INFO = VersionInfo(1, "Mock release", "Mock revision", "Mock Vendor")
|
||||
|
||||
fun StartedNode<InternalMockNetwork.MockNode>.pumpReceive(block: Boolean = false): InMemoryMessagingNetwork.MessageTransfer? {
|
||||
return (network as InMemoryMessagingNetwork.TestMessagingService).pumpReceive(block)
|
||||
return (network as InternalMockMessagingService).pumpReceive(block)
|
||||
}
|
||||
|
||||
data class MockNodeArgs(
|
||||
@ -76,6 +77,19 @@ data class MockNodeArgs(
|
||||
val version: VersionInfo = MOCK_VERSION_INFO
|
||||
)
|
||||
|
||||
data class InternalMockNodeParameters(
|
||||
val forcedID: Int? = null,
|
||||
val legalName: CordaX500Name? = null,
|
||||
val entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()),
|
||||
val configOverrides: (NodeConfiguration) -> Any? = {},
|
||||
val version: VersionInfo = MOCK_VERSION_INFO) {
|
||||
constructor(mockNodeParameters: MockNodeParameters) : this(
|
||||
mockNodeParameters.forcedID,
|
||||
mockNodeParameters.legalName,
|
||||
mockNodeParameters.entropyRoot,
|
||||
mockNodeParameters.configOverrides)
|
||||
}
|
||||
|
||||
open class InternalMockNetwork(private val cordappPackages: List<String>,
|
||||
defaultParameters: MockNetworkParameters = MockNetworkParameters(),
|
||||
val networkSendManuallyPumped: Boolean = defaultParameters.networkSendManuallyPumped,
|
||||
@ -194,7 +208,7 @@ open class InternalMockNetwork(private val cordappPackages: List<String>,
|
||||
@VisibleForTesting
|
||||
internal open fun createNotaries(): List<StartedNode<MockNode>> {
|
||||
return notarySpecs.map { (name, validating) ->
|
||||
createNode(MockNodeParameters(legalName = name, configOverrides = {
|
||||
createNode(InternalMockNodeParameters(legalName = name, configOverrides = {
|
||||
doReturn(NotaryConfig(validating)).whenever(it).notary
|
||||
}))
|
||||
}
|
||||
@ -329,24 +343,24 @@ open class InternalMockNetwork(private val cordappPackages: List<String>,
|
||||
}
|
||||
}
|
||||
|
||||
fun createUnstartedNode(parameters: MockNodeParameters = MockNodeParameters()): MockNode {
|
||||
fun createUnstartedNode(parameters: InternalMockNodeParameters = InternalMockNodeParameters()): MockNode {
|
||||
return createUnstartedNode(parameters, defaultFactory)
|
||||
}
|
||||
|
||||
fun <N : MockNode> createUnstartedNode(parameters: MockNodeParameters = MockNodeParameters(), nodeFactory: (MockNodeArgs) -> N): N {
|
||||
fun <N : MockNode> createUnstartedNode(parameters: InternalMockNodeParameters = InternalMockNodeParameters(), nodeFactory: (MockNodeArgs) -> N): N {
|
||||
return createNodeImpl(parameters, nodeFactory, false)
|
||||
}
|
||||
|
||||
fun createNode(parameters: MockNodeParameters = MockNodeParameters()): StartedNode<MockNode> {
|
||||
fun createNode(parameters: InternalMockNodeParameters = InternalMockNodeParameters()): StartedNode<MockNode> {
|
||||
return createNode(parameters, defaultFactory)
|
||||
}
|
||||
|
||||
/** Like the other [createNode] but takes a [nodeFactory] and propagates its [MockNode] subtype. */
|
||||
fun <N : MockNode> createNode(parameters: MockNodeParameters = MockNodeParameters(), nodeFactory: (MockNodeArgs) -> N): StartedNode<N> {
|
||||
fun <N : MockNode> createNode(parameters: InternalMockNodeParameters = InternalMockNodeParameters(), nodeFactory: (MockNodeArgs) -> N): StartedNode<N> {
|
||||
return uncheckedCast(createNodeImpl(parameters, nodeFactory, true).started)!!
|
||||
}
|
||||
|
||||
private fun <N : MockNode> createNodeImpl(parameters: MockNodeParameters, nodeFactory: (MockNodeArgs) -> N, start: Boolean): N {
|
||||
private fun <N : MockNode> createNodeImpl(parameters: InternalMockNodeParameters, nodeFactory: (MockNodeArgs) -> N, start: Boolean): N {
|
||||
val id = parameters.forcedID ?: nextNodeId++
|
||||
val config = mockNodeConfiguration().also {
|
||||
doReturn(baseDirectory(id).createDirectories()).whenever(it).baseDirectory
|
||||
@ -386,16 +400,15 @@ open class InternalMockNetwork(private val cordappPackages: List<String>,
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
|
||||
fun createPartyNode(legalName: CordaX500Name? = null): StartedNode<MockNode> {
|
||||
return createNode(MockNodeParameters(legalName = legalName))
|
||||
return createNode(InternalMockNodeParameters(legalName = legalName))
|
||||
}
|
||||
|
||||
@Suppress("unused") // This is used from the network visualiser tool.
|
||||
fun addressToNode(msgRecipient: MessageRecipients): MockNode {
|
||||
return when (msgRecipient) {
|
||||
is SingleMessageRecipient -> nodes.single { it.started!!.network.myAddress == msgRecipient }
|
||||
is InMemoryMessagingNetwork.ServiceHandle -> {
|
||||
is InMemoryMessagingNetwork.DistributedServiceHandle -> {
|
||||
nodes.firstOrNull { it.started!!.info.isLegalIdentity(msgRecipient.party) }
|
||||
?: throw IllegalArgumentException("Couldn't find node advertising service with owning party name: ${msgRecipient.party.name} ")
|
||||
}
|
||||
@ -425,6 +438,8 @@ open class InternalMockNetwork(private val cordappPackages: List<String>,
|
||||
|
||||
}
|
||||
|
||||
open class MessagingServiceSpy(val messagingService: MessagingService) : MessagingService by messagingService
|
||||
|
||||
/**
|
||||
* Attach a [MessagingServiceSpy] to the [InternalMockNetwork.MockNode] allowing interception and modification of messages.
|
||||
*/
|
||||
|
@ -3,7 +3,6 @@ package net.corda.testing.node.internal
|
||||
import net.corda.core.CordaException
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.context.InvocationContext
|
||||
import net.corda.core.context.InvocationOrigin
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.internal.FlowStateMachine
|
||||
import net.corda.core.internal.concurrent.openFuture
|
||||
@ -13,7 +12,11 @@ import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.core.utilities.millis
|
||||
import net.corda.core.utilities.seconds
|
||||
import net.corda.node.services.api.StartedNodeServices
|
||||
import net.corda.testing.node.newContext
|
||||
import net.corda.node.services.messaging.Message
|
||||
import net.corda.node.services.messaging.MessagingService
|
||||
import net.corda.testing.internal.chooseIdentity
|
||||
import net.corda.testing.node.InMemoryMessagingNetwork
|
||||
import net.corda.testing.node.testContext
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.net.Socket
|
||||
import java.net.SocketException
|
||||
@ -99,3 +102,15 @@ class ListenProcessDeathException(hostAndPort: NetworkHostAndPort, listenProcess
|
||||
CordaException("The process that was expected to listen on $hostAndPort has died with status: ${listenProcess.exitValue()}")
|
||||
|
||||
fun <T> StartedNodeServices.startFlow(logic: FlowLogic<T>): FlowStateMachine<T> = startFlow(logic, newContext()).getOrThrow()
|
||||
|
||||
fun StartedNodeServices.newContext(): InvocationContext = testContext(myInfo.chooseIdentity().name)
|
||||
|
||||
fun InMemoryMessagingNetwork.MessageTransfer.getMessage(): Message = message
|
||||
|
||||
internal interface InternalMockMessagingService : MessagingService {
|
||||
fun pumpReceive(block: Boolean): InMemoryMessagingNetwork.MessageTransfer?
|
||||
|
||||
fun stop()
|
||||
}
|
||||
|
||||
|
||||
|
@ -0,0 +1,57 @@
|
||||
package net.corda.testing.node.internal
|
||||
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.identity.PartyAndCertificate
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.core.node.services.KeyManagementService
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.node.services.keys.freshCertificate
|
||||
import org.bouncycastle.operator.ContentSigner
|
||||
import java.security.KeyPair
|
||||
import java.security.PrivateKey
|
||||
import java.security.PublicKey
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* A class which provides an implementation of [KeyManagementService] which is used in [MockServices]
|
||||
*
|
||||
* @property identityService The [IdentityService] which contains the given identities.
|
||||
*/
|
||||
class MockKeyManagementService(val identityService: IdentityService,
|
||||
vararg initialKeys: KeyPair) : SingletonSerializeAsToken(), KeyManagementService {
|
||||
private val keyStore: MutableMap<PublicKey, PrivateKey> = initialKeys.associateByTo(HashMap(), { it.public }, { it.private })
|
||||
|
||||
override val keys: Set<PublicKey> get() = keyStore.keys
|
||||
|
||||
private val nextKeys = LinkedList<KeyPair>()
|
||||
|
||||
override fun freshKey(): PublicKey {
|
||||
val k = nextKeys.poll() ?: generateKeyPair()
|
||||
keyStore[k.public] = k.private
|
||||
return k.public
|
||||
}
|
||||
|
||||
override fun filterMyKeys(candidateKeys: Iterable<PublicKey>): Iterable<PublicKey> = candidateKeys.filter { it in this.keys }
|
||||
|
||||
override fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): PartyAndCertificate {
|
||||
return freshCertificate(identityService, freshKey(), identity, getSigner(identity.owningKey), revocationEnabled)
|
||||
}
|
||||
|
||||
private fun getSigner(publicKey: PublicKey): ContentSigner = net.corda.node.services.keys.getSigner(getSigningKeyPair(publicKey))
|
||||
|
||||
private fun getSigningKeyPair(publicKey: PublicKey): KeyPair {
|
||||
val pk = publicKey.keys.firstOrNull { keyStore.containsKey(it) }
|
||||
?: throw IllegalArgumentException("Public key not found: ${publicKey.toStringShort()}")
|
||||
return KeyPair(pk, keyStore[pk]!!)
|
||||
}
|
||||
|
||||
override fun sign(bytes: ByteArray, publicKey: PublicKey): DigitalSignature.WithKey {
|
||||
val keyPair = getSigningKeyPair(publicKey)
|
||||
return keyPair.sign(bytes)
|
||||
}
|
||||
|
||||
override fun sign(signableData: SignableData, publicKey: PublicKey): TransactionSignature {
|
||||
val keyPair = getSigningKeyPair(publicKey)
|
||||
return keyPair.sign(signableData)
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package net.corda.testing.node.internal
|
||||
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.messaging.DataFeed
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.node.services.api.WritableTransactionStorage
|
||||
import rx.Observable
|
||||
import rx.subjects.PublishSubject
|
||||
import java.util.HashMap
|
||||
|
||||
/**
|
||||
* A class which provides an implementation of [WritableTransactionStorage] which is used in [MockServices]
|
||||
*/
|
||||
open class MockTransactionStorage : WritableTransactionStorage, SingletonSerializeAsToken() {
|
||||
override fun track(): DataFeed<List<SignedTransaction>, SignedTransaction> {
|
||||
return DataFeed(txns.values.toList(), _updatesPublisher)
|
||||
}
|
||||
|
||||
private val txns = HashMap<SecureHash, SignedTransaction>()
|
||||
|
||||
private val _updatesPublisher = PublishSubject.create<SignedTransaction>()
|
||||
|
||||
override val updates: Observable<SignedTransaction>
|
||||
get() = _updatesPublisher
|
||||
|
||||
private fun notify(transaction: SignedTransaction) = _updatesPublisher.onNext(transaction)
|
||||
|
||||
override fun addTransaction(transaction: SignedTransaction): Boolean {
|
||||
val recorded = txns.putIfAbsent(transaction.id, transaction) == null
|
||||
if (recorded) {
|
||||
notify(transaction)
|
||||
}
|
||||
return recorded
|
||||
}
|
||||
|
||||
override fun getTransaction(id: SecureHash): SignedTransaction? = txns[id]
|
||||
}
|
@ -19,7 +19,6 @@ import net.corda.testing.node.User
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import net.corda.testing.core.getFreeLocalPorts
|
||||
import net.corda.testing.internal.testThreadFactory
|
||||
import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO
|
||||
import org.apache.logging.log4j.Level
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
|
@ -9,12 +9,16 @@ import net.corda.core.transactions.TransactionBuilder
|
||||
|
||||
// The dummy contract doesn't do anything useful. It exists for testing purposes, but has to be serializable
|
||||
|
||||
/**
|
||||
* Dummy contract for testing purposes. Doesn't do anything useful.
|
||||
*/
|
||||
data class DummyContract(val blank: Any? = null) : Contract {
|
||||
|
||||
val PROGRAM_ID = "net.corda.testing.contracts.DummyContract"
|
||||
|
||||
@DoNotImplement // State is effectively a sealed class.
|
||||
interface State : ContractState {
|
||||
/** Some information that the state represents for test purposes. **/
|
||||
val magicNumber: Int
|
||||
}
|
||||
|
||||
@ -47,6 +51,11 @@ data class DummyContract(val blank: Any? = null) : Contract {
|
||||
companion object {
|
||||
const val PROGRAM_ID: ContractClassName = "net.corda.testing.contracts.DummyContract"
|
||||
|
||||
/**
|
||||
* Returns a [TransactionBuilder] with the given notary, a list of owners and an output state of type
|
||||
* [SingleOwnerState] or [MultipleOwnerState] (depending on the number of owner parameters passed) containing
|
||||
* the given magicNumber.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun generateInitial(magicNumber: Int, notary: Party, owner: PartyAndReference, vararg otherOwners: PartyAndReference): TransactionBuilder {
|
||||
val owners = listOf(owner) + otherOwners
|
||||
@ -59,9 +68,15 @@ data class DummyContract(val blank: Any? = null) : Contract {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An overload of move for just one input state.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun move(prior: StateAndRef<SingleOwnerState>, newOwner: AbstractParty) = move(listOf(prior), newOwner)
|
||||
|
||||
/**
|
||||
* Returns a [TransactionBuilder] that takes the given input states and transfers them to the newOwner.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun move(priors: List<StateAndRef<SingleOwnerState>>, newOwner: AbstractParty): TransactionBuilder {
|
||||
require(priors.isNotEmpty())
|
||||
|
@ -6,6 +6,8 @@ import net.corda.core.identity.AbstractParty
|
||||
/**
|
||||
* Dummy state for use in testing. Not part of any contract, not even the [DummyContract].
|
||||
*/
|
||||
data class DummyState(val magicNumber: Int = 0) : ContractState {
|
||||
data class DummyState(
|
||||
/** Some information that the state represents for test purposes. **/
|
||||
val magicNumber: Int = 0) : ContractState {
|
||||
override val participants: List<AbstractParty> get() = emptyList()
|
||||
}
|
||||
|
@ -190,6 +190,9 @@ fun <S, E : Any> S.genericExpectEvents(
|
||||
finishFuture.getOrThrow()
|
||||
}
|
||||
|
||||
/**
|
||||
* Part of the Expectation DSL
|
||||
*/
|
||||
@DoNotImplement
|
||||
sealed class ExpectCompose<out E> {
|
||||
internal class Single<out E, T : E>(val expect: Expect<E, T>) : ExpectCompose<E>()
|
||||
|
@ -28,7 +28,11 @@ import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
/** @param inheritable whether new threads inherit the environment, use sparingly. */
|
||||
/**
|
||||
* A test serialization rule implementation for use in tests
|
||||
*
|
||||
* @param inheritable whether new threads inherit the environment, use sparingly.
|
||||
*/
|
||||
class SerializationEnvironmentRule(private val inheritable: Boolean = false) : TestRule {
|
||||
companion object {
|
||||
init {
|
||||
@ -71,10 +75,3 @@ class SerializationEnvironmentRule(private val inheritable: Boolean = false) : T
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DoNotImplement
|
||||
interface GlobalSerializationEnvironment : SerializationEnvironment {
|
||||
/** Unset this environment. */
|
||||
fun unset()
|
||||
}
|
||||
|
||||
|
@ -10,28 +10,35 @@ import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
|
||||
import java.security.PublicKey
|
||||
import java.time.Instant
|
||||
|
||||
// A dummy time at which we will be pretending test transactions are created.
|
||||
@JvmField
|
||||
val TEST_TX_TIME = Instant.parse("2015-04-17T12:00:00.00Z")
|
||||
/** A test notary name **/
|
||||
@JvmField
|
||||
val DUMMY_NOTARY_NAME = CordaX500Name("Notary Service", "Zurich", "CH")
|
||||
/** A test node name **/
|
||||
@JvmField
|
||||
val DUMMY_BANK_A_NAME = CordaX500Name("Bank A", "London", "GB")
|
||||
/** A test node name **/
|
||||
@JvmField
|
||||
val DUMMY_BANK_B_NAME = CordaX500Name("Bank B", "New York", "US")
|
||||
/** A test node name **/
|
||||
@JvmField
|
||||
val DUMMY_BANK_C_NAME = CordaX500Name("Bank C", "Tokyo", "JP")
|
||||
/** A test node name **/
|
||||
@JvmField
|
||||
val BOC_NAME = CordaX500Name("BankOfCorda", "London", "GB")
|
||||
/** A test node name **/
|
||||
@JvmField
|
||||
val ALICE_NAME = CordaX500Name("Alice Corp", "Madrid", "ES")
|
||||
/** A test node name **/
|
||||
@JvmField
|
||||
val BOB_NAME = CordaX500Name("Bob Plc", "Rome", "IT")
|
||||
/** A test node name **/
|
||||
@JvmField
|
||||
val CHARLIE_NAME = CordaX500Name("Charlie Ltd", "Athens", "GR")
|
||||
|
||||
/** Generates a dummy command that doesn't do anything useful for use in tests **/
|
||||
fun dummyCommand(vararg signers: PublicKey = arrayOf(generateKeyPair().public)) = Command<TypeOnlyCommandData>(DummyCommandData, signers.toList())
|
||||
|
||||
/** Trivial implementation of [TypeOnlyCommandData] for test purposes */
|
||||
object DummyCommandData : TypeOnlyCommandData()
|
||||
|
||||
/** Maximum artemis message size. 10 MiB maximum allowed file size for attachments, including message headers. */
|
||||
|
@ -18,7 +18,6 @@ import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.nodeapi.internal.createDevNodeCa
|
||||
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
|
||||
import net.corda.nodeapi.internal.crypto.CertificateType
|
||||
import net.corda.nodeapi.internal.crypto.X509CertificateFactory
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import net.corda.testing.internal.DEV_INTERMEDIATE_CA
|
||||
import net.corda.testing.internal.DEV_ROOT_CA
|
||||
@ -46,7 +45,8 @@ import java.util.concurrent.atomic.AtomicInteger
|
||||
* - The Int.DOLLARS syntax doesn't work from Java. Use the DOLLARS(int) function instead.
|
||||
*/
|
||||
|
||||
fun generateStateRef() = StateRef(SecureHash.randomSHA256(), 0)
|
||||
/** Returns a fake state reference for testing purposes **/
|
||||
fun generateStateRef(): StateRef = StateRef(SecureHash.randomSHA256(), 0)
|
||||
|
||||
private val freePortCounter = AtomicInteger(30000)
|
||||
/**
|
||||
@ -100,6 +100,10 @@ fun getTestPartyAndCertificate(name: CordaX500Name, publicKey: PublicKey): Party
|
||||
return getTestPartyAndCertificate(Party(name, publicKey))
|
||||
}
|
||||
|
||||
/**
|
||||
* A class that encapsulates a test identity containing a [CordaX500Name] and a [KeyPair], alongside a range
|
||||
* of utility methods for use during testing.
|
||||
*/
|
||||
class TestIdentity(val name: CordaX500Name, val keyPair: KeyPair) {
|
||||
companion object {
|
||||
/**
|
||||
@ -122,16 +126,11 @@ class TestIdentity(val name: CordaX500Name, val keyPair: KeyPair) {
|
||||
val publicKey: PublicKey get() = keyPair.public
|
||||
val party: Party = Party(name, publicKey)
|
||||
val identity: PartyAndCertificate by lazy { getTestPartyAndCertificate(party) } // Often not needed.
|
||||
|
||||
/** Returns a [PartyAndReference] for this identity and the given reference */
|
||||
fun ref(vararg bytes: Byte): PartyAndReference = party.ref(*bytes)
|
||||
}
|
||||
|
||||
/**
|
||||
* Until we have proper handling of multiple identities per node, for tests we use the first identity as special one.
|
||||
* TODO: Should be removed after multiple identities are introduced.
|
||||
*/
|
||||
fun NodeInfo.chooseIdentityAndCert(): PartyAndCertificate = legalIdentitiesAndCerts.first()
|
||||
|
||||
fun NodeInfo.chooseIdentity(): Party = chooseIdentityAndCert().party
|
||||
/**
|
||||
* Extract a single identity from the node info. Throws an error if the node has multiple identities.
|
||||
*/
|
||||
|
@ -76,6 +76,9 @@ interface TransactionDSLInterpreter : Verifies, OutputStateLookup {
|
||||
fun _attachment(contractClassName: ContractClassName)
|
||||
}
|
||||
|
||||
/**
|
||||
* Underlying class for the transaction DSL. Do not instantiate directly, instead use the [transaction] function.
|
||||
* */
|
||||
class TransactionDSL<out T : TransactionDSLInterpreter>(interpreter: T, private val notary: Party) : TransactionDSLInterpreter by interpreter {
|
||||
/**
|
||||
* Looks up the output label and adds the found state as an input.
|
||||
|
@ -1,4 +1,4 @@
|
||||
package net.corda.testing.services
|
||||
package net.corda.testing.internal
|
||||
|
||||
import co.paralleluniverse.fibers.Fiber
|
||||
import co.paralleluniverse.fibers.Instrumented
|
@ -3,12 +3,12 @@ package net.corda.testing.internal
|
||||
import com.nhaarman.mockito_kotlin.doNothing
|
||||
import com.nhaarman.mockito_kotlin.whenever
|
||||
import net.corda.client.rpc.internal.KryoClientSerializationScheme
|
||||
import net.corda.core.DoNotImplement
|
||||
import net.corda.core.serialization.internal.*
|
||||
import net.corda.node.serialization.KryoServerSerializationScheme
|
||||
import net.corda.nodeapi.internal.serialization.*
|
||||
import net.corda.nodeapi.internal.serialization.amqp.AMQPClientSerializationScheme
|
||||
import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme
|
||||
import net.corda.testing.core.GlobalSerializationEnvironment
|
||||
import net.corda.testing.core.SerializationEnvironmentRule
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.ExecutorService
|
||||
@ -67,4 +67,11 @@ fun setGlobalSerialization(armed: Boolean): GlobalSerializationEnvironment {
|
||||
doNothing().whenever(it).unset()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DoNotImplement
|
||||
interface GlobalSerializationEnvironment : SerializationEnvironment {
|
||||
/** Unset this environment. */
|
||||
fun unset()
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,12 @@
|
||||
package net.corda.testing.internal
|
||||
|
||||
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
|
||||
import java.time.Instant
|
||||
|
||||
val DEV_INTERMEDIATE_CA: CertificateAndKeyPair by lazy { net.corda.nodeapi.internal.DEV_INTERMEDIATE_CA }
|
||||
|
||||
val DEV_ROOT_CA: CertificateAndKeyPair by lazy { net.corda.nodeapi.internal.DEV_ROOT_CA }
|
||||
|
||||
/** A dummy time at which we will be pretending test transactions are created. **/
|
||||
@JvmField
|
||||
val TEST_TX_TIME = Instant.parse("2015-04-17T12:00:00.00Z")
|
@ -4,6 +4,9 @@ import com.nhaarman.mockito_kotlin.doAnswer
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.Crypto.generateKeyPair
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.identity.PartyAndCertificate
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.node.services.config.SslOptions
|
||||
@ -136,3 +139,16 @@ fun SslOptions.noSslRpcOverrides(rpcAdminAddress: NetworkHostAndPort): Map<Strin
|
||||
"rpcSettings.ssl.trustStorePassword" to trustStorePassword
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Until we have proper handling of multiple identities per node, for tests we use the first identity as special one.
|
||||
* TODO: Should be removed after multiple identities are introduced.
|
||||
*/
|
||||
fun NodeInfo.chooseIdentityAndCert(): PartyAndCertificate = legalIdentitiesAndCerts.first()
|
||||
|
||||
/**
|
||||
* Returns the party identity of the first identity on the node. Until we have proper handling of multiple identities per node,
|
||||
* for tests we use the first identity as special one.
|
||||
* TODO: Should be removed after multiple identities are introduced.
|
||||
*/
|
||||
fun NodeInfo.chooseIdentity(): Party = chooseIdentityAndCert().party
|
||||
|
@ -17,6 +17,8 @@ import net.corda.finance.contracts.DealState
|
||||
import net.corda.finance.contracts.asset.Cash
|
||||
import net.corda.finance.contracts.asset.CommodityContract
|
||||
import net.corda.testing.core.*
|
||||
import net.corda.testing.internal.chooseIdentity
|
||||
import net.corda.testing.internal.chooseIdentityAndCert
|
||||
import java.security.PublicKey
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
|
@ -18,17 +18,14 @@ import java.io.InputStream
|
||||
import java.util.*
|
||||
import java.util.jar.JarInputStream
|
||||
|
||||
/**
|
||||
* A mock implementation of [AttachmentStorage] for use within tests
|
||||
*/
|
||||
class MockAttachmentStorage : AttachmentStorage, SingletonSerializeAsToken() {
|
||||
companion object {
|
||||
fun getBytes(jar: InputStream) = run {
|
||||
val s = ByteArrayOutputStream()
|
||||
jar.copyTo(s)
|
||||
s.close()
|
||||
s.toByteArray()
|
||||
}
|
||||
}
|
||||
|
||||
val files = HashMap<SecureHash, Pair<Attachment, ByteArray>>()
|
||||
private val _files = HashMap<SecureHash, Pair<Attachment, ByteArray>>()
|
||||
/** A map of the currently stored files by their [SecureHash] */
|
||||
val files: Map<SecureHash, Pair<Attachment, ByteArray>> get() = _files
|
||||
|
||||
override fun importAttachment(jar: InputStream): AttachmentId = importAttachment(jar, UNKNOWN_UPLOADER, null)
|
||||
|
||||
@ -56,7 +53,7 @@ class MockAttachmentStorage : AttachmentStorage, SingletonSerializeAsToken() {
|
||||
|
||||
fun importContractAttachment(contractClassNames: List<ContractClassName>, uploader: String, jar: InputStream): AttachmentId = importAttachmentInternal(jar, uploader, null, contractClassNames)
|
||||
|
||||
fun getAttachmentIdAndBytes(jar: InputStream): Pair<AttachmentId, ByteArray> = getBytes(jar).let { bytes -> Pair(bytes.sha256(), bytes) }
|
||||
fun getAttachmentIdAndBytes(jar: InputStream): Pair<AttachmentId, ByteArray> = jar.readBytes().let { bytes -> Pair(bytes.sha256(), bytes) }
|
||||
|
||||
private class MockAttachment(dataLoader: () -> ByteArray, override val id: SecureHash) : AbstractAttachment(dataLoader)
|
||||
|
||||
@ -64,14 +61,14 @@ class MockAttachmentStorage : AttachmentStorage, SingletonSerializeAsToken() {
|
||||
// JIS makes read()/readBytes() return bytes of the current file, but we want to hash the entire container here.
|
||||
require(jar !is JarInputStream)
|
||||
|
||||
val bytes = getBytes(jar)
|
||||
val bytes = jar.readBytes()
|
||||
|
||||
val sha256 = bytes.sha256()
|
||||
if (sha256 !in files.keys) {
|
||||
val baseAttachment = MockAttachment({ bytes }, sha256)
|
||||
val attachment = if (contractClassNames == null || contractClassNames.isEmpty()) baseAttachment else ContractAttachment(baseAttachment, contractClassNames.first(), contractClassNames.toSet(), uploader)
|
||||
files[sha256] = Pair(attachment, bytes)
|
||||
_files[sha256] = Pair(attachment, bytes)
|
||||
}
|
||||
return sha256
|
||||
}
|
||||
}
|
||||
}
|
@ -1 +1 @@
|
||||
net.corda.testing.services.FlowStackSnapshotFactoryImpl
|
||||
net.corda.testing.internal.FlowStackSnapshotFactoryImpl
|
Reference in New Issue
Block a user