mirror of
https://github.com/corda/corda.git
synced 2025-06-22 17:09:00 +00:00
Persitent network map and key service. Temporary persistence workaround for scheduler.
This commit is contained in:
@ -4,7 +4,6 @@ import com.google.common.util.concurrent.ListenableFuture
|
|||||||
import com.google.common.util.concurrent.SettableFuture
|
import com.google.common.util.concurrent.SettableFuture
|
||||||
import com.r3corda.core.contracts.*
|
import com.r3corda.core.contracts.*
|
||||||
import com.r3corda.core.crypto.Party
|
import com.r3corda.core.crypto.Party
|
||||||
import com.r3corda.core.crypto.SecureHash
|
|
||||||
import com.r3corda.core.transactions.WireTransaction
|
import com.r3corda.core.transactions.WireTransaction
|
||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
import java.security.PrivateKey
|
import java.security.PrivateKey
|
||||||
@ -29,13 +28,13 @@ val DEFAULT_SESSION_ID = 0L
|
|||||||
*
|
*
|
||||||
* This abstract class has no references to Cash contracts.
|
* This abstract class has no references to Cash contracts.
|
||||||
*
|
*
|
||||||
* [states] Holds the list of states that are *active* and *relevant*.
|
* [states] Holds the states that are *active* and *relevant*.
|
||||||
* Active means they haven't been consumed yet (or we don't know about it).
|
* Active means they haven't been consumed yet (or we don't know about it).
|
||||||
* Relevant means they contain at least one of our pubkeys.
|
* Relevant means they contain at least one of our pubkeys.
|
||||||
*/
|
*/
|
||||||
class Wallet(val states: Iterable<StateAndRef<ContractState>>) {
|
class Wallet(val states: Iterable<StateAndRef<ContractState>>) {
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
inline fun <reified T : OwnableState> statesOfType() = states.filter { it.state.data is T } as List<StateAndRef<T>>
|
inline fun <reified T : ContractState> statesOfType() = states.filter { it.state.data is T } as List<StateAndRef<T>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents an update observed by the Wallet that will be notified to observers. Include the [StateRef]s of
|
* Represents an update observed by the Wallet that will be notified to observers. Include the [StateRef]s of
|
||||||
@ -57,7 +56,8 @@ class Wallet(val states: Iterable<StateAndRef<ContractState>>) {
|
|||||||
val previouslyConsumed = consumed
|
val previouslyConsumed = consumed
|
||||||
val combined = Wallet.Update(
|
val combined = Wallet.Update(
|
||||||
previouslyConsumed + (rhs.consumed - previouslyProduced),
|
previouslyConsumed + (rhs.consumed - previouslyProduced),
|
||||||
rhs.produced + produced.filter { it.ref !in rhs.consumed })
|
// The ordering below matters to preserve ordering of consumed/produced Sets when they are insertion order dependent implementations.
|
||||||
|
produced.filter { it.ref !in rhs.consumed }.toSet() + rhs.produced)
|
||||||
return combined
|
return combined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,21 +2,25 @@ package com.r3corda.node.utilities
|
|||||||
|
|
||||||
import com.r3corda.testing.node.makeTestDataSourceProperties
|
import com.r3corda.testing.node.makeTestDataSourceProperties
|
||||||
import junit.framework.TestSuite
|
import junit.framework.TestSuite
|
||||||
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.jetbrains.exposed.sql.Transaction
|
import org.jetbrains.exposed.sql.Transaction
|
||||||
import org.jetbrains.exposed.sql.transactions.TransactionManager
|
import org.jetbrains.exposed.sql.transactions.TransactionManager
|
||||||
import org.junit.AfterClass
|
import org.junit.AfterClass
|
||||||
import org.junit.BeforeClass
|
import org.junit.BeforeClass
|
||||||
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.junit.runners.Suite
|
import org.junit.runners.Suite
|
||||||
import java.io.Closeable
|
import java.io.Closeable
|
||||||
import java.sql.Connection
|
import java.sql.Connection
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
@RunWith(Suite::class)
|
@RunWith(Suite::class)
|
||||||
@Suite.SuiteClasses(
|
@Suite.SuiteClasses(
|
||||||
JDBCHashMapTestSuite.MapLoadOnInitFalse::class,
|
JDBCHashMapTestSuite.MapLoadOnInitFalse::class,
|
||||||
JDBCHashMapTestSuite.MapLoadOnInitTrue::class,
|
JDBCHashMapTestSuite.MapLoadOnInitTrue::class,
|
||||||
JDBCHashMapTestSuite.SetLoadOnInitFalse::class,
|
JDBCHashMapTestSuite.SetLoadOnInitFalse::class,
|
||||||
JDBCHashMapTestSuite.SetLoadOnInitTrue::class)
|
JDBCHashMapTestSuite.SetLoadOnInitTrue::class,
|
||||||
|
JDBCHashMapTestSuite.MapCanBeReloaded::class)
|
||||||
class JDBCHashMapTestSuite {
|
class JDBCHashMapTestSuite {
|
||||||
companion object {
|
companion object {
|
||||||
lateinit var dataSource: Closeable
|
lateinit var dataSource: Closeable
|
||||||
@ -144,4 +148,70 @@ class JDBCHashMapTestSuite {
|
|||||||
return set
|
return set
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that the contents of a map can be reloaded from the database.
|
||||||
|
*
|
||||||
|
* If the Map reloads, then so will the Set as it just delegates.
|
||||||
|
*/
|
||||||
|
class MapCanBeReloaded {
|
||||||
|
private val ops = listOf(Triple(AddOrRemove.ADD, "A", "1"),
|
||||||
|
Triple(AddOrRemove.ADD, "B", "2"),
|
||||||
|
Triple(AddOrRemove.ADD, "C", "3"),
|
||||||
|
Triple(AddOrRemove.ADD, "D", "4"),
|
||||||
|
Triple(AddOrRemove.ADD, "E", "5"),
|
||||||
|
Triple(AddOrRemove.REMOVE, "A", "6"),
|
||||||
|
Triple(AddOrRemove.ADD, "G", "7"),
|
||||||
|
Triple(AddOrRemove.ADD, "H", "8"),
|
||||||
|
Triple(AddOrRemove.REMOVE, "D", "9"),
|
||||||
|
Triple(AddOrRemove.ADD, "C", "10"))
|
||||||
|
|
||||||
|
private fun applyOpsToMap(map: MutableMap<String, String>): MutableMap<String, String> {
|
||||||
|
for (op in ops) {
|
||||||
|
if (op.first == AddOrRemove.ADD) {
|
||||||
|
map[op.second] = op.third
|
||||||
|
} else {
|
||||||
|
map.remove(op.second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map
|
||||||
|
}
|
||||||
|
|
||||||
|
private val transientMapForComparison = applyOpsToMap(LinkedHashMap())
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
lateinit var dataSource: Closeable
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@BeforeClass
|
||||||
|
fun before() {
|
||||||
|
dataSource = configureDatabase(makeTestDataSourceProperties()).first
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@AfterClass
|
||||||
|
fun after() {
|
||||||
|
dataSource.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `fill map and check content after reconstruction`() {
|
||||||
|
databaseTransaction {
|
||||||
|
val persistentMap = JDBCHashMap<String, String>("the_table")
|
||||||
|
// Populate map the first time.
|
||||||
|
applyOpsToMap(persistentMap)
|
||||||
|
assertThat(persistentMap.entries).containsExactly(*transientMapForComparison.entries.toTypedArray())
|
||||||
|
}
|
||||||
|
databaseTransaction {
|
||||||
|
val persistentMap = JDBCHashMap<String, String>("the_table", loadOnInit = false)
|
||||||
|
assertThat(persistentMap.entries).containsExactly(*transientMapForComparison.entries.toTypedArray())
|
||||||
|
}
|
||||||
|
databaseTransaction {
|
||||||
|
val persistentMap = JDBCHashMap<String, String>("the_table", loadOnInit = true)
|
||||||
|
assertThat(persistentMap.entries).containsExactly(*transientMapForComparison.entries.toTypedArray())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,13 +28,13 @@ import com.r3corda.node.services.config.NodeConfiguration
|
|||||||
import com.r3corda.node.services.events.NodeSchedulerService
|
import com.r3corda.node.services.events.NodeSchedulerService
|
||||||
import com.r3corda.node.services.events.ScheduledActivityObserver
|
import com.r3corda.node.services.events.ScheduledActivityObserver
|
||||||
import com.r3corda.node.services.identity.InMemoryIdentityService
|
import com.r3corda.node.services.identity.InMemoryIdentityService
|
||||||
import com.r3corda.node.services.keys.E2ETestKeyManagementService
|
import com.r3corda.node.services.keys.PersistentKeyManagementService
|
||||||
import com.r3corda.node.services.monitor.WalletMonitorService
|
import com.r3corda.node.services.monitor.WalletMonitorService
|
||||||
import com.r3corda.node.services.network.InMemoryNetworkMapCache
|
import com.r3corda.node.services.network.InMemoryNetworkMapCache
|
||||||
import com.r3corda.node.services.network.InMemoryNetworkMapService
|
|
||||||
import com.r3corda.node.services.network.NetworkMapService
|
import com.r3corda.node.services.network.NetworkMapService
|
||||||
import com.r3corda.node.services.network.NetworkMapService.Companion.REGISTER_PROTOCOL_TOPIC
|
import com.r3corda.node.services.network.NetworkMapService.Companion.REGISTER_PROTOCOL_TOPIC
|
||||||
import com.r3corda.node.services.network.NodeRegistration
|
import com.r3corda.node.services.network.NodeRegistration
|
||||||
|
import com.r3corda.node.services.network.PersistentNetworkMapService
|
||||||
import com.r3corda.node.services.persistence.NodeAttachmentService
|
import com.r3corda.node.services.persistence.NodeAttachmentService
|
||||||
import com.r3corda.node.services.persistence.PerFileCheckpointStorage
|
import com.r3corda.node.services.persistence.PerFileCheckpointStorage
|
||||||
import com.r3corda.node.services.persistence.PerFileTransactionStorage
|
import com.r3corda.node.services.persistence.PerFileTransactionStorage
|
||||||
@ -296,17 +296,17 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration,
|
|||||||
"Initial network map address must indicate a node that provides a network map service"
|
"Initial network map address must indicate a node that provides a network map service"
|
||||||
}
|
}
|
||||||
services.networkMapCache.addNode(info)
|
services.networkMapCache.addNode(info)
|
||||||
if (networkMapService != null) {
|
|
||||||
// Only register if we are pointed at a network map service and it's not us.
|
|
||||||
// TODO: Return a future so the caller knows these operations may not have completed yet, and can monitor if needed
|
|
||||||
updateRegistration(networkMapService, AddOrRemove.ADD)
|
|
||||||
return services.networkMapCache.addMapService(net, networkMapService, true, null)
|
|
||||||
}
|
|
||||||
// In the unit test environment, we may run without any network map service sometimes.
|
// In the unit test environment, we may run without any network map service sometimes.
|
||||||
if (inNodeNetworkMapService == null)
|
if (networkMapService == null && inNodeNetworkMapService == null)
|
||||||
return noNetworkMapConfigured()
|
return noNetworkMapConfigured()
|
||||||
|
else
|
||||||
|
return registerWithNetworkMap(networkMapService ?: info.address)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun registerWithNetworkMap(networkMapServiceAddress: SingleMessageRecipient): ListenableFuture<Unit> {
|
||||||
// Register for updates, even if we're the one running the network map.
|
// Register for updates, even if we're the one running the network map.
|
||||||
return services.networkMapCache.addMapService(net, info.address, true, null)
|
updateRegistration(networkMapServiceAddress, AddOrRemove.ADD)
|
||||||
|
return services.networkMapCache.addMapService(net, networkMapServiceAddress, true, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** This is overriden by the mock node implementation to enable operation without any network map service */
|
/** This is overriden by the mock node implementation to enable operation without any network map service */
|
||||||
@ -318,8 +318,9 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration,
|
|||||||
|
|
||||||
private fun updateRegistration(networkMapAddr: SingleMessageRecipient, type: AddOrRemove): ListenableFuture<NetworkMapService.RegistrationResponse> {
|
private fun updateRegistration(networkMapAddr: SingleMessageRecipient, type: AddOrRemove): ListenableFuture<NetworkMapService.RegistrationResponse> {
|
||||||
// Register this node against the network
|
// Register this node against the network
|
||||||
val expires = platformClock.instant() + NetworkMapService.DEFAULT_EXPIRATION_PERIOD
|
val instant = platformClock.instant()
|
||||||
val reg = NodeRegistration(info, networkMapSeq++, type, expires)
|
val expires = instant + NetworkMapService.DEFAULT_EXPIRATION_PERIOD
|
||||||
|
val reg = NodeRegistration(info, instant.toEpochMilli(), type, expires)
|
||||||
val sessionID = random63BitValue()
|
val sessionID = random63BitValue()
|
||||||
val request = NetworkMapService.RegistrationRequest(reg.toWire(storage.myLegalIdentityKey.private), net.myAddress, sessionID)
|
val request = NetworkMapService.RegistrationRequest(reg.toWire(storage.myLegalIdentityKey.private), net.myAddress, sessionID)
|
||||||
val message = net.createMessage(REGISTER_PROTOCOL_TOPIC, DEFAULT_SESSION_ID, request.serialize().bits)
|
val message = net.createMessage(REGISTER_PROTOCOL_TOPIC, DEFAULT_SESSION_ID, request.serialize().bits)
|
||||||
@ -333,12 +334,10 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration,
|
|||||||
return future
|
return future
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun makeKeyManagementService(): KeyManagementService = E2ETestKeyManagementService(setOf(storage.myLegalIdentityKey))
|
protected open fun makeKeyManagementService(): KeyManagementService = PersistentKeyManagementService(setOf(storage.myLegalIdentityKey))
|
||||||
|
|
||||||
open protected fun makeNetworkMapService() {
|
open protected fun makeNetworkMapService() {
|
||||||
val expires = platformClock.instant() + NetworkMapService.DEFAULT_EXPIRATION_PERIOD
|
inNodeNetworkMapService = PersistentNetworkMapService(services)
|
||||||
val reg = NodeRegistration(info, Long.MAX_VALUE, AddOrRemove.ADD, expires)
|
|
||||||
inNodeNetworkMapService = InMemoryNetworkMapService(services, reg)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
open protected fun makeNotaryService(type: ServiceType): NotaryService {
|
open protected fun makeNotaryService(type: ServiceType): NotaryService {
|
||||||
|
@ -2,6 +2,7 @@ package com.r3corda.node.services.api
|
|||||||
|
|
||||||
import com.google.common.util.concurrent.ListenableFuture
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
import com.r3corda.core.messaging.Message
|
import com.r3corda.core.messaging.Message
|
||||||
|
import com.r3corda.core.messaging.MessageHandlerRegistration
|
||||||
import com.r3corda.core.node.services.DEFAULT_SESSION_ID
|
import com.r3corda.core.node.services.DEFAULT_SESSION_ID
|
||||||
import com.r3corda.core.protocols.ProtocolLogic
|
import com.r3corda.core.protocols.ProtocolLogic
|
||||||
import com.r3corda.core.serialization.SingletonSerializeAsToken
|
import com.r3corda.core.serialization.SingletonSerializeAsToken
|
||||||
@ -36,8 +37,8 @@ abstract class AbstractNodeService(val services: ServiceHubInternal) : Singleton
|
|||||||
protected inline fun <reified Q : ServiceRequestMessage, reified R : Any>
|
protected inline fun <reified Q : ServiceRequestMessage, reified R : Any>
|
||||||
addMessageHandler(topic: String,
|
addMessageHandler(topic: String,
|
||||||
crossinline handler: (Q) -> R,
|
crossinline handler: (Q) -> R,
|
||||||
crossinline exceptionConsumer: (Message, Exception) -> Unit) {
|
crossinline exceptionConsumer: (Message, Exception) -> Unit): MessageHandlerRegistration {
|
||||||
net.addMessageHandler(topic, DEFAULT_SESSION_ID, null) { message, r ->
|
return net.addMessageHandler(topic, DEFAULT_SESSION_ID, null) { message, r ->
|
||||||
try {
|
try {
|
||||||
val request = message.data.deserialize<Q>()
|
val request = message.data.deserialize<Q>()
|
||||||
val response = handler(request)
|
val response = handler(request)
|
||||||
@ -62,8 +63,8 @@ abstract class AbstractNodeService(val services: ServiceHubInternal) : Singleton
|
|||||||
*/
|
*/
|
||||||
protected inline fun <reified Q : ServiceRequestMessage, reified R : Any>
|
protected inline fun <reified Q : ServiceRequestMessage, reified R : Any>
|
||||||
addMessageHandler(topic: String,
|
addMessageHandler(topic: String,
|
||||||
crossinline handler: (Q) -> R) {
|
crossinline handler: (Q) -> R): MessageHandlerRegistration {
|
||||||
addMessageHandler(topic, handler, { message: Message, exception: Exception -> throw exception })
|
return addMessageHandler(topic, handler, { message: Message, exception: Exception -> throw exception })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -18,6 +18,13 @@ class ScheduledActivityObserver(val services: ServiceHubInternal) {
|
|||||||
update.consumed.forEach { services.schedulerService.unscheduleStateActivity(it) }
|
update.consumed.forEach { services.schedulerService.unscheduleStateActivity(it) }
|
||||||
update.produced.forEach { scheduleStateActivity(it, services.protocolLogicRefFactory) }
|
update.produced.forEach { scheduleStateActivity(it, services.protocolLogicRefFactory) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In the short term, to get restart-able IRS demo, re-initialise from wallet state
|
||||||
|
// TODO: there's a race condition here. We need to move persistence into the scheduler but that is a bigger
|
||||||
|
// change so I want to revisit as a distinct branch/PR.
|
||||||
|
for (state in services.walletService.currentWallet.statesOfType<SchedulableState>()) {
|
||||||
|
scheduleStateActivity(state, services.protocolLogicRefFactory)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun scheduleStateActivity(produced: StateAndRef<ContractState>, protocolLogicRefFactory: ProtocolLogicRefFactory) {
|
private fun scheduleStateActivity(produced: StateAndRef<ContractState>, protocolLogicRefFactory: ProtocolLogicRefFactory) {
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
package com.r3corda.node.services.keys
|
||||||
|
|
||||||
|
import com.r3corda.core.ThreadBox
|
||||||
|
import com.r3corda.core.crypto.generateKeyPair
|
||||||
|
import com.r3corda.core.node.services.KeyManagementService
|
||||||
|
import com.r3corda.core.serialization.SingletonSerializeAsToken
|
||||||
|
import com.r3corda.node.utilities.JDBCHashMap
|
||||||
|
import java.security.KeyPair
|
||||||
|
import java.security.PrivateKey
|
||||||
|
import java.security.PublicKey
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A persistent re-implementation of [E2ETestKeyManagementService] to support node re-start.
|
||||||
|
*
|
||||||
|
* This is not the long-term implementation. See the list of items in the above class.
|
||||||
|
*
|
||||||
|
* This class needs database transactions to be in-flight during method calls and init.
|
||||||
|
*/
|
||||||
|
class PersistentKeyManagementService(initialKeys: Set<KeyPair>) : SingletonSerializeAsToken(), KeyManagementService {
|
||||||
|
private class InnerState {
|
||||||
|
val keys = JDBCHashMap<PublicKey, PrivateKey>("key_pairs", loadOnInit = false)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val mutex = ThreadBox(InnerState())
|
||||||
|
|
||||||
|
init {
|
||||||
|
mutex.locked {
|
||||||
|
keys.putAll(initialKeys.associate { Pair(it.public, it.private) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val keys: Map<PublicKey, PrivateKey> get() = mutex.locked { HashMap(keys) }
|
||||||
|
|
||||||
|
override fun freshKey(): KeyPair {
|
||||||
|
val keypair = generateKeyPair()
|
||||||
|
mutex.locked {
|
||||||
|
keys[keypair.public] = keypair.private
|
||||||
|
}
|
||||||
|
return keypair
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,7 @@ import com.r3corda.core.crypto.DigitalSignature
|
|||||||
import com.r3corda.core.crypto.Party
|
import com.r3corda.core.crypto.Party
|
||||||
import com.r3corda.core.crypto.SignedData
|
import com.r3corda.core.crypto.SignedData
|
||||||
import com.r3corda.core.crypto.signWithECDSA
|
import com.r3corda.core.crypto.signWithECDSA
|
||||||
|
import com.r3corda.core.messaging.MessageHandlerRegistration
|
||||||
import com.r3corda.core.messaging.MessageRecipients
|
import com.r3corda.core.messaging.MessageRecipients
|
||||||
import com.r3corda.core.messaging.SingleMessageRecipient
|
import com.r3corda.core.messaging.SingleMessageRecipient
|
||||||
import com.r3corda.core.node.NodeInfo
|
import com.r3corda.core.node.NodeInfo
|
||||||
@ -82,13 +83,13 @@ interface NetworkMapService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ThreadSafe
|
@ThreadSafe
|
||||||
class InMemoryNetworkMapService(services: ServiceHubInternal, home: NodeRegistration) : AbstractNetworkMapService(services) {
|
class InMemoryNetworkMapService(services: ServiceHubInternal) : AbstractNetworkMapService(services) {
|
||||||
|
|
||||||
override val registeredNodes: MutableMap<Party, NodeRegistrationInfo> = ConcurrentHashMap()
|
override val registeredNodes: MutableMap<Party, NodeRegistrationInfo> = ConcurrentHashMap()
|
||||||
override val subscribers = ThreadBox(mutableMapOf<SingleMessageRecipient, LastAcknowledgeInfo>())
|
override val subscribers = ThreadBox(mutableMapOf<SingleMessageRecipient, LastAcknowledgeInfo>())
|
||||||
|
|
||||||
init {
|
init {
|
||||||
setup(home)
|
setup()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,7 +100,8 @@ class InMemoryNetworkMapService(services: ServiceHubInternal, home: NodeRegistra
|
|||||||
* subscriber clean up and is simpler to persist than the previous implementation based on a set of missing messages acks.
|
* subscriber clean up and is simpler to persist than the previous implementation based on a set of missing messages acks.
|
||||||
*/
|
*/
|
||||||
@ThreadSafe
|
@ThreadSafe
|
||||||
abstract class AbstractNetworkMapService(services: ServiceHubInternal) : NetworkMapService, AbstractNodeService(services) {
|
abstract class AbstractNetworkMapService
|
||||||
|
(services: ServiceHubInternal) : NetworkMapService, AbstractNodeService(services) {
|
||||||
protected abstract val registeredNodes: MutableMap<Party, NodeRegistrationInfo>
|
protected abstract val registeredNodes: MutableMap<Party, NodeRegistrationInfo>
|
||||||
|
|
||||||
// Map from subscriber address, to most recently acknowledged update map version.
|
// Map from subscriber address, to most recently acknowledged update map version.
|
||||||
@ -121,35 +123,38 @@ abstract class AbstractNetworkMapService(services: ServiceHubInternal) : Network
|
|||||||
*/
|
*/
|
||||||
val maxSizeRegistrationRequestBytes = 5500
|
val maxSizeRegistrationRequestBytes = 5500
|
||||||
|
|
||||||
|
private val handlers = ArrayList<MessageHandlerRegistration>()
|
||||||
|
|
||||||
// Filter reduces this to the entries that add a node to the map
|
// Filter reduces this to the entries that add a node to the map
|
||||||
override val nodes: List<NodeInfo>
|
override val nodes: List<NodeInfo>
|
||||||
get() = registeredNodes.mapNotNull { if (it.value.reg.type == AddOrRemove.ADD) it.value.reg.node else null }
|
get() = registeredNodes.mapNotNull { if (it.value.reg.type == AddOrRemove.ADD) it.value.reg.node else null }
|
||||||
|
|
||||||
protected fun setup(home: NodeRegistration) {
|
protected fun setup() {
|
||||||
// Register the local node with the service
|
|
||||||
val homeIdentity = home.node.identity
|
|
||||||
val registrationInfo = NodeRegistrationInfo(home, mapVersionIncrementAndGet())
|
|
||||||
registeredNodes[homeIdentity] = registrationInfo
|
|
||||||
|
|
||||||
// Register message handlers
|
// Register message handlers
|
||||||
addMessageHandler(NetworkMapService.FETCH_PROTOCOL_TOPIC,
|
handlers += addMessageHandler(NetworkMapService.FETCH_PROTOCOL_TOPIC,
|
||||||
{ req: NetworkMapService.FetchMapRequest -> processFetchAllRequest(req) }
|
{ req: NetworkMapService.FetchMapRequest -> processFetchAllRequest(req) }
|
||||||
)
|
)
|
||||||
addMessageHandler(NetworkMapService.QUERY_PROTOCOL_TOPIC,
|
handlers += addMessageHandler(NetworkMapService.QUERY_PROTOCOL_TOPIC,
|
||||||
{ req: NetworkMapService.QueryIdentityRequest -> processQueryRequest(req) }
|
{ req: NetworkMapService.QueryIdentityRequest -> processQueryRequest(req) }
|
||||||
)
|
)
|
||||||
addMessageHandler(NetworkMapService.REGISTER_PROTOCOL_TOPIC,
|
handlers += addMessageHandler(NetworkMapService.REGISTER_PROTOCOL_TOPIC,
|
||||||
{ req: NetworkMapService.RegistrationRequest -> processRegistrationChangeRequest(req) }
|
{ req: NetworkMapService.RegistrationRequest -> processRegistrationChangeRequest(req) }
|
||||||
)
|
)
|
||||||
addMessageHandler(NetworkMapService.SUBSCRIPTION_PROTOCOL_TOPIC,
|
handlers += addMessageHandler(NetworkMapService.SUBSCRIPTION_PROTOCOL_TOPIC,
|
||||||
{ req: NetworkMapService.SubscribeRequest -> processSubscriptionRequest(req) }
|
{ req: NetworkMapService.SubscribeRequest -> processSubscriptionRequest(req) }
|
||||||
)
|
)
|
||||||
net.addMessageHandler(NetworkMapService.PUSH_ACK_PROTOCOL_TOPIC, DEFAULT_SESSION_ID, null) { message, r ->
|
handlers += net.addMessageHandler(NetworkMapService.PUSH_ACK_PROTOCOL_TOPIC, DEFAULT_SESSION_ID, null) { message, r ->
|
||||||
val req = message.data.deserialize<NetworkMapService.UpdateAcknowledge>()
|
val req = message.data.deserialize<NetworkMapService.UpdateAcknowledge>()
|
||||||
processAcknowledge(req)
|
processAcknowledge(req)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: notify subscribers of name service registration. Network service is not up, so how?
|
@VisibleForTesting
|
||||||
|
fun unregisterNetworkHandlers() {
|
||||||
|
for (handler in handlers) {
|
||||||
|
net.removeMessageHandler(handler)
|
||||||
|
}
|
||||||
|
handlers.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addSubscriber(subscriber: MessageRecipients) {
|
private fun addSubscriber(subscriber: MessageRecipients) {
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
package com.r3corda.node.services.network
|
||||||
|
|
||||||
|
import com.r3corda.core.ThreadBox
|
||||||
|
import com.r3corda.core.crypto.Party
|
||||||
|
import com.r3corda.core.messaging.SingleMessageRecipient
|
||||||
|
import com.r3corda.node.services.api.ServiceHubInternal
|
||||||
|
import com.r3corda.node.utilities.JDBCHashMap
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A network map service backed by a database to survive restarts of the node hosting it.
|
||||||
|
*
|
||||||
|
* Majority of the logic is inherited from [AbstractNetworkMapService].
|
||||||
|
*
|
||||||
|
* This class needs database transactions to be in-flight during method calls and init, otherwise it will throw
|
||||||
|
* exceptions.
|
||||||
|
*/
|
||||||
|
class PersistentNetworkMapService(services: ServiceHubInternal) : AbstractNetworkMapService(services) {
|
||||||
|
|
||||||
|
override val registeredNodes: MutableMap<Party, NodeRegistrationInfo> = Collections.synchronizedMap(JDBCHashMap("network_map_nodes", loadOnInit = true))
|
||||||
|
|
||||||
|
override val subscribers = ThreadBox(JDBCHashMap<SingleMessageRecipient, LastAcknowledgeInfo>("network_map_subscribers", loadOnInit = true))
|
||||||
|
|
||||||
|
init {
|
||||||
|
// Initialise the network map version with the current highest persisted version, or zero if there are no entries.
|
||||||
|
_mapVersion.set(registeredNodes.values.map { it.mapVersion }.max() ?: 0)
|
||||||
|
setup()
|
||||||
|
}
|
||||||
|
}
|
@ -1,85 +1,135 @@
|
|||||||
package com.r3corda.node.services.wallet
|
package com.r3corda.node.services.wallet
|
||||||
|
|
||||||
import com.r3corda.core.contracts.ContractState
|
import com.google.common.collect.Sets
|
||||||
import com.r3corda.core.contracts.StateAndRef
|
import com.r3corda.core.contracts.*
|
||||||
import com.r3corda.core.contracts.StateRef
|
|
||||||
import com.r3corda.core.crypto.SecureHash
|
import com.r3corda.core.crypto.SecureHash
|
||||||
import com.r3corda.core.node.ServiceHub
|
import com.r3corda.core.node.ServiceHub
|
||||||
import com.r3corda.core.node.services.Wallet
|
import com.r3corda.core.node.services.Wallet
|
||||||
import com.r3corda.core.testing.InMemoryWalletService
|
import com.r3corda.core.node.services.WalletService
|
||||||
|
import com.r3corda.core.serialization.SingletonSerializeAsToken
|
||||||
|
import com.r3corda.core.transactions.WireTransaction
|
||||||
import com.r3corda.core.utilities.loggerFor
|
import com.r3corda.core.utilities.loggerFor
|
||||||
import com.r3corda.core.utilities.trace
|
import com.r3corda.core.utilities.trace
|
||||||
import com.r3corda.node.utilities.databaseTransaction
|
import com.r3corda.node.utilities.AbstractJDBCHashSet
|
||||||
import org.jetbrains.exposed.sql.*
|
import com.r3corda.node.utilities.JDBCHashedTable
|
||||||
import org.jetbrains.exposed.sql.SchemaUtils.create
|
import org.jetbrains.exposed.sql.ResultRow
|
||||||
|
import org.jetbrains.exposed.sql.statements.InsertStatement
|
||||||
|
import rx.Observable
|
||||||
|
import rx.subjects.PublishSubject
|
||||||
|
import java.security.PublicKey
|
||||||
|
import java.util.concurrent.locks.ReentrantLock
|
||||||
|
import kotlin.concurrent.withLock
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Currently, the node wallet service is a very simple RDBMS backed implementation. It will change significantly when
|
* Currently, the node wallet service is a very simple RDBMS backed implementation. It will change significantly when
|
||||||
* we add further functionality as the design for the wallet and wallet service matures.
|
* we add further functionality as the design for the wallet and wallet service matures.
|
||||||
*
|
*
|
||||||
|
* This class needs database transactions to be in-flight during method calls and init, and will throw exceptions if
|
||||||
|
* this is not the case.
|
||||||
|
*
|
||||||
* TODO: move query / filter criteria into the database query.
|
* TODO: move query / filter criteria into the database query.
|
||||||
* TODO: keep an audit trail with time stamps of previously unconsumed states "as of" a particular point in time.
|
* TODO: keep an audit trail with time stamps of previously unconsumed states "as of" a particular point in time.
|
||||||
* TODO: have transaction storage do some caching.
|
* TODO: have transaction storage do some caching.
|
||||||
*/
|
*/
|
||||||
class NodeWalletService(services: ServiceHub) : InMemoryWalletService(services) {
|
class NodeWalletService(private val services: ServiceHub) : SingletonSerializeAsToken(), WalletService {
|
||||||
|
|
||||||
override val log = loggerFor<NodeWalletService>()
|
private companion object {
|
||||||
|
val log = loggerFor<NodeWalletService>()
|
||||||
// For now we are just tracking the current state, with no historical reporting ability.
|
|
||||||
private object UnconsumedStates : Table("vault_unconsumed_states") {
|
|
||||||
val txhash = binary("transaction_id", 32).primaryKey()
|
|
||||||
val index = integer("output_index").primaryKey()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
private object StatesSetTable : JDBCHashedTable("vault_unconsumed_states") {
|
||||||
// TODO: at some future point, we'll use some schema creation tool to deploy database artifacts if the database
|
val txhash = binary("transaction_id", 32)
|
||||||
// is not yet initalised to the right version of the schema.
|
val index = integer("output_index")
|
||||||
createTablesIfNecessary()
|
|
||||||
|
|
||||||
// Note that our wallet implementation currently does nothing with respect to attempting to apply criteria in the database.
|
|
||||||
mutex.locked { wallet = Wallet(allUnconsumedStates()) }
|
|
||||||
|
|
||||||
// Now we need to make sure we listen to updates
|
|
||||||
updates.subscribe { recordUpdate(it) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun recordUpdate(update: Wallet.Update) {
|
private val unconsumedStates = object : AbstractJDBCHashSet<StateRef, StatesSetTable>(StatesSetTable) {
|
||||||
val producedStateRefs = update.produced.map { it.ref }
|
override fun elementFromRow(it: ResultRow): StateRef = StateRef(SecureHash.SHA256(it[table.txhash]), it[table.index])
|
||||||
val consumedStateRefs = update.consumed
|
|
||||||
log.trace { "Removing $consumedStateRefs consumed contract states and adding $producedStateRefs produced contract states to the database." }
|
override fun addElementToInsert(insert: InsertStatement, entry: StateRef, finalizables: MutableList<() -> Unit>) {
|
||||||
databaseTransaction {
|
insert[table.txhash] = entry.txhash.bits
|
||||||
// Note we also remove the produced in case we are re-inserting in some form of recovery situation.
|
insert[table.index] = entry.index
|
||||||
for (consumed in (consumedStateRefs + producedStateRefs)) {
|
|
||||||
UnconsumedStates.deleteWhere {
|
|
||||||
(UnconsumedStates.txhash eq consumed.txhash.bits) and (UnconsumedStates.index eq consumed.index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (produced in producedStateRefs) {
|
|
||||||
UnconsumedStates.insert {
|
|
||||||
it[txhash] = produced.txhash.bits
|
|
||||||
it[index] = produced.index
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createTablesIfNecessary() {
|
protected val mutex = ReentrantLock()
|
||||||
log.trace { "Creating database tables if necessary." }
|
|
||||||
databaseTransaction {
|
override val currentWallet: Wallet get() = mutex.withLock { Wallet(allUnconsumedStates()) }
|
||||||
create(UnconsumedStates)
|
|
||||||
|
private val _updatesPublisher = PublishSubject.create<Wallet.Update>()
|
||||||
|
|
||||||
|
override val updates: Observable<Wallet.Update>
|
||||||
|
get() = _updatesPublisher
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a snapshot of the heads of LinearStates.
|
||||||
|
*
|
||||||
|
* TODO: Represent this using an actual JDBCHashMap or look at vault design further.
|
||||||
|
*/
|
||||||
|
override val linearHeads: Map<UniqueIdentifier, StateAndRef<LinearState>>
|
||||||
|
get() = currentWallet.states.filterStatesOfType<LinearState>().associateBy { it.state.data.linearId }.mapValues { it.value }
|
||||||
|
|
||||||
|
override fun notifyAll(txns: Iterable<WireTransaction>): Wallet {
|
||||||
|
val ourKeys = services.keyManagementService.keys.keys
|
||||||
|
val netDelta = txns.fold(Wallet.NoUpdate) { netDelta, txn -> netDelta + makeUpdate(txn, netDelta, ourKeys) }
|
||||||
|
if (netDelta != Wallet.NoUpdate) {
|
||||||
|
mutex.withLock {
|
||||||
|
recordUpdate(netDelta)
|
||||||
|
}
|
||||||
|
_updatesPublisher.onNext(netDelta)
|
||||||
}
|
}
|
||||||
|
return currentWallet
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun makeUpdate(tx: WireTransaction, netDelta: Wallet.Update, ourKeys: Set<PublicKey>): Wallet.Update {
|
||||||
|
val ourNewStates = tx.outputs.
|
||||||
|
filter { isRelevant(it.data, ourKeys) }.
|
||||||
|
map { tx.outRef<ContractState>(it.data) }
|
||||||
|
|
||||||
|
// Now calculate the states that are being spent by this transaction.
|
||||||
|
val consumed = tx.inputs.toHashSet()
|
||||||
|
// We use Guava union here as it's lazy for contains() which is how retainAll() is implemented.
|
||||||
|
// i.e. retainAll() iterates over consumed, checking contains() on the parameter. Sets.union() does not physically create
|
||||||
|
// a new collection and instead contains() just checks the contains() of both parameters, and so we don't end up
|
||||||
|
// iterating over all (a potentially very large) unconsumedStates at any point.
|
||||||
|
consumed.retainAll(Sets.union(netDelta.produced, unconsumedStates))
|
||||||
|
|
||||||
|
// Is transaction irrelevant?
|
||||||
|
if (consumed.isEmpty() && ourNewStates.isEmpty()) {
|
||||||
|
log.trace { "tx ${tx.id} was irrelevant to this wallet, ignoring" }
|
||||||
|
return Wallet.NoUpdate
|
||||||
|
}
|
||||||
|
|
||||||
|
return Wallet.Update(consumed, ourNewStates.toHashSet())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isRelevant(state: ContractState, ourKeys: Set<PublicKey>): Boolean {
|
||||||
|
return if (state is OwnableState) {
|
||||||
|
state.owner in ourKeys
|
||||||
|
} else if (state is LinearState) {
|
||||||
|
// It's potentially of interest to the wallet
|
||||||
|
state.isRelevant(ourKeys)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun recordUpdate(update: Wallet.Update): Wallet.Update {
|
||||||
|
if (update != Wallet.NoUpdate) {
|
||||||
|
val producedStateRefs = update.produced.map { it.ref }
|
||||||
|
val consumedStateRefs = update.consumed
|
||||||
|
log.trace { "Removing $consumedStateRefs consumed contract states and adding $producedStateRefs produced contract states to the database." }
|
||||||
|
unconsumedStates.removeAll(consumedStateRefs)
|
||||||
|
unconsumedStates.addAll(producedStateRefs)
|
||||||
|
}
|
||||||
|
return update
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun allUnconsumedStates(): Iterable<StateAndRef<ContractState>> {
|
private fun allUnconsumedStates(): Iterable<StateAndRef<ContractState>> {
|
||||||
// Order by txhash for if and when transaction storage has some caching.
|
// Order by txhash for if and when transaction storage has some caching.
|
||||||
// Map to StateRef and then to StateAndRef.
|
// Map to StateRef and then to StateAndRef. Use Sequence to avoid conversion to ArrayList that Iterable.map() performs.
|
||||||
return databaseTransaction {
|
return unconsumedStates.asSequence().map {
|
||||||
UnconsumedStates.selectAll().orderBy(UnconsumedStates.txhash)
|
val storedTx = services.storageService.validatedTransactions.getTransaction(it.txhash) ?: throw Error("Found transaction hash ${it.txhash} in unconsumed contract states that is not in transaction storage.")
|
||||||
.map { StateRef(SecureHash.SHA256(it[UnconsumedStates.txhash]), it[UnconsumedStates.index]) }
|
StateAndRef(storedTx.tx.outputs[it.index], it)
|
||||||
.map {
|
}.asIterable()
|
||||||
val storedTx = services.storageService.validatedTransactions.getTransaction(it.txhash) ?: throw Error("Found transaction hash ${it.txhash} in unconsumed contract states that is not in transaction storage.")
|
|
||||||
StateAndRef(storedTx.tx.outputs[it.index], it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -336,6 +336,7 @@ abstract class AbstractJDBCHashMap<K : Any, V : Any, T : JDBCHashedTable>(val ta
|
|||||||
|
|
||||||
override fun put(key: K, value: V): V? {
|
override fun put(key: K, value: V): V? {
|
||||||
var oldValue: V? = null
|
var oldValue: V? = null
|
||||||
|
var oldSeqNo: Int? = null
|
||||||
getBucket(key)
|
getBucket(key)
|
||||||
buckets.compute(key.hashCode()) { hashCode, list ->
|
buckets.compute(key.hashCode()) { hashCode, list ->
|
||||||
val newList = list ?: newBucket()
|
val newList = list ?: newBucket()
|
||||||
@ -344,12 +345,13 @@ abstract class AbstractJDBCHashMap<K : Any, V : Any, T : JDBCHashedTable>(val ta
|
|||||||
val entry = iterator.next()
|
val entry = iterator.next()
|
||||||
if (entry.key == key) {
|
if (entry.key == key) {
|
||||||
oldValue = entry.value
|
oldValue = entry.value
|
||||||
|
oldSeqNo = entry.seqNo
|
||||||
iterator.remove()
|
iterator.remove()
|
||||||
deleteRecord(entry)
|
deleteRecord(entry)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val seqNo = addRecord(key, value)
|
val seqNo = addRecord(key, value, oldSeqNo)
|
||||||
val newEntry = NotReallyMutableEntry<K, V>(key, value, seqNo)
|
val newEntry = NotReallyMutableEntry<K, V>(key, value, seqNo)
|
||||||
newList.add(newEntry)
|
newList.add(newEntry)
|
||||||
newList
|
newList
|
||||||
@ -450,7 +452,7 @@ abstract class AbstractJDBCHashMap<K : Any, V : Any, T : JDBCHashedTable>(val ta
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addRecord(key: K, value: V): Int {
|
private fun addRecord(key: K, value: V, oldSeqNo: Int?): Int {
|
||||||
val finalizables = mutableListOf<() -> Unit>()
|
val finalizables = mutableListOf<() -> Unit>()
|
||||||
try {
|
try {
|
||||||
return table.insert {
|
return table.insert {
|
||||||
@ -458,6 +460,10 @@ abstract class AbstractJDBCHashMap<K : Any, V : Any, T : JDBCHashedTable>(val ta
|
|||||||
val entry = SimpleEntry<K, V>(key, value)
|
val entry = SimpleEntry<K, V>(key, value)
|
||||||
addKeyToInsert(it, entry, finalizables)
|
addKeyToInsert(it, entry, finalizables)
|
||||||
addValueToInsert(it, entry, finalizables)
|
addValueToInsert(it, entry, finalizables)
|
||||||
|
if (oldSeqNo != null) {
|
||||||
|
it[seqNo] = oldSeqNo
|
||||||
|
it.generatedKey = oldSeqNo
|
||||||
|
}
|
||||||
} get table.seqNo
|
} get table.seqNo
|
||||||
} finally {
|
} finally {
|
||||||
finalizables.forEach { it() }
|
finalizables.forEach { it() }
|
||||||
|
@ -1,25 +1,14 @@
|
|||||||
package com.r3corda.node.services
|
package com.r3corda.node.services
|
||||||
|
|
||||||
import com.google.common.util.concurrent.ListenableFuture
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
import com.google.common.util.concurrent.SettableFuture
|
|
||||||
import com.r3corda.core.map
|
import com.r3corda.core.map
|
||||||
import com.r3corda.core.messaging.TopicSession
|
|
||||||
import com.r3corda.core.messaging.runOnNextMessage
|
|
||||||
import com.r3corda.core.messaging.send
|
|
||||||
import com.r3corda.core.random63BitValue
|
import com.r3corda.core.random63BitValue
|
||||||
import com.r3corda.core.serialization.deserialize
|
import com.r3corda.node.services.network.AbstractNetworkMapService
|
||||||
import com.r3corda.node.services.network.InMemoryNetworkMapService
|
import com.r3corda.node.services.network.InMemoryNetworkMapService
|
||||||
import com.r3corda.node.services.network.NetworkMapService
|
import com.r3corda.node.services.network.NetworkMapService
|
||||||
import com.r3corda.node.services.network.NetworkMapService.*
|
|
||||||
import com.r3corda.node.services.network.NetworkMapService.Companion.FETCH_PROTOCOL_TOPIC
|
|
||||||
import com.r3corda.node.services.network.NetworkMapService.Companion.PUSH_ACK_PROTOCOL_TOPIC
|
|
||||||
import com.r3corda.node.services.network.NetworkMapService.Companion.REGISTER_PROTOCOL_TOPIC
|
|
||||||
import com.r3corda.node.services.network.NetworkMapService.Companion.SUBSCRIPTION_PROTOCOL_TOPIC
|
|
||||||
import com.r3corda.node.services.network.NodeRegistration
|
import com.r3corda.node.services.network.NodeRegistration
|
||||||
import com.r3corda.node.utilities.AddOrRemove
|
import com.r3corda.node.utilities.AddOrRemove
|
||||||
import com.r3corda.protocols.ServiceRequestMessage
|
|
||||||
import com.r3corda.testing.node.MockNetwork
|
import com.r3corda.testing.node.MockNetwork
|
||||||
import com.r3corda.testing.node.MockNetwork.MockNode
|
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.security.PrivateKey
|
import java.security.PrivateKey
|
||||||
@ -30,7 +19,164 @@ import kotlin.test.assertNotNull
|
|||||||
import kotlin.test.assertNull
|
import kotlin.test.assertNull
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
class InMemoryNetworkMapServiceTest {
|
/**
|
||||||
|
* Abstracted out test logic to be re-used by [PersistentNetworkMapServiceTest].
|
||||||
|
*/
|
||||||
|
abstract class AbstractNetworkMapServiceTest {
|
||||||
|
|
||||||
|
protected fun success(mapServiceNode: MockNetwork.MockNode,
|
||||||
|
registerNode: MockNetwork.MockNode,
|
||||||
|
service: () -> AbstractNetworkMapService,
|
||||||
|
swizzle: () -> Unit) {
|
||||||
|
// For persistent service, switch out the implementation for a newly instantiated one so we can check the state is preserved.
|
||||||
|
swizzle()
|
||||||
|
|
||||||
|
// Confirm the service contains no nodes as own node only registered if network is run.
|
||||||
|
assertEquals(0, service().nodes.count())
|
||||||
|
assertNull(service().processQueryRequest(NetworkMapService.QueryIdentityRequest(registerNode.info.identity, mapServiceNode.info.address, Long.MIN_VALUE)).node)
|
||||||
|
|
||||||
|
// Register the new node
|
||||||
|
val instant = Instant.now()
|
||||||
|
val expires = instant + NetworkMapService.DEFAULT_EXPIRATION_PERIOD
|
||||||
|
val nodeKey = registerNode.storage.myLegalIdentityKey
|
||||||
|
val addChange = NodeRegistration(registerNode.info, instant.toEpochMilli(), AddOrRemove.ADD, expires)
|
||||||
|
val addWireChange = addChange.toWire(nodeKey.private)
|
||||||
|
service().processRegistrationChangeRequest(NetworkMapService.RegistrationRequest(addWireChange, mapServiceNode.info.address, Long.MIN_VALUE))
|
||||||
|
swizzle()
|
||||||
|
|
||||||
|
assertEquals(1, service().nodes.count())
|
||||||
|
assertEquals(registerNode.info, service().processQueryRequest(NetworkMapService.QueryIdentityRequest(registerNode.info.identity, mapServiceNode.info.address, Long.MIN_VALUE)).node)
|
||||||
|
|
||||||
|
// Re-registering should be a no-op
|
||||||
|
service().processRegistrationChangeRequest(NetworkMapService.RegistrationRequest(addWireChange, mapServiceNode.info.address, Long.MIN_VALUE))
|
||||||
|
swizzle()
|
||||||
|
|
||||||
|
assertEquals(1, service().nodes.count())
|
||||||
|
|
||||||
|
// Confirm that de-registering the node succeeds and drops it from the node lists
|
||||||
|
val removeChange = NodeRegistration(registerNode.info, instant.toEpochMilli()+1, AddOrRemove.REMOVE, expires)
|
||||||
|
val removeWireChange = removeChange.toWire(nodeKey.private)
|
||||||
|
assert(service().processRegistrationChangeRequest(NetworkMapService.RegistrationRequest(removeWireChange, mapServiceNode.info.address, Long.MIN_VALUE)).success)
|
||||||
|
swizzle()
|
||||||
|
|
||||||
|
assertNull(service().processQueryRequest(NetworkMapService.QueryIdentityRequest(registerNode.info.identity, mapServiceNode.info.address, Long.MIN_VALUE)).node)
|
||||||
|
swizzle()
|
||||||
|
|
||||||
|
// Trying to de-register a node that doesn't exist should fail
|
||||||
|
assert(!service().processRegistrationChangeRequest(NetworkMapService.RegistrationRequest(removeWireChange, mapServiceNode.info.address, Long.MIN_VALUE)).success)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun `success with network`(network: MockNetwork,
|
||||||
|
mapServiceNode: MockNetwork.MockNode,
|
||||||
|
registerNode: MockNetwork.MockNode,
|
||||||
|
swizzle: () -> Unit) {
|
||||||
|
// For persistent service, switch out the implementation for a newly instantiated one so we can check the state is preserved.
|
||||||
|
swizzle()
|
||||||
|
|
||||||
|
// Confirm all nodes have registered themselves
|
||||||
|
network.runNetwork()
|
||||||
|
var fetchPsm = fetchMap(registerNode, mapServiceNode, false)
|
||||||
|
network.runNetwork()
|
||||||
|
assertEquals(2, fetchPsm.get()?.count())
|
||||||
|
|
||||||
|
// Forcibly deregister the second node
|
||||||
|
val nodeKey = registerNode.storage.myLegalIdentityKey
|
||||||
|
val instant = Instant.now()
|
||||||
|
val expires = instant + NetworkMapService.DEFAULT_EXPIRATION_PERIOD
|
||||||
|
val reg = NodeRegistration(registerNode.info, instant.toEpochMilli()+1, AddOrRemove.REMOVE, expires)
|
||||||
|
val registerPsm = registration(registerNode, mapServiceNode, reg, nodeKey.private)
|
||||||
|
network.runNetwork()
|
||||||
|
assertTrue(registerPsm.get().success)
|
||||||
|
|
||||||
|
swizzle()
|
||||||
|
|
||||||
|
// Now only map service node should be registered
|
||||||
|
fetchPsm = fetchMap(registerNode, mapServiceNode, false)
|
||||||
|
network.runNetwork()
|
||||||
|
assertEquals(mapServiceNode.info, fetchPsm.get()?.filter { it.type == AddOrRemove.ADD }?.map { it.node }?.single())
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun `subscribe with network`(network: MockNetwork,
|
||||||
|
mapServiceNode: MockNetwork.MockNode,
|
||||||
|
registerNode: MockNetwork.MockNode,
|
||||||
|
service: () -> AbstractNetworkMapService,
|
||||||
|
swizzle: () -> Unit) {
|
||||||
|
// For persistent service, switch out the implementation for a newly instantiated one so we can check the state is preserved.
|
||||||
|
swizzle()
|
||||||
|
|
||||||
|
// Test subscribing to updates
|
||||||
|
network.runNetwork()
|
||||||
|
val subscribePsm = subscribe(registerNode, mapServiceNode, true)
|
||||||
|
network.runNetwork()
|
||||||
|
subscribePsm.get()
|
||||||
|
|
||||||
|
swizzle()
|
||||||
|
|
||||||
|
val startingMapVersion = service().mapVersion
|
||||||
|
|
||||||
|
// Check the unacknowledged count is zero
|
||||||
|
assertEquals(0, service().getUnacknowledgedCount(registerNode.info.address, startingMapVersion))
|
||||||
|
|
||||||
|
// Fire off an update
|
||||||
|
val nodeKey = registerNode.storage.myLegalIdentityKey
|
||||||
|
var seq = 0L
|
||||||
|
val expires = Instant.now() + NetworkMapService.DEFAULT_EXPIRATION_PERIOD
|
||||||
|
var reg = NodeRegistration(registerNode.info, seq++, AddOrRemove.ADD, expires)
|
||||||
|
var wireReg = reg.toWire(nodeKey.private)
|
||||||
|
service().notifySubscribers(wireReg, startingMapVersion + 1)
|
||||||
|
|
||||||
|
swizzle()
|
||||||
|
|
||||||
|
// Check the unacknowledged count is one
|
||||||
|
assertEquals(1, service().getUnacknowledgedCount(registerNode.info.address, startingMapVersion + 1))
|
||||||
|
|
||||||
|
// Send in an acknowledgment and verify the count goes down
|
||||||
|
updateAcknowlege(registerNode, mapServiceNode, startingMapVersion + 1)
|
||||||
|
network.runNetwork()
|
||||||
|
|
||||||
|
swizzle()
|
||||||
|
|
||||||
|
assertEquals(0, service().getUnacknowledgedCount(registerNode.info.address, startingMapVersion + 1))
|
||||||
|
|
||||||
|
// Intentionally fill the pending acknowledgements to verify it doesn't drop subscribers before the limit
|
||||||
|
// is hit. On the last iteration overflow the pending list, and check the node is unsubscribed
|
||||||
|
for (i in 0..service().maxUnacknowledgedUpdates) {
|
||||||
|
reg = NodeRegistration(registerNode.info, seq++, AddOrRemove.ADD, expires)
|
||||||
|
wireReg = reg.toWire(nodeKey.private)
|
||||||
|
service().notifySubscribers(wireReg, i + startingMapVersion + 2)
|
||||||
|
|
||||||
|
swizzle()
|
||||||
|
|
||||||
|
if (i < service().maxUnacknowledgedUpdates) {
|
||||||
|
assertEquals(i + 1, service().getUnacknowledgedCount(registerNode.info.address, i + startingMapVersion + 2))
|
||||||
|
} else {
|
||||||
|
assertNull(service().getUnacknowledgedCount(registerNode.info.address, i + startingMapVersion + 2))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun registration(registerNode: MockNetwork.MockNode, mapServiceNode: MockNetwork.MockNode, reg: NodeRegistration, privateKey: PrivateKey): ListenableFuture<NetworkMapService.RegistrationResponse> {
|
||||||
|
val req = NetworkMapService.RegistrationRequest(reg.toWire(privateKey), registerNode.services.networkService.myAddress, random63BitValue())
|
||||||
|
return registerNode.sendAndReceive<NetworkMapService.RegistrationResponse>(NetworkMapService.REGISTER_PROTOCOL_TOPIC, mapServiceNode, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun subscribe(registerNode: MockNetwork.MockNode, mapServiceNode: MockNetwork.MockNode, subscribe: Boolean): ListenableFuture<NetworkMapService.SubscribeResponse> {
|
||||||
|
val req = NetworkMapService.SubscribeRequest(subscribe, registerNode.services.networkService.myAddress, random63BitValue())
|
||||||
|
return registerNode.sendAndReceive<NetworkMapService.SubscribeResponse>(NetworkMapService.SUBSCRIPTION_PROTOCOL_TOPIC, mapServiceNode, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateAcknowlege(registerNode: MockNetwork.MockNode, mapServiceNode: MockNetwork.MockNode, mapVersion: Int) {
|
||||||
|
val req = NetworkMapService.UpdateAcknowledge(mapVersion, registerNode.services.networkService.myAddress)
|
||||||
|
registerNode.send(NetworkMapService.PUSH_ACK_PROTOCOL_TOPIC, mapServiceNode, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fetchMap(registerNode: MockNetwork.MockNode, mapServiceNode: MockNetwork.MockNode, subscribe: Boolean, ifChangedSinceVersion: Int? = null): Future<Collection<NodeRegistration>?> {
|
||||||
|
val req = NetworkMapService.FetchMapRequest(subscribe, ifChangedSinceVersion, registerNode.services.networkService.myAddress, random63BitValue())
|
||||||
|
return registerNode.sendAndReceive<NetworkMapService.FetchMapResponse>(NetworkMapService.FETCH_PROTOCOL_TOPIC, mapServiceNode, req).map { it.nodes }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InMemoryNetworkMapServiceTest : AbstractNetworkMapServiceTest() {
|
||||||
lateinit var network: MockNetwork
|
lateinit var network: MockNetwork
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@ -45,33 +191,7 @@ class InMemoryNetworkMapServiceTest {
|
|||||||
fun success() {
|
fun success() {
|
||||||
val (mapServiceNode, registerNode) = network.createTwoNodes()
|
val (mapServiceNode, registerNode) = network.createTwoNodes()
|
||||||
val service = mapServiceNode.inNodeNetworkMapService!! as InMemoryNetworkMapService
|
val service = mapServiceNode.inNodeNetworkMapService!! as InMemoryNetworkMapService
|
||||||
|
success(mapServiceNode, registerNode, { service }, { })
|
||||||
// Confirm the service contains only its own node
|
|
||||||
assertEquals(1, service.nodes.count())
|
|
||||||
assertNull(service.processQueryRequest(QueryIdentityRequest(registerNode.info.identity, mapServiceNode.info.address, Long.MIN_VALUE)).node)
|
|
||||||
|
|
||||||
// Register the second node
|
|
||||||
var seq = 1L
|
|
||||||
val expires = Instant.now() + NetworkMapService.DEFAULT_EXPIRATION_PERIOD
|
|
||||||
val nodeKey = registerNode.storage.myLegalIdentityKey
|
|
||||||
val addChange = NodeRegistration(registerNode.info, seq++, AddOrRemove.ADD, expires)
|
|
||||||
val addWireChange = addChange.toWire(nodeKey.private)
|
|
||||||
service.processRegistrationChangeRequest(RegistrationRequest(addWireChange, mapServiceNode.info.address, Long.MIN_VALUE))
|
|
||||||
assertEquals(2, service.nodes.count())
|
|
||||||
assertEquals(mapServiceNode.info, service.processQueryRequest(QueryIdentityRequest(mapServiceNode.info.identity, mapServiceNode.info.address, Long.MIN_VALUE)).node)
|
|
||||||
|
|
||||||
// Re-registering should be a no-op
|
|
||||||
service.processRegistrationChangeRequest(RegistrationRequest(addWireChange, mapServiceNode.info.address, Long.MIN_VALUE))
|
|
||||||
assertEquals(2, service.nodes.count())
|
|
||||||
|
|
||||||
// Confirm that de-registering the node succeeds and drops it from the node lists
|
|
||||||
val removeChange = NodeRegistration(registerNode.info, seq, AddOrRemove.REMOVE, expires)
|
|
||||||
val removeWireChange = removeChange.toWire(nodeKey.private)
|
|
||||||
assert(service.processRegistrationChangeRequest(RegistrationRequest(removeWireChange, mapServiceNode.info.address, Long.MIN_VALUE)).success)
|
|
||||||
assertNull(service.processQueryRequest(QueryIdentityRequest(registerNode.info.identity, mapServiceNode.info.address, Long.MIN_VALUE)).node)
|
|
||||||
|
|
||||||
// Trying to de-register a node that doesn't exist should fail
|
|
||||||
assert(!service.processRegistrationChangeRequest(RegistrationRequest(removeWireChange, mapServiceNode.info.address, Long.MIN_VALUE)).success)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -80,93 +200,13 @@ class InMemoryNetworkMapServiceTest {
|
|||||||
|
|
||||||
// Confirm there's a network map service on node 0
|
// Confirm there's a network map service on node 0
|
||||||
assertNotNull(mapServiceNode.inNodeNetworkMapService)
|
assertNotNull(mapServiceNode.inNodeNetworkMapService)
|
||||||
|
`success with network`(network, mapServiceNode, registerNode, { })
|
||||||
// Confirm all nodes have registered themselves
|
|
||||||
network.runNetwork()
|
|
||||||
var fetchPsm = fetchMap(registerNode, mapServiceNode, false)
|
|
||||||
network.runNetwork()
|
|
||||||
assertEquals(2, fetchPsm.get()?.count())
|
|
||||||
|
|
||||||
// Forcibly deregister the second node
|
|
||||||
val nodeKey = registerNode.storage.myLegalIdentityKey
|
|
||||||
val expires = Instant.now() + NetworkMapService.DEFAULT_EXPIRATION_PERIOD
|
|
||||||
val seq = 2L
|
|
||||||
val reg = NodeRegistration(registerNode.info, seq, AddOrRemove.REMOVE, expires)
|
|
||||||
val registerPsm = registration(registerNode, mapServiceNode, reg, nodeKey.private)
|
|
||||||
network.runNetwork()
|
|
||||||
assertTrue(registerPsm.get().success)
|
|
||||||
|
|
||||||
// Now only map service node should be registered
|
|
||||||
fetchPsm = fetchMap(registerNode, mapServiceNode, false)
|
|
||||||
network.runNetwork()
|
|
||||||
assertEquals(mapServiceNode.info, fetchPsm.get()?.filter { it.type == AddOrRemove.ADD }?.map { it.node }?.single())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `subscribe with network`() {
|
fun `subscribe with network`() {
|
||||||
val (mapServiceNode, registerNode) = network.createTwoNodes()
|
val (mapServiceNode, registerNode) = network.createTwoNodes()
|
||||||
val service = (mapServiceNode.inNodeNetworkMapService as InMemoryNetworkMapService)
|
val service = (mapServiceNode.inNodeNetworkMapService as InMemoryNetworkMapService)
|
||||||
|
`subscribe with network`(network, mapServiceNode, registerNode, { service }, { })
|
||||||
// Test subscribing to updates
|
|
||||||
network.runNetwork()
|
|
||||||
val subscribePsm = subscribe(registerNode, mapServiceNode, true)
|
|
||||||
network.runNetwork()
|
|
||||||
subscribePsm.get()
|
|
||||||
|
|
||||||
val startingMapVersion = service.mapVersion
|
|
||||||
|
|
||||||
// Check the unacknowledged count is zero
|
|
||||||
assertEquals(0, service.getUnacknowledgedCount(registerNode.info.address, startingMapVersion))
|
|
||||||
|
|
||||||
// Fire off an update
|
|
||||||
val nodeKey = registerNode.storage.myLegalIdentityKey
|
|
||||||
var seq = 0L
|
|
||||||
val expires = Instant.now() + NetworkMapService.DEFAULT_EXPIRATION_PERIOD
|
|
||||||
var reg = NodeRegistration(registerNode.info, seq++, AddOrRemove.ADD, expires)
|
|
||||||
var wireReg = reg.toWire(nodeKey.private)
|
|
||||||
service.notifySubscribers(wireReg, startingMapVersion + 1)
|
|
||||||
|
|
||||||
// Check the unacknowledged count is one
|
|
||||||
assertEquals(1, service.getUnacknowledgedCount(registerNode.info.address, startingMapVersion + 1))
|
|
||||||
|
|
||||||
// Send in an acknowledgment and verify the count goes down
|
|
||||||
updateAcknowlege(registerNode, mapServiceNode, startingMapVersion + 1)
|
|
||||||
network.runNetwork()
|
|
||||||
|
|
||||||
assertEquals(0, service.getUnacknowledgedCount(registerNode.info.address, startingMapVersion + 1))
|
|
||||||
|
|
||||||
// Intentionally fill the pending acknowledgements to verify it doesn't drop subscribers before the limit
|
|
||||||
// is hit. On the last iteration overflow the pending list, and check the node is unsubscribed
|
|
||||||
for (i in 0..service.maxUnacknowledgedUpdates) {
|
|
||||||
reg = NodeRegistration(registerNode.info, seq++, AddOrRemove.ADD, expires)
|
|
||||||
wireReg = reg.toWire(nodeKey.private)
|
|
||||||
service.notifySubscribers(wireReg, i + startingMapVersion + 2)
|
|
||||||
if (i < service.maxUnacknowledgedUpdates) {
|
|
||||||
assertEquals(i + 1, service.getUnacknowledgedCount(registerNode.info.address, i + startingMapVersion + 2))
|
|
||||||
} else {
|
|
||||||
assertNull(service.getUnacknowledgedCount(registerNode.info.address, i + startingMapVersion + 2))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun registration(registerNode: MockNode, mapServiceNode: MockNode, reg: NodeRegistration, privateKey: PrivateKey): ListenableFuture<RegistrationResponse> {
|
|
||||||
val req = RegistrationRequest(reg.toWire(privateKey), registerNode.services.networkService.myAddress, random63BitValue())
|
|
||||||
return registerNode.sendAndReceive<RegistrationResponse>(REGISTER_PROTOCOL_TOPIC, mapServiceNode, req)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun subscribe(registerNode: MockNode, mapServiceNode: MockNode, subscribe: Boolean): ListenableFuture<SubscribeResponse> {
|
|
||||||
val req = SubscribeRequest(subscribe, registerNode.services.networkService.myAddress, random63BitValue())
|
|
||||||
return registerNode.sendAndReceive<SubscribeResponse>(SUBSCRIPTION_PROTOCOL_TOPIC, mapServiceNode, req)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateAcknowlege(registerNode: MockNode, mapServiceNode: MockNode, mapVersion: Int) {
|
|
||||||
val req = UpdateAcknowledge(mapVersion, registerNode.services.networkService.myAddress)
|
|
||||||
registerNode.send(PUSH_ACK_PROTOCOL_TOPIC, mapServiceNode, req)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun fetchMap(registerNode: MockNode, mapServiceNode: MockNode, subscribe: Boolean, ifChangedSinceVersion: Int? = null): Future<Collection<NodeRegistration>?> {
|
|
||||||
val req = FetchMapRequest(subscribe, ifChangedSinceVersion, registerNode.services.networkService.myAddress, random63BitValue())
|
|
||||||
return registerNode.sendAndReceive<FetchMapResponse>(FETCH_PROTOCOL_TOPIC, mapServiceNode, req).map { it.nodes }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
@ -2,15 +2,16 @@ package com.r3corda.node.services
|
|||||||
|
|
||||||
import com.r3corda.contracts.testing.fillWithSomeTestCash
|
import com.r3corda.contracts.testing.fillWithSomeTestCash
|
||||||
import com.r3corda.core.contracts.DOLLARS
|
import com.r3corda.core.contracts.DOLLARS
|
||||||
import com.r3corda.core.transactions.SignedTransaction
|
|
||||||
import com.r3corda.core.node.services.TxWritableStorageService
|
import com.r3corda.core.node.services.TxWritableStorageService
|
||||||
import com.r3corda.core.node.services.WalletService
|
import com.r3corda.core.node.services.WalletService
|
||||||
import com.r3corda.testing.node.MockServices
|
import com.r3corda.core.transactions.SignedTransaction
|
||||||
import com.r3corda.testing.node.makeTestDataSourceProperties
|
|
||||||
import com.r3corda.core.utilities.DUMMY_NOTARY
|
import com.r3corda.core.utilities.DUMMY_NOTARY
|
||||||
import com.r3corda.core.utilities.LogHelper
|
import com.r3corda.core.utilities.LogHelper
|
||||||
import com.r3corda.node.services.wallet.NodeWalletService
|
import com.r3corda.node.services.wallet.NodeWalletService
|
||||||
import com.r3corda.node.utilities.configureDatabase
|
import com.r3corda.node.utilities.configureDatabase
|
||||||
|
import com.r3corda.node.utilities.databaseTransaction
|
||||||
|
import com.r3corda.testing.node.MockServices
|
||||||
|
import com.r3corda.testing.node.makeTestDataSourceProperties
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
@ -35,37 +36,39 @@ class NodeWalletServiceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `states not local to instance`() {
|
fun `states not local to instance`() {
|
||||||
val services1 = object : MockServices() {
|
databaseTransaction {
|
||||||
override val walletService: WalletService = NodeWalletService(this)
|
val services1 = object : MockServices() {
|
||||||
|
override val walletService: WalletService = NodeWalletService(this)
|
||||||
|
|
||||||
override fun recordTransactions(txs: Iterable<SignedTransaction>) {
|
override fun recordTransactions(txs: Iterable<SignedTransaction>) {
|
||||||
for (stx in txs) {
|
for (stx in txs) {
|
||||||
storageService.validatedTransactions.addTransaction(stx)
|
storageService.validatedTransactions.addTransaction(stx)
|
||||||
walletService.notify(stx.tx)
|
walletService.notify(stx.tx)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
services1.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L))
|
||||||
services1.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L))
|
|
||||||
|
|
||||||
val w1 = services1.walletService.currentWallet
|
val w1 = services1.walletService.currentWallet
|
||||||
assertThat(w1.states).hasSize(3)
|
assertThat(w1.states).hasSize(3)
|
||||||
|
|
||||||
val originalStorage = services1.storageService
|
val originalStorage = services1.storageService
|
||||||
val services2 = object : MockServices() {
|
val services2 = object : MockServices() {
|
||||||
override val walletService: WalletService = NodeWalletService(this)
|
override val walletService: WalletService = NodeWalletService(this)
|
||||||
|
|
||||||
// We need to be able to find the same transactions as before, too.
|
// We need to be able to find the same transactions as before, too.
|
||||||
override val storageService: TxWritableStorageService get() = originalStorage
|
override val storageService: TxWritableStorageService get() = originalStorage
|
||||||
|
|
||||||
override fun recordTransactions(txs: Iterable<SignedTransaction>) {
|
override fun recordTransactions(txs: Iterable<SignedTransaction>) {
|
||||||
for (stx in txs) {
|
for (stx in txs) {
|
||||||
storageService.validatedTransactions.addTransaction(stx)
|
storageService.validatedTransactions.addTransaction(stx)
|
||||||
walletService.notify(stx.tx)
|
walletService.notify(stx.tx)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
val w2 = services2.walletService.currentWallet
|
val w2 = services2.walletService.currentWallet
|
||||||
assertThat(w2.states).hasSize(3)
|
assertThat(w2.states).hasSize(3)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,118 @@
|
|||||||
|
package com.r3corda.node.services
|
||||||
|
|
||||||
|
import com.r3corda.core.messaging.SingleMessageRecipient
|
||||||
|
import com.r3corda.core.node.NodeInfo
|
||||||
|
import com.r3corda.core.node.services.ServiceType
|
||||||
|
import com.r3corda.node.services.api.ServiceHubInternal
|
||||||
|
import com.r3corda.node.services.config.NodeConfiguration
|
||||||
|
import com.r3corda.node.services.network.AbstractNetworkMapService
|
||||||
|
import com.r3corda.node.services.network.InMemoryNetworkMapService
|
||||||
|
import com.r3corda.node.services.network.NetworkMapService
|
||||||
|
import com.r3corda.node.services.network.PersistentNetworkMapService
|
||||||
|
import com.r3corda.node.utilities.configureDatabase
|
||||||
|
import com.r3corda.node.utilities.databaseTransaction
|
||||||
|
import com.r3corda.testing.node.MockNetwork
|
||||||
|
import com.r3corda.testing.node.makeTestDataSourceProperties
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import java.io.Closeable
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.security.KeyPair
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class mirrors [InMemoryNetworkMapServiceTest] but switches in a [PersistentNetworkMapService] and
|
||||||
|
* repeatedly replaces it with new instances to check that the service correctly restores the most recent state.
|
||||||
|
*/
|
||||||
|
class PersistentNetworkMapServiceTest : AbstractNetworkMapServiceTest() {
|
||||||
|
lateinit var network: MockNetwork
|
||||||
|
lateinit var dataSource: Closeable
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
network = MockNetwork()
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
dataSource.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We use a special [NetworkMapService] that allows us to switch in a new instance at any time to check that the
|
||||||
|
* state within it is correctly restored.
|
||||||
|
*/
|
||||||
|
private class SwizzleNetworkMapService(services: ServiceHubInternal) : NetworkMapService {
|
||||||
|
var delegate: AbstractNetworkMapService = InMemoryNetworkMapService(services)
|
||||||
|
|
||||||
|
override val nodes: List<NodeInfo>
|
||||||
|
get() = delegate.nodes
|
||||||
|
|
||||||
|
fun swizzle() {
|
||||||
|
delegate.unregisterNetworkHandlers()
|
||||||
|
delegate=makeNetworkMapService(delegate.services)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun makeNetworkMapService(services: ServiceHubInternal): AbstractNetworkMapService {
|
||||||
|
return PersistentNetworkMapService(services)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private object NodeFactory : MockNetwork.Factory {
|
||||||
|
override fun create(dir: Path, config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?,
|
||||||
|
advertisedServices: Set<ServiceType>, id: Int, keyPair: KeyPair?): MockNetwork.MockNode {
|
||||||
|
return object : MockNetwork.MockNode(dir, config, network, networkMapAddr, advertisedServices, id, keyPair) {
|
||||||
|
|
||||||
|
override fun makeNetworkMapService() {
|
||||||
|
inNodeNetworkMapService = SwizzleNetworkMapService(services)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform basic tests of registering, de-registering and fetching the full network map.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun success() {
|
||||||
|
val (mapServiceNode, registerNode) = network.createTwoNodes(NodeFactory)
|
||||||
|
val service = mapServiceNode.inNodeNetworkMapService!! as SwizzleNetworkMapService
|
||||||
|
|
||||||
|
// We have to set this up after the non-persistent nodes as they install a dummy transaction manager.
|
||||||
|
dataSource = configureDatabase(makeTestDataSourceProperties()).first
|
||||||
|
|
||||||
|
databaseTransaction {
|
||||||
|
success(mapServiceNode, registerNode, { service.delegate }, {service.swizzle()})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `success with network`() {
|
||||||
|
val (mapServiceNode, registerNode) = network.createTwoNodes(NodeFactory)
|
||||||
|
|
||||||
|
// Confirm there's a network map service on node 0
|
||||||
|
val service = mapServiceNode.inNodeNetworkMapService!! as SwizzleNetworkMapService
|
||||||
|
|
||||||
|
// We have to set this up after the non-persistent nodes as they install a dummy transaction manager.
|
||||||
|
dataSource = configureDatabase(makeTestDataSourceProperties()).first
|
||||||
|
|
||||||
|
databaseTransaction {
|
||||||
|
`success with network`(network, mapServiceNode, registerNode, { service.swizzle() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `subscribe with network`() {
|
||||||
|
val (mapServiceNode, registerNode) = network.createTwoNodes(NodeFactory)
|
||||||
|
|
||||||
|
// Confirm there's a network map service on node 0
|
||||||
|
val service = mapServiceNode.inNodeNetworkMapService!! as SwizzleNetworkMapService
|
||||||
|
|
||||||
|
// We have to set this up after the non-persistent nodes as they install a dummy transaction manager.
|
||||||
|
dataSource = configureDatabase(makeTestDataSourceProperties()).first
|
||||||
|
|
||||||
|
databaseTransaction {
|
||||||
|
`subscribe with network`(network, mapServiceNode, registerNode, { service.delegate }, { service.swizzle() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,10 +12,10 @@ import com.r3corda.core.utilities.DUMMY_NOTARY_KEY
|
|||||||
import com.r3corda.core.utilities.LogHelper
|
import com.r3corda.core.utilities.LogHelper
|
||||||
import com.r3corda.node.services.wallet.NodeWalletService
|
import com.r3corda.node.services.wallet.NodeWalletService
|
||||||
import com.r3corda.node.utilities.configureDatabase
|
import com.r3corda.node.utilities.configureDatabase
|
||||||
|
import com.r3corda.node.utilities.databaseTransaction
|
||||||
|
import com.r3corda.testing.*
|
||||||
import com.r3corda.testing.node.MockServices
|
import com.r3corda.testing.node.MockServices
|
||||||
import com.r3corda.testing.node.makeTestDataSourceProperties
|
import com.r3corda.testing.node.makeTestDataSourceProperties
|
||||||
import com.r3corda.testing.DummyLinearContract
|
|
||||||
import com.r3corda.testing.*
|
|
||||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
@ -36,13 +36,16 @@ class WalletWithCashTest {
|
|||||||
fun setUp() {
|
fun setUp() {
|
||||||
LogHelper.setLevel(NodeWalletService::class)
|
LogHelper.setLevel(NodeWalletService::class)
|
||||||
dataSource = configureDatabase(makeTestDataSourceProperties()).first
|
dataSource = configureDatabase(makeTestDataSourceProperties()).first
|
||||||
services = object : MockServices() {
|
databaseTransaction {
|
||||||
override val walletService: WalletService = NodeWalletService(this)
|
services = object : MockServices() {
|
||||||
|
override val walletService: WalletService = NodeWalletService(this)
|
||||||
|
|
||||||
override fun recordTransactions(txs: Iterable<SignedTransaction>) {
|
override fun recordTransactions(txs: Iterable<SignedTransaction>) {
|
||||||
for (stx in txs) {
|
for (stx in txs) {
|
||||||
storageService.validatedTransactions.addTransaction(stx)
|
storageService.validatedTransactions.addTransaction(stx)
|
||||||
walletService.notify(stx.tx)
|
}
|
||||||
|
// Refactored to use notifyAll() as we have no other unit test for that method with multiple transactions.
|
||||||
|
walletService.notifyAll(txs.map { it.tx })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -56,102 +59,111 @@ class WalletWithCashTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun splits() {
|
fun splits() {
|
||||||
// Fix the PRNG so that we get the same splits every time.
|
databaseTransaction {
|
||||||
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L))
|
// Fix the PRNG so that we get the same splits every time.
|
||||||
|
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L))
|
||||||
|
|
||||||
val w = wallet.currentWallet
|
val w = wallet.currentWallet
|
||||||
assertEquals(3, w.states.toList().size)
|
assertEquals(3, w.states.toList().size)
|
||||||
|
|
||||||
val state = w.states.toList()[0].state.data as Cash.State
|
val state = w.states.toList()[0].state.data as Cash.State
|
||||||
assertEquals(30.45.DOLLARS `issued by` DUMMY_CASH_ISSUER, state.amount)
|
assertEquals(30.45.DOLLARS `issued by` DUMMY_CASH_ISSUER, state.amount)
|
||||||
assertEquals(services.key.public, state.owner)
|
assertEquals(services.key.public, state.owner)
|
||||||
|
|
||||||
assertEquals(34.70.DOLLARS `issued by` DUMMY_CASH_ISSUER, (w.states.toList()[2].state.data as Cash.State).amount)
|
assertEquals(34.70.DOLLARS `issued by` DUMMY_CASH_ISSUER, (w.states.toList()[2].state.data as Cash.State).amount)
|
||||||
assertEquals(34.85.DOLLARS `issued by` DUMMY_CASH_ISSUER, (w.states.toList()[1].state.data as Cash.State).amount)
|
assertEquals(34.85.DOLLARS `issued by` DUMMY_CASH_ISSUER, (w.states.toList()[1].state.data as Cash.State).amount)
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun basics() {
|
|
||||||
// A tx that sends us money.
|
|
||||||
val freshKey = services.keyManagementService.freshKey()
|
|
||||||
val usefulTX = TransactionType.General.Builder(null).apply {
|
|
||||||
Cash().generateIssue(this, 100.DOLLARS `issued by` MEGA_CORP.ref(1), freshKey.public, DUMMY_NOTARY)
|
|
||||||
signWith(MEGA_CORP_KEY)
|
|
||||||
}.toSignedTransaction()
|
|
||||||
val myOutput = usefulTX.toLedgerTransaction(services).outRef<Cash.State>(0)
|
|
||||||
|
|
||||||
// A tx that spends our money.
|
|
||||||
val spendTX = TransactionType.General.Builder(DUMMY_NOTARY).apply {
|
|
||||||
Cash().generateSpend(this, 80.DOLLARS, BOB_PUBKEY, listOf(myOutput))
|
|
||||||
signWith(freshKey)
|
|
||||||
signWith(DUMMY_NOTARY_KEY)
|
|
||||||
}.toSignedTransaction()
|
|
||||||
|
|
||||||
// A tx that doesn't send us anything.
|
|
||||||
val irrelevantTX = TransactionType.General.Builder(DUMMY_NOTARY).apply {
|
|
||||||
Cash().generateIssue(this, 100.DOLLARS `issued by` MEGA_CORP.ref(1), BOB_KEY.public, DUMMY_NOTARY)
|
|
||||||
signWith(MEGA_CORP_KEY)
|
|
||||||
signWith(DUMMY_NOTARY_KEY)
|
|
||||||
}.toSignedTransaction()
|
|
||||||
|
|
||||||
assertNull(wallet.currentWallet.cashBalances[USD])
|
|
||||||
wallet.notify(usefulTX.tx)
|
|
||||||
assertEquals(100.DOLLARS, wallet.currentWallet.cashBalances[USD])
|
|
||||||
wallet.notify(irrelevantTX.tx)
|
|
||||||
assertEquals(100.DOLLARS, wallet.currentWallet.cashBalances[USD])
|
|
||||||
wallet.notify(spendTX.tx)
|
|
||||||
assertEquals(20.DOLLARS, wallet.currentWallet.cashBalances[USD])
|
|
||||||
|
|
||||||
// TODO: Flesh out these tests as needed.
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun branchingLinearStatesFailsToVerify() {
|
|
||||||
val freshKey = services.keyManagementService.freshKey()
|
|
||||||
val linearId = UniqueIdentifier()
|
|
||||||
|
|
||||||
// Issue a linear state
|
|
||||||
val dummyIssue = TransactionType.General.Builder(notary = DUMMY_NOTARY).apply {
|
|
||||||
addOutputState(DummyLinearContract.State(linearId = linearId, participants = listOf(freshKey.public)))
|
|
||||||
addOutputState(DummyLinearContract.State(linearId = linearId, participants = listOf(freshKey.public)))
|
|
||||||
signWith(freshKey)
|
|
||||||
signWith(DUMMY_NOTARY_KEY)
|
|
||||||
}.toSignedTransaction()
|
|
||||||
|
|
||||||
assertThatThrownBy {
|
|
||||||
dummyIssue.toLedgerTransaction(services).verify()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun sequencingLinearStatesWorks() {
|
fun `issue and spend total correctly and irrelevant ignored`() {
|
||||||
val freshKey = services.keyManagementService.freshKey()
|
databaseTransaction {
|
||||||
|
// A tx that sends us money.
|
||||||
|
val freshKey = services.keyManagementService.freshKey()
|
||||||
|
val usefulTX = TransactionType.General.Builder(null).apply {
|
||||||
|
Cash().generateIssue(this, 100.DOLLARS `issued by` MEGA_CORP.ref(1), freshKey.public, DUMMY_NOTARY)
|
||||||
|
signWith(MEGA_CORP_KEY)
|
||||||
|
}.toSignedTransaction()
|
||||||
|
val myOutput = usefulTX.toLedgerTransaction(services).outRef<Cash.State>(0)
|
||||||
|
|
||||||
val linearId = UniqueIdentifier()
|
// A tx that spends our money.
|
||||||
|
val spendTX = TransactionType.General.Builder(DUMMY_NOTARY).apply {
|
||||||
|
Cash().generateSpend(this, 80.DOLLARS, BOB_PUBKEY, listOf(myOutput))
|
||||||
|
signWith(freshKey)
|
||||||
|
signWith(DUMMY_NOTARY_KEY)
|
||||||
|
}.toSignedTransaction()
|
||||||
|
|
||||||
// Issue a linear state
|
// A tx that doesn't send us anything.
|
||||||
val dummyIssue = TransactionType.General.Builder(notary = DUMMY_NOTARY).apply {
|
val irrelevantTX = TransactionType.General.Builder(DUMMY_NOTARY).apply {
|
||||||
addOutputState(DummyLinearContract.State(linearId = linearId, participants = listOf(freshKey.public)))
|
Cash().generateIssue(this, 100.DOLLARS `issued by` MEGA_CORP.ref(1), BOB_KEY.public, DUMMY_NOTARY)
|
||||||
signWith(freshKey)
|
signWith(MEGA_CORP_KEY)
|
||||||
signWith(DUMMY_NOTARY_KEY)
|
signWith(DUMMY_NOTARY_KEY)
|
||||||
}.toSignedTransaction()
|
}.toSignedTransaction()
|
||||||
|
|
||||||
dummyIssue.toLedgerTransaction(services).verify()
|
assertNull(wallet.currentWallet.cashBalances[USD])
|
||||||
|
services.recordTransactions(usefulTX)
|
||||||
|
assertEquals(100.DOLLARS, wallet.currentWallet.cashBalances[USD])
|
||||||
|
services.recordTransactions(irrelevantTX)
|
||||||
|
assertEquals(100.DOLLARS, wallet.currentWallet.cashBalances[USD])
|
||||||
|
services.recordTransactions(spendTX)
|
||||||
|
|
||||||
wallet.notify(dummyIssue.tx)
|
assertEquals(20.DOLLARS, wallet.currentWallet.cashBalances[USD])
|
||||||
assertEquals(1, wallet.currentWallet.states.toList().size)
|
|
||||||
|
|
||||||
// Move the same state
|
// TODO: Flesh out these tests as needed.
|
||||||
val dummyMove = TransactionType.General.Builder(notary = DUMMY_NOTARY).apply {
|
}
|
||||||
addOutputState(DummyLinearContract.State(linearId = linearId, participants = listOf(freshKey.public)))
|
}
|
||||||
addInputState(dummyIssue.tx.outRef<LinearState>(0))
|
|
||||||
signWith(DUMMY_NOTARY_KEY)
|
|
||||||
}.toSignedTransaction()
|
|
||||||
|
|
||||||
dummyIssue.toLedgerTransaction(services).verify()
|
|
||||||
|
|
||||||
wallet.notify(dummyMove.tx)
|
@Test
|
||||||
assertEquals(1, wallet.currentWallet.states.toList().size)
|
fun `branching LinearStates fails to verify`() {
|
||||||
|
databaseTransaction {
|
||||||
|
val freshKey = services.keyManagementService.freshKey()
|
||||||
|
val linearId = UniqueIdentifier()
|
||||||
|
|
||||||
|
// Issue a linear state
|
||||||
|
val dummyIssue = TransactionType.General.Builder(notary = DUMMY_NOTARY).apply {
|
||||||
|
addOutputState(DummyLinearContract.State(linearId = linearId, participants = listOf(freshKey.public)))
|
||||||
|
addOutputState(DummyLinearContract.State(linearId = linearId, participants = listOf(freshKey.public)))
|
||||||
|
signWith(freshKey)
|
||||||
|
signWith(DUMMY_NOTARY_KEY)
|
||||||
|
}.toSignedTransaction()
|
||||||
|
|
||||||
|
assertThatThrownBy {
|
||||||
|
dummyIssue.toLedgerTransaction(services).verify()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `sequencing LinearStates works`() {
|
||||||
|
databaseTransaction {
|
||||||
|
val freshKey = services.keyManagementService.freshKey()
|
||||||
|
|
||||||
|
val linearId = UniqueIdentifier()
|
||||||
|
|
||||||
|
// Issue a linear state
|
||||||
|
val dummyIssue = TransactionType.General.Builder(notary = DUMMY_NOTARY).apply {
|
||||||
|
addOutputState(DummyLinearContract.State(linearId = linearId, participants = listOf(freshKey.public)))
|
||||||
|
signWith(freshKey)
|
||||||
|
signWith(DUMMY_NOTARY_KEY)
|
||||||
|
}.toSignedTransaction()
|
||||||
|
|
||||||
|
dummyIssue.toLedgerTransaction(services).verify()
|
||||||
|
|
||||||
|
services.recordTransactions(dummyIssue)
|
||||||
|
assertEquals(1, wallet.currentWallet.states.toList().size)
|
||||||
|
|
||||||
|
// Move the same state
|
||||||
|
val dummyMove = TransactionType.General.Builder(notary = DUMMY_NOTARY).apply {
|
||||||
|
addOutputState(DummyLinearContract.State(linearId = linearId, participants = listOf(freshKey.public)))
|
||||||
|
addInputState(dummyIssue.tx.outRef<LinearState>(0))
|
||||||
|
signWith(DUMMY_NOTARY_KEY)
|
||||||
|
}.toSignedTransaction()
|
||||||
|
|
||||||
|
dummyIssue.toLedgerTransaction(services).verify()
|
||||||
|
|
||||||
|
services.recordTransactions(dummyMove)
|
||||||
|
assertEquals(1, wallet.currentWallet.states.toList().size)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ import com.r3corda.node.services.messaging.NodeMessagingClient
|
|||||||
import com.r3corda.node.services.network.NetworkMapService
|
import com.r3corda.node.services.network.NetworkMapService
|
||||||
import com.r3corda.node.services.persistence.NodeAttachmentService
|
import com.r3corda.node.services.persistence.NodeAttachmentService
|
||||||
import com.r3corda.node.services.transactions.SimpleNotaryService
|
import com.r3corda.node.services.transactions.SimpleNotaryService
|
||||||
|
import com.r3corda.node.utilities.databaseTransaction
|
||||||
import com.r3corda.protocols.HandshakeMessage
|
import com.r3corda.protocols.HandshakeMessage
|
||||||
import com.r3corda.protocols.NotaryProtocol
|
import com.r3corda.protocols.NotaryProtocol
|
||||||
import com.r3corda.protocols.TwoPartyTradeProtocol
|
import com.r3corda.protocols.TwoPartyTradeProtocol
|
||||||
@ -198,9 +199,11 @@ private fun runBuyer(node: Node, amount: Amount<Currency>) {
|
|||||||
// Self issue some cash.
|
// Self issue some cash.
|
||||||
//
|
//
|
||||||
// TODO: At some point this demo should be extended to have a central bank node.
|
// TODO: At some point this demo should be extended to have a central bank node.
|
||||||
node.services.fillWithSomeTestCash(300000.DOLLARS,
|
databaseTransaction {
|
||||||
outputNotary = node.info.identity, // In this demo, the buyer and notary are the same.
|
node.services.fillWithSomeTestCash(300000.DOLLARS,
|
||||||
ownedBy = node.services.keyManagementService.freshKey().public)
|
outputNotary = node.info.identity, // In this demo, the buyer and notary are the same.
|
||||||
|
ownedBy = node.services.keyManagementService.freshKey().public)
|
||||||
|
}
|
||||||
|
|
||||||
// Wait around until a node asks to start a trade with us. In a real system, this part would happen out of band
|
// Wait around until a node asks to start a trade with us. In a real system, this part would happen out of band
|
||||||
// via some other system like an exchange or maybe even a manual messaging system like Bloomberg. But for the
|
// via some other system like an exchange or maybe even a manual messaging system like Bloomberg. But for the
|
||||||
|
@ -21,10 +21,7 @@ import com.r3corda.core.utilities.loggerFor
|
|||||||
import com.r3corda.node.services.config.NodeConfiguration
|
import com.r3corda.node.services.config.NodeConfiguration
|
||||||
import com.r3corda.node.services.keys.E2ETestKeyManagementService
|
import com.r3corda.node.services.keys.E2ETestKeyManagementService
|
||||||
import com.r3corda.node.services.network.InMemoryNetworkMapService
|
import com.r3corda.node.services.network.InMemoryNetworkMapService
|
||||||
import com.r3corda.node.services.network.NetworkMapService
|
|
||||||
import com.r3corda.node.services.network.NodeRegistration
|
|
||||||
import com.r3corda.node.services.transactions.InMemoryUniquenessProvider
|
import com.r3corda.node.services.transactions.InMemoryUniquenessProvider
|
||||||
import com.r3corda.node.utilities.AddOrRemove
|
|
||||||
import com.r3corda.protocols.ServiceRequestMessage
|
import com.r3corda.protocols.ServiceRequestMessage
|
||||||
import org.jetbrains.exposed.sql.transactions.TransactionManager
|
import org.jetbrains.exposed.sql.transactions.TransactionManager
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
@ -114,9 +111,7 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun makeNetworkMapService() {
|
override fun makeNetworkMapService() {
|
||||||
val expires = platformClock.instant() + NetworkMapService.DEFAULT_EXPIRATION_PERIOD
|
inNodeNetworkMapService = InMemoryNetworkMapService(services)
|
||||||
val reg = NodeRegistration(info, Long.MAX_VALUE, AddOrRemove.ADD, expires)
|
|
||||||
inNodeNetworkMapService = InMemoryNetworkMapService(services, reg)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun generateKeyPair(): KeyPair = keyPair ?: super.generateKeyPair()
|
override fun generateKeyPair(): KeyPair = keyPair ?: super.generateKeyPair()
|
||||||
|
Reference in New Issue
Block a user