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:
Rick Parker
2016-05-13 13:22:08 +01:00
11 changed files with 106 additions and 81 deletions

View 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 })
}
}

View 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)"
}
}

View 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()
}

View 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)

View 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
}