mirror of
https://github.com/corda/corda.git
synced 2025-06-24 02:04:12 +00:00
Merged in plt-141-moves-only-part-2 (pull request #92)
File moves to core module and split of interfaces and implementations in preparation for further moves
This commit is contained in:
60
core/src/main/kotlin/core/node/ServiceHub.kt
Normal file
60
core/src/main/kotlin/core/node/ServiceHub.kt
Normal file
@ -0,0 +1,60 @@
|
||||
package core.node
|
||||
|
||||
import core.*
|
||||
import core.crypto.SecureHash
|
||||
import core.messaging.MessagingService
|
||||
import core.node.services.IdentityService
|
||||
import core.node.subsystems.*
|
||||
import core.utilities.RecordingMap
|
||||
import java.time.Clock
|
||||
|
||||
/**
|
||||
* A service hub simply vends references to the other services a node has. Some of those services may be missing or
|
||||
* mocked out. This class is useful to pass to chunks of pluggable code that might have need of many different kinds of
|
||||
* functionality and you don't want to hard-code which types in the interface.
|
||||
*
|
||||
* TODO: Split into a public (to contracts etc) and private (to node) view
|
||||
*/
|
||||
interface ServiceHub {
|
||||
val walletService: WalletService
|
||||
val keyManagementService: KeyManagementService
|
||||
val identityService: IdentityService
|
||||
val storageService: StorageService
|
||||
val networkService: MessagingService
|
||||
val networkMapCache: NetworkMapCache
|
||||
val monitoringService: MonitoringService
|
||||
val clock: Clock
|
||||
|
||||
/**
|
||||
* Given a [LedgerTransaction], looks up all its dependencies in the local database, uses the identity service to map
|
||||
* the [SignedTransaction]s the DB gives back into [LedgerTransaction]s, and then runs the smart contracts for the
|
||||
* transaction. If no exception is thrown, the transaction is valid.
|
||||
*/
|
||||
fun verifyTransaction(ltx: LedgerTransaction) {
|
||||
val dependencies = ltx.inputs.map {
|
||||
storageService.validatedTransactions[it.txhash] ?: throw TransactionResolutionException(it.txhash)
|
||||
}
|
||||
val ltxns = dependencies.map { it.verifyToLedgerTransaction(identityService, storageService.attachments) }
|
||||
TransactionGroup(setOf(ltx), ltxns.toSet()).verify()
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a list of [SignedTransaction]s, writes them to the local storage for validated transactions and then
|
||||
* sends them to the wallet for further processing.
|
||||
*
|
||||
* TODO: Need to come up with a way for preventing transactions being written other than by this method.
|
||||
* TODO: RecordingMap is test infrastructure. Refactor it away or find a way to ensure it's only used in tests.
|
||||
*
|
||||
* @param txs The transactions to record
|
||||
* @param skipRecordingMap This is used in unit testing and can be ignored most of the time.
|
||||
*/
|
||||
fun recordTransactions(txs: List<SignedTransaction>, skipRecordingMap: Boolean = false) {
|
||||
val txns: Map<SecureHash, SignedTransaction> = txs.groupBy { it.id }.mapValues { it.value.first() }
|
||||
val txStorage = storageService.validatedTransactions
|
||||
if (txStorage is RecordingMap && skipRecordingMap)
|
||||
txStorage.putAllUnrecorded(txns)
|
||||
else
|
||||
txStorage.putAll(txns)
|
||||
walletService.notifyAll(txs.map { it.tx })
|
||||
}
|
||||
}
|
43
core/src/main/kotlin/core/node/storage/CheckpointStorage.kt
Normal file
43
core/src/main/kotlin/core/node/storage/CheckpointStorage.kt
Normal file
@ -0,0 +1,43 @@
|
||||
package core.node.storage
|
||||
|
||||
import core.crypto.sha256
|
||||
import core.protocols.ProtocolStateMachine
|
||||
import core.serialization.SerializedBytes
|
||||
|
||||
/**
|
||||
* Thread-safe storage of fiber checkpoints.
|
||||
*
|
||||
* TODO: Make internal to node again once split [ServiceHub] into a public (to contracts etc) and private (to node) view
|
||||
*/
|
||||
interface CheckpointStorage {
|
||||
|
||||
/**
|
||||
* Add a new checkpoint to the store.
|
||||
*/
|
||||
fun addCheckpoint(checkpoint: Checkpoint)
|
||||
|
||||
/**
|
||||
* Remove existing checkpoint from the store. It is an error to attempt to remove a checkpoint which doesn't exist
|
||||
* in the store. Doing so will throw an [IllegalArgumentException].
|
||||
*/
|
||||
fun removeCheckpoint(checkpoint: Checkpoint)
|
||||
|
||||
/**
|
||||
* Returns a snapshot of all the checkpoints in the store.
|
||||
* This may return more checkpoints than were added to this instance of the store; for example if the store persists
|
||||
* checkpoints to disk.
|
||||
*/
|
||||
val checkpoints: Iterable<Checkpoint>
|
||||
|
||||
}
|
||||
|
||||
// This class will be serialised, so everything it points to transitively must also be serialisable (with Kryo).
|
||||
data class Checkpoint(
|
||||
val serialisedFiber: SerializedBytes<out ProtocolStateMachine<*>>,
|
||||
val awaitingTopic: String,
|
||||
val awaitingObjectOfType: String // java class name
|
||||
) {
|
||||
override fun toString(): String {
|
||||
return "Checkpoint(#serialisedFiber=${serialisedFiber.sha256()}, awaitingTopic=$awaitingTopic, awaitingObjectOfType=$awaitingObjectOfType)"
|
||||
}
|
||||
}
|
93
core/src/main/kotlin/core/node/subsystems/NetworkMapCache.kt
Normal file
93
core/src/main/kotlin/core/node/subsystems/NetworkMapCache.kt
Normal file
@ -0,0 +1,93 @@
|
||||
package core.node.subsystems
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import core.Contract
|
||||
import core.Party
|
||||
import core.messaging.MessagingService
|
||||
import core.node.NodeInfo
|
||||
import core.node.services.ServiceType
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
/**
|
||||
* A network map contains lists of nodes on the network along with information about their identity keys, services
|
||||
* they provide and host names or IP addresses where they can be connected to. The cache wraps around a map fetched
|
||||
* from an authoritative service, and adds easy lookup of the data stored within it. Generally it would be initialised
|
||||
* with a specified network map service, which it fetches data from and then subscribes to updates of.
|
||||
*/
|
||||
interface NetworkMapCache {
|
||||
companion object {
|
||||
val logger = LoggerFactory.getLogger(NetworkMapCache::class.java)
|
||||
}
|
||||
|
||||
/** A list of nodes that advertise a network map service */
|
||||
val networkMapNodes: List<NodeInfo>
|
||||
/** A list of nodes that advertise a timestamping service */
|
||||
val timestampingNodes: List<NodeInfo>
|
||||
/** A list of nodes that advertise a rates oracle service */
|
||||
val ratesOracleNodes: List<NodeInfo>
|
||||
/** A list of all nodes the cache is aware of */
|
||||
val partyNodes: List<NodeInfo>
|
||||
/** A list of nodes that advertise a regulatory service. Identifying the correct regulator for a trade is outwith
|
||||
* the scope of the network map service, and this is intended solely as a sanity check on configuration stored
|
||||
* elsewhere.
|
||||
*/
|
||||
val regulators: List<NodeInfo>
|
||||
|
||||
/**
|
||||
* Look up the node info for a party.
|
||||
*/
|
||||
fun nodeForPartyName(name: String): NodeInfo? = partyNodes.singleOrNull { it.identity.name == name }
|
||||
|
||||
/**
|
||||
* Get a copy of all nodes in the map.
|
||||
*/
|
||||
fun get(): Collection<NodeInfo>
|
||||
|
||||
/**
|
||||
* Get the collection of nodes which advertise a specific service.
|
||||
*/
|
||||
fun get(serviceType: ServiceType): Collection<NodeInfo>
|
||||
|
||||
/**
|
||||
* Get a recommended node that advertises a service, and is suitable for the specified contract and parties.
|
||||
* Implementations might understand, for example, the correct regulator to use for specific contracts/parties,
|
||||
* or the appropriate oracle for a contract.
|
||||
*/
|
||||
fun getRecommended(type: ServiceType, contract: Contract, vararg party: Party): NodeInfo?
|
||||
|
||||
/**
|
||||
* Add a network map service; fetches a copy of the latest map from the service and subscribes to any further
|
||||
* updates.
|
||||
*
|
||||
* @param net the network messaging service
|
||||
* @param service the network map service to fetch current state from.
|
||||
* @param subscribe if the cache should subscribe to updates
|
||||
* @param ifChangedSinceVer an optional version number to limit updating the map based on. If the latest map
|
||||
* version is less than or equal to the given version, no update is fetched.
|
||||
*/
|
||||
fun addMapService(net: MessagingService, service: NodeInfo,
|
||||
subscribe: Boolean, ifChangedSinceVer: Int? = null): ListenableFuture<Unit>
|
||||
|
||||
/**
|
||||
* Adds a node to the local cache (generally only used for adding ourselves)
|
||||
*/
|
||||
fun addNode(node: NodeInfo)
|
||||
|
||||
/**
|
||||
* Removes a node from the local cache
|
||||
*/
|
||||
fun removeNode(node: NodeInfo)
|
||||
|
||||
/**
|
||||
* Deregister from updates from the given map service.
|
||||
*
|
||||
* @param net the network messaging service
|
||||
* @param service the network map service to fetch current state from.
|
||||
*/
|
||||
fun deregisterForUpdates(net: MessagingService, service: NodeInfo): ListenableFuture<Unit>
|
||||
}
|
||||
|
||||
sealed class NetworkCacheError : Exception() {
|
||||
/** Indicates a failure to deregister, because of a rejected request from the remote node */
|
||||
class DeregistrationFailed : NetworkCacheError()
|
||||
}
|
147
core/src/main/kotlin/core/node/subsystems/Services.kt
Normal file
147
core/src/main/kotlin/core/node/subsystems/Services.kt
Normal file
@ -0,0 +1,147 @@
|
||||
package core.node.subsystems
|
||||
|
||||
import com.codahale.metrics.MetricRegistry
|
||||
import core.*
|
||||
import core.crypto.SecureHash
|
||||
import core.node.services.AttachmentStorage
|
||||
import core.node.storage.CheckpointStorage
|
||||
import java.security.KeyPair
|
||||
import java.security.PrivateKey
|
||||
import java.security.PublicKey
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Postfix for base topics when sending a request to a service.
|
||||
*/
|
||||
val TOPIC_DEFAULT_POSTFIX = ".0"
|
||||
|
||||
/**
|
||||
* This file defines various 'services' which are not currently fleshed out. A service is a module that provides
|
||||
* immutable snapshots of data that may be changing in response to user or network events.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A wallet (name may be temporary) wraps a set of states that are useful for us to keep track of, for instance,
|
||||
* because we own them. This class represents an immutable, stable state of a wallet: it is guaranteed not to
|
||||
* change out from underneath you, even though the canonical currently-best-known wallet may change as we learn
|
||||
* about new transactions from our peers and generate new transactions that consume states ourselves.
|
||||
*
|
||||
* This absract class has no references to Cash contracts.
|
||||
*/
|
||||
abstract class Wallet {
|
||||
abstract val states: List<StateAndRef<ContractState>>
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
inline fun <reified T : OwnableState> statesOfType() = states.filter { it.state is T } as List<StateAndRef<T>>
|
||||
|
||||
/**
|
||||
* Returns a map of how much cash we have in each currency, ignoring details like issuer. Note: currencies for
|
||||
* which we have no cash evaluate to null (not present in map), not 0.
|
||||
*/
|
||||
abstract val cashBalances: Map<Currency, Amount>
|
||||
}
|
||||
|
||||
/**
|
||||
* A [WalletService] is responsible for securely and safely persisting the current state of a wallet to storage. The
|
||||
* wallet service vends immutable snapshots of the current wallet for working with: if you build a transaction based
|
||||
* on a wallet that isn't current, be aware that it may end up being invalid if the states that were used have been
|
||||
* consumed by someone else first!
|
||||
*/
|
||||
interface WalletService {
|
||||
/**
|
||||
* Returns a read-only snapshot of the wallet at the time the call is made. Note that if you consume states or
|
||||
* keys in this wallet, you must inform the wallet service so it can update its internal state.
|
||||
*/
|
||||
val currentWallet: Wallet
|
||||
|
||||
/**
|
||||
* Returns a snapshot of how much cash we have in each currency, ignoring details like issuer. Note: currencies for
|
||||
* which we have no cash evaluate to null, not 0.
|
||||
*/
|
||||
val cashBalances: Map<Currency, Amount>
|
||||
|
||||
/**
|
||||
* Returns a snapshot of the heads of LinearStates
|
||||
*/
|
||||
val linearHeads: Map<SecureHash, StateAndRef<LinearState>>
|
||||
|
||||
// TODO: When KT-10399 is fixed, rename this and remove the inline version below.
|
||||
|
||||
/** Returns the [linearHeads] only when the type of the state would be considered an 'instanceof' the given type. */
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T : LinearState> linearHeadsOfType_(stateType: Class<T>): Map<SecureHash, StateAndRef<T>> {
|
||||
return linearHeads.filterValues { stateType.isInstance(it.state) }.mapValues { StateAndRef(it.value.state as T, it.value.ref) }
|
||||
}
|
||||
|
||||
fun statesForRefs(refs: List<StateRef>): Map<StateRef, ContractState?> {
|
||||
val refsToStates = currentWallet.states.associateBy { it.ref }
|
||||
return refs.associateBy({ it }, { refsToStates[it]?.state })
|
||||
}
|
||||
|
||||
/**
|
||||
* Possibly update the wallet by marking as spent states that these transactions consume, and adding any relevant
|
||||
* new states that they create. You should only insert transactions that have been successfully verified here!
|
||||
*
|
||||
* Returns the new wallet that resulted from applying the transactions (note: it may quickly become out of date).
|
||||
*
|
||||
* TODO: Consider if there's a good way to enforce the must-be-verified requirement in the type system.
|
||||
*/
|
||||
fun notifyAll(txns: Iterable<WireTransaction>): Wallet
|
||||
|
||||
/** Same as notifyAll but with a single transaction. */
|
||||
fun notify(tx: WireTransaction): Wallet = notifyAll(listOf(tx))
|
||||
}
|
||||
|
||||
inline fun <reified T : LinearState> WalletService.linearHeadsOfType() = linearHeadsOfType_(T::class.java)
|
||||
|
||||
/**
|
||||
* The KMS is responsible for storing and using private keys to sign things. An implementation of this may, for example,
|
||||
* call out to a hardware security module that enforces various auditing and frequency-of-use requirements.
|
||||
*
|
||||
* The current interface is obviously not usable for those use cases: this is just where we'd put a real signing
|
||||
* interface if/when one is developed.
|
||||
*/
|
||||
interface KeyManagementService {
|
||||
/** Returns a snapshot of the current pubkey->privkey mapping. */
|
||||
val keys: Map<PublicKey, PrivateKey>
|
||||
|
||||
fun toPrivate(publicKey: PublicKey) = keys[publicKey] ?: throw IllegalStateException("No private key known for requested public key")
|
||||
|
||||
fun toKeyPair(publicKey: PublicKey) = KeyPair(publicKey, toPrivate(publicKey))
|
||||
|
||||
/** Generates a new random key and adds it to the exposed map. */
|
||||
fun freshKey(): KeyPair
|
||||
}
|
||||
|
||||
/**
|
||||
* A sketch of an interface to a simple key/value storage system. Intended for persistence of simple blobs like
|
||||
* transactions, serialised protocol state machines and so on. Again, this isn't intended to imply lack of SQL or
|
||||
* anything like that, this interface is only big enough to support the prototyping work.
|
||||
*/
|
||||
interface StorageService {
|
||||
/**
|
||||
* A map of hash->tx where tx has been signature/contract validated and the states are known to be correct.
|
||||
* The signatures aren't technically needed after that point, but we keep them around so that we can relay
|
||||
* the transaction data to other nodes that need it.
|
||||
*/
|
||||
val validatedTransactions: MutableMap<SecureHash, SignedTransaction>
|
||||
|
||||
val checkpointStorage: CheckpointStorage
|
||||
|
||||
/** Provides access to storage of arbitrary JAR files (which may contain only data, no code). */
|
||||
val attachments: AttachmentStorage
|
||||
|
||||
/**
|
||||
* Returns the legal identity that this node is configured with. Assumed to be initialised when the node is
|
||||
* first installed.
|
||||
*/
|
||||
val myLegalIdentity: Party
|
||||
val myLegalIdentityKey: KeyPair
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides access to various metrics and ways to notify monitoring services of things, for sysadmin purposes.
|
||||
* This is not an interface because it is too lightweight to bother mocking out.
|
||||
*/
|
||||
class MonitoringService(val metrics: MetricRegistry)
|
||||
|
26
core/src/main/kotlin/core/protocols/ProtocolStateMachine.kt
Normal file
26
core/src/main/kotlin/core/protocols/ProtocolStateMachine.kt
Normal file
@ -0,0 +1,26 @@
|
||||
package core.protocols
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import core.messaging.MessageRecipients
|
||||
import core.node.ServiceHub
|
||||
import core.utilities.UntrustworthyData
|
||||
import org.slf4j.Logger
|
||||
|
||||
|
||||
/**
|
||||
* The interface of [ProtocolStateMachineImpl] exposing methods and properties required by ProtocolLogic for compilation
|
||||
*/
|
||||
interface ProtocolStateMachine<R> {
|
||||
@Suspendable
|
||||
fun <T : Any> sendAndReceive(topic: String, destination: MessageRecipients, sessionIDForSend: Long, sessionIDForReceive: Long,
|
||||
obj: Any, recvType: Class<T>): UntrustworthyData<T>
|
||||
|
||||
@Suspendable
|
||||
fun <T : Any> receive(topic: String, sessionIDForReceive: Long, recvType: Class<T>): UntrustworthyData<T>
|
||||
|
||||
@Suspendable
|
||||
fun send(topic: String, destination: MessageRecipients, sessionID: Long, obj: Any)
|
||||
|
||||
val serviceHub: ServiceHub
|
||||
val logger: Logger
|
||||
}
|
Reference in New Issue
Block a user