diff --git a/.ci/api-current.txt b/.ci/api-current.txt index 9416a54a9c..84b4d41817 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -1533,7 +1533,6 @@ public final class net.corda.core.identity.IdentityUtils extends java.lang.Objec @org.jetbrains.annotations.NotNull public abstract List networkMapSnapshot() @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.NodeInfo nodeInfo() @org.jetbrains.annotations.Nullable public abstract net.corda.core.node.NodeInfo nodeInfoFromParty(net.corda.core.identity.AbstractParty) - @net.corda.core.messaging.RPCReturnsObservables @org.jetbrains.annotations.NotNull public abstract rx.Observable nodeStateObservable() @org.jetbrains.annotations.NotNull public abstract List notaryIdentities() @org.jetbrains.annotations.Nullable public abstract net.corda.core.identity.Party notaryPartyFromX500Name(net.corda.core.identity.CordaX500Name) @org.jetbrains.annotations.NotNull public abstract java.io.InputStream openAttachment(net.corda.core.crypto.SecureHash) @@ -1613,11 +1612,6 @@ public final class net.corda.core.messaging.CordaRPCOpsKt extends java.lang.Obje ## @net.corda.core.serialization.CordaSerializable public interface net.corda.core.messaging.MessageRecipients ## -@net.corda.core.serialization.CordaSerializable public final class net.corda.core.messaging.NodeState extends java.lang.Enum - protected (String, int) - public static net.corda.core.messaging.NodeState valueOf(String) - public static net.corda.core.messaging.NodeState[] values() -## @net.corda.core.DoNotImplement public interface net.corda.core.messaging.RPCOps public abstract int getProtocolVersion() ## @@ -1714,7 +1708,6 @@ public @interface net.corda.core.messaging.RPCReturnsObservables @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.ContractUpgradeService getContractUpgradeService() @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.KeyManagementService getKeyManagementService() @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.NodeInfo getMyInfo() - @org.jetbrains.annotations.NotNull public abstract rx.Observable getMyNodeStateObservable() @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.NetworkMapCache getNetworkMapCache() @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.TransactionVerifierService getTransactionVerifierService() @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.TransactionStorage getValidatedTransactions() diff --git a/build.gradle b/build.gradle index 980087902f..175f05f187 100644 --- a/build.gradle +++ b/build.gradle @@ -48,6 +48,7 @@ buildscript { ext.dependency_checker_version = '3.0.1' ext.commons_collections_version = '4.1' ext.beanutils_version = '1.9.3' + ext.crash_version = 'faba68332800f21278c5b600bf14ad55cef5989e' ext.spring_jdbc_version ='5.0.0.RELEASE' // Update 121 is required for ObjectInputFilter and at time of writing 131 was latest: diff --git a/client/jackson/src/main/kotlin/net/corda/client/jackson/StringToMethodCallParser.kt b/client/jackson/src/main/kotlin/net/corda/client/jackson/StringToMethodCallParser.kt index a79d14f591..5ff3c3b08e 100644 --- a/client/jackson/src/main/kotlin/net/corda/client/jackson/StringToMethodCallParser.kt +++ b/client/jackson/src/main/kotlin/net/corda/client/jackson/StringToMethodCallParser.kt @@ -7,7 +7,7 @@ import com.google.common.collect.HashMultimap import com.google.common.collect.Multimap import net.corda.client.jackson.StringToMethodCallParser.ParsedMethodCall import net.corda.core.CordaException -import org.slf4j.LoggerFactory +import net.corda.core.utilities.contextLogger import java.lang.reflect.Constructor import java.lang.reflect.Method import java.util.concurrent.Callable @@ -90,7 +90,7 @@ open class StringToMethodCallParser @JvmOverloads constructor( return result } - private val log = LoggerFactory.getLogger(StringToMethodCallParser::class.java)!! + private val log = contextLogger() } /** The methods that can be invoked via this parser. */ diff --git a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt index f12141f7d3..6d6b520109 100644 --- a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt +++ b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt @@ -6,16 +6,12 @@ import net.corda.core.context.Trace import net.corda.core.crypto.random63BitValue import net.corda.core.internal.concurrent.fork import net.corda.core.internal.concurrent.transpose -import net.corda.core.messaging.CordaRPCOps -import net.corda.core.messaging.NodeState import net.corda.core.messaging.RPCOps import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.serialize import net.corda.core.utilities.* -import net.corda.node.services.Permissions.Companion.invokeRpc import net.corda.node.services.messaging.RPCServerConfiguration import net.corda.nodeapi.RPCApi -import net.corda.nodeapi.User import net.corda.testing.IntegrationTest import net.corda.testing.driver.poll import net.corda.testing.internal.* @@ -242,30 +238,6 @@ class RPCStabilityTests : IntegrationTest() { } } - @Test - fun `clients receive notifications that node is shutting down`() { - val alice = User("Alice", "Alice", setOf(invokeRpc(CordaRPCOps::nodeStateObservable))) - val bob = User("Bob", "Bob", setOf(invokeRpc(CordaRPCOps::nodeStateObservable))) - val slagathor = User("Slagathor", "Slagathor", setOf(invokeRpc(CordaRPCOps::nodeStateObservable))) - val userList = listOf(alice, bob, slagathor) - val expectedMessages = ArrayList() - - rpcDriver(startNodesInProcess = true) { - val node = startNode(rpcUsers = listOf(alice, bob, slagathor)).getOrThrow() - userList.forEach { - val connection = node.rpcClientToNode().start(it.username, it.password) - val nodeStateObservable = connection.proxy.nodeStateObservable() - nodeStateObservable.subscribe { update -> - expectedMessages.add(update) - } - } - - node.stop() - } - assertEquals(userList.size, expectedMessages.size) - assertEquals(NodeState.SHUTTING_DOWN, expectedMessages.first()) - } - interface TrackSubscriberOps : RPCOps { fun subscribe(): Observable } diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClient.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClient.kt index 12fcb8bcd7..f7318b34b3 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClient.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClient.kt @@ -11,10 +11,7 @@ import net.corda.core.messaging.RPCOps import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.internal.nodeSerializationEnv -import net.corda.core.utilities.NetworkHostAndPort -import net.corda.core.utilities.loggerFor -import net.corda.core.utilities.minutes -import net.corda.core.utilities.seconds +import net.corda.core.utilities.* import net.corda.nodeapi.ArtemisTcpTransport.Companion.tcpTransport import net.corda.nodeapi.ConnectionDirection import net.corda.nodeapi.RPCApi @@ -96,7 +93,7 @@ class RPCClient( ) : this(tcpTransport(ConnectionDirection.Outbound(), hostAndPort, sslConfiguration), configuration, serializationContext) companion object { - private val log = loggerFor>() + private val log = contextLogger() } fun start( diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClientProxyHandler.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClientProxyHandler.kt index e3e2e07d84..a85a6342b2 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClientProxyHandler.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/RPCClientProxyHandler.kt @@ -22,10 +22,7 @@ import net.corda.core.internal.ThreadBox import net.corda.core.messaging.RPCOps import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.serialize -import net.corda.core.utilities.Try -import net.corda.core.utilities.debug -import net.corda.core.utilities.getOrThrow -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.* import net.corda.nodeapi.ArtemisConsumer import net.corda.nodeapi.ArtemisProducer import net.corda.nodeapi.RPCApi @@ -91,7 +88,7 @@ class RPCClientProxyHandler( private val lifeCycle = LifeCycle(State.UNSTARTED) private companion object { - val log = loggerFor() + private val log = contextLogger() // To check whether toString() is being invoked val toStringMethod: Method = Object::toString.javaMethod!! diff --git a/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt b/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt index d6d305f542..36323ec068 100644 --- a/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt +++ b/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt @@ -11,10 +11,7 @@ import net.corda.core.messaging.* import net.corda.core.node.NodeInfo import net.corda.core.node.services.Vault import net.corda.core.node.services.vault.* -import net.corda.core.utilities.OpaqueBytes -import net.corda.core.utilities.getOrThrow -import net.corda.core.utilities.loggerFor -import net.corda.core.utilities.seconds +import net.corda.core.utilities.* import net.corda.finance.DOLLARS import net.corda.finance.POUNDS import net.corda.finance.SWISS_FRANCS @@ -45,7 +42,7 @@ import kotlin.test.assertTrue class StandaloneCordaRPClientTest { private companion object { - val log = loggerFor() + private val log = contextLogger() val user = User("user1", "test", permissions = setOf("ALL")) val port = AtomicInteger(15200) const val attachmentSize = 2116 diff --git a/constants.properties b/constants.properties index a780da9214..f278032d63 100644 --- a/constants.properties +++ b/constants.properties @@ -1,5 +1,5 @@ -gradlePluginsVersion=2.0.8 -kotlinVersion=1.1.50 +gradlePluginsVersion=2.0.9 +kotlinVersion=1.1.60 guavaVersion=21.0 bouncycastleVersion=1.57 typesafeConfigVersion=1.3.1 \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt b/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt index 322a9f9925..8c9fba60de 100644 --- a/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt +++ b/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt @@ -348,6 +348,20 @@ abstract class FlowLogic { } } + fun trackStepsTreeIndex(): DataFeed? { + // TODO this is not threadsafe, needs an atomic get-step-and-subscribe + return progressTracker?.let { + DataFeed(it.stepsTreeIndex, it.stepsTreeIndexChanges) + } + } + + fun trackStepsTree(): DataFeed>, List>>? { + // TODO this is not threadsafe, needs an atomic get-step-and-subscribe + return progressTracker?.let { + DataFeed(it.allStepsLabels, it.stepsTreeChanges) + } + } + /** * Suspends the flow until the transaction with the specified ID is received, successfully verified and * sent to the vault for processing. Note that this call suspends until the transaction is considered diff --git a/core/src/main/kotlin/net/corda/core/internal/ToggleField.kt b/core/src/main/kotlin/net/corda/core/internal/ToggleField.kt index 36ae18d05c..032d0d8802 100644 --- a/core/src/main/kotlin/net/corda/core/internal/ToggleField.kt +++ b/core/src/main/kotlin/net/corda/core/internal/ToggleField.kt @@ -1,6 +1,6 @@ package net.corda.core.internal -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import org.slf4j.Logger import java.util.concurrent.atomic.AtomicReference import kotlin.reflect.KProperty @@ -47,8 +47,12 @@ class ThreadLeakException : RuntimeException("Leaked thread detected: ${Thread.c /** @param isAGlobalThreadBeingCreated whether a global thread (that should not inherit any value) is being created. */ class InheritableThreadLocalToggleField(name: String, - private val log: Logger = loggerFor>(), + private val log: Logger = staticLog, private val isAGlobalThreadBeingCreated: (Array) -> Boolean) : ToggleField(name) { + companion object { + private val staticLog = contextLogger() + } + private inner class Holder(value: T) : AtomicReference(value) { fun valueOrDeclareLeak() = get() ?: throw ThreadLeakException() fun childValue(): Holder? { diff --git a/core/src/main/kotlin/net/corda/core/internal/concurrent/CordaFutureImpl.kt b/core/src/main/kotlin/net/corda/core/internal/concurrent/CordaFutureImpl.kt index a97eff4c56..6ff8ce4f2c 100644 --- a/core/src/main/kotlin/net/corda/core/internal/concurrent/CordaFutureImpl.kt +++ b/core/src/main/kotlin/net/corda/core/internal/concurrent/CordaFutureImpl.kt @@ -3,8 +3,8 @@ package net.corda.core.internal.concurrent import net.corda.core.internal.VisibleForTesting import net.corda.core.concurrent.CordaFuture import net.corda.core.concurrent.match +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.getOrThrow -import net.corda.core.utilities.loggerFor import org.slf4j.Logger import java.time.Duration import java.util.concurrent.CompletableFuture @@ -118,7 +118,7 @@ interface OpenFuture : ValueOrException, CordaFuture @VisibleForTesting internal class CordaFutureImpl(private val impl: CompletableFuture = CompletableFuture()) : Future by impl, OpenFuture { companion object { - private val defaultLog = loggerFor>() + private val defaultLog = contextLogger() internal val listenerFailedMessage = "Future listener failed:" } diff --git a/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt b/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt index 8859d94ec8..b6880a8103 100644 --- a/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt +++ b/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt @@ -226,10 +226,6 @@ interface CordaRPCOps : RPCOps { /** Returns Node's NodeInfo, assuming this will not change while the node is running. */ fun nodeInfo(): NodeInfo - /** Returns and [Observable] object with future states of the node. */ - @RPCReturnsObservables - fun nodeStateObservable(): Observable - /** * Returns network's notary identities, assuming this will not change while the node is running. * @@ -468,8 +464,3 @@ inline fun > CordaRPCOps.startTrac */ @CordaSerializable data class DataFeed(val snapshot: A, val updates: Observable) - -@CordaSerializable -enum class NodeState { - SHUTTING_DOWN -} \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/messaging/FlowHandle.kt b/core/src/main/kotlin/net/corda/core/messaging/FlowHandle.kt index ee241d1924..90ba7e2596 100644 --- a/core/src/main/kotlin/net/corda/core/messaging/FlowHandle.kt +++ b/core/src/main/kotlin/net/corda/core/messaging/FlowHandle.kt @@ -31,6 +31,9 @@ interface FlowHandle : AutoCloseable { interface FlowProgressHandle : FlowHandle { val progress: Observable + val stepsTreeIndexFeed: DataFeed? + + val stepsTreeFeed: DataFeed>, List>>? /** * Use this function for flows whose returnValue and progress are not going to be used or tracked, so as to free up * server resources. @@ -52,10 +55,17 @@ data class FlowHandleImpl( } @CordaSerializable -data class FlowProgressHandleImpl( +data class FlowProgressHandleImpl @JvmOverloads constructor( override val id: StateMachineRunId, override val returnValue: CordaFuture, - override val progress: Observable) : FlowProgressHandle { + override val progress: Observable, + override val stepsTreeIndexFeed: DataFeed? = null, + override val stepsTreeFeed: DataFeed>, List>>? = null) : FlowProgressHandle { + + // For API compatibility + fun copy(id: StateMachineRunId, returnValue: CordaFuture, progress: Observable): FlowProgressHandleImpl { + return copy(id = id, returnValue = returnValue, progress = progress, stepsTreeFeed = null, stepsTreeIndexFeed = null) + } // Remember to add @Throws to FlowProgressHandle.close() if this throws an exception. override fun close() { diff --git a/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt b/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt index 68ae3fc5f3..78f1704336 100644 --- a/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt +++ b/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt @@ -8,13 +8,11 @@ import net.corda.core.crypto.SignableData import net.corda.core.crypto.SignatureMetadata import net.corda.core.crypto.TransactionSignature import net.corda.core.flows.ContractUpgradeFlow -import net.corda.core.messaging.NodeState import net.corda.core.node.services.* import net.corda.core.serialization.SerializeAsToken import net.corda.core.transactions.FilteredTransaction import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder -import rx.Observable import java.security.PublicKey import java.sql.Connection import java.time.Clock @@ -150,9 +148,6 @@ interface ServiceHub : ServicesForResolution { /** The [NodeInfo] object corresponding to our own entry in the network map. */ val myInfo: NodeInfo - /** The [Observable] object used to communicate to RPC clients the state of the node. */ - val myNodeStateObservable: Observable - /** * Return the singleton instance of the given Corda service type. This is a class that is annotated with * [CordaService] and will have automatically been registered by the node. diff --git a/core/src/main/kotlin/net/corda/core/node/services/AttachmentStorage.kt b/core/src/main/kotlin/net/corda/core/node/services/AttachmentStorage.kt index e7da510f23..9332587f7a 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/AttachmentStorage.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/AttachmentStorage.kt @@ -45,6 +45,13 @@ interface AttachmentStorage { @Throws(FileAlreadyExistsException::class, IOException::class) fun importAttachment(jar: InputStream, uploader: String, filename: String): AttachmentId + /** + * Inserts or returns Attachment Id of attachment. Does not throw an exception if already uploaded. + * @param jar [InputStream] of Jar file + * @return [AttachmentId] of uploaded attachment + */ + fun importOrGetAttachment(jar: InputStream): AttachmentId + /** * Searches attachment using given criteria and optional sort rules * @param criteria Query criteria to use as a filter @@ -53,5 +60,12 @@ interface AttachmentStorage { * @return List of AttachmentId of attachment matching criteria, sorted according to given sorting parameter */ fun queryAttachments(criteria: AttachmentQueryCriteria, sorting: AttachmentSort? = null): List + + /** + * Searches for an attachment already in the store + * @param attachmentId The attachment Id + * @return true if it's in there + */ + fun hasAttachment(attachmentId: AttachmentId): Boolean } diff --git a/core/src/main/kotlin/net/corda/core/node/services/NotaryService.kt b/core/src/main/kotlin/net/corda/core/node/services/NotaryService.kt index 8241fcd13c..46227d0027 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/NotaryService.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/NotaryService.kt @@ -9,7 +9,7 @@ import net.corda.core.identity.Party import net.corda.core.node.ServiceHub import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.serialize -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import org.slf4j.Logger import java.security.PublicKey @@ -45,8 +45,11 @@ abstract class NotaryService : SingletonSerializeAsToken() { * of the cluster is sufficient for transaction notarisation. For example, a single-node or a Raft notary. */ abstract class TrustedAuthorityNotaryService : NotaryService() { - protected open val log: Logger = loggerFor() + companion object { + private val staticLog = contextLogger() + } + protected open val log: Logger get() = staticLog // TODO: specify the valid time window in config, and convert TimeWindowChecker to a utility method protected abstract val timeWindowChecker: TimeWindowChecker protected abstract val uniquenessProvider: UniquenessProvider diff --git a/core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt b/core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt index dd76afb644..ce0370319c 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt +++ b/core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt @@ -24,12 +24,19 @@ infix fun Int.exactAdd(b: Int): Int = Math.addExact(this, b) infix fun Long.exactAdd(b: Long): Long = Math.addExact(this, b) /** - * Get the [Logger] for a class using the syntax + * Usually you won't need this method: + * * If you're in a companion object, use [contextLogger] + * * If you're in an object singleton, use [LoggerFactory.getLogger] directly on javaClass * - * `val logger = loggerFor()` + * Otherwise, this gets the [Logger] for a class using the syntax + * + * `private val log = loggerFor()` */ inline fun loggerFor(): Logger = LoggerFactory.getLogger(T::class.java) +/** When called from a companion object, returns the logger for the enclosing class. */ +fun Any.contextLogger(): Logger = LoggerFactory.getLogger(javaClass.enclosingClass) + /** Log a TRACE level message produced by evaluating the given lamdba, but only if TRACE logging is enabled. */ inline fun Logger.trace(msg: () -> String) { if (isTraceEnabled) trace(msg()) diff --git a/core/src/main/kotlin/net/corda/core/utilities/ProgressTracker.kt b/core/src/main/kotlin/net/corda/core/utilities/ProgressTracker.kt index 6cd9526795..22661eeb7b 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/ProgressTracker.kt +++ b/core/src/main/kotlin/net/corda/core/utilities/ProgressTracker.kt @@ -6,9 +6,6 @@ import rx.Subscription import rx.subjects.PublishSubject import java.util.* -// TODO: Expose the concept of errors. -// TODO: It'd be helpful if this class was at least partly thread safe. - /** * A progress tracker helps surface information about the progress of an operation to a user interface or API of some * kind. It lets you define a set of _steps_ that represent an operation. A step is represented by an object (typically @@ -34,16 +31,16 @@ import java.util.* @CordaSerializable class ProgressTracker(vararg steps: Step) { @CordaSerializable - sealed class Change { - data class Position(val tracker: ProgressTracker, val newStep: Step) : Change() { + sealed class Change(val progressTracker: ProgressTracker) { + data class Position(val tracker: ProgressTracker, val newStep: Step) : Change(tracker) { override fun toString() = newStep.label } - data class Rendering(val tracker: ProgressTracker, val ofStep: Step) : Change() { + data class Rendering(val tracker: ProgressTracker, val ofStep: Step) : Change(tracker) { override fun toString() = ofStep.label } - data class Structural(val tracker: ProgressTracker, val parent: Step) : Change() { + data class Structural(val tracker: ProgressTracker, val parent: Step) : Change(tracker) { override fun toString() = "Structural step change in child of ${parent.label}" } } @@ -70,17 +67,23 @@ class ProgressTracker(vararg steps: Step) { override fun equals(other: Any?) = other is DONE } - /** The steps in this tracker, same as the steps passed to the constructor but with UNSTARTED and DONE inserted. */ - val steps = arrayOf(UNSTARTED, *steps, DONE) - - // This field won't be serialized. - private val _changes by transient { PublishSubject.create() } - @CordaSerializable private data class Child(val tracker: ProgressTracker, @Transient val subscription: Subscription?) private val childProgressTrackers = mutableMapOf() + /** The steps in this tracker, same as the steps passed to the constructor but with UNSTARTED and DONE inserted. */ + val steps = arrayOf(UNSTARTED, *steps, DONE) + + private var _allStepsCache: List> = _allSteps() + + // This field won't be serialized. + private val _changes by transient { PublishSubject.create() } + private val _stepsTreeChanges by transient { PublishSubject.create>>() } + private val _stepsTreeIndexChanges by transient { PublishSubject.create() } + + + init { steps.forEach { val childTracker = it.childProgressTracker() @@ -92,7 +95,15 @@ class ProgressTracker(vararg steps: Step) { /** The zero-based index of the current step in the [steps] array (i.e. with UNSTARTED and DONE) */ var stepIndex: Int = 0 - private set + private set(value) { + field = value + } + + var stepsTreeIndex: Int = -1 + private set(value) { + field = value + _stepsTreeIndexChanges.onNext(value) + } /** * Reading returns the value of steps[stepIndex], writing moves the position of the current tracker. Once moved to @@ -118,22 +129,39 @@ class ProgressTracker(vararg steps: Step) { curChangeSubscription?.unsubscribe() stepIndex = index _changes.onNext(Change.Position(this, steps[index])) - curChangeSubscription = currentStep.changes.subscribe({ _changes.onNext(it) }, { _changes.onError(it) }) + recalculateStepsTreeIndex() + curChangeSubscription = currentStep.changes.subscribe({ + _changes.onNext(it) + if (it is Change.Structural || it is Change.Rendering) rebuildStepsTree() else recalculateStepsTreeIndex() + }, { _changes.onError(it) }) - if (currentStep == DONE) _changes.onCompleted() + if (currentStep == DONE) { + _changes.onCompleted() + _stepsTreeIndexChanges.onCompleted() + _stepsTreeChanges.onCompleted() + } } /** Returns the current step, descending into children to find the deepest step we are up to. */ val currentStepRecursive: Step get() = getChildProgressTracker(currentStep)?.currentStepRecursive ?: currentStep + private fun currentStepRecursiveWithoutUnstarted(): Step { + val stepRecursive = getChildProgressTracker(currentStep)?.currentStepRecursive + return if (stepRecursive == null || stepRecursive == UNSTARTED) currentStep else stepRecursive + } + fun getChildProgressTracker(step: Step): ProgressTracker? = childProgressTrackers[step]?.tracker fun setChildProgressTracker(step: ProgressTracker.Step, childProgressTracker: ProgressTracker) { - val subscription = childProgressTracker.changes.subscribe({ _changes.onNext(it) }, { _changes.onError(it) }) + val subscription = childProgressTracker.changes.subscribe({ + _changes.onNext(it) + if (it is Change.Structural || it is Change.Rendering) rebuildStepsTree() else recalculateStepsTreeIndex() + }, { _changes.onError(it) }) childProgressTrackers[step] = Child(childProgressTracker, subscription) childProgressTracker.parent = this _changes.onNext(Change.Structural(this, step)) + rebuildStepsTree() } private fun removeChildProgressTracker(step: ProgressTracker.Step) { @@ -142,6 +170,7 @@ class ProgressTracker(vararg steps: Step) { it.subscription?.unsubscribe() } _changes.onNext(Change.Structural(this, step)) + rebuildStepsTree() } /** @@ -166,6 +195,18 @@ class ProgressTracker(vararg steps: Step) { return cursor } + private fun rebuildStepsTree() { + _allStepsCache = _allSteps() + _stepsTreeChanges.onNext(allStepsLabels) + + recalculateStepsTreeIndex() + } + + private fun recalculateStepsTreeIndex() { + val step = currentStepRecursiveWithoutUnstarted() + stepsTreeIndex = _allStepsCache.indexOfFirst { it.second == step } + } + private fun _allSteps(level: Int = 0): List> { val result = ArrayList>() for (step in steps) { @@ -177,11 +218,15 @@ class ProgressTracker(vararg steps: Step) { return result } + private fun _allStepsLabels(level: Int = 0): List> = _allSteps(level).map { Pair(it.first, it.second.label) } + /** * A list of all steps in this ProgressTracker and the children, with the indent level provided starting at zero. * Note that UNSTARTED is never counted, and DONE is only counted at the calling level. */ - val allSteps: List> get() = _allSteps() + val allSteps: List> get() = _allStepsCache + + val allStepsLabels: List> get() = _allStepsLabels() private var curChangeSubscription: Subscription? = null @@ -200,8 +245,15 @@ class ProgressTracker(vararg steps: Step) { */ val changes: Observable get() = _changes + val stepsTreeChanges: Observable>> get() = _stepsTreeChanges + + val stepsTreeIndexChanges: Observable get() = _stepsTreeIndexChanges + /** Returns true if the progress tracker has ended, either by reaching the [DONE] step or prematurely with an error */ val hasEnded: Boolean get() = _changes.hasCompleted() || _changes.hasThrowable() } +// TODO: Expose the concept of errors. +// TODO: It'd be helpful if this class was at least partly thread safe. + diff --git a/core/src/test/kotlin/net/corda/core/contracts/PrivacySaltTest.kt b/core/src/test/kotlin/net/corda/core/contracts/PrivacySaltTest.kt new file mode 100644 index 0000000000..c9fc2033a6 --- /dev/null +++ b/core/src/test/kotlin/net/corda/core/contracts/PrivacySaltTest.kt @@ -0,0 +1,13 @@ +package net.corda.core.contracts + +import org.assertj.core.api.Assertions.assertThatExceptionOfType +import org.junit.Test + +class PrivacySaltTest { + @Test + fun `all-zero PrivacySalt not allowed`() { + assertThatExceptionOfType(IllegalArgumentException::class.java).isThrownBy { + PrivacySalt(ByteArray(32)) + }.withMessage("Privacy salt should not be all zeros.") + } +} \ No newline at end of file diff --git a/core/src/test/kotlin/net/corda/core/flows/ReceiveAllFlowTests.kt b/core/src/test/kotlin/net/corda/core/flows/ReceiveAllFlowTests.kt index ea2db6947b..dd2dbab17f 100644 --- a/core/src/test/kotlin/net/corda/core/flows/ReceiveAllFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/ReceiveAllFlowTests.kt @@ -5,46 +5,45 @@ import net.corda.core.identity.Party import net.corda.core.utilities.UntrustworthyData import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.unwrap -import net.corda.testing.node.network +import net.corda.testing.node.MockNetwork import net.corda.testing.singleIdentity import net.corda.testing.startFlow import org.assertj.core.api.Assertions.assertThat +import org.junit.After import org.junit.Test class ReceiveMultipleFlowTests { + private val mockNet = MockNetwork() + private val nodes = (0..2).map { mockNet.createPartyNode() } + @After + fun stopNodes() { + mockNet.stopNodes() + } + @Test fun `receive all messages in parallel using map style`() { - network(3) { nodes -> - val doubleValue = 5.0 - nodes[1].registerAnswer(AlgorithmDefinition::class, doubleValue) - val stringValue = "Thriller" - nodes[2].registerAnswer(AlgorithmDefinition::class, stringValue) - - val flow = nodes[0].services.startFlow(ParallelAlgorithmMap(nodes[1].info.singleIdentity(), nodes[2].info.singleIdentity())) - runNetwork() - - val result = flow.resultFuture.getOrThrow() - - assertThat(result).isEqualTo(doubleValue * stringValue.length) - } + val doubleValue = 5.0 + nodes[1].registerAnswer(AlgorithmDefinition::class, doubleValue) + val stringValue = "Thriller" + nodes[2].registerAnswer(AlgorithmDefinition::class, stringValue) + val flow = nodes[0].services.startFlow(ParallelAlgorithmMap(nodes[1].info.singleIdentity(), nodes[2].info.singleIdentity())) + mockNet.runNetwork() + val result = flow.resultFuture.getOrThrow() + assertThat(result).isEqualTo(doubleValue * stringValue.length) } @Test fun `receive all messages in parallel using list style`() { - network(3) { nodes -> - val value1 = 5.0 - nodes[1].registerAnswer(ParallelAlgorithmList::class, value1) - val value2 = 6.0 - nodes[2].registerAnswer(ParallelAlgorithmList::class, value2) - - val flow = nodes[0].services.startFlow(ParallelAlgorithmList(nodes[1].info.singleIdentity(), nodes[2].info.singleIdentity())) - runNetwork() - val data = flow.resultFuture.getOrThrow() - - assertThat(data[0]).isEqualTo(value1) - assertThat(data[1]).isEqualTo(value2) - assertThat(data.fold(1.0) { a, b -> a * b }).isEqualTo(value1 * value2) - } + val value1 = 5.0 + nodes[1].registerAnswer(ParallelAlgorithmList::class, value1) + val value2 = 6.0 + nodes[2].registerAnswer(ParallelAlgorithmList::class, value2) + val flow = nodes[0].services.startFlow(ParallelAlgorithmList(nodes[1].info.singleIdentity(), nodes[2].info.singleIdentity())) + mockNet.runNetwork() + val data = flow.resultFuture.getOrThrow() + assertThat(data[0]).isEqualTo(value1) + assertThat(data[1]).isEqualTo(value2) + assertThat(data.fold(1.0) { a, b -> a * b }).isEqualTo(value1 * value2) } class ParallelAlgorithmMap(doubleMember: Party, stringMember: Party) : AlgorithmDefinition(doubleMember, stringMember) { diff --git a/core/src/test/kotlin/net/corda/core/utilities/ProgressTrackerTest.kt b/core/src/test/kotlin/net/corda/core/utilities/ProgressTrackerTest.kt index f307a2375e..9bcf5c0fb5 100644 --- a/core/src/test/kotlin/net/corda/core/utilities/ProgressTrackerTest.kt +++ b/core/src/test/kotlin/net/corda/core/utilities/ProgressTrackerTest.kt @@ -5,6 +5,7 @@ import org.junit.Test import java.util.* import kotlin.test.assertEquals import kotlin.test.assertFails +import org.assertj.core.api.Assertions.* class ProgressTrackerTest { object SimpleSteps { @@ -24,13 +25,23 @@ class ProgressTrackerTest { fun tracker() = ProgressTracker(AYY, BEE, SEA) } + object BabySteps { + object UNOS : ProgressTracker.Step("unos") + object DOES : ProgressTracker.Step("does") + object TRES : ProgressTracker.Step("tres") + + fun tracker() = ProgressTracker(UNOS, DOES, TRES) + } + lateinit var pt: ProgressTracker lateinit var pt2: ProgressTracker + lateinit var pt3: ProgressTracker @Before fun before() { pt = SimpleSteps.tracker() pt2 = ChildSteps.tracker() + pt3 = BabySteps.tracker() } @Test @@ -81,6 +92,118 @@ class ProgressTrackerTest { assertEquals(ChildSteps.BEE, pt2.nextStep()) } + @Test + fun `steps tree index counts children steps`() { + pt.setChildProgressTracker(SimpleSteps.TWO, pt2) + + val allSteps = pt.allSteps + + //capture notifications + val stepsIndexNotifications = LinkedList() + pt.stepsTreeIndexChanges.subscribe { + stepsIndexNotifications += it + } + val stepsTreeNotification = LinkedList>>() + pt.stepsTreeChanges.subscribe { + stepsTreeNotification += it + } + + fun assertCurrentStepsTree(index:Int, step: ProgressTracker.Step) { + assertEquals(index, pt.stepsTreeIndex) + assertEquals(step, allSteps[pt.stepsTreeIndex].second) + } + + //travel tree + pt.currentStep = SimpleSteps.ONE + assertCurrentStepsTree(0, SimpleSteps.ONE) + + pt.currentStep = SimpleSteps.TWO + assertCurrentStepsTree(1, SimpleSteps.TWO) + + pt2.currentStep = ChildSteps.BEE + assertCurrentStepsTree(3, ChildSteps.BEE) + + pt.currentStep = SimpleSteps.THREE + assertCurrentStepsTree(5, SimpleSteps.THREE) + + //assert no structure changes and proper steps propagation + assertThat(stepsIndexNotifications).containsExactlyElementsOf(listOf(0, 1, 3, 5)) + assertThat(stepsTreeNotification).isEmpty() + } + + @Test + fun `structure changes are pushed down when progress trackers are added`() { + pt.setChildProgressTracker(SimpleSteps.TWO, pt2) + + //capture notifications + val stepsIndexNotifications = LinkedList() + pt.stepsTreeIndexChanges.subscribe { + stepsIndexNotifications += it + } + + //put current state as a first change for simplicity when asserting + val stepsTreeNotification = mutableListOf(pt.allStepsLabels) + println(pt.allStepsLabels) + pt.stepsTreeChanges.subscribe { + stepsTreeNotification += it + } + + fun assertCurrentStepsTree(index:Int, step: ProgressTracker.Step) { + assertEquals(index, pt.stepsTreeIndex) + assertEquals(step.label, stepsTreeNotification.last()[pt.stepsTreeIndex].second) + } + + pt.currentStep = SimpleSteps.TWO + assertCurrentStepsTree(1, SimpleSteps.TWO) + + pt.currentStep = SimpleSteps.FOUR + assertCurrentStepsTree(6, SimpleSteps.FOUR) + + + pt.setChildProgressTracker(SimpleSteps.THREE, pt3) + + assertCurrentStepsTree(9, SimpleSteps.FOUR) + + //assert no structure changes and proper steps propagation + assertThat(stepsIndexNotifications).containsExactlyElementsOf(listOf(1, 6, 9)) + assertThat(stepsTreeNotification).hasSize(2) // 1 change + 1 our initial state + } + + @Test + fun `structure changes are pushed down when progress trackers are removed`() { + pt.setChildProgressTracker(SimpleSteps.TWO, pt2) + + //capture notifications + val stepsIndexNotifications = LinkedList() + pt.stepsTreeIndexChanges.subscribe { + stepsIndexNotifications += it + } + + //put current state as a first change for simplicity when asserting + val stepsTreeNotification = mutableListOf(pt.allStepsLabels) + pt.stepsTreeChanges.subscribe { + stepsTreeNotification += it + } + + fun assertCurrentStepsTree(index:Int, step: ProgressTracker.Step) { + assertEquals(index, pt.stepsTreeIndex) + assertEquals(step.label, stepsTreeNotification.last()[pt.stepsTreeIndex].second) + } + + pt.currentStep = SimpleSteps.TWO + pt2.currentStep = ChildSteps.SEA + pt3.currentStep = BabySteps.UNOS + assertCurrentStepsTree(4, ChildSteps.SEA) + + pt.setChildProgressTracker(SimpleSteps.TWO, pt3) + + assertCurrentStepsTree(2, BabySteps.UNOS) + + //assert no structure changes and proper steps propagation + assertThat(stepsIndexNotifications).containsExactlyElementsOf(listOf(1, 4, 2)) + assertThat(stepsTreeNotification).hasSize(2) // 1 change + 1 our initial state + } + @Test fun `can be rewound`() { pt.setChildProgressTracker(SimpleSteps.TWO, pt2) diff --git a/docs/source/_static/versions b/docs/source/_static/versions index 1152a8e46a..b7932ed452 100644 --- a/docs/source/_static/versions +++ b/docs/source/_static/versions @@ -9,5 +9,6 @@ "https://docs.corda.net/releases/release-M13.0": "M13.0", "https://docs.corda.net/releases/release-M14.0": "M14.0", "https://docs.corda.net/releases/release-V1.0": "V1.0", + "https://docs.corda.net/releases/release-V2.0": "V2.0", "https://docs.corda.net/head/": "Master" } diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 96e57df434..37fb52144f 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -6,11 +6,14 @@ from the previous milestone release. UNRELEASED ---------- + * ``ConfigUtilities`` now read system properties for a node. This allow to specify data source properties at runtime. * ``AttachmentStorage`` now allows providing metadata on attachments upload - username and filename, currently as plain strings. Those can be then used for querying, utilizing ``queryAttachments`` method of the same interface. +* ``SSH Server`` - The node can now expose shell via SSH server with proper authorization and permissioning built in. + * ``CordaRPCOps`` implementation now checks permissions for any function invocation, rather than just when starting flows. * ``wellKnownPartyFromAnonymous()`` now always resolve the key to a ``Party``, then the party to the well known party. diff --git a/docs/source/corda-configuration-file.rst b/docs/source/corda-configuration-file.rst index 4de5affc2f..ea3029d908 100644 --- a/docs/source/corda-configuration-file.rst +++ b/docs/source/corda-configuration-file.rst @@ -153,6 +153,11 @@ path to the node's base directory. Each should be a string. Only the JARs in the directories are added, not the directories themselves. This is useful for including JDBC drivers and the like. e.g. ``jarDirs = [ 'lib' ]`` +:sshd: If provided, node will start internal SSH server which will provide a management shell. It uses the same credentials + and permissions as RPC subsystem. It has one required parameter. + + :port: - the port to start SSH server on + :relay: If provided, the node will attempt to tunnel inbound connections via an external relay. The relay's address will be advertised to the network map service instead of the provided ``p2pAddress``. diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/CustomVaultQuery.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/CustomVaultQuery.kt index 1ae88a6015..506dc8bc02 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/CustomVaultQuery.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/CustomVaultQuery.kt @@ -9,10 +9,7 @@ import net.corda.core.node.services.CordaService import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.transactions.SignedTransaction -import net.corda.core.utilities.OpaqueBytes -import net.corda.core.utilities.ProgressTracker -import net.corda.core.utilities.loggerFor -import net.corda.core.utilities.unwrap +import net.corda.core.utilities.* import net.corda.finance.flows.AbstractCashFlow import net.corda.finance.flows.CashException import net.corda.finance.flows.CashIssueFlow @@ -25,7 +22,7 @@ object CustomVaultQuery { @CordaService class Service(val services: AppServiceHub) : SingletonSerializeAsToken() { private companion object { - val log = loggerFor() + private val log = contextLogger() } fun rebalanceCurrencyReserves(): List> { val nativeQuery = """ diff --git a/docs/source/hello-world-running.rst b/docs/source/hello-world-running.rst index 5504b83b65..17d506471d 100644 --- a/docs/source/hello-world-running.rst +++ b/docs/source/hello-world-running.rst @@ -33,15 +33,16 @@ service. rpcPort 10006 webPort 10007 cordapps = ["net.corda:corda-finance:$corda_release_version"] - rpcUsers = [[ user: "user1", "password": "test", "permissions": []]] + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL]]] } node { name "O=PartyB,L=New York,C=US" p2pPort 10008 rpcPort 10009 webPort 10010 + sshdPort 10024 cordapps = ["net.corda:corda-finance:$corda_release_version"] - rpcUsers = [[ user: "user1", "password": "test", "permissions": []]] + rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]] } } @@ -101,6 +102,9 @@ node via its built-in CRaSH shell. Go to the terminal window displaying the CRaSH shell of PartyA. Typing ``help`` will display a list of the available commands. +.. note:: Local terminal shell is available only in a development mode. In production environment SSH server can be enabled. + More about SSH and how to connect can be found on :doc:`Shell` page. + We want to create an IOU of 100 with PartyB. We start the ``IOUFlow`` by typing: .. code:: bash diff --git a/docs/source/node-administration.rst b/docs/source/node-administration.rst index 1b69ad9eae..7a677c34d2 100644 --- a/docs/source/node-administration.rst +++ b/docs/source/node-administration.rst @@ -53,6 +53,11 @@ reserve the right to move and rename it as it's not part of the public API as ye logging name construction. If you can't find what you need to refer to, use the ``--logging-level`` option as above and then determine the logging module name from the console output. +SSH access +---------- + +Node can be configured to run SSH server. See :doc:`shell` for details. + Database access --------------- diff --git a/docs/source/release-notes.rst b/docs/source/release-notes.rst index d0a7fa14fd..14d9a494cc 100644 --- a/docs/source/release-notes.rst +++ b/docs/source/release-notes.rst @@ -6,8 +6,24 @@ Here are release notes for each snapshot release from M9 onwards. Unreleased ---------- -Support for observer/regulator nodes has returned. Read :doc:`tutorial-observer-nodes` to learn more or examine the -interest rate swaps demo. +Release 2.0 +---------- +Following quickly on the heels of the release of Corda 1.0, Corda version 2.0 consolidates +a number of security updates for our dependent libraries alongside the reintroduction of the Observer node functionality. +This was absent from version 1 but based on user feedback its re-introduction removes the need for complicated "isRelevant()" checks. + +In addition the fix for a small bug present in the coin selection code of V1.0 is integrated from master. + +* **Version Bump** + +Due to the introduction of new APIs, Corda 2.0 has a platform version of 2. This will be advertised in the network map structures +and via the versioning APIs. + +* **Observer Nodes** + +Adds the facility for transparent forwarding of transactions to some third party observer, such as a regulator. By having +that entity simply run an Observer node they can simply recieve a stream of digitally signed, de-duplicated reports that +can be used for reporting. Release 1.0 ----------- @@ -32,8 +48,8 @@ unless an incompatible change is required for security reasons: Utilities and serialisers for working with JSON representations of basic types. Our extensive testing frameworks will continue to evolve alongside future Corda APIs. As part of our commitment to ease of use and modularity -we have introduced a new test node driver module to encapsulate all test functionality in support of building standalone node integration -tests using our DSL driver. +we have introduced a new test node driver module to encapsulate all test functionality in support of building standalone node integration +tests using our DSL driver. Please read :doc:`corda-api` for complete details. diff --git a/docs/source/shell.rst b/docs/source/shell.rst index cdddc4548e..fe74e44f29 100644 --- a/docs/source/shell.rst +++ b/docs/source/shell.rst @@ -18,11 +18,47 @@ Some of its features include: * View JMX metrics and monitoring exports. * UNIX style pipes for both text and objects, an ``egrep`` command and a command for working with columnular data. -.. note:: A future version of Corda will add SSH access to the node. - It is based on the popular `CRaSH`_ shell used in various other projects and supports many of the same features. -The shell may be disabled by passing the ``--no-local-shell`` flag to the node. +Local terminal shell runs only in development mode. It may be disabled by passing the ``--no-local-shell`` flag to the node. + +SSH server +---------- + +Shell can also be accessible via SSH. By default SSH server is *disabled*. To enable it port must be configured - in ``node.conf`` file + +.. code:: bash + + sshd { + port = 2222 + } + +Authentication and authorization +-------------------------------- +SSH require user to login first - using the same users as RPC system. In fact, shell serves as a proxy to RPC and communicates +with node using RPC calls. This also means that RPC permissions are enforced. No permissions are required to allow the connection +and login in. +Watching flows (``flow watch``) requires ``InvokeRpc.stateMachinesFeed`` while starting flows requires +``InvokeRpc.startTrackedFlowDynamic`` and ``InvokeRpc.registeredFlows`` in addition to a permission for a particular flow. + +Host key +-------- + +The host key is loaded from ``sshkey/hostkey.pem`` file. If the file does not exist, it will be generated randomly, however +in the development mode seed may be tuned to give the same results on the same computer - in order to avoid host checking +errors. + +Connecting +---------- + +Linux and MacOS computers usually come with SSH client preinstalled. On Windows it usually require extra download. +Usual connection syntax is ``ssh user@host -p 2222`` - where ``user`` is a RPC username, and ``-p`` specifies a port parameters - +it's the same as setup in ``node.conf`` file. ``host`` should point to a node hostname, usually ``localhost`` if connecting and +running node on the same computer. Password will be asked after establishing connection. + +:note: While developing, checking multiple samples or simply restarting a node frequently host key may be regenerated. SSH usually + saved once trusted hosts and will refuse to connect in case of a change. Then check may be disabled with extra options + ``ssh -o StrictHostKeyChecking=no user@host -p2222``. This option should never be used in production environment! Getting help ------------ diff --git a/finance/src/main/kotlin/net/corda/finance/contracts/asset/OnLedgerAsset.kt b/finance/src/main/kotlin/net/corda/finance/contracts/asset/OnLedgerAsset.kt index d64bb817b2..eb0078d8ad 100644 --- a/finance/src/main/kotlin/net/corda/finance/contracts/asset/OnLedgerAsset.kt +++ b/finance/src/main/kotlin/net/corda/finance/contracts/asset/OnLedgerAsset.kt @@ -4,7 +4,7 @@ import net.corda.core.contracts.* import net.corda.core.contracts.Amount.Companion.sumOrThrow import net.corda.core.identity.AbstractParty import net.corda.core.transactions.TransactionBuilder -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.trace import java.security.PublicKey import java.util.* @@ -32,8 +32,7 @@ data class PartyAndAmount(val party: AbstractParty, val amount: Amount< */ abstract class OnLedgerAsset> : Contract { companion object { - val log = loggerFor>() - + private val log = contextLogger() /** * Generate a transaction that moves an amount of currency to the given pubkey. * diff --git a/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/AbstractCashSelection.kt b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/AbstractCashSelection.kt index dc574838a9..5548a29077 100644 --- a/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/AbstractCashSelection.kt +++ b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/AbstractCashSelection.kt @@ -4,15 +4,12 @@ import co.paralleluniverse.fibers.Suspendable import net.corda.core.contracts.Amount import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateRef -import net.corda.core.contracts.TransactionState import net.corda.core.crypto.SecureHash import net.corda.core.flows.FlowLogic import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party import net.corda.core.node.ServiceHub import net.corda.core.node.services.StatesNotAvailableException -import net.corda.core.serialization.SerializationDefaults -import net.corda.core.serialization.deserialize import net.corda.core.utilities.* import net.corda.finance.contracts.asset.Cash import java.sql.* @@ -46,7 +43,7 @@ abstract class AbstractCashSelection(private val maxRetries : Int = 8, private v }.invoke() } - val log = loggerFor() + private val log = contextLogger() } // coin selection retry loop counter, sleep (msecs) and lock for selecting states diff --git a/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2Impl.kt b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2Impl.kt index 08fa686c07..440047f19d 100644 --- a/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2Impl.kt +++ b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2Impl.kt @@ -5,8 +5,8 @@ import net.corda.core.crypto.toStringShort import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party import net.corda.core.utilities.OpaqueBytes +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.debug -import net.corda.core.utilities.loggerFor import java.sql.Connection import java.sql.DatabaseMetaData import java.sql.ResultSet @@ -15,7 +15,7 @@ import java.util.* class CashSelectionH2Impl : AbstractCashSelection() { companion object { const val JDBC_DRIVER_NAME = "H2 JDBC Driver" - val log = loggerFor() + private val log = contextLogger() } override fun isCompatible(metadata: DatabaseMetaData): Boolean { diff --git a/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionPostgreSQLImpl.kt b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionPostgreSQLImpl.kt index d76fde536d..f96ef6f441 100644 --- a/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionPostgreSQLImpl.kt +++ b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionPostgreSQLImpl.kt @@ -3,10 +3,7 @@ package net.corda.finance.contracts.asset.cash.selection import net.corda.core.contracts.Amount import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party -import net.corda.core.utilities.OpaqueBytes -import net.corda.core.utilities.debug -import net.corda.core.utilities.loggerFor -import net.corda.core.utilities.toBase58String +import net.corda.core.utilities.* import java.sql.Connection import java.sql.DatabaseMetaData import java.sql.ResultSet @@ -16,7 +13,7 @@ class CashSelectionPostgreSQLImpl : AbstractCashSelection() { companion object { val JDBC_DRIVER_NAME = "PostgreSQL JDBC Driver" - val log = loggerFor() + private val log = contextLogger() } override fun isCompatible(metadata: DatabaseMetaData): Boolean { @@ -63,13 +60,13 @@ class CashSelectionPostgreSQLImpl : AbstractCashSelection() { paramOffset += 1 } if (onlyFromIssuerParties.isNotEmpty()) { - val issuerKeys = connection.createArrayOf("BYTEA", onlyFromIssuerParties.map + val issuerKeys = connection.createArrayOf("VARCHAR", onlyFromIssuerParties.map { it.owningKey.toBase58String() }.toTypedArray()) statement.setArray(3 + paramOffset, issuerKeys) paramOffset += 1 } if (withIssuerRefs.isNotEmpty()) { - val issuerRefs = connection.createArrayOf("VARCHAR", withIssuerRefs.map + val issuerRefs = connection.createArrayOf("BYTEA", withIssuerRefs.map { it.bytes }.toTypedArray()) statement.setArray(3 + paramOffset, issuerRefs) paramOffset += 1 @@ -79,5 +76,4 @@ class CashSelectionPostgreSQLImpl : AbstractCashSelection() { return statement.executeQuery() } - } diff --git a/finance/src/main/kotlin/net/corda/finance/flows/CashIssueAndPaymentFlow.kt b/finance/src/main/kotlin/net/corda/finance/flows/CashIssueAndPaymentFlow.kt index 95a58e8cfb..04a3c1cd90 100644 --- a/finance/src/main/kotlin/net/corda/finance/flows/CashIssueAndPaymentFlow.kt +++ b/finance/src/main/kotlin/net/corda/finance/flows/CashIssueAndPaymentFlow.kt @@ -27,7 +27,7 @@ class CashIssueAndPaymentFlow(val amount: Amount, val anonymous: Boolean, val notary: Party, progressTracker: ProgressTracker) : AbstractCashFlow(progressTracker) { - constructor(amount: Amount, + constructor(amount: Amount, issueRef: OpaqueBytes, recipient: Party, anonymous: Boolean, diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt index 266ae6434c..93ce4cad7b 100644 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt @@ -96,15 +96,15 @@ class Node(private val project: Project) : CordformNode() { } /** - * Set the SSHD port for this node. + * Enables SSH access on given port * - * @param sshdPort The SSHD port. + * @param sshdPort The port for SSH server to listen on */ - fun sshdPort(sshdPort: Int) { - config = config.withValue("sshdAddress", - ConfigValueFactory.fromAnyRef("$DEFAULT_HOST:$sshdPort")) + fun sshdPort(sshdPort: Int?) { + config = config.withValue("sshd.port", ConfigValueFactory.fromAnyRef(sshdPort)) } + internal fun build() { configureProperties() installCordaJar() diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/NodeInfoFilesCopier.kt b/node-api/src/main/kotlin/net/corda/nodeapi/NodeInfoFilesCopier.kt index aadc49f4c3..7017e89a56 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/NodeInfoFilesCopier.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/NodeInfoFilesCopier.kt @@ -5,7 +5,7 @@ import net.corda.core.internal.ThreadBox import net.corda.core.internal.createDirectories import net.corda.core.internal.isRegularFile import net.corda.core.internal.list -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import rx.Observable import rx.Scheduler import rx.Subscription @@ -28,7 +28,7 @@ import java.util.concurrent.TimeUnit class NodeInfoFilesCopier(scheduler: Scheduler = Schedulers.io()) : AutoCloseable { companion object { - private val log = loggerFor() + private val log = contextLogger() const val NODE_INFO_FILE_NAME_PREFIX = "nodeInfo-" } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolver.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolver.kt index 52690a1780..d4c98fc1e1 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolver.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolver.kt @@ -10,7 +10,7 @@ import net.corda.nodeapi.internal.AttachmentsClassLoader import net.corda.core.serialization.ClassWhitelist import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.SerializationContext -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.nodeapi.internal.serialization.amqp.hasAnnotationInHierarchy import net.corda.nodeapi.internal.serialization.kryo.ThrowableSerializer import java.io.PrintWriter @@ -187,7 +187,7 @@ class TransientClassWhiteList(delegate: ClassWhitelist) : AbstractMutableClassWh @Suppress("unused") class LoggingWhitelist(val delegate: ClassWhitelist, val global: Boolean = true) : MutableClassWhitelist { companion object { - val log = loggerFor() + private val log = contextLogger() val globallySeen: MutableSet = Collections.synchronizedSet(mutableSetOf()) val journalWriter: PrintWriter? = openOptionalDynamicWhitelistJournal() diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializationScheme.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializationScheme.kt index ca3487ead8..4327d114ea 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializationScheme.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializationScheme.kt @@ -36,7 +36,6 @@ abstract class AbstractAMQPSerializationScheme : SerializationScheme { register(publicKeySerializer) register(net.corda.nodeapi.internal.serialization.amqp.custom.PrivateKeySerializer) register(net.corda.nodeapi.internal.serialization.amqp.custom.ThrowableSerializer(this)) - register(net.corda.nodeapi.internal.serialization.amqp.custom.X500NameSerializer) register(net.corda.nodeapi.internal.serialization.amqp.custom.BigDecimalSerializer) register(net.corda.nodeapi.internal.serialization.amqp.custom.CurrencySerializer) register(net.corda.nodeapi.internal.serialization.amqp.custom.OpaqueBytesSubSequenceSerializer(this)) @@ -54,8 +53,8 @@ abstract class AbstractAMQPSerializationScheme : SerializationScheme { register(net.corda.nodeapi.internal.serialization.amqp.custom.MonthDaySerializer(this)) register(net.corda.nodeapi.internal.serialization.amqp.custom.PeriodSerializer(this)) register(net.corda.nodeapi.internal.serialization.amqp.custom.ClassSerializer(this)) - register(net.corda.nodeapi.internal.serialization.amqp.custom.X509CertificateHolderSerializer) - register(net.corda.nodeapi.internal.serialization.amqp.custom.PartyAndCertificateSerializer(factory)) + register(net.corda.nodeapi.internal.serialization.amqp.custom.X509CertificateSerializer) + register(net.corda.nodeapi.internal.serialization.amqp.custom.CertPathSerializer(this)) register(net.corda.nodeapi.internal.serialization.amqp.custom.StringBufferSerializer) register(net.corda.nodeapi.internal.serialization.amqp.custom.SimpleStringSerializer) register(net.corda.nodeapi.internal.serialization.amqp.custom.InputStreamSerializer) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ObjectSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ObjectSerializer.kt index 7dcb450803..433f8731ba 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ObjectSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ObjectSerializer.kt @@ -1,7 +1,7 @@ package net.corda.nodeapi.internal.serialization.amqp +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.debug -import net.corda.core.utilities.loggerFor import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory.Companion.nameForType import org.apache.qpid.proton.amqp.Symbol import org.apache.qpid.proton.codec.Data @@ -17,7 +17,9 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS open val kotlinConstructor = constructorForDeserialization(clazz) val javaConstructor by lazy { kotlinConstructor?.javaConstructor } - private val logger = loggerFor() + companion object { + private val logger = contextLogger() + } open internal val propertySerializers: Collection by lazy { propertiesForSerialization(kotlinConstructor, clazz, factory) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/PropertySerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/PropertySerializer.kt index 3d9fa8b0a5..59c8b983fa 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/PropertySerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/PropertySerializer.kt @@ -1,6 +1,6 @@ package net.corda.nodeapi.internal.serialization.amqp -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import org.apache.qpid.proton.amqp.Binary import org.apache.qpid.proton.codec.Data import java.lang.reflect.Method @@ -61,8 +61,7 @@ sealed class PropertySerializer(val name: String, val readMethod: Method?, val r } companion object { - private val logger = loggerFor() - + private val logger = contextLogger() fun make(name: String, readMethod: Method?, resolvedType: Type, factory: SerializerFactory): PropertySerializer { readMethod?.isAccessible = true if (SerializerFactory.isPrimitive(resolvedType)) { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/CertPathSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/CertPathSerializer.kt new file mode 100644 index 0000000000..b6ed889190 --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/CertPathSerializer.kt @@ -0,0 +1,27 @@ +package net.corda.nodeapi.internal.serialization.amqp.custom + +import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer +import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory +import java.io.NotSerializableException +import java.security.cert.CertPath +import java.security.cert.CertificateException +import java.security.cert.CertificateFactory + +class CertPathSerializer(factory: SerializerFactory) + : CustomSerializer.Proxy(CertPath::class.java, CertPathProxy::class.java, factory) { + override fun toProxy(obj: CertPath): CertPathProxy = CertPathProxy(obj.type, obj.encoded) + + override fun fromProxy(proxy: CertPathProxy): CertPath { + try { + val cf = CertificateFactory.getInstance(proxy.type) + return cf.generateCertPath(proxy.encoded.inputStream()) + } catch (ce: CertificateException) { + val nse = NotSerializableException("java.security.cert.CertPath: $type") + nse.initCause(ce) + throw nse + } + } + + @Suppress("ArrayInDataClass") + data class CertPathProxy(val type: String, val encoded: ByteArray) +} \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/PartyAndCertificateSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/PartyAndCertificateSerializer.kt deleted file mode 100644 index 8bdde643d7..0000000000 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/PartyAndCertificateSerializer.kt +++ /dev/null @@ -1,30 +0,0 @@ -package net.corda.nodeapi.internal.serialization.amqp.custom - -import net.corda.core.crypto.Crypto -import net.corda.core.identity.PartyAndCertificate -import net.corda.nodeapi.internal.serialization.amqp.* -import java.io.ByteArrayInputStream -import java.io.NotSerializableException -import java.security.cert.CertPath -import java.security.cert.CertificateException -import java.security.cert.CertificateFactory - -/** - * A serializer that writes out a party and certificate in encoded format. - */ -class PartyAndCertificateSerializer(factory: SerializerFactory) : CustomSerializer.Proxy(PartyAndCertificate::class.java, PartyAndCertificateProxy::class.java, factory) { - override fun toProxy(obj: PartyAndCertificate): PartyAndCertificateProxy = PartyAndCertificateProxy(obj.certPath.type, obj.certPath.encoded) - - override fun fromProxy(proxy: PartyAndCertificateProxy): PartyAndCertificate { - try { - val cf = CertificateFactory.getInstance(proxy.type) - return PartyAndCertificate(cf.generateCertPath(ByteArrayInputStream(proxy.encoded))) - } catch (ce: CertificateException) { - val nse = NotSerializableException("java.security.cert.CertPath: " + type) - nse.initCause(ce) - throw nse - } - } - - data class PartyAndCertificateProxy(val type: String, val encoded: ByteArray) -} \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/ThrowableSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/ThrowableSerializer.kt index 8697cd98c7..e78cf2b3d8 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/ThrowableSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/ThrowableSerializer.kt @@ -3,14 +3,14 @@ package net.corda.nodeapi.internal.serialization.amqp.custom import net.corda.core.CordaRuntimeException import net.corda.core.CordaThrowable import net.corda.core.serialization.SerializationFactory -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.nodeapi.internal.serialization.amqp.* import java.io.NotSerializableException class ThrowableSerializer(factory: SerializerFactory) : CustomSerializer.Proxy(Throwable::class.java, ThrowableProxy::class.java, factory) { companion object { - private val logger = loggerFor() + private val logger = contextLogger() } override val revealSubclassesInSchema: Boolean = true diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/X500NameSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/X500NameSerializer.kt deleted file mode 100644 index 0138d28dd0..0000000000 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/X500NameSerializer.kt +++ /dev/null @@ -1,23 +0,0 @@ -package net.corda.nodeapi.internal.serialization.amqp.custom - -import net.corda.nodeapi.internal.serialization.amqp.* -import org.apache.qpid.proton.codec.Data -import org.bouncycastle.asn1.ASN1InputStream -import org.bouncycastle.asn1.x500.X500Name -import java.lang.reflect.Type - -/** - * Custom serializer for X500 names that utilizes their ASN.1 encoding on the wire. - */ -object X500NameSerializer : CustomSerializer.Implements(X500Name::class.java) { - override val schemaForDocumentation = Schema(listOf(RestrictedType(type.toString(), "", listOf(type.toString()), SerializerFactory.primitiveTypeName(ByteArray::class.java)!!, descriptor, emptyList()))) - - override fun writeDescribedObject(obj: X500Name, data: Data, type: Type, output: SerializationOutput) { - output.writeObject(obj.encoded, data, clazz) - } - - override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): X500Name { - val binary = input.readObject(obj, schema, ByteArray::class.java) as ByteArray - return X500Name.getInstance(ASN1InputStream(binary).readObject()) - } -} \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/X509CertificateHolderSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/X509CertificateHolderSerializer.kt deleted file mode 100644 index 13ca64dd75..0000000000 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/X509CertificateHolderSerializer.kt +++ /dev/null @@ -1,23 +0,0 @@ -package net.corda.nodeapi.internal.serialization.amqp.custom - -import net.corda.core.crypto.Crypto -import net.corda.nodeapi.internal.serialization.amqp.* -import org.apache.qpid.proton.codec.Data -import org.bouncycastle.cert.X509CertificateHolder -import java.lang.reflect.Type - -/** - * A serializer that writes out a certificate in X.509 format. - */ -object X509CertificateHolderSerializer : CustomSerializer.Implements(X509CertificateHolder::class.java) { - override val schemaForDocumentation = Schema(listOf(RestrictedType(type.toString(), "", listOf(type.toString()), SerializerFactory.primitiveTypeName(ByteArray::class.java)!!, descriptor, emptyList()))) - - override fun writeDescribedObject(obj: X509CertificateHolder, data: Data, type: Type, output: SerializationOutput) { - output.writeObject(obj.encoded, data, clazz) - } - - override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): X509CertificateHolder { - val bits = input.readObject(obj, schema, ByteArray::class.java) as ByteArray - return X509CertificateHolder(bits) - } -} \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/X509CertificateSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/X509CertificateSerializer.kt new file mode 100644 index 0000000000..a1e3c17b8f --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/X509CertificateSerializer.kt @@ -0,0 +1,27 @@ +package net.corda.nodeapi.internal.serialization.amqp.custom + +import net.corda.nodeapi.internal.serialization.amqp.* +import org.apache.qpid.proton.codec.Data +import java.lang.reflect.Type +import java.security.cert.CertificateFactory +import java.security.cert.X509Certificate + +object X509CertificateSerializer : CustomSerializer.Implements(X509Certificate::class.java) { + override val schemaForDocumentation = Schema(listOf(RestrictedType( + type.toString(), + "", + listOf(type.toString()), + SerializerFactory.primitiveTypeName(ByteArray::class.java)!!, + descriptor, + emptyList() + ))) + + override fun writeDescribedObject(obj: X509Certificate, data: Data, type: Type, output: SerializationOutput) { + output.writeObject(obj.encoded, data, clazz) + } + + override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): X509Certificate { + val bits = input.readObject(obj, schema, ByteArray::class.java) as ByteArray + return CertificateFactory.getInstance("X.509").generateCertificate(bits.inputStream()) as X509Certificate + } +} diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/DefaultKryoCustomizer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/DefaultKryoCustomizer.kt index 4191d72842..7d69264a43 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/DefaultKryoCustomizer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/DefaultKryoCustomizer.kt @@ -32,8 +32,6 @@ import net.corda.nodeapi.internal.serialization.GeneratedAttachment import net.corda.nodeapi.internal.serialization.MutableClassWhitelist import net.i2p.crypto.eddsa.EdDSAPrivateKey import net.i2p.crypto.eddsa.EdDSAPublicKey -import org.bouncycastle.asn1.x500.X500Name -import org.bouncycastle.cert.X509CertificateHolder import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey import org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPrivateCrtKey @@ -53,6 +51,7 @@ import java.io.InputStream import java.lang.reflect.Modifier.isPublic import java.security.PublicKey import java.security.cert.CertPath +import java.security.cert.X509Certificate import java.util.* import kotlin.collections.ArrayList @@ -109,8 +108,7 @@ object DefaultKryoCustomizer { register(FileInputStream::class.java, InputStreamSerializer) register(CertPath::class.java, CertPathSerializer) register(X509CertPath::class.java, CertPathSerializer) - register(X500Name::class.java, X500NameSerializer) - register(X509CertificateHolder::class.java, X509CertificateSerializer) + register(X509Certificate::class.java, X509CertificateSerializer) register(BCECPrivateKey::class.java, PrivateKeySerializer) register(BCECPublicKey::class.java, publicKeySerializer) register(BCRSAPrivateCrtKey::class.java, PrivateKeySerializer) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/Kryo.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/Kryo.kt index c3a859b084..505b4eb1c9 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/Kryo.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/Kryo.kt @@ -15,7 +15,8 @@ import net.corda.core.crypto.TransactionSignature import net.corda.core.identity.Party import net.corda.core.internal.uncheckedCast import net.corda.core.serialization.SerializationContext -import net.corda.core.serialization.SerializationContext.UseCase.* +import net.corda.core.serialization.SerializationContext.UseCase.Checkpoint +import net.corda.core.serialization.SerializationContext.UseCase.Storage import net.corda.core.serialization.SerializeAsTokenContext import net.corda.core.serialization.SerializedBytes import net.corda.core.toFuture @@ -24,9 +25,6 @@ import net.corda.core.transactions.* import net.corda.core.utilities.SgxSupport import net.corda.nodeapi.internal.serialization.CordaClassResolver import net.corda.nodeapi.internal.serialization.serializationContextKey -import org.bouncycastle.asn1.ASN1InputStream -import org.bouncycastle.asn1.x500.X500Name -import org.bouncycastle.cert.X509CertificateHolder import org.slf4j.Logger import org.slf4j.LoggerFactory import rx.Observable @@ -37,6 +35,7 @@ import java.security.PrivateKey import java.security.PublicKey import java.security.cert.CertPath import java.security.cert.CertificateFactory +import java.security.cert.X509Certificate import java.util.* import javax.annotation.concurrent.ThreadSafe import kotlin.reflect.KClass @@ -475,46 +474,28 @@ object ClassSerializer : Serializer>() { } } -/** - * For serialising an [X500Name] without touching Sun internal classes. - */ -@ThreadSafe -object X500NameSerializer : Serializer() { - override fun read(kryo: Kryo, input: Input, type: Class): X500Name { - return X500Name.getInstance(ASN1InputStream(input.readBytes()).readObject()) - } - - override fun write(kryo: Kryo, output: Output, obj: X500Name) { - output.writeBytes(obj.encoded) - } -} - -/** - * For serialising an [CertPath] in an X.500 standard format. - */ @ThreadSafe object CertPathSerializer : Serializer() { - val factory: CertificateFactory = CertificateFactory.getInstance("X.509") override fun read(kryo: Kryo, input: Input, type: Class): CertPath { - return factory.generateCertPath(input) + val factory = CertificateFactory.getInstance(input.readString()) + return factory.generateCertPath(input.readBytesWithLength().inputStream()) } override fun write(kryo: Kryo, output: Output, obj: CertPath) { - output.writeBytes(obj.encoded) + output.writeString(obj.type) + output.writeBytesWithLength(obj.encoded) } } -/** - * For serialising an [X509CertificateHolder] in an X.500 standard format. - */ @ThreadSafe -object X509CertificateSerializer : Serializer() { - override fun read(kryo: Kryo, input: Input, type: Class): X509CertificateHolder { - return X509CertificateHolder(input.readBytes()) +object X509CertificateSerializer : Serializer() { + override fun read(kryo: Kryo, input: Input, type: Class): X509Certificate { + val factory = CertificateFactory.getInstance("X.509") + return factory.generateCertificate(input.readBytesWithLength().inputStream()) as X509Certificate } - override fun write(kryo: Kryo, output: Output, obj: X509CertificateHolder) { - output.writeBytes(obj.encoded) + override fun write(kryo: Kryo, output: Output, obj: X509Certificate) { + output.writeBytesWithLength(obj.encoded) } } diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/KryoTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/KryoTests.kt index cafeb14dd0..6eee1230be 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/KryoTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/KryoTests.kt @@ -21,13 +21,14 @@ import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Before import org.junit.Rule import org.junit.Test -import org.junit.rules.ExpectedException import org.slf4j.LoggerFactory import java.io.ByteArrayInputStream import java.io.InputStream import java.time.Instant -import java.util.Collections -import kotlin.test.* +import java.util.* +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertTrue class KryoTests { @Rule @@ -36,9 +37,6 @@ class KryoTests { private lateinit var factory: SerializationFactory private lateinit var context: SerializationContext - @get:Rule - val expectedEx: ExpectedException = ExpectedException.none() - @Before fun setup() { factory = SerializationFactoryImpl().apply { registerScheme(KryoServerSerializationScheme()) } @@ -51,7 +49,7 @@ class KryoTests { } @Test - fun ok() { + fun `simple data class`() { val birthday = Instant.parse("1984-04-17T00:30:00.00Z") val mike = Person("mike", birthday) val bits = mike.serialize(factory, context) @@ -59,7 +57,7 @@ class KryoTests { } @Test - fun nullables() { + fun `null values`() { val bob = Person("bob", null) val bits = bob.serialize(factory, context) assertThat(bits.deserialize(factory, context)).isEqualTo(Person("bob", null)) @@ -202,13 +200,6 @@ class KryoTests { assertEquals(expected, actual) } - @Test - fun `all-zero PrivacySalt not allowed`() { - expectedEx.expect(IllegalArgumentException::class.java) - expectedEx.expectMessage("Privacy salt should not be all zeros.") - PrivacySalt(ByteArray(32)) - } - @CordaSerializable private object TestSingleton diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/MapsSerializationTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/MapsSerializationTest.kt index ed45e63b68..f726d0b97a 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/MapsSerializationTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/MapsSerializationTest.kt @@ -2,16 +2,16 @@ package net.corda.nodeapi.internal.serialization import com.esotericsoftware.kryo.Kryo import com.esotericsoftware.kryo.util.DefaultClassResolver +import net.corda.core.identity.CordaX500Name import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.node.services.statemachine.SessionData import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1 +import net.corda.testing.SerializationEnvironmentRule import net.corda.testing.amqpSpecific import net.corda.testing.kryoSpecific -import net.corda.testing.SerializationEnvironmentRule -import org.assertj.core.api.Assertions -import org.bouncycastle.asn1.x500.X500Name +import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Assert.assertArrayEquals import org.junit.Rule import org.junit.Test @@ -53,7 +53,7 @@ class MapsSerializationTest { fun `check throws for forbidden declared type`() = amqpSpecific("Such exceptions are not expected in Kryo mode.") { val payload = HashMap(smallMap) val wrongPayloadType = WrongPayloadType(payload) - Assertions.assertThatThrownBy { wrongPayloadType.serialize() } + assertThatThrownBy { wrongPayloadType.serialize() } .isInstanceOf(IllegalArgumentException::class.java).hasMessageContaining( "Map type class java.util.HashMap is unstable under iteration. Suggested fix: use java.util.LinkedHashMap instead.") } @@ -62,27 +62,29 @@ class MapsSerializationTest { data class MyKey(val keyContent: Double) @CordaSerializable - data class MyValue(val valueContent: X500Name) + data class MyValue(val valueContent: CordaX500Name) @Test fun `check map serialization works with custom types`() { val myMap = mapOf( - MyKey(1.0) to MyValue(X500Name("CN=one")), - MyKey(10.0) to MyValue(X500Name("CN=ten"))) + MyKey(1.0) to MyValue(CordaX500Name("OOO", "LLL", "CC")), + MyKey(10.0) to MyValue(CordaX500Name("OO", "LL", "CC"))) assertEqualAfterRoundTripSerialization(myMap) } @Test - fun `check empty map serialises as Java emptyMap`() = kryoSpecific("Specifically checks Kryo serialization") { - val nameID = 0 - val serializedForm = emptyMap().serialize() - val output = ByteArrayOutputStream().apply { - write(KryoHeaderV0_1.bytes) - write(DefaultClassResolver.NAME + 2) - write(nameID) - write(javaEmptyMapClass.name.toAscii()) - write(Kryo.NOT_NULL.toInt()) + fun `check empty map serialises as Java emptyMap`() { + kryoSpecific("Specifically checks Kryo serialization") { + val nameID = 0 + val serializedForm = emptyMap().serialize() + val output = ByteArrayOutputStream().apply { + write(KryoHeaderV0_1.bytes) + write(DefaultClassResolver.NAME + 2) + write(nameID) + write(javaEmptyMapClass.name.toAscii()) + write(Kryo.NOT_NULL.toInt()) + } + assertArrayEquals(output.toByteArray(), serializedForm.bytes) } - assertArrayEquals(output.toByteArray(), serializedForm.bytes) } } diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt index ea93b0038f..04c390202d 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt @@ -1,17 +1,20 @@ +@file:Suppress("unused", "MemberVisibilityCanPrivate") + package net.corda.nodeapi.internal.serialization.amqp +import net.corda.client.rpc.RPCException import net.corda.core.CordaRuntimeException +import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.secureRandomBytes import net.corda.core.flows.FlowException import net.corda.core.identity.AbstractParty -import net.corda.core.internal.toX509CertHolder +import net.corda.core.internal.AbstractAttachment import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.MissingAttachmentsException import net.corda.core.serialization.SerializationFactory import net.corda.core.transactions.LedgerTransaction -import net.corda.client.rpc.RPCException -import net.corda.core.contracts.* -import net.corda.core.internal.AbstractAttachment -import net.corda.core.serialization.MissingAttachmentsException +import net.corda.core.utilities.OpaqueBytes import net.corda.nodeapi.internal.serialization.AllWhitelist import net.corda.nodeapi.internal.serialization.EmptyWhitelist import net.corda.nodeapi.internal.serialization.GeneratedAttachment @@ -25,10 +28,8 @@ import org.apache.activemq.artemis.api.core.SimpleString import org.apache.qpid.proton.amqp.* import org.apache.qpid.proton.codec.DecoderImpl import org.apache.qpid.proton.codec.EncoderImpl -import org.junit.Assert.assertArrayEquals -import org.junit.Assert.assertNotSame -import org.junit.Assert.assertSame -import org.assertj.core.api.Assertions.assertThatThrownBy +import org.assertj.core.api.Assertions.* +import org.junit.Assert.* import org.junit.Ignore import org.junit.Test import java.io.ByteArrayInputStream @@ -39,10 +40,7 @@ import java.nio.ByteBuffer import java.time.* import java.time.temporal.ChronoUnit import java.util.* -import kotlin.reflect.full.declaredFunctions -import kotlin.reflect.full.declaredMemberFunctions import kotlin.reflect.full.superclasses -import kotlin.reflect.jvm.javaMethod import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertTrue @@ -79,7 +77,6 @@ class SerializationOutputTests { } data class Woo(val fred: Int) { - @Suppress("unused") val bob = "Bob" } @@ -89,7 +86,6 @@ class SerializationOutputTests { @CordaSerializable data class AnnotatedWoo(val fred: Int) { - @Suppress("unused") val bob = "Bob" } @@ -151,6 +147,13 @@ class SerializationOutputTests { data class PolymorphicProperty(val foo: FooInterface?) + @CordaSerializable + class NonZeroByte(val value: Byte) { + init { + require(value.toInt() != 0) { "Zero not allowed" } + } + } + private inline fun serdes(obj: T, factory: SerializerFactory = SerializerFactory( AllWhitelist, ClassLoader.getSystemClassLoader()), @@ -406,6 +409,32 @@ class SerializationOutputTests { serdes(obj) } + @Test + fun `class constructor is invoked on deserialisation`() { + val ser = SerializationOutput(SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())) + val des = DeserializationInput(ser.serializerFactory) + + val serialisedOne = ser.serialize(NonZeroByte(1)).bytes + val serialisedTwo = ser.serialize(NonZeroByte(2)).bytes + + // Find the index that holds the value byte + val valueIndex = serialisedOne.zip(serialisedTwo).mapIndexedNotNull { index, (oneByte, twoByte) -> + if (oneByte.toInt() == 1 && twoByte.toInt() == 2) index else null + }.single() + + val copy = serialisedTwo.clone() + + // Double check + copy[valueIndex] = 0x03 + assertThat(des.deserialize(OpaqueBytes(copy), NonZeroByte::class.java).value).isEqualTo(3) + + // Now use the forbidden value + copy[valueIndex] = 0x00 + assertThatExceptionOfType(NotSerializableException::class.java).isThrownBy { + des.deserialize(OpaqueBytes(copy), NonZeroByte::class.java) + }.withMessageContaining("Zero not allowed") + } + @Test fun `test custom serializers on public key`() { val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) @@ -762,26 +791,32 @@ class SerializationOutputTests { } @Test - fun `test certificate holder serialize`() { + fun `test privacy salt serialize`() { + serdes(PrivacySalt()) + serdes(PrivacySalt(secureRandomBytes(32))) + } + + @Test + fun `test X509 certificate serialize`() { val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) - factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.X509CertificateHolderSerializer) + factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.X509CertificateSerializer) val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) - factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.X509CertificateHolderSerializer) + factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.X509CertificateSerializer) - val obj = BOB_IDENTITY.certificate.toX509CertHolder() + val obj = BOB_IDENTITY.certificate serdes(obj, factory, factory2) } @Test - fun `test party and certificate serialize`() { + fun `test cert path serialize`() { val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) - factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.PartyAndCertificateSerializer(factory)) + factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.CertPathSerializer(factory)) val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) - factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.PartyAndCertificateSerializer(factory2)) + factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.CertPathSerializer(factory2)) - val obj = BOB_IDENTITY + val obj = BOB_IDENTITY.certPath serdes(obj, factory, factory2) } diff --git a/node/build.gradle b/node/build.gradle index f9a65e4104..8c0de7bee0 100644 --- a/node/build.gradle +++ b/node/build.gradle @@ -139,8 +139,14 @@ dependencies { compile "io.netty:netty-all:$netty_version" // CRaSH: An embeddable monitoring and admin shell with support for adding new commands written in Groovy. - compile("com.github.corda.crash:crash.shell:d5da86ba1b38e9c33af2a621dd15ba286307bec4") { + compile("com.github.corda.crash:crash.shell:$crash_version") { exclude group: "org.slf4j", module: "slf4j-jdk14" + exclude group: "org.bouncycastle" + } + + compile("com.github.corda.crash:crash.connectors.ssh:$crash_version") { + exclude group: "org.slf4j", module: "slf4j-jdk14" + exclude group: "org.bouncycastle" } // OkHTTP: Simple HTTP library. @@ -160,6 +166,9 @@ dependencies { integrationTestCompile "junit:junit:$junit_version" integrationTestCompile "org.assertj:assertj-core:${assertj_version}" + // Jsh: Testing SSH server + integrationTestCompile group: 'com.jcraft', name: 'jsch', version: '0.1.54' + // Jetty dependencies for NetworkMapClient test. // Web stuff: for HTTP[S] servlets testCompile "org.eclipse.jetty:jetty-servlet:${jetty_version}" diff --git a/node/src/integration-test/kotlin/net/corda/node/SSHServerTest.kt b/node/src/integration-test/kotlin/net/corda/node/SSHServerTest.kt new file mode 100644 index 0000000000..e7346a1932 --- /dev/null +++ b/node/src/integration-test/kotlin/net/corda/node/SSHServerTest.kt @@ -0,0 +1,170 @@ +package net.corda.node + +import co.paralleluniverse.fibers.Suspendable +import com.jcraft.jsch.ChannelExec +import com.jcraft.jsch.JSch +import com.jcraft.jsch.JSchException +import net.corda.core.flows.* +import net.corda.core.identity.Party +import net.corda.core.utilities.ProgressTracker +import net.corda.core.utilities.getOrThrow +import net.corda.core.utilities.unwrap +import net.corda.nodeapi.User +import net.corda.testing.ALICE +import net.corda.testing.driver.driver +import org.bouncycastle.util.io.Streams +import org.junit.Test +import net.corda.node.services.Permissions.Companion.startFlow +import java.net.ConnectException +import kotlin.test.assertTrue +import kotlin.test.fail +import org.assertj.core.api.Assertions.assertThat +import java.util.regex.Pattern + +class SSHServerTest { + + @Test() + fun `ssh server does not start be default`() { + val user = User("u", "p", setOf()) + // The driver will automatically pick up the annotated flows below + driver() { + val node = startNode(providedName = ALICE.name, rpcUsers = listOf(user)) + node.getOrThrow() + + val session = JSch().getSession("u", "localhost", 2222) + session.setConfig("StrictHostKeyChecking", "no") + session.setPassword("p") + + try { + session.connect() + fail() + } catch (e:JSchException) { + assertTrue(e.cause is ConnectException) + } + } + } + + @Test + fun `ssh server starts when configured`() { + val user = User("u", "p", setOf()) + // The driver will automatically pick up the annotated flows below + driver { + val node = startNode(providedName = ALICE.name, rpcUsers = listOf(user), + customOverrides = mapOf("sshd" to mapOf("port" to 2222))) + node.getOrThrow() + + val session = JSch().getSession("u", "localhost", 2222) + session.setConfig("StrictHostKeyChecking", "no") + session.setPassword("p") + + session.connect() + + assertTrue(session.isConnected) + } + } + + + @Test + fun `ssh server verify credentials`() { + val user = User("u", "p", setOf()) + // The driver will automatically pick up the annotated flows below + driver { + val node = startNode(providedName = ALICE.name, rpcUsers = listOf(user), + customOverrides = mapOf("sshd" to mapOf("port" to 2222))) + node.getOrThrow() + + val session = JSch().getSession("u", "localhost", 2222) + session.setConfig("StrictHostKeyChecking", "no") + session.setPassword("p_is_bad_password") + + try { + session.connect() + fail("Server should reject invalid credentials") + } catch (e: JSchException) { + //There is no specialized exception for this + assertTrue(e.message == "Auth fail") + } + } + } + + @Test + fun `ssh respects permissions`() { + val user = User("u", "p", setOf(startFlow())) + // The driver will automatically pick up the annotated flows below + driver(isDebug = true) { + val node = startNode(providedName = ALICE.name, rpcUsers = listOf(user), + customOverrides = mapOf("sshd" to mapOf("port" to 2222))) + node.getOrThrow() + + val session = JSch().getSession("u", "localhost", 2222) + session.setConfig("StrictHostKeyChecking", "no") + session.setPassword("p") + session.connect() + + assertTrue(session.isConnected) + + val channel = session.openChannel("exec") as ChannelExec + channel.setCommand("start FlowICannotRun otherParty: \"O=Alice Corp,L=Madrid,C=ES\"") + channel.connect() + val response = String(Streams.readAll(channel.inputStream)) + + val flowNameEscaped = Pattern.quote("StartFlow.${SSHServerTest::class.qualifiedName}$${FlowICannotRun::class.simpleName}") + + channel.disconnect() + session.disconnect() + + assertThat(response).matches("(?s)User not permissioned with any of \\[[^]]*${flowNameEscaped}.*") + } + } + + @Test + fun `ssh runs flows`() { + val user = User("u", "p", setOf(startFlow())) + // The driver will automatically pick up the annotated flows below + driver(isDebug = true) { + val node = startNode(providedName = ALICE.name, rpcUsers = listOf(user), + customOverrides = mapOf("sshd" to mapOf("port" to 2222))) + node.getOrThrow() + + val session = JSch().getSession("u", "localhost", 2222) + session.setConfig("StrictHostKeyChecking", "no") + session.setPassword("p") + session.connect() + + assertTrue(session.isConnected) + + val channel = session.openChannel("exec") as ChannelExec + channel.setCommand("start FlowICanRun") + channel.connect() + + val response = String(Streams.readAll(channel.inputStream)) + + //There are ANSI control characters involved, so we want to avoid direct byte to byte matching + assertThat(response.lines()).filteredOn( { it.contains("✓") && it.contains("Done")}).hasSize(1) + } + } + + @StartableByRPC + @InitiatingFlow + class FlowICanRun : FlowLogic() { + + private val HELLO_STEP = ProgressTracker.Step("Hello") + + @Suspendable + override fun call(): String { + progressTracker?.currentStep = HELLO_STEP + return "bambam" + } + + override val progressTracker: ProgressTracker? = ProgressTracker(HELLO_STEP) + } + + @StartableByRPC + @InitiatingFlow + class FlowICannotRun(val otherParty: Party) : FlowLogic() { + @Suspendable + override fun call(): String = initiateFlow(otherParty).receive().unwrap { it } + + override val progressTracker: ProgressTracker? = ProgressTracker() + } +} \ No newline at end of file diff --git a/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt index 1f6e4d660f..0c81dba7dd 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt @@ -13,8 +13,8 @@ import net.corda.core.internal.div import net.corda.core.internal.toLedgerTransaction import net.corda.core.serialization.SerializationFactory import net.corda.core.transactions.TransactionBuilder +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.getOrThrow -import net.corda.core.utilities.loggerFor import net.corda.node.internal.cordapp.CordappLoader import net.corda.node.internal.cordapp.CordappProviderImpl import net.corda.testing.* @@ -43,7 +43,7 @@ class AttachmentLoadingTests : IntegrationTest() { } private companion object { - val logger = loggerFor() + private val logger = contextLogger() val isolatedJAR = AttachmentLoadingTests::class.java.getResource("isolated.jar")!! val ISOLATED_CONTRACT_ID = "net.corda.finance.contracts.isolated.AnotherDummyContract" diff --git a/node/src/main/java/net/corda/node/shell/FlowShellCommand.java b/node/src/main/java/net/corda/node/shell/FlowShellCommand.java index 4bac0ce5b5..d3ed752531 100644 --- a/node/src/main/java/net/corda/node/shell/FlowShellCommand.java +++ b/node/src/main/java/net/corda/node/shell/FlowShellCommand.java @@ -2,6 +2,9 @@ package net.corda.node.shell; // See the comments at the top of run.java +import net.corda.core.messaging.CordaRPCOps; +import net.corda.node.utilities.ANSIProgressRenderer; +import net.corda.node.utilities.CRaSHNSIProgressRenderer; import org.crsh.cli.*; import org.crsh.command.*; import org.crsh.text.*; @@ -9,6 +12,7 @@ import org.crsh.text.ui.TableElement; import java.util.*; +import static net.corda.node.services.messaging.RPCServerKt.CURRENT_RPC_CONTEXT; import static net.corda.node.shell.InteractiveShell.*; @Man( @@ -25,25 +29,27 @@ public class FlowShellCommand extends InteractiveShellCommand { @Usage("The class name of the flow to run, or an unambiguous substring") @Argument String name, @Usage("The data to pass as input") @Argument(unquote = false) List input ) { - startFlow(name, input, out); + startFlow(name, input, out, ops(), ansiProgressRenderer()); } // TODO Limit number of flows shown option? @Command @Usage("watch information about state machines running on the node with result information") public void watch(InvocationContext context) throws Exception { - runStateMachinesView(out); + runStateMachinesView(out, ops()); } static void startFlow(@Usage("The class name of the flow to run, or an unambiguous substring") @Argument String name, @Usage("The data to pass as input") @Argument(unquote = false) List input, - RenderPrintWriter out) { + RenderPrintWriter out, + CordaRPCOps rpcOps, + ANSIProgressRenderer ansiProgressRenderer) { if (name == null) { out.println("You must pass a name for the flow, see 'man flow'", Color.red); return; } String inp = input == null ? "" : String.join(" ", input).trim(); - runFlowByNameFragment(name, inp, out); + runFlowByNameFragment(name, inp, out, rpcOps, ansiProgressRenderer != null ? ansiProgressRenderer : new CRaSHNSIProgressRenderer(out) ); } @Command diff --git a/node/src/main/java/net/corda/node/shell/StartShellCommand.java b/node/src/main/java/net/corda/node/shell/StartShellCommand.java index 3ec2e8e2ee..e1c91ebb75 100644 --- a/node/src/main/java/net/corda/node/shell/StartShellCommand.java +++ b/node/src/main/java/net/corda/node/shell/StartShellCommand.java @@ -2,6 +2,8 @@ package net.corda.node.shell; // A simple forwarder to the "flow start" command, for easier typing. +import net.corda.node.utilities.ANSIProgressRenderer; +import net.corda.node.utilities.CRaSHNSIProgressRenderer; import org.crsh.cli.*; import java.util.*; @@ -11,6 +13,7 @@ public class StartShellCommand extends InteractiveShellCommand { @Man("An alias for 'flow start'. Example: \"start Yo target: Some other company\"") public void main(@Usage("The class name of the flow to run, or an unambiguous substring") @Argument String name, @Usage("The data to pass as input") @Argument(unquote = false) List input) { - FlowShellCommand.startFlow(name, input, out); + ANSIProgressRenderer ansiProgressRenderer = ansiProgressRenderer(); + FlowShellCommand.startFlow(name, input, out, ops(), ansiProgressRenderer != null ? ansiProgressRenderer : new CRaSHNSIProgressRenderer(out)); } } diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 141eddb8b9..d5c708181f 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -36,6 +36,7 @@ import net.corda.node.internal.cordapp.CordappProviderInternal import net.corda.node.services.ContractUpgradeHandler import net.corda.node.services.FinalityHandler import net.corda.node.services.NotaryChangeHandler +import net.corda.node.services.RPCUserService import net.corda.node.services.api.* import net.corda.node.services.config.BFTSMaRtConfiguration import net.corda.node.services.config.NodeConfiguration @@ -55,11 +56,11 @@ import net.corda.node.services.transactions.* import net.corda.node.services.upgrade.ContractUpgradeServiceImpl import net.corda.node.services.vault.NodeVaultService import net.corda.node.services.vault.VaultSoftLockManager +import net.corda.node.shell.InteractiveShell import net.corda.node.utilities.* import org.apache.activemq.artemis.utils.ReusableLatch import org.slf4j.Logger import rx.Observable -import rx.subjects.PublishSubject import java.io.IOException import java.lang.reflect.InvocationTargetException import java.security.KeyPair @@ -119,7 +120,6 @@ abstract class AbstractNode(val configuration: NodeConfiguration, protected val services: ServiceHubInternal get() = _services private lateinit var _services: ServiceHubInternalImpl - protected val nodeStateObservable: PublishSubject = PublishSubject.create() protected var myNotaryIdentity: PartyAndCertificate? = null protected lateinit var checkpointStorage: CheckpointStorage protected lateinit var smm: StateMachineManager @@ -130,6 +130,8 @@ abstract class AbstractNode(val configuration: NodeConfiguration, protected val _nodeReadyFuture = openFuture() protected val networkMapClient: NetworkMapClient? by lazy { configuration.compatibilityZoneURL?.let(::NetworkMapClient) } + lateinit var userService: RPCUserService get + /** Completes once the node has successfully registered with the network map service * or has loaded network map data from local database */ val nodeReadyFuture: CordaFuture @@ -211,8 +213,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, registerCordappFlows() _services.rpcFlows += cordappLoader.cordapps.flatMap { it.rpcFlows } FlowLogicRefFactoryImpl.classloader = cordappLoader.appClassLoader - - runOnStop += network::stop + startShell(rpcOps) Pair(StartedNodeImpl(this, _services, info, checkpointStorage, smm, attachments, network, database, rpcOps, flowStarter, notaryService), schedulerService) } @@ -243,6 +244,10 @@ abstract class AbstractNode(val configuration: NodeConfiguration, } } + open fun startShell(rpcOps: CordaRPCOps) { + InteractiveShell.startShell(configuration, rpcOps, userService, _services.identityService, _services.database) + } + private fun initNodeInfo(): Pair, NodeInfo> { val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null) val keyPairs = mutableSetOf(identityKeyPair) @@ -624,9 +629,6 @@ abstract class AbstractNode(val configuration: NodeConfiguration, // Meanwhile, we let the remote service send us updates until the acknowledgment buffer overflows and it // unsubscribes us forcibly, rather than blocking the shutdown process. - // Notify observers that the node is shutting down - nodeStateObservable.onNext(NodeState.SHUTTING_DOWN) - // Run shutdown hooks in opposite order to starting for (toRun in runOnStop.reversed()) { toRun() @@ -727,7 +729,6 @@ abstract class AbstractNode(val configuration: NodeConfiguration, override val attachments: AttachmentStorage get() = this@AbstractNode.attachments override val networkService: MessagingService get() = network override val clock: Clock get() = platformClock - override val myNodeStateObservable: Observable get() = nodeStateObservable override val configuration: NodeConfiguration get() = this@AbstractNode.configuration override fun cordaService(type: Class): T { require(type.isAnnotationPresent(CordaService::class.java)) { "${type.name} is not a Corda service" } diff --git a/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt b/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt index 0f73300772..ef0597c675 100644 --- a/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt +++ b/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt @@ -20,8 +20,8 @@ import net.corda.core.node.services.NetworkMapCache import net.corda.core.node.services.Vault import net.corda.core.node.services.vault.* import net.corda.core.transactions.SignedTransaction +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.getOrThrow -import net.corda.core.utilities.loggerFor import net.corda.node.services.api.FlowStarter import net.corda.node.services.api.ServiceHubInternal import net.corda.node.services.messaging.context @@ -117,10 +117,6 @@ internal class CordaRPCOpsImpl( return services.myInfo } - override fun nodeStateObservable(): Observable { - return services.myNodeStateObservable - } - override fun notaryIdentities(): List { return services.networkMapCache.notaryIdentities } @@ -142,7 +138,9 @@ internal class CordaRPCOpsImpl( return FlowProgressHandleImpl( id = stateMachine.id, returnValue = stateMachine.resultFuture, - progress = stateMachine.logic.track()?.updates ?: Observable.empty() + progress = stateMachine.logic.track()?.updates ?: Observable.empty(), + stepsTreeIndexFeed = stateMachine.logic.trackStepsTreeIndex(), + stepsTreeFeed = stateMachine.logic.trackStepsTree() ) } @@ -296,6 +294,6 @@ internal class CordaRPCOpsImpl( } companion object { - private val log = loggerFor() + private val log = contextLogger() } } \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/internal/Node.kt b/node/src/main/kotlin/net/corda/node/internal/Node.kt index d6969e19f3..379d9a2ef9 100644 --- a/node/src/main/kotlin/net/corda/node/internal/Node.kt +++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt @@ -8,20 +8,20 @@ import net.corda.core.internal.uncheckedCast import net.corda.core.messaging.RPCOps import net.corda.core.node.NodeInfo import net.corda.core.node.ServiceHub +import net.corda.core.node.services.TransactionVerifierService import net.corda.core.utilities.NetworkHostAndPort -import net.corda.core.utilities.loggerFor import net.corda.core.serialization.internal.SerializationEnvironmentImpl import net.corda.core.serialization.internal.nodeSerializationEnv +import net.corda.core.utilities.contextLogger import net.corda.node.VersionInfo import net.corda.node.internal.cordapp.CordappLoader import net.corda.node.serialization.KryoServerSerializationScheme -import net.corda.node.services.RPCUserService import net.corda.node.services.RPCUserServiceImpl import net.corda.node.services.api.SchemaService import net.corda.node.services.config.NodeConfiguration -import net.corda.node.services.messaging.ArtemisMessagingServer -import net.corda.node.services.messaging.MessagingService -import net.corda.node.services.messaging.NodeMessagingClient +import net.corda.node.services.config.VerifierType +import net.corda.node.services.messaging.* +import net.corda.node.services.transactions.InMemoryTransactionVerifierService import net.corda.node.utilities.AddressUtils import net.corda.node.utilities.AffinityExecutor import net.corda.node.utilities.CordaPersistence @@ -49,7 +49,7 @@ open class Node(configuration: NodeConfiguration, cordappLoader: CordappLoader = makeCordappLoader(configuration) ) : AbstractNode(configuration, createClock(configuration), versionInfo, cordappLoader) { companion object { - private val logger = loggerFor() + private val staticLog = contextLogger() var renderBasicInfoToConsole = true /** Used for useful info that we always want to show, even when not logging to the console */ @@ -79,8 +79,11 @@ open class Node(configuration: NodeConfiguration, } } - override val log: Logger get() = logger - override fun makeTransactionVerifierService() = (network as NodeMessagingClient).verifierService + override val log: Logger get() = staticLog + override fun makeTransactionVerifierService(): TransactionVerifierService = when (configuration.verifierType) { + VerifierType.OutOfProcess -> verifierMessagingClient!!.verifierService + VerifierType.InMemory -> InMemoryTransactionVerifierService(numberOfWorkers = 4) + } private val sameVmNodeNumber = sameVmNodeCounter.incrementAndGet() // Under normal (non-test execution) it will always be "1" @@ -127,7 +130,6 @@ open class Node(configuration: NodeConfiguration, private var shutdownHook: ShutdownHook? = null - private lateinit var userService: RPCUserService override fun makeMessagingService(database: CordaPersistence, info: NodeInfo): MessagingService { userService = RPCUserServiceImpl(configuration.rpcUsers) @@ -135,15 +137,18 @@ open class Node(configuration: NodeConfiguration, val advertisedAddress = info.addresses.single() printBasicNodeInfo("Incoming connection address", advertisedAddress.toString()) - - return NodeMessagingClient( + rpcMessagingClient = RPCMessagingClient(configuration, serverAddress) + verifierMessagingClient = when (configuration.verifierType) { + VerifierType.OutOfProcess -> VerifierMessagingClient(configuration, serverAddress, services.monitoringService.metrics) + VerifierType.InMemory -> null + } + return P2PMessagingClient( configuration, versionInfo, serverAddress, info.legalIdentities[0].owningKey, serverThread, database, - services.monitoringService.metrics, advertisedAddress) } @@ -202,9 +207,19 @@ open class Node(configuration: NodeConfiguration, runOnStop += this::stop start() } - - // Start up the MQ client. - (network as NodeMessagingClient).start(rpcOps, userService) + // Start up the MQ clients. + rpcMessagingClient.run { + runOnStop += this::stop + start(rpcOps, userService) + } + verifierMessagingClient?.run { + runOnStop += this::stop + start() + } + (network as P2PMessagingClient).apply { + runOnStop += this::stop + start() + } } /** @@ -273,7 +288,7 @@ open class Node(configuration: NodeConfiguration, _startupComplete.set(Unit) } }, - { th -> logger.error("Unexpected exception", th) } + { th -> staticLog.error("Unexpected exception", th) } // XXX: Why not use log? ) shutdownHook = addShutdownHook { stop() @@ -294,9 +309,13 @@ open class Node(configuration: NodeConfiguration, checkpointContext = KRYO_CHECKPOINT_CONTEXT.withClassLoader(classloader)) } + private lateinit var rpcMessagingClient: RPCMessagingClient + private var verifierMessagingClient: VerifierMessagingClient? = null /** Starts a blocking event loop for message dispatch. */ fun run() { - (network as NodeMessagingClient).run(messageBroker!!.serverControl) + rpcMessagingClient.start2(messageBroker!!.serverControl) + verifierMessagingClient?.start2() + (network as P2PMessagingClient).run() } private var shutdown = false diff --git a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt index a8e23eeb5b..2947700025 100644 --- a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt +++ b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt @@ -23,14 +23,13 @@ import java.lang.management.ManagementFactory import java.net.InetAddress import java.nio.file.Path import java.nio.file.Paths -import java.time.LocalDate import java.util.* import kotlin.system.exitProcess /** This class is responsible for starting a Node from command line arguments. */ open class NodeStartup(val args: Array) { companion object { - private val logger by lazy { loggerFor() } + private val logger by lazy { loggerFor() } // I guess this is lazy to allow for logging init, but why Node? val LOGS_DIRECTORY_NAME = "logs" val LOGS_CAN_BE_FOUND_IN_STRING = "Logs can be found in" } @@ -118,12 +117,13 @@ open class NodeStartup(val args: Array) { Node.printBasicNodeInfo("Node for \"$name\" started up and registered in $elapsed sec") // Don't start the shell if there's no console attached. - val runShell = !cmdlineOptions.noLocalShell && System.console() != null - startedNode.internals.startupComplete.then { - try { - InteractiveShell.startShell(cmdlineOptions.baseDirectory, runShell, cmdlineOptions.sshdServer, startedNode) - } catch (e: Throwable) { - logger.error("Shell failed to start", e) + if (!cmdlineOptions.noLocalShell && System.console() != null && conf.devMode) { + startedNode.internals.startupComplete.then { + try { + InteractiveShell.runLocalShell(startedNode) + } catch (e: Throwable) { + logger.error("Shell failed to start", e) + } } } }, @@ -305,11 +305,6 @@ open class NodeStartup(val args: Array) { "Computers are useless. They can only\ngive you answers. -- Picasso" ) - // TODO: Delete this after CordaCon. - val cordaCon2017date = LocalDate.of(2017, 9, 12) - val cordaConBanner = if (LocalDate.now() < cordaCon2017date) - "${Emoji.soon} Register for our Free CordaCon event : see https://goo.gl/Z15S8W" else "" - if (Emoji.hasEmojiTerminal) messages += "Kind of like a regular database but\nwith emojis, colours and ascii art. ${Emoji.coolGuy}" val (msg1, msg2) = messages.randomOrNull()!!.split('\n') @@ -323,8 +318,6 @@ open class NodeStartup(val args: Array) { a("--- ${versionInfo.vendor} ${versionInfo.releaseVersion} (${versionInfo.revision.take(7)}) -----------------------------------------------"). newline(). newline(). - a(cordaConBanner). - newline(). reset()) } } diff --git a/node/src/main/kotlin/net/corda/node/internal/RpcAuthorisationProxy.kt b/node/src/main/kotlin/net/corda/node/internal/RpcAuthorisationProxy.kt index 2ae8594db9..7a1e66f6ea 100644 --- a/node/src/main/kotlin/net/corda/node/internal/RpcAuthorisationProxy.kt +++ b/node/src/main/kotlin/net/corda/node/internal/RpcAuthorisationProxy.kt @@ -8,14 +8,12 @@ import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.DataFeed -import net.corda.core.messaging.NodeState import net.corda.core.node.NodeInfo import net.corda.core.node.services.AttachmentId import net.corda.core.node.services.NetworkMapCache import net.corda.core.node.services.Vault import net.corda.core.node.services.vault.* import net.corda.node.services.messaging.RpcAuthContext -import rx.Observable import java.io.InputStream import java.security.PublicKey @@ -68,8 +66,6 @@ class RpcAuthorisationProxy(private val implementation: CordaRPCOps, private val override fun nodeInfo(): NodeInfo = guard("nodeInfo", implementation::nodeInfo) - override fun nodeStateObservable(): Observable = guard("nodeStateObservable", implementation::nodeStateObservable) - override fun notaryIdentities(): List = guard("notaryIdentities", implementation::notaryIdentities) override fun addVaultTransactionNote(txnId: SecureHash, txnNote: String) = guard("addVaultTransactionNote") { diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt index 025d0bc94e..0e5c186af1 100644 --- a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt @@ -12,7 +12,7 @@ import net.corda.core.node.services.CordaService import net.corda.core.schemas.MappedSchema import net.corda.core.serialization.SerializationWhitelist import net.corda.core.serialization.SerializeAsToken -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.node.internal.classloading.requireAnnotation import net.corda.node.services.config.NodeConfiguration import net.corda.nodeapi.internal.serialization.DefaultWhitelist @@ -53,8 +53,7 @@ class CordappLoader private constructor(private val cordappJarPaths: List() - + private val logger = contextLogger() /** * Default cordapp dir name */ diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderImpl.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderImpl.kt index ba305283f2..cfed6d0cd2 100644 --- a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderImpl.kt +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderImpl.kt @@ -2,18 +2,24 @@ package net.corda.node.internal.cordapp import com.google.common.collect.HashBiMap import net.corda.core.contracts.ContractClassName -import net.corda.core.crypto.SecureHash -import net.corda.core.node.services.AttachmentStorage import net.corda.core.cordapp.Cordapp import net.corda.core.cordapp.CordappContext +import net.corda.core.crypto.SecureHash import net.corda.core.node.services.AttachmentId +import net.corda.core.node.services.AttachmentStorage import net.corda.core.serialization.SingletonSerializeAsToken +import net.corda.core.utilities.loggerFor import java.net.URL /** * Cordapp provider and store. For querying CorDapps for their attachment and vice versa. */ open class CordappProviderImpl(private val cordappLoader: CordappLoader, attachmentStorage: AttachmentStorage) : SingletonSerializeAsToken(), CordappProviderInternal { + + companion object { + private val log = loggerFor() + } + override fun getAppContext(): CordappContext { // TODO: Use better supported APIs in Java 9 Exception().stackTrace.forEach { stackFrame -> @@ -45,7 +51,7 @@ open class CordappProviderImpl(private val cordappLoader: CordappLoader, attachm private fun loadContractsIntoAttachmentStore(attachmentStorage: AttachmentStorage): Map { val cordappsWithAttachments = cordapps.filter { !it.contractClassNames.isEmpty() }.map { it.jarPath } - val attachmentIds = cordappsWithAttachments.map { it.openStream().use { attachmentStorage.importAttachment(it) } } + val attachmentIds = cordappsWithAttachments.map { it.openStream().use { attachmentStorage.importOrGetAttachment(it) }} return attachmentIds.zip(cordappsWithAttachments).toMap() } diff --git a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt index 52ced3c7f0..a5999d2026 100644 --- a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt +++ b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt @@ -16,7 +16,7 @@ import net.corda.core.node.services.NetworkMapCache import net.corda.core.node.services.NetworkMapCacheBase import net.corda.core.node.services.TransactionStorage import net.corda.core.transactions.SignedTransaction -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.node.internal.InitiatedFlowFactory import net.corda.node.internal.cordapp.CordappProviderInternal import net.corda.node.services.config.NodeConfiguration @@ -43,7 +43,7 @@ interface NetworkMapCacheBaseInternal : NetworkMapCacheBase { interface ServiceHubInternal : ServiceHub { companion object { - private val log = loggerFor() + private val log = contextLogger() } override val vaultService: VaultServiceInternal diff --git a/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt b/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt index e5b71712a8..6330461517 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt @@ -5,13 +5,13 @@ import net.corda.core.crypto.Crypto import net.corda.core.crypto.SignatureScheme import net.corda.core.identity.CordaX500Name import net.corda.core.internal.* -import net.corda.core.utilities.loggerFor import net.corda.node.utilities.* import net.corda.nodeapi.config.SSLConfiguration import net.corda.nodeapi.config.toProperties import org.bouncycastle.asn1.x509.GeneralName import org.bouncycastle.asn1.x509.GeneralSubtree import org.bouncycastle.asn1.x509.NameConstraints +import org.slf4j.LoggerFactory import java.nio.file.Path import java.security.KeyStore @@ -19,8 +19,7 @@ fun configOf(vararg pairs: Pair): Config = ConfigFactory.parseMap( operator fun Config.plus(overrides: Map): Config = ConfigFactory.parseMap(overrides).withFallback(this) object ConfigHelper { - private val log = loggerFor() - + private val log = LoggerFactory.getLogger(javaClass) fun loadConfig(baseDirectory: Path, configFile: Path = baseDirectory / "node.conf", allowMissingConfig: Boolean = false, diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt index 154cd82af2..7d4367bce6 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt @@ -39,6 +39,7 @@ interface NodeConfiguration : NodeSSLConfiguration { // TODO Move into DevModeOptions val useTestClock: Boolean get() = false val detectPublicIp: Boolean get() = true + val sshd: SSHDConfiguration? val relay: RelayConfiguration? } @@ -111,7 +112,9 @@ data class NodeConfigurationImpl( override val detectPublicIp: Boolean = true, override val activeMQServer: ActiveMqServerConfiguration, // TODO See TODO above. Rename this to nodeInfoPollingFrequency and make it of type Duration - override val additionalNodeInfoPollingFrequencyMsec: Long = 5.seconds.toMillis() + override val additionalNodeInfoPollingFrequencyMsec: Long = 5.seconds.toMillis(), + override val sshd: SSHDConfiguration? = null + ) : NodeConfiguration { override val exportJMXto: String get() = "http" @@ -147,6 +150,7 @@ data class CertChainPolicyConfig(val role: String, private val policy: CertChain } } +data class SSHDConfiguration(val port: Int) data class RelayConfiguration(val relayHost: String, val remoteInboundPort: Int, val username: String, diff --git a/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt b/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt index 27ca5d579b..ec4fdd855d 100644 --- a/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt +++ b/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt @@ -17,7 +17,7 @@ import net.corda.core.internal.until import net.corda.core.node.StateLoader import net.corda.core.schemas.PersistentStateRef import net.corda.core.serialization.SingletonSerializeAsToken -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.trace import net.corda.node.internal.MutableClock import net.corda.node.services.api.FlowStarter @@ -65,8 +65,7 @@ class NodeSchedulerService(private val clock: Clock, : SchedulerService, SingletonSerializeAsToken() { companion object { - private val log = loggerFor() - + private val log = contextLogger() /** * Wait until the given [Future] is complete or the deadline is reached, with support for [MutableClock] implementations * used in demos or testing. This will substitute a Fiber compatible Future so the current diff --git a/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt b/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt index 679e5251c4..77997cd721 100644 --- a/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt +++ b/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt @@ -8,7 +8,7 @@ import net.corda.core.internal.toX509CertHolder import net.corda.core.node.services.IdentityService import net.corda.core.node.services.UnknownAnonymousPartyException import net.corda.core.serialization.SingletonSerializeAsToken -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.trace import org.bouncycastle.cert.X509CertificateHolder import java.security.InvalidAlgorithmParameterException @@ -16,7 +16,6 @@ import java.security.PublicKey import java.security.cert.* import java.util.concurrent.ConcurrentHashMap import javax.annotation.concurrent.ThreadSafe -import javax.security.auth.x500.X500Principal /** * Simple identity service which caches parties and provides functionality for efficient lookup. @@ -33,7 +32,7 @@ class InMemoryIdentityService(identities: Iterable = emptyS trustRoot: X509CertificateHolder) : this(wellKnownIdentities, confidentialIdentities, trustRoot.cert) companion object { - private val log = loggerFor() + private val log = contextLogger() } /** diff --git a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt index 0e7f43460e..c1fd8ab618 100644 --- a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt +++ b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt @@ -10,11 +10,10 @@ import net.corda.core.node.services.IdentityService import net.corda.core.node.services.UnknownAnonymousPartyException import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.utilities.debug -import net.corda.core.utilities.loggerFor import net.corda.core.utilities.MAX_HASH_HEX_SIZE +import net.corda.core.utilities.contextLogger import net.corda.node.utilities.AppendOnlyPersistentMap import net.corda.node.utilities.NODE_DATABASE_PREFIX -import net.corda.node.utilities.X509Utilities import org.bouncycastle.cert.X509CertificateHolder import java.io.ByteArrayInputStream import java.security.InvalidAlgorithmParameterException @@ -36,7 +35,7 @@ class PersistentIdentityService(identities: Iterable = empt trustRoot: X509CertificateHolder) : this(wellKnownIdentities, confidentialIdentities, trustRoot.cert) companion object { - private val log = loggerFor() + private val log = contextLogger() private val certFactory: CertificateFactory = CertificateFactory.getInstance("X.509") fun createPKMap(): AppendOnlyPersistentMap { diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingClient.kt b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingClient.kt new file mode 100644 index 0000000000..f377087b90 --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingClient.kt @@ -0,0 +1,58 @@ +package net.corda.node.services.messaging + +import net.corda.core.serialization.internal.nodeSerializationEnv +import net.corda.core.utilities.NetworkHostAndPort +import net.corda.core.utilities.loggerFor +import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER +import net.corda.nodeapi.ArtemisTcpTransport +import net.corda.nodeapi.ConnectionDirection +import net.corda.nodeapi.config.SSLConfiguration +import org.apache.activemq.artemis.api.core.client.* +import org.apache.activemq.artemis.api.core.client.ActiveMQClient.DEFAULT_ACK_BATCH_SIZE + +class ArtemisMessagingClient(private val config: SSLConfiguration, private val serverAddress: NetworkHostAndPort) { + companion object { + private val log = loggerFor() + } + + class Started(val sessionFactory: ClientSessionFactory, val session: ClientSession, val producer: ClientProducer) + + var started: Started? = null + private set + + fun start(): Started = synchronized(this) { + check(started == null) { "start can't be called twice" } + log.info("Connecting to message broker: $serverAddress") + // TODO Add broker CN to config for host verification in case the embedded broker isn't used + val tcpTransport = ArtemisTcpTransport.tcpTransport(ConnectionDirection.Outbound(), serverAddress, config) + val locator = ActiveMQClient.createServerLocatorWithoutHA(tcpTransport).apply { + // Never time out on our loopback Artemis connections. If we switch back to using the InVM transport this + // would be the default and the two lines below can be deleted. + connectionTTL = -1 + clientFailureCheckPeriod = -1 + minLargeMessageSize = ArtemisMessagingServer.MAX_FILE_SIZE + isUseGlobalPools = nodeSerializationEnv != null + } + val sessionFactory = locator.createSessionFactory() + // Login using the node username. The broker will authentiate us as its node (as opposed to another peer) + // using our TLS certificate. + // Note that the acknowledgement of messages is not flushed to the Artermis journal until the default buffer + // size of 1MB is acknowledged. + val session = sessionFactory!!.createSession(NODE_USER, NODE_USER, false, true, true, locator.isPreAcknowledge, DEFAULT_ACK_BATCH_SIZE) + session.start() + // Create a general purpose producer. + val producer = session.createProducer() + return Started(sessionFactory, session, producer).also { started = it } + } + + fun stop() = synchronized(this) { + started!!.run { + producer.close() + // Ensure any trailing messages are committed to the journal + session.commit() + // Closing the factory closes all the sessions it produced as well. + sessionFactory.close() + } + started = null + } +} diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt index 8166d75080..68e9b0f79c 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt @@ -12,10 +12,7 @@ import net.corda.core.node.NodeInfo import net.corda.core.node.services.NetworkMapCache import net.corda.core.node.services.NetworkMapCache.MapChange import net.corda.core.serialization.SingletonSerializeAsToken -import net.corda.core.utilities.NetworkHostAndPort -import net.corda.core.utilities.debug -import net.corda.core.utilities.loggerFor -import net.corda.core.utilities.parsePublicKeyBase58 +import net.corda.core.utilities.* import net.corda.node.internal.Node import net.corda.node.services.RPCUserService import net.corda.node.services.config.NodeConfiguration @@ -99,7 +96,7 @@ class ArtemisMessagingServer(private val config: NodeConfiguration, val networkMapCache: NetworkMapCache, val userService: RPCUserService) : SingletonSerializeAsToken() { companion object { - private val log = loggerFor() + private val log = contextLogger() /** 10 MiB maximum allowed file size for attachments, including message headers. TODO: acquire this value from Network Map when supported. */ @JvmStatic val MAX_FILE_SIZE = 10485760 @@ -235,8 +232,10 @@ class ArtemisMessagingServer(private val config: NodeConfiguration, securityRoles["$INTERNAL_PREFIX#"] = setOf(nodeInternalRole) // Do not add any other roles here as it's only for the node securityRoles[P2P_QUEUE] = setOf(nodeInternalRole, restrictedRole(PEER_ROLE, send = true)) securityRoles[RPCApi.RPC_SERVER_QUEUE_NAME] = setOf(nodeInternalRole, restrictedRole(RPC_ROLE, send = true)) - // TODO remove the NODE_USER role once the webserver doesn't need it + // TODO: remove the NODE_USER role below once the webserver doesn't need it anymore. securityRoles["${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.$NODE_USER.#"] = setOf(nodeInternalRole) + // Each RPC user must have its own role and its own queue. This prevents users accessing each other's queues + // and stealing RPC responses. for ((username) in userService.users) { securityRoles["${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.$username.#"] = setOf( nodeInternalRole, @@ -417,7 +416,7 @@ private class VerifyingNettyConnector(configuration: MutableMap, protocolManager: ClientProtocolManager?) : NettyConnector(configuration, handler, listener, closeExecutor, threadPool, scheduledThreadPool, protocolManager) { companion object { - private val log = loggerFor() + private val log = contextLogger() } private val sslEnabled = ConfigurationHelper.getBooleanProperty(TransportConstants.SSL_ENABLED_PROP_NAME, TransportConstants.DEFAULT_SSL_ENABLED, configuration) @@ -540,8 +539,7 @@ class NodeLoginModule : LoginModule { const val VERIFIER_ROLE = "SystemRoles/Verifier" const val CERT_CHAIN_CHECKS_OPTION_NAME = "CertChainChecks" - - val log = loggerFor() + private val log = contextLogger() } private var loginSucceeded: Boolean = false diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt b/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt index 2241e3096f..4d91e00ec6 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt @@ -1,6 +1,5 @@ package net.corda.node.services.messaging -import com.google.common.util.concurrent.ListenableFuture import net.corda.core.concurrent.CordaFuture import net.corda.core.identity.CordaX500Name import net.corda.core.internal.concurrent.openFuture @@ -133,14 +132,6 @@ interface MessagingService { /** Returns an address that refers to this node. */ val myAddress: SingleMessageRecipient - - /** - * Initiates shutdown: if called from a thread that isn't controlled by the executor passed to the constructor - * then this will block until all in-flight messages have finished being handled and acknowledged. If called - * from a thread that's a part of the [net.corda.node.utilities.AffinityExecutor] given to the constructor, - * it returns immediately and shutdown is asynchronous. - */ - fun stop() } /** diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt b/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt similarity index 76% rename from node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt rename to node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt index 0720bd443a..8b56a45e70 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt @@ -1,50 +1,32 @@ package net.corda.node.services.messaging -import com.codahale.metrics.MetricRegistry -import net.corda.core.crypto.random63BitValue import net.corda.core.identity.CordaX500Name import net.corda.core.internal.ThreadBox import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.MessageRecipients -import net.corda.core.messaging.RPCOps import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.node.services.PartyInfo -import net.corda.core.node.services.TransactionVerifierService import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.deserialize -import net.corda.core.serialization.internal.nodeSerializationEnv import net.corda.core.serialization.serialize -import net.corda.core.transactions.LedgerTransaction +import net.corda.core.utilities.* import net.corda.core.utilities.NetworkHostAndPort -import net.corda.core.utilities.loggerFor import net.corda.core.utilities.sequence import net.corda.core.utilities.trace import net.corda.node.VersionInfo -import net.corda.node.services.RPCUserService import net.corda.node.services.config.NodeConfiguration -import net.corda.node.services.config.VerifierType import net.corda.node.services.statemachine.StateMachineManagerImpl -import net.corda.node.services.transactions.InMemoryTransactionVerifierService -import net.corda.node.services.transactions.OutOfProcessTransactionVerifierService import net.corda.node.utilities.* -import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_QUEUE import net.corda.nodeapi.internal.ArtemisMessagingComponent.ArtemisAddress import net.corda.nodeapi.internal.ArtemisMessagingComponent.NodeAddress import net.corda.nodeapi.internal.ArtemisMessagingComponent.ServiceAddress -import net.corda.nodeapi.ArtemisTcpTransport -import net.corda.nodeapi.ConnectionDirection -import net.corda.nodeapi.VerifierApi -import net.corda.nodeapi.VerifierApi.VERIFICATION_REQUESTS_QUEUE_NAME -import net.corda.nodeapi.VerifierApi.VERIFICATION_RESPONSES_QUEUE_NAME_PREFIX import org.apache.activemq.artemis.api.core.ActiveMQObjectClosedException import org.apache.activemq.artemis.api.core.Message.* import org.apache.activemq.artemis.api.core.RoutingType import org.apache.activemq.artemis.api.core.SimpleString import org.apache.activemq.artemis.api.core.client.* -import org.apache.activemq.artemis.api.core.client.ActiveMQClient.DEFAULT_ACK_BATCH_SIZE -import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl import java.security.PublicKey import java.time.Instant import java.util.* @@ -77,18 +59,16 @@ import javax.persistence.Lob * If not provided, will default to [serverAddress]. */ @ThreadSafe -class NodeMessagingClient(private val config: NodeConfiguration, - private val versionInfo: VersionInfo, - private val serverAddress: NetworkHostAndPort, - private val myIdentity: PublicKey, - private val nodeExecutor: AffinityExecutor.ServiceAffinityExecutor, - private val database: CordaPersistence, - private val metrics: MetricRegistry, - advertisedAddress: NetworkHostAndPort = serverAddress +class P2PMessagingClient(config: NodeConfiguration, + private val versionInfo: VersionInfo, + serverAddress: NetworkHostAndPort, + private val myIdentity: PublicKey, + private val nodeExecutor: AffinityExecutor.ServiceAffinityExecutor, + private val database: CordaPersistence, + advertisedAddress: NetworkHostAndPort = serverAddress ) : SingletonSerializeAsToken(), MessagingService { companion object { - private val log = loggerFor() - + private val log = contextLogger() // This is a "property" attached to an Artemis MQ message object, which contains our own notion of "topic". // We should probably try to unify our notion of "topic" (really, just a string that identifies an endpoint // that will handle messages, like a URL) with the terminology used by underlying MQ libraries, to avoid @@ -99,8 +79,6 @@ class NodeMessagingClient(private val config: NodeConfiguration, private val releaseVersionProperty = SimpleString("release-version") private val platformVersionProperty = SimpleString("platform-version") private val amqDelayMillis = System.getProperty("amq.delivery.delay.ms", "0").toInt() - private val verifierResponseAddress = "$VERIFICATION_RESPONSES_QUEUE_NAME_PREFIX.${random63BitValue()}" - private val messageMaxRetryCount: Int = 3 fun createProcessedMessage(): AppendOnlyPersistentMap { @@ -144,15 +122,8 @@ class NodeMessagingClient(private val config: NodeConfiguration, } private class InnerState { - var started = false var running = false - var producer: ClientProducer? = null var p2pConsumer: ClientConsumer? = null - var session: ClientSession? = null - var sessionFactory: ClientSessionFactory? = null - var rpcServer: RPCServer? = null - // Consumer for inbound client RPC messages. - var verificationResponseConsumer: ClientConsumer? = null } private val messagesToRedeliver = database.transaction { @@ -161,11 +132,6 @@ class NodeMessagingClient(private val config: NodeConfiguration, private val scheduledMessageRedeliveries = ConcurrentHashMap>() - val verifierService = when (config.verifierType) { - VerifierType.InMemory -> InMemoryTransactionVerifierService(numberOfWorkers = 4) - VerifierType.OutOfProcess -> createOutOfProcessVerifierService() - } - /** A registration to handle messages of different types */ data class Handler(val topicSession: TopicSession, val callback: (ReceivedMessage, MessageHandlerRegistration) -> Unit) : MessageHandlerRegistration @@ -176,7 +142,8 @@ class NodeMessagingClient(private val config: NodeConfiguration, private val messagingExecutor = AffinityExecutor.ServiceAffinityExecutor("Messaging", 1) override val myAddress: SingleMessageRecipient = NodeAddress(myIdentity, advertisedAddress) - + private val messageRedeliveryDelaySeconds = config.messageRedeliveryDelaySeconds.toLong() + private val artemis = ArtemisMessagingClient(config, serverAddress) private val state = ThreadBox(InnerState()) private val handlers = CopyOnWriteArrayList() @@ -209,54 +176,11 @@ class NodeMessagingClient(private val config: NodeConfiguration, var recipients: ByteArray = ByteArray(0) ) - fun start(rpcOps: RPCOps, userService: RPCUserService) { + fun start() { state.locked { - check(!started) { "start can't be called twice" } - started = true - - log.info("Connecting to message broker: $serverAddress") - // TODO Add broker CN to config for host verification in case the embedded broker isn't used - val tcpTransport = ArtemisTcpTransport.tcpTransport(ConnectionDirection.Outbound(), serverAddress, config) - val locator = ActiveMQClient.createServerLocatorWithoutHA(tcpTransport).apply { - // Never time out on our loopback Artemis connections. If we switch back to using the InVM transport this - // would be the default and the two lines below can be deleted. - connectionTTL = -1 - clientFailureCheckPeriod = -1 - minLargeMessageSize = ArtemisMessagingServer.MAX_FILE_SIZE - isUseGlobalPools = nodeSerializationEnv != null - } - sessionFactory = locator.createSessionFactory() - - // Login using the node username. The broker will authentiate us as its node (as opposed to another peer) - // using our TLS certificate. - // Note that the acknowledgement of messages is not flushed to the Artermis journal until the default buffer - // size of 1MB is acknowledged. - val session = sessionFactory!!.createSession(NODE_USER, NODE_USER, false, true, true, locator.isPreAcknowledge, DEFAULT_ACK_BATCH_SIZE) - this.session = session - session.start() - - // Create a general purpose producer. - val producer = session.createProducer() - this.producer = producer - + val session = artemis.start().session // Create a queue, consumer and producer for handling P2P network messages. p2pConsumer = session.createConsumer(P2P_QUEUE) - - val myCert = loadKeyStore(config.sslKeystore, config.keyStorePassword).getX509Certificate(X509Utilities.CORDA_CLIENT_TLS) - rpcServer = RPCServer(rpcOps, NODE_USER, NODE_USER, locator, userService, CordaX500Name.build(myCert.subjectX500Principal)) - - fun checkVerifierCount() { - if (session.queueQuery(SimpleString(VERIFICATION_REQUESTS_QUEUE_NAME)).consumerCount == 0) { - log.warn("No connected verifier listening on $VERIFICATION_REQUESTS_QUEUE_NAME!") - } - } - - if (config.verifierType == VerifierType.OutOfProcess) { - createQueueIfAbsent(VerifierApi.VERIFICATION_REQUESTS_QUEUE_NAME) - createQueueIfAbsent(verifierResponseAddress) - verificationResponseConsumer = session.createConsumer(verifierResponseAddress) - messagingExecutor.scheduleAtFixedRate(::checkVerifierCount, 0, 10, TimeUnit.SECONDS) - } } resumeMessageRedelivery() @@ -309,14 +233,12 @@ class NodeMessagingClient(private val config: NodeConfiguration, /** * Starts the p2p event loop: this method only returns once [stop] has been called. */ - fun run(serverControl: ActiveMQServerControl) { + fun run() { try { val consumer = state.locked { - check(started) { "start must be called first" } + check(artemis.started != null) { "start must be called first" } check(!running) { "run can't be called twice" } running = true - rpcServer!!.start(serverControl) - (verifierService as? OutOfProcessTransactionVerifierService)?.start(verificationResponseConsumer!!) // If it's null, it means we already called stop, so return immediately. p2pConsumer ?: return } @@ -401,10 +323,16 @@ class NodeMessagingClient(private val config: NodeConfiguration, } } - override fun stop() { + /** + * Initiates shutdown: if called from a thread that isn't controlled by the executor passed to the constructor + * then this will block until all in-flight messages have finished being handled and acknowledged. If called + * from a thread that's a part of the [net.corda.node.utilities.AffinityExecutor] given to the constructor, + * it returns immediately and shutdown is asynchronous. + */ + fun stop() { val running = state.locked { // We allow stop() to be called without a run() in between, but it must have at least been started. - check(started) + check(artemis.started != null) val prevRunning = running running = false val c = p2pConsumer ?: throw IllegalStateException("stop can't be called twice") @@ -423,13 +351,7 @@ class NodeMessagingClient(private val config: NodeConfiguration, // Only first caller to gets running true to protect against double stop, which seems to happen in some integration tests. if (running) { state.locked { - producer?.close() - producer = null - // Ensure any trailing messages are committed to the journal - session!!.commit() - // Closing the factory closes all the sessions it produced as well. - sessionFactory!!.close() - sessionFactory = null + artemis.stop() } } } @@ -440,7 +362,8 @@ class NodeMessagingClient(private val config: NodeConfiguration, messagingExecutor.fetchFrom { state.locked { val mqAddress = getMQAddress(target) - val artemisMessage = session!!.createMessage(true).apply { + val artemis = artemis.started!! + val artemisMessage = artemis.session.createMessage(true).apply { putStringProperty(cordaVendorProperty, cordaVendor) putStringProperty(releaseVersionProperty, releaseVersion) putIntProperty(platformVersionProperty, versionInfo.platformVersion) @@ -459,15 +382,14 @@ class NodeMessagingClient(private val config: NodeConfiguration, "Send to: $mqAddress topic: ${message.topicSession.topic} " + "sessionID: ${message.topicSession.sessionID} uuid: ${message.uniqueMessageId}" } - producer!!.send(mqAddress, artemisMessage) - + artemis.producer.send(mqAddress, artemisMessage) retryId?.let { database.transaction { messagesToRedeliver.computeIfAbsent(it, { Pair(message, target) }) } scheduledMessageRedeliveries[it] = messagingExecutor.schedule({ sendWithRetry(0, mqAddress, artemisMessage, it) - }, config.messageRedeliveryDelaySeconds.toLong(), TimeUnit.SECONDS) + }, messageRedeliveryDelaySeconds, TimeUnit.SECONDS) } } @@ -498,12 +420,12 @@ class NodeMessagingClient(private val config: NodeConfiguration, state.locked { log.trace { "Retry #$retryCount sending message $message to $address for $retryId" } - producer!!.send(address, message) + artemis.started!!.producer.send(address, message) } scheduledMessageRedeliveries[retryId] = messagingExecutor.schedule({ sendWithRetry(retryCount + 1, address, message, retryId) - }, config.messageRedeliveryDelaySeconds.toLong(), TimeUnit.SECONDS) + }, messageRedeliveryDelaySeconds, TimeUnit.SECONDS) } override fun cancelRedelivery(retryId: Long) { @@ -533,10 +455,11 @@ class NodeMessagingClient(private val config: NodeConfiguration, /** Attempts to create a durable queue on the broker which is bound to an address of the same name. */ private fun createQueueIfAbsent(queueName: String) { state.alreadyLocked { - val queueQuery = session!!.queueQuery(SimpleString(queueName)) + val session = artemis.started!!.session + val queueQuery = session.queueQuery(SimpleString(queueName)) if (!queueQuery.isExists) { log.info("Create fresh queue $queueName bound on same address") - session!!.createQueue(queueName, RoutingType.MULTICAST, queueName, true) + session.createQueue(queueName, RoutingType.MULTICAST, queueName, true) } } } @@ -564,22 +487,6 @@ class NodeMessagingClient(private val config: NodeConfiguration, return NodeClientMessage(topicSession, data, uuid) } - private fun createOutOfProcessVerifierService(): TransactionVerifierService { - return object : OutOfProcessTransactionVerifierService(metrics) { - override fun sendRequest(nonce: Long, transaction: LedgerTransaction) { - messagingExecutor.fetchFrom { - state.locked { - val message = session!!.createMessage(false) - val request = VerifierApi.VerificationRequest(nonce, transaction, SimpleString(verifierResponseAddress)) - request.writeToClientMessage(message) - producer!!.send(VERIFICATION_REQUESTS_QUEUE_NAME, message) - } - } - } - - } - } - // TODO Rethink PartyInfo idea and merging PeerAddress/ServiceAddress (the only difference is that Service address doesn't hold host and port) override fun getAddressOfParty(partyInfo: PartyInfo): MessageRecipients { return when (partyInfo) { diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/RPCMessagingClient.kt b/node/src/main/kotlin/net/corda/node/services/messaging/RPCMessagingClient.kt new file mode 100644 index 0000000000..4aeb8339fa --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/services/messaging/RPCMessagingClient.kt @@ -0,0 +1,29 @@ +package net.corda.node.services.messaging + +import net.corda.core.identity.CordaX500Name +import net.corda.core.messaging.RPCOps +import net.corda.core.serialization.SingletonSerializeAsToken +import net.corda.core.utilities.NetworkHostAndPort +import net.corda.node.services.RPCUserService +import net.corda.node.utilities.* +import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER +import net.corda.nodeapi.config.SSLConfiguration +import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl + +class RPCMessagingClient(private val config: SSLConfiguration, serverAddress: NetworkHostAndPort) : SingletonSerializeAsToken() { + private val artemis = ArtemisMessagingClient(config, serverAddress) + private var rpcServer: RPCServer? = null + fun start(rpcOps: RPCOps, userService: RPCUserService) = synchronized(this) { + val locator = artemis.start().sessionFactory.serverLocator + val myCert = loadKeyStore(config.sslKeystore, config.keyStorePassword).getX509Certificate(X509Utilities.CORDA_CLIENT_TLS) + rpcServer = RPCServer(rpcOps, NODE_USER, NODE_USER, locator, userService, CordaX500Name.build(myCert.subjectX500Principal)) + } + + fun start2(serverControl: ActiveMQServerControl) = synchronized(this) { + rpcServer!!.start(serverControl) + } + + fun stop() = synchronized(this) { + artemis.stop() + } +} diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt b/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt index 9de779a9a9..2f2716dd76 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt @@ -24,10 +24,7 @@ import net.corda.core.messaging.RPCOps import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationDefaults.RPC_SERVER_CONTEXT import net.corda.core.serialization.deserialize -import net.corda.core.utilities.Try -import net.corda.core.utilities.debug -import net.corda.core.utilities.loggerFor -import net.corda.core.utilities.seconds +import net.corda.core.utilities.* import net.corda.node.services.RPCUserService import net.corda.node.services.logging.pushToLoggingContext import net.corda.nodeapi.* @@ -42,6 +39,7 @@ import org.apache.activemq.artemis.api.core.client.ServerLocator import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl import org.apache.activemq.artemis.api.core.management.CoreNotificationType import org.apache.activemq.artemis.api.core.management.ManagementHelper +import org.slf4j.LoggerFactory import org.slf4j.MDC import rx.Notification import rx.Observable @@ -90,7 +88,7 @@ class RPCServer( private val rpcConfiguration: RPCServerConfiguration = RPCServerConfiguration.default ) { private companion object { - val log = loggerFor() + private val log = contextLogger() } private enum class State { @@ -442,7 +440,7 @@ class ObservableContext( val observationSendExecutor: ExecutorService ) { private companion object { - val log = loggerFor() + private val log = contextLogger() } private val serializationContextWithObservableContext = RpcServerObservableSerializer.createContext(this) @@ -465,8 +463,7 @@ class ObservableContext( object RpcServerObservableSerializer : Serializer>() { private object RpcObservableContextKey - private val log = loggerFor() - + private val log = LoggerFactory.getLogger(javaClass) fun createContext(observableContext: ObservableContext): SerializationContext { return RPC_SERVER_CONTEXT.withProperty(RpcServerObservableSerializer.RpcObservableContextKey, observableContext) } diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/RpcAuthContext.kt b/node/src/main/kotlin/net/corda/node/services/messaging/RpcAuthContext.kt index 8daa20f2a3..58cd73de22 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/RpcAuthContext.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/RpcAuthContext.kt @@ -23,6 +23,7 @@ data class RpcPermissions(private val values: Set = emptySet()) { companion object { val NONE = RpcPermissions() + val ALL = RpcPermissions(setOf("ALL")) } fun coverAny(permissions: Set) = !values.intersect(permissions + Permissions.all()).isEmpty() diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/VerifierMessagingClient.kt b/node/src/main/kotlin/net/corda/node/services/messaging/VerifierMessagingClient.kt new file mode 100644 index 0000000000..d916e63cc5 --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/services/messaging/VerifierMessagingClient.kt @@ -0,0 +1,73 @@ +package net.corda.node.services.messaging + +import com.codahale.metrics.MetricRegistry +import net.corda.core.crypto.random63BitValue +import net.corda.core.serialization.SingletonSerializeAsToken +import net.corda.core.transactions.LedgerTransaction +import net.corda.core.utilities.NetworkHostAndPort +import net.corda.core.utilities.loggerFor +import net.corda.node.services.transactions.OutOfProcessTransactionVerifierService +import net.corda.node.utilities.* +import net.corda.nodeapi.VerifierApi +import net.corda.nodeapi.VerifierApi.VERIFICATION_REQUESTS_QUEUE_NAME +import net.corda.nodeapi.VerifierApi.VERIFICATION_RESPONSES_QUEUE_NAME_PREFIX +import net.corda.nodeapi.config.SSLConfiguration +import org.apache.activemq.artemis.api.core.RoutingType +import org.apache.activemq.artemis.api.core.SimpleString +import org.apache.activemq.artemis.api.core.client.* +import java.util.concurrent.* + +class VerifierMessagingClient(config: SSLConfiguration, serverAddress: NetworkHostAndPort, metrics: MetricRegistry) : SingletonSerializeAsToken() { + companion object { + private val log = loggerFor() + private val verifierResponseAddress = "$VERIFICATION_RESPONSES_QUEUE_NAME_PREFIX.${random63BitValue()}" + } + + private val artemis = ArtemisMessagingClient(config, serverAddress) + /** An executor for sending messages */ + private val messagingExecutor = AffinityExecutor.ServiceAffinityExecutor("Messaging", 1) + private var verificationResponseConsumer: ClientConsumer? = null + fun start(): Unit = synchronized(this) { + val session = artemis.start().session + fun checkVerifierCount() { + if (session.queueQuery(SimpleString(VERIFICATION_REQUESTS_QUEUE_NAME)).consumerCount == 0) { + log.warn("No connected verifier listening on $VERIFICATION_REQUESTS_QUEUE_NAME!") + } + } + + // Attempts to create a durable queue on the broker which is bound to an address of the same name. + fun createQueueIfAbsent(queueName: String) { + val queueQuery = session.queueQuery(SimpleString(queueName)) + if (!queueQuery.isExists) { + log.info("Create fresh queue $queueName bound on same address") + session.createQueue(queueName, RoutingType.MULTICAST, queueName, true) + } + } + createQueueIfAbsent(VERIFICATION_REQUESTS_QUEUE_NAME) + createQueueIfAbsent(verifierResponseAddress) + verificationResponseConsumer = session.createConsumer(verifierResponseAddress) + messagingExecutor.scheduleAtFixedRate(::checkVerifierCount, 0, 10, TimeUnit.SECONDS) + } + + fun start2() = synchronized(this) { + verifierService.start(verificationResponseConsumer!!) + } + + fun stop() = synchronized(this) { + artemis.stop() + } + + internal val verifierService = OutOfProcessTransactionVerifierService(metrics) { nonce, transaction -> + messagingExecutor.fetchFrom { + sendRequest(nonce, transaction) + } + } + + private fun sendRequest(nonce: Long, transaction: LedgerTransaction) = synchronized(this) { + val started = artemis.started!! + val message = started.session.createMessage(false) + val request = VerifierApi.VerificationRequest(nonce, transaction, SimpleString(verifierResponseAddress)) + request.writeToClientMessage(message) + started.producer.send(VERIFICATION_REQUESTS_QUEUE_NAME, message) + } +} diff --git a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt index 24a3b92fe0..02f4310061 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt @@ -8,7 +8,7 @@ import net.corda.core.internal.openHttpConnection import net.corda.core.node.NodeInfo import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.minutes import net.corda.core.utilities.seconds import net.corda.node.services.api.NetworkMapCacheInternal @@ -25,10 +25,6 @@ import java.util.concurrent.Executors import java.util.concurrent.TimeUnit class NetworkMapClient(compatibilityZoneURL: URL) { - companion object { - val logger = loggerFor() - } - private val networkMapUrl = URL("$compatibilityZoneURL/network-map") fun publish(signedNodeInfo: SignedData) { @@ -73,7 +69,7 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal, private val fileWatcher: NodeInfoWatcher, private val networkMapClient: NetworkMapClient?) : Closeable { companion object { - private val logger = loggerFor() + private val logger = contextLogger() private val retryInterval = 1.minutes } diff --git a/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt b/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt index 579c91afe4..76fcf6c472 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt @@ -7,7 +7,7 @@ import net.corda.core.internal.* import net.corda.core.node.NodeInfo import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.seconds import net.corda.nodeapi.NodeInfoFilesCopier import rx.Observable @@ -40,8 +40,7 @@ class NodeInfoWatcher(private val nodePath: Path, val processedNodeInfoHashes: Set get() = _processedNodeInfoHashes.toSet() companion object { - private val logger = loggerFor() - + private val logger = contextLogger() /** * Saves the given [NodeInfo] to a path. * The node is 'encoded' as a SignedData, signed with the owning key of its first identity. diff --git a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt index 35da4d013b..982f4da070 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt @@ -19,7 +19,7 @@ import net.corda.core.node.services.PartyInfo import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.serialize import net.corda.core.utilities.NetworkHostAndPort -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.node.services.api.NetworkMapCacheBaseInternal import net.corda.node.services.api.NetworkMapCacheInternal import net.corda.node.utilities.CordaPersistence @@ -63,7 +63,7 @@ class NetworkMapCacheImpl( @ThreadSafe open class PersistentNetworkMapCache(private val database: CordaPersistence) : SingletonSerializeAsToken(), NetworkMapCacheBaseInternal { companion object { - val logger = loggerFor() + private val logger = contextLogger() } // TODO Small explanation, partyNodes and registeredNodes is left in memory as it was before, because it will be removed in diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/AbstractPartyDescriptor.kt b/node/src/main/kotlin/net/corda/node/services/persistence/AbstractPartyDescriptor.kt index 7ae975368e..7201c7df56 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/AbstractPartyDescriptor.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/AbstractPartyDescriptor.kt @@ -3,7 +3,7 @@ package net.corda.node.services.persistence import net.corda.core.identity.AbstractParty import net.corda.core.identity.CordaX500Name import net.corda.core.node.services.IdentityService -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import org.hibernate.type.descriptor.WrapperOptions import org.hibernate.type.descriptor.java.AbstractTypeDescriptor import org.hibernate.type.descriptor.java.ImmutableMutabilityPlan @@ -11,7 +11,7 @@ import org.hibernate.type.descriptor.java.MutabilityPlan class AbstractPartyDescriptor(identitySvc: () -> IdentityService) : AbstractTypeDescriptor(AbstractParty::class.java) { companion object { - private val log = loggerFor() + private val log = contextLogger() } private val identityService: IdentityService by lazy(identitySvc) diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/AbstractPartyToX500NameAsStringConverter.kt b/node/src/main/kotlin/net/corda/node/services/persistence/AbstractPartyToX500NameAsStringConverter.kt index 81dfe1c7ec..dcaf9f82d3 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/AbstractPartyToX500NameAsStringConverter.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/AbstractPartyToX500NameAsStringConverter.kt @@ -3,7 +3,7 @@ package net.corda.node.services.persistence import net.corda.core.identity.AbstractParty import net.corda.core.identity.CordaX500Name import net.corda.core.node.services.IdentityService -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import javax.persistence.AttributeConverter import javax.persistence.Converter @@ -14,7 +14,7 @@ import javax.persistence.Converter @Converter(autoApply = true) class AbstractPartyToX500NameAsStringConverter(identitySvc: () -> IdentityService) : AttributeConverter { companion object { - private val log = loggerFor() + private val log = contextLogger() } private val identityService: IdentityService by lazy(identitySvc) diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/HibernateConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/persistence/HibernateConfiguration.kt index cd6b93de57..7d5f20b248 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/HibernateConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/HibernateConfiguration.kt @@ -3,8 +3,7 @@ package net.corda.node.services.persistence import net.corda.core.internal.castIfPossible import net.corda.core.node.services.IdentityService import net.corda.core.schemas.MappedSchema -import net.corda.core.utilities.OpaqueBytes -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.toHexString import net.corda.node.services.api.SchemaService import net.corda.node.utilities.DatabaseTransactionManager @@ -29,7 +28,7 @@ import java.util.concurrent.ConcurrentHashMap class HibernateConfiguration(val schemaService: SchemaService, private val databaseProperties: Properties, private val createIdentityService: () -> IdentityService) { companion object { - val logger = loggerFor() + private val logger = contextLogger() } // TODO: make this a guava cache or similar to limit ability for this to grow forever. diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt b/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt index 4e5fcf4530..6c4d4a9e75 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt @@ -1,32 +1,31 @@ package net.corda.node.services.persistence import com.codahale.metrics.MetricRegistry -import net.corda.core.internal.VisibleForTesting import com.google.common.hash.HashCode import com.google.common.hash.Hashing import com.google.common.hash.HashingInputStream import com.google.common.io.CountingInputStream import net.corda.core.CordaRuntimeException -import net.corda.core.contracts.* -import net.corda.core.internal.AbstractAttachment +import net.corda.core.contracts.Attachment import net.corda.core.crypto.SecureHash +import net.corda.core.internal.AbstractAttachment +import net.corda.core.internal.VisibleForTesting import net.corda.core.node.services.AttachmentId import net.corda.core.node.services.AttachmentStorage -import net.corda.core.node.services.vault.* +import net.corda.core.node.services.vault.AttachmentQueryCriteria +import net.corda.core.node.services.vault.AttachmentSort import net.corda.core.serialization.* -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.node.services.vault.HibernateAttachmentQueryCriteriaParser import net.corda.node.utilities.DatabaseTransactionManager import net.corda.node.utilities.NODE_DATABASE_PREFIX import net.corda.node.utilities.currentDBSession import java.io.* -import java.lang.Exception import java.nio.file.Paths import java.time.Instant import java.util.jar.JarInputStream import javax.annotation.concurrent.ThreadSafe import javax.persistence.* -import javax.persistence.Column /** * Stores attachments using Hibernate to database. @@ -34,6 +33,29 @@ import javax.persistence.Column @ThreadSafe class NodeAttachmentService(metrics: MetricRegistry) : AttachmentStorage, SingletonSerializeAsToken() { + companion object { + private val log = contextLogger() + + // Just iterate over the entries with verification enabled: should be good enough to catch mistakes. + // Note that JarInputStream won't throw any kind of error at all if the file stream is in fact not + // a ZIP! It'll just pretend it's an empty archive, which is kind of stupid but that's how it works. + // So we have to check to ensure we found at least one item. + private fun checkIsAValidJAR(stream: InputStream) { + val jar = JarInputStream(stream, true) + var count = 0 + while (true) { + val cursor = jar.nextJarEntry ?: break + val entryPath = Paths.get(cursor.name) + // Security check to stop zips trying to escape their rightful place. + require(!entryPath.isAbsolute) { "Path $entryPath is absolute" } + require(entryPath.normalize() == entryPath) { "Path $entryPath is not normalised" } + require(!('\\' in cursor.name || cursor.name == "." || cursor.name == "..")) { "Bad character in $entryPath" } + count++ + } + require(count > 0) { "Stream is either empty or not a JAR/ZIP" } + } + } + @Entity @Table(name = "${NODE_DATABASE_PREFIX}attachments", indexes = arrayOf(Index(name = "att_id_idx", columnList = "att_id"))) @@ -56,10 +78,6 @@ class NodeAttachmentService(metrics: MetricRegistry) : AttachmentStorage, Single var filename: String? = null ) : Serializable - companion object { - private val log = loggerFor() - } - @VisibleForTesting var checkAttachmentsOnLoad = true @@ -171,6 +189,24 @@ class NodeAttachmentService(metrics: MetricRegistry) : AttachmentStorage, Single return import(jar, uploader, filename) } + fun getAttachmentIdAndBytes(jar: InputStream): Pair { + val hs = HashingInputStream(Hashing.sha256(), jar) + val bytes = hs.readBytes() + checkIsAValidJAR(ByteArrayInputStream(bytes)) + val id = SecureHash.SHA256(hs.hash().asBytes()) + return Pair(id, bytes) + } + + override fun hasAttachment(attachmentId: AttachmentId): Boolean { + val session = currentDBSession() + val criteriaBuilder = session.criteriaBuilder + val criteriaQuery = criteriaBuilder.createQuery(Long::class.java) + val attachments = criteriaQuery.from(NodeAttachmentService.DBAttachment::class.java) + criteriaQuery.select(criteriaBuilder.count(criteriaQuery.from(NodeAttachmentService.DBAttachment::class.java))) + criteriaQuery.where(criteriaBuilder.equal(attachments.get(DBAttachment::attId.name), attachmentId.toString())) + return (session.createQuery(criteriaQuery).singleResult > 0) + } + // TODO: PLT-147: The attachment should be randomised to prevent brute force guessing and thus privacy leaks. private fun import(jar: InputStream, uploader: String?, filename: String?): AttachmentId { require(jar !is JarInputStream) @@ -180,27 +216,28 @@ class NodeAttachmentService(metrics: MetricRegistry) : AttachmentStorage, Single // To do this we must pipe stream into the database without knowing its hash, which we will learn only once // the insert/upload is complete. We can then query to see if it's a duplicate and if so, erase, and if not // set the hash field of the new attachment record. - val hs = HashingInputStream(Hashing.sha256(), jar) - val bytes = hs.readBytes() - checkIsAValidJAR(ByteArrayInputStream(bytes)) - val id = SecureHash.SHA256(hs.hash().asBytes()) - val session = currentDBSession() - val criteriaBuilder = session.criteriaBuilder - val criteriaQuery = criteriaBuilder.createQuery(Long::class.java) - val attachments = criteriaQuery.from(NodeAttachmentService.DBAttachment::class.java) - criteriaQuery.select(criteriaBuilder.count(criteriaQuery.from(NodeAttachmentService.DBAttachment::class.java))) - criteriaQuery.where(criteriaBuilder.equal(attachments.get(DBAttachment::attId.name), id.toString())) - val count = session.createQuery(criteriaQuery).singleResult - if (count == 0L) { + val (id, bytes) = getAttachmentIdAndBytes(jar) + if (!hasAttachment(id)) { + checkIsAValidJAR(ByteArrayInputStream(bytes)) + val session = currentDBSession() val attachment = NodeAttachmentService.DBAttachment(attId = id.toString(), content = bytes, uploader = uploader, filename = filename) session.save(attachment) - attachmentCount.inc() log.info("Stored new attachment $id") + return id + } else { + throw java.nio.file.FileAlreadyExistsException(id.toString()) } + } - return id + override fun importOrGetAttachment(jar: InputStream): AttachmentId { + try { + return importAttachment(jar) + } + catch (faee: java.nio.file.FileAlreadyExistsException) { + return AttachmentId.parse(faee.message!!) + } } override fun queryAttachments(criteria: AttachmentQueryCriteria, sorting: AttachmentSort?): List { @@ -227,22 +264,4 @@ class NodeAttachmentService(metrics: MetricRegistry) : AttachmentStorage, Single } - private fun checkIsAValidJAR(stream: InputStream) { - // Just iterate over the entries with verification enabled: should be good enough to catch mistakes. - // Note that JarInputStream won't throw any kind of error at all if the file stream is in fact not - // a ZIP! It'll just pretend it's an empty archive, which is kind of stupid but that's how it works. - // So we have to check to ensure we found at least one item. - val jar = JarInputStream(stream, true) - var count = 0 - while (true) { - val cursor = jar.nextJarEntry ?: break - val entryPath = Paths.get(cursor.name) - // Security check to stop zips trying to escape their rightful place. - require(!entryPath.isAbsolute) { "Path $entryPath is absolute" } - require(entryPath.normalize() == entryPath) { "Path $entryPath is not normalised" } - require(!('\\' in cursor.name || cursor.name == "." || cursor.name == "..")) { "Bad character in $entryPath" } - count++ - } - require(count > 0) { "Stream is either empty or not a JAR/ZIP" } - } } diff --git a/node/src/main/kotlin/net/corda/node/services/schema/HibernateObserver.kt b/node/src/main/kotlin/net/corda/node/services/schema/HibernateObserver.kt index 2babcc1d70..41e6b6d641 100644 --- a/node/src/main/kotlin/net/corda/node/services/schema/HibernateObserver.kt +++ b/node/src/main/kotlin/net/corda/node/services/schema/HibernateObserver.kt @@ -7,8 +7,8 @@ import net.corda.core.internal.VisibleForTesting import net.corda.core.node.services.Vault import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentStateRef +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.debug -import net.corda.core.utilities.loggerFor import net.corda.node.services.persistence.HibernateConfiguration import net.corda.node.utilities.DatabaseTransactionManager import org.hibernate.FlushMode @@ -20,7 +20,7 @@ import rx.Observable // TODO: Manage version evolution of the schemas via additional tooling. class HibernateObserver private constructor(private val config: HibernateConfiguration) { companion object { - private val log = loggerFor() + private val log = contextLogger() @JvmStatic fun install(vaultUpdates: Observable>, config: HibernateConfiguration): HibernateObserver { val observer = HibernateObserver(config) diff --git a/node/src/main/kotlin/net/corda/node/services/schema/NodeSchemaService.kt b/node/src/main/kotlin/net/corda/node/services/schema/NodeSchemaService.kt index 00cb1b5f79..99282681b8 100644 --- a/node/src/main/kotlin/net/corda/node/services/schema/NodeSchemaService.kt +++ b/node/src/main/kotlin/net/corda/node/services/schema/NodeSchemaService.kt @@ -14,7 +14,7 @@ import net.corda.node.services.api.SchemaService import net.corda.node.services.events.NodeSchedulerService import net.corda.node.services.identity.PersistentIdentityService import net.corda.node.services.keys.PersistentKeyManagementService -import net.corda.node.services.messaging.NodeMessagingClient +import net.corda.node.services.messaging.P2PMessagingClient import net.corda.node.services.persistence.DBCheckpointStorage import net.corda.node.services.persistence.DBTransactionMappingStorage import net.corda.node.services.persistence.DBTransactionStorage @@ -47,8 +47,8 @@ class NodeSchemaService(cordappLoader: CordappLoader?) : SchemaService, Singleto PersistentUniquenessProvider.PersistentNotaryCommit::class.java, NodeSchedulerService.PersistentScheduledState::class.java, NodeAttachmentService.DBAttachment::class.java, - NodeMessagingClient.ProcessedMessage::class.java, - NodeMessagingClient.RetryMessage::class.java, + P2PMessagingClient.ProcessedMessage::class.java, + P2PMessagingClient.RetryMessage::class.java, NodeAttachmentService.DBAttachment::class.java, RaftUniquenessProvider.RaftState::class.java, BFTNonValidatingNotaryService.PersistedCommittedState::class.java, diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStackSnapshotFactory.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStackSnapshotFactory.kt index 72a7926eb2..7360954d31 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStackSnapshotFactory.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStackSnapshotFactory.kt @@ -3,7 +3,7 @@ package net.corda.node.services.statemachine import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowStackSnapshot import net.corda.core.flows.StateMachineRunId -import net.corda.core.utilities.loggerFor +import org.slf4j.LoggerFactory import java.nio.file.Path import java.util.* @@ -26,8 +26,7 @@ interface FlowStackSnapshotFactory { fun persistAsJsonFile(flowClass: Class>, baseDir: Path, flowId: StateMachineRunId) private object DefaultFlowStackSnapshotFactory : FlowStackSnapshotFactory { - private val log = loggerFor() - + private val log = LoggerFactory.getLogger(javaClass) override fun getFlowStackSnapshot(flowClass: Class>): FlowStackSnapshot? { log.warn("Flow stack snapshot are not supposed to be used in a production deployment") return null diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt index 29c261aa7a..73fe043ee8 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt @@ -27,10 +27,7 @@ import net.corda.core.serialization.SerializationDefaults.SERIALIZATION_FACTORY import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize -import net.corda.core.utilities.Try -import net.corda.core.utilities.debug -import net.corda.core.utilities.loggerFor -import net.corda.core.utilities.trace +import net.corda.core.utilities.* import net.corda.node.internal.InitiatedFlowFactory import net.corda.node.services.api.Checkpoint import net.corda.node.services.api.CheckpointStorage @@ -67,7 +64,7 @@ class StateMachineManagerImpl( inner class FiberScheduler : FiberExecutorScheduler("Same thread scheduler", executor) companion object { - private val logger = loggerFor() + private val logger = contextLogger() internal val sessionTopic = TopicSession("platform.session") init { diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt index 8266aaa0c0..adf1afd69d 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt @@ -16,7 +16,6 @@ import net.corda.core.node.services.NotaryService import net.corda.core.node.services.TimeWindowChecker import net.corda.core.node.services.UniquenessProvider import net.corda.core.schemas.PersistentStateRef -import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.transactions.FilteredTransaction @@ -41,7 +40,7 @@ class BFTNonValidatingNotaryService(override val services: ServiceHubInternal, cluster: BFTSMaRt.Cluster) : NotaryService() { companion object { val id = constructId(validating = false, bft = true) - private val log = loggerFor() + private val log = contextLogger() } private val client: BFTSMaRt.Client diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRt.kt b/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRt.kt index b1ea728694..67e49887ea 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRt.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRt.kt @@ -29,8 +29,8 @@ import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.transactions.FilteredTransaction import net.corda.core.transactions.SignedTransaction +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.debug -import net.corda.core.utilities.loggerFor import net.corda.node.services.api.ServiceHubInternal import net.corda.node.services.transactions.BFTSMaRt.Client import net.corda.node.services.transactions.BFTSMaRt.Replica @@ -77,7 +77,7 @@ object BFTSMaRt { class Client(config: BFTSMaRtConfig, private val clientId: Int, private val cluster: Cluster, private val notaryService: BFTNonValidatingNotaryService) : SingletonSerializeAsToken() { companion object { - private val log = loggerFor() + private val log = contextLogger() } /** A proxy for communicating with the BFT cluster */ @@ -181,7 +181,7 @@ object BFTSMaRt { protected val notaryIdentityKey: PublicKey, private val timeWindowChecker: TimeWindowChecker) : DefaultRecoverable() { companion object { - private val log = loggerFor() + private val log = contextLogger() } private val stateManagerOverride = run { diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRtConfig.kt b/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRtConfig.kt index 827c3356ae..1ba8515fee 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRtConfig.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/BFTSMaRtConfig.kt @@ -2,8 +2,8 @@ package net.corda.node.services.transactions import net.corda.core.internal.div import net.corda.core.utilities.NetworkHostAndPort +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.debug -import net.corda.core.utilities.loggerFor import java.io.FileWriter import java.io.PrintWriter import java.net.InetAddress @@ -19,7 +19,7 @@ import java.util.concurrent.TimeUnit.MILLISECONDS */ class BFTSMaRtConfig(private val replicaAddresses: List, debug: Boolean, val exposeRaces: Boolean) : PathManager(Files.createTempDirectory("bft-smart-config")) { companion object { - private val log = loggerFor() + private val log = contextLogger() internal val portIsClaimedFormat = "Port %s is claimed by another replica: %s" } diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/DistributedImmutableMap.kt b/node/src/main/kotlin/net/corda/node/services/transactions/DistributedImmutableMap.kt index 0b8b00983f..5a67023df9 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/DistributedImmutableMap.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/DistributedImmutableMap.kt @@ -4,7 +4,7 @@ import io.atomix.copycat.Command import io.atomix.copycat.Query import io.atomix.copycat.server.Commit import io.atomix.copycat.server.StateMachine -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.node.utilities.* import java.util.LinkedHashMap @@ -17,7 +17,7 @@ import java.util.LinkedHashMap */ class DistributedImmutableMap(val db: CordaPersistence, createMap: () -> AppendOnlyPersistentMap, E, EK>) : StateMachine() { companion object { - private val log = loggerFor>() + private val log = contextLogger() } object Commands { diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/OutOfProcessTransactionVerifierService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/OutOfProcessTransactionVerifierService.kt index 4819672f9f..fbd7db4f93 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/OutOfProcessTransactionVerifierService.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/OutOfProcessTransactionVerifierService.kt @@ -11,16 +11,17 @@ import net.corda.core.internal.concurrent.OpenFuture import net.corda.core.internal.concurrent.openFuture import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.transactions.LedgerTransaction -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.nodeapi.VerifierApi import org.apache.activemq.artemis.api.core.client.ClientConsumer import java.util.concurrent.ConcurrentHashMap -abstract class OutOfProcessTransactionVerifierService( - private val metrics: MetricRegistry +class OutOfProcessTransactionVerifierService( + private val metrics: MetricRegistry, + private val sendRequest: (Long, LedgerTransaction) -> Unit ) : SingletonSerializeAsToken(), TransactionVerifierService { companion object { - val log = loggerFor() + private val log = contextLogger() } private data class VerificationHandle( @@ -60,8 +61,6 @@ abstract class OutOfProcessTransactionVerifierService( } } - abstract fun sendRequest(nonce: Long, transaction: LedgerTransaction) - override fun verify(transaction: LedgerTransaction): CordaFuture<*> { log.info("Verifying ${transaction.id}") val future = openFuture() diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt b/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt index d3cfaaa877..dd0e527ba1 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt @@ -9,11 +9,8 @@ import net.corda.core.internal.ThreadBox import net.corda.core.node.services.UniquenessException import net.corda.core.node.services.UniquenessProvider import net.corda.core.schemas.PersistentStateRef -import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.SingletonSerializeAsToken -import net.corda.core.serialization.deserialize -import net.corda.core.serialization.serialize -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.node.utilities.AppendOnlyPersistentMap import net.corda.node.utilities.NODE_DATABASE_PREFIX import java.io.Serializable @@ -62,8 +59,7 @@ class PersistentUniquenessProvider : UniquenessProvider, SingletonSerializeAsTok private val mutex = ThreadBox(InnerState()) companion object { - private val log = loggerFor() - + private val log = contextLogger() fun createMap(): AppendOnlyPersistentMap = AppendOnlyPersistentMap( toPersistentEntityKey = { PersistentStateRef(it.txhash.toString(), it.index) }, diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt b/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt index 655d19d108..5b1e1743d1 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt @@ -26,7 +26,7 @@ import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.node.services.config.RaftConfig import net.corda.node.utilities.AppendOnlyPersistentMap import net.corda.node.utilities.CordaPersistence @@ -49,8 +49,7 @@ import javax.persistence.* @ThreadSafe class RaftUniquenessProvider(private val transportConfiguration: NodeSSLConfiguration, private val db: CordaPersistence, private val metrics: MetricRegistry, private val raftConfig: RaftConfig) : UniquenessProvider, SingletonSerializeAsToken() { companion object { - private val log = loggerFor() - + private val log = contextLogger() fun createMap(): AppendOnlyPersistentMap, RaftState, String> = AppendOnlyPersistentMap( toPersistentEntityKey = { it }, @@ -76,10 +75,10 @@ class RaftUniquenessProvider(private val transportConfiguration: NodeSSLConfigur var key: String = "", @Lob - @Column + @Column(name = "state_value") var value: ByteArray = ByteArray(0), - @Column + @Column(name = "state_index") var index: Long = 0 ) diff --git a/node/src/main/kotlin/net/corda/node/services/vault/HibernateQueryCriteriaParser.kt b/node/src/main/kotlin/net/corda/node/services/vault/HibernateQueryCriteriaParser.kt index 02af1f4b1b..042ff71c01 100644 --- a/node/src/main/kotlin/net/corda/node/services/vault/HibernateQueryCriteriaParser.kt +++ b/node/src/main/kotlin/net/corda/node/services/vault/HibernateQueryCriteriaParser.kt @@ -11,7 +11,7 @@ import net.corda.core.node.services.vault.QueryCriteria.CommonQueryCriteria import net.corda.core.schemas.PersistentState import net.corda.core.schemas.PersistentStateRef import net.corda.core.utilities.OpaqueBytes -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.trace import net.corda.node.services.persistence.NodeAttachmentService import org.hibernate.query.criteria.internal.expression.LiteralExpression @@ -107,7 +107,7 @@ class HibernateAttachmentQueryCriteriaParser(override val criteriaBuilder: Crite AbstractQueryCriteriaParser(), AttachmentsQueryCriteriaParser { private companion object { - val log = loggerFor() + private val log = contextLogger() } init { @@ -170,7 +170,7 @@ class HibernateQueryCriteriaParser(val contractStateType: Class, val vaultStates: Root) : AbstractQueryCriteriaParser(), IQueryCriteriaParser { private companion object { - val log = loggerFor() + private val log = contextLogger() } // incrementally build list of join predicates diff --git a/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt b/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt index 13023a6c68..c3c2e8e8b7 100644 --- a/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt +++ b/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt @@ -57,7 +57,7 @@ class NodeVaultService( hibernateConfig: HibernateConfiguration ) : SingletonSerializeAsToken(), VaultServiceInternal { private companion object { - val log = loggerFor() + private val log = contextLogger() } private class InnerState { diff --git a/node/src/main/kotlin/net/corda/node/services/vault/VaultSchema.kt b/node/src/main/kotlin/net/corda/node/services/vault/VaultSchema.kt index e54f2cba23..bd1912ac22 100644 --- a/node/src/main/kotlin/net/corda/node/services/vault/VaultSchema.kt +++ b/node/src/main/kotlin/net/corda/node/services/vault/VaultSchema.kt @@ -29,7 +29,8 @@ object VaultSchemaV1 : MappedSchema(schemaFamily = VaultSchema.javaClass, versio mappedTypes = listOf(VaultStates::class.java, VaultLinearStates::class.java, VaultFungibleStates::class.java, VaultTxnNote::class.java)) { @Entity @Table(name = "vault_states", - indexes = arrayOf(Index(name = "state_status_idx", columnList = "state_status"))) + indexes = arrayOf(Index(name = "state_status_idx", columnList = "state_status"), + Index(name = "lock_id_idx", columnList = "lock_id, state_status"))) class VaultStates( /** NOTE: serialized transaction state (including contract state) is now resolved from transaction store */ // TODO: create a distinct table to hold serialized state data (once DBTransactionStore is encrypted) diff --git a/node/src/main/kotlin/net/corda/node/services/vault/VaultSoftLockManager.kt b/node/src/main/kotlin/net/corda/node/services/vault/VaultSoftLockManager.kt index 8bf233589b..b57ac3a45c 100644 --- a/node/src/main/kotlin/net/corda/node/services/vault/VaultSoftLockManager.kt +++ b/node/src/main/kotlin/net/corda/node/services/vault/VaultSoftLockManager.kt @@ -4,17 +4,14 @@ import net.corda.core.contracts.FungibleAsset import net.corda.core.contracts.StateRef import net.corda.core.flows.FlowLogic import net.corda.core.node.services.VaultService -import net.corda.core.utilities.NonEmptySet -import net.corda.core.utilities.loggerFor -import net.corda.core.utilities.toNonEmptySet -import net.corda.core.utilities.trace +import net.corda.core.utilities.* import net.corda.node.services.statemachine.FlowStateMachineImpl import net.corda.node.services.statemachine.StateMachineManager import java.util.* class VaultSoftLockManager private constructor(private val vault: VaultService) { companion object { - private val log = loggerFor() + private val log = contextLogger() @JvmStatic fun install(vault: VaultService, smm: StateMachineManager) { val manager = VaultSoftLockManager(vault) diff --git a/node/src/main/kotlin/net/corda/node/shell/CordaAuthenticationPlugin.kt b/node/src/main/kotlin/net/corda/node/shell/CordaAuthenticationPlugin.kt new file mode 100644 index 0000000000..9854a9caf4 --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/shell/CordaAuthenticationPlugin.kt @@ -0,0 +1,35 @@ +package net.corda.node.shell + +import net.corda.core.context.Actor +import net.corda.core.context.InvocationContext +import net.corda.core.identity.CordaX500Name +import net.corda.core.messaging.CordaRPCOps +import net.corda.node.services.RPCUserService +import net.corda.node.services.messaging.RpcPermissions +import org.crsh.auth.AuthInfo +import org.crsh.auth.AuthenticationPlugin +import org.crsh.plugin.CRaSHPlugin + +class CordaAuthenticationPlugin(val rpcOps:CordaRPCOps, val userService:RPCUserService, val nodeLegalName:CordaX500Name) : CRaSHPlugin>(), AuthenticationPlugin { + + override fun getImplementation(): AuthenticationPlugin = this + + override fun getName(): String = "corda" + + override fun authenticate(username: String?, credential: String?): AuthInfo { + if (username == null || credential == null) { + return AuthInfo.UNSUCCESSFUL + } + + val user = userService.getUser(username) + + if (user != null && user.password == credential) { + val actor = Actor(Actor.Id(username), userService.id, nodeLegalName) + return CordaSSHAuthInfo(true, RPCOpsWithContext(rpcOps, InvocationContext.rpc(actor), RpcPermissions(user.permissions))) + } + + return AuthInfo.UNSUCCESSFUL; + } + + override fun getCredentialType(): Class = String::class.java +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/shell/CordaSSHAuthInfo.kt b/node/src/main/kotlin/net/corda/node/shell/CordaSSHAuthInfo.kt new file mode 100644 index 0000000000..04bda1a4bb --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/shell/CordaSSHAuthInfo.kt @@ -0,0 +1,9 @@ +package net.corda.node.shell + +import net.corda.core.messaging.CordaRPCOps +import net.corda.node.utilities.ANSIProgressRenderer +import org.crsh.auth.AuthInfo + +class CordaSSHAuthInfo(val successful: Boolean, val rpcOps: CordaRPCOps, val ansiProgressRenderer: ANSIProgressRenderer? = null) : AuthInfo { + override fun isSuccessful(): Boolean = successful +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/shell/InteractiveShell.kt b/node/src/main/kotlin/net/corda/node/shell/InteractiveShell.kt index 9a7bf0630e..d2ca683740 100644 --- a/node/src/main/kotlin/net/corda/node/shell/InteractiveShell.kt +++ b/node/src/main/kotlin/net/corda/node/shell/InteractiveShell.kt @@ -9,26 +9,32 @@ import com.fasterxml.jackson.dataformat.yaml.YAMLFactory import com.google.common.io.Closeables import net.corda.client.jackson.JacksonSupport import net.corda.client.jackson.StringToMethodCallParser +import net.corda.client.rpc.PermissionException import net.corda.core.CordaException import net.corda.core.concurrent.CordaFuture import net.corda.core.contracts.UniqueIdentifier import net.corda.core.flows.FlowLogic +import net.corda.core.identity.CordaX500Name import net.corda.core.internal.* -import net.corda.core.internal.concurrent.OpenFuture +import net.corda.core.internal.concurrent.doneFuture import net.corda.core.internal.concurrent.openFuture import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.DataFeed +import net.corda.core.messaging.FlowProgressHandle import net.corda.core.messaging.StateMachineUpdate import net.corda.core.utilities.getOrThrow +import net.corda.core.node.services.IdentityService import net.corda.core.utilities.loggerFor import net.corda.node.internal.Node import net.corda.node.internal.StartedNode +import net.corda.node.services.RPCUserService +import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.messaging.CURRENT_RPC_CONTEXT import net.corda.node.services.messaging.RpcAuthContext import net.corda.node.services.messaging.RpcPermissions -import net.corda.node.services.statemachine.FlowStateMachineImpl import net.corda.node.utilities.ANSIProgressRenderer import net.corda.node.utilities.CordaPersistence +import net.corda.node.utilities.StdoutANSIProgressRenderer import org.crsh.command.InvocationContext import org.crsh.console.jline.JLineProcessor import org.crsh.console.jline.TerminalFactory @@ -48,6 +54,7 @@ import org.crsh.util.Utils import org.crsh.vfs.FS import org.crsh.vfs.spi.file.FileMountFactory import org.crsh.vfs.spi.url.ClassPathMountFactory +import org.slf4j.LoggerFactory import rx.Observable import rx.Subscriber import java.io.* @@ -73,63 +80,59 @@ import kotlin.concurrent.thread // TODO: Make it notice new shell commands added after the node started. object InteractiveShell { - private val log = loggerFor() + private val log = LoggerFactory.getLogger(javaClass) private lateinit var node: StartedNode @VisibleForTesting internal lateinit var database: CordaPersistence + private lateinit var rpcOps:CordaRPCOps + private lateinit var userService:RPCUserService + private lateinit var identityService:IdentityService + private var shell:Shell? = null + private lateinit var nodeLegalName: CordaX500Name /** * Starts an interactive shell connected to the local terminal. This shell gives administrator access to the node * internals. */ - fun startShell(dir: Path, runLocalShell: Boolean, runSSHServer: Boolean, node: StartedNode) { - this.node = node - this.database = node.database - var runSSH = runSSHServer + fun startShell(configuration:NodeConfiguration, cordaRPCOps: CordaRPCOps, userService: RPCUserService, identityService: IdentityService, database:CordaPersistence) { + this.rpcOps = cordaRPCOps + this.userService = userService + this.identityService = identityService + this.nodeLegalName = configuration.myLegalName + this.database = database + val dir = configuration.baseDirectory + val runSshDeamon = configuration.sshd != null val config = Properties() - if (runSSH) { - // TODO: Finish and enable SSH access. - // This means bringing the CRaSH SSH plugin into the Corda tree and applying Marek's patches - // found in https://github.com/marekdapps/crash/commit/8a37ce1c7ef4d32ca18f6396a1a9d9841f7ff643 - // to that local copy, as CRaSH is no longer well maintained by the upstream and the SSH plugin - // that it comes with is based on a very old version of Apache SSHD which can't handle connections - // from newer SSH clients. It also means hooking things up to the authentication system. - Node.printBasicNodeInfo("SSH server access is not fully implemented, sorry.") - runSSH = false - } + if (runSshDeamon) { + val sshKeysDir = dir / "sshkey" + sshKeysDir.toFile().mkdirs() - if (runSSH) { // Enable SSH access. Note: these have to be strings, even though raw object assignments also work. - config["crash.ssh.keypath"] = (dir / "sshkey").toString() + config["crash.ssh.keypath"] = (sshKeysDir / "hostkey.pem").toString() config["crash.ssh.keygen"] = "true" - // config["crash.ssh.port"] = node.configuration.sshdAddress.port.toString() - config["crash.auth"] = "simple" - config["crash.auth.simple.username"] = "admin" - config["crash.auth.simple.password"] = "admin" + config["crash.ssh.port"] = configuration.sshd?.port.toString() + config["crash.auth"] = "corda" } ExternalResolver.INSTANCE.addCommand("run", "Runs a method from the CordaRPCOps interface on the node.", RunShellCommand::class.java) ExternalResolver.INSTANCE.addCommand("flow", "Commands to work with flows. Flows are how you can change the ledger.", FlowShellCommand::class.java) ExternalResolver.INSTANCE.addCommand("start", "An alias for 'flow start'", StartShellCommand::class.java) - val shell = ShellLifecycle(dir).start(config) + shell = ShellLifecycle(dir).start(config) - if (runSSH) { - // printBasicNodeInfo("SSH server listening on address", node.configuration.sshdAddress.toString()) + if (runSshDeamon) { + Node.printBasicNodeInfo("SSH server listening on port", configuration.sshd!!.port.toString()) } + } - // Possibly bring up a local shell in the launching terminal window, unless it's disabled. - if (!runLocalShell) - return - // TODO: Automatically set up the JDBC sub-command with a connection to the database. + fun runLocalShell(node:StartedNode) { val terminal = TerminalFactory.create() val consoleReader = ConsoleReader("Corda", FileInputStream(FileDescriptor.`in`), System.out, terminal) val jlineProcessor = JLineProcessor(terminal.isAnsiSupported, shell, consoleReader, System.out) InterruptHandler { jlineProcessor.interrupt() }.install() thread(name = "Command line shell processor", isDaemon = true) { // Give whoever has local shell access administrator access to the node. - // TODO remove this after Shell switches to RPC - val context = RpcAuthContext(net.corda.core.context.InvocationContext.shell(), RpcPermissions.NONE) + val context = RpcAuthContext(net.corda.core.context.InvocationContext.shell(), RpcPermissions.ALL) CURRENT_RPC_CONTEXT.set(context) Emoji.renderIfSupported { jlineProcessor.run() @@ -168,27 +171,25 @@ object InteractiveShell { // Don't use the Java language plugin (we may not have tools.jar available at runtime), this // will cause any commands using JIT Java compilation to be suppressed. In CRaSH upstream that // is only the 'jmx' command. - return super.getPlugins().filterNot { it is JavaLanguage } + return super.getPlugins().filterNot { it is JavaLanguage } + CordaAuthenticationPlugin(rpcOps, userService, nodeLegalName) } } val attributes = mapOf( - "node" to node.internals, - "services" to node.services, - "ops" to node.rpcOps, + "ops" to rpcOps, "mapper" to yamlInputMapper ) val context = PluginContext(discovery, attributes, commandsFS, confFS, classLoader) context.refresh() this.config = config start(context) - return context.getPlugin(ShellFactory::class.java).create(null) + return context.getPlugin(ShellFactory::class.java).create(null, CordaSSHAuthInfo(false, RPCOpsWithContext(rpcOps, net.corda.core.context.InvocationContext.shell(), RpcPermissions.ALL), StdoutANSIProgressRenderer)) } } private val yamlInputMapper: ObjectMapper by lazy { // Return a standard Corda Jackson object mapper, configured to use YAML by default and with extra // serializers. - JacksonSupport.createInMemoryMapper(node.services.identityService, YAMLFactory(), true).apply { + JacksonSupport.createInMemoryMapper(identityService, YAMLFactory(), true).apply { val rpcModule = SimpleModule() rpcModule.addDeserializer(InputStream::class.java, InputStreamDeserializer) rpcModule.addDeserializer(UniqueIdentifier::class.java, UniqueIdentifierDeserializer) @@ -217,42 +218,41 @@ object InteractiveShell { /** * Called from the 'flow' shell command. Takes a name fragment and finds a matching flow, or prints out * the list of options if the request is ambiguous. Then parses [inputData] as constructor arguments using - * the [runFlowFromString] method and starts the requested flow using the [ANSIProgressRenderer] to draw - * the progress tracker. Ctrl-C can be used to cancel. + * the [runFlowFromString] method and starts the requested flow. Ctrl-C can be used to cancel. */ @JvmStatic - fun runFlowByNameFragment(nameFragment: String, inputData: String, output: RenderPrintWriter) { - val matches = node.services.rpcFlows.filter { nameFragment in it.name } + fun runFlowByNameFragment(nameFragment: String, inputData: String, output: RenderPrintWriter, rpcOps: CordaRPCOps, ansiProgressRenderer: ANSIProgressRenderer) { + val matches = rpcOps.registeredFlows().filter { nameFragment in it } if (matches.isEmpty()) { output.println("No matching flow found, run 'flow list' to see your options.", Color.red) return } else if (matches.size > 1) { - output.println("Ambigous name provided, please be more specific. Your options are:") + output.println("Ambiguous name provided, please be more specific. Your options are:") matches.forEachIndexed { i, s -> output.println("${i + 1}. $s", Color.yellow) } return } - val clazz: Class> = uncheckedCast(matches.single()) + val clazz: Class> = uncheckedCast(Class.forName(matches.single())) try { - // TODO Flow invocation should use startFlowDynamic. - val context = net.corda.core.context.InvocationContext.shell() - val fsm = runFlowFromString({ node.services.startFlow(it, context).getOrThrow() }, inputData, clazz) // Show the progress tracker on the console until the flow completes or is interrupted with a // Ctrl-C keypress. + val stateObservable = runFlowFromString({ clazz,args -> rpcOps.startTrackedFlowDynamic (clazz, *args) }, inputData, clazz) + val latch = CountDownLatch(1) - ANSIProgressRenderer.onDone = { latch.countDown() } - ANSIProgressRenderer.progressTracker = (fsm as FlowStateMachineImpl).logic.progressTracker + ansiProgressRenderer.render(stateObservable, { latch.countDown() }) try { // Wait for the flow to end and the progress tracker to notice. By the time the latch is released // the tracker is done with the screen. latch.await() } catch (e: InterruptedException) { - ANSIProgressRenderer.progressTracker = null // TODO: When the flow framework allows us to kill flows mid-flight, do so here. } + } catch (e: NoApplicableConstructor) { output.println("No matching constructor found:", Color.red) e.errors.forEach { output.println("- $it", Color.red) } + } catch (e:PermissionException) { + output.println(e.message ?: "Access denied", Color.red) } finally { InputStreamDeserializer.closeAll() } @@ -273,10 +273,10 @@ object InteractiveShell { * @throws NoApplicableConstructor if no constructor could be found for the given set of types. */ @Throws(NoApplicableConstructor::class) - fun runFlowFromString(invoke: (FlowLogic<*>) -> FlowStateMachine<*>, + fun runFlowFromString(invoke: (Class>, Array) -> FlowProgressHandle, inputData: String, - clazz: Class>, - om: ObjectMapper = yamlInputMapper): FlowStateMachine<*> { + clazz: Class>, + om: ObjectMapper = yamlInputMapper): FlowProgressHandle { // For each constructor, attempt to parse the input data as a method call. Use the first that succeeds, // and keep track of the reasons we failed so we can print them out if no constructors are usable. val parser = StringToMethodCallParser(clazz, om) @@ -303,7 +303,7 @@ object InteractiveShell { errors.add("A flow must override the progress tracker in order to be run from the shell") continue } - return invoke(flow) + return invoke(clazz, args) } catch (e: StringToMethodCallParser.UnparseableCallException.MissingParameter) { errors.add("${getPrototype()}: missing parameter ${e.paramName}") } catch (e: StringToMethodCallParser.UnparseableCallException.TooManyParameters) { @@ -321,8 +321,8 @@ object InteractiveShell { // TODO Filtering on error/success when we will have some sort of flow auditing, for now it doesn't make much sense. @JvmStatic - fun runStateMachinesView(out: RenderPrintWriter): Any? { - val proxy = node.rpcOps + fun runStateMachinesView(out: RenderPrintWriter, rpcOps: CordaRPCOps): Any? { + val proxy = rpcOps val (stateMachines, stateMachineUpdates) = proxy.stateMachinesFeed() val currentStateMachines = stateMachines.map { StateMachineUpdate.Added(it) } val subscriber = FlowWatchPrintingSubscriber(out) @@ -395,7 +395,7 @@ object InteractiveShell { return result } - private fun printAndFollowRPCResponse(response: Any?, toStream: PrintWriter): CordaFuture? { + private fun printAndFollowRPCResponse(response: Any?, toStream: PrintWriter): CordaFuture { val printerFun = yamlMapper::writeValueAsString toStream.println(printerFun(response)) toStream.flush() @@ -422,28 +422,31 @@ object InteractiveShell { override fun onNext(t: Any?) { count++ toStream.println("Observation $count: " + printerFun(t)) - toStream.flush() } @Synchronized override fun onError(e: Throwable) { toStream.println("Observable completed with an error") - e.printStackTrace() + e.printStackTrace(toStream) future.setException(e) } } - private fun maybeFollow(response: Any?, printerFun: (Any?) -> String, toStream: PrintWriter): OpenFuture? { + private fun maybeFollow(response: Any?, printerFun: (Any?) -> String, toStream: PrintWriter): CordaFuture { // Match on a couple of common patterns for "important" observables. It's tough to do this in a generic // way because observables can be embedded anywhere in the object graph, and can emit other arbitrary // object graphs that contain yet more observables. So we just look for top level responses that follow // the standard "track" pattern, and print them until the user presses Ctrl-C - if (response == null) return null + if (response == null) return doneFuture(Unit) val observable: Observable<*> = when (response) { is Observable<*> -> response - is DataFeed<*, *> -> response.updates - else -> return null + is DataFeed<*, *> -> { + toStream.println("Snapshot") + toStream.println(response.snapshot) + response.updates + } + else -> return doneFuture(Unit) } val subscriber = PrintingSubscriber(printerFun, toStream) diff --git a/node/src/main/kotlin/net/corda/node/shell/InteractiveShellCommand.kt b/node/src/main/kotlin/net/corda/node/shell/InteractiveShellCommand.kt index 1c352c9bd4..9278fffb95 100644 --- a/node/src/main/kotlin/net/corda/node/shell/InteractiveShellCommand.kt +++ b/node/src/main/kotlin/net/corda/node/shell/InteractiveShellCommand.kt @@ -4,12 +4,14 @@ import com.fasterxml.jackson.databind.ObjectMapper import net.corda.core.messaging.CordaRPCOps import net.corda.node.services.api.ServiceHubInternal import org.crsh.command.BaseCommand +import org.crsh.shell.impl.command.CRaSHSession /** * Simply extends CRaSH BaseCommand to add easy access to the RPC ops class. */ open class InteractiveShellCommand : BaseCommand() { - fun ops() = context.attributes["ops"] as CordaRPCOps + fun ops() = ((context.session as CRaSHSession).authInfo as CordaSSHAuthInfo).rpcOps + fun ansiProgressRenderer() = ((context.session as CRaSHSession).authInfo as CordaSSHAuthInfo).ansiProgressRenderer fun services() = context.attributes["services"] as ServiceHubInternal fun objectMapper() = context.attributes["mapper"] as ObjectMapper } diff --git a/node/src/main/kotlin/net/corda/node/shell/RPCOpsWithContext.kt b/node/src/main/kotlin/net/corda/node/shell/RPCOpsWithContext.kt new file mode 100644 index 0000000000..adcb8f30b7 --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/shell/RPCOpsWithContext.kt @@ -0,0 +1,205 @@ +package net.corda.node.shell + +import net.corda.core.concurrent.CordaFuture +import net.corda.core.context.InvocationContext +import net.corda.core.contracts.ContractState +import net.corda.core.crypto.SecureHash +import net.corda.core.flows.FlowLogic +import net.corda.core.identity.AbstractParty +import net.corda.core.identity.CordaX500Name +import net.corda.core.identity.Party +import net.corda.core.messaging.* +import net.corda.core.node.NodeInfo +import net.corda.core.node.services.AttachmentId +import net.corda.core.node.services.NetworkMapCache +import net.corda.core.node.services.Vault +import net.corda.core.node.services.vault.* +import net.corda.core.transactions.SignedTransaction +import net.corda.core.utilities.getOrThrow +import net.corda.node.services.messaging.CURRENT_RPC_CONTEXT +import net.corda.node.services.messaging.RpcAuthContext +import net.corda.node.services.messaging.RpcPermissions +import java.io.InputStream +import java.security.PublicKey +import java.time.Instant +import java.util.concurrent.CompletableFuture +import java.util.concurrent.Future + +class RPCOpsWithContext(val cordaRPCOps: CordaRPCOps, val invocationContext:InvocationContext, val rpcPermissions: RpcPermissions) : CordaRPCOps { + + + class RPCContextRunner(val invocationContext:InvocationContext, val permissions:RpcPermissions, val block:() -> T) : Thread() { + private var result: CompletableFuture = CompletableFuture() + override fun run() { + CURRENT_RPC_CONTEXT.set(RpcAuthContext(invocationContext, permissions)) + try { + result.complete(block()) + } catch (e:Throwable) { + result.completeExceptionally(e) + } + CURRENT_RPC_CONTEXT.remove() + } + + fun get(): Future { + start() + join() + return result + } + } + + override fun uploadAttachmentWithMetadata(jar: InputStream, uploader: String, filename: String): SecureHash { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.uploadAttachmentWithMetadata(jar, uploader, filename) }.get().getOrThrow() + } + + override fun queryAttachments(query: AttachmentQueryCriteria, sorting: AttachmentSort?): List { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.queryAttachments(query, sorting) }.get().getOrThrow() + } + + override fun vaultTrackByWithSorting(contractStateType: Class, criteria: QueryCriteria, sorting: Sort): DataFeed, Vault.Update> { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultTrackByWithSorting(contractStateType, criteria, sorting) }.get().getOrThrow() + } + + override fun vaultTrackByWithPagingSpec(contractStateType: Class, criteria: QueryCriteria, paging: PageSpecification): DataFeed, Vault.Update> { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultTrackByWithPagingSpec(contractStateType, criteria, paging) }.get().getOrThrow() + } + + override fun vaultTrackByCriteria(contractStateType: Class, criteria: QueryCriteria): DataFeed, Vault.Update> { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultTrackByCriteria(contractStateType, criteria) }.get().getOrThrow() + } + + override fun vaultTrack(contractStateType: Class): DataFeed, Vault.Update> { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultTrack(contractStateType) }.get().getOrThrow() + } + + override fun vaultQueryByWithSorting(contractStateType: Class, criteria: QueryCriteria, sorting: Sort): Vault.Page { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultQueryByWithSorting(contractStateType, criteria, sorting) }.get().getOrThrow() + } + + override fun vaultQueryByWithPagingSpec(contractStateType: Class, criteria: QueryCriteria, paging: PageSpecification): Vault.Page { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultQueryByWithPagingSpec(contractStateType, criteria, paging) }.get().getOrThrow() + } + + override fun vaultQueryByCriteria(criteria: QueryCriteria, contractStateType: Class): Vault.Page { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultQueryByCriteria(criteria, contractStateType) }.get().getOrThrow() + } + + override fun vaultQuery(contractStateType: Class): Vault.Page { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultQuery(contractStateType) }.get().getOrThrow() + } + + override fun stateMachinesSnapshot(): List { + return RPCContextRunner(invocationContext, rpcPermissions, cordaRPCOps::stateMachinesSnapshot).get().getOrThrow() + } + + override fun stateMachinesFeed(): DataFeed, StateMachineUpdate> { + return RPCContextRunner(invocationContext, rpcPermissions, cordaRPCOps::stateMachinesFeed).get().getOrThrow() + } + + override fun vaultQueryBy(criteria: QueryCriteria, paging: PageSpecification, sorting: Sort, contractStateType: Class): Vault.Page { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultQueryBy(criteria, paging, sorting, contractStateType) }.get().getOrThrow() + } + + override fun vaultTrackBy(criteria: QueryCriteria, paging: PageSpecification, sorting: Sort, contractStateType: Class): DataFeed, Vault.Update> { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.vaultTrackBy(criteria, paging, sorting, contractStateType) }.get().getOrThrow() + } + + override fun internalVerifiedTransactionsSnapshot(): List { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.internalVerifiedTransactionsSnapshot() }.get().getOrThrow() + } + + override fun internalVerifiedTransactionsFeed(): DataFeed, SignedTransaction> { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.internalVerifiedTransactionsFeed() }.get().getOrThrow() + } + + override fun stateMachineRecordedTransactionMappingSnapshot(): List { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.stateMachineRecordedTransactionMappingSnapshot() }.get().getOrThrow() + } + + override fun stateMachineRecordedTransactionMappingFeed(): DataFeed, StateMachineTransactionMapping> { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.stateMachineRecordedTransactionMappingFeed() }.get().getOrThrow() + } + + override fun networkMapSnapshot(): List { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.networkMapSnapshot() }.get().getOrThrow() + } + + override fun networkMapFeed(): DataFeed, NetworkMapCache.MapChange> { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.networkMapFeed() }.get().getOrThrow() + } + + override fun startFlowDynamic(logicType: Class>, vararg args: Any?): FlowHandle { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.startFlowDynamic(logicType, *args) }.get().getOrThrow() + } + + override fun startTrackedFlowDynamic(logicType: Class>, vararg args: Any?): FlowProgressHandle { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.startTrackedFlowDynamic(logicType, *args) }.get().getOrThrow() + } + + override fun nodeInfo(): NodeInfo { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.nodeInfo() }.get().getOrThrow() + } + + override fun notaryIdentities(): List { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.notaryIdentities() }.get().getOrThrow() + } + + override fun addVaultTransactionNote(txnId: SecureHash, txnNote: String) { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.addVaultTransactionNote(txnId, txnNote) }.get().getOrThrow() + } + + override fun getVaultTransactionNotes(txnId: SecureHash): Iterable { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.getVaultTransactionNotes(txnId) }.get().getOrThrow() + } + + override fun attachmentExists(id: SecureHash): Boolean { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.attachmentExists(id) }.get().getOrThrow() + } + + override fun openAttachment(id: SecureHash): InputStream { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.openAttachment(id) }.get().getOrThrow() + } + + override fun uploadAttachment(jar: InputStream): SecureHash { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.uploadAttachment(jar) }.get().getOrThrow() + } + + override fun currentNodeTime(): Instant { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.currentNodeTime() }.get().getOrThrow() + } + + override fun waitUntilNetworkReady(): CordaFuture { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.waitUntilNetworkReady() }.get().getOrThrow() + } + + override fun wellKnownPartyFromAnonymous(party: AbstractParty): Party? { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.wellKnownPartyFromAnonymous(party) }.get().getOrThrow() + } + + override fun partyFromKey(key: PublicKey): Party? { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.partyFromKey(key) }.get().getOrThrow() + } + + override fun wellKnownPartyFromX500Name(x500Name: CordaX500Name): Party? { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.wellKnownPartyFromX500Name(x500Name) }.get().getOrThrow() + } + + override fun notaryPartyFromX500Name(x500Name: CordaX500Name): Party? { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.notaryPartyFromX500Name(x500Name) }.get().getOrThrow() + } + + override fun partiesFromName(query: String, exactMatch: Boolean): Set { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.partiesFromName(query, exactMatch) }.get().getOrThrow() + } + + override fun registeredFlows(): List { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.registeredFlows() }.get().getOrThrow() + } + + override fun nodeInfoFromParty(party: AbstractParty): NodeInfo? { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.nodeInfoFromParty(party) }.get().getOrThrow() + } + + override fun clearNetworkMapCache() { + return RPCContextRunner(invocationContext, rpcPermissions) { cordaRPCOps.clearNetworkMapCache() }.get().getOrThrow() + } +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/utilities/ANSIProgressRenderer.kt b/node/src/main/kotlin/net/corda/node/utilities/ANSIProgressRenderer.kt index 8339d0ae22..303f6e91c0 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/ANSIProgressRenderer.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/ANSIProgressRenderer.kt @@ -1,138 +1,113 @@ package net.corda.node.utilities import net.corda.core.internal.Emoji -import net.corda.core.utilities.ProgressTracker -import net.corda.node.utilities.ANSIProgressRenderer.progressTracker +import net.corda.core.messaging.FlowProgressHandle import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.core.LogEvent import org.apache.logging.log4j.core.LoggerContext import org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender import org.apache.logging.log4j.core.appender.ConsoleAppender import org.apache.logging.log4j.core.appender.OutputStreamManager +import org.crsh.text.RenderPrintWriter import org.fusesource.jansi.Ansi import org.fusesource.jansi.AnsiConsole import org.fusesource.jansi.AnsiOutputStream import rx.Subscription -/** - * Knows how to render a [ProgressTracker] to the terminal using coloured, emoji-fied output. Useful when writing small - * command line tools, demos, tests etc. Just set the [progressTracker] field and it will go ahead and start drawing - * if the terminal supports it. Otherwise it just prints out the name of the step whenever it changes. - * - * When a progress tracker is on the screen, it takes over the bottom part and reconfigures logging so that, assuming - * 1 log event == 1 line, the progress tracker is always glued to the bottom and logging scrolls above it. - * - * TODO: More thread safety - */ -object ANSIProgressRenderer { +abstract class ANSIProgressRenderer { + + private var subscriptionIndex: Subscription? = null + private var subscriptionTree: Subscription? = null + + protected var usingANSI = false + protected var checkEmoji = false + + protected var treeIndex: Int = 0 + protected var tree: List> = listOf() + private var installedYet = false - private var subscription: Subscription? = null - private var usingANSI = false - - var progressTracker: ProgressTracker? = null - set(value) { - subscription?.unsubscribe() - - field = value - if (!installedYet) { - setup() - } - - // Reset the state when a new tracker is wired up. - if (value != null) { - prevMessagePrinted = null - prevLinesDrawn = 0 - draw(true) - subscription = value.changes.subscribe({ draw(true) }, { done(it) }, { done(null) }) - } - } - - var onDone: () -> Unit = {} - - private fun done(error: Throwable?) { - if (error == null) progressTracker = null - draw(true, error) - onDone() - } - - private fun setup() { - AnsiConsole.systemInstall() - - // This line looks weird as hell because the magic code to decide if we really have a TTY or not isn't - // actually exposed anywhere as a function (weak sauce). So we have to rely on our knowledge of jansi - // implementation details. - usingANSI = AnsiConsole.wrapOutputStream(System.out) !is AnsiOutputStream - - if (usingANSI) { - // This super ugly code hacks into log4j and swaps out its console appender for our own. It's a bit simpler - // than doing things the official way with a dedicated plugin, etc, as it avoids mucking around with all - // the config XML and lifecycle goop. - val manager = LogManager.getContext(false) as LoggerContext - val consoleAppender = manager.configuration.appenders.values.filterIsInstance().single { it.name == "Console-Appender" } - val scrollingAppender = object : AbstractOutputStreamAppender( - consoleAppender.name, consoleAppender.layout, consoleAppender.filter, - consoleAppender.ignoreExceptions(), true, consoleAppender.manager) { - override fun append(event: LogEvent) { - // We lock on the renderer to avoid threads that are logging to the screen simultaneously messing - // things up. Of course this slows stuff down a bit, but only whilst this little utility is in use. - // Eventually it will be replaced with a real GUI and we can delete all this. - synchronized(ANSIProgressRenderer) { - if (progressTracker != null) { - val ansi = Ansi.ansi() - repeat(prevLinesDrawn) { ansi.eraseLine().cursorUp(1).eraseLine() } - System.out.print(ansi) - System.out.flush() - } - - super.append(event) - - if (progressTracker != null) - draw(false) - } - } - } - scrollingAppender.start() - manager.configuration.appenders[consoleAppender.name] = scrollingAppender - val loggerConfigs = manager.configuration.loggers.values - for (config in loggerConfigs) { - val appenderRefs = config.appenderRefs - val consoleAppenders = config.appenders.filter { it.value is ConsoleAppender }.keys - consoleAppenders.forEach { config.removeAppender(it) } - appenderRefs.forEach { config.addAppender(manager.configuration.appenders[it.ref], it.level, it.filter) } - } - manager.updateLoggers() - } - - installedYet = true - } + private var onDone: () -> Unit = {} // prevMessagePrinted is just for non-ANSI mode. private var prevMessagePrinted: String? = null // prevLinesDraw is just for ANSI mode. - private var prevLinesDrawn = 0 + protected var prevLinesDrawn = 0 - @Synchronized private fun draw(moveUp: Boolean, error: Throwable? = null) { + private fun done(error: Throwable?) { + if (error == null) _render(null) + draw(true, error) + onDone() + } + + fun render(flowProgressHandle: FlowProgressHandle<*>, onDone: () -> Unit = {}) { + this.onDone = onDone + _render(flowProgressHandle) + } + + protected abstract fun printLine(line:String) + + protected abstract fun printAnsi(ansi:Ansi) + + protected abstract fun setup() + + private fun _render(flowProgressHandle: FlowProgressHandle<*>?) { + subscriptionIndex?.unsubscribe() + subscriptionTree?.unsubscribe() + treeIndex = 0 + tree = listOf() + + if (!installedYet) { + setup() + installedYet = true + } + + prevMessagePrinted = null + prevLinesDrawn = 0 + draw(true) + + + flowProgressHandle?.apply { + stepsTreeIndexFeed?.apply { + treeIndex = snapshot + subscriptionIndex = updates.subscribe({ + treeIndex = it + draw(true) + }, { done(it) }, { done(null) }) + } + stepsTreeFeed?.apply { + tree = snapshot + subscriptionTree = updates.subscribe({ + tree = it + draw(true) + }, { done(it) }, { done(null) }) + } + } + } + + + + @Synchronized protected fun draw(moveUp: Boolean, error: Throwable? = null) { if (!usingANSI) { - val currentMessage = progressTracker?.currentStepRecursive?.label + val currentMessage = tree.getOrNull(treeIndex)?.second if (currentMessage != null && currentMessage != prevMessagePrinted) { - println(currentMessage) + printLine(currentMessage) prevMessagePrinted = currentMessage } return } - Emoji.renderIfSupported { + fun printingBody() { // Handle the case where the number of steps in a progress tracker is changed during execution. - val ansi = Ansi.ansi() + val ansi = Ansi() if (prevLinesDrawn > 0 && moveUp) ansi.cursorUp(prevLinesDrawn) // Put a blank line between any logging and us. ansi.eraseLine() ansi.newline() - val pt = progressTracker ?: return - var newLinesDrawn = 1 + pt.renderLevel(ansi, 0, error != null) + if (tree.isEmpty()) return + var newLinesDrawn = 1 + renderLevel(ansi, error != null) if (error != null) { ansi.a("${Emoji.skullAndCrossbones} ${error.message}") @@ -152,46 +127,137 @@ object ANSIProgressRenderer { } prevLinesDrawn = newLinesDrawn - // Need to force a flush here in order to ensure stderr/stdout sync up properly. - System.out.print(ansi) - System.out.flush() + printAnsi(ansi) } + if (checkEmoji) { + Emoji.renderIfSupported(::printingBody) + } else { + printingBody() + } } // Returns number of lines rendered. - private fun ProgressTracker.renderLevel(ansi: Ansi, indent: Int, error: Boolean): Int { + private fun renderLevel(ansi: Ansi, error: Boolean): Int { with(ansi) { var lines = 0 - for ((index, step) in steps.withIndex()) { - // Don't bother rendering these special steps in some cases. - if (step == ProgressTracker.UNSTARTED) continue - if (indent > 0 && step == ProgressTracker.DONE) continue + for ((index, step) in tree.withIndex()) { val marker = when { - index < stepIndex -> "${Emoji.greenTick} " - index == stepIndex && step == ProgressTracker.DONE -> "${Emoji.greenTick} " - index == stepIndex -> "${Emoji.rightArrow} " + index < treeIndex -> "${Emoji.greenTick} " + treeIndex == tree.lastIndex -> "${Emoji.greenTick} " + index == treeIndex -> "${Emoji.rightArrow} " error -> "${Emoji.noEntry} " else -> " " // Not reached yet. } - a(" ".repeat(indent)) + a(" ".repeat(step.first)) a(marker) - val active = index == stepIndex && step != ProgressTracker.DONE + val active = index == treeIndex if (active) bold() - a(step.label) + a(step.second) if (active) boldOff() eraseLine(Ansi.Erase.FORWARD) newline() lines++ - - val child = getChildProgressTracker(step) - if (child != null) - lines += child.renderLevel(ansi, indent + 1, error) } return lines } } + + +} + +class CRaSHNSIProgressRenderer(val renderPrintWriter:RenderPrintWriter) : ANSIProgressRenderer() { + + override fun printLine(line: String) { + renderPrintWriter.println(line) + } + + override fun printAnsi(ansi: Ansi) { + renderPrintWriter.print(ansi) + renderPrintWriter.flush() + } + + override fun setup() { + //we assume SSH always use ansi + usingANSI = true + } + + +} + +/** + * Knows how to render a [FlowProgressHandle] to the terminal using coloured, emoji-fied output. Useful when writing small + * command line tools, demos, tests etc. Just call [draw] method and it will go ahead and start drawing + * if the terminal supports it. Otherwise it just prints out the name of the step whenever it changes. + * + * When a progress tracker is on the screen, it takes over the bottom part and reconfigures logging so that, assuming + * 1 log event == 1 line, the progress tracker is always glued to the bottom and logging scrolls above it. + * + * TODO: More thread safety + */ +object StdoutANSIProgressRenderer : ANSIProgressRenderer() { + + override fun setup() { + AnsiConsole.systemInstall() + + checkEmoji = true + + // This line looks weird as hell because the magic code to decide if we really have a TTY or not isn't + // actually exposed anywhere as a function (weak sauce). So we have to rely on our knowledge of jansi + // implementation details. + usingANSI = AnsiConsole.wrapOutputStream(System.out) !is AnsiOutputStream + + if (usingANSI) { + // This super ugly code hacks into log4j and swaps out its console appender for our own. It's a bit simpler + // than doing things the official way with a dedicated plugin, etc, as it avoids mucking around with all + // the config XML and lifecycle goop. + val manager = LogManager.getContext(false) as LoggerContext + val consoleAppender = manager.configuration.appenders.values.filterIsInstance().single { it.name == "Console-Appender" } + val scrollingAppender = object : AbstractOutputStreamAppender( + consoleAppender.name, consoleAppender.layout, consoleAppender.filter, + consoleAppender.ignoreExceptions(), true, consoleAppender.manager) { + override fun append(event: LogEvent) { + // We lock on the renderer to avoid threads that are logging to the screen simultaneously messing + // things up. Of course this slows stuff down a bit, but only whilst this little utility is in use. + // Eventually it will be replaced with a real GUI and we can delete all this. + synchronized(StdoutANSIProgressRenderer) { + if (tree.isNotEmpty()) { + val ansi = Ansi.ansi() + repeat(prevLinesDrawn) { ansi.eraseLine().cursorUp(1).eraseLine() } + System.out.print(ansi) + System.out.flush() + } + + super.append(event) + + if (tree.isNotEmpty()) + draw(false) + } + } + } + scrollingAppender.start() + manager.configuration.appenders[consoleAppender.name] = scrollingAppender + val loggerConfigs = manager.configuration.loggers.values + for (config in loggerConfigs) { + val appenderRefs = config.appenderRefs + val consoleAppenders = config.appenders.filter { it.value is ConsoleAppender }.keys + consoleAppenders.forEach { config.removeAppender(it) } + appenderRefs.forEach { config.addAppender(manager.configuration.appenders[it.ref], it.level, it.filter) } + } + manager.updateLoggers() + } + } + + override fun printLine(line:String) { + System.out.println(line) + } + + override fun printAnsi(ansi: Ansi) { + // Need to force a flush here in order to ensure stderr/stdout sync up properly. + System.out.print(ansi) + System.out.flush() + } } diff --git a/node/src/main/kotlin/net/corda/node/utilities/AffinityExecutor.kt b/node/src/main/kotlin/net/corda/node/utilities/AffinityExecutor.kt index 7a188044d1..89c7e68716 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/AffinityExecutor.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/AffinityExecutor.kt @@ -2,7 +2,6 @@ package net.corda.node.utilities import com.google.common.util.concurrent.SettableFuture import com.google.common.util.concurrent.Uninterruptibles -import net.corda.core.utilities.loggerFor import java.util.* import java.util.concurrent.CompletableFuture import java.util.concurrent.Executor @@ -55,10 +54,6 @@ interface AffinityExecutor : Executor { */ open class ServiceAffinityExecutor(threadName: String, numThreads: Int) : AffinityExecutor, ScheduledThreadPoolExecutor(numThreads) { - companion object { - val logger = loggerFor() - } - private val threads = Collections.synchronizedSet(HashSet()) init { diff --git a/node/src/main/kotlin/net/corda/node/utilities/AppendOnlyPersistentMap.kt b/node/src/main/kotlin/net/corda/node/utilities/AppendOnlyPersistentMap.kt index a90f77d171..90f2d018bb 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/AppendOnlyPersistentMap.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/AppendOnlyPersistentMap.kt @@ -1,6 +1,6 @@ package net.corda.node.utilities -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import java.util.* @@ -18,7 +18,7 @@ class AppendOnlyPersistentMap( ) { //TODO determine cacheBound based on entity class later or with node config allowing tuning, or using some heuristic based on heap size private companion object { - val log = loggerFor>() + private val log = contextLogger() } private val cache = NonInvalidatingCache>( diff --git a/node/src/main/kotlin/net/corda/node/utilities/PersistentMap.kt b/node/src/main/kotlin/net/corda/node/utilities/PersistentMap.kt index 11ace024ef..b0dbd2208b 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/PersistentMap.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/PersistentMap.kt @@ -4,7 +4,7 @@ package net.corda.node.utilities import com.google.common.cache.RemovalCause import com.google.common.cache.RemovalListener import com.google.common.cache.RemovalNotification -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import java.util.* @@ -19,7 +19,7 @@ class PersistentMap( ) : MutableMap, AbstractMap() { private companion object { - val log = loggerFor>() + private val log = contextLogger() } private val cache = NonInvalidatingUnboundCache( diff --git a/node/src/main/kotlin/net/corda/node/utilities/ServiceIdentityGenerator.kt b/node/src/main/kotlin/net/corda/node/utilities/ServiceIdentityGenerator.kt index 872892161b..051f68c63c 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/ServiceIdentityGenerator.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/ServiceIdentityGenerator.kt @@ -7,13 +7,12 @@ import net.corda.core.identity.Party import net.corda.core.internal.cert import net.corda.core.internal.createDirectories import net.corda.core.internal.div -import net.corda.core.utilities.loggerFor import net.corda.core.utilities.trace +import org.slf4j.LoggerFactory import java.nio.file.Path object ServiceIdentityGenerator { - private val log = loggerFor() - + private val log = LoggerFactory.getLogger(javaClass) /** * Generates signing key pairs and a common distributed service identity for a set of nodes. * The key pairs and the group identity get serialized to disk in the corresponding node directories. diff --git a/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt b/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt index 02e2f80dc4..5ddff0c2d5 100644 --- a/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt +++ b/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt @@ -241,6 +241,18 @@ class CordaRPCOpsImplTest { } } + @Test + fun `can't upload the same attachment`() { + withPermissions(invokeRpc(CordaRPCOps::uploadAttachment), invokeRpc(CordaRPCOps::attachmentExists)) { + assertThatExceptionOfType(java.nio.file.FileAlreadyExistsException::class.java).isThrownBy { + val inputJar1 = Thread.currentThread().contextClassLoader.getResourceAsStream(testJar) + val inputJar2 = Thread.currentThread().contextClassLoader.getResourceAsStream(testJar) + val secureHash1 = rpc.uploadAttachment(inputJar1) + val secureHash2 = rpc.uploadAttachment(inputJar2) + } + } + } + @Test fun `can download an uploaded attachment`() { withPermissions(invokeRpc(CordaRPCOps::uploadAttachment), invokeRpc(CordaRPCOps::openAttachment)) { diff --git a/node/src/test/kotlin/net/corda/node/InteractiveShellTest.kt b/node/src/test/kotlin/net/corda/node/InteractiveShellTest.kt index 622fc9667c..5f55195c22 100644 --- a/node/src/test/kotlin/net/corda/node/InteractiveShellTest.kt +++ b/node/src/test/kotlin/net/corda/node/InteractiveShellTest.kt @@ -2,11 +2,17 @@ package net.corda.node import com.fasterxml.jackson.dataformat.yaml.YAMLFactory import net.corda.client.jackson.JacksonSupport +import net.corda.core.concurrent.CordaFuture import net.corda.core.contracts.Amount import net.corda.core.crypto.SecureHash import net.corda.core.flows.FlowLogic +import net.corda.core.flows.StateMachineRunId import net.corda.core.identity.Party import net.corda.core.internal.FlowStateMachine +import net.corda.core.internal.concurrent.openFuture +import net.corda.core.internal.objectOrNewInstance +import net.corda.core.messaging.FlowProgressHandle +import net.corda.core.messaging.FlowProgressHandleImpl import net.corda.core.utilities.ProgressTracker import net.corda.node.services.identity.InMemoryIdentityService import net.corda.node.shell.InteractiveShell @@ -20,6 +26,7 @@ import net.corda.testing.rigorousMock import org.junit.After import org.junit.Before import org.junit.Test +import rx.Observable import java.util.* import kotlin.test.assertEquals @@ -36,8 +43,8 @@ class InteractiveShellTest { @Suppress("UNUSED") class FlowA(val a: String) : FlowLogic() { - constructor(b: Int) : this(b.toString()) - constructor(b: Int, c: String) : this(b.toString() + c) + constructor(b: Int?) : this(b.toString()) + constructor(b: Int?, c: String) : this(b.toString() + c) constructor(amount: Amount) : this(amount.toString()) constructor(pair: Pair, SecureHash.SHA256>) : this(pair.toString()) constructor(party: Party) : this(party.name.toString()) @@ -50,9 +57,16 @@ class InteractiveShellTest { private val om = JacksonSupport.createInMemoryMapper(ids, YAMLFactory()) private fun check(input: String, expected: String) { - var output: DummyFSM? = null - InteractiveShell.runFlowFromString({ DummyFSM(it as FlowA).apply { output = this } }, input, FlowA::class.java, om) - assertEquals(expected, output!!.logic.a, input) + var output: String? = null + InteractiveShell.runFlowFromString( { clazz, args -> + + val instance = clazz.getConstructor(*args.map { it!!::class.java }.toTypedArray()).newInstance(*args) as FlowA + output = instance.a + val future = openFuture() + future.set("ABC") + FlowProgressHandleImpl(StateMachineRunId.createRandom(), future, Observable.just("Some string")) + }, input, FlowA::class.java, om) + assertEquals(expected, output!!, input) } @Test diff --git a/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt b/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt index ebc9e59b74..225ea6952f 100644 --- a/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt @@ -1,11 +1,9 @@ package net.corda.node.services.messaging -import com.codahale.metrics.MetricRegistry import net.corda.core.concurrent.CordaFuture import net.corda.core.crypto.generateKeyPair import net.corda.core.internal.concurrent.doneFuture import net.corda.core.internal.concurrent.openFuture -import net.corda.core.messaging.RPCOps import net.corda.core.utilities.NetworkHostAndPort import net.corda.node.services.RPCUserService import net.corda.node.services.RPCUserServiceImpl @@ -58,16 +56,10 @@ class ArtemisMessagingTests { private lateinit var database: CordaPersistence private lateinit var userService: RPCUserService private lateinit var networkMapRegistrationFuture: CordaFuture - - private var messagingClient: NodeMessagingClient? = null + private var messagingClient: P2PMessagingClient? = null private var messagingServer: ArtemisMessagingServer? = null private lateinit var networkMapCache: NetworkMapCacheImpl - - private val rpcOps = object : RPCOps { - override val protocolVersion: Int get() = throw UnsupportedOperationException() - } - @Before fun setUp() { val baseDirectory = temporaryFolder.root.toPath() @@ -186,10 +178,10 @@ class ArtemisMessagingTests { } private fun startNodeMessagingClient() { - messagingClient!!.start(rpcOps, userService) + messagingClient!!.start() } - private fun createAndStartClientAndServer(receivedMessages: LinkedBlockingQueue): NodeMessagingClient { + private fun createAndStartClientAndServer(receivedMessages: LinkedBlockingQueue): P2PMessagingClient { createMessagingServer().start() val messagingClient = createMessagingClient() @@ -198,20 +190,19 @@ class ArtemisMessagingTests { receivedMessages.add(message) } // Run after the handlers are added, otherwise (some of) the messages get delivered and discarded / dead-lettered. - thread { messagingClient.run(messagingServer!!.serverControl) } + thread { messagingClient.run() } return messagingClient } - private fun createMessagingClient(server: NetworkHostAndPort = NetworkHostAndPort("localhost", serverPort)): NodeMessagingClient { + private fun createMessagingClient(server: NetworkHostAndPort = NetworkHostAndPort("localhost", serverPort)): P2PMessagingClient { return database.transaction { - NodeMessagingClient( + P2PMessagingClient( config, MOCK_VERSION_INFO, server, identity.public, ServiceAffinityExecutor("ArtemisMessagingTests", 1), - database, - MetricRegistry()).apply { + database).apply { config.configureWithDevSSLCertificate() messagingClient = this } diff --git a/samples/attachment-demo/build.gradle b/samples/attachment-demo/build.gradle index b977d45ffb..93f10fefb1 100644 --- a/samples/attachment-demo/build.gradle +++ b/samples/attachment-demo/build.gradle @@ -35,7 +35,10 @@ dependencies { } task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { - ext.rpcUsers = [['username': "demo", 'password': "demo", 'permissions': ["StartFlow.net.corda.attachmentdemo.AttachmentDemoFlow"]]] + ext.rpcUsers = [['username': "demo", 'password': "demo", 'permissions': ["StartFlow.net.corda.attachmentdemo.AttachmentDemoFlow", + "InvokeRpc.attachmentExists", + "InvokeRpc.uploadAttachment", + "InvokeRpc.internalVerifiedTransactionsFeed"]]] directory "./build/nodes" node { diff --git a/samples/bank-of-corda-demo/build.gradle b/samples/bank-of-corda-demo/build.gradle index d95d203fc4..4d291f727a 100644 --- a/samples/bank-of-corda-demo/build.gradle +++ b/samples/bank-of-corda-demo/build.gradle @@ -71,7 +71,10 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { "StartFlow.net.corda.finance.flows.CashConfigDataFlow", "StartFlow.net.corda.finance.flows.CashExitFlow", "StartFlow.net.corda.finance.flows.CashIssueAndPaymentFlow", - "StartFlow.net.corda.finance.flows.CashConfigDataFlow"]] + "StartFlow.net.corda.finance.flows.CashConfigDataFlow", + "InvokeRpc.waitUntilNetworkReady", + "InvokeRpc.wellKnownPartyFromX500Name", + "InvokeRpc.notaryIdentities"]] ] } node { @@ -84,7 +87,10 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { ['username' : "bigCorpUser", 'password' : "test", 'permissions': ["StartFlow.net.corda.finance.flows.CashPaymentFlow", - "StartFlow.net.corda.finance.flows.CashConfigDataFlow"]] + "StartFlow.net.corda.finance.flows.CashConfigDataFlow", + "InvokeRpc.waitUntilNetworkReady", + "InvokeRpc.wellKnownPartyFromX500Name", + "InvokeRpc.notaryIdentities"]] ] } } diff --git a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt index f836788d71..00ada75a26 100644 --- a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt +++ b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt @@ -9,6 +9,7 @@ import net.corda.finance.flows.CashConfigDataFlow import net.corda.finance.flows.CashExitFlow import net.corda.finance.flows.CashIssueAndPaymentFlow import net.corda.finance.flows.CashPaymentFlow +import net.corda.node.services.Permissions.Companion.all import net.corda.node.services.Permissions.Companion.startFlow import net.corda.nodeapi.User import net.corda.testing.BOC @@ -65,7 +66,8 @@ private class BankOfCordaDriver { startFlow(), startFlow(), startFlow(), - startFlow() + startFlow(), + all() )) val bankOfCorda = startNode( providedName = BOC.name, @@ -73,7 +75,8 @@ private class BankOfCordaDriver { val bigCorpUser = User(BIGCORP_USERNAME, "test", permissions = setOf( startFlow(), - startFlow())) + startFlow(), + all())) startNode(providedName = BIGCORP_LEGAL_NAME, rpcUsers = listOf(bigCorpUser)) startWebserver(bankOfCorda.get()) } diff --git a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/api/BankOfCordaWebApi.kt b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/api/BankOfCordaWebApi.kt index 8180350c74..30614d0cdf 100644 --- a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/api/BankOfCordaWebApi.kt +++ b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/api/BankOfCordaWebApi.kt @@ -5,8 +5,8 @@ import net.corda.core.identity.CordaX500Name import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.startFlow import net.corda.core.utilities.OpaqueBytes +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.getOrThrow -import net.corda.core.utilities.loggerFor import net.corda.finance.flows.CashIssueAndPaymentFlow import java.time.LocalDateTime import java.util.* @@ -23,7 +23,7 @@ class BankOfCordaWebApi(val rpc: CordaRPCOps) { val notaryName: CordaX500Name) private companion object { - val logger = loggerFor() + private val logger = contextLogger() } @GET diff --git a/samples/irs-demo/cordapp/build.gradle b/samples/irs-demo/cordapp/build.gradle index 74dfa92b45..66e7aa6953 100644 --- a/samples/irs-demo/cordapp/build.gradle +++ b/samples/irs-demo/cordapp/build.gradle @@ -51,7 +51,9 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { 'permissions' : [ "StartFlow.net.corda.irs.flows.AutoOfferFlow\$Requester", "StartFlow.net.corda.irs.flows.UpdateBusinessDayFlow\$Broadcast", - "StartFlow.net.corda.irs.api.NodeInterestRates\$UploadFixesFlow" + "StartFlow.net.corda.irs.api.NodeInterestRates\$UploadFixesFlow", + "InvokeRpc.vaultQueryBy", + "InvokeRpc.networkMapSnapshot" ]] ] diff --git a/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt b/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt index 0efd48d8d8..4d2d1a12ae 100644 --- a/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt +++ b/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt @@ -14,8 +14,8 @@ import net.corda.core.contracts.UniqueIdentifier import net.corda.core.identity.Party import net.corda.core.messaging.vaultTrackBy import net.corda.core.toFuture +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.getOrThrow -import net.corda.core.utilities.loggerFor import net.corda.core.utilities.seconds import net.corda.finance.plugin.registerFinanceJSONMappers import net.corda.irs.contract.InterestRateSwap @@ -35,7 +35,7 @@ import java.time.LocalDate class IRSDemoTest : IntegrationTest() { companion object { - val log = loggerFor() + private val log = contextLogger() } private val rpcUsers = listOf(User("user", "password", setOf("ALL"))) diff --git a/samples/irs-demo/src/integration-test/kotlin/net/corda/test/spring/SpringDriver.kt b/samples/irs-demo/src/integration-test/kotlin/net/corda/test/spring/SpringDriver.kt index 30a5b28513..7a5cf1bdb0 100644 --- a/samples/irs-demo/src/integration-test/kotlin/net/corda/test/spring/SpringDriver.kt +++ b/samples/irs-demo/src/integration-test/kotlin/net/corda/test/spring/SpringDriver.kt @@ -2,7 +2,7 @@ package net.corda.test.spring import net.corda.core.concurrent.CordaFuture import net.corda.core.internal.concurrent.map -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.testing.driver.* import net.corda.testing.internal.ProcessUtilities import net.corda.testing.node.NotarySpec @@ -61,7 +61,7 @@ fun springDriver( data class SpringBootDriverDSL(private val driverDSL: DriverDSL) : DriverDSLInternalInterface by driverDSL, SpringDriverInternalDSLInterface { companion object { - val log = loggerFor() + private val log = contextLogger() } override fun startSpringBootWebapp(clazz: Class<*>, handle: NodeHandle, checkUrl: String): CordaFuture { diff --git a/samples/irs-demo/web/src/main/kotlin/net/corda/irs/web/api/InterestSwapRestAPI.kt b/samples/irs-demo/web/src/main/kotlin/net/corda/irs/web/api/InterestSwapRestAPI.kt index 1c1a564d19..f242edace4 100644 --- a/samples/irs-demo/web/src/main/kotlin/net/corda/irs/web/api/InterestSwapRestAPI.kt +++ b/samples/irs-demo/web/src/main/kotlin/net/corda/irs/web/api/InterestSwapRestAPI.kt @@ -4,8 +4,8 @@ import net.corda.core.contracts.filterStatesOfType import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.startFlow import net.corda.core.messaging.vaultQueryBy +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.getOrThrow -import net.corda.core.utilities.loggerFor import net.corda.irs.contract.InterestRateSwap import org.springframework.beans.factory.annotation.Autowired import org.springframework.web.bind.annotation.* @@ -33,8 +33,9 @@ import net.corda.irs.flows.AutoOfferFlow @RestController @RequestMapping("/api/irs") class InterestRateSwapAPI { - - private val logger = loggerFor() + companion object { + private val logger = contextLogger() + } private fun generateDealLink(deal: InterestRateSwap.State) = "/api/irs/deals/" + deal.common.tradeID diff --git a/samples/irs-demo/web/src/test/kotlin/net/corda/irs/web/api/InterestRatesSwapDemoAPI.kt b/samples/irs-demo/web/src/test/kotlin/net/corda/irs/web/api/InterestRatesSwapDemoAPI.kt index 6a54266e28..c66ab8ec2d 100644 --- a/samples/irs-demo/web/src/test/kotlin/net/corda/irs/web/api/InterestRatesSwapDemoAPI.kt +++ b/samples/irs-demo/web/src/test/kotlin/net/corda/irs/web/api/InterestRatesSwapDemoAPI.kt @@ -2,8 +2,8 @@ package net.corda.irs.web.api import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.startFlow +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.getOrThrow -import net.corda.core.utilities.loggerFor import net.corda.irs.api.NodeInterestRates import net.corda.irs.flows.UpdateBusinessDayFlow import org.springframework.beans.factory.annotation.Autowired @@ -23,7 +23,7 @@ import java.time.ZoneId @RequestMapping("/api/irs") class InterestRatesSwapDemoAPI { companion object { - private val logger = loggerFor() + private val logger = contextLogger() } @Autowired diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/analytics/AnalyticsEngine.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/analytics/AnalyticsEngine.kt index a0bea3ea32..009049f754 100644 --- a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/analytics/AnalyticsEngine.kt +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/analytics/AnalyticsEngine.kt @@ -16,7 +16,7 @@ import com.opengamma.strata.market.param.CurrencyParameterSensitivities import com.opengamma.strata.pricer.rate.ImmutableRatesProvider import com.opengamma.strata.pricer.swap.DiscountingSwapProductPricer import com.opengamma.strata.product.swap.ResolvedSwapTrade -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.vega.flows.toCordaCompatible import java.time.LocalDate @@ -63,7 +63,7 @@ abstract class AnalyticsEngine { class OGSIMMAnalyticsEngine : AnalyticsEngine() { private companion object { - val log = loggerFor() + private val log = contextLogger() } override fun curveGroup(): CurveGroupDefinition { diff --git a/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/TraderDemo.kt b/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/TraderDemo.kt index 43c82f5565..e519481e3a 100644 --- a/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/TraderDemo.kt +++ b/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/TraderDemo.kt @@ -3,11 +3,10 @@ package net.corda.traderdemo import joptsimple.OptionParser import net.corda.client.rpc.CordaRPCClient import net.corda.core.utilities.NetworkHostAndPort -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.finance.DOLLARS import net.corda.testing.DUMMY_BANK_A import net.corda.testing.DUMMY_BANK_B -import org.slf4j.Logger import kotlin.system.exitProcess /** @@ -24,7 +23,7 @@ private class TraderDemo { } companion object { - val logger: Logger = loggerFor() + private val logger = contextLogger() val buyerName = DUMMY_BANK_A.name val sellerName = DUMMY_BANK_B.name val sellerRpcPort = 10009 diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt index a19cdad47b..2b358051ab 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -71,10 +71,22 @@ private val DEFAULT_POLL_INTERVAL = 500.millis private const val DEFAULT_WARN_COUNT = 120 +/** + * A sub-set of permissions that grant most of the essential operations used in the unit/integration tests as well as + * in demo application like NodeExplorer. + */ private val DRIVER_REQUIRED_PERMISSIONS = setOf( invokeRpc(CordaRPCOps::nodeInfo), invokeRpc(CordaRPCOps::networkMapFeed), - invokeRpc(CordaRPCOps::networkMapSnapshot) + invokeRpc(CordaRPCOps::networkMapSnapshot), + invokeRpc(CordaRPCOps::notaryIdentities), + invokeRpc(CordaRPCOps::stateMachinesFeed), + invokeRpc(CordaRPCOps::stateMachineRecordedTransactionMappingFeed), + invokeRpc(CordaRPCOps::nodeInfoFromParty), + invokeRpc(CordaRPCOps::internalVerifiedTransactionsFeed), + invokeRpc("vaultQueryBy"), + invokeRpc("vaultTrackBy"), + invokeRpc(CordaRPCOps::registeredFlows) ) /** @@ -146,7 +158,7 @@ interface DriverDSLExposedInterface : CordformContext { * Helper function for starting a [Node] with custom parameters from Java. * * @param parameters The default parameters for the driver. - * @return The value returned in the [dsl] closure. + * @return [NodeHandle] that will be available sometime in the future. */ fun startNode(parameters: NodeParameters): CordaFuture = startNode(defaultParameters = parameters) @@ -317,8 +329,8 @@ data class NodeParameters( * @param useTestClock If true the test clock will be used in Node. * @param startNodesInProcess Provides the default behaviour of whether new nodes should start inside this process or * not. Note that this may be overridden in [DriverDSLExposedInterface.startNode]. - * @param notarySpecs The notaries advertised in the [NetworkParameters] for this network. These nodes will be started - * automatically and will be available from [DriverDSLExposedInterface.notaryHandles]. Defaults to a simple validating notary. + * @param notarySpecs The notaries advertised for this network. These nodes will be started automatically and will be + * available from [DriverDSLExposedInterface.notaryHandles]. Defaults to a simple validating notary. * @param dsl The dsl itself. * @return The value returned in the [dsl] closure. */ diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/ShutdownManager.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/ShutdownManager.kt index 9ca4875c4c..0e8752aeb6 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/ShutdownManager.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/ShutdownManager.kt @@ -3,10 +3,7 @@ package net.corda.testing.driver import net.corda.core.concurrent.CordaFuture import net.corda.core.internal.ThreadBox import net.corda.core.internal.concurrent.doneFuture -import net.corda.core.utilities.Try -import net.corda.core.utilities.getOrThrow -import net.corda.core.utilities.loggerFor -import net.corda.core.utilities.seconds +import net.corda.core.utilities.* import java.util.concurrent.ExecutorService import java.util.concurrent.Executors import java.util.concurrent.TimeoutException @@ -21,8 +18,7 @@ class ShutdownManager(private val executorService: ExecutorService) { private val state = ThreadBox(State()) companion object { - private val log = loggerFor() - + private val log = contextLogger() inline fun run(providedExecutorService: ExecutorService? = null, block: ShutdownManager.() -> A): A { val executorService = providedExecutorService ?: Executors.newScheduledThreadPool(1) val shutdownManager = ShutdownManager(executorService) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt index e1195084a5..11fc77758d 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt @@ -1,6 +1,5 @@ package net.corda.testing.node -import net.corda.core.concurrent.CordaFuture import net.corda.core.crypto.CompositeKey import net.corda.core.identity.CordaX500Name import net.corda.core.internal.ThreadBox @@ -10,7 +9,6 @@ import net.corda.core.messaging.MessageRecipients import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate -import net.corda.core.internal.concurrent.doneFuture import net.corda.core.internal.concurrent.openFuture import net.corda.core.node.services.PartyInfo import net.corda.core.serialization.CordaSerializable @@ -33,19 +31,6 @@ import javax.annotation.concurrent.ThreadSafe import kotlin.concurrent.schedule import kotlin.concurrent.thread -/** - * This class lets you start up a [MessagingService]. Its purpose is to stop you from getting access to the methods - * on the messaging service interface until you have successfully started up the system. One of these objects should - * be the only way to obtain a reference to a [MessagingService]. Startup may be a slow process: some implementations - * may let you cast the returned future to an object that lets you get status info. - * - * A specific implementation of the controller class will have extra features that let you customise it before starting - * it up. - */ -interface MessagingServiceBuilder { - fun start(): CordaFuture -} - /** * An in-memory network allows you to manufacture [InMemoryMessaging]s for a set of participants. Each * [InMemoryMessaging] maintains a queue of messages it has received, and a background thread that dispatches @@ -59,11 +44,11 @@ interface MessagingServiceBuilder { @ThreadSafe class InMemoryMessagingNetwork( val sendManuallyPumped: Boolean, - val servicePeerAllocationStrategy: ServicePeerAllocationStrategy = InMemoryMessagingNetwork.ServicePeerAllocationStrategy.Random(), + private val servicePeerAllocationStrategy: ServicePeerAllocationStrategy = InMemoryMessagingNetwork.ServicePeerAllocationStrategy.Random(), private val messagesInFlight: ReusableLatch = ReusableLatch() ) : SingletonSerializeAsToken() { companion object { - const val MESSAGES_LOG_NAME = "messages" + private const val MESSAGES_LOG_NAME = "messages" private val log = LoggerFactory.getLogger(MESSAGES_LOG_NAME) } @@ -103,38 +88,15 @@ class InMemoryMessagingNetwork( get() = _receivedMessages val endpoints: List @Synchronized get() = handleEndpointMap.values.toList() - fun endpoint(peer: PeerHandle): InMemoryMessaging? = handleEndpointMap.get(peer) - - /** - * Creates a node and returns the new object that identifies its location on the network to senders, and the - * [InMemoryMessaging] that the recipient/in-memory node uses to receive messages and send messages itself. - * - * If [manuallyPumped] is set to true, then you are expected to call the [InMemoryMessaging.pump] method on the [InMemoryMessaging] - * in order to cause the delivery of a single message, which will occur on the thread of the caller. If set to false - * then this class will set up a background thread to deliver messages asynchronously, if the handler specifies no - * executor. - * - * @param persistenceTx a lambda to wrap message handling in a transaction if necessary. Defaults to a no-op. - */ - @Synchronized - fun createNode(manuallyPumped: Boolean, - executor: AffinityExecutor, - notaryService: PartyAndCertificate?, - database: CordaPersistence): Pair> { - check(counter >= 0) { "In memory network stopped: please recreate." } - val builder = createNodeWithID(manuallyPumped, counter, executor, notaryService, database = database) as Builder - counter++ - val id = builder.id - return Pair(id, builder) - } - /** * Creates a node at the given address: useful if you want to recreate a node to simulate a restart. * - * @param manuallyPumped see [createNode]. + * @param manuallyPumped if set to true, then you are expected to call the [InMemoryMessaging.pump] method on the [InMemoryMessaging] + * in order to cause the delivery of a single message, which will occur on the thread of the caller. If set to false + * then this class will set up a background thread to deliver messages asynchronously, if the handler specifies no + * executor. * @param id the numeric ID to use, e.g. set to whatever ID the node used last time. * @param description text string that identifies this node for message logging (if is enabled) or null to autogenerate. - * @param persistenceTx a lambda to wrap message handling in a transaction if necessary. */ fun createNodeWithID( manuallyPumped: Boolean, @@ -143,12 +105,19 @@ class InMemoryMessagingNetwork( notaryService: PartyAndCertificate?, description: CordaX500Name = CordaX500Name(organisation = "In memory node $id", locality = "London", country = "UK"), database: CordaPersistence) - : MessagingServiceBuilder { + : InMemoryMessaging { val peerHandle = PeerHandle(id, description) peersMapping[peerHandle.description] = peerHandle // Assume that the same name - the same entity in MockNetwork. notaryService?.let { if (it.owningKey !is CompositeKey) peersMapping[it.name] = peerHandle } val serviceHandles = notaryService?.let { listOf(ServiceHandle(it.party)) } ?: emptyList() //TODO only notary can be distributed? - return Builder(manuallyPumped, peerHandle, serviceHandles, executor, database = database) + synchronized(this) { + val node = InMemoryMessaging(manuallyPumped, peerHandle, executor, database) + handleEndpointMap[peerHandle] = node + serviceHandles.forEach { + serviceToPeersMapping.getOrPut(it) { LinkedHashSet() }.add(peerHandle) + } + return node + } } interface LatencyCalculator { @@ -157,7 +126,7 @@ class InMemoryMessagingNetwork( /** This can be set to an object which can inject artificial latency between sender/recipient pairs. */ @Volatile - var latencyCalculator: LatencyCalculator? = null + private var latencyCalculator: LatencyCalculator? = null private val timer = Timer() @Synchronized @@ -197,25 +166,6 @@ class InMemoryMessagingNetwork( messageReceiveQueues.clear() } - inner class Builder( - val manuallyPumped: Boolean, - val id: PeerHandle, - val serviceHandles: List, - val executor: AffinityExecutor, - val database: CordaPersistence) : MessagingServiceBuilder { - override fun start(): CordaFuture { - synchronized(this@InMemoryMessagingNetwork) { - val node = InMemoryMessaging(manuallyPumped, id, executor, database) - handleEndpointMap[id] = node - serviceHandles.forEach { - serviceToPeersMapping.getOrPut(it) { LinkedHashSet() }.add(id) - Unit - } - return doneFuture(node) - } - } - } - @CordaSerializable data class PeerHandle(val id: Int, val description: CordaX500Name) : SingleMessageRecipient { override fun toString() = description.toString() @@ -240,7 +190,7 @@ class InMemoryMessagingNetwork( } class RoundRobin : ServicePeerAllocationStrategy() { - val previousPicks = HashMap() + private val previousPicks = HashMap() override fun pickNext(service: ServiceHandle, pickFrom: List): A { val nextIndex = previousPicks.compute(service) { _, previous -> (previous?.plus(1) ?: 0) % pickFrom.size @@ -392,7 +342,7 @@ class InMemoryMessagingNetwork( acknowledgementHandler?.invoke() } - override fun stop() { + fun stop() { if (backgroundThread != null) { backgroundThread.interrupt() backgroundThread.join() @@ -463,9 +413,7 @@ class InMemoryMessagingNetwork( private fun pumpReceiveInternal(block: Boolean): MessageTransfer? { val q = getQueueForPeerHandle(peerHandle) - val next = getNextQueue(q, block) ?: return null - val (transfer, deliverTo) = next - + val (transfer, deliverTo) = getNextQueue(q, block) ?: return null if (transfer.message.uniqueMessageId !in processedMessages) { executor.execute { database.transaction { diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt index 3f45d3dd4c..8ce500abea 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt @@ -12,6 +12,7 @@ import net.corda.core.identity.PartyAndCertificate import net.corda.core.internal.createDirectories import net.corda.core.internal.createDirectory import net.corda.core.internal.uncheckedCast +import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.MessageRecipients import net.corda.core.messaging.RPCOps import net.corda.core.messaging.SingleMessageRecipient @@ -20,8 +21,7 @@ import net.corda.core.node.services.IdentityService import net.corda.core.node.services.KeyManagementService import net.corda.core.serialization.SerializationWhitelist import net.corda.core.utilities.NetworkHostAndPort -import net.corda.core.utilities.getOrThrow -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.node.internal.AbstractNode import net.corda.node.internal.StartedNode import net.corda.node.internal.cordapp.CordappLoader @@ -44,8 +44,7 @@ import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties import net.corda.testing.setGlobalSerialization import net.corda.testing.testNodeConfiguration import org.apache.activemq.artemis.utils.ReusableLatch -import org.slf4j.Logger -import java.io.Closeable +import org.apache.sshd.common.util.security.SecurityUtils import java.math.BigInteger import java.nio.file.Path import java.security.KeyPair @@ -122,9 +121,17 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete private val defaultFactory: (MockNodeArgs) -> MockNode = defaultParameters.defaultFactory, initialiseSerialization: Boolean = defaultParameters.initialiseSerialization, private val notarySpecs: List = listOf(NotarySpec(DUMMY_NOTARY.name)), - private val cordappPackages: List = defaultParameters.cordappPackages) : Closeable { + private val cordappPackages: List = defaultParameters.cordappPackages) { /** Helper constructor for creating a [MockNetwork] with custom parameters from Java. */ constructor(parameters: MockNetworkParameters) : this(defaultParameters = parameters) + + init { + // Apache SSHD for whatever reason registers a SFTP FileSystemProvider - which gets loaded by JimFS. + // This SFTP support loads BouncyCastle, which we want to avoid. + // Please see https://issues.apache.org/jira/browse/SSHD-736 - it's easier then to create our own fork of SSHD + SecurityUtils.setAPrioriDisabledProvider("BC", true) + } + var nextNodeId = 0 private set private val filesystem = Jimfs.newFileSystem(unix()) @@ -214,13 +221,15 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete CordappLoader.createDefaultWithTestPackages(args.config, args.network.cordappPackages), args.network.busyLatch ) { + companion object { + private val staticLog = contextLogger() + } + val mockNet = args.network val id = args.id private val entropyRoot = args.entropyRoot var counter = entropyRoot - - override val log: Logger = loggerFor() - + override val log get() = staticLog override val serverThread: AffinityExecutor = if (mockNet.threadPerNode) { ServiceAffinityExecutor("Mock node $id thread", 1) @@ -256,8 +265,7 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete serverThread, myNotaryIdentity, myLegalName, - database - ).start().getOrThrow() + database).also { runOnStop += it::stop } } fun setMessagingServiceSpy(messagingServiceSpy: MessagingServiceSpy) { @@ -268,6 +276,10 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete return E2ETestKeyManagementService(identityService, keyPairs) } + override fun startShell(rpcOps: CordaRPCOps) { + //No mock shell + } + override fun startMessagingService(rpcOps: RPCOps) { // Nothing to do } @@ -410,6 +422,7 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete fun stopNodes() { nodes.forEach { it.started?.dispose() } serializationEnv.unset() + messagingNetwork.stop() } // Test method to block until all scheduled activity, active flows @@ -418,29 +431,18 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete busyLatch.await() } - override fun close() { - stopNodes() - } - data class NotarySpec(val name: CordaX500Name, val validating: Boolean = true) { constructor(name: CordaX500Name) : this(name, validating = true) } } -fun network(nodesCount: Int, action: MockNetwork.(List>) -> Unit) { - MockNetwork().use { mockNet -> - val nodes = (1..nodesCount).map { mockNet.createPartyNode() } - mockNet.action(nodes) - } -} - /** - * Extend this class in order to intercept and modify messages passing through the [MessagingService] when using the [InMemoryNetwork]. + * Extend this class in order to intercept and modify messages passing through the [MessagingService] when using the [InMemoryMessagingNetwork]. */ open class MessagingServiceSpy(val messagingService: MessagingService) : MessagingService by messagingService /** - * Attach a [MessagingServiceSpy] to the [MockNode] allowing interception and modification of messages. + * Attach a [MessagingServiceSpy] to the [MockNetwork.MockNode] allowing interception and modification of messages. */ fun StartedNode.setMessagingServiceSpy(messagingServiceSpy: MessagingServiceSpy) { internals.setMessagingServiceSpy(messagingServiceSpy) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt index 32aabfe022..7548ee9c36 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt @@ -13,7 +13,6 @@ import net.corda.core.identity.PartyAndCertificate import net.corda.core.messaging.DataFeed import net.corda.core.messaging.FlowHandle import net.corda.core.messaging.FlowProgressHandle -import net.corda.core.messaging.NodeState import net.corda.core.node.* import net.corda.core.node.services.* import net.corda.core.serialization.SerializeAsToken @@ -204,8 +203,6 @@ open class MockServices( val identity = getTestPartyAndCertificate(initialIdentityName, key.public) return NodeInfo(emptyList(), listOf(identity), 1, serial = 1L) } - override val myNodeStateObservable: Observable - get() = PublishSubject.create() override val transactionVerifierService: TransactionVerifierService get() = InMemoryTransactionVerifierService(2) val mockCordappProvider = MockCordappProvider(cordappLoader, attachments) override val cordappProvider: CordappProvider get() = mockCordappProvider diff --git a/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt b/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt index 34c5a9d8cc..b6656977b7 100644 --- a/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt +++ b/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt @@ -6,7 +6,7 @@ import net.corda.core.internal.copyTo import net.corda.core.internal.createDirectories import net.corda.core.internal.div import net.corda.core.utilities.NetworkHostAndPort -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import java.nio.file.Path import java.nio.file.Paths import java.time.Instant @@ -22,7 +22,7 @@ class NodeProcess( private val client: CordaRPCClient ) : AutoCloseable { private companion object { - val log = loggerFor() + private val log = contextLogger() } fun connect(): CordaRPCConnection { diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/http/HttpUtils.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/http/HttpUtils.kt index b07d55db86..327f85193b 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/http/HttpUtils.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/http/HttpUtils.kt @@ -1,11 +1,11 @@ package net.corda.testing.http import com.fasterxml.jackson.databind.ObjectMapper -import net.corda.core.utilities.loggerFor import okhttp3.MediaType import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.RequestBody +import org.slf4j.LoggerFactory import java.net.URL import java.util.concurrent.TimeUnit @@ -13,7 +13,7 @@ import java.util.concurrent.TimeUnit * A small set of utilities for making HttpCalls, aimed at demos and tests. */ object HttpUtils { - private val logger = loggerFor() + private val logger = LoggerFactory.getLogger(javaClass) private val client by lazy { OkHttpClient.Builder() .connectTimeout(5, TimeUnit.SECONDS) diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/node/MockAttachmentStorage.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/node/MockAttachmentStorage.kt index 0550e1722e..c12e202b33 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/node/MockAttachmentStorage.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/node/MockAttachmentStorage.kt @@ -11,21 +11,25 @@ import net.corda.core.node.services.vault.AttachmentSort import net.corda.core.serialization.SingletonSerializeAsToken import java.io.ByteArrayOutputStream import java.io.InputStream -import java.util.HashMap +import java.util.* import java.util.jar.JarInputStream class MockAttachmentStorage : AttachmentStorage, SingletonSerializeAsToken() { - - override fun importAttachment(jar: InputStream): AttachmentId { - // JIS makes read()/readBytes() return bytes of the current file, but we want to hash the entire container here. - require(jar !is JarInputStream) - - val bytes = run { + companion object { + fun getBytes(jar: InputStream) = run { val s = ByteArrayOutputStream() jar.copyTo(s) s.close() s.toByteArray() } + } + + override fun importAttachment(jar: InputStream): AttachmentId { + // JIS makes read()/readBytes() return bytes of the current file, but we want to hash the entire container here. + require(jar !is JarInputStream) + + val bytes = getBytes(jar) + val sha256 = bytes.sha256() if (!files.containsKey(sha256)) { files[sha256] = bytes @@ -49,4 +53,21 @@ class MockAttachmentStorage : AttachmentStorage, SingletonSerializeAsToken() { override fun queryAttachments(criteria: AttachmentQueryCriteria, sorting: AttachmentSort?): List { throw NotImplementedError("Querying for attachments not implemented") } + + override fun hasAttachment(attachmentId: AttachmentId) = files.containsKey(attachmentId) + + fun getAttachmentIdAndBytes(jar: InputStream): Pair { + val bytes = getBytes(jar) + return Pair(bytes.sha256(), bytes) + } + + override fun importOrGetAttachment(jar: InputStream): AttachmentId { + try { + return importAttachment(jar) + } + catch (faee: java.nio.file.FileAlreadyExistsException) { + return AttachmentId.parse(faee.message!!) + } + } + } diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/explorer/Explorer.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/explorer/Explorer.kt index 286ad1e4c6..3c4aaebe84 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/explorer/Explorer.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/explorer/Explorer.kt @@ -3,7 +3,7 @@ package net.corda.demobench.explorer import net.corda.core.internal.createDirectories import net.corda.core.internal.div import net.corda.core.internal.list -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.demobench.model.JVMConfig import net.corda.demobench.model.NodeConfig import net.corda.demobench.model.NodeConfigWrapper @@ -16,7 +16,7 @@ import java.util.concurrent.Executors class Explorer internal constructor(private val explorerController: ExplorerController) : AutoCloseable { private companion object { - val log = loggerFor() + private val log = contextLogger() } private val executor = Executors.newSingleThreadExecutor() diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/pty/R3Pty.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/pty/R3Pty.kt index cd82181367..fff015a4f2 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/pty/R3Pty.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/pty/R3Pty.kt @@ -5,7 +5,7 @@ import com.jediterm.terminal.ui.UIUtil import com.jediterm.terminal.ui.settings.SettingsProvider import com.pty4j.PtyProcess import net.corda.core.identity.CordaX500Name -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import java.awt.Dimension import java.io.IOException import java.nio.charset.StandardCharsets.UTF_8 @@ -14,7 +14,7 @@ import java.util.concurrent.TimeUnit.SECONDS class R3Pty(val name: CordaX500Name, settings: SettingsProvider, dimension: Dimension, val onExit: (Int) -> Unit) : AutoCloseable { private companion object { - val log = loggerFor() + private val log = contextLogger() } private val executor = Executors.newSingleThreadExecutor() diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/rpc/NodeRPC.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/rpc/NodeRPC.kt index a92dbd4a3d..c07dd6cef6 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/rpc/NodeRPC.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/rpc/NodeRPC.kt @@ -4,14 +4,14 @@ import net.corda.client.rpc.CordaRPCClient import net.corda.client.rpc.CordaRPCConnection import net.corda.core.messaging.CordaRPCOps import net.corda.core.utilities.NetworkHostAndPort -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.demobench.model.NodeConfigWrapper import java.util.* import java.util.concurrent.TimeUnit.SECONDS class NodeRPC(config: NodeConfigWrapper, start: (NodeConfigWrapper, CordaRPCOps) -> Unit, invoke: (CordaRPCOps) -> Unit) : AutoCloseable { private companion object { - val log = loggerFor() + private val log = contextLogger() val oneSecond = SECONDS.toMillis(1) } diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/web/DBViewer.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/web/DBViewer.kt index 01cd406252..94908d6a31 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/web/DBViewer.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/web/DBViewer.kt @@ -1,6 +1,6 @@ package net.corda.demobench.web -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import org.h2.Driver import org.h2.server.web.LocalWebServer import org.h2.tools.Server @@ -11,7 +11,7 @@ import kotlin.reflect.jvm.jvmName class DBViewer : AutoCloseable { private companion object { - val log = loggerFor() + private val log = contextLogger() } private val webServer: Server diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/web/WebServer.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/web/WebServer.kt index 5fe5f96fe1..f3bfefdd17 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/web/WebServer.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/web/WebServer.kt @@ -4,7 +4,7 @@ import com.google.common.util.concurrent.RateLimiter import net.corda.core.concurrent.CordaFuture import net.corda.core.internal.concurrent.openFuture import net.corda.core.internal.until -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.minutes import net.corda.demobench.model.NodeConfigWrapper import net.corda.demobench.readErrorLines @@ -18,7 +18,7 @@ import kotlin.concurrent.thread class WebServer internal constructor(private val webServerController: WebServerController) : AutoCloseable { private companion object { - val log = loggerFor() + private val log = contextLogger() } private val executor = Executors.newSingleThreadExecutor() diff --git a/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt b/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt index 1e3d0a2707..ee1495ebd6 100644 --- a/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt +++ b/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt @@ -124,7 +124,7 @@ class ExplorerSimulation(private val options: OptionSet) { fun FlowHandle.log(seq: Int, name: String) { val out = "[$seq] $name $id :" returnValue.thenMatch({ (stx) -> - Main.log.info("$out ${stx.id} ${(stx.tx.outputs.first().data as Cash.State).amount}") + Main.log.info("$out ${stx.id} ${(stx.tx.outputs.first().data as Cash.State).amount}") // XXX: Why Main's log? }, { Main.log.info("$out ${it.message}") }) diff --git a/tools/explorer/src/main/kotlin/net/corda/explorer/Main.kt b/tools/explorer/src/main/kotlin/net/corda/explorer/Main.kt index 5b7c2bd83f..0bacc9cb99 100644 --- a/tools/explorer/src/main/kotlin/net/corda/explorer/Main.kt +++ b/tools/explorer/src/main/kotlin/net/corda/explorer/Main.kt @@ -11,7 +11,7 @@ import jfxtras.resources.JFXtrasFontRoboto import joptsimple.OptionParser import net.corda.client.jfx.model.Models import net.corda.client.jfx.model.observableValue -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.explorer.model.CordaViewModel import net.corda.explorer.model.SettingsModel import net.corda.explorer.views.* @@ -30,7 +30,7 @@ class Main : App(MainView::class) { private val fullscreen by observableValue(SettingsModel::fullscreenProperty) companion object { - val log = loggerFor
() + internal val log = contextLogger() } override fun start(stage: Stage) { diff --git a/tools/loadtest/src/main/kotlin/net/corda/loadtest/ConnectionManager.kt b/tools/loadtest/src/main/kotlin/net/corda/loadtest/ConnectionManager.kt index fe3e4fc621..8e8cd14e0f 100644 --- a/tools/loadtest/src/main/kotlin/net/corda/loadtest/ConnectionManager.kt +++ b/tools/loadtest/src/main/kotlin/net/corda/loadtest/ConnectionManager.kt @@ -8,12 +8,12 @@ import com.jcraft.jsch.agentproxy.AgentProxy import com.jcraft.jsch.agentproxy.connector.SSHAgentConnector import com.jcraft.jsch.agentproxy.usocket.JNAUSocketFactory import net.corda.core.utilities.NetworkHostAndPort +import net.corda.core.utilities.loggerFor import net.corda.testing.driver.PortAllocation -import org.slf4j.LoggerFactory import java.util.* import kotlin.streams.toList -private val log = LoggerFactory.getLogger(ConnectionManager::class.java) +private val log = loggerFor() /** * Creates a new [JSch] instance with identities loaded from the running SSH agent. diff --git a/tools/loadtest/src/main/kotlin/net/corda/loadtest/Disruption.kt b/tools/loadtest/src/main/kotlin/net/corda/loadtest/Disruption.kt index b704f50acd..e70a1f6621 100644 --- a/tools/loadtest/src/main/kotlin/net/corda/loadtest/Disruption.kt +++ b/tools/loadtest/src/main/kotlin/net/corda/loadtest/Disruption.kt @@ -1,12 +1,13 @@ package net.corda.loadtest import net.corda.client.mock.* +import net.corda.core.utilities.loggerFor import org.slf4j.LoggerFactory import java.util.* import java.util.concurrent.Callable import java.util.concurrent.Executors -private val log = LoggerFactory.getLogger(Disruption::class.java) +private val log = loggerFor() /** * A [Disruption] puts strain on the passed in node in some way. Each disruption runs in its own thread in a tight loop diff --git a/tools/loadtest/src/main/kotlin/net/corda/loadtest/LoadTest.kt b/tools/loadtest/src/main/kotlin/net/corda/loadtest/LoadTest.kt index ed88325174..62fd304866 100644 --- a/tools/loadtest/src/main/kotlin/net/corda/loadtest/LoadTest.kt +++ b/tools/loadtest/src/main/kotlin/net/corda/loadtest/LoadTest.kt @@ -2,15 +2,15 @@ package net.corda.loadtest import com.google.common.util.concurrent.RateLimiter import net.corda.client.mock.Generator +import net.corda.core.utilities.loggerFor import net.corda.core.utilities.toBase58String import net.corda.testing.driver.PortAllocation -import org.slf4j.LoggerFactory import java.util.* import java.util.concurrent.Callable import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.Executors -private val log = LoggerFactory.getLogger(LoadTest::class.java) +private val log = loggerFor>() /** * @param T The type of generated object in the load test. This should describe the basic unit of execution, for example diff --git a/tools/loadtest/src/main/kotlin/net/corda/loadtest/NodeConnection.kt b/tools/loadtest/src/main/kotlin/net/corda/loadtest/NodeConnection.kt index 9406fe0dad..620cc92d99 100644 --- a/tools/loadtest/src/main/kotlin/net/corda/loadtest/NodeConnection.kt +++ b/tools/loadtest/src/main/kotlin/net/corda/loadtest/NodeConnection.kt @@ -11,7 +11,7 @@ import net.corda.core.internal.concurrent.fork import net.corda.core.messaging.CordaRPCOps import net.corda.core.node.NodeInfo import net.corda.core.utilities.NetworkHostAndPort -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.nodeapi.internal.addShutdownHook import java.io.ByteArrayOutputStream import java.io.Closeable @@ -27,7 +27,7 @@ import java.util.concurrent.ForkJoinPool */ class NodeConnection(val remoteNode: RemoteNode, private val jSchSession: Session, private val localTunnelAddress: NetworkHostAndPort) : Closeable { companion object { - val log = loggerFor() + private val log = contextLogger() } init { diff --git a/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/StabilityTest.kt b/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/StabilityTest.kt index 66379f6e16..e3460316cc 100644 --- a/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/StabilityTest.kt +++ b/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/StabilityTest.kt @@ -7,7 +7,6 @@ import net.corda.core.internal.concurrent.thenMatch import net.corda.core.messaging.startFlow import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.getOrThrow -import net.corda.core.utilities.loggerFor import net.corda.finance.USD import net.corda.finance.flows.CashExitFlow import net.corda.finance.flows.CashExitFlow.ExitRequest @@ -16,10 +15,10 @@ import net.corda.finance.flows.CashIssueAndPaymentFlow.IssueAndPaymentRequest import net.corda.finance.flows.CashPaymentFlow import net.corda.finance.flows.CashPaymentFlow.PaymentRequest import net.corda.loadtest.LoadTest - +import org.slf4j.LoggerFactory object StabilityTest { - private val log = loggerFor() + private val log = LoggerFactory.getLogger(javaClass) fun crossCashTest(replication: Int) = LoadTest( "Creating Cash transactions", generate = { _, _ -> diff --git a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt index cb1c5c600a..04e46e395d 100644 --- a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt +++ b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt @@ -14,7 +14,7 @@ import net.corda.core.internal.div import net.corda.core.serialization.internal.nodeSerializationEnv import net.corda.core.transactions.LedgerTransaction import net.corda.core.utilities.NetworkHostAndPort -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.node.services.config.configureDevKeyAndTrustStores import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER import net.corda.nodeapi.ArtemisTcpTransport @@ -149,7 +149,7 @@ data class VerifierDriverDSL( val verifierCount = AtomicInteger(0) companion object { - private val log = loggerFor() + private val log = contextLogger() fun createConfiguration(baseDirectory: Path, nodeHostAndPort: NetworkHostAndPort): Config { return ConfigFactory.parseMap( mapOf( diff --git a/verifier/src/main/kotlin/net/corda/verifier/Verifier.kt b/verifier/src/main/kotlin/net/corda/verifier/Verifier.kt index dea5bdcadf..7c04bf1ce9 100644 --- a/verifier/src/main/kotlin/net/corda/verifier/Verifier.kt +++ b/verifier/src/main/kotlin/net/corda/verifier/Verifier.kt @@ -7,10 +7,7 @@ import net.corda.core.internal.div import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.internal.SerializationEnvironmentImpl import net.corda.core.serialization.internal.nodeSerializationEnv -import net.corda.core.utilities.ByteSequence -import net.corda.core.utilities.NetworkHostAndPort -import net.corda.core.utilities.debug -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.* import net.corda.nodeapi.ArtemisTcpTransport.Companion.tcpTransport import net.corda.nodeapi.ConnectionDirection import net.corda.nodeapi.VerifierApi @@ -39,8 +36,7 @@ data class VerifierConfiguration( class Verifier { companion object { - private val log = loggerFor() - + private val log = contextLogger() fun loadConfiguration(baseDirectory: Path, configPath: Path): VerifierConfiguration { val defaultConfig = ConfigFactory.parseResources("verifier-reference.conf", ConfigParseOptions.defaults().setAllowMissing(false)) val customConfig = ConfigFactory.parseFile(configPath.toFile(), ConfigParseOptions.defaults().setAllowMissing(false)) diff --git a/webserver/src/main/kotlin/net/corda/webserver/WebArgsParser.kt b/webserver/src/main/kotlin/net/corda/webserver/WebArgsParser.kt index 718678bb73..868ee24d18 100644 --- a/webserver/src/main/kotlin/net/corda/webserver/WebArgsParser.kt +++ b/webserver/src/main/kotlin/net/corda/webserver/WebArgsParser.kt @@ -75,7 +75,7 @@ data class CmdLineOptions(val baseDirectory: Path, .withFallback(appConfig) .withFallback(defaultConfig) .resolve() - val log = loggerFor() + val log = loggerFor() // I guess this is lazy so it happens after logging init. log.info("Config:\n${finalConfig.root().render(ConfigRenderOptions.defaults())}") return finalConfig } diff --git a/webserver/src/main/kotlin/net/corda/webserver/internal/AllExceptionMapper.kt b/webserver/src/main/kotlin/net/corda/webserver/internal/AllExceptionMapper.kt index 3a8687fcf5..3530b74e56 100644 --- a/webserver/src/main/kotlin/net/corda/webserver/internal/AllExceptionMapper.kt +++ b/webserver/src/main/kotlin/net/corda/webserver/internal/AllExceptionMapper.kt @@ -9,7 +9,7 @@ import javax.ws.rs.ext.Provider @Provider class AllExceptionMapper : ExceptionMapper { companion object { - val logger = loggerFor() + private val logger = loggerFor() // XXX: Really? } override fun toResponse(exception: Exception?): Response { diff --git a/webserver/src/main/kotlin/net/corda/webserver/internal/NodeWebServer.kt b/webserver/src/main/kotlin/net/corda/webserver/internal/NodeWebServer.kt index 8d5bf05a03..46bdf5a7ef 100644 --- a/webserver/src/main/kotlin/net/corda/webserver/internal/NodeWebServer.kt +++ b/webserver/src/main/kotlin/net/corda/webserver/internal/NodeWebServer.kt @@ -4,7 +4,7 @@ import com.google.common.html.HtmlEscapers.htmlEscaper import net.corda.client.jackson.JacksonSupport import net.corda.client.rpc.CordaRPCClient import net.corda.core.messaging.CordaRPCOps -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.webserver.WebServerConfig import net.corda.webserver.converters.CordaConverterProvider import net.corda.webserver.services.WebServerPluginRegistry @@ -32,7 +32,7 @@ import javax.ws.rs.core.MediaType class NodeWebServer(val config: WebServerConfig) { private companion object { - val log = loggerFor() + private val log = contextLogger() const val retryDelay = 1000L // Milliseconds } diff --git a/webserver/src/main/kotlin/net/corda/webserver/servlets/AttachmentDownloadServlet.kt b/webserver/src/main/kotlin/net/corda/webserver/servlets/AttachmentDownloadServlet.kt index e23d420059..e76d6633d2 100644 --- a/webserver/src/main/kotlin/net/corda/webserver/servlets/AttachmentDownloadServlet.kt +++ b/webserver/src/main/kotlin/net/corda/webserver/servlets/AttachmentDownloadServlet.kt @@ -3,7 +3,7 @@ package net.corda.webserver.servlets import net.corda.core.internal.extractFile import net.corda.core.crypto.SecureHash import net.corda.core.messaging.CordaRPCOps -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import java.io.FileNotFoundException import java.io.IOException import java.util.jar.JarInputStream @@ -25,7 +25,9 @@ import javax.ws.rs.core.MediaType * TODO: Provide an endpoint that exposes attachment file listings, to make attachments browsable. */ class AttachmentDownloadServlet : HttpServlet() { - private val log = loggerFor() + companion object { + private val log = contextLogger() + } @Throws(IOException::class) override fun doGet(req: HttpServletRequest, resp: HttpServletResponse) { diff --git a/webserver/src/main/kotlin/net/corda/webserver/servlets/DataUploadServlet.kt b/webserver/src/main/kotlin/net/corda/webserver/servlets/DataUploadServlet.kt index e2c4c49e3d..d860d5d5a5 100644 --- a/webserver/src/main/kotlin/net/corda/webserver/servlets/DataUploadServlet.kt +++ b/webserver/src/main/kotlin/net/corda/webserver/servlets/DataUploadServlet.kt @@ -1,7 +1,7 @@ package net.corda.webserver.servlets import net.corda.core.messaging.CordaRPCOps -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import org.apache.commons.fileupload.servlet.ServletFileUpload import java.io.IOException import java.util.* @@ -13,7 +13,9 @@ import javax.servlet.http.HttpServletResponse * Uploads to the node via the [CordaRPCOps] uploadFile interface. */ class DataUploadServlet : HttpServlet() { - private val log = loggerFor() + companion object { + private val log = contextLogger() + } @Throws(IOException::class) override fun doPost(req: HttpServletRequest, resp: HttpServletResponse) {