From 92c8861802df2fb23a822766ca26dec519843347 Mon Sep 17 00:00:00 2001 From: Michele Sollecito Date: Wed, 15 Nov 2017 14:58:43 +0000 Subject: [PATCH] [CORDA-760]: Propagate invocation context across the codebase. (#2016) --- .ci/api-current.txt | 269 +++++++++++++++++- .../corda/client/jfx/NodeMonitorModelTest.kt | 14 +- .../corda/client/rpc/CordaRPCClientTest.kt | 79 +++-- .../net/corda/client/rpc/RPCStabilityTests.kt | 6 +- .../net/corda/client/rpc/CordaRPCClient.kt | 19 +- .../corda/client/rpc/internal/RPCClient.kt | 10 +- .../rpc/internal/RPCClientProxyHandler.kt | 58 ++-- .../rpc/ClientRPCInfrastructureTests.kt | 4 +- .../corda/client/rpc/RPCPermissionsTests.kt | 1 - .../confidential/IdentitySyncFlowTests.kt | 5 +- config/dev/log4j2.xml | 4 +- .../net/corda/core/context/AuthServiceId.kt | 9 + .../corda/core/context/InvocationContext.kt | 136 +++++++++ .../kotlin/net/corda/core/context/Trace.kt | 56 ++++ .../net/corda/core/contracts/Structures.kt | 1 + .../net/corda/core/flows/FlowInitiator.kt | 6 + .../corda/core/internal/FlowStateMachine.kt | 3 +- .../net/corda/core/messaging/CordaRPCOps.kt | 38 ++- .../kotlin/net/corda/core/utilities/Id.kt | 46 +++ .../net/corda/core/utilities/UuidGenerator.kt | 10 + .../net/corda/core/flows/FlowsInJavaTest.java | 5 +- .../net/corda/core/flows/AttachmentTests.kt | 5 +- .../net/corda/core/flows/FinalityFlowTests.kt | 5 +- .../corda/core/flows/ReceiveAllFlowTests.kt | 1 + .../internal/ResolveTransactionsFlowTest.kt | 1 + .../AttachmentSerializationTest.kt | 1 + .../mocknetwork/TutorialMockNetwork.kt | 1 + .../net/corda/docs/CustomVaultQueryTest.kt | 1 + .../docs/FxTransactionBuildTutorialTest.kt | 1 + .../WorkflowTransactionBuildTutorialTest.kt | 3 +- .../corda/finance/flows/CashConfigDataFlow.kt | 3 +- .../contracts/asset/CashSelectionH2Test.kt | 1 + .../corda/finance/flows/CashExitFlowTests.kt | 1 + .../corda/finance/flows/CashIssueFlowTests.kt | 1 + .../main/kotlin/net/corda/nodeapi/RPCApi.kt | 181 +++++++++--- .../node/services/BFTNotaryServiceTests.kt | 1 + .../node/services/RaftNotaryServiceTests.kt | 1 + .../statemachine/FlowVersioningTest.kt | 1 + .../services/messaging/MQSecurityTest.kt | 5 +- .../net/corda/node/internal/AbstractNode.kt | 11 +- .../corda/node/internal/CordaRPCOpsImpl.kt | 41 ++- .../node/internal/RpcAuthorisationProxy.kt | 9 +- .../net/corda/node/services/RPCUserService.kt | 6 + .../corda/node/services/api/AuditService.kt | 18 +- .../node/services/api/ServiceHubInternal.kt | 23 +- .../services/events/NodeSchedulerService.kt | 8 +- .../logging/ContextualLoggingUtils.kt | 28 ++ .../node/services/messaging/RPCServer.kt | 103 +++++-- .../services/messaging/RPCServerStructures.kt | 20 -- .../node/services/messaging/RpcAuthContext.kt | 29 ++ .../statemachine/FlowStateMachineImpl.kt | 16 +- .../statemachine/StateMachineManager.kt | 10 +- .../statemachine/StateMachineManagerImpl.kt | 38 ++- .../vault/HibernateQueryCriteriaParser.kt | 3 +- .../node/services/vault/NodeVaultService.kt | 11 +- .../node/shell/FlowWatchPrintingSubscriber.kt | 14 +- .../net/corda/node/shell/InteractiveShell.kt | 13 +- .../net/corda/node/CordaRPCOpsImplTest.kt | 18 +- .../corda/node/internal/CordaServiceTest.kt | 14 +- .../events/NodeSchedulerServiceTest.kt | 1 + .../services/events/ScheduledFlowTests.kt | 11 +- .../messaging/ArtemisMessagingTests.kt | 2 - .../statemachine/FlowFrameworkTests.kt | 4 +- .../transactions/NotaryServiceTests.kt | 1 + .../ValidatingNotaryServiceTests.kt | 5 +- .../vault/VaultSoftLockManagerTest.kt | 1 + .../corda/netmap/simulation/IRSSimulation.kt | 1 + .../kotlin/net/corda/testing/NodeTestUtils.kt | 24 +- .../net/corda/testing/internal/RPCDriver.kt | 12 +- .../src/main/resources/log4j2-test.xml | 2 +- 70 files changed, 1177 insertions(+), 313 deletions(-) create mode 100644 core/src/main/kotlin/net/corda/core/context/AuthServiceId.kt create mode 100644 core/src/main/kotlin/net/corda/core/context/InvocationContext.kt create mode 100644 core/src/main/kotlin/net/corda/core/context/Trace.kt create mode 100644 core/src/main/kotlin/net/corda/core/utilities/Id.kt create mode 100644 core/src/main/kotlin/net/corda/core/utilities/UuidGenerator.kt create mode 100644 node/src/main/kotlin/net/corda/node/services/logging/ContextualLoggingUtils.kt delete mode 100644 node/src/main/kotlin/net/corda/node/services/messaging/RPCServerStructures.kt create mode 100644 node/src/main/kotlin/net/corda/node/services/messaging/RpcAuthContext.kt diff --git a/.ci/api-current.txt b/.ci/api-current.txt index f00c85996a..b59f61b0ac 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -53,6 +53,154 @@ public interface net.corda.core.concurrent.CordaFuture extends java.util.concurr public abstract void then(kotlin.jvm.functions.Function1) @org.jetbrains.annotations.NotNull public abstract concurrent.CompletableFuture toCompletableFuture() ## +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.context.Actor extends java.lang.Object + public (net.corda.core.context.Actor$Id, net.corda.core.context.AuthServiceId, net.corda.core.identity.CordaX500Name) + @org.jetbrains.annotations.NotNull public final net.corda.core.context.Actor$Id component1() + @org.jetbrains.annotations.NotNull public final net.corda.core.context.AuthServiceId component2() + @org.jetbrains.annotations.NotNull public final net.corda.core.identity.CordaX500Name component3() + @org.jetbrains.annotations.NotNull public final net.corda.core.context.Actor copy(net.corda.core.context.Actor$Id, net.corda.core.context.AuthServiceId, net.corda.core.identity.CordaX500Name) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public final net.corda.core.context.Actor$Id getId() + @org.jetbrains.annotations.NotNull public final net.corda.core.identity.CordaX500Name getOwningLegalIdentity() + @org.jetbrains.annotations.NotNull public final net.corda.core.context.AuthServiceId getServiceId() + public int hashCode() + @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.context.Actor service(String, net.corda.core.identity.CordaX500Name) + public String toString() + public static final net.corda.core.context.Actor$Companion Companion +## +public static final class net.corda.core.context.Actor$Companion extends java.lang.Object + @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.context.Actor service(String, net.corda.core.identity.CordaX500Name) +## +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.context.Actor$Id extends java.lang.Object + public (String) + @org.jetbrains.annotations.NotNull public final String component1() + @org.jetbrains.annotations.NotNull public final net.corda.core.context.Actor$Id copy(String) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public final String getValue() + public int hashCode() + public String toString() +## +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.context.AuthServiceId extends java.lang.Object + public (String) + @org.jetbrains.annotations.NotNull public final String component1() + @org.jetbrains.annotations.NotNull public final net.corda.core.context.AuthServiceId copy(String) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public final String getValue() + public int hashCode() + public String toString() +## +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.context.InvocationContext extends java.lang.Object + public (net.corda.core.context.Origin, net.corda.core.context.Trace, net.corda.core.context.Actor, net.corda.core.context.Trace, net.corda.core.context.Actor) + @org.jetbrains.annotations.NotNull public final net.corda.core.context.Origin component1() + @org.jetbrains.annotations.NotNull public final net.corda.core.context.Trace component2() + @org.jetbrains.annotations.Nullable public final net.corda.core.context.Actor component3() + @org.jetbrains.annotations.Nullable public final net.corda.core.context.Trace component4() + @org.jetbrains.annotations.Nullable public final net.corda.core.context.Actor component5() + @org.jetbrains.annotations.NotNull public final net.corda.core.context.InvocationContext copy(net.corda.core.context.Origin, net.corda.core.context.Trace, net.corda.core.context.Actor, net.corda.core.context.Trace, net.corda.core.context.Actor) + public boolean equals(Object) + @org.jetbrains.annotations.Nullable public final net.corda.core.context.Actor getActor() + @org.jetbrains.annotations.Nullable public final net.corda.core.context.Trace getExternalTrace() + @org.jetbrains.annotations.Nullable public final net.corda.core.context.Actor getImpersonatedActor() + @org.jetbrains.annotations.NotNull public final net.corda.core.context.Origin getOrigin() + @org.jetbrains.annotations.NotNull public final net.corda.core.context.Trace getTrace() + public int hashCode() + @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.context.InvocationContext newInstance(net.corda.core.context.Origin, net.corda.core.context.Trace, net.corda.core.context.Actor, net.corda.core.context.Trace, net.corda.core.context.Actor) + @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.context.InvocationContext peer(net.corda.core.identity.CordaX500Name, net.corda.core.context.Trace, net.corda.core.context.Trace, net.corda.core.context.Actor) + @org.jetbrains.annotations.NotNull public final java.security.Principal principal() + @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.context.InvocationContext rpc(net.corda.core.context.Actor, net.corda.core.context.Trace, net.corda.core.context.Trace, net.corda.core.context.Actor) + @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.context.InvocationContext scheduled(net.corda.core.contracts.ScheduledStateRef, net.corda.core.context.Trace, net.corda.core.context.Trace) + @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.context.InvocationContext service(String, net.corda.core.identity.CordaX500Name, net.corda.core.context.Trace, net.corda.core.context.Trace) + @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.context.InvocationContext shell(net.corda.core.context.Trace, net.corda.core.context.Trace) + public String toString() + public static final net.corda.core.context.InvocationContext$Companion Companion +## +public static final class net.corda.core.context.InvocationContext$Companion extends java.lang.Object + @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.context.InvocationContext newInstance(net.corda.core.context.Origin, net.corda.core.context.Trace, net.corda.core.context.Actor, net.corda.core.context.Trace, net.corda.core.context.Actor) + @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.context.InvocationContext peer(net.corda.core.identity.CordaX500Name, net.corda.core.context.Trace, net.corda.core.context.Trace, net.corda.core.context.Actor) + @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.context.InvocationContext rpc(net.corda.core.context.Actor, net.corda.core.context.Trace, net.corda.core.context.Trace, net.corda.core.context.Actor) + @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.context.InvocationContext scheduled(net.corda.core.contracts.ScheduledStateRef, net.corda.core.context.Trace, net.corda.core.context.Trace) + @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.context.InvocationContext service(String, net.corda.core.identity.CordaX500Name, net.corda.core.context.Trace, net.corda.core.context.Trace) + @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.context.InvocationContext shell(net.corda.core.context.Trace, net.corda.core.context.Trace) +## +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.context.Origin extends java.lang.Object + @org.jetbrains.annotations.NotNull public abstract java.security.Principal principal() +## +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.context.Origin$Peer extends net.corda.core.context.Origin + public (net.corda.core.identity.CordaX500Name) + @org.jetbrains.annotations.NotNull public final net.corda.core.identity.CordaX500Name component1() + @org.jetbrains.annotations.NotNull public final net.corda.core.context.Origin$Peer copy(net.corda.core.identity.CordaX500Name) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public final net.corda.core.identity.CordaX500Name getParty() + public int hashCode() + @org.jetbrains.annotations.NotNull public java.security.Principal principal() + public String toString() +## +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.context.Origin$RPC extends net.corda.core.context.Origin + public (net.corda.core.context.Actor) + @org.jetbrains.annotations.NotNull public final net.corda.core.context.Origin$RPC copy(net.corda.core.context.Actor) + public boolean equals(Object) + public int hashCode() + @org.jetbrains.annotations.NotNull public java.security.Principal principal() + public String toString() +## +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.context.Origin$Scheduled extends net.corda.core.context.Origin + public (net.corda.core.contracts.ScheduledStateRef) + @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.ScheduledStateRef component1() + @org.jetbrains.annotations.NotNull public final net.corda.core.context.Origin$Scheduled copy(net.corda.core.contracts.ScheduledStateRef) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.ScheduledStateRef getScheduledState() + public int hashCode() + @org.jetbrains.annotations.NotNull public java.security.Principal principal() + public String toString() +## +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.context.Origin$Service extends net.corda.core.context.Origin + public (String, net.corda.core.identity.CordaX500Name) + @org.jetbrains.annotations.NotNull public final String component1() + @org.jetbrains.annotations.NotNull public final net.corda.core.identity.CordaX500Name component2() + @org.jetbrains.annotations.NotNull public final net.corda.core.context.Origin$Service copy(String, net.corda.core.identity.CordaX500Name) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public final net.corda.core.identity.CordaX500Name getOwningLegalIdentity() + @org.jetbrains.annotations.NotNull public final String getServiceClassName() + public int hashCode() + @org.jetbrains.annotations.NotNull public java.security.Principal principal() + public String toString() +## +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.context.Origin$Shell extends net.corda.core.context.Origin + @org.jetbrains.annotations.NotNull public java.security.Principal principal() + public static final net.corda.core.context.Origin$Shell INSTANCE +## +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.context.Trace extends java.lang.Object + public (net.corda.core.context.Trace$InvocationId, net.corda.core.context.Trace$SessionId) + @org.jetbrains.annotations.NotNull public final net.corda.core.context.Trace$InvocationId component1() + @org.jetbrains.annotations.NotNull public final net.corda.core.context.Trace$SessionId component2() + @org.jetbrains.annotations.NotNull public final net.corda.core.context.Trace copy(net.corda.core.context.Trace$InvocationId, net.corda.core.context.Trace$SessionId) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public final net.corda.core.context.Trace$InvocationId getInvocationId() + @org.jetbrains.annotations.NotNull public final net.corda.core.context.Trace$SessionId getSessionId() + public int hashCode() + @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.context.Trace newInstance(net.corda.core.context.Trace$InvocationId, net.corda.core.context.Trace$SessionId) + public String toString() + public static final net.corda.core.context.Trace$Companion Companion +## +public static final class net.corda.core.context.Trace$Companion extends java.lang.Object + @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.context.Trace newInstance(net.corda.core.context.Trace$InvocationId, net.corda.core.context.Trace$SessionId) +## +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.context.Trace$InvocationId extends net.corda.core.utilities.Id + public (String, java.time.Instant) + @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.context.Trace$InvocationId newInstance(String, java.time.Instant) + public static final net.corda.core.context.Trace$InvocationId$Companion Companion +## +public static final class net.corda.core.context.Trace$InvocationId$Companion extends java.lang.Object + @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.context.Trace$InvocationId newInstance(String, java.time.Instant) +## +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.context.Trace$SessionId extends net.corda.core.utilities.Id + public (String, java.time.Instant) + @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.context.Trace$SessionId newInstance(String, java.time.Instant) + public static final net.corda.core.context.Trace$SessionId$Companion Companion +## +public static final class net.corda.core.context.Trace$SessionId$Companion extends java.lang.Object + @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.context.Trace$SessionId newInstance(String, java.time.Instant) +## @net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.AlwaysAcceptAttachmentConstraint extends java.lang.Object implements net.corda.core.contracts.AttachmentConstraint public boolean isSatisfiedBy(net.corda.core.contracts.Attachment) public static final net.corda.core.contracts.AlwaysAcceptAttachmentConstraint INSTANCE @@ -283,7 +431,7 @@ public final class net.corda.core.contracts.ScheduledActivity extends java.lang. public int hashCode() public String toString() ## -public final class net.corda.core.contracts.ScheduledStateRef extends java.lang.Object implements net.corda.core.contracts.Scheduled +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.ScheduledStateRef extends java.lang.Object implements net.corda.core.contracts.Scheduled public (net.corda.core.contracts.StateRef, java.time.Instant) @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.StateRef component1() @org.jetbrains.annotations.NotNull public final java.time.Instant component2() @@ -351,6 +499,7 @@ public final class net.corda.core.contracts.Structures extends java.lang.Object @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.contracts.TimeWindow fromOnly(java.time.Instant) @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.contracts.TimeWindow fromStartAndDuration(java.time.Instant, java.time.Duration) @org.jetbrains.annotations.Nullable public abstract java.time.Instant getFromTime() + @org.jetbrains.annotations.Nullable public final java.time.Duration getLength() @org.jetbrains.annotations.Nullable public abstract java.time.Instant getMidpoint() @org.jetbrains.annotations.Nullable public abstract java.time.Instant getUntilTime() @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.contracts.TimeWindow untilOnly(java.time.Instant) @@ -418,6 +567,7 @@ public final class net.corda.core.contracts.TransactionStateKt extends java.lang ## @net.corda.core.serialization.CordaSerializable public static final class net.corda.core.contracts.TransactionVerificationException$MissingAttachmentRejection extends net.corda.core.contracts.TransactionVerificationException public (net.corda.core.crypto.SecureHash, String) + @org.jetbrains.annotations.NotNull public final String getContractClass() ## @net.corda.core.serialization.CordaSerializable public static final class net.corda.core.contracts.TransactionVerificationException$MoreThanOneNotary extends net.corda.core.contracts.TransactionVerificationException public (net.corda.core.crypto.SecureHash) @@ -675,6 +825,7 @@ public final class net.corda.core.crypto.CryptoUtils extends java.lang.Object public final boolean isValid(byte[]) public final boolean verify(net.corda.core.utilities.OpaqueBytes) public final boolean verify(byte[]) + @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.DigitalSignature withoutKey() ## public abstract class net.corda.core.crypto.MerkleTree extends java.lang.Object @org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.SecureHash getHash() @@ -1038,6 +1189,16 @@ public static final class net.corda.core.flows.FinalityFlow$Companion extends ja public int hashCode() public String toString() ## +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.FlowInitiator$Service extends net.corda.core.flows.FlowInitiator + public (String) + @org.jetbrains.annotations.NotNull public final String component1() + @org.jetbrains.annotations.NotNull public final net.corda.core.flows.FlowInitiator$Service copy(String) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public String getName() + @org.jetbrains.annotations.NotNull public final String getServiceClassName() + public int hashCode() + public String toString() +## @net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.FlowInitiator$Shell extends net.corda.core.flows.FlowInitiator @org.jetbrains.annotations.NotNull public String getName() public static final net.corda.core.flows.FlowInitiator$Shell INSTANCE @@ -1047,6 +1208,7 @@ public abstract class net.corda.core.flows.FlowLogic extends java.lang.Object @co.paralleluniverse.fibers.Suspendable public abstract Object call() public final void checkFlowPermission(String, Map) @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.Nullable public final net.corda.core.flows.FlowStackSnapshot flowStackSnapshot() + @org.jetbrains.annotations.Nullable public static final net.corda.core.flows.FlowLogic getCurrentTopLevel() @kotlin.Deprecated @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public final net.corda.core.flows.FlowInfo getFlowInfo(net.corda.core.identity.Party) @org.jetbrains.annotations.NotNull public final org.slf4j.Logger getLogger() @org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party getOurIdentity() @@ -1058,13 +1220,22 @@ public abstract class net.corda.core.flows.FlowLogic extends java.lang.Object @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public final net.corda.core.flows.FlowSession initiateFlow(net.corda.core.identity.Party) @co.paralleluniverse.fibers.Suspendable public final void persistFlowStackSnapshot() @kotlin.Deprecated @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public net.corda.core.utilities.UntrustworthyData receive(Class, net.corda.core.identity.Party) + @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public List receiveAll(Class, List) + @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public Map receiveAll(Map) public final void recordAuditEvent(String, String, Map) @kotlin.Deprecated @co.paralleluniverse.fibers.Suspendable public void send(net.corda.core.identity.Party, Object) @kotlin.Deprecated @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public net.corda.core.utilities.UntrustworthyData sendAndReceive(Class, net.corda.core.identity.Party, Object) public final void setStateMachine(net.corda.core.internal.FlowStateMachine) + @co.paralleluniverse.fibers.Suspendable @kotlin.jvm.JvmStatic public static final void sleep(java.time.Duration) @co.paralleluniverse.fibers.Suspendable public Object subFlow(net.corda.core.flows.FlowLogic) @org.jetbrains.annotations.Nullable public final net.corda.core.messaging.DataFeed track() @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.SignedTransaction waitForLedgerCommit(net.corda.core.crypto.SecureHash) + @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.SignedTransaction waitForLedgerCommit(net.corda.core.crypto.SecureHash, boolean) + public static final net.corda.core.flows.FlowLogic$Companion Companion +## +public static final class net.corda.core.flows.FlowLogic$Companion extends java.lang.Object + @org.jetbrains.annotations.Nullable public final net.corda.core.flows.FlowLogic getCurrentTopLevel() + @co.paralleluniverse.fibers.Suspendable @kotlin.jvm.JvmStatic public final void sleep(java.time.Duration) ## @net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public interface net.corda.core.flows.FlowLogicRef ## @@ -1185,6 +1356,7 @@ public final class net.corda.core.flows.ReceiveStateAndRefFlow extends net.corda public final class net.corda.core.flows.ReceiveTransactionFlow extends net.corda.core.flows.FlowLogic public (net.corda.core.flows.FlowSession) public (net.corda.core.flows.FlowSession, boolean) + public (net.corda.core.flows.FlowSession, boolean, net.corda.core.node.StatesToRecord) @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public net.corda.core.transactions.SignedTransaction call() ## public @interface net.corda.core.flows.SchedulableFlow @@ -1227,6 +1399,8 @@ public final class net.corda.core.flows.StackFrameDataToken extends java.lang.Ob ## public @interface net.corda.core.flows.StartableByRPC ## +public @interface net.corda.core.flows.StartableByService +## @net.corda.core.serialization.CordaSerializable public final class net.corda.core.flows.StateMachineRunId extends java.lang.Object public (UUID) @org.jetbrains.annotations.NotNull public final UUID component1() @@ -1359,6 +1533,7 @@ 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) @@ -1438,6 +1613,11 @@ 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() ## @@ -1447,12 +1627,17 @@ public @interface net.corda.core.messaging.RPCReturnsObservables ## @net.corda.core.serialization.CordaSerializable public final class net.corda.core.messaging.StateMachineInfo extends java.lang.Object public (net.corda.core.flows.StateMachineRunId, String, net.corda.core.flows.FlowInitiator, net.corda.core.messaging.DataFeed) + public (net.corda.core.flows.StateMachineRunId, String, net.corda.core.flows.FlowInitiator, net.corda.core.messaging.DataFeed, net.corda.core.context.InvocationContext) @org.jetbrains.annotations.NotNull public final net.corda.core.flows.StateMachineRunId component1() @org.jetbrains.annotations.NotNull public final String component2() @org.jetbrains.annotations.NotNull public final net.corda.core.flows.FlowInitiator component3() @org.jetbrains.annotations.Nullable public final net.corda.core.messaging.DataFeed component4() + @org.jetbrains.annotations.Nullable public final net.corda.core.context.InvocationContext component5() + @org.jetbrains.annotations.NotNull public final net.corda.core.context.InvocationContext context() @org.jetbrains.annotations.NotNull public final net.corda.core.messaging.StateMachineInfo copy(net.corda.core.flows.StateMachineRunId, String, net.corda.core.flows.FlowInitiator, net.corda.core.messaging.DataFeed) + @org.jetbrains.annotations.NotNull public final net.corda.core.messaging.StateMachineInfo copy(net.corda.core.flows.StateMachineRunId, String, net.corda.core.flows.FlowInitiator, net.corda.core.messaging.DataFeed, net.corda.core.context.InvocationContext) public boolean equals(Object) + @org.jetbrains.annotations.Nullable public final net.corda.core.context.InvocationContext getContext() @org.jetbrains.annotations.NotNull public final String getFlowLogicClassName() @org.jetbrains.annotations.NotNull public final net.corda.core.flows.StateMachineRunId getId() @org.jetbrains.annotations.NotNull public final net.corda.core.flows.FlowInitiator getInitiator() @@ -1499,6 +1684,27 @@ public @interface net.corda.core.messaging.RPCReturnsObservables @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.FlowHandle startFlow(net.corda.core.flows.FlowLogic) @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.FlowProgressHandle startTrackedFlow(net.corda.core.flows.FlowLogic) ## +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.NetworkParameters extends java.lang.Object + public (int, List, java.time.Duration, int, int, java.time.Instant, int) + public final int component1() + @org.jetbrains.annotations.NotNull public final List component2() + @org.jetbrains.annotations.NotNull public final java.time.Duration component3() + public final int component4() + public final int component5() + @org.jetbrains.annotations.NotNull public final java.time.Instant component6() + public final int component7() + @org.jetbrains.annotations.NotNull public final net.corda.core.node.NetworkParameters copy(int, List, java.time.Duration, int, int, java.time.Instant, int) + public boolean equals(Object) + public final int getEpoch() + @org.jetbrains.annotations.NotNull public final java.time.Duration getEventHorizon() + public final int getMaxMessageSize() + public final int getMaxTransactionSize() + public final int getMinimumPlatformVersion() + @org.jetbrains.annotations.NotNull public final java.time.Instant getModifiedTime() + @org.jetbrains.annotations.NotNull public final List getNotaries() + public int hashCode() + public String toString() +## @net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.NodeInfo extends java.lang.Object public (List, List, int, long) @org.jetbrains.annotations.NotNull public final List component1() @@ -1513,9 +1719,21 @@ public @interface net.corda.core.messaging.RPCReturnsObservables public final int getPlatformVersion() public final long getSerial() public int hashCode() + @org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party identityFromX500Name(net.corda.core.identity.CordaX500Name) public final boolean isLegalIdentity(net.corda.core.identity.Party) public String toString() ## +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.NotaryInfo extends java.lang.Object + public (net.corda.core.identity.Party, boolean) + @org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party component1() + public final boolean component2() + @org.jetbrains.annotations.NotNull public final net.corda.core.node.NotaryInfo copy(net.corda.core.identity.Party, boolean) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party getIdentity() + public final boolean getValidating() + public int hashCode() + public String toString() +## @net.corda.core.DoNotImplement public interface net.corda.core.node.ServiceHub extends net.corda.core.node.ServicesForResolution @org.jetbrains.annotations.NotNull public abstract net.corda.core.transactions.SignedTransaction addSignature(net.corda.core.transactions.SignedTransaction) @org.jetbrains.annotations.NotNull public abstract net.corda.core.transactions.SignedTransaction addSignature(net.corda.core.transactions.SignedTransaction, java.security.PublicKey) @@ -1528,12 +1746,14 @@ 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() @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.VaultService getVaultService() @org.jetbrains.annotations.NotNull public abstract java.sql.Connection jdbcSession() public abstract void recordTransactions(Iterable) + public abstract void recordTransactions(net.corda.core.node.StatesToRecord, Iterable) public abstract void recordTransactions(boolean, Iterable) @org.jetbrains.annotations.NotNull public abstract net.corda.core.transactions.SignedTransaction signInitialTransaction(net.corda.core.transactions.TransactionBuilder) @org.jetbrains.annotations.NotNull public abstract net.corda.core.transactions.SignedTransaction signInitialTransaction(net.corda.core.transactions.TransactionBuilder, Iterable) @@ -1548,6 +1768,11 @@ public @interface net.corda.core.messaging.RPCReturnsObservables @net.corda.core.DoNotImplement public interface net.corda.core.node.StateLoader @org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.TransactionState loadState(net.corda.core.contracts.StateRef) ## +public final class net.corda.core.node.StatesToRecord extends java.lang.Enum + protected (String, int) + public static net.corda.core.node.StatesToRecord valueOf(String) + public static net.corda.core.node.StatesToRecord[] values() +## @net.corda.core.DoNotImplement public interface net.corda.core.node.services.AttachmentStorage @org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.SecureHash importAttachment(java.io.InputStream) @org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.SecureHash importAttachment(java.io.InputStream, String, String) @@ -1646,6 +1871,11 @@ public @interface net.corda.core.node.services.CordaService @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.ServiceHub getServices() public abstract void start() public abstract void stop() + public static final net.corda.core.node.services.NotaryService$Companion Companion + @org.jetbrains.annotations.NotNull public static final String ID_PREFIX = "corda.notary." +## +public static final class net.corda.core.node.services.NotaryService$Companion extends java.lang.Object + @org.jetbrains.annotations.NotNull public final String constructId(boolean, boolean, boolean, boolean) ## public abstract class net.corda.core.node.services.PartyInfo extends java.lang.Object @org.jetbrains.annotations.NotNull public abstract net.corda.core.identity.Party getParty() @@ -1948,6 +2178,8 @@ public abstract class net.corda.core.node.services.vault.BaseSort extends java.l @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression in(reflect.Field, Collection) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.ColumnPredicate$CollectionExpression in(Collection) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression in(kotlin.reflect.KProperty1, Collection) + @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.ColumnPredicate$NullExpression isNotNull() + @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.ColumnPredicate$NullExpression isNull() @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression isNull(reflect.Field) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression isNull(kotlin.reflect.KProperty1) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.ColumnPredicate$BinaryComparison lessThan(Comparable) @@ -1956,6 +2188,7 @@ public abstract class net.corda.core.node.services.vault.BaseSort extends java.l @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.ColumnPredicate$BinaryComparison lessThanOrEqual(Comparable) @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression lessThanOrEqual(reflect.Field, Comparable) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression lessThanOrEqual(kotlin.reflect.KProperty1, Comparable) + @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.ColumnPredicate$Likeness like(String) @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression like(reflect.Field, String) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression like(kotlin.reflect.KProperty1, String) @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression max(reflect.Field) @@ -1972,6 +2205,7 @@ public abstract class net.corda.core.node.services.vault.BaseSort extends java.l @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression notIn(reflect.Field, Collection) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.ColumnPredicate$CollectionExpression notIn(Collection) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression notIn(kotlin.reflect.KProperty1, Collection) + @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.ColumnPredicate$Likeness notLike(String) @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression notLike(reflect.Field, String) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression notLike(kotlin.reflect.KProperty1, String) @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression notNull(reflect.Field) @@ -2631,8 +2865,10 @@ public static final class net.corda.core.serialization.SingletonSerializationTok public String toString() ## @net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.transactions.FilteredTransaction extends net.corda.core.transactions.TraversableTransaction + public (net.corda.core.crypto.SecureHash, List, List) @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.transactions.FilteredTransaction buildFilteredTransaction(net.corda.core.transactions.WireTransaction, function.Predicate) public final void checkAllComponentsVisible(net.corda.core.contracts.ComponentGroupEnum) + public final void checkCommandVisibility(java.security.PublicKey) public final boolean checkWithFun(kotlin.jvm.functions.Function1) @org.jetbrains.annotations.NotNull public final List getFilteredComponentGroups() @org.jetbrains.annotations.NotNull public final List getGroupHashes() @@ -2692,6 +2928,7 @@ public static final class net.corda.core.transactions.FilteredTransaction$Compan @org.jetbrains.annotations.NotNull public final List inputsOfType(Class) public String toString() public final void verify() + public static final net.corda.core.transactions.LedgerTransaction$Companion Companion ## public static final class net.corda.core.transactions.LedgerTransaction$InOutGroup extends java.lang.Object public (List, List, Object) @@ -2732,6 +2969,7 @@ public static final class net.corda.core.transactions.LedgerTransaction$InOutGro public int hashCode() public String toString() public void verifyRequiredSignatures() + public void verifySignaturesExcept(Collection) ## @net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.transactions.NotaryChangeWireTransaction extends net.corda.core.transactions.CoreTransaction public (List, net.corda.core.identity.Party, net.corda.core.identity.Party) @@ -2747,6 +2985,7 @@ public static final class net.corda.core.transactions.LedgerTransaction$InOutGro @org.jetbrains.annotations.NotNull public List getOutputs() public int hashCode() @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.NotaryChangeLedgerTransaction resolve(net.corda.core.node.ServiceHub, List) + @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.NotaryChangeLedgerTransaction resolve(net.corda.core.node.StateLoader, List) public String toString() ## @net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.transactions.SignedTransaction extends java.lang.Object implements net.corda.core.transactions.TransactionWithSignatures @@ -2772,13 +3011,17 @@ public static final class net.corda.core.transactions.LedgerTransaction$InOutGro public final boolean isNotaryChangeTransaction() @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.SignedTransaction plus(Collection) @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.SignedTransaction plus(net.corda.core.crypto.TransactionSignature) + @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.BaseTransaction resolveBaseTransaction(net.corda.core.node.StateLoader) @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.NotaryChangeLedgerTransaction resolveNotaryChangeTransaction(net.corda.core.node.ServiceHub) + @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.NotaryChangeLedgerTransaction resolveNotaryChangeTransaction(net.corda.core.node.StateLoader) + @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.TransactionWithSignatures resolveTransactionWithSignatures(net.corda.core.node.ServiceHub) @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.LedgerTransaction toLedgerTransaction(net.corda.core.node.ServiceHub) @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.LedgerTransaction toLedgerTransaction(net.corda.core.node.ServiceHub, boolean) @org.jetbrains.annotations.NotNull public String toString() public final void verify(net.corda.core.node.ServiceHub) public final void verify(net.corda.core.node.ServiceHub, boolean) public void verifyRequiredSignatures() + public void verifySignaturesExcept(Collection) @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.SignedTransaction withAdditionalSignature(java.security.KeyPair, net.corda.core.crypto.SignatureMetadata) @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.SignedTransaction withAdditionalSignature(net.corda.core.crypto.TransactionSignature) @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.SignedTransaction withAdditionalSignatures(Iterable) @@ -2842,6 +3085,7 @@ public class net.corda.core.transactions.TransactionBuilder extends java.lang.Ob @org.jetbrains.annotations.NotNull public abstract Set getRequiredSigningKeys() @org.jetbrains.annotations.NotNull public abstract List getSigs() public abstract void verifyRequiredSignatures() + public abstract void verifySignaturesExcept(Collection) ## @net.corda.core.DoNotImplement public abstract class net.corda.core.transactions.TraversableTransaction extends net.corda.core.transactions.CoreTransaction public (List) @@ -2919,6 +3163,21 @@ public final class net.corda.core.utilities.EncodingUtils extends java.lang.Obje @org.jetbrains.annotations.NotNull public static final String toBase64(byte[]) @org.jetbrains.annotations.NotNull public static final String toHex(byte[]) @org.jetbrains.annotations.NotNull public static final byte[] toSHA256Bytes(java.security.PublicKey) + public static final int MAX_HASH_HEX_SIZE = 130 +## +public class net.corda.core.utilities.Id extends java.lang.Object + public (Object, String, java.time.Instant) + public final boolean equals(Object) + @org.jetbrains.annotations.Nullable public final String getEntityType() + @org.jetbrains.annotations.NotNull public final java.time.Instant getTimestamp() + @org.jetbrains.annotations.NotNull public final Object getValue() + public final int hashCode() + @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.utilities.Id newInstance(Object, String, java.time.Instant) + @org.jetbrains.annotations.NotNull public final String toString() + public static final net.corda.core.utilities.Id$Companion Companion +## +public static final class net.corda.core.utilities.Id$Companion extends java.lang.Object + @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.Id newInstance(Object, String, java.time.Instant) ## public final class net.corda.core.utilities.KotlinUtilsKt extends java.lang.Object public static final void debug(org.slf4j.Logger, kotlin.jvm.functions.Function0) @@ -3120,6 +3379,13 @@ public static interface net.corda.core.utilities.UntrustworthyData$Validator ext public final class net.corda.core.utilities.UntrustworthyDataKt extends java.lang.Object public static final Object unwrap(net.corda.core.utilities.UntrustworthyData, kotlin.jvm.functions.Function1) ## +public final class net.corda.core.utilities.UuidGenerator extends java.lang.Object + public () + public static final net.corda.core.utilities.UuidGenerator$Companion Companion +## +public static final class net.corda.core.utilities.UuidGenerator$Companion extends java.lang.Object + @org.jetbrains.annotations.NotNull public final UUID next() +## public interface net.corda.core.utilities.VariablePropertyDelegate extends net.corda.core.utilities.PropertyDelegate public abstract void setValue(Object, kotlin.reflect.KProperty, Object) ## @@ -3295,6 +3561,7 @@ public final class net.corda.client.rpc.CordaRPCClient extends java.lang.Object public (net.corda.core.utilities.NetworkHostAndPort) public (net.corda.core.utilities.NetworkHostAndPort, net.corda.client.rpc.CordaRPCClientConfiguration) @org.jetbrains.annotations.NotNull public final net.corda.client.rpc.CordaRPCConnection start(String, String) + @org.jetbrains.annotations.NotNull public final net.corda.client.rpc.CordaRPCConnection start(String, String, net.corda.core.context.Trace, net.corda.core.context.Actor) public final Object use(String, String, kotlin.jvm.functions.Function1) ## public final class net.corda.client.rpc.CordaRPCClientConfiguration extends java.lang.Object diff --git a/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt b/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt index 238d6fd05a..9ca6dabb37 100644 --- a/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt +++ b/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt @@ -6,11 +6,11 @@ import net.corda.core.contracts.Amount import net.corda.core.contracts.ContractState import net.corda.core.crypto.isFulfilledBy import net.corda.core.crypto.keys -import net.corda.core.flows.FlowInitiator import net.corda.core.flows.StateMachineRunId import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.internal.bufferUntilSubscribed +import net.corda.core.context.Origin import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.StateMachineTransactionMapping import net.corda.core.messaging.StateMachineUpdate @@ -148,8 +148,8 @@ class NodeMonitorModelTest { // ISSUE expect { add: StateMachineUpdate.Added -> issueSmId = add.id - val initiator = add.stateMachineInfo.initiator - require(initiator is FlowInitiator.RPC && initiator.username == "user1") + val context = add.stateMachineInfo.context() + require(context.origin is Origin.RPC && context.principal().name == "user1") }, expect { remove: StateMachineUpdate.Removed -> require(remove.id == issueSmId) @@ -157,8 +157,8 @@ class NodeMonitorModelTest { // MOVE - N.B. There are other framework flows that happen in parallel for the remote resolve transactions flow expect(match = { it.stateMachineInfo.flowLogicClassName == CashPaymentFlow::class.java.name }) { add: StateMachineUpdate.Added -> moveSmId = add.id - val initiator = add.stateMachineInfo.initiator - require(initiator is FlowInitiator.RPC && initiator.username == "user1") + val context = add.stateMachineInfo.context() + require(context.origin is Origin.RPC && context.principal().name == "user1") }, expect(match = { it is StateMachineUpdate.Removed && it.id == moveSmId }) { } @@ -169,8 +169,8 @@ class NodeMonitorModelTest { sequence( // MOVE expect { add: StateMachineUpdate.Added -> - val initiator = add.stateMachineInfo.initiator - require(initiator is FlowInitiator.Peer && aliceNode.isLegalIdentity(initiator.party)) + val context = add.stateMachineInfo.context() + require(context.origin is Origin.Peer && aliceNode.isLegalIdentity(aliceNode.identityFromX500Name((context.origin as Origin.Peer).party))) } ) } diff --git a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt index 4d67be16d8..065abee220 100644 --- a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt +++ b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt @@ -1,7 +1,7 @@ package net.corda.client.rpc +import net.corda.core.context.* import net.corda.core.crypto.random63BitValue -import net.corda.core.flows.FlowInitiator import net.corda.core.internal.concurrent.flatMap import net.corda.core.internal.packageName import net.corda.core.messaging.* @@ -20,14 +20,15 @@ import net.corda.node.internal.StartedNode import net.corda.node.services.Permissions.Companion.invokeRpc import net.corda.node.services.Permissions.Companion.startFlow import net.corda.nodeapi.User -import net.corda.testing.ALICE -import net.corda.testing.chooseIdentity +import net.corda.testing.* import net.corda.testing.internal.NodeBasedTest import org.apache.activemq.artemis.api.core.ActiveMQSecurityException +import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.junit.After import org.junit.Before import org.junit.Test +import kotlin.reflect.KClass import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue @@ -44,8 +45,8 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C private lateinit var client: CordaRPCClient private var connection: CordaRPCConnection? = null - private fun login(username: String, password: String) { - connection = client.start(username, password) + private fun login(username: String, password: String, externalTrace: Trace? = null, impersonatedActor: Actor? = null) { + connection = client.start(username, password, externalTrace, impersonatedActor) } @Before @@ -131,31 +132,51 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C @Test fun `flow initiator via RPC`() { - login(rpcUser.username, rpcUser.password) + val externalTrace = Trace.newInstance() + val impersonatedActor = Actor(Actor.Id("Mark Dadada"), AuthServiceId("Test"), owningLegalIdentity = BOB.name) + login(rpcUser.username, rpcUser.password, externalTrace, impersonatedActor) val proxy = connection!!.proxy - var countRpcFlows = 0 - var countShellFlows = 0 - proxy.stateMachinesFeed().updates.subscribe { - if (it is StateMachineUpdate.Added) { - val initiator = it.stateMachineInfo.initiator - if (initiator is FlowInitiator.RPC) - countRpcFlows++ - if (initiator is FlowInitiator.Shell) - countShellFlows++ - } - } val nodeIdentity = node.info.chooseIdentity() - node.services.startFlow(CashIssueFlow(2000.DOLLARS, OpaqueBytes.of(0), nodeIdentity), FlowInitiator.Shell).flatMap { it.resultFuture }.getOrThrow() - proxy.startFlow(::CashIssueFlow, - 123.DOLLARS, - OpaqueBytes.of(0), - nodeIdentity - ).returnValue.getOrThrow() - proxy.startFlowDynamic(CashIssueFlow::class.java, - 1000.DOLLARS, - OpaqueBytes.of(0), - nodeIdentity).returnValue.getOrThrow() - assertEquals(2, countRpcFlows) - assertEquals(1, countShellFlows) + + val updates = proxy.stateMachinesFeed().updates + + node.services.startFlow(CashIssueFlow(2000.DOLLARS, OpaqueBytes.of(0), nodeIdentity), InvocationContext.shell()).flatMap { it.resultFuture }.getOrThrow() + proxy.startFlow(::CashIssueFlow, 123.DOLLARS, OpaqueBytes.of(0), nodeIdentity).returnValue.getOrThrow() + proxy.startFlowDynamic(CashIssueFlow::class.java, 1000.DOLLARS, OpaqueBytes.of(0), nodeIdentity).returnValue.getOrThrow() + + val historicalIds = mutableSetOf() + var sessionId: Trace.SessionId? = null + updates.expectEvents(isStrict = false) { + sequence( + expect { update: StateMachineUpdate.Added -> + checkShellNotification(update.stateMachineInfo) + }, + expect { update: StateMachineUpdate.Added -> + checkRpcNotification(update.stateMachineInfo, rpcUser.username, historicalIds, externalTrace, impersonatedActor) + sessionId = update.stateMachineInfo.context().trace.sessionId + }, + expect { update: StateMachineUpdate.Added -> + checkRpcNotification(update.stateMachineInfo, rpcUser.username, historicalIds, externalTrace, impersonatedActor) + assertThat(update.stateMachineInfo.context().trace.sessionId).isEqualTo(sessionId) + } + ) + } } } + +private fun checkShellNotification(info: StateMachineInfo) { + + val context = info.context() + assertThat(context.origin).isInstanceOf(Origin.Shell::class.java) +} + +private fun checkRpcNotification(info: StateMachineInfo, rpcUsername: String, historicalIds: MutableSet, externalTrace: Trace?, impersonatedActor: Actor?) { + + val context = info.context() + assertThat(context.origin).isInstanceOf(Origin.RPC::class.java) + assertThat(context.externalTrace).isEqualTo(externalTrace) + assertThat(context.impersonatedActor).isEqualTo(impersonatedActor) + assertThat(context.actor?.id?.value).isEqualTo(rpcUsername) + assertThat(historicalIds).doesNotContain(context.trace.invocationId) + historicalIds.add(context.trace.invocationId) +} 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 13cce44238..7247c33ddd 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 @@ -2,6 +2,7 @@ package net.corda.client.rpc import net.corda.client.rpc.internal.RPCClient import net.corda.client.rpc.internal.RPCClientConfiguration +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 @@ -348,9 +349,10 @@ class RPCStabilityTests { val message = session.createMessage(false) val request = RPCApi.ClientToServer.RpcRequest( clientAddress = SimpleString(myQueue), - id = RPCApi.RpcRequestId(random63BitValue()), methodName = SlowConsumerRPCOps::streamAtInterval.name, - serialisedArguments = listOf(10.millis, 123456).serialize(context = SerializationDefaults.RPC_SERVER_CONTEXT).bytes + serialisedArguments = listOf(10.millis, 123456).serialize(context = SerializationDefaults.RPC_SERVER_CONTEXT).bytes, + replyId = Trace.InvocationId.newInstance(), + sessionId = Trace.SessionId.newInstance() ) request.writeToClientMessage(message) producer.send(message) diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt index 7326a5a182..c09f621550 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt @@ -3,6 +3,8 @@ package net.corda.client.rpc import net.corda.client.rpc.internal.KryoClientSerializationScheme import net.corda.client.rpc.internal.RPCClient import net.corda.client.rpc.internal.RPCClientConfiguration +import net.corda.core.context.Actor +import net.corda.core.context.Trace import net.corda.core.messaging.CordaRPCOps import net.corda.core.serialization.internal.effectiveSerializationEnv import net.corda.core.utilities.NetworkHostAndPort @@ -99,7 +101,22 @@ class CordaRPCClient @JvmOverloads constructor( * @throws RPCException if the server version is too low or if the server isn't reachable within a reasonable timeout. */ fun start(username: String, password: String): CordaRPCConnection { - return CordaRPCConnection(rpcClient.start(CordaRPCOps::class.java, username, password)) + return start(username, password, null, null) + } + + /** + * Logs in to the target server and returns an active connection. The returned connection is a [java.io.Closeable] + * and can be used with a try-with-resources statement. If you don't use that, you should use the + * [RPCConnection.notifyServerAndClose] or [RPCConnection.forceClose] methods to dispose of the connection object + * when done. + * + * @param username The username to authenticate with. + * @param password The password to authenticate with. + * @param externalTrace external [Trace] for correlation. + * @throws RPCException if the server version is too low or if the server isn't reachable within a reasonable timeout. + */ + fun start(username: String, password: String, externalTrace: Trace?, impersonatedActor: Actor?): CordaRPCConnection { + return CordaRPCConnection(rpcClient.start(CordaRPCOps::class.java, username, password, externalTrace, impersonatedActor)) } /** 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 9f925e540c..12fcb8bcd7 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 @@ -2,6 +2,8 @@ package net.corda.client.rpc.internal import net.corda.client.rpc.RPCConnection import net.corda.client.rpc.RPCException +import net.corda.core.context.Actor +import net.corda.core.context.Trace import net.corda.core.crypto.random63BitValue import net.corda.core.internal.logElapsedTime import net.corda.core.internal.uncheckedCast @@ -100,7 +102,9 @@ class RPCClient( fun start( rpcOpsClass: Class, username: String, - password: String + password: String, + externalTrace: Trace? = null, + impersonatedActor: Actor? = null ): RPCConnection { return log.logElapsedTime("Startup") { val clientAddress = SimpleString("${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.$username.${random63BitValue()}") @@ -113,8 +117,8 @@ class RPCClient( minLargeMessageSize = rpcConfiguration.maxFileSize isUseGlobalPools = nodeSerializationEnv != null } - - val proxyHandler = RPCClientProxyHandler(rpcConfiguration, username, password, serverLocator, clientAddress, rpcOpsClass, serializationContext) + val sessionId = Trace.SessionId.newInstance() + val proxyHandler = RPCClientProxyHandler(rpcConfiguration, username, password, serverLocator, clientAddress, rpcOpsClass, serializationContext, sessionId, externalTrace, impersonatedActor) try { proxyHandler.start() val ops: I = uncheckedCast(Proxy.newProxyInstance(rpcOpsClass.classLoader, arrayOf(rpcOpsClass), proxyHandler)) 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 0a89aecc92..e3e2e07d84 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 @@ -12,7 +12,9 @@ import com.google.common.util.concurrent.SettableFuture import com.google.common.util.concurrent.ThreadFactoryBuilder import net.corda.client.rpc.RPCException import net.corda.client.rpc.RPCSinceVersion -import net.corda.core.crypto.random63BitValue +import net.corda.core.context.Actor +import net.corda.core.context.Trace +import net.corda.core.context.Trace.InvocationId import net.corda.core.internal.LazyPool import net.corda.core.internal.LazyStickyPool import net.corda.core.internal.LifeCycle @@ -24,7 +26,9 @@ 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.nodeapi.* +import net.corda.nodeapi.ArtemisConsumer +import net.corda.nodeapi.ArtemisProducer +import net.corda.nodeapi.RPCApi import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration import org.apache.activemq.artemis.api.core.SimpleString import org.apache.activemq.artemis.api.core.client.ActiveMQClient.DEFAULT_ACK_BATCH_SIZE @@ -35,6 +39,7 @@ import rx.Observable import rx.subjects.UnicastSubject import java.lang.reflect.InvocationHandler import java.lang.reflect.Method +import java.time.Instant import java.util.* import java.util.concurrent.* import java.util.concurrent.atomic.AtomicInteger @@ -70,7 +75,10 @@ class RPCClientProxyHandler( private val serverLocator: ServerLocator, private val clientAddress: SimpleString, private val rpcOpsClass: Class, - serializationContext: SerializationContext + serializationContext: SerializationContext, + private val sessionId: Trace.SessionId, + private val externalTrace: Trace?, + private val impersonatedActor: Actor? ) : InvocationHandler { private enum class State { @@ -127,14 +135,14 @@ class RPCClientProxyHandler( // Stores the Observable IDs that are already removed from the map but are not yet sent to the server. private val observablesToReap = ThreadBox(object { - var observables = ArrayList() + var observables = ArrayList() }) private val serializationContextWithObservableContext = RpcClientObservableSerializer.createContext(serializationContext, observableContext) private fun createRpcObservableMap(): RpcObservableMap { - val onObservableRemove = RemovalListener>> { + val onObservableRemove = RemovalListener>> { val observableId = it.key!! - val rpcCallSite = callSiteMap?.remove(observableId.toLong) + val rpcCallSite = callSiteMap?.remove(observableId) if (it.cause == RemovalCause.COLLECTED) { log.warn(listOf( "A hot observable returned from an RPC was never subscribed to.", @@ -207,11 +215,12 @@ class RPCClientProxyHandler( if (sessionAndConsumer!!.session.isClosed) { throw RPCException("RPC Proxy is closed") } - val rpcId = RPCApi.RpcRequestId(random63BitValue()) - callSiteMap?.set(rpcId.toLong, Throwable("")) + + val replyId = InvocationId.newInstance() + callSiteMap?.set(replyId, Throwable("")) try { val serialisedArguments = (arguments?.toList() ?: emptyList()).serialize(context = serializationContextWithObservableContext) - val request = RPCApi.ClientToServer.RpcRequest(clientAddress, rpcId, method.name, serialisedArguments.bytes) + val request = RPCApi.ClientToServer.RpcRequest(clientAddress, method.name, serialisedArguments.bytes, replyId, sessionId, externalTrace, impersonatedActor) val replyFuture = SettableFuture.create() sessionAndProducerPool.run { val message = it.session.createMessage(false) @@ -219,11 +228,11 @@ class RPCClientProxyHandler( log.debug { val argumentsString = arguments?.joinToString() ?: "" - "-> RPC($rpcId) -> ${method.name}($argumentsString): ${method.returnType}" + "-> RPC(${replyId.value}) -> ${method.name}($argumentsString): ${method.returnType}" } - require(rpcReplyMap.put(rpcId, replyFuture) == null) { - "Generated several RPC requests with same ID $rpcId" + require(rpcReplyMap.put(replyId, replyFuture) == null) { + "Generated several RPC requests with same ID $replyId" } it.producer.send(message) it.session.commit() @@ -236,7 +245,7 @@ class RPCClientProxyHandler( // This must be a checked exception, so wrap it throw RPCException(e.message ?: "", e) } finally { - callSiteMap?.remove(rpcId.toLong) + callSiteMap?.remove(replyId) } } @@ -254,7 +263,7 @@ class RPCClientProxyHandler( when (result) { is Try.Success -> replyFuture.set(result.value) is Try.Failure -> { - val rpcCallSite = callSiteMap?.get(serverToClient.id.toLong) + val rpcCallSite = callSiteMap?.get(serverToClient.id) if (rpcCallSite != null) addRpcCallSiteToThrowable(result.exception, rpcCallSite) replyFuture.setException(result.exception) } @@ -277,7 +286,7 @@ class RPCClientProxyHandler( } // Add call site information on error if (content.isOnError) { - val rpcCallSite = callSiteMap?.get(serverToClient.id.toLong) + val rpcCallSite = callSiteMap?.get(serverToClient.id) if (rpcCallSite != null) addRpcCallSiteToThrowable(content.throwable, rpcCallSite) } observable.onNext(content) @@ -385,9 +394,9 @@ class RPCClientProxyHandler( } } -private typealias RpcObservableMap = Cache>> -private typealias RpcReplyMap = ConcurrentHashMap> -private typealias CallSiteMap = ConcurrentHashMap +private typealias RpcObservableMap = Cache>> +private typealias RpcReplyMap = ConcurrentHashMap> +private typealias CallSiteMap = ConcurrentHashMap /** * Holds a context available during Kryo deserialisation of messages that are expected to contain Observables. @@ -426,14 +435,14 @@ object RpcClientObservableSerializer : Serializer>() { override fun read(kryo: Kryo, input: Input, type: Class>): Observable { val observableContext = kryo.context[RpcObservableContextKey] as ObservableContext - val observableId = RPCApi.ObservableId(input.readLong(true)) + val observableId = input.readInvocationId() ?: throw IllegalStateException("Unable to read invocationId from Input.") val observable = UnicastSubject.create>() require(observableContext.observableMap.getIfPresent(observableId) == null) { "Multiple Observables arrived with the same ID $observableId" } val rpcCallSite = getRpcCallSite(kryo, observableContext) observableContext.observableMap.put(observableId, observable) - observableContext.callSiteMap?.put(observableId.toLong, rpcCallSite) + observableContext.callSiteMap?.put(observableId, rpcCallSite) // We pin all Observables into a hard reference store (rooted in the RPC proxy) on subscription so that users // don't need to store a reference to the Observables themselves. return pinInSubscriptions(observable, observableContext.hardReferenceStore).doOnUnsubscribe { @@ -444,12 +453,19 @@ object RpcClientObservableSerializer : Serializer>() { }.dematerialize() } + private fun Input.readInvocationId() : InvocationId? { + + val value = readString() ?: return null + val timestamp = readLong() + return InvocationId(value, Instant.ofEpochMilli(timestamp)) + } + override fun write(kryo: Kryo, output: Output, observable: Observable<*>) { throw UnsupportedOperationException("Cannot serialise Observables on the client side") } private fun getRpcCallSite(kryo: Kryo, observableContext: ObservableContext): Throwable? { - val rpcRequestOrObservableId = kryo.context[RPCApi.RpcRequestOrObservableIdKey] as Long + val rpcRequestOrObservableId = kryo.context[RPCApi.RpcRequestOrObservableIdKey] as InvocationId return observableContext.callSiteMap?.get(rpcRequestOrObservableId) } } \ No newline at end of file diff --git a/client/rpc/src/test/kotlin/net/corda/client/rpc/ClientRPCInfrastructureTests.kt b/client/rpc/src/test/kotlin/net/corda/client/rpc/ClientRPCInfrastructureTests.kt index 4b6c94e6f4..a5bf6c9b7d 100644 --- a/client/rpc/src/test/kotlin/net/corda/client/rpc/ClientRPCInfrastructureTests.kt +++ b/client/rpc/src/test/kotlin/net/corda/client/rpc/ClientRPCInfrastructureTests.kt @@ -57,6 +57,7 @@ class ClientRPCInfrastructureTests : AbstractRPCTest() { inner class TestOpsImpl : TestOps { override val protocolVersion = 1 + // do not remove Unit override fun barf(): Unit = throw IllegalArgumentException("Barf!") override fun void() {} override fun someCalculation(str: String, num: Int) = "$str $num" @@ -64,8 +65,9 @@ class ClientRPCInfrastructureTests : AbstractRPCTest() { override fun makeListenableFuture() = doneFuture(1) override fun makeComplicatedObservable() = complicatedObservable override fun makeComplicatedListenableFuture() = complicatedListenableFuturee + // do not remove Unit override fun addedLater(): Unit = throw IllegalStateException() - override fun captureUser(): String = rpcContext().currentUser.username + override fun captureUser(): String = rpcContext().invocation.principal().name } @Test diff --git a/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPermissionsTests.kt b/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPermissionsTests.kt index 597061aff1..82f5c53afc 100644 --- a/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPermissionsTests.kt +++ b/client/rpc/src/test/kotlin/net/corda/client/rpc/RPCPermissionsTests.kt @@ -4,7 +4,6 @@ import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.RPCOps import net.corda.node.services.Permissions.Companion.invokeRpc import net.corda.node.services.messaging.rpcContext -import net.corda.node.services.messaging.requirePermission import net.corda.nodeapi.User import net.corda.testing.internal.RPCDriverExposedDSLInterface import net.corda.testing.internal.rpcDriver diff --git a/confidential-identities/src/test/kotlin/net/corda/confidential/IdentitySyncFlowTests.kt b/confidential-identities/src/test/kotlin/net/corda/confidential/IdentitySyncFlowTests.kt index 798a954708..7d05154ace 100644 --- a/confidential-identities/src/test/kotlin/net/corda/confidential/IdentitySyncFlowTests.kt +++ b/confidential-identities/src/test/kotlin/net/corda/confidential/IdentitySyncFlowTests.kt @@ -14,11 +14,8 @@ import net.corda.finance.DOLLARS import net.corda.finance.contracts.asset.Cash import net.corda.finance.flows.CashIssueAndPaymentFlow import net.corda.finance.flows.CashPaymentFlow -import net.corda.testing.ALICE_NAME -import net.corda.testing.BOB_NAME -import net.corda.testing.CHARLIE_NAME +import net.corda.testing.* import net.corda.testing.node.MockNetwork -import net.corda.testing.singleIdentity import org.junit.After import org.junit.Before import org.junit.Test diff --git a/config/dev/log4j2.xml b/config/dev/log4j2.xml index c0cb385396..0ad0431e4d 100644 --- a/config/dev/log4j2.xml +++ b/config/dev/log4j2.xml @@ -13,7 +13,7 @@ - + @@ -27,7 +27,7 @@ fileName="${sys:log-path}/${log-name}.log" filePattern="${archive}/${log-name}.%date{yyyy-MM-dd}-%i.log.gz"> - + diff --git a/core/src/main/kotlin/net/corda/core/context/AuthServiceId.kt b/core/src/main/kotlin/net/corda/core/context/AuthServiceId.kt new file mode 100644 index 0000000000..9edc2680ca --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/context/AuthServiceId.kt @@ -0,0 +1,9 @@ +package net.corda.core.context + +import net.corda.core.serialization.CordaSerializable + +/** + * Authentication / Authorisation Service ID. + */ +@CordaSerializable +data class AuthServiceId(val value: String) \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/context/InvocationContext.kt b/core/src/main/kotlin/net/corda/core/context/InvocationContext.kt new file mode 100644 index 0000000000..9ca93033c0 --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/context/InvocationContext.kt @@ -0,0 +1,136 @@ +package net.corda.core.context + +import net.corda.core.contracts.ScheduledStateRef +import net.corda.core.identity.CordaX500Name +import net.corda.core.serialization.CordaSerializable +import java.security.Principal + +/** + * Models the information needed to trace an invocation in Corda. + * Includes initiating actor, origin, trace information, and optional external trace information to correlate clients' IDs. + * + * @param origin origin of the invocation. + * @param trace Corda invocation trace. + * @param actor acting agent of the invocation, used to derive the security principal. + * @param externalTrace optional external invocation trace for cross-system logs correlation. + * @param impersonatedActor optional impersonated actor, used for logging but not for authorisation. + */ +@CordaSerializable +data class InvocationContext(val origin: Origin, val trace: Trace, val actor: Actor?, val externalTrace: Trace? = null, val impersonatedActor: Actor? = null) { + + companion object { + + /** + * Creates an [InvocationContext] with a [Trace] that defaults to a [java.util.UUID] as value and [java.time.Instant.now] timestamp. + */ + @JvmStatic + fun newInstance(origin: Origin, trace: Trace = Trace.newInstance(), actor: Actor? = null, externalTrace: Trace? = null, impersonatedActor: Actor? = null) = InvocationContext(origin, trace, actor, externalTrace, impersonatedActor) + + /** + * Creates an [InvocationContext] with [Origin.RPC] origin. + */ + @JvmStatic + fun rpc(actor: Actor, trace: Trace = Trace.newInstance(), externalTrace: Trace? = null, impersonatedActor: Actor? = null): InvocationContext = newInstance(Origin.RPC(actor), trace, actor, externalTrace, impersonatedActor) + + /** + * Creates an [InvocationContext] with [Origin.Peer] origin. + */ + @JvmStatic + fun peer(party: CordaX500Name, trace: Trace = Trace.newInstance(), externalTrace: Trace? = null, impersonatedActor: Actor? = null): InvocationContext = newInstance(Origin.Peer(party), trace, null, externalTrace, impersonatedActor) + + /** + * Creates an [InvocationContext] with [Origin.Service] origin. + */ + @JvmStatic + fun service(serviceClassName: String, owningLegalIdentity: CordaX500Name, trace: Trace = Trace.newInstance(), externalTrace: Trace? = null): InvocationContext = newInstance(Origin.Service(serviceClassName, owningLegalIdentity), trace, null, externalTrace) + + /** + * Creates an [InvocationContext] with [Origin.Scheduled] origin. + */ + @JvmStatic + fun scheduled(scheduledState: ScheduledStateRef, trace: Trace = Trace.newInstance(), externalTrace: Trace? = null): InvocationContext = newInstance(Origin.Scheduled(scheduledState), trace, null, externalTrace) + + /** + * Creates an [InvocationContext] with [Origin.Shell] origin. + */ + @JvmStatic + fun shell(trace: Trace = Trace.newInstance(), externalTrace: Trace? = null): InvocationContext = InvocationContext(Origin.Shell, trace, null, externalTrace) + } + + /** + * Associated security principal. + */ + fun principal(): Principal = origin.principal() +} + +/** + * Models an initiator in Corda, can be a user, a service, etc. + */ +@CordaSerializable +data class Actor(val id: Id, val serviceId: AuthServiceId, val owningLegalIdentity: CordaX500Name) { + + companion object { + @JvmStatic + fun service(serviceClassName: String, owningLegalIdentity: CordaX500Name): Actor = Actor(Id(serviceClassName), AuthServiceId("SERVICE"), owningLegalIdentity) + } + + /** + * Actor id. + */ + @CordaSerializable + data class Id(val value: String) + +} + +/** + * Invocation origin for tracing purposes. + */ +@CordaSerializable +sealed class Origin { + + /** + * Returns the [Principal] for a given [Actor]. + */ + abstract fun principal(): Principal + + /** + * Origin was an RPC call. + */ + data class RPC(private val actor: Actor) : Origin() { + + override fun principal() = Principal { actor.id.value } + } + + /** + * Origin was a message sent by a [Peer]. + */ + data class Peer(val party: CordaX500Name) : Origin() { + + override fun principal() = Principal { party.toString() } + } + + /** + * Origin was a Corda Service. + */ + data class Service(val serviceClassName: String, val owningLegalIdentity: CordaX500Name) : Origin() { + + override fun principal() = Principal { serviceClassName } + } + + /** + * Origin was a scheduled activity. + */ + data class Scheduled(val scheduledState: ScheduledStateRef) : Origin() { + + override fun principal() = Principal { "Scheduler" } + } + + // TODO When proper ssh access enabled, add username/use RPC? + /** + * Origin was the Shell. + */ + object Shell : Origin() { + + override fun principal() = Principal { "Shell User" } + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/context/Trace.kt b/core/src/main/kotlin/net/corda/core/context/Trace.kt new file mode 100644 index 0000000000..6b9703c545 --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/context/Trace.kt @@ -0,0 +1,56 @@ +package net.corda.core.context + +import net.corda.core.serialization.CordaSerializable +import net.corda.core.utilities.Id +import net.corda.core.utilities.UuidGenerator +import java.time.Instant + +/** + * Contextual tracing information, including invocation and session id. + */ +@CordaSerializable +data class Trace(val invocationId: InvocationId, val sessionId: SessionId) { + + companion object { + + /** + * Creates a trace using a [InvocationId.newInstance] with default arguments and a [SessionId] matching the value and timestamp from the invocation id.. + */ + @JvmStatic + fun newInstance(invocationId: InvocationId = InvocationId.newInstance(), sessionId: SessionId = SessionId(invocationId.value, invocationId.timestamp)) = Trace(invocationId, sessionId) + } + + /** + * Represents id and timestamp of an invocation. + */ + @CordaSerializable + class InvocationId(value: String, timestamp: Instant) : Id(value, TYPE, timestamp) { + + companion object { + private val TYPE = "Invocation" + + /** + * Creates an invocation id using a [java.util.UUID] as value and [Instant.now] as timestamp. + */ + @JvmStatic + fun newInstance(value: String = UuidGenerator.next().toString(), timestamp: Instant = Instant.now()) = InvocationId(value, timestamp) + } + } + + /** + * Represents id and timestamp of a session. + */ + @CordaSerializable + class SessionId(value: String, timestamp: Instant) : Id(value, TYPE, timestamp) { + + companion object { + private val TYPE = "Session" + + /** + * Creates a session id using a [java.util.UUID] as value and [Instant.now] as timestamp. + */ + @JvmStatic + fun newInstance(value: String = UuidGenerator.next().toString(), timestamp: Instant = Instant.now()) = SessionId(value, timestamp) + } + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/contracts/Structures.kt b/core/src/main/kotlin/net/corda/core/contracts/Structures.kt index e509b7da14..d0359f0ded 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/Structures.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/Structures.kt @@ -86,6 +86,7 @@ interface Scheduled { * This is effectively the input to a scheduler, which wakes up at that point in time and asks the contract state what * lifecycle processing needs to take place. e.g. a fixing or a late payment etc. */ +@CordaSerializable data class ScheduledStateRef(val ref: StateRef, override val scheduledAt: Instant) : Scheduled /** diff --git a/core/src/main/kotlin/net/corda/core/flows/FlowInitiator.kt b/core/src/main/kotlin/net/corda/core/flows/FlowInitiator.kt index 6bc488c162..8623169da8 100644 --- a/core/src/main/kotlin/net/corda/core/flows/FlowInitiator.kt +++ b/core/src/main/kotlin/net/corda/core/flows/FlowInitiator.kt @@ -10,29 +10,35 @@ import java.security.Principal * communication started by peer node [FlowInitiator.Peer], scheduled flows [FlowInitiator.Scheduled] * or via the Corda Shell [FlowInitiator.Shell]. */ +@Deprecated("Do not use these types. Future releases might remove them.") @CordaSerializable sealed class FlowInitiator : Principal { /** Started using [net.corda.core.messaging.CordaRPCOps.startFlowDynamic]. */ + @Deprecated("Do not use this type. Future releases might remove it.") data class RPC(val username: String) : FlowInitiator() { override fun getName(): String = username } /** Started when we get new session initiation request. */ + @Deprecated("Do not use this type. Future releases might remove it.") data class Peer(val party: Party) : FlowInitiator() { override fun getName(): String = party.name.toString() } /** Started by a CordaService. */ + @Deprecated("Do not use this type. Future releases might remove it.") data class Service(val serviceClassName: String) : FlowInitiator() { override fun getName(): String = serviceClassName } /** Started as scheduled activity. */ + @Deprecated("Do not use this type. Future releases might remove it.") data class Scheduled(val scheduledState: ScheduledStateRef) : FlowInitiator() { override fun getName(): String = "Scheduler" } // TODO When proper ssh access enabled, add username/use RPC? + @Deprecated("Do not use this type. Future releases might remove it.") object Shell : FlowInitiator() { override fun getName(): String = "Shell User" } diff --git a/core/src/main/kotlin/net/corda/core/internal/FlowStateMachine.kt b/core/src/main/kotlin/net/corda/core/internal/FlowStateMachine.kt index 5e4f3e490a..3d9af23d71 100644 --- a/core/src/main/kotlin/net/corda/core/internal/FlowStateMachine.kt +++ b/core/src/main/kotlin/net/corda/core/internal/FlowStateMachine.kt @@ -6,6 +6,7 @@ import net.corda.core.crypto.SecureHash import net.corda.core.flows.* import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate +import net.corda.core.context.InvocationContext import net.corda.core.node.ServiceHub import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.UntrustworthyData @@ -55,7 +56,7 @@ interface FlowStateMachine { val logger: Logger val id: StateMachineRunId val resultFuture: CordaFuture - val flowInitiator: FlowInitiator + val context: InvocationContext val ourIdentityAndCert: PartyAndCertificate @Suspendable 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 6b3074564d..8859d94ec8 100644 --- a/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt +++ b/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt @@ -1,6 +1,10 @@ package net.corda.core.messaging import net.corda.core.concurrent.CordaFuture +import net.corda.core.context.Actor +import net.corda.core.context.AuthServiceId +import net.corda.core.context.InvocationContext +import net.corda.core.context.Origin import net.corda.core.contracts.ContractState import net.corda.core.crypto.SecureHash import net.corda.core.flows.FlowInitiator @@ -23,13 +27,41 @@ import java.io.InputStream import java.security.PublicKey import java.time.Instant +private val unknownName = CordaX500Name("UNKNOWN", "UNKNOWN", "GB") + @CordaSerializable -data class StateMachineInfo( +data class StateMachineInfo @JvmOverloads constructor( val id: StateMachineRunId, val flowLogicClassName: String, val initiator: FlowInitiator, - val progressTrackerStepAndUpdates: DataFeed? + val progressTrackerStepAndUpdates: DataFeed?, + val context: InvocationContext? = null ) { + fun context(): InvocationContext = context ?: contextFrom(initiator) + + private fun contextFrom(initiator: FlowInitiator): InvocationContext { + var actor: Actor? = null + val origin: Origin + when (initiator) { + is FlowInitiator.RPC -> { + actor = Actor(Actor.Id(initiator.username), AuthServiceId("UNKNOWN"), unknownName) + origin = Origin.RPC(actor) + } + is FlowInitiator.Peer -> origin = Origin.Peer(initiator.party.name) + is FlowInitiator.Service -> origin = Origin.Service(initiator.serviceClassName, unknownName) + is FlowInitiator.Shell -> origin = Origin.Shell + is FlowInitiator.Scheduled -> origin = Origin.Scheduled(initiator.scheduledState) + } + return InvocationContext.newInstance(origin = origin, actor = actor) + } + + fun copy(id: StateMachineRunId = this.id, + flowLogicClassName: String = this.flowLogicClassName, + initiator: FlowInitiator = this.initiator, + progressTrackerStepAndUpdates: DataFeed? = this.progressTrackerStepAndUpdates): StateMachineInfo { + return copy(id = id, flowLogicClassName = flowLogicClassName, initiator = initiator, progressTrackerStepAndUpdates = progressTrackerStepAndUpdates, context = context) + } + override fun toString(): String = "${javaClass.simpleName}($id, $flowLogicClassName)" } @@ -221,7 +253,7 @@ interface CordaRPCOps : RPCOps { fun uploadAttachment(jar: InputStream): SecureHash /** Uploads a jar including metadata to the node, returns it's hash. */ - fun uploadAttachmentWithMetadata(jar: InputStream, uploader:String, filename:String): SecureHash + fun uploadAttachmentWithMetadata(jar: InputStream, uploader: String, filename: String): SecureHash /** Queries attachments metadata */ fun queryAttachments(query: AttachmentQueryCriteria, sorting: AttachmentSort?): List diff --git a/core/src/main/kotlin/net/corda/core/utilities/Id.kt b/core/src/main/kotlin/net/corda/core/utilities/Id.kt new file mode 100644 index 0000000000..5020a08700 --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/utilities/Id.kt @@ -0,0 +1,46 @@ +package net.corda.core.utilities + +import java.time.Instant +import java.time.Instant.now + +/** + * Represents a unique, timestamped id. + * @param value unique value of the id. + * @param entityType optional id entity type. + * @param timestamp timestamp for the id. + */ +open class Id(val value: VALUE, val entityType: String?, val timestamp: Instant) { + + companion object { + /** + * Creates an id using [Instant.now] as timestamp. + */ + @JvmStatic + fun newInstance(value: V, entityType: String? = null, timestamp: Instant = now()) = Id(value, entityType, timestamp) + } + + final override fun equals(other: Any?): Boolean { + + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Id<*> + + if (value != other.value) return false + if (entityType != other.entityType) return false + + return true + } + + final override fun hashCode(): Int { + + var result = value.hashCode() + result = 31 * result + (entityType?.hashCode() ?: 0) + return result + } + + final override fun toString(): String { + + return "$value, timestamp: $timestamp" + (entityType?.let { ", entityType: $it" } ?: "") + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/utilities/UuidGenerator.kt b/core/src/main/kotlin/net/corda/core/utilities/UuidGenerator.kt new file mode 100644 index 0000000000..b56c693aec --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/utilities/UuidGenerator.kt @@ -0,0 +1,10 @@ +package net.corda.core.utilities + +import java.util.* + +class UuidGenerator { + + companion object { + fun next() : UUID = UUID.randomUUID() + } +} \ No newline at end of file diff --git a/core/src/test/java/net/corda/core/flows/FlowsInJavaTest.java b/core/src/test/java/net/corda/core/flows/FlowsInJavaTest.java index fe07e87496..8393c915e5 100644 --- a/core/src/test/java/net/corda/core/flows/FlowsInJavaTest.java +++ b/core/src/test/java/net/corda/core/flows/FlowsInJavaTest.java @@ -16,6 +16,7 @@ import java.util.concurrent.Future; import static net.corda.testing.CoreTestUtils.chooseIdentity; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.junit.Assert.fail; +import static net.corda.testing.NodeTestUtils.startFlow; public class FlowsInJavaTest { private final MockNetwork mockNet = new MockNetwork(); @@ -36,7 +37,7 @@ public class FlowsInJavaTest { @Test public void suspendableActionInsideUnwrap() throws Exception { bobNode.getInternals().registerInitiatedFlow(SendHelloAndThenReceive.class); - Future result = aliceNode.getServices().startFlow(new SendInUnwrapFlow(chooseIdentity(bobNode.getInfo()))).getResultFuture(); + Future result = startFlow(aliceNode.getServices(), new SendInUnwrapFlow(chooseIdentity(bobNode.getInfo()))).getResultFuture(); mockNet.runNetwork(); assertThat(result.get()).isEqualTo("Hello"); } @@ -52,7 +53,7 @@ public class FlowsInJavaTest { private void primitiveReceiveTypeTest(Class receiveType) throws InterruptedException { PrimitiveReceiveFlow flow = new PrimitiveReceiveFlow(chooseIdentity(bobNode.getInfo()), receiveType); - Future result = aliceNode.getServices().startFlow(flow).getResultFuture(); + Future result = startFlow(aliceNode.getServices(), flow).getResultFuture(); mockNet.runNetwork(); try { result.get(); diff --git a/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt b/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt index bcd4ad8b94..147e135d4f 100644 --- a/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt @@ -10,12 +10,9 @@ import net.corda.core.internal.FetchDataFlow import net.corda.core.utilities.getOrThrow import net.corda.node.internal.StartedNode import net.corda.node.services.persistence.NodeAttachmentService -import net.corda.testing.ALICE -import net.corda.testing.ALICE_NAME -import net.corda.testing.BOB +import net.corda.testing.* import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNodeParameters -import net.corda.testing.singleIdentity import org.junit.After import org.junit.Before import org.junit.Test diff --git a/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt b/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt index 84ef817619..07ac06b4cd 100644 --- a/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt @@ -7,11 +7,8 @@ import net.corda.finance.POUNDS import net.corda.finance.contracts.asset.Cash import net.corda.finance.issuedBy import net.corda.node.services.api.StartedNodeServices -import net.corda.testing.ALICE_NAME -import net.corda.testing.BOB_NAME -import net.corda.testing.CHARLIE +import net.corda.testing.* import net.corda.testing.node.MockNetwork -import net.corda.testing.singleIdentity import org.junit.After import org.junit.Before import org.junit.Test 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 9b1ee8161c..c5a9e0a3e0 100644 --- a/core/src/test/kotlin/net/corda/core/flows/ReceiveAllFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/ReceiveAllFlowTests.kt @@ -7,6 +7,7 @@ import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.unwrap import net.corda.testing.chooseIdentity import net.corda.testing.node.network +import net.corda.testing.startFlow import org.assertj.core.api.Assertions.assertThat import org.junit.Test diff --git a/core/src/test/kotlin/net/corda/core/internal/ResolveTransactionsFlowTest.kt b/core/src/test/kotlin/net/corda/core/internal/ResolveTransactionsFlowTest.kt index b5955c24be..2473c58a0e 100644 --- a/core/src/test/kotlin/net/corda/core/internal/ResolveTransactionsFlowTest.kt +++ b/core/src/test/kotlin/net/corda/core/internal/ResolveTransactionsFlowTest.kt @@ -13,6 +13,7 @@ import net.corda.testing.MINI_CORP import net.corda.testing.chooseIdentity import net.corda.testing.contracts.DummyContract import net.corda.testing.node.MockNetwork +import net.corda.testing.startFlow import org.junit.After import org.junit.Before import org.junit.Test diff --git a/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt b/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt index c10b345613..00f176f268 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt @@ -18,6 +18,7 @@ import net.corda.node.utilities.currentDBSession import net.corda.testing.chooseIdentity import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNodeParameters +import net.corda.testing.startFlow import org.junit.After import org.junit.Before import org.junit.Test diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/mocknetwork/TutorialMockNetwork.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/mocknetwork/TutorialMockNetwork.kt index a787ccdd85..ba8802dceb 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/mocknetwork/TutorialMockNetwork.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/mocknetwork/TutorialMockNetwork.kt @@ -19,6 +19,7 @@ import net.corda.testing.node.InMemoryMessagingNetwork import net.corda.testing.node.MessagingServiceSpy import net.corda.testing.node.MockNetwork import net.corda.testing.node.setMessagingServiceSpy +import net.corda.testing.startFlow import org.junit.After import org.junit.Before import org.junit.Rule diff --git a/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt b/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt index 795af0f758..9c850288ed 100644 --- a/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt +++ b/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt @@ -10,6 +10,7 @@ import net.corda.finance.flows.CashIssueFlow import net.corda.node.internal.StartedNode import net.corda.testing.chooseIdentity import net.corda.testing.node.MockNetwork +import net.corda.testing.startFlow import org.junit.After import org.junit.Assert import org.junit.Before diff --git a/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt b/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt index 7ba382623b..7fa6292568 100644 --- a/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt +++ b/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt @@ -10,6 +10,7 @@ import net.corda.finance.flows.CashIssueFlow import net.corda.node.internal.StartedNode import net.corda.testing.chooseIdentity import net.corda.testing.node.MockNetwork +import net.corda.testing.startFlow import org.junit.After import org.junit.Before import org.junit.Test diff --git a/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt b/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt index 1cbd83a282..45553176eb 100644 --- a/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt +++ b/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt @@ -10,8 +10,7 @@ import net.corda.core.node.services.vault.QueryCriteria import net.corda.core.toFuture import net.corda.core.utilities.getOrThrow import net.corda.node.services.api.StartedNodeServices -import net.corda.testing.ALICE_NAME -import net.corda.testing.BOB_NAME +import net.corda.testing.* import net.corda.testing.node.MockNetwork import org.junit.After import org.junit.Before diff --git a/finance/src/main/kotlin/net/corda/finance/flows/CashConfigDataFlow.kt b/finance/src/main/kotlin/net/corda/finance/flows/CashConfigDataFlow.kt index c202d5633c..24735d8505 100644 --- a/finance/src/main/kotlin/net/corda/finance/flows/CashConfigDataFlow.kt +++ b/finance/src/main/kotlin/net/corda/finance/flows/CashConfigDataFlow.kt @@ -33,7 +33,8 @@ class ConfigHolder(services: AppServiceHub) : SingletonSerializeAsToken() { // Warning!! You are about to see a major hack! val baseDirectory = services.declaredField("serviceHub").value .let { it.javaClass.getMethod("getConfiguration").apply { isAccessible = true }.invoke(it) } - .declaredField("baseDirectory").value + .let { it.javaClass.getMethod("getBaseDirectory").apply { isAccessible = true }.invoke(it)} + .let { it.javaClass.getMethod("toString").apply { isAccessible = true }.invoke(it) as String } val config = (baseDirectory / "node.conf").read { ConfigFactory.parseReader(it.reader()) } if (config.hasPath("issuableCurrencies")) { issuableCurrencies = config.getStringList("issuableCurrencies").map { Currency.getInstance(it) } diff --git a/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashSelectionH2Test.kt b/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashSelectionH2Test.kt index 5dcde25664..4f4a39abec 100644 --- a/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashSelectionH2Test.kt +++ b/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashSelectionH2Test.kt @@ -6,6 +6,7 @@ import net.corda.finance.flows.CashException import net.corda.finance.flows.CashPaymentFlow import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNodeParameters +import net.corda.testing.startFlow import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.After import org.junit.Test diff --git a/finance/src/test/kotlin/net/corda/finance/flows/CashExitFlowTests.kt b/finance/src/test/kotlin/net/corda/finance/flows/CashExitFlowTests.kt index 4f7b914a9a..d6bbaa30ac 100644 --- a/finance/src/test/kotlin/net/corda/finance/flows/CashExitFlowTests.kt +++ b/finance/src/test/kotlin/net/corda/finance/flows/CashExitFlowTests.kt @@ -12,6 +12,7 @@ import net.corda.testing.chooseIdentity import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNetwork.MockNode +import net.corda.testing.startFlow import org.junit.After import org.junit.Before import org.junit.Test diff --git a/finance/src/test/kotlin/net/corda/finance/flows/CashIssueFlowTests.kt b/finance/src/test/kotlin/net/corda/finance/flows/CashIssueFlowTests.kt index 15d2645190..c4ea887d0c 100644 --- a/finance/src/test/kotlin/net/corda/finance/flows/CashIssueFlowTests.kt +++ b/finance/src/test/kotlin/net/corda/finance/flows/CashIssueFlowTests.kt @@ -12,6 +12,7 @@ import net.corda.testing.chooseIdentity import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNetwork.MockNode +import net.corda.testing.startFlow import org.junit.After import org.junit.Before import org.junit.Test diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/RPCApi.kt b/node-api/src/main/kotlin/net/corda/nodeapi/RPCApi.kt index aa062e3195..96bd8c0560 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/RPCApi.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/RPCApi.kt @@ -1,15 +1,24 @@ package net.corda.nodeapi +import net.corda.core.context.Actor +import net.corda.core.context.AuthServiceId +import net.corda.core.context.Trace +import net.corda.core.context.Trace.InvocationId +import net.corda.core.context.Trace.SessionId +import net.corda.core.identity.CordaX500Name import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize +import net.corda.core.utilities.Id import net.corda.core.utilities.Try +import org.apache.activemq.artemis.api.core.ActiveMQBuffer import org.apache.activemq.artemis.api.core.SimpleString import org.apache.activemq.artemis.api.core.client.* import org.apache.activemq.artemis.api.core.management.CoreNotificationType import org.apache.activemq.artemis.api.core.management.ManagementHelper import org.apache.activemq.artemis.reader.MessageUtil import rx.Notification +import java.time.Instant import java.util.* // The RPC protocol: @@ -51,10 +60,6 @@ import java.util.* * Constants and data types used by the RPC API. */ object RPCApi { - private val TAG_FIELD_NAME = "tag" - private val RPC_ID_FIELD_NAME = "rpc-id" - private val OBSERVABLE_ID_FIELD_NAME = "observable-id" - private val METHOD_NAME_FIELD_NAME = "method-name" /** Name of the Artemis queue on which the server receives RPC requests (as [ClientToServer.RpcRequest]). */ const val RPC_SERVER_QUEUE_NAME = "rpc.server" @@ -65,6 +70,7 @@ object RPCApi { const val RPC_CLIENT_QUEUE_NAME_PREFIX = "rpc.client" const val RPC_CLIENT_BINDING_REMOVALS = "rpc.clientqueueremovals" const val RPC_CLIENT_BINDING_ADDITIONS = "rpc.clientqueueadditions" + const val RPC_TARGET_LEGAL_IDENTITY = "rpc-target-legal-identity" val RPC_CLIENT_BINDING_REMOVAL_FILTER_EXPRESSION = "${ManagementHelper.HDR_NOTIFICATION_TYPE} = '${CoreNotificationType.BINDING_REMOVED.name}' AND " + @@ -73,9 +79,6 @@ object RPCApi { "${ManagementHelper.HDR_NOTIFICATION_TYPE} = '${CoreNotificationType.BINDING_ADDED.name}' AND " + "${ManagementHelper.HDR_ROUTING_NAME} LIKE '$RPC_CLIENT_QUEUE_NAME_PREFIX.%'" - data class RpcRequestId(val toLong: Long) - data class ObservableId(val toLong: Long) - object RpcRequestOrObservableIdKey private fun ClientMessage.getBodyAsByteArray(): ByteArray { @@ -101,28 +104,35 @@ object RPCApi { */ data class RpcRequest( val clientAddress: SimpleString, - val id: RpcRequestId, val methodName: String, - val serialisedArguments: ByteArray + val serialisedArguments: ByteArray, + val replyId: InvocationId, + val sessionId: SessionId, + val externalTrace: Trace? = null, + val impersonatedActor: Actor? = null ) : ClientToServer() { fun writeToClientMessage(message: ClientMessage) { MessageUtil.setJMSReplyTo(message, clientAddress) message.putIntProperty(TAG_FIELD_NAME, Tag.RPC_REQUEST.ordinal) - message.putLongProperty(RPC_ID_FIELD_NAME, id.toLong) + + replyId.mapTo(message) + sessionId.mapTo(message) + + externalTrace?.mapToExternal(message) + impersonatedActor?.mapToImpersonated(message) + message.putStringProperty(METHOD_NAME_FIELD_NAME, methodName) message.bodyBuffer.writeBytes(serialisedArguments) } } - data class ObservablesClosed( - val ids: List - ) : ClientToServer() { + data class ObservablesClosed(val ids: List) : ClientToServer() { fun writeToClientMessage(message: ClientMessage) { message.putIntProperty(TAG_FIELD_NAME, Tag.OBSERVABLES_CLOSED.ordinal) val buffer = message.bodyBuffer buffer.writeInt(ids.size) ids.forEach { - buffer.writeLong(it.toLong) + buffer.writeInvocationId(it) } } } @@ -133,16 +143,19 @@ object RPCApi { return when (tag) { RPCApi.ClientToServer.Tag.RPC_REQUEST -> RpcRequest( clientAddress = MessageUtil.getJMSReplyTo(message), - id = RpcRequestId(message.getLongProperty(RPC_ID_FIELD_NAME)), methodName = message.getStringProperty(METHOD_NAME_FIELD_NAME), - serialisedArguments = message.getBodyAsByteArray() + serialisedArguments = message.getBodyAsByteArray(), + replyId = message.replyId(), + sessionId = message.sessionId(), + externalTrace = message.externalTrace(), + impersonatedActor = message.impersonatedActor() ) RPCApi.ClientToServer.Tag.OBSERVABLES_CLOSED -> { - val ids = ArrayList() + val ids = ArrayList() val buffer = message.bodyBuffer val numberOfIds = buffer.readInt() for (i in 1..numberOfIds) { - ids.add(ObservableId(buffer.readLong())) + ids.add(buffer.readInvocationId()) } ObservablesClosed(ids) } @@ -164,23 +177,23 @@ object RPCApi { /** Reply in response to an [ClientToServer.RpcRequest]. */ data class RpcReply( - val id: RpcRequestId, + val id: InvocationId, val result: Try ) : ServerToClient() { override fun writeToClientMessage(context: SerializationContext, message: ClientMessage) { message.putIntProperty(TAG_FIELD_NAME, Tag.RPC_REPLY.ordinal) - message.putLongProperty(RPC_ID_FIELD_NAME, id.toLong) + id.mapTo(message, RPC_ID_FIELD_NAME, RPC_ID_TIMESTAMP_FIELD_NAME) message.bodyBuffer.writeBytes(result.safeSerialize(context) { Try.Failure(it) }.bytes) } } data class Observation( - val id: ObservableId, + val id: InvocationId, val content: Notification<*> ) : ServerToClient() { override fun writeToClientMessage(context: SerializationContext, message: ClientMessage) { message.putIntProperty(TAG_FIELD_NAME, Tag.OBSERVATION.ordinal) - message.putLongProperty(OBSERVABLE_ID_FIELD_NAME, id.toLong) + id.mapTo(message, OBSERVABLE_ID_FIELD_NAME, OBSERVABLE_ID_TIMESTAMP_FIELD_NAME) message.bodyBuffer.writeBytes(content.safeSerialize(context) { Notification.createOnError(it) }.bytes) } } @@ -196,20 +209,15 @@ object RPCApi { val tag = Tag.values()[message.getIntProperty(TAG_FIELD_NAME)] return when (tag) { RPCApi.ServerToClient.Tag.RPC_REPLY -> { - val id = RpcRequestId(message.getLongProperty(RPC_ID_FIELD_NAME)) - val poolWithIdContext = context.withProperty(RpcRequestOrObservableIdKey, id.toLong) - RpcReply( - id = id, - result = message.getBodyAsByteArray().deserialize(context = poolWithIdContext) - ) + val id = message.invocationId(RPC_ID_FIELD_NAME, RPC_ID_TIMESTAMP_FIELD_NAME) ?: throw IllegalStateException("Cannot parse invocation id from client message.") + val poolWithIdContext = context.withProperty(RpcRequestOrObservableIdKey, id) + RpcReply(id, message.getBodyAsByteArray().deserialize(context = poolWithIdContext)) } RPCApi.ServerToClient.Tag.OBSERVATION -> { - val id = ObservableId(message.getLongProperty(OBSERVABLE_ID_FIELD_NAME)) - val poolWithIdContext = context.withProperty(RpcRequestOrObservableIdKey, id.toLong) - Observation( - id = id, - content = message.getBodyAsByteArray().deserialize(context = poolWithIdContext) - ) + val observableId = message.invocationId(OBSERVABLE_ID_FIELD_NAME, OBSERVABLE_ID_TIMESTAMP_FIELD_NAME) ?: throw IllegalStateException("Cannot parse invocation id from client message.") + val poolWithIdContext = context.withProperty(RpcRequestOrObservableIdKey, observableId) + val payload = message.getBodyAsByteArray().deserialize>(context = poolWithIdContext) + Observation(observableId, payload) } } } @@ -228,3 +236,108 @@ data class ArtemisConsumer( val session: ClientSession, val consumer: ClientConsumer ) + +private val TAG_FIELD_NAME = "tag" +private val RPC_ID_FIELD_NAME = "rpc-id" +private val RPC_ID_TIMESTAMP_FIELD_NAME = "rpc-id-timestamp" +private val RPC_SESSION_ID_FIELD_NAME = "rpc-session-id" +private val RPC_SESSION_ID_TIMESTAMP_FIELD_NAME = "rpc-session-id-timestamp" +private val RPC_EXTERNAL_ID_FIELD_NAME = "rpc-external-id" +private val RPC_EXTERNAL_ID_TIMESTAMP_FIELD_NAME = "rpc-external-id-timestamp" +private val RPC_EXTERNAL_SESSION_ID_FIELD_NAME = "rpc-external-session-id" +private val RPC_EXTERNAL_SESSION_ID_TIMESTAMP_FIELD_NAME = "rpc-external-session-id-timestamp" +private val RPC_IMPERSONATED_ACTOR_ID = "rpc-impersonated-actor-id" +private val RPC_IMPERSONATED_ACTOR_STORE_ID = "rpc-impersonated-actor-store-id" +private val RPC_IMPERSONATED_ACTOR_OWNING_LEGAL_IDENTITY = "rpc-impersonated-actor-owningLegalIdentity" +private val OBSERVABLE_ID_FIELD_NAME = "observable-id" +private val OBSERVABLE_ID_TIMESTAMP_FIELD_NAME = "observable-id-timestamp" +private val METHOD_NAME_FIELD_NAME = "method-name" + +fun ClientMessage.replyId(): InvocationId { + + return invocationId(RPC_ID_FIELD_NAME, RPC_ID_TIMESTAMP_FIELD_NAME) ?: throw IllegalStateException("Cannot extract reply id from client message.") +} + +fun ClientMessage.sessionId(): SessionId { + + return sessionId(RPC_SESSION_ID_FIELD_NAME, RPC_SESSION_ID_TIMESTAMP_FIELD_NAME) ?: throw IllegalStateException("Cannot extract the session id from client message.") +} + +fun ClientMessage.externalTrace(): Trace? { + + val invocationId = invocationId(RPC_EXTERNAL_ID_FIELD_NAME, RPC_EXTERNAL_ID_TIMESTAMP_FIELD_NAME) + val sessionId = sessionId(RPC_EXTERNAL_SESSION_ID_FIELD_NAME, RPC_EXTERNAL_SESSION_ID_TIMESTAMP_FIELD_NAME) + + return when { + invocationId == null || sessionId == null -> null + else -> Trace(invocationId, sessionId) + } +} + +fun ClientMessage.impersonatedActor(): Actor? { + + return getStringProperty(RPC_IMPERSONATED_ACTOR_ID)?.let { + val impersonatedStoreId = getStringProperty(RPC_IMPERSONATED_ACTOR_STORE_ID) + val impersonatingOwningLegalIdentity = getStringProperty(RPC_IMPERSONATED_ACTOR_OWNING_LEGAL_IDENTITY) + if (impersonatedStoreId == null || impersonatingOwningLegalIdentity == null) { + throw IllegalStateException("Cannot extract impersonated actor from client message.") + } + Actor(Actor.Id(it), AuthServiceId(impersonatedStoreId), CordaX500Name.parse(impersonatingOwningLegalIdentity)) + } +} + +private fun Id.mapTo(message: ClientMessage, valueProperty: String, timestampProperty: String) { + + message.putStringProperty(valueProperty, value) + message.putLongProperty(timestampProperty, timestamp.toEpochMilli()) +} + +private fun ActiveMQBuffer.writeInvocationId(invocationId: InvocationId) { + + this.writeString(invocationId.value) + this.writeLong(invocationId.timestamp.toEpochMilli()) +} + +private fun ActiveMQBuffer.readInvocationId() : InvocationId { + + val value = this.readString() + val timestamp = this.readLong() + return InvocationId(value, Instant.ofEpochMilli(timestamp)) +} + +private fun InvocationId.mapTo(message: ClientMessage) = mapTo(message, RPC_ID_FIELD_NAME, RPC_ID_TIMESTAMP_FIELD_NAME) + +private fun SessionId.mapTo(message: ClientMessage) = mapTo(message, RPC_SESSION_ID_FIELD_NAME, RPC_SESSION_ID_TIMESTAMP_FIELD_NAME) + +private fun Trace.mapToExternal(message: ClientMessage) = mapTo(message, RPC_EXTERNAL_ID_FIELD_NAME, RPC_EXTERNAL_ID_TIMESTAMP_FIELD_NAME, RPC_EXTERNAL_SESSION_ID_FIELD_NAME, RPC_EXTERNAL_SESSION_ID_TIMESTAMP_FIELD_NAME) + +private fun Actor.mapToImpersonated(message: ClientMessage) { + + message.putStringProperty(RPC_IMPERSONATED_ACTOR_ID, this.id.value) + message.putStringProperty(RPC_IMPERSONATED_ACTOR_STORE_ID, this.serviceId.value) + message.putStringProperty(RPC_IMPERSONATED_ACTOR_OWNING_LEGAL_IDENTITY, this.owningLegalIdentity.toString()) +} + +private fun Trace.mapTo(message: ClientMessage, valueProperty: String, timestampProperty: String, sessionValueProperty: String, sessionTimestampProperty: String) = apply { + + invocationId.apply { + message.putStringProperty(valueProperty, value) + message.putLongProperty(timestampProperty, timestamp.toEpochMilli()) + } + sessionId.apply { + message.putStringProperty(sessionValueProperty, value) + message.putLongProperty(sessionTimestampProperty, timestamp.toEpochMilli()) + } +} + +private fun ClientMessage.invocationId(valueProperty: String, timestampProperty: String): InvocationId? = id(valueProperty, timestampProperty, ::InvocationId) + +private fun ClientMessage.sessionId(valueProperty: String, timestampProperty: String): SessionId? = id(valueProperty, timestampProperty, ::SessionId) + +private fun > ClientMessage.id(valueProperty: String, timestampProperty: String, construct: (value: String, timestamp: Instant) -> ID): ID? { + + // returning null because getLongProperty throws trying to convert null to long + val idRaw = this.getStringProperty(valueProperty) ?: return null + val timestampRaw = this.getLongProperty(timestampProperty) + return construct(idRaw, Instant.ofEpochMilli(timestampRaw)) +} \ No newline at end of file diff --git a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt index dafdb08c7f..e7d05ff80e 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt @@ -34,6 +34,7 @@ import net.corda.testing.dummyCommand import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNetwork.MockNode import net.corda.testing.node.MockNodeParameters +import net.corda.testing.startFlow import org.junit.After import org.junit.Test import java.nio.file.Paths diff --git a/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt index b9e95db5bd..a948d2fe93 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt @@ -18,6 +18,7 @@ import net.corda.testing.contracts.DummyContract import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.driver import net.corda.testing.dummyCommand +import net.corda.testing.startFlow import net.corda.testing.node.ClusterSpec import net.corda.testing.node.NotarySpec import org.junit.Test diff --git a/node/src/integration-test/kotlin/net/corda/node/services/statemachine/FlowVersioningTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/statemachine/FlowVersioningTest.kt index 533a5df56a..3396277518 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/statemachine/FlowVersioningTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/statemachine/FlowVersioningTest.kt @@ -11,6 +11,7 @@ import net.corda.testing.ALICE import net.corda.testing.BOB import net.corda.testing.chooseIdentity import net.corda.testing.internal.NodeBasedTest +import net.corda.testing.startFlow import org.assertj.core.api.Assertions.assertThat import org.junit.Test diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt index 693c31ce1c..15ba68fb24 100644 --- a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt +++ b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt @@ -24,10 +24,7 @@ import net.corda.nodeapi.ArtemisMessagingComponent.Companion.PEERS_PREFIX import net.corda.nodeapi.RPCApi import net.corda.nodeapi.User import net.corda.nodeapi.config.SSLConfiguration -import net.corda.testing.ALICE -import net.corda.testing.BOB -import net.corda.testing.chooseIdentity -import net.corda.testing.configureTestSSL +import net.corda.testing.* import net.corda.testing.internal.NodeBasedTest import net.corda.testing.messaging.SimpleMQClient import org.apache.activemq.artemis.api.core.ActiveMQNonExistentQueueException 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 82b39fa383..dc86253897 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -7,6 +7,7 @@ import net.corda.confidential.SwapIdentitiesFlow import net.corda.confidential.SwapIdentitiesHandler import net.corda.core.CordaException import net.corda.core.concurrent.CordaFuture +import net.corda.core.context.InvocationContext import net.corda.core.crypto.SignedData import net.corda.core.crypto.sign import net.corda.core.flows.* @@ -347,8 +348,10 @@ abstract class AbstractNode(val configuration: NodeConfiguration, private fun startFlowChecked(flow: FlowLogic): FlowStateMachine { val logicType = flow.javaClass require(logicType.isAnnotationPresent(StartableByService::class.java)) { "${logicType.name} was not designed for starting by a CordaService" } - val currentUser = FlowInitiator.Service(serviceInstance.javaClass.name) - return flowStarter.startFlow(flow, currentUser).getOrThrow() + // TODO check service permissions + // TODO switch from myInfo.legalIdentities[0].name to current node's identity as soon as available + val context = InvocationContext.service(serviceInstance.javaClass.name, myInfo.legalIdentities[0].name) + return flowStarter.startFlow(flow, context).getOrThrow() } override fun equals(other: Any?): Boolean { @@ -766,8 +769,8 @@ abstract class AbstractNode(val configuration: NodeConfiguration, } internal class FlowStarterImpl(private val serverThread: AffinityExecutor, private val smm: StateMachineManager) : FlowStarter { - override fun startFlow(logic: FlowLogic, flowInitiator: FlowInitiator, ourIdentity: Party?): CordaFuture> { - return serverThread.fetchFrom { smm.startFlow(logic, flowInitiator, ourIdentity) } + override fun startFlow(logic: FlowLogic, context: InvocationContext): CordaFuture> { + return serverThread.fetchFrom { smm.startFlow(logic, context) } } } 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 931a260e39..0f73300772 100644 --- a/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt +++ b/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt @@ -2,6 +2,8 @@ package net.corda.node.internal import net.corda.client.rpc.notUsed import net.corda.core.concurrent.CordaFuture +import net.corda.core.context.InvocationContext +import net.corda.core.context.Origin import net.corda.core.contracts.ContractState import net.corda.core.crypto.SecureHash import net.corda.core.flows.FlowInitiator @@ -22,7 +24,7 @@ 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.rpcContext +import net.corda.node.services.messaging.context import net.corda.node.services.statemachine.StateMachineManager import net.corda.node.utilities.CordaPersistence import rx.Observable @@ -151,9 +153,7 @@ internal class CordaRPCOpsImpl( private fun startFlow(logicType: Class>, args: Array): FlowStateMachine { require(logicType.isAnnotationPresent(StartableByRPC::class.java)) { "${logicType.name} was not designed for RPC" } - val currentUser = FlowInitiator.RPC(rpcContext().currentUser.username) - // TODO RPC flows should have mapping user -> identity that should be resolved automatically on starting flow. - return flowStarter.invokeFlowAsync(logicType, currentUser, *args).getOrThrow() + return flowStarter.invokeFlowAsync(logicType, context(), *args).getOrThrow() } override fun attachmentExists(id: SecureHash): Boolean { @@ -192,7 +192,7 @@ internal class CordaRPCOpsImpl( } catch (e: Exception) { // log and rethrow exception so we keep a copy server side log.error(e.message) - throw e.cause ?: e + throw e.cause ?: e } } @@ -272,17 +272,30 @@ internal class CordaRPCOpsImpl( return vaultTrackBy(criteria, PageSpecification(), sorting, contractStateType) } - companion object { - private fun stateMachineInfoFromFlowLogic(flowLogic: FlowLogic<*>): StateMachineInfo { - return StateMachineInfo(flowLogic.runId, flowLogic.javaClass.name, flowLogic.stateMachine.flowInitiator, flowLogic.track()) - } + private fun stateMachineInfoFromFlowLogic(flowLogic: FlowLogic<*>): StateMachineInfo { + return StateMachineInfo(flowLogic.runId, flowLogic.javaClass.name, flowLogic.stateMachine.context.toFlowInitiator(), flowLogic.track(), flowLogic.stateMachine.context) + } - private fun stateMachineUpdateFromStateMachineChange(change: StateMachineManager.Change): StateMachineUpdate { - return when (change) { - is StateMachineManager.Change.Add -> StateMachineUpdate.Added(stateMachineInfoFromFlowLogic(change.logic)) - is StateMachineManager.Change.Removed -> StateMachineUpdate.Removed(change.logic.runId, change.result) - } + private fun stateMachineUpdateFromStateMachineChange(change: StateMachineManager.Change): StateMachineUpdate { + return when (change) { + is StateMachineManager.Change.Add -> StateMachineUpdate.Added(stateMachineInfoFromFlowLogic(change.logic)) + is StateMachineManager.Change.Removed -> StateMachineUpdate.Removed(change.logic.runId, change.result) } + } + + private fun InvocationContext.toFlowInitiator(): FlowInitiator { + + val principal = origin.principal().name + return when (origin) { + is Origin.RPC -> FlowInitiator.RPC(principal) + is Origin.Peer -> services.identityService.wellKnownPartyFromX500Name((origin as Origin.Peer).party)?.let { FlowInitiator.Peer(it) } ?: throw IllegalStateException("Unknown peer with name ${(origin as Origin.Peer).party}.") + is Origin.Service -> FlowInitiator.Service(principal) + is Origin.Shell -> FlowInitiator.Shell + is Origin.Scheduled -> FlowInitiator.Scheduled((origin as Origin.Scheduled).scheduledState) + } + } + + companion object { private val log = loggerFor() } } \ No newline at end of file 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 1f7c0ed011..2ae8594db9 100644 --- a/node/src/main/kotlin/net/corda/node/internal/RpcAuthorisationProxy.kt +++ b/node/src/main/kotlin/net/corda/node/internal/RpcAuthorisationProxy.kt @@ -14,14 +14,13 @@ 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.RpcContext -import net.corda.node.services.messaging.requireEitherPermission +import net.corda.node.services.messaging.RpcAuthContext import rx.Observable import java.io.InputStream import java.security.PublicKey // TODO change to KFunction reference after Kotlin fixes https://youtrack.jetbrains.com/issue/KT-12140 -class RpcAuthorisationProxy(private val implementation: CordaRPCOps, private val context: () -> RpcContext, private val permissionsAllowing: (methodName: String, args: List) -> Set) : CordaRPCOps { +class RpcAuthorisationProxy(private val implementation: CordaRPCOps, private val context: () -> RpcAuthContext, private val permissionsAllowing: (methodName: String, args: List) -> Set) : CordaRPCOps { override fun uploadAttachmentWithMetadata(jar: InputStream, uploader: String, filename: String): SecureHash = guard("uploadAttachmentWithMetadata") { implementation.uploadAttachmentWithMetadata(jar, uploader, filename) @@ -163,7 +162,7 @@ class RpcAuthorisationProxy(private val implementation: CordaRPCOps, private val // TODO change to KFunction reference after Kotlin fixes https://youtrack.jetbrains.com/issue/KT-12140 private inline fun guard(methodName: String, args: List, action: () -> RESULT): RESULT { - context.invoke().requireEitherPermission(permissionsAllowing.invoke(methodName, args)) - return action.invoke() + context().requireEitherPermission(permissionsAllowing.invoke(methodName, args)) + return action() } } \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/services/RPCUserService.kt b/node/src/main/kotlin/net/corda/node/services/RPCUserService.kt index 371815ed94..e852e764b1 100644 --- a/node/src/main/kotlin/net/corda/node/services/RPCUserService.kt +++ b/node/src/main/kotlin/net/corda/node/services/RPCUserService.kt @@ -1,5 +1,6 @@ package net.corda.node.services +import net.corda.core.context.AuthServiceId import net.corda.nodeapi.User /** @@ -11,12 +12,17 @@ interface RPCUserService { fun getUser(username: String): User? val users: List + + val id: AuthServiceId } // TODO Store passwords as salted hashes // TODO Or ditch this and consider something like Apache Shiro // TODO Need access to permission checks from inside flows and at other point during audit checking. class RPCUserServiceImpl(override val users: List) : RPCUserService { + + override val id: AuthServiceId = AuthServiceId("NODE_FILE_CONFIGURATION") + init { users.forEach { require(it.username.matches("\\w+".toRegex())) { "Username ${it.username} contains invalid characters" } diff --git a/node/src/main/kotlin/net/corda/node/services/api/AuditService.kt b/node/src/main/kotlin/net/corda/node/services/api/AuditService.kt index 127f69e3d6..a958510f48 100644 --- a/node/src/main/kotlin/net/corda/node/services/api/AuditService.kt +++ b/node/src/main/kotlin/net/corda/node/services/api/AuditService.kt @@ -1,10 +1,10 @@ package net.corda.node.services.api +import net.corda.core.context.InvocationContext import net.corda.core.flows.FlowLogic import net.corda.core.flows.StateMachineRunId import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.utilities.ProgressTracker -import java.security.Principal import java.time.Instant /** @@ -17,9 +17,9 @@ sealed class AuditEvent { */ abstract val timestamp: Instant /** - * The responsible individual, node, or subsystem to which the audit event can be mapped. + * The invocation context at the time the event was generated. */ - abstract val principal: Principal + abstract val context: InvocationContext /** * A human readable description of audit event including any permission check results. */ @@ -36,7 +36,7 @@ sealed class AuditEvent { * Sealed data class to mark system related events as a distinct category. */ data class SystemAuditEvent(override val timestamp: Instant, - override val principal: Principal, + override val context: InvocationContext, override val description: String, override val contextData: Map) : AuditEvent() @@ -60,7 +60,7 @@ interface FlowAuditInfo { */ data class FlowAppAuditEvent( override val timestamp: Instant, - override val principal: Principal, + override val context: InvocationContext, override val description: String, override val contextData: Map, override val flowType: Class>, @@ -73,7 +73,7 @@ data class FlowAppAuditEvent( */ data class FlowStartEvent( override val timestamp: Instant, - override val principal: Principal, + override val context: InvocationContext, override val description: String, override val contextData: Map, override val flowType: Class>, @@ -86,7 +86,7 @@ data class FlowStartEvent( */ data class FlowProgressAuditEvent( override val timestamp: Instant, - override val principal: Principal, + override val context: InvocationContext, override val description: String, override val flowType: Class>, override val flowId: StateMachineRunId, @@ -98,7 +98,7 @@ data class FlowProgressAuditEvent( * Sealed data class to record any FlowExceptions, or other unexpected terminations of a Flow. */ data class FlowErrorAuditEvent(override val timestamp: Instant, - override val principal: Principal, + override val context: InvocationContext, override val description: String, override val contextData: Map, override val flowType: Class>, @@ -111,7 +111,7 @@ data class FlowErrorAuditEvent(override val timestamp: Instant, * after recording the FlowPermissionAuditEvent. This may cause an extra FlowErrorAuditEvent to be recorded too. */ data class FlowPermissionAuditEvent(override val timestamp: Instant, - override val principal: Principal, + override val context: InvocationContext, override val description: String, override val contextData: Map, override val flowType: Class>, 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 85b9dceae1..52ced3c7f0 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 @@ -2,12 +2,10 @@ package net.corda.node.services.api import net.corda.core.concurrent.CordaFuture import net.corda.core.crypto.SecureHash -import net.corda.core.flows.FlowInitiator 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.VisibleForTesting +import net.corda.core.context.InvocationContext import net.corda.core.internal.uncheckedCast import net.corda.core.messaging.DataFeed import net.corda.core.messaging.StateMachineTransactionMapping @@ -18,7 +16,6 @@ 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.getOrThrow import net.corda.core.utilities.loggerFor import net.corda.node.internal.InitiatedFlowFactory import net.corda.node.internal.cordapp.CordappProviderInternal @@ -122,34 +119,28 @@ interface ServiceHubInternal : ServiceHub { } interface FlowStarter { - /** - * Starts an already constructed flow. Note that you must be on the server thread to call this method. [FlowInitiator] - * defaults to [FlowInitiator.RPC] with username "Only For Testing". - */ - @VisibleForTesting - fun startFlow(logic: FlowLogic): FlowStateMachine = startFlow(logic, FlowInitiator.RPC("Only For Testing")).getOrThrow() /** * Starts an already constructed flow. Note that you must be on the server thread to call this method. - * @param flowInitiator indicates who started the flow, see: [FlowInitiator]. + * @param context indicates who started the flow, see: [InvocationContext]. */ - fun startFlow(logic: FlowLogic, flowInitiator: FlowInitiator, ourIdentity: Party? = null): CordaFuture> + fun startFlow(logic: FlowLogic, context: InvocationContext): CordaFuture> /** * Will check [logicType] and [args] against a whitelist and if acceptable then construct and initiate the flow. - * Note that you must be on the server thread to call this method. [flowInitiator] points how flow was started, - * See: [FlowInitiator]. + * Note that you must be on the server thread to call this method. [context] points how flow was started, + * See: [InvocationContext]. * * @throws net.corda.core.flows.IllegalFlowLogicException or IllegalArgumentException if there are problems with the * [logicType] or [args]. */ fun invokeFlowAsync( logicType: Class>, - flowInitiator: FlowInitiator, + context: InvocationContext, vararg args: Any?): CordaFuture> { val logicRef = FlowLogicRefFactoryImpl.createForRPC(logicType, *args) val logic: FlowLogic = uncheckedCast(FlowLogicRefFactoryImpl.toFlowLogic(logicRef)) - return startFlow(logic, flowInitiator, ourIdentity = null) + return startFlow(logic, context) } } 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 85aa36a0cb..27ca5d579b 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 @@ -2,17 +2,17 @@ package net.corda.node.services.events import co.paralleluniverse.fibers.Suspendable import com.google.common.util.concurrent.ListenableFuture -import com.google.common.util.concurrent.SettableFuture +import net.corda.core.context.InvocationContext import net.corda.core.contracts.SchedulableState import net.corda.core.contracts.ScheduledActivity import net.corda.core.contracts.ScheduledStateRef import net.corda.core.contracts.StateRef import net.corda.core.crypto.SecureHash -import net.corda.core.flows.FlowInitiator import net.corda.core.flows.FlowLogic import net.corda.core.internal.ThreadBox import net.corda.core.internal.VisibleForTesting import net.corda.core.internal.concurrent.flatMap +import net.corda.core.context.Origin import net.corda.core.internal.until import net.corda.core.node.StateLoader import net.corda.core.schemas.PersistentStateRef @@ -247,7 +247,9 @@ class NodeSchedulerService(private val clock: Clock, val scheduledFlow = getScheduledFlow(scheduledState) if (scheduledFlow != null) { flowName = scheduledFlow.javaClass.name - val future = flowStarter.startFlow(scheduledFlow, FlowInitiator.Scheduled(scheduledState)).flatMap { it.resultFuture } + // TODO refactor the scheduler to store and propagate the original invocation context + val context = InvocationContext.newInstance(Origin.Scheduled(scheduledState)) + val future = flowStarter.startFlow(scheduledFlow, context).flatMap { it.resultFuture } future.then { unfinishedSchedules.countDown() } diff --git a/node/src/main/kotlin/net/corda/node/services/logging/ContextualLoggingUtils.kt b/node/src/main/kotlin/net/corda/node/services/logging/ContextualLoggingUtils.kt new file mode 100644 index 0000000000..ffebc2af06 --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/services/logging/ContextualLoggingUtils.kt @@ -0,0 +1,28 @@ +package net.corda.node.services.logging + +import net.corda.core.context.InvocationContext +import org.slf4j.MDC + +internal fun InvocationContext.pushToLoggingContext() { + + MDC.put("invocation_id", trace.invocationId.value) + MDC.put("invocation_timestamp", trace.invocationId.timestamp.toString()) + MDC.put("session_id", trace.sessionId.value) + MDC.put("session_timestamp", trace.sessionId.timestamp.toString()) + actor?.let { + MDC.put("actor_id", it.id.value) + MDC.put("actor_store_id", it.serviceId.value) + MDC.put("actor_owningIdentity", it.owningLegalIdentity.toString()) + } + externalTrace?.let { + MDC.put("external_invocation_id", it.invocationId.value) + MDC.put("external_invocation_timestamp", it.invocationId.timestamp.toString()) + MDC.put("external_session_id", it.sessionId.value) + MDC.put("external_session_timestamp", it.sessionId.timestamp.toString()) + } + impersonatedActor?.let { + MDC.put("impersonating_actor_id", it.id.value) + MDC.put("impersonating_actor_store_id", it.serviceId.value) + MDC.put("impersonating_actor_owningIdentity", it.owningLegalIdentity.toString()) + } +} \ No newline at end of file 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 d74ffa35c6..30c8d4adb1 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 @@ -12,7 +12,11 @@ import com.google.common.collect.Multimaps import com.google.common.collect.SetMultimap import com.google.common.util.concurrent.ThreadFactoryBuilder import net.corda.client.rpc.RPCException -import net.corda.core.crypto.random63BitValue +import net.corda.core.context.Actor +import net.corda.core.context.Actor.Id +import net.corda.core.context.InvocationContext +import net.corda.core.context.Trace +import net.corda.core.context.Trace.InvocationId import net.corda.core.identity.CordaX500Name import net.corda.core.internal.LazyStickyPool import net.corda.core.internal.LifeCycle @@ -25,6 +29,7 @@ import net.corda.core.utilities.debug import net.corda.core.utilities.loggerFor import net.corda.core.utilities.seconds import net.corda.node.services.RPCUserService +import net.corda.node.services.logging.pushToLoggingContext import net.corda.nodeapi.* import net.corda.nodeapi.ArtemisMessagingComponent.Companion.NODE_USER import org.apache.activemq.artemis.api.core.Message @@ -37,6 +42,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.MDC import rx.Notification import rx.Observable import rx.Subscriber @@ -106,7 +112,7 @@ class RPCServer( /** The observable subscription mapping. */ private val observableMap = createObservableSubscriptionMap() /** A mapping from client addresses to IDs of associated Observables */ - private val clientAddressToObservables = Multimaps.synchronizedSetMultimap(HashMultimap.create()) + private val clientAddressToObservables = Multimaps.synchronizedSetMultimap(HashMultimap.create()) /** The scheduled reaper handle. */ private var reaperScheduledFuture: ScheduledFuture<*>? = null @@ -138,7 +144,7 @@ class RPCServer( } private fun createObservableSubscriptionMap(): ObservableSubscriptionMap { - val onObservableRemove = RemovalListener { + val onObservableRemove = RemovalListener { log.debug { "Unsubscribing from Observable with id ${it.key} because of ${it.cause}" } it.value.subscription.unsubscribe() } @@ -269,18 +275,19 @@ class RPCServer( val arguments = Try.on { clientToServer.serialisedArguments.deserialize>(context = RPC_SERVER_CONTEXT) } + val context = artemisMessage.context(clientToServer.sessionId) + context.invocation.pushToLoggingContext() when (arguments) { is Try.Success -> { - val rpcContext = RpcContext(currentUser = getUser(artemisMessage)) rpcExecutor!!.submit { - val result = invokeRpc(rpcContext, clientToServer.methodName, arguments.value) - sendReply(clientToServer.id, clientToServer.clientAddress, result) + val result = invokeRpc(context, clientToServer.methodName, arguments.value) + sendReply(clientToServer.replyId, clientToServer.clientAddress, result) } } is Try.Failure -> { // We failed to deserialise the arguments, route back the error log.warn("Inbound RPC failed", arguments.exception) - sendReply(clientToServer.id, clientToServer.clientAddress, arguments) + sendReply(clientToServer.replyId, clientToServer.clientAddress, arguments) } } } @@ -291,10 +298,10 @@ class RPCServer( artemisMessage.acknowledge() } - private fun invokeRpc(rpcContext: RpcContext, methodName: String, arguments: List): Try { + private fun invokeRpc(context: RpcAuthContext, methodName: String, arguments: List): Try { return Try.on { try { - CURRENT_RPC_CONTEXT.set(rpcContext) + CURRENT_RPC_CONTEXT.set(context) log.debug { "Calling $methodName" } val method = methodTable[methodName] ?: throw RPCException("Received RPC for unknown method $methodName - possible client/server version skew?") @@ -307,10 +314,10 @@ class RPCServer( } } - private fun sendReply(requestId: RPCApi.RpcRequestId, clientAddress: SimpleString, result: Try) { - val reply = RPCApi.ServerToClient.RpcReply(requestId, result) + private fun sendReply(replyId: InvocationId, clientAddress: SimpleString, result: Try) { + val reply = RPCApi.ServerToClient.RpcReply(replyId, result) val observableContext = ObservableContext( - requestId, + replyId, observableMap, clientAddressToObservables, clientAddress, @@ -352,51 +359,83 @@ class RPCServer( // TODO remove this User once webserver doesn't need it private val nodeUser = User(NODE_USER, NODE_USER, setOf()) - private fun getUser(message: ClientMessage): User { + private fun ClientMessage.context(sessionId: Trace.SessionId): RpcAuthContext { + val trace = Trace.newInstance(sessionId = sessionId) + val externalTrace = externalTrace() + val rpcActor = actorFrom(this) + val impersonatedActor = impersonatedActor() + return RpcAuthContext(InvocationContext.rpc(rpcActor.first, trace, externalTrace, impersonatedActor), rpcActor.second) + } + + private fun actorFrom(message: ClientMessage): Pair { val validatedUser = message.getStringProperty(Message.HDR_VALIDATED_USER) ?: throw IllegalArgumentException("Missing validated user from the Artemis message") + val targetLegalIdentity = message.getStringProperty(RPCApi.RPC_TARGET_LEGAL_IDENTITY)?.let(CordaX500Name.Companion::parse) ?: nodeLegalName + // TODO switch userService based on targetLegalIdentity val rpcUser = userService.getUser(validatedUser) - if (rpcUser != null) { - return rpcUser + return if (rpcUser != null) { + Actor(Id(rpcUser.username), userService.id, targetLegalIdentity) to RpcPermissions(rpcUser.permissions) } else if (CordaX500Name.parse(validatedUser) == nodeLegalName) { - return nodeUser + // TODO remove this after Shell and WebServer will no longer need it + Actor(Id(nodeUser.username), userService.id, targetLegalIdentity) to RpcPermissions(nodeUser.permissions) } else { throw IllegalArgumentException("Validated user '$validatedUser' is not an RPC user nor the NODE user") } } } +// TODO replace this by creating a new CordaRPCImpl for each request, passing the context, after we fix Shell and WebServer @JvmField -internal val CURRENT_RPC_CONTEXT: ThreadLocal = ThreadLocal() +internal val CURRENT_RPC_CONTEXT: ThreadLocal = CurrentRpcContext() + +internal class CurrentRpcContext : ThreadLocal() { + + override fun remove() { + super.remove() + MDC.clear() + } + + override fun set(context: RpcAuthContext?) { + when { + context != null -> { + super.set(context) + // this is needed here as well because the Shell sets the context without going through the RpcServer + context.invocation.pushToLoggingContext() + } + else -> remove() + } + } +} /** * Returns a context specific to the current RPC call. Note that trying to call this function outside of an RPC will * throw. If you'd like to use the context outside of the call (e.g. in another thread) then pass the returned reference * around explicitly. + * The [InvocationContext] does not include permissions. */ -fun rpcContext(): RpcContext = CURRENT_RPC_CONTEXT.get() +internal fun context(): InvocationContext = rpcContext().invocation /** - * @param currentUser This is available to RPC implementations to query the validated [User] that is calling it. Each - * user has a set of permissions they're entitled to which can be used to control access. + * Returns a context specific to the current RPC call. Note that trying to call this function outside of an RPC will + * throw. If you'd like to use the context outside of the call (e.g. in another thread) then pass the returned reference + * around explicitly. + * The [RpcAuthContext] includes permissions. */ -data class RpcContext( - val currentUser: User -) +fun rpcContext(): RpcAuthContext = CURRENT_RPC_CONTEXT.get() class ObservableSubscription( val subscription: Subscription ) -typealias ObservableSubscriptionMap = Cache +typealias ObservableSubscriptionMap = Cache // We construct an observable context on each RPC request. If subsequently a nested Observable is // encountered this same context is propagated by the instrumented KryoPool. This way all // observations rooted in a single RPC will be muxed correctly. Note that the context construction // itself is quite cheap. class ObservableContext( - val rpcRequestId: RPCApi.RpcRequestId, + val invocationId: InvocationId, val observableMap: ObservableSubscriptionMap, - val clientAddressToObservables: SetMultimap, + val clientAddressToObservables: SetMultimap, val clientAddress: SimpleString, val serverControl: ActiveMQServerControl, val sessionAndProducerPool: LazyStickyPool, @@ -410,7 +449,7 @@ class ObservableContext( fun sendMessage(serverToClient: RPCApi.ServerToClient) { try { - sessionAndProducerPool.run(rpcRequestId) { + sessionAndProducerPool.run(invocationId) { val artemisMessage = it.session.createMessage(false) serverToClient.writeToClientMessage(serializationContextWithObservableContext, artemisMessage) it.producer.send(clientAddress, artemisMessage) @@ -437,9 +476,9 @@ object RpcServerObservableSerializer : Serializer>() { } override fun write(kryo: Kryo, output: Output, observable: Observable<*>) { - val observableId = RPCApi.ObservableId(random63BitValue()) + val observableId = InvocationId.newInstance() val observableContext = kryo.context[RpcObservableContextKey] as ObservableContext - output.writeLong(observableId.toLong, true) + output.writeInvocationId(observableId) val observableWithSubscription = ObservableSubscription( // We capture [observableContext] in the subscriber. Note that all synchronisation/kryo borrowing // must be done again within the subscriber @@ -465,4 +504,10 @@ object RpcServerObservableSerializer : Serializer>() { observableContext.clientAddressToObservables.put(observableContext.clientAddress, observableId) observableContext.observableMap.put(observableId, observableWithSubscription) } + + private fun Output.writeInvocationId(id: InvocationId) { + + writeString(id.value) + writeLong(id.timestamp.toEpochMilli()) + } } diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/RPCServerStructures.kt b/node/src/main/kotlin/net/corda/node/services/messaging/RPCServerStructures.kt deleted file mode 100644 index 6e68a81687..0000000000 --- a/node/src/main/kotlin/net/corda/node/services/messaging/RPCServerStructures.kt +++ /dev/null @@ -1,20 +0,0 @@ -@file:JvmName("RPCServerStructures") - -package net.corda.node.services.messaging - -import net.corda.client.rpc.PermissionException -import net.corda.node.services.Permissions.Companion.all -import net.corda.nodeapi.ArtemisMessagingComponent - -/** Helper method which checks that the current RPC user is entitled for the given permission. Throws a [PermissionException] otherwise. */ -fun RpcContext.requirePermission(permission: String): RpcContext = requireEitherPermission(setOf(permission)) - -/** Helper method which checks that the current RPC user is entitled with any of the given permissions. Throws a [PermissionException] otherwise. */ -fun RpcContext.requireEitherPermission(permissions: Set): RpcContext { - // TODO remove the NODE_USER condition once webserver doesn't need it - val currentUserPermissions = currentUser.permissions - if (currentUser.username != ArtemisMessagingComponent.NODE_USER && currentUserPermissions.intersect(permissions + all()).isEmpty()) { - throw PermissionException("User not permissioned with any of $permissions, permissions are $currentUserPermissions") - } - return this -} \ No newline at end of file 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 new file mode 100644 index 0000000000..7631c797c6 --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/services/messaging/RpcAuthContext.kt @@ -0,0 +1,29 @@ +package net.corda.node.services.messaging + +import net.corda.client.rpc.PermissionException +import net.corda.core.context.InvocationContext +import net.corda.node.services.Permissions +import net.corda.nodeapi.ArtemisMessagingComponent + +data class RpcAuthContext(val invocation: InvocationContext, val grantedPermissions: RpcPermissions) { + + fun requirePermission(permission: String) = requireEitherPermission(setOf(permission)) + + fun requireEitherPermission(permissions: Set): RpcAuthContext { + + // TODO remove the NODE_USER condition once webserver and shell won't need it anymore + if (invocation.principal().name != ArtemisMessagingComponent.NODE_USER && !grantedPermissions.coverAny(permissions)) { + throw PermissionException("User not permissioned with any of $permissions, permissions are ${this.grantedPermissions}.") + } + return this + } +} + +data class RpcPermissions(private val values: Set = emptySet()) { + + companion object { + val NONE = RpcPermissions() + } + + fun coverAny(permissions: Set) = !values.intersect(permissions + Permissions.all()).isEmpty() +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStateMachineImpl.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStateMachineImpl.kt index 57cc99378a..69bc41778d 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStateMachineImpl.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStateMachineImpl.kt @@ -15,6 +15,7 @@ import net.corda.core.identity.PartyAndCertificate import net.corda.core.internal.* import net.corda.core.internal.concurrent.OpenFuture import net.corda.core.internal.concurrent.openFuture +import net.corda.core.context.InvocationContext import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.serialize import net.corda.core.transactions.SignedTransaction @@ -22,6 +23,7 @@ import net.corda.core.utilities.* import net.corda.node.services.api.FlowAppAuditEvent import net.corda.node.services.api.FlowPermissionAuditEvent import net.corda.node.services.api.ServiceHubInternal +import net.corda.node.services.logging.pushToLoggingContext import net.corda.node.services.statemachine.FlowSessionState.Initiating import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.DatabaseTransaction @@ -40,9 +42,8 @@ class FlowPermissionException(message: String) : FlowException(message) class FlowStateMachineImpl(override val id: StateMachineRunId, override val logic: FlowLogic, scheduler: FiberScheduler, - override val flowInitiator: FlowInitiator, - // Store the Party rather than the full cert path with PartyAndCertificate - val ourIdentity: Party) : Fiber(id.toString(), scheduler), FlowStateMachine { + val ourIdentity: Party, + override val context: InvocationContext) : Fiber(id.toString(), scheduler), FlowStateMachine { companion object { // Used to work around a small limitation in Quasar. @@ -254,7 +255,7 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, val permissionGranted = true // TODO define permission control service on ServiceHubInternal and actually check authorization. val checkPermissionEvent = FlowPermissionAuditEvent( serviceHub.clock.instant(), - flowInitiator, + context, "Flow Permission Required: $permissionName", extraAuditData, logic.javaClass, @@ -264,7 +265,7 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, serviceHub.auditService.recordAuditEvent(checkPermissionEvent) @Suppress("ConstantConditionIf") if (!permissionGranted) { - throw FlowPermissionException("User $flowInitiator not permissioned for $permissionName on flow $id") + throw FlowPermissionException("User ${context.principal()} not permissioned for $permissionName on flow $id") } } @@ -272,7 +273,7 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, override fun recordAuditEvent(eventType: String, comment: String, extraAuditData: Map) { val flowAuditEvent = FlowAppAuditEvent( serviceHub.clock.instant(), - flowInitiator, + context, comment, extraAuditData, logic.javaClass, @@ -306,6 +307,8 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, return result } + internal fun pushToLoggingContext() = context.pushToLoggingContext() + /** * This method will suspend the state machine and wait for incoming session init response from other party. */ @@ -392,6 +395,7 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, session.retryable = retryable val (version, initiatingFlowClass) = session.flow.javaClass.flowVersionAndInitiatingClass val payloadBytes = firstPayload?.serialize(context = SerializationDefaults.P2P_CONTEXT) + logger.info("Initiating flow session with party ${otherParty.name}. Session id for tracing purposes is ${session.ourSessionId}.") val sessionInit = SessionInit(session.ourSessionId, initiatingFlowClass.name, version, session.flow.javaClass.appName, payloadBytes) sendInternal(session, sessionInit) if (waitForConfirmation) { diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManager.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManager.kt index e790d28b2c..fcddc980ea 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManager.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManager.kt @@ -1,10 +1,9 @@ package net.corda.node.services.statemachine import net.corda.core.concurrent.CordaFuture -import net.corda.core.flows.FlowInitiator import net.corda.core.flows.FlowLogic -import net.corda.core.identity.Party import net.corda.core.internal.FlowStateMachine +import net.corda.core.context.InvocationContext import net.corda.core.messaging.DataFeed import net.corda.core.utilities.Try import rx.Observable @@ -20,8 +19,7 @@ import rx.Observable * A flow is a class with a single call method. The call method and any others it invokes are rewritten by a bytecode * rewriting engine called Quasar, to ensure the code can be suspended and resumed at any point. * - * TODO: Consider the issue of continuation identity more deeply: is it a safe assumption that a serialised - * continuation is always unique? + * TODO: Consider the issue of continuation identity more deeply: is it a safe assumption that a serialised continuation is always unique? * TODO: Think about how to bring the system to a clean stop so it can be upgraded without any serialised stacks on disk * TODO: Timeouts * TODO: Surfacing of exceptions via an API and/or management UI @@ -43,9 +41,9 @@ interface StateMachineManager { * Starts a new flow. * * @param flowLogic The flow's code. - * @param flowInitiator The initiator of the flow. + * @param context The context of the flow. */ - fun startFlow(flowLogic: FlowLogic, flowInitiator: FlowInitiator, ourIdentity: Party? = null): CordaFuture> + fun startFlow(flowLogic: FlowLogic, context: InvocationContext): CordaFuture> /** * Represents an addition/removal of a state machine. 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 391b636684..29c261aa7a 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 @@ -11,9 +11,13 @@ import com.google.common.collect.HashMultimap import com.google.common.util.concurrent.MoreExecutors import net.corda.core.CordaException import net.corda.core.concurrent.CordaFuture +import net.corda.core.context.InvocationContext import net.corda.core.crypto.SecureHash import net.corda.core.crypto.random63BitValue -import net.corda.core.flows.* +import net.corda.core.flows.FlowException +import net.corda.core.flows.FlowInfo +import net.corda.core.flows.FlowLogic +import net.corda.core.flows.StateMachineRunId import net.corda.core.identity.Party import net.corda.core.internal.* import net.corda.core.internal.concurrent.doneFuture @@ -262,26 +266,28 @@ class StateMachineManagerImpl( } private fun onSessionMessage(message: ReceivedMessage) { + val peer = message.peer val sessionMessage = try { message.data.deserialize() } catch (ex: Exception) { - logger.error("Received corrupt SessionMessage data from ${message.peer}") + logger.error("Received corrupt SessionMessage data from $peer") return } - val sender = serviceHub.networkMapCache.getPeerByLegalName(message.peer) + val sender = serviceHub.networkMapCache.getPeerByLegalName(peer) if (sender != null) { when (sessionMessage) { is ExistingSessionMessage -> onExistingSessionMessage(sessionMessage, sender) is SessionInit -> onSessionInit(sessionMessage, message, sender) } } else { - logger.error("Unknown peer ${message.peer} in $sessionMessage") + logger.error("Unknown peer $peer in $sessionMessage") } } private fun onExistingSessionMessage(message: ExistingSessionMessage, sender: Party) { val session = openSessions[message.recipientSessionId] if (session != null) { + session.fiber.pushToLoggingContext() session.fiber.logger.trace { "Received $message on $session from $sender" } if (session.retryable) { if (message is SessionConfirm && session.state is FlowSessionState.Initiated) { @@ -327,6 +333,7 @@ class StateMachineManagerImpl( } private fun onSessionInit(sessionInit: SessionInit, receivedMessage: ReceivedMessage, sender: Party) { + logger.trace { "Received $sessionInit from $sender" } val senderSessionId = sessionInit.initiatorSessionId @@ -350,9 +357,10 @@ class StateMachineManagerImpl( session.receivedMessages += ReceivedSessionMessage(sender, SessionData(session.ourSessionId, sessionInit.firstPayload)) } openSessions[session.ourSessionId] = session - // TODO Perhaps the session-init will specificy which of our multiple identies to use, which we would have to - // double-check is actually ours. However, what if we want to control how our identities gets used? - val fiber = createFiber(flow, FlowInitiator.Peer(sender)) + val context = InvocationContext.peer(sender.name) + val fiber = createFiber(flow, context) + fiber.pushToLoggingContext() + logger.info("Accepting flow session from party ${sender.name}. Session id for tracing purposes is ${sessionInit.initiatorSessionId}.") flowSession.sessionFlow = flow flowSession.stateMachine = fiber fiber.openSessions[Pair(flow, sender)] = session @@ -407,13 +415,13 @@ class StateMachineManagerImpl( } } - private fun createFiber(logic: FlowLogic, flowInitiator: FlowInitiator, ourIdentity: Party? = null): FlowStateMachineImpl { + private fun createFiber(logic: FlowLogic, context: InvocationContext, ourIdentity: Party? = null): FlowStateMachineImpl { val fsm = FlowStateMachineImpl( StateMachineRunId.createRandom(), logic, scheduler, - flowInitiator, - ourIdentity ?: serviceHub.myInfo.legalIdentities[0]) + ourIdentity ?: serviceHub.myInfo.legalIdentities[0], + context) initFiber(fsm) return fsm } @@ -423,7 +431,7 @@ class StateMachineManagerImpl( fiber.database = database fiber.serviceHub = serviceHub fiber.ourIdentityAndCert = serviceHub.myInfo.legalIdentitiesAndCerts.find { it.party == fiber.ourIdentity } - ?: throw IllegalStateException("Identity specified by ${fiber.id} (${fiber.ourIdentity}) is not one of ours!") + ?: throw IllegalStateException("Identity specified by ${fiber.id} (${fiber.ourIdentity.name}) is not one of ours!") fiber.actionOnSuspend = { ioRequest -> updateCheckpoint(fiber) // We commit on the fibers transaction that was copied across ThreadLocals during suspend @@ -469,7 +477,7 @@ class StateMachineManagerImpl( private fun endAllFiberSessions(fiber: FlowStateMachineImpl<*>, result: Try<*>, propagated: Boolean) { openSessions.values.removeIf { session -> if (session.fiber == fiber) { - session.endSession((result as? Try.Failure)?.exception, propagated) + session.endSession(fiber.context, (result as? Try.Failure)?.exception, propagated) true } else { false @@ -477,7 +485,7 @@ class StateMachineManagerImpl( } } - private fun FlowSessionInternal.endSession(exception: Throwable?, propagated: Boolean) { + private fun FlowSessionInternal.endSession(context: InvocationContext, exception: Throwable?, propagated: Boolean) { val initiatedState = state as? FlowSessionState.Initiated ?: return val sessionEnd = if (exception == null) { NormalSessionEnd(initiatedState.peerSessionId) @@ -502,11 +510,11 @@ class StateMachineManagerImpl( * * Note that you must be on the [executor] thread. */ - override fun startFlow(flowLogic: FlowLogic, flowInitiator: FlowInitiator, ourIdentity: Party?): CordaFuture> { + override fun startFlow(flowLogic: FlowLogic, context: InvocationContext): CordaFuture> { // TODO: Check that logic has @Suspendable on its call method. executor.checkOnThread() val fiber = database.transaction { - val fiber = createFiber(flowLogic, flowInitiator, ourIdentity) + val fiber = createFiber(flowLogic, context) updateCheckpoint(fiber) fiber } 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 7ee8d1a61d..02af1f4b1b 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 @@ -12,7 +12,6 @@ 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.toHexString import net.corda.core.utilities.trace import net.corda.node.services.persistence.NodeAttachmentService import org.hibernate.query.criteria.internal.expression.LiteralExpression @@ -215,7 +214,7 @@ class HibernateQueryCriteriaParser(val contractStateType: Class).map { PersistentStateRef(it.txhash.bytes.toHexString(), it.index) } + val persistentStateRefs = (criteria.stateRefs as List).map(::PersistentStateRef) val compositeKey = vaultStates.get("stateRef") predicateSet.add(criteriaBuilder.and(compositeKey.`in`(persistentStateRefs))) } 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 818f0d58bc..8636e3ee90 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 @@ -5,15 +5,12 @@ import co.paralleluniverse.strands.Strand import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash import net.corda.core.internal.* +import net.corda.core.messaging.DataFeed import net.corda.core.node.StateLoader -import net.corda.core.node.services.* +import net.corda.core.node.StatesToRecord +import net.corda.core.node.services.KeyManagementService import net.corda.core.node.services.StatesNotAvailableException import net.corda.core.node.services.Vault -import net.corda.core.node.services.vault.QueryCriteria -import net.corda.core.node.services.vault.Sort -import net.corda.core.node.services.vault.SortAttribute -import net.corda.core.messaging.DataFeed -import net.corda.core.node.StatesToRecord import net.corda.core.node.services.VaultQueryException import net.corda.core.node.services.vault.* import net.corda.core.schemas.PersistentStateRef @@ -219,7 +216,7 @@ class NodeVaultService( val criteriaQuery = criteriaBuilder.createQuery(VaultSchemaV1.VaultStates::class.java) val vaultStates = criteriaQuery.from(VaultSchemaV1.VaultStates::class.java) val statusPredicate = criteriaBuilder.equal(vaultStates.get(VaultSchemaV1.VaultStates::stateStatus.name), Vault.StateStatus.UNCONSUMED) - val persistentStateRefs = refs.map { PersistentStateRef(it.txhash.bytes.toHexString(), it.index) } + val persistentStateRefs = refs.map(::PersistentStateRef) val compositeKey = vaultStates.get(VaultSchemaV1.VaultStates::stateRef.name) val stateRefsPredicate = criteriaBuilder.and(compositeKey.`in`(persistentStateRefs)) criteriaQuery.where(statusPredicate, stateRefsPredicate) diff --git a/node/src/main/kotlin/net/corda/node/shell/FlowWatchPrintingSubscriber.kt b/node/src/main/kotlin/net/corda/node/shell/FlowWatchPrintingSubscriber.kt index 235c2bcbe9..7f52b430f2 100644 --- a/node/src/main/kotlin/net/corda/node/shell/FlowWatchPrintingSubscriber.kt +++ b/node/src/main/kotlin/net/corda/node/shell/FlowWatchPrintingSubscriber.kt @@ -1,8 +1,8 @@ package net.corda.node.shell -import net.corda.core.flows.FlowInitiator import net.corda.core.flows.StateMachineRunId import net.corda.core.internal.concurrent.openFuture +import net.corda.core.context.InvocationContext import net.corda.core.messaging.StateMachineUpdate import net.corda.core.messaging.StateMachineUpdate.Added import net.corda.core.messaging.StateMachineUpdate.Removed @@ -72,7 +72,7 @@ class FlowWatchPrintingSubscriber(private val toStream: RenderPrintWriter) : Sub table.add(RowElement().add( LabelElement(formatFlowId(smmUpdate.id)), LabelElement(formatFlowName(smmUpdate.stateMachineInfo.flowLogicClassName)), - LabelElement(formatFlowInitiator(smmUpdate.stateMachineInfo.initiator)), + LabelElement(formatInvocationContext(smmUpdate.stateMachineInfo.context())), LabelElement("In progress") ).style(stateColor(smmUpdate).fg())) indexMap[smmUpdate.id] = table.rows.size - 1 @@ -105,14 +105,8 @@ class FlowWatchPrintingSubscriber(private val toStream: RenderPrintWriter) : Sub return flowId.toString().removeSurrounding("[", "]") } - private fun formatFlowInitiator(flowInitiator: FlowInitiator): String { - return when (flowInitiator) { - is FlowInitiator.Scheduled -> flowInitiator.scheduledState.ref.toString() - is FlowInitiator.Shell -> "Shell" // TODO Change when we will have more information on shell user. - is FlowInitiator.Peer -> flowInitiator.party.name.organisation - is FlowInitiator.RPC -> "RPC: " + flowInitiator.username - is FlowInitiator.Service -> "Service: " + flowInitiator.name - } + private fun formatInvocationContext(context: InvocationContext): String { + return context.principal().name } private fun formatFlowResult(flowResult: Try<*>): String { 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 d818e30b8f..9a7bf0630e 100644 --- a/node/src/main/kotlin/net/corda/node/shell/InteractiveShell.kt +++ b/node/src/main/kotlin/net/corda/node/shell/InteractiveShell.kt @@ -12,7 +12,6 @@ import net.corda.client.jackson.StringToMethodCallParser import net.corda.core.CordaException import net.corda.core.concurrent.CordaFuture import net.corda.core.contracts.UniqueIdentifier -import net.corda.core.flows.FlowInitiator import net.corda.core.flows.FlowLogic import net.corda.core.internal.* import net.corda.core.internal.concurrent.OpenFuture @@ -25,12 +24,11 @@ import net.corda.core.utilities.loggerFor import net.corda.node.internal.Node import net.corda.node.internal.StartedNode import net.corda.node.services.messaging.CURRENT_RPC_CONTEXT -import net.corda.node.services.messaging.RpcContext +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.nodeapi.ArtemisMessagingComponent -import net.corda.nodeapi.User import org.crsh.command.InvocationContext import org.crsh.console.jline.JLineProcessor import org.crsh.console.jline.TerminalFactory @@ -130,7 +128,9 @@ object InteractiveShell { InterruptHandler { jlineProcessor.interrupt() }.install() thread(name = "Command line shell processor", isDaemon = true) { // Give whoever has local shell access administrator access to the node. - CURRENT_RPC_CONTEXT.set(RpcContext(User(ArtemisMessagingComponent.NODE_USER, "", setOf()))) + // TODO remove this after Shell switches to RPC + val context = RpcAuthContext(net.corda.core.context.InvocationContext.shell(), RpcPermissions.NONE) + CURRENT_RPC_CONTEXT.set(context) Emoji.renderIfSupported { jlineProcessor.run() } @@ -235,7 +235,8 @@ object InteractiveShell { val clazz: Class> = uncheckedCast(matches.single()) try { // TODO Flow invocation should use startFlowDynamic. - val fsm = runFlowFromString({ node.services.startFlow(it, FlowInitiator.Shell).getOrThrow() }, inputData, clazz) + 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 latch = CountDownLatch(1) diff --git a/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt b/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt index 552d16a484..13e2387e8b 100644 --- a/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt +++ b/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt @@ -2,6 +2,7 @@ package net.corda.node import co.paralleluniverse.fibers.Suspendable import net.corda.client.rpc.PermissionException +import net.corda.core.context.InvocationContext import net.corda.core.contracts.Amount import net.corda.core.contracts.ContractState import net.corda.core.contracts.Issued @@ -25,17 +26,14 @@ import net.corda.finance.flows.CashIssueFlow import net.corda.finance.flows.CashPaymentFlow import net.corda.node.internal.SecureCordaRPCOps import net.corda.node.internal.StartedNode -import net.corda.node.services.Permissions.Companion.startFlow import net.corda.node.services.Permissions.Companion.invokeRpc +import net.corda.node.services.Permissions.Companion.startFlow import net.corda.node.services.messaging.CURRENT_RPC_CONTEXT -import net.corda.node.services.messaging.RpcContext -import net.corda.nodeapi.User -import net.corda.testing.chooseIdentity -import net.corda.testing.expect -import net.corda.testing.expectEvents +import net.corda.node.services.messaging.RpcAuthContext +import net.corda.node.services.messaging.RpcPermissions +import net.corda.testing.* import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNetwork.MockNode -import net.corda.testing.sequence import org.apache.commons.io.IOUtils import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.junit.After @@ -62,14 +60,12 @@ class CordaRPCOpsImplTest { private lateinit var transactions: Observable private lateinit var vaultTrackCash: Observable> - private val user = User("user", "pwd", permissions = emptySet()) - @Before fun setup() { mockNet = MockNetwork(cordappPackages = listOf("net.corda.finance.contracts.asset")) aliceNode = mockNet.createNode() rpc = SecureCordaRPCOps(aliceNode.services, aliceNode.smm, aliceNode.database, aliceNode.services) - CURRENT_RPC_CONTEXT.set(RpcContext(user)) + CURRENT_RPC_CONTEXT.set(RpcAuthContext(InvocationContext.rpc(testActor()), RpcPermissions.NONE)) mockNet.runNetwork() withPermissions(invokeRpc(CordaRPCOps::notaryIdentities)) { @@ -290,7 +286,7 @@ class CordaRPCOpsImplTest { val previous = CURRENT_RPC_CONTEXT.get() try { - CURRENT_RPC_CONTEXT.set(RpcContext(user.copy(permissions = permissions.toSet()))) + CURRENT_RPC_CONTEXT.set(previous.copy(grantedPermissions = RpcPermissions(permissions.toSet()))) action.invoke() } finally { CURRENT_RPC_CONTEXT.set(previous) diff --git a/node/src/test/kotlin/net/corda/node/internal/CordaServiceTest.kt b/node/src/test/kotlin/net/corda/node/internal/CordaServiceTest.kt index 7e5f99d32a..b7b47d9165 100644 --- a/node/src/test/kotlin/net/corda/node/internal/CordaServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/internal/CordaServiceTest.kt @@ -1,9 +1,10 @@ package net.corda.node.internal import co.paralleluniverse.fibers.Suspendable -import net.corda.core.flows.FlowInitiator import net.corda.core.flows.FlowLogic import net.corda.core.flows.StartableByService +import net.corda.core.context.InvocationContext +import net.corda.core.context.Origin import net.corda.core.node.AppServiceHub import net.corda.core.node.ServiceHub import net.corda.core.node.services.CordaService @@ -24,18 +25,18 @@ import kotlin.test.assertNotEquals import kotlin.test.assertTrue @StartableByService -class DummyServiceFlow : FlowLogic() { +class DummyServiceFlow : FlowLogic() { companion object { object TEST_STEP : ProgressTracker.Step("Custom progress step") } override val progressTracker: ProgressTracker = ProgressTracker(TEST_STEP) @Suspendable - override fun call(): FlowInitiator { + override fun call(): InvocationContext { // We call a subFlow, otehrwise there is no chance to subscribe to the ProgressTracker subFlow(CashIssueFlow(100.DOLLARS, OpaqueBytes.of(1), serviceHub.networkMapCache.notaryIdentities.first())) progressTracker.currentStep = TEST_STEP - return stateMachine.flowInitiator + return stateMachine.context } } @@ -43,9 +44,8 @@ class DummyServiceFlow : FlowLogic() { class TestCordaService(val appServiceHub: AppServiceHub): SingletonSerializeAsToken() { fun startServiceFlow() { val handle = appServiceHub.startFlow(DummyServiceFlow()) - val initiator = handle.returnValue.get() - initiator as FlowInitiator.Service - assertEquals(this.javaClass.name, initiator.serviceClassName) + val context = handle.returnValue.get() + assertEquals(this.javaClass.name, (context.origin as Origin.Service).serviceClassName) } fun startServiceFlowAndTrack() { diff --git a/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt index 849e56d69c..25b931fb76 100644 --- a/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt @@ -77,6 +77,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() { private lateinit var smmHasRemovedAllFlows: CountDownLatch private lateinit var kms: MockKeyManagementService private lateinit var mockSMM: StateMachineManager + private val ourIdentity = ALICE_NAME var calls: Int = 0 /** diff --git a/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt b/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt index 348909fc11..52ed332a7f 100644 --- a/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt @@ -3,8 +3,12 @@ package net.corda.node.services.events import co.paralleluniverse.fibers.Suspendable import net.corda.core.concurrent.CordaFuture import net.corda.core.contracts.* -import net.corda.core.flows.* +import net.corda.core.flows.FinalityFlow +import net.corda.core.flows.FlowLogic +import net.corda.core.flows.FlowLogicRefFactory +import net.corda.core.flows.SchedulableFlow import net.corda.core.identity.Party +import net.corda.core.context.Origin import net.corda.core.node.services.VaultService import net.corda.core.node.services.queryBy import net.corda.core.node.services.vault.DEFAULT_PAGE_NUM @@ -20,6 +24,7 @@ import net.corda.testing.chooseIdentity import net.corda.testing.contracts.DummyContract import net.corda.testing.dummyCommand import net.corda.testing.node.MockNetwork +import net.corda.testing.startFlow import org.junit.After import org.junit.Assert.* import org.junit.Before @@ -107,8 +112,8 @@ class ScheduledFlowTests { var countScheduledFlows = 0 nodeA.smm.track().updates.subscribe { if (it is StateMachineManager.Change.Add) { - val initiator = it.logic.stateMachine.flowInitiator - if (initiator is FlowInitiator.Scheduled) + val context = it.logic.stateMachine.context + if (context.origin is Origin.Scheduled) countScheduledFlows++ } } 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 95c0be2bd7..1adfc955b1 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,7 +1,6 @@ package net.corda.node.services.messaging import com.codahale.metrics.MetricRegistry -import com.nhaarman.mockito_kotlin.mock import net.corda.core.concurrent.CordaFuture import net.corda.core.crypto.generateKeyPair import net.corda.core.internal.concurrent.doneFuture @@ -14,7 +13,6 @@ import net.corda.node.services.api.MonitoringService import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.configureWithDevSSLCertificate import net.corda.node.services.network.NetworkMapCacheImpl -import net.corda.node.services.network.NetworkMapClient import net.corda.node.services.network.PersistentNetworkMapCache import net.corda.node.services.transactions.PersistentUniquenessProvider import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor diff --git a/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt index 0edcdedb27..4174882130 100644 --- a/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt @@ -9,8 +9,10 @@ import net.corda.core.contracts.StateAndRef import net.corda.core.crypto.random63BitValue import net.corda.core.flows.* import net.corda.core.identity.Party +import net.corda.core.internal.FlowStateMachine import net.corda.core.internal.concurrent.flatMap import net.corda.core.internal.concurrent.map +import net.corda.core.internal.uncheckedCast import net.corda.core.messaging.MessageRecipients import net.corda.core.node.services.PartyInfo import net.corda.core.node.services.queryBy @@ -505,7 +507,7 @@ class FlowFrameworkTests { val committerFiber = aliceNode.registerFlowFactory(WaitingFlows.Waiter::class) { WaitingFlows.Committer(it) - }.map { it.stateMachine } + }.map { it.stateMachine }.map { uncheckedCast, FlowStateMachine>(it) } val waiterStx = bobNode.services.startFlow(WaitingFlows.Waiter(stx, alice)).resultFuture mockNet.runNetwork() assertThat(waiterStx.getOrThrow()).isEqualTo(committerFiber.getOrThrow().resultFuture.getOrThrow()) diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt index ebf2de34a1..734d046401 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt @@ -20,6 +20,7 @@ import net.corda.testing.dummyCommand import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNodeParameters import net.corda.testing.singleIdentity +import net.corda.testing.startFlow import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Before diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt index 8b20175632..42a754ce01 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt @@ -15,13 +15,10 @@ import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.getOrThrow import net.corda.node.services.api.StartedNodeServices import net.corda.node.services.issueInvalidState -import net.corda.testing.ALICE_NAME -import net.corda.testing.MEGA_CORP_KEY +import net.corda.testing.* import net.corda.testing.contracts.DummyContract -import net.corda.testing.dummyCommand import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNodeParameters -import net.corda.testing.singleIdentity import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Before diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt index a6b1acfa38..3647a62d2b 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt @@ -30,6 +30,7 @@ import net.corda.testing.chooseIdentity import net.corda.testing.node.MockNetwork import net.corda.testing.rigorousMock import net.corda.testing.node.MockNodeParameters +import net.corda.testing.startFlow import org.junit.After import org.junit.Test import java.util.* diff --git a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt index 6a25314b1f..41a0bc7710 100644 --- a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt +++ b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt @@ -25,6 +25,7 @@ import net.corda.node.services.identity.InMemoryIdentityService import net.corda.testing.DEV_TRUST_ROOT import net.corda.testing.chooseIdentity import net.corda.testing.node.InMemoryMessagingNetwork +import net.corda.testing.startFlow import rx.Observable import java.time.LocalDate import java.util.* diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt index 0649fbe0fc..acd655123a 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt @@ -5,10 +5,18 @@ package net.corda.testing import com.nhaarman.mockito_kotlin.doCallRealMethod import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever +import net.corda.core.context.Actor +import net.corda.core.context.InvocationContext +import net.corda.core.context.Origin +import net.corda.core.context.AuthServiceId +import net.corda.core.flows.FlowLogic import net.corda.core.identity.CordaX500Name +import net.corda.core.internal.FlowStateMachine import net.corda.core.node.ServiceHub import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.seconds +import net.corda.core.utilities.getOrThrow +import net.corda.node.services.api.StartedNodeServices import net.corda.node.services.config.CertChainPolicyConfig import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.VerifierType @@ -16,7 +24,6 @@ import net.corda.nodeapi.User import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties -import java.net.URL import java.nio.file.Path /** @@ -73,3 +80,18 @@ fun testNodeConfiguration( doCallRealMethod().whenever(it).nodeKeystore } } + +fun testActor(owningLegalIdentity: CordaX500Name = CordaX500Name("Test Company Inc.", "London", "GB")) = Actor(Actor.Id("Only For Testing"), AuthServiceId("TEST"), owningLegalIdentity) + +fun testContext(owningLegalIdentity: CordaX500Name = CordaX500Name("Test Company Inc.", "London", "GB")) = InvocationContext.rpc(testActor(owningLegalIdentity)) + +/** + * Starts an already constructed flow. Note that you must be on the server thread to call this method. [InvocationContext] + * has origin [Origin.RPC] and actor with id "Only For Testing". + */ +fun StartedNodeServices.startFlow(logic: FlowLogic): FlowStateMachine = startFlow(logic, newContext()).getOrThrow() + +/** + * Creates a new [InvocationContext] for testing purposes. + */ +fun StartedNodeServices.newContext() = testContext(myInfo.chooseIdentity().name) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/RPCDriver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/RPCDriver.kt index 7a862e9296..3146a2c821 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/internal/RPCDriver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/internal/RPCDriver.kt @@ -5,6 +5,8 @@ import net.corda.client.rpc.internal.KryoClientSerializationScheme import net.corda.client.rpc.internal.RPCClient import net.corda.client.rpc.internal.RPCClientConfiguration import net.corda.core.concurrent.CordaFuture +import net.corda.core.context.AuthServiceId +import net.corda.core.context.Trace import net.corda.core.crypto.random63BitValue import net.corda.core.identity.CordaX500Name import net.corda.core.internal.concurrent.doneFuture @@ -233,6 +235,7 @@ fun rpcDriver( waitForNodesToFinish: Boolean = false, extraCordappPackagesToScan: List = emptyList(), notarySpecs: List = emptyList(), + externalTrace: Trace? = null, dsl: RPCDriverExposedDSLInterface.() -> A ) = genericDriver( driverDsl = RPCDriverDSL( @@ -247,7 +250,7 @@ fun rpcDriver( waitForNodesToFinish = waitForNodesToFinish, extraCordappPackagesToScan = extraCordappPackagesToScan, notarySpecs = notarySpecs - ) + ), externalTrace ), coerce = { it }, dsl = dsl, @@ -274,7 +277,7 @@ private class SingleUserSecurityManager(val rpcUser: User) : ActiveMQSecurityMan } data class RPCDriverDSL( - val driverDSL: DriverDSL + private val driverDSL: DriverDSL, private val externalTrace: Trace? ) : DriverDSLInternalInterface by driverDSL, RPCDriverInternalDSLInterface { private companion object { val notificationAddress = "notifications" @@ -354,7 +357,7 @@ data class RPCDriverDSL( override fun startInVmRpcClient(rpcOpsClass: Class, username: String, password: String, configuration: RPCClientConfiguration): CordaFuture { return driverDSL.executorService.fork { val client = RPCClient(inVmClientTransportConfiguration, configuration) - val connection = client.start(rpcOpsClass, username, password) + val connection = client.start(rpcOpsClass, username, password, externalTrace) driverDSL.shutdownManager.registerShutdown { connection.close() } @@ -398,7 +401,7 @@ data class RPCDriverDSL( ): CordaFuture { return driverDSL.executorService.fork { val client = RPCClient(ArtemisTcpTransport.tcpTransport(ConnectionDirection.Outbound(), rpcAddress, null), configuration) - val connection = client.start(rpcOpsClass, username, password) + val connection = client.start(rpcOpsClass, username, password, externalTrace) driverDSL.shutdownManager.registerShutdown { connection.close() } @@ -483,6 +486,7 @@ data class RPCDriverDSL( val userService = object : RPCUserService { override fun getUser(username: String): User? = if (username == rpcUser.username) rpcUser else null override val users: List get() = listOf(rpcUser) + override val id: AuthServiceId = AuthServiceId("RPC_DRIVER") } val rpcServer = RPCServer( ops, diff --git a/testing/test-common/src/main/resources/log4j2-test.xml b/testing/test-common/src/main/resources/log4j2-test.xml index 222a4a1778..0cfee94d15 100644 --- a/testing/test-common/src/main/resources/log4j2-test.xml +++ b/testing/test-common/src/main/resources/log4j2-test.xml @@ -5,7 +5,7 @@ - +