From ef7ccd3147f6735c0b62c40c9373da3fdead84a9 Mon Sep 17 00:00:00 2001 From: josecoll Date: Wed, 25 Oct 2017 13:54:34 +0100 Subject: [PATCH] Merge Open Source to Enterprise (#79) * Check array size before accessing * Review fixes * CORDA-540: Make Verifier work in AMQP mode (#1870) * reference to finance module via not hardcoded group ID (#1515) * generic way to reference to group id when loading finance.jar via cordapp * Fixed the node shell to work with the DataFeed class * Attempt to make NodeStatePersistenceTests more stable (#1895) By ensuring that the nodes are properly started and aware of each other before firing any flows through them. Also minor refactoring. * Disable unstable test on Windows (#1899) * CORDA-530 Don't soft-lock non-fungible states (#1794) * Don't run unlock query if nothing was locked * Constructors should not have side-effects * [CORDA-442] let Driver run without network map (#1890) * [CORDA-442] let Driver run without network map - Nodes started by driver run without a networkMapNode. - Driver does not take a networkMapStartStrategy anymore - a new parameter in the configuration "noNetworkMapServiceMode" allows for a node not to be a networkMapNode nor to connect to one. - Driver now waits for each node to write its own NodeInfo file to disk and then copies it into each other node. - When driver starts a node N, it waits for every node to be have N nodes in their network map. Note: the code to copy around the NodeInfo files was already in DemoBench, the NodeInfoFilesCopier class was just moved from DemoBench into core (I'm very open to core not being the best place, please advise) * Added missing cordappPackage dependencies. (#1894) * Eliminate circular dependency of NodeSchedulerService on ServiceHub. (#1891) * Update customSchemas documentation. (#1902) * [CORDA-694] Commands visibility for Oracles (without sacrificing privacy) (#1835) new checkCommandVisibility feature for Oracles * CORDA-599 PersistentNetworkMapCache no longer circularly depends on SH (#1652) * CORDA-725 - Change AMQP identifier to officially assigned value This does change our header format so pre-cached test files need regenerating * CORDA-725 - update changelog * CORDA-680 Update cordapp packages documentation (#1901) * Introduce MockNetworkParameters * Cordformation in Kotlin (#1873) Cordformation rewritten in kotlin. * Kotlin migration * Review Comments * CORDA-704: Implement `@DoNotImplement` annotation (#1903) * Enhance the API Scanner plugin to monitor class annotations. * Implement @DoNotImplement annotation, and apply it. * Update API definition. * Update API change detection to handle @DoNotImplement. * Document the `@DoNotImplement` annotation. * Experimental support for PostgreSQL (#1525) * Cash selection refactoring such that 3d party DB providers are only required to implement Coin Selection SQL logic. * Re-added debug logging statement. * Updated to include PR review feedback from VK * Refactoring following rebase from master. * Fix broken JUnits following rebase. * Use JDBC ResultSet getBlob() and added custom serializer to address concern raised by tomtau in PR. * Fix failing JUnits. * Experimental support for PostgreSQL: CashSelection done using window functions * Moved postgresql version information into corda/build.gradle * Using a PreparedStatement in CashSelectionPostgreSQLImpl * Changed the PostgreSQL Cash Selection implementation to use the new refactored AbstractCashSelection * * Retire MockServiceHubInternal (#1909) * Introduce rigorousMock * Add test-utils and node-driver to generated documentation * Fix-up: Bank Of Corda sample (#1912) In the previous version when running with `--role ISSUER` the application failed to start. The reason was that in spite of `quantity` and `currency` were optional, un-necessary `requestParams` been constructed regardless. * move SMM * Interface changes for multi-threading * CORDA-351: added dependency check plugin to gradle build script (#1911) * CORDA-351: added dependency check plugin to gradle build script * CORDA-351: Added suppression stub file with example * CORDA-351: added suppresionFile property * CORDA-435 - Ensure Kryo only tests use Kryo serializatin context Also correct lambda typos (from lamba) * Network map service REST API wrapper (#1907) * Network map client - WIP * Java doc and doc for doc site * remove javax.ws dependency * NetworkParameter -> NetworkParameters * move network map client to node * Fix jetty test dependencies * NetworkParameter -> NetworkParameters * Address PR issues * Address PR issues and unit test fix * Address PR issues * Fixing Bank-Of-Corda Demo in `master` (#1922) * Fix-up: Bank Of Corda sample Use correct CorDapp packages to scan (cherry picked from commit 2caa134) * Set adequate permissions for the nodes such that NodeExplorer can connect (cherry picked from commit ae88242) * Set adequate permissions for the nodes such that NodeExplorer can connect (cherry picked from commit ae88242) * Correct run configuration * Fix-up port numbers * CORDA-435 - AMQP serialisation cannot work with private vals They won't be reported as properties by the introspector and thus we will fail to find a constructor for them. This makes sense as we will be unable to serialise an object whose members we cannot read * CORDA-435 - AMQP enablement fixes AMQP has different serialization rules than Kryo surrounding the way we introspect objects to work out how to construct them * [CORDA-442] make MockNetwork not start a networkmap node (#1908) * [CORDA-442] make MockNetwork not start a networkmap node Now MockNetwork will put the appropriate NodeInfos inside each running node networkMapCache. Tests relating to networkmap node starting and interaction have been removed since they where relaying on MockNetwork * Minor fix for api checker script to support macOS * Retrofit changes from Enterprise PR #61 (#1934) * Introduce MockNodeParameters/Args (#1923) * CORDA-736 Add some new features to corda.jar via node.conf for testing (#1926) * CORDA-699 Add injection or modification of memory network messages (#1920) * Updated API stability changeset to reflect new schema attribute name. --- .ci/api-current.txt | 584 +++++++-------- .ci/check-api-changes.sh | 13 +- .../suppressedLibraries.xml | 14 + .../BankOfCordaDriverKt___Issue_Web.xml | 15 + .../BankOfCordaDriverKt___Run_Stack.xml | 15 + build.gradle | 9 + .../client/jackson/JacksonSupportTest.kt | 16 +- .../corda/client/rpc/CordaRPCClientTest.kt | 3 +- .../net/corda/client/rpc/RPCStabilityTests.kt | 1 + .../net/corda/client/rpc/RPCConnection.kt | 2 + .../confidential/SwapIdentitiesFlowTests.kt | 8 +- constants.properties | 2 +- .../kotlin/net/corda/core/DoNotImplement.kt | 18 + .../net/corda/core/contracts/Attachment.kt | 2 + .../core/contracts/ComponentGroupEnum.kt | 3 +- .../TransactionVerificationException.kt | 2 +- .../kotlin/net/corda/core/cordapp/Cordapp.kt | 6 +- .../net/corda/core/cordapp/CordappProvider.kt | 2 + .../corda/core/crypto/PartialMerkleTree.kt | 37 + .../kotlin/net/corda/core/flows/FlowLogic.kt | 31 +- .../net/corda/core/flows/FlowLogicRef.kt | 4 +- .../net/corda/core/flows/FlowSession.kt | 65 +- .../net/corda/core/identity/AbstractParty.kt | 2 + .../corda/core/internal/FlowStateMachine.kt | 11 +- .../net/corda/core/messaging/CordaRPCOps.kt | 1 - .../net/corda/core/messaging/FlowHandle.kt | 2 + .../kotlin/net/corda/core/messaging/RPCOps.kt | 3 + .../kotlin/net/corda/core/node/ServiceHub.kt | 4 +- .../core/node/services/AttachmentStorage.kt | 2 + .../node/services/ContractUpgradeService.kt | 2 + .../core/node/services/IdentityService.kt | 2 + .../node/services/KeyManagementService.kt | 2 + .../core/node/services/NetworkMapCache.kt | 38 +- .../core/node/services/TransactionStorage.kt | 2 + .../services/TransactionVerifierService.kt | 2 + .../corda/core/node/services/VaultService.kt | 3 +- .../core/node/services/vault/QueryCriteria.kt | 2 + .../node/services/vault/QueryCriteriaUtils.kt | 3 + .../core/serialization/SerializationAPI.kt | 27 +- .../core/transactions/BaseTransaction.kt | 2 + .../core/transactions/MerkleTransaction.kt | 91 ++- .../transactions/TransactionWithSignatures.kt | 2 + .../core/transactions/WireTransaction.kt | 11 +- .../core/concurrent/ConcurrencyUtilsTest.kt | 6 +- .../contracts/CompatibleTransactionTests.kt | 347 ++++++++- .../core/crypto/PartialMerkleTreeTest.kt | 71 +- .../net/corda/core/flows/AttachmentTests.kt | 22 +- .../core/flows/CollectSignaturesFlowTests.kt | 1 - .../core/flows/ContractUpgradeFlowTest.kt | 3 +- .../net/corda/core/flows/FinalityFlowTests.kt | 7 +- .../concurrent/CordaFutureImplTest.kt | 10 +- .../AttachmentSerializationTest.kt | 15 +- .../corda/core/utilities/KotlinUtilsTest.kt | 17 +- docs/build.gradle | 13 +- docs/source/api-persistence.rst | 6 +- docs/source/changelog.rst | 15 +- docs/source/corda-api.rst | 11 + docs/source/corda-configuration-file.rst | 10 + docs/source/example-code/build.gradle | 1 - .../mocknetwork/TutorialMockNetwork.kt | 111 +++ .../net/corda/docs/CustomVaultQueryTest.kt | 10 +- .../WorkflowTransactionBuildTutorialTest.kt | 6 +- docs/source/flow-testing.rst | 15 +- .../key-concepts-contract-constraints.rst | 18 +- docs/source/network-map.rst | 38 + docs/source/upgrade-notes.rst | 13 + .../selection/CashSelectionPostgreSQLImpl.kt | 83 +++ ...asset.cash.selection.AbstractCashSelection | 1 + .../contracts/asset/CashSelectionH2Test.kt | 11 +- .../main/java/net/corda/plugins/ScanApi.java | 62 +- .../kotlin/net/corda/plugins/CordappPlugin.kt | 4 +- .../main/kotlin/net/corda/plugins/Utils.kt | 11 + gradle-plugins/cordformation/build.gradle | 3 +- .../groovy/net/corda/plugins/Cordform.groovy | 153 ---- .../net/corda/plugins/Cordformation.groovy | 28 - .../main/groovy/net/corda/plugins/Node.groovy | 268 ------- .../main/kotlin/net/corda/plugins/Cordform.kt | 198 ++++++ .../kotlin/net/corda/plugins/Cordformation.kt | 33 + .../src/main/kotlin/net/corda/plugins/Node.kt | 290 ++++++++ node-api/build.gradle | 2 + .../net/corda/nodeapi/NodeInfoFilesCopier.kt | 165 +++++ .../kotlin/net/corda/nodeapi/VerifierApi.kt | 23 +- .../serialization/GeneratedAttachment.kt | 2 +- .../serialization/SerializationScheme.kt | 31 +- .../internal/serialization/amqp/Schema.kt | 11 +- .../corda/nodeapi}/NodeInfoFilesCopierTest.kt | 41 +- .../internal/AttachmentsClassLoaderTests.kt | 11 +- .../serialization/CordaClassResolverTests.kt | 37 +- .../serialization/SerializationTokenTest.kt | 5 +- .../serialization/amqp/EvolvabilityTests.kt | 34 +- .../amqp/EvolvabilityTests.addAdditionalParam | Bin 261 -> 261 bytes ...bilityTests.addAdditionalParamNotMandatory | Bin 273 -> 273 bytes .../EvolvabilityTests.addAndRemoveParameters | Bin 390 -> 390 bytes ...yTests.addMandatoryFieldWithAltConstructor | Bin 285 -> 285 bytes ...andatoryFieldWithAltConstructorUnAnnotated | Bin 296 -> 296 bytes ...dMandatoryFieldWithAltReorderedConstructor | Bin 387 -> 387 bytes ...FieldWithAltReorderedConstructorAndRemoval | Bin 403 -> 403 bytes .../amqp/EvolvabilityTests.changeSubType | Bin 616 -> 616 bytes .../amqp/EvolvabilityTests.multiVersion.1 | Bin 294 -> 294 bytes .../amqp/EvolvabilityTests.multiVersion.2 | Bin 327 -> 327 bytes .../amqp/EvolvabilityTests.multiVersion.3 | Bin 372 -> 372 bytes ...volvabilityTests.multiVersionWithRemoval.1 | Bin 338 -> 338 bytes ...volvabilityTests.multiVersionWithRemoval.2 | Bin 392 -> 392 bytes ...volvabilityTests.multiVersionWithRemoval.3 | Bin 425 -> 425 bytes .../amqp/EvolvabilityTests.removeParameters | Bin 378 -> 378 bytes ...vabilityTests.simpleOrderSwapDifferentType | Bin 311 -> 311 bytes .../EvolvabilityTests.simpleOrderSwapSameType | Bin 302 -> 302 bytes node/build.gradle | 14 + node/capsule/build.gradle | 6 +- .../kotlin/net/corda/node/BootTests.kt | 3 +- .../corda/node/NodeStartupPerformanceTests.kt | 4 +- .../node/services/BFTNotaryServiceTests.kt | 8 +- .../services/network/NodeInfoWatcherTest.kt | 9 +- .../network/PersistentNetworkMapCacheTest.kt | 4 +- .../services/messaging/P2PSecurityTest.kt | 7 +- .../test/node/NodeStatePersistenceTests.kt | 40 +- node/src/main/java/CordaCaplet.java | 116 ++- .../net/corda/node/internal/AbstractNode.kt | 189 ++--- .../corda/node/internal/CordaRPCOpsImpl.kt | 13 +- .../net/corda/node/internal/NodeStartup.kt | 1 + .../net/corda/node/internal/StartedNode.kt | 6 +- .../node/services/api/ServiceHubInternal.kt | 19 +- .../node/services/config/NodeConfiguration.kt | 2 + .../services/events/NodeSchedulerService.kt | 36 +- .../events/ScheduledActivityObserver.kt | 29 +- .../identity/PersistentIdentityService.kt | 2 +- .../keys/PersistentKeyManagementService.kt | 5 +- .../node/services/messaging/Messaging.kt | 35 +- .../services/messaging/NodeMessagingClient.kt | 14 +- .../node/services/network/NetworkMapClient.kt | 70 ++ .../node/services/network/NodeInfoWatcher.kt | 4 +- .../network/PersistentNetworkMapCache.kt | 69 +- .../persistence/HibernateConfiguration.kt | 1 + .../node/services/schema/HibernateObserver.kt | 21 +- .../statemachine/FlowLogicRefFactoryImpl.kt | 3 +- .../services/statemachine/FlowSessionImpl.kt | 40 +- .../statemachine/FlowStateMachineImpl.kt | 24 +- .../statemachine/StateMachineManager.kt | 671 ++---------------- .../statemachine/StateMachineManagerImpl.kt | 634 +++++++++++++++++ .../node/services/vault/NodeVaultService.kt | 8 +- .../services/vault/VaultSoftLockManager.kt | 72 +- .../net/corda/node/shell/InteractiveShell.kt | 36 +- .../corda/node/utilities/CordaPersistence.kt | 8 + .../net/corda/node/CordaRPCOpsImplTest.kt | 8 +- .../net/corda/node/InteractiveShellTest.kt | 6 +- .../cordapp/CordappProviderImplTests.kt | 3 +- .../node/messaging/InMemoryMessagingTests.kt | 20 +- .../node/messaging/TwoPartyTradeFlowTests.kt | 49 +- .../corda/node/services/NotaryChangeTests.kt | 2 - .../events/NodeSchedulerServiceTest.kt | 79 ++- .../services/events/ScheduledFlowTests.kt | 7 +- .../messaging/ArtemisMessagingTests.kt | 6 +- .../network/AbstractNetworkMapServiceTest.kt | 281 -------- .../network/HTTPNetworkMapClientTest.kt | 154 ++++ .../network/InMemoryNetworkMapServiceTest.kt | 9 - .../services/network/NetworkMapCacheTest.kt | 25 +- .../PersistentNetworkMapServiceTest.kt | 56 -- .../persistence/DBTransactionStorageTests.kt | 2 +- .../services/schema/HibernateObserverTests.kt | 3 +- .../statemachine/FlowFrameworkTests.kt | 65 +- .../transactions/NotaryServiceTests.kt | 10 +- .../ValidatingNotaryServiceTests.kt | 10 +- .../vault/VaultSoftLockManagerTest.kt | 162 +++++ .../NetworkisRegistrationHelperTest.kt | 12 +- .../contracts/asset/CashSelectionH2Test.kt | 5 +- .../flows/TwoPartyTradeFlowTest.kt | 75 +- samples/bank-of-corda-demo/build.gradle | 6 +- .../net/corda/bank/BankOfCordaDriver.kt | 54 +- samples/irs-demo/build.gradle | 6 +- .../net/corda/irs/api/NodeInterestRates.kt | 2 +- .../corda/irs/api/NodeInterestRatesTest.kt | 4 +- .../corda/netmap/simulation/IRSSimulation.kt | 2 +- .../net/corda/netmap/simulation/Simulation.kt | 124 +--- .../net/corda/notarydemo/BFTNotaryCordform.kt | 4 +- .../main/kotlin/net/corda/notarydemo/Clean.kt | 2 +- .../corda/notarydemo/CustomNotaryCordform.kt | 4 +- .../corda/notarydemo/RaftNotaryCordform.kt | 4 +- .../corda/notarydemo/SingleNotaryCordform.kt | 4 +- samples/simm-valuation-demo/build.gradle | 8 +- samples/trader-demo/build.gradle | 8 +- .../node/testing/MockServiceHubInternal.kt | 90 --- .../kotlin/net/corda/testing/NodeTestUtils.kt | 53 +- .../kotlin/net/corda/testing/RPCDriver.kt | 2 - .../kotlin/net/corda/testing/driver/Driver.kt | 163 +++-- .../testing/driver/NetworkMapStartStrategy.kt | 23 - .../testing/node/InMemoryMessagingNetwork.kt | 12 +- .../corda/testing/node/MockNetworkMapCache.kt | 6 +- .../kotlin/net/corda/testing/node/MockNode.kt | 273 ++++--- .../net/corda/testing/node/MockServices.kt | 3 +- .../net/corda/testing/node/SimpleNode.kt | 4 +- .../net/corda/smoketesting/NodeProcess.kt | 4 +- .../kotlin/net/corda/testing/CoreTestUtils.kt | 19 + .../corda/testing/SerializationTestHelpers.kt | 6 +- .../model/DemoBenchNodeInfoFilesCopier.kt | 35 + .../corda/demobench/model/NodeController.kt | 2 +- .../demobench/model/NodeInfoFilesCopier.kt | 133 ---- .../net/corda/demobench/pty/ZeroFilterTest.kt | 11 +- .../net/corda/verifier/VerifierDriver.kt | 2 - .../net/corda/verifier/VerifierTests.kt | 26 +- .../kotlin/net/corda/verifier/Verifier.kt | 31 +- 200 files changed, 4549 insertions(+), 3192 deletions(-) create mode 100644 .ci/dependency-checker/suppressedLibraries.xml create mode 100644 .idea/runConfigurations/BankOfCordaDriverKt___Issue_Web.xml create mode 100644 .idea/runConfigurations/BankOfCordaDriverKt___Run_Stack.xml create mode 100644 core/src/main/kotlin/net/corda/core/DoNotImplement.kt create mode 100644 docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/mocknetwork/TutorialMockNetwork.kt create mode 100644 docs/source/network-map.rst create mode 100644 finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionPostgreSQLImpl.kt delete mode 100644 gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Cordform.groovy delete mode 100644 gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Cordformation.groovy delete mode 100644 gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Node.groovy create mode 100644 gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt create mode 100644 gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordformation.kt create mode 100644 gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt create mode 100644 node-api/src/main/kotlin/net/corda/nodeapi/NodeInfoFilesCopier.kt rename {tools/demobench/src/test/kotlin/net/corda/demobench/model => node-api/src/test/kotlin/net/corda/nodeapi}/NodeInfoFilesCopierTest.kt (73%) create mode 100644 node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt create mode 100644 node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt delete mode 100644 node/src/test/kotlin/net/corda/node/services/network/AbstractNetworkMapServiceTest.kt create mode 100644 node/src/test/kotlin/net/corda/node/services/network/HTTPNetworkMapClientTest.kt delete mode 100644 node/src/test/kotlin/net/corda/node/services/network/InMemoryNetworkMapServiceTest.kt delete mode 100644 node/src/test/kotlin/net/corda/node/services/network/PersistentNetworkMapServiceTest.kt create mode 100644 node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt delete mode 100644 testing/node-driver/src/main/kotlin/net/corda/node/testing/MockServiceHubInternal.kt delete mode 100644 testing/node-driver/src/main/kotlin/net/corda/testing/driver/NetworkMapStartStrategy.kt create mode 100644 tools/demobench/src/main/kotlin/net/corda/demobench/model/DemoBenchNodeInfoFilesCopier.kt delete mode 100644 tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeInfoFilesCopier.kt diff --git a/.ci/api-current.txt b/.ci/api-current.txt index 6e8e8df371..e66520b6f2 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -1,4 +1,4 @@ -public class net.corda.core.CordaException extends java.lang.Exception implements net.corda.core.CordaThrowable +@net.corda.core.serialization.CordaSerializable public class net.corda.core.CordaException extends java.lang.Exception implements net.corda.core.CordaThrowable public () public (String) public (String, String, Throwable) @@ -14,7 +14,7 @@ public class net.corda.core.CordaException extends java.lang.Exception implement public void setMessage(String) public void setOriginalExceptionClassName(String) ## -public class net.corda.core.CordaRuntimeException extends java.lang.RuntimeException implements net.corda.core.CordaThrowable +@net.corda.core.serialization.CordaSerializable public class net.corda.core.CordaRuntimeException extends java.lang.RuntimeException implements net.corda.core.CordaThrowable public (String) public (String, String, Throwable) public (String, Throwable) @@ -29,7 +29,7 @@ public class net.corda.core.CordaRuntimeException extends java.lang.RuntimeExcep public void setMessage(String) public void setOriginalExceptionClassName(String) ## -public interface net.corda.core.CordaThrowable +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.CordaThrowable public abstract void addSuppressed(Throwable[]) @org.jetbrains.annotations.Nullable public abstract String getOriginalExceptionClassName() @org.jetbrains.annotations.Nullable public abstract String getOriginalMessage() @@ -37,6 +37,8 @@ public interface net.corda.core.CordaThrowable public abstract void setMessage(String) public abstract void setOriginalExceptionClassName(String) ## +public @interface net.corda.core.DoNotImplement +## public final class net.corda.core.Utils extends java.lang.Object @org.jetbrains.annotations.NotNull public static final net.corda.core.concurrent.CordaFuture toFuture(rx.Observable) @org.jetbrains.annotations.NotNull public static final rx.Observable toObservable(net.corda.core.concurrent.CordaFuture) @@ -51,11 +53,11 @@ 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() ## -public final class net.corda.core.contracts.AlwaysAcceptAttachmentConstraint extends java.lang.Object implements net.corda.core.contracts.AttachmentConstraint +@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 ## -public final class net.corda.core.contracts.Amount extends java.lang.Object implements java.lang.Comparable +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.Amount extends java.lang.Object implements java.lang.Comparable public (long, Object) public (long, java.math.BigDecimal, Object) public int compareTo(net.corda.core.contracts.Amount) @@ -95,7 +97,7 @@ public static final class net.corda.core.contracts.Amount$Companion extends java @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.Amount sumOrZero(Iterable, Object) @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.Amount zero(Object) ## -public final class net.corda.core.contracts.AmountTransfer extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.AmountTransfer extends java.lang.Object public (long, Object, Object, Object) @org.jetbrains.annotations.NotNull public final List apply(List, Object) @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.AmountTransfer copy(long, Object, Object, Object) @@ -119,24 +121,24 @@ public static final class net.corda.core.contracts.AmountTransfer$Companion exte @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.AmountTransfer fromDecimal(java.math.BigDecimal, Object, Object, Object, java.math.RoundingMode) @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.AmountTransfer zero(Object, Object, Object) ## -public interface net.corda.core.contracts.Attachment extends net.corda.core.contracts.NamedByHash +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.contracts.Attachment extends net.corda.core.contracts.NamedByHash public abstract void extractFile(String, java.io.OutputStream) @org.jetbrains.annotations.NotNull public abstract List getSigners() @org.jetbrains.annotations.NotNull public abstract java.io.InputStream open() @org.jetbrains.annotations.NotNull public abstract jar.JarInputStream openAsJAR() ## -public interface net.corda.core.contracts.AttachmentConstraint +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.contracts.AttachmentConstraint public abstract boolean isSatisfiedBy(net.corda.core.contracts.Attachment) ## -public final class net.corda.core.contracts.AttachmentResolutionException extends net.corda.core.flows.FlowException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.AttachmentResolutionException extends net.corda.core.flows.FlowException public (net.corda.core.crypto.SecureHash) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getHash() ## -public final class net.corda.core.contracts.AutomaticHashConstraint extends java.lang.Object implements net.corda.core.contracts.AttachmentConstraint +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.AutomaticHashConstraint 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.AutomaticHashConstraint INSTANCE ## -public final class net.corda.core.contracts.Command extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.Command extends java.lang.Object public (net.corda.core.contracts.CommandData, java.security.PublicKey) public (net.corda.core.contracts.CommandData, List) @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.CommandData component1() @@ -159,9 +161,9 @@ public final class net.corda.core.contracts.CommandAndState extends java.lang.Ob public int hashCode() public String toString() ## -public interface net.corda.core.contracts.CommandData +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.contracts.CommandData ## -public final class net.corda.core.contracts.CommandWithParties extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.CommandWithParties extends java.lang.Object public (List, List, net.corda.core.contracts.CommandData) @org.jetbrains.annotations.NotNull public final List component1() @org.jetbrains.annotations.NotNull public final List component2() @@ -179,10 +181,10 @@ public final class net.corda.core.contracts.ComponentGroupEnum extends java.lang public static net.corda.core.contracts.ComponentGroupEnum valueOf(String) public static net.corda.core.contracts.ComponentGroupEnum[] values() ## -public interface net.corda.core.contracts.Contract +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.contracts.Contract public abstract void verify(net.corda.core.transactions.LedgerTransaction) ## -public final class net.corda.core.contracts.ContractAttachment extends java.lang.Object implements net.corda.core.contracts.Attachment +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.ContractAttachment extends java.lang.Object implements net.corda.core.contracts.Attachment public (net.corda.core.contracts.Attachment, String) public void extractFile(String, java.io.OutputStream) @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.Attachment getAttachment() @@ -192,19 +194,19 @@ public final class net.corda.core.contracts.ContractAttachment extends java.lang @org.jetbrains.annotations.NotNull public java.io.InputStream open() @org.jetbrains.annotations.NotNull public jar.JarInputStream openAsJAR() ## -public interface net.corda.core.contracts.ContractState +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.contracts.ContractState @org.jetbrains.annotations.NotNull public abstract List getParticipants() ## public final class net.corda.core.contracts.ContractsDSL extends java.lang.Object @org.jetbrains.annotations.NotNull public static final net.corda.core.contracts.CommandWithParties requireSingleCommand(Collection, Class) public static final Object requireThat(kotlin.jvm.functions.Function1) ## -public interface net.corda.core.contracts.FungibleAsset extends net.corda.core.contracts.OwnableState +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.contracts.FungibleAsset extends net.corda.core.contracts.OwnableState @org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.Amount getAmount() @org.jetbrains.annotations.NotNull public abstract Collection getExitKeys() @org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.FungibleAsset withNewOwnerAndAmount(net.corda.core.contracts.Amount, net.corda.core.identity.AbstractParty) ## -public final class net.corda.core.contracts.HashAttachmentConstraint extends java.lang.Object implements net.corda.core.contracts.AttachmentConstraint +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.HashAttachmentConstraint extends java.lang.Object implements net.corda.core.contracts.AttachmentConstraint public (net.corda.core.crypto.SecureHash) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component1() @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.HashAttachmentConstraint copy(net.corda.core.crypto.SecureHash) @@ -214,11 +216,11 @@ public final class net.corda.core.contracts.HashAttachmentConstraint extends jav public boolean isSatisfiedBy(net.corda.core.contracts.Attachment) public String toString() ## -public final class net.corda.core.contracts.InsufficientBalanceException extends net.corda.core.flows.FlowException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.InsufficientBalanceException extends net.corda.core.flows.FlowException public (net.corda.core.contracts.Amount) @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.Amount getAmountMissing() ## -public final class net.corda.core.contracts.Issued extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.Issued extends java.lang.Object public (net.corda.core.contracts.PartyAndReference, Object) @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.PartyAndReference component1() @org.jetbrains.annotations.NotNull public final Object component2() @@ -232,20 +234,20 @@ public final class net.corda.core.contracts.Issued extends java.lang.Object public @interface net.corda.core.contracts.LegalProseReference public abstract String uri() ## -public interface net.corda.core.contracts.LinearState extends net.corda.core.contracts.ContractState +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.contracts.LinearState extends net.corda.core.contracts.ContractState @org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.UniqueIdentifier getLinearId() ## -public interface net.corda.core.contracts.MoveCommand extends net.corda.core.contracts.CommandData +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.contracts.MoveCommand extends net.corda.core.contracts.CommandData @org.jetbrains.annotations.Nullable public abstract Class getContract() ## public interface net.corda.core.contracts.NamedByHash @org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.SecureHash getId() ## -public interface net.corda.core.contracts.OwnableState extends net.corda.core.contracts.ContractState +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.contracts.OwnableState extends net.corda.core.contracts.ContractState @org.jetbrains.annotations.NotNull public abstract net.corda.core.identity.AbstractParty getOwner() @org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.CommandAndState withNewOwner(net.corda.core.identity.AbstractParty) ## -public final class net.corda.core.contracts.PartyAndReference extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.PartyAndReference extends java.lang.Object public (net.corda.core.identity.AbstractParty, net.corda.core.utilities.OpaqueBytes) @org.jetbrains.annotations.NotNull public final net.corda.core.identity.AbstractParty component1() @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.OpaqueBytes component2() @@ -256,7 +258,7 @@ public final class net.corda.core.contracts.PartyAndReference extends java.lang. public int hashCode() @org.jetbrains.annotations.NotNull public String toString() ## -public final class net.corda.core.contracts.PrivacySalt extends net.corda.core.utilities.OpaqueBytes +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.PrivacySalt extends net.corda.core.utilities.OpaqueBytes public () public (byte[]) ## @@ -264,7 +266,7 @@ public final class net.corda.core.contracts.Requirements extends java.lang.Objec public final void using(String, boolean) public static final net.corda.core.contracts.Requirements INSTANCE ## -public interface net.corda.core.contracts.SchedulableState extends net.corda.core.contracts.ContractState +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.contracts.SchedulableState extends net.corda.core.contracts.ContractState @org.jetbrains.annotations.Nullable public abstract net.corda.core.contracts.ScheduledActivity nextScheduledActivity(net.corda.core.contracts.StateRef, net.corda.core.flows.FlowLogicRefFactory) ## public interface net.corda.core.contracts.Scheduled @@ -316,7 +318,7 @@ public final class net.corda.core.contracts.StateAndContract extends java.lang.O public int hashCode() public String toString() ## -public final class net.corda.core.contracts.StateAndRef extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.StateAndRef extends java.lang.Object public (net.corda.core.contracts.TransactionState, net.corda.core.contracts.StateRef) @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.TransactionState component1() @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.StateRef component2() @@ -327,7 +329,7 @@ public final class net.corda.core.contracts.StateAndRef extends java.lang.Object public int hashCode() public String toString() ## -public final class net.corda.core.contracts.StateRef extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.StateRef extends java.lang.Object public (net.corda.core.crypto.SecureHash, int) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component1() public final int component2() @@ -342,7 +344,7 @@ public final class net.corda.core.contracts.Structures extends java.lang.Object @org.jetbrains.annotations.NotNull public static final net.corda.core.crypto.SecureHash hash(net.corda.core.contracts.ContractState) @org.jetbrains.annotations.NotNull public static final net.corda.core.contracts.Amount withoutIssuer(net.corda.core.contracts.Amount) ## -public abstract class net.corda.core.contracts.TimeWindow extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.contracts.TimeWindow extends java.lang.Object public () @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.contracts.TimeWindow between(java.time.Instant, java.time.Instant) public abstract boolean contains(java.time.Instant) @@ -365,11 +367,11 @@ public static final class net.corda.core.contracts.TimeWindow$Companion extends public interface net.corda.core.contracts.TokenizableAssetInfo @org.jetbrains.annotations.NotNull public abstract java.math.BigDecimal getDisplayTokenSize() ## -public final class net.corda.core.contracts.TransactionResolutionException extends net.corda.core.flows.FlowException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.TransactionResolutionException extends net.corda.core.flows.FlowException public (net.corda.core.crypto.SecureHash) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getHash() ## -public final class net.corda.core.contracts.TransactionState extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.TransactionState extends java.lang.Object public (net.corda.core.contracts.ContractState, String, net.corda.core.identity.Party) public (net.corda.core.contracts.ContractState, String, net.corda.core.identity.Party, Integer) public (net.corda.core.contracts.ContractState, String, net.corda.core.identity.Party, Integer, net.corda.core.contracts.AttachmentConstraint) @@ -390,51 +392,51 @@ public final class net.corda.core.contracts.TransactionState extends java.lang.O ## public final class net.corda.core.contracts.TransactionStateKt extends java.lang.Object ## -public abstract class net.corda.core.contracts.TransactionVerificationException extends net.corda.core.flows.FlowException +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.contracts.TransactionVerificationException extends net.corda.core.flows.FlowException @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getTxId() ## -public static final class net.corda.core.contracts.TransactionVerificationException$ContractConstraintRejection extends net.corda.core.contracts.TransactionVerificationException +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.contracts.TransactionVerificationException$ContractConstraintRejection extends net.corda.core.contracts.TransactionVerificationException public (net.corda.core.crypto.SecureHash, String) ## -public static final class net.corda.core.contracts.TransactionVerificationException$ContractCreationError extends net.corda.core.contracts.TransactionVerificationException +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.contracts.TransactionVerificationException$ContractCreationError extends net.corda.core.contracts.TransactionVerificationException public (net.corda.core.crypto.SecureHash, String, Throwable) ## -public static final class net.corda.core.contracts.TransactionVerificationException$ContractRejection extends net.corda.core.contracts.TransactionVerificationException +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.contracts.TransactionVerificationException$ContractRejection extends net.corda.core.contracts.TransactionVerificationException public (net.corda.core.crypto.SecureHash, net.corda.core.contracts.Contract, Throwable) ## -public static final class net.corda.core.contracts.TransactionVerificationException$Direction extends java.lang.Enum +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.contracts.TransactionVerificationException$Direction extends java.lang.Enum protected (String, int) public static net.corda.core.contracts.TransactionVerificationException$Direction valueOf(String) public static net.corda.core.contracts.TransactionVerificationException$Direction[] values() ## -public static final class net.corda.core.contracts.TransactionVerificationException$DuplicateInputStates extends net.corda.core.contracts.TransactionVerificationException +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.contracts.TransactionVerificationException$DuplicateInputStates extends net.corda.core.contracts.TransactionVerificationException public (net.corda.core.crypto.SecureHash, net.corda.core.utilities.NonEmptySet) @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.NonEmptySet getDuplicates() ## -public static final class net.corda.core.contracts.TransactionVerificationException$InvalidNotaryChange extends net.corda.core.contracts.TransactionVerificationException +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.contracts.TransactionVerificationException$InvalidNotaryChange extends net.corda.core.contracts.TransactionVerificationException public (net.corda.core.crypto.SecureHash) ## -public static final class net.corda.core.contracts.TransactionVerificationException$MissingAttachmentRejection extends net.corda.core.contracts.TransactionVerificationException +@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) ## -public static final class net.corda.core.contracts.TransactionVerificationException$MoreThanOneNotary extends net.corda.core.contracts.TransactionVerificationException +@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) ## -public static final class net.corda.core.contracts.TransactionVerificationException$NotaryChangeInWrongTransactionType extends net.corda.core.contracts.TransactionVerificationException +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.contracts.TransactionVerificationException$NotaryChangeInWrongTransactionType extends net.corda.core.contracts.TransactionVerificationException public (net.corda.core.crypto.SecureHash, net.corda.core.identity.Party, net.corda.core.identity.Party) ## -public static final class net.corda.core.contracts.TransactionVerificationException$SignersMissing extends net.corda.core.contracts.TransactionVerificationException +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.contracts.TransactionVerificationException$SignersMissing extends net.corda.core.contracts.TransactionVerificationException public (net.corda.core.crypto.SecureHash, List) ## -public static final class net.corda.core.contracts.TransactionVerificationException$TransactionMissingEncumbranceException extends net.corda.core.contracts.TransactionVerificationException +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.contracts.TransactionVerificationException$TransactionMissingEncumbranceException extends net.corda.core.contracts.TransactionVerificationException public (net.corda.core.crypto.SecureHash, int, net.corda.core.contracts.TransactionVerificationException$Direction) ## -public abstract class net.corda.core.contracts.TypeOnlyCommandData extends java.lang.Object implements net.corda.core.contracts.CommandData +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.contracts.TypeOnlyCommandData extends java.lang.Object implements net.corda.core.contracts.CommandData public () public boolean equals(Object) public int hashCode() ## -public final class net.corda.core.contracts.UniqueIdentifier extends java.lang.Object implements java.lang.Comparable +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.UniqueIdentifier extends java.lang.Object implements java.lang.Comparable public () public (String, UUID) public int compareTo(net.corda.core.contracts.UniqueIdentifier) @@ -451,11 +453,11 @@ public final class net.corda.core.contracts.UniqueIdentifier extends java.lang.O public static final class net.corda.core.contracts.UniqueIdentifier$Companion extends java.lang.Object @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.UniqueIdentifier fromString(String) ## -public interface net.corda.core.contracts.UpgradedContract extends net.corda.core.contracts.Contract +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.contracts.UpgradedContract extends net.corda.core.contracts.Contract @org.jetbrains.annotations.NotNull public abstract String getLegacyContract() @org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.ContractState upgrade(net.corda.core.contracts.ContractState) ## -public interface net.corda.core.cordapp.Cordapp +@net.corda.core.DoNotImplement public interface net.corda.core.cordapp.Cordapp @org.jetbrains.annotations.NotNull public abstract List getContractClassNames() @org.jetbrains.annotations.NotNull public abstract List getCordappClasses() @org.jetbrains.annotations.NotNull public abstract Set getCustomSchemas() @@ -474,7 +476,7 @@ public final class net.corda.core.cordapp.CordappContext extends java.lang.Objec @org.jetbrains.annotations.NotNull public final ClassLoader getClassLoader() @org.jetbrains.annotations.NotNull public final net.corda.core.cordapp.Cordapp getCordapp() ## -public interface net.corda.core.cordapp.CordappProvider +@net.corda.core.DoNotImplement public interface net.corda.core.cordapp.CordappProvider @org.jetbrains.annotations.NotNull public abstract net.corda.core.cordapp.CordappContext getAppContext() @org.jetbrains.annotations.Nullable public abstract net.corda.core.crypto.SecureHash getContractAttachmentID(String) ## @@ -489,7 +491,7 @@ public class net.corda.core.crypto.Base58 extends java.lang.Object public static java.math.BigInteger decodeToBigInteger(String) public static String encode(byte[]) ## -public final class net.corda.core.crypto.CompositeKey extends java.lang.Object implements java.security.PublicKey +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.crypto.CompositeKey extends java.lang.Object implements java.security.PublicKey public final void checkValidity() public boolean equals(Object) @org.jetbrains.annotations.NotNull public String getAlgorithm() @@ -515,7 +517,7 @@ public static final class net.corda.core.crypto.CompositeKey$Companion extends j @org.jetbrains.annotations.NotNull public final java.security.PublicKey getInstance(org.bouncycastle.asn1.ASN1Primitive) @org.jetbrains.annotations.NotNull public final java.security.PublicKey getInstance(byte[]) ## -public static final class net.corda.core.crypto.CompositeKey$NodeAndWeight extends org.bouncycastle.asn1.ASN1Object implements java.lang.Comparable +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.crypto.CompositeKey$NodeAndWeight extends org.bouncycastle.asn1.ASN1Object implements java.lang.Comparable public (java.security.PublicKey, int) public int compareTo(net.corda.core.crypto.CompositeKey$NodeAndWeight) @org.jetbrains.annotations.NotNull public final java.security.PublicKey component1() @@ -565,7 +567,7 @@ public static final class net.corda.core.crypto.CompositeSignature$State extends public int hashCode() public String toString() ## -public final class net.corda.core.crypto.CompositeSignaturesWithKeys extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.crypto.CompositeSignaturesWithKeys extends java.lang.Object public (List) @org.jetbrains.annotations.NotNull public final List component1() @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.CompositeSignaturesWithKeys copy(List) @@ -664,10 +666,10 @@ public final class net.corda.core.crypto.CryptoUtils extends java.lang.Object public static final boolean verify(java.security.PublicKey, byte[], net.corda.core.crypto.DigitalSignature) public static final boolean verify(java.security.PublicKey, byte[], byte[]) ## -public class net.corda.core.crypto.DigitalSignature extends net.corda.core.utilities.OpaqueBytes +@net.corda.core.serialization.CordaSerializable public class net.corda.core.crypto.DigitalSignature extends net.corda.core.utilities.OpaqueBytes public (byte[]) ## -public static class net.corda.core.crypto.DigitalSignature$WithKey extends net.corda.core.crypto.DigitalSignature +@net.corda.core.serialization.CordaSerializable public static class net.corda.core.crypto.DigitalSignature$WithKey extends net.corda.core.crypto.DigitalSignature public (java.security.PublicKey, byte[]) @org.jetbrains.annotations.NotNull public final java.security.PublicKey getBy() public final boolean isValid(byte[]) @@ -703,7 +705,7 @@ public static final class net.corda.core.crypto.MerkleTree$Node extends net.cord public int hashCode() public String toString() ## -public final class net.corda.core.crypto.MerkleTreeException extends net.corda.core.CordaException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.crypto.MerkleTreeException extends net.corda.core.CordaException public (String) @org.jetbrains.annotations.NotNull public final String getReason() ## @@ -712,7 +714,7 @@ public final class net.corda.core.crypto.NullKeys extends java.lang.Object @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.TransactionSignature getNULL_SIGNATURE() public static final net.corda.core.crypto.NullKeys INSTANCE ## -public static final class net.corda.core.crypto.NullKeys$NullPublicKey extends java.lang.Object implements java.security.PublicKey, java.lang.Comparable +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.crypto.NullKeys$NullPublicKey extends java.lang.Object implements java.security.PublicKey, java.lang.Comparable public int compareTo(java.security.PublicKey) @org.jetbrains.annotations.NotNull public String getAlgorithm() @org.jetbrains.annotations.NotNull public byte[] getEncoded() @@ -720,7 +722,7 @@ public static final class net.corda.core.crypto.NullKeys$NullPublicKey extends j @org.jetbrains.annotations.NotNull public String toString() public static final net.corda.core.crypto.NullKeys$NullPublicKey INSTANCE ## -public final class net.corda.core.crypto.PartialMerkleTree extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.crypto.PartialMerkleTree extends java.lang.Object public (net.corda.core.crypto.PartialMerkleTree$PartialTree) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.PartialMerkleTree$PartialTree getRoot() public final boolean verify(net.corda.core.crypto.SecureHash, List) @@ -730,9 +732,9 @@ public static final class net.corda.core.crypto.PartialMerkleTree$Companion exte @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.PartialMerkleTree build(net.corda.core.crypto.MerkleTree, List) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash rootAndUsedHashes(net.corda.core.crypto.PartialMerkleTree$PartialTree, List) ## -public abstract static class net.corda.core.crypto.PartialMerkleTree$PartialTree extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public abstract static class net.corda.core.crypto.PartialMerkleTree$PartialTree extends java.lang.Object ## -public static final class net.corda.core.crypto.PartialMerkleTree$PartialTree$IncludedLeaf extends net.corda.core.crypto.PartialMerkleTree$PartialTree +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.crypto.PartialMerkleTree$PartialTree$IncludedLeaf extends net.corda.core.crypto.PartialMerkleTree$PartialTree public (net.corda.core.crypto.SecureHash) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component1() @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.PartialMerkleTree$PartialTree$IncludedLeaf copy(net.corda.core.crypto.SecureHash) @@ -741,7 +743,7 @@ public static final class net.corda.core.crypto.PartialMerkleTree$PartialTree$In public int hashCode() public String toString() ## -public static final class net.corda.core.crypto.PartialMerkleTree$PartialTree$Leaf extends net.corda.core.crypto.PartialMerkleTree$PartialTree +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.crypto.PartialMerkleTree$PartialTree$Leaf extends net.corda.core.crypto.PartialMerkleTree$PartialTree public (net.corda.core.crypto.SecureHash) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component1() @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.PartialMerkleTree$PartialTree$Leaf copy(net.corda.core.crypto.SecureHash) @@ -750,7 +752,7 @@ public static final class net.corda.core.crypto.PartialMerkleTree$PartialTree$Le public int hashCode() public String toString() ## -public static final class net.corda.core.crypto.PartialMerkleTree$PartialTree$Node extends net.corda.core.crypto.PartialMerkleTree$PartialTree +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.crypto.PartialMerkleTree$PartialTree$Node extends net.corda.core.crypto.PartialMerkleTree$PartialTree public (net.corda.core.crypto.PartialMerkleTree$PartialTree, net.corda.core.crypto.PartialMerkleTree$PartialTree) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.PartialMerkleTree$PartialTree component1() @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.PartialMerkleTree$PartialTree component2() @@ -761,7 +763,7 @@ public static final class net.corda.core.crypto.PartialMerkleTree$PartialTree$No public int hashCode() public String toString() ## -public abstract class net.corda.core.crypto.SecureHash extends net.corda.core.utilities.OpaqueBytes +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.crypto.SecureHash extends net.corda.core.utilities.OpaqueBytes @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash$SHA256 hashConcat(net.corda.core.crypto.SecureHash) @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.crypto.SecureHash$SHA256 parse(String) @org.jetbrains.annotations.NotNull public final String prefixChars(int) @@ -781,14 +783,14 @@ public static final class net.corda.core.crypto.SecureHash$Companion extends jav @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash$SHA256 sha256(byte[]) @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash$SHA256 sha256Twice(byte[]) ## -public static final class net.corda.core.crypto.SecureHash$SHA256 extends net.corda.core.crypto.SecureHash +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.crypto.SecureHash$SHA256 extends net.corda.core.crypto.SecureHash public (byte[]) ## public final class net.corda.core.crypto.SecureHashKt extends java.lang.Object @org.jetbrains.annotations.NotNull public static final net.corda.core.crypto.SecureHash$SHA256 sha256(net.corda.core.utilities.OpaqueBytes) @org.jetbrains.annotations.NotNull public static final net.corda.core.crypto.SecureHash$SHA256 sha256(byte[]) ## -public final class net.corda.core.crypto.SignableData extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.crypto.SignableData extends java.lang.Object public (net.corda.core.crypto.SecureHash, net.corda.core.crypto.SignatureMetadata) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component1() @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SignatureMetadata component2() @@ -799,7 +801,7 @@ public final class net.corda.core.crypto.SignableData extends java.lang.Object public int hashCode() public String toString() ## -public final class net.corda.core.crypto.SignatureMetadata extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.crypto.SignatureMetadata extends java.lang.Object public (int, int) public final int component1() public final int component2() @@ -837,14 +839,14 @@ public final class net.corda.core.crypto.SignatureScheme extends java.lang.Objec public int hashCode() public String toString() ## -public class net.corda.core.crypto.SignedData extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public class net.corda.core.crypto.SignedData extends java.lang.Object public (net.corda.core.serialization.SerializedBytes, net.corda.core.crypto.DigitalSignature$WithKey) @org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializedBytes getRaw() @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.DigitalSignature$WithKey getSig() @org.jetbrains.annotations.NotNull public final Object verified() protected void verifyData(Object) ## -public final class net.corda.core.crypto.TransactionSignature extends net.corda.core.crypto.DigitalSignature +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.crypto.TransactionSignature extends net.corda.core.crypto.DigitalSignature public (byte[], java.security.PublicKey, net.corda.core.crypto.SignatureMetadata) public boolean equals(Object) @org.jetbrains.annotations.NotNull public final java.security.PublicKey getBy() @@ -868,10 +870,10 @@ public abstract static class net.corda.core.flows.AbstractStateReplacementFlow$A public static final class net.corda.core.flows.AbstractStateReplacementFlow$Acceptor$Companion extends java.lang.Object @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ProgressTracker tracker() ## -public static final class net.corda.core.flows.AbstractStateReplacementFlow$Acceptor$Companion$APPROVING extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.AbstractStateReplacementFlow$Acceptor$Companion$APPROVING extends net.corda.core.utilities.ProgressTracker$Step public static final net.corda.core.flows.AbstractStateReplacementFlow$Acceptor$Companion$APPROVING INSTANCE ## -public static final class net.corda.core.flows.AbstractStateReplacementFlow$Acceptor$Companion$VERIFYING extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.AbstractStateReplacementFlow$Acceptor$Companion$VERIFYING extends net.corda.core.utilities.ProgressTracker$Step public static final net.corda.core.flows.AbstractStateReplacementFlow$Acceptor$Companion$VERIFYING INSTANCE ## public abstract static class net.corda.core.flows.AbstractStateReplacementFlow$Instigator extends net.corda.core.flows.FlowLogic @@ -887,13 +889,13 @@ public abstract static class net.corda.core.flows.AbstractStateReplacementFlow$I public static final class net.corda.core.flows.AbstractStateReplacementFlow$Instigator$Companion extends java.lang.Object @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ProgressTracker tracker() ## -public static final class net.corda.core.flows.AbstractStateReplacementFlow$Instigator$Companion$NOTARY extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.AbstractStateReplacementFlow$Instigator$Companion$NOTARY extends net.corda.core.utilities.ProgressTracker$Step public static final net.corda.core.flows.AbstractStateReplacementFlow$Instigator$Companion$NOTARY INSTANCE ## -public static final class net.corda.core.flows.AbstractStateReplacementFlow$Instigator$Companion$SIGNING extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.AbstractStateReplacementFlow$Instigator$Companion$SIGNING extends net.corda.core.utilities.ProgressTracker$Step public static final net.corda.core.flows.AbstractStateReplacementFlow$Instigator$Companion$SIGNING INSTANCE ## -public static final class net.corda.core.flows.AbstractStateReplacementFlow$Proposal extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.AbstractStateReplacementFlow$Proposal extends java.lang.Object public (net.corda.core.contracts.StateRef, Object) @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.StateRef component1() public final Object component2() @@ -913,7 +915,7 @@ public static final class net.corda.core.flows.AbstractStateReplacementFlow$Upgr public int hashCode() public String toString() ## -public final class net.corda.core.flows.CollectSignatureFlow extends net.corda.core.flows.FlowLogic +@co.paralleluniverse.fibers.Suspendable public final class net.corda.core.flows.CollectSignatureFlow extends net.corda.core.flows.FlowLogic public (net.corda.core.transactions.SignedTransaction, net.corda.core.flows.FlowSession, List) @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public List call() @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.SignedTransaction getPartiallySignedTx() @@ -936,26 +938,26 @@ public final class net.corda.core.flows.CollectSignaturesFlow extends net.corda. public static final class net.corda.core.flows.CollectSignaturesFlow$Companion extends java.lang.Object @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ProgressTracker tracker() ## -public static final class net.corda.core.flows.CollectSignaturesFlow$Companion$COLLECTING extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.CollectSignaturesFlow$Companion$COLLECTING extends net.corda.core.utilities.ProgressTracker$Step public static final net.corda.core.flows.CollectSignaturesFlow$Companion$COLLECTING INSTANCE ## -public static final class net.corda.core.flows.CollectSignaturesFlow$Companion$VERIFYING extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.CollectSignaturesFlow$Companion$VERIFYING extends net.corda.core.utilities.ProgressTracker$Step public static final net.corda.core.flows.CollectSignaturesFlow$Companion$VERIFYING INSTANCE ## public final class net.corda.core.flows.ContractUpgradeFlow extends java.lang.Object public static final net.corda.core.flows.ContractUpgradeFlow INSTANCE ## -public static final class net.corda.core.flows.ContractUpgradeFlow$Authorise extends net.corda.core.flows.FlowLogic +@net.corda.core.flows.StartableByRPC public static final class net.corda.core.flows.ContractUpgradeFlow$Authorise extends net.corda.core.flows.FlowLogic public (net.corda.core.contracts.StateAndRef, Class) @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.Nullable public Void call() @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.StateAndRef getStateAndRef() ## -public static final class net.corda.core.flows.ContractUpgradeFlow$Deauthorise extends net.corda.core.flows.FlowLogic +@net.corda.core.flows.StartableByRPC public static final class net.corda.core.flows.ContractUpgradeFlow$Deauthorise extends net.corda.core.flows.FlowLogic public (net.corda.core.contracts.StateRef) @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.Nullable public Void call() @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.StateRef getStateRef() ## -public static final class net.corda.core.flows.ContractUpgradeFlow$Initiate extends net.corda.core.flows.AbstractStateReplacementFlow$Instigator +@net.corda.core.flows.InitiatingFlow @net.corda.core.flows.StartableByRPC public static final class net.corda.core.flows.ContractUpgradeFlow$Initiate extends net.corda.core.flows.AbstractStateReplacementFlow$Instigator public (net.corda.core.contracts.StateAndRef, Class) @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull protected net.corda.core.flows.AbstractStateReplacementFlow$UpgradeTx assembleTx() ## @@ -966,7 +968,7 @@ public abstract class net.corda.core.flows.DataVendingFlow extends net.corda.cor @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull protected net.corda.core.utilities.UntrustworthyData sendPayloadAndReceiveDataRequest(net.corda.core.flows.FlowSession, Object) @co.paralleluniverse.fibers.Suspendable protected void verifyDataRequest(net.corda.core.internal.FetchDataFlow$Request$Data) ## -public final class net.corda.core.flows.FinalityFlow extends net.corda.core.flows.FlowLogic +@net.corda.core.flows.InitiatingFlow public final class net.corda.core.flows.FinalityFlow extends net.corda.core.flows.FlowLogic public (net.corda.core.transactions.SignedTransaction) public (net.corda.core.transactions.SignedTransaction, Set) public (net.corda.core.transactions.SignedTransaction, Set, net.corda.core.utilities.ProgressTracker) @@ -980,20 +982,20 @@ public final class net.corda.core.flows.FinalityFlow extends net.corda.core.flow public static final class net.corda.core.flows.FinalityFlow$Companion extends java.lang.Object @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ProgressTracker tracker() ## -public static final class net.corda.core.flows.FinalityFlow$Companion$BROADCASTING extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.FinalityFlow$Companion$BROADCASTING extends net.corda.core.utilities.ProgressTracker$Step public static final net.corda.core.flows.FinalityFlow$Companion$BROADCASTING INSTANCE ## -public static final class net.corda.core.flows.FinalityFlow$Companion$NOTARISING extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.FinalityFlow$Companion$NOTARISING extends net.corda.core.utilities.ProgressTracker$Step @org.jetbrains.annotations.NotNull public net.corda.core.utilities.ProgressTracker childProgressTracker() public static final net.corda.core.flows.FinalityFlow$Companion$NOTARISING INSTANCE ## -public class net.corda.core.flows.FlowException extends net.corda.core.CordaException +@net.corda.core.serialization.CordaSerializable public class net.corda.core.flows.FlowException extends net.corda.core.CordaException public () public (String) public (String, Throwable) public (Throwable) ## -public final class net.corda.core.flows.FlowInfo extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.flows.FlowInfo extends java.lang.Object public (int, String) public final int component1() @org.jetbrains.annotations.NotNull public final String component2() @@ -1004,9 +1006,9 @@ public final class net.corda.core.flows.FlowInfo extends java.lang.Object public int hashCode() public String toString() ## -public abstract class net.corda.core.flows.FlowInitiator extends java.lang.Object implements java.security.Principal +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.flows.FlowInitiator extends java.lang.Object implements java.security.Principal ## -public static final class net.corda.core.flows.FlowInitiator$Peer extends net.corda.core.flows.FlowInitiator +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.FlowInitiator$Peer extends net.corda.core.flows.FlowInitiator public (net.corda.core.identity.Party) @org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party component1() @org.jetbrains.annotations.NotNull public final net.corda.core.flows.FlowInitiator$Peer copy(net.corda.core.identity.Party) @@ -1016,7 +1018,7 @@ public static final class net.corda.core.flows.FlowInitiator$Peer extends net.co public int hashCode() public String toString() ## -public static final class net.corda.core.flows.FlowInitiator$RPC extends net.corda.core.flows.FlowInitiator +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.FlowInitiator$RPC 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$RPC copy(String) @@ -1026,7 +1028,7 @@ public static final class net.corda.core.flows.FlowInitiator$RPC extends net.cor public int hashCode() public String toString() ## -public static final class net.corda.core.flows.FlowInitiator$Scheduled extends net.corda.core.flows.FlowInitiator +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.FlowInitiator$Scheduled extends net.corda.core.flows.FlowInitiator 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.flows.FlowInitiator$Scheduled copy(net.corda.core.contracts.ScheduledStateRef) @@ -1036,7 +1038,7 @@ public static final class net.corda.core.flows.FlowInitiator$Scheduled extends n public int hashCode() public String toString() ## -public static final class net.corda.core.flows.FlowInitiator$Shell extends net.corda.core.flows.FlowInitiator +@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 ## @@ -1064,17 +1066,21 @@ public abstract class net.corda.core.flows.FlowLogic extends java.lang.Object @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) ## -public interface net.corda.core.flows.FlowLogicRef +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public interface net.corda.core.flows.FlowLogicRef ## -public interface net.corda.core.flows.FlowLogicRefFactory +@net.corda.core.DoNotImplement public interface net.corda.core.flows.FlowLogicRefFactory ## -public abstract class net.corda.core.flows.FlowSession extends java.lang.Object +@net.corda.core.DoNotImplement public abstract class net.corda.core.flows.FlowSession extends java.lang.Object public () @org.jetbrains.annotations.NotNull public abstract net.corda.core.identity.Party getCounterparty() @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public abstract net.corda.core.flows.FlowInfo getCounterpartyFlowInfo() + @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public abstract net.corda.core.flows.FlowInfo getCounterpartyFlowInfo(boolean) @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public abstract net.corda.core.utilities.UntrustworthyData receive(Class) + @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public abstract net.corda.core.utilities.UntrustworthyData receive(Class, boolean) @co.paralleluniverse.fibers.Suspendable public abstract void send(Object) + @co.paralleluniverse.fibers.Suspendable public abstract void send(Object, boolean) @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public abstract net.corda.core.utilities.UntrustworthyData sendAndReceive(Class, Object) + @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public abstract net.corda.core.utilities.UntrustworthyData sendAndReceive(Class, Object, boolean) ## public final class net.corda.core.flows.FlowStackSnapshot extends java.lang.Object public (java.time.Instant, String, List) @@ -1100,7 +1106,7 @@ public static final class net.corda.core.flows.FlowStackSnapshot$Frame extends j public int hashCode() @org.jetbrains.annotations.NotNull public String toString() ## -public final class net.corda.core.flows.IllegalFlowLogicException extends java.lang.IllegalArgumentException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.flows.IllegalFlowLogicException extends java.lang.IllegalArgumentException public (Class, String) ## public @interface net.corda.core.flows.InitiatedBy @@ -1109,13 +1115,13 @@ public @interface net.corda.core.flows.InitiatedBy public @interface net.corda.core.flows.InitiatingFlow public abstract int version() ## -public final class net.corda.core.flows.NotaryChangeFlow extends net.corda.core.flows.AbstractStateReplacementFlow$Instigator +@net.corda.core.flows.InitiatingFlow public final class net.corda.core.flows.NotaryChangeFlow extends net.corda.core.flows.AbstractStateReplacementFlow$Instigator public (net.corda.core.contracts.StateAndRef, net.corda.core.identity.Party, net.corda.core.utilities.ProgressTracker) @org.jetbrains.annotations.NotNull protected net.corda.core.flows.AbstractStateReplacementFlow$UpgradeTx assembleTx() ## -public abstract class net.corda.core.flows.NotaryError extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.flows.NotaryError extends java.lang.Object ## -public static final class net.corda.core.flows.NotaryError$Conflict extends net.corda.core.flows.NotaryError +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.NotaryError$Conflict extends net.corda.core.flows.NotaryError public (net.corda.core.crypto.SecureHash, net.corda.core.crypto.SignedData) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component1() @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SignedData component2() @@ -1126,10 +1132,10 @@ public static final class net.corda.core.flows.NotaryError$Conflict extends net. public int hashCode() @org.jetbrains.annotations.NotNull public String toString() ## -public static final class net.corda.core.flows.NotaryError$TimeWindowInvalid extends net.corda.core.flows.NotaryError +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.NotaryError$TimeWindowInvalid extends net.corda.core.flows.NotaryError public static final net.corda.core.flows.NotaryError$TimeWindowInvalid INSTANCE ## -public static final class net.corda.core.flows.NotaryError$TransactionInvalid extends net.corda.core.flows.NotaryError +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.NotaryError$TransactionInvalid extends net.corda.core.flows.NotaryError public (Throwable) @org.jetbrains.annotations.NotNull public final Throwable component1() @org.jetbrains.annotations.NotNull public final net.corda.core.flows.NotaryError$TransactionInvalid copy(Throwable) @@ -1138,17 +1144,17 @@ public static final class net.corda.core.flows.NotaryError$TransactionInvalid ex public int hashCode() @org.jetbrains.annotations.NotNull public String toString() ## -public static final class net.corda.core.flows.NotaryError$WrongNotary extends net.corda.core.flows.NotaryError +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.NotaryError$WrongNotary extends net.corda.core.flows.NotaryError public static final net.corda.core.flows.NotaryError$WrongNotary INSTANCE ## -public final class net.corda.core.flows.NotaryException extends net.corda.core.flows.FlowException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.flows.NotaryException extends net.corda.core.flows.FlowException public (net.corda.core.flows.NotaryError) @org.jetbrains.annotations.NotNull public final net.corda.core.flows.NotaryError getError() ## public final class net.corda.core.flows.NotaryFlow extends java.lang.Object public () ## -public static class net.corda.core.flows.NotaryFlow$Client extends net.corda.core.flows.FlowLogic +@net.corda.core.flows.InitiatingFlow public static class net.corda.core.flows.NotaryFlow$Client extends net.corda.core.flows.FlowLogic public (net.corda.core.transactions.SignedTransaction) public (net.corda.core.transactions.SignedTransaction, net.corda.core.utilities.ProgressTracker) @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public List call() @@ -1158,10 +1164,10 @@ public static class net.corda.core.flows.NotaryFlow$Client extends net.corda.cor public static final class net.corda.core.flows.NotaryFlow$Client$Companion extends java.lang.Object @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ProgressTracker tracker() ## -public static final class net.corda.core.flows.NotaryFlow$Client$Companion$REQUESTING extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.NotaryFlow$Client$Companion$REQUESTING extends net.corda.core.utilities.ProgressTracker$Step public static final net.corda.core.flows.NotaryFlow$Client$Companion$REQUESTING INSTANCE ## -public static final class net.corda.core.flows.NotaryFlow$Client$Companion$VALIDATING extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.NotaryFlow$Client$Companion$VALIDATING extends net.corda.core.utilities.ProgressTracker$Step public static final net.corda.core.flows.NotaryFlow$Client$Companion$VALIDATING INSTANCE ## public abstract static class net.corda.core.flows.NotaryFlow$Service extends net.corda.core.flows.FlowLogic @@ -1201,13 +1207,13 @@ public abstract class net.corda.core.flows.SignTransactionFlow extends net.corda public static final class net.corda.core.flows.SignTransactionFlow$Companion extends java.lang.Object @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ProgressTracker tracker() ## -public static final class net.corda.core.flows.SignTransactionFlow$Companion$RECEIVING extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.SignTransactionFlow$Companion$RECEIVING extends net.corda.core.utilities.ProgressTracker$Step public static final net.corda.core.flows.SignTransactionFlow$Companion$RECEIVING INSTANCE ## -public static final class net.corda.core.flows.SignTransactionFlow$Companion$SIGNING extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.SignTransactionFlow$Companion$SIGNING extends net.corda.core.utilities.ProgressTracker$Step public static final net.corda.core.flows.SignTransactionFlow$Companion$SIGNING INSTANCE ## -public static final class net.corda.core.flows.SignTransactionFlow$Companion$VERIFYING extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.SignTransactionFlow$Companion$VERIFYING extends net.corda.core.utilities.ProgressTracker$Step public static final net.corda.core.flows.SignTransactionFlow$Companion$VERIFYING INSTANCE ## public final class net.corda.core.flows.StackFrameDataToken extends java.lang.Object @@ -1221,7 +1227,7 @@ public final class net.corda.core.flows.StackFrameDataToken extends java.lang.Ob ## public @interface net.corda.core.flows.StartableByRPC ## -public final class net.corda.core.flows.StateMachineRunId extends java.lang.Object +@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() @org.jetbrains.annotations.NotNull public final net.corda.core.flows.StateMachineRunId copy(UUID) @@ -1234,7 +1240,7 @@ public final class net.corda.core.flows.StateMachineRunId extends java.lang.Obje public static final class net.corda.core.flows.StateMachineRunId$Companion extends java.lang.Object @org.jetbrains.annotations.NotNull public final net.corda.core.flows.StateMachineRunId createRandom() ## -public class net.corda.core.flows.StateReplacementException extends net.corda.core.flows.FlowException +@net.corda.core.serialization.CordaSerializable public class net.corda.core.flows.StateReplacementException extends net.corda.core.flows.FlowException public () public (String) public (String, Throwable) @@ -1254,11 +1260,11 @@ public final class net.corda.core.flows.TransactionParts extends java.lang.Objec public int hashCode() public String toString() ## -public final class net.corda.core.flows.UnexpectedFlowEndException extends net.corda.core.CordaRuntimeException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.flows.UnexpectedFlowEndException extends net.corda.core.CordaRuntimeException public (String) public (String, Throwable) ## -public abstract class net.corda.core.identity.AbstractParty extends java.lang.Object +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public abstract class net.corda.core.identity.AbstractParty extends java.lang.Object public (java.security.PublicKey) public boolean equals(Object) @org.jetbrains.annotations.NotNull public final java.security.PublicKey getOwningKey() @@ -1266,13 +1272,13 @@ public abstract class net.corda.core.identity.AbstractParty extends java.lang.Ob @org.jetbrains.annotations.Nullable public abstract net.corda.core.identity.CordaX500Name nameOrNull() @org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.PartyAndReference ref(net.corda.core.utilities.OpaqueBytes) ## -public final class net.corda.core.identity.AnonymousParty extends net.corda.core.identity.AbstractParty +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.identity.AnonymousParty extends net.corda.core.identity.AbstractParty public (java.security.PublicKey) @org.jetbrains.annotations.Nullable public net.corda.core.identity.CordaX500Name nameOrNull() @org.jetbrains.annotations.NotNull public net.corda.core.contracts.PartyAndReference ref(net.corda.core.utilities.OpaqueBytes) @org.jetbrains.annotations.NotNull public String toString() ## -public final class net.corda.core.identity.CordaX500Name extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.identity.CordaX500Name extends java.lang.Object public (String, String, String) public (String, String, String, String) public (String, String, String, String, String, String) @@ -1315,7 +1321,7 @@ public final class net.corda.core.identity.IdentityUtils extends java.lang.Objec @org.jetbrains.annotations.NotNull public static final Map groupPublicKeysByWellKnownParty(net.corda.core.node.ServiceHub, Collection) @org.jetbrains.annotations.NotNull public static final Map groupPublicKeysByWellKnownParty(net.corda.core.node.ServiceHub, Collection, boolean) ## -public final class net.corda.core.identity.Party extends net.corda.core.identity.AbstractParty +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.identity.Party extends net.corda.core.identity.AbstractParty public (java.security.cert.X509Certificate) public (net.corda.core.identity.CordaX500Name, java.security.PublicKey) @org.jetbrains.annotations.NotNull public final net.corda.core.identity.AnonymousParty anonymise() @@ -1324,7 +1330,7 @@ public final class net.corda.core.identity.Party extends net.corda.core.identity @org.jetbrains.annotations.NotNull public net.corda.core.contracts.PartyAndReference ref(net.corda.core.utilities.OpaqueBytes) @org.jetbrains.annotations.NotNull public String toString() ## -public final class net.corda.core.identity.PartyAndCertificate extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.identity.PartyAndCertificate extends java.lang.Object public (java.security.cert.CertPath) @org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party component1() @org.jetbrains.annotations.NotNull public final java.security.cert.X509Certificate component2() @@ -1338,9 +1344,9 @@ public final class net.corda.core.identity.PartyAndCertificate extends java.lang @org.jetbrains.annotations.NotNull public String toString() @org.jetbrains.annotations.NotNull public final java.security.cert.PKIXCertPathValidatorResult verify(java.security.cert.TrustAnchor) ## -public interface net.corda.core.messaging.AllPossibleRecipients extends net.corda.core.messaging.MessageRecipients +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.messaging.AllPossibleRecipients extends net.corda.core.messaging.MessageRecipients ## -public interface net.corda.core.messaging.CordaRPCOps extends net.corda.core.messaging.RPCOps +@net.corda.core.DoNotImplement public interface net.corda.core.messaging.CordaRPCOps extends net.corda.core.messaging.RPCOps public abstract void addVaultTransactionNote(net.corda.core.crypto.SecureHash, String) public abstract boolean attachmentExists(net.corda.core.crypto.SecureHash) public abstract void clearNetworkMapCache() @@ -1380,7 +1386,7 @@ public interface net.corda.core.messaging.CordaRPCOps extends net.corda.core.mes ## public final class net.corda.core.messaging.CordaRPCOpsKt extends java.lang.Object ## -public final class net.corda.core.messaging.DataFeed extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.messaging.DataFeed extends java.lang.Object public (Object, rx.Observable) public final Object component1() @org.jetbrains.annotations.NotNull public final rx.Observable component2() @@ -1391,12 +1397,12 @@ public final class net.corda.core.messaging.DataFeed extends java.lang.Object public int hashCode() public String toString() ## -public interface net.corda.core.messaging.FlowHandle extends java.lang.AutoCloseable +@net.corda.core.DoNotImplement public interface net.corda.core.messaging.FlowHandle extends java.lang.AutoCloseable public abstract void close() @org.jetbrains.annotations.NotNull public abstract net.corda.core.flows.StateMachineRunId getId() @org.jetbrains.annotations.NotNull public abstract net.corda.core.concurrent.CordaFuture getReturnValue() ## -public final class net.corda.core.messaging.FlowHandleImpl extends java.lang.Object implements net.corda.core.messaging.FlowHandle +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.messaging.FlowHandleImpl extends java.lang.Object implements net.corda.core.messaging.FlowHandle public (net.corda.core.flows.StateMachineRunId, net.corda.core.concurrent.CordaFuture) public void close() @org.jetbrains.annotations.NotNull public final net.corda.core.flows.StateMachineRunId component1() @@ -1408,11 +1414,11 @@ public final class net.corda.core.messaging.FlowHandleImpl extends java.lang.Obj public int hashCode() public String toString() ## -public interface net.corda.core.messaging.FlowProgressHandle extends net.corda.core.messaging.FlowHandle +@net.corda.core.DoNotImplement public interface net.corda.core.messaging.FlowProgressHandle extends net.corda.core.messaging.FlowHandle public abstract void close() @org.jetbrains.annotations.NotNull public abstract rx.Observable getProgress() ## -public final class net.corda.core.messaging.FlowProgressHandleImpl extends java.lang.Object implements net.corda.core.messaging.FlowProgressHandle +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.messaging.FlowProgressHandleImpl extends java.lang.Object implements net.corda.core.messaging.FlowProgressHandle public (net.corda.core.flows.StateMachineRunId, net.corda.core.concurrent.CordaFuture, rx.Observable) public void close() @org.jetbrains.annotations.NotNull public final net.corda.core.flows.StateMachineRunId component1() @@ -1426,18 +1432,18 @@ public final class net.corda.core.messaging.FlowProgressHandleImpl extends java. public int hashCode() public String toString() ## -public interface net.corda.core.messaging.MessageRecipientGroup extends net.corda.core.messaging.MessageRecipients +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.messaging.MessageRecipientGroup extends net.corda.core.messaging.MessageRecipients ## -public interface net.corda.core.messaging.MessageRecipients +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.messaging.MessageRecipients ## -public interface net.corda.core.messaging.RPCOps +@net.corda.core.DoNotImplement public interface net.corda.core.messaging.RPCOps public abstract int getProtocolVersion() ## public @interface net.corda.core.messaging.RPCReturnsObservables ## -public interface net.corda.core.messaging.SingleMessageRecipient extends net.corda.core.messaging.MessageRecipients +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.messaging.SingleMessageRecipient extends net.corda.core.messaging.MessageRecipients ## -public final class net.corda.core.messaging.StateMachineInfo extends java.lang.Object +@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) @org.jetbrains.annotations.NotNull public final net.corda.core.flows.StateMachineRunId component1() @org.jetbrains.annotations.NotNull public final String component2() @@ -1452,7 +1458,7 @@ public final class net.corda.core.messaging.StateMachineInfo extends java.lang.O public int hashCode() @org.jetbrains.annotations.NotNull public String toString() ## -public final class net.corda.core.messaging.StateMachineTransactionMapping extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.messaging.StateMachineTransactionMapping extends java.lang.Object public (net.corda.core.flows.StateMachineRunId, net.corda.core.crypto.SecureHash) @org.jetbrains.annotations.NotNull public final net.corda.core.flows.StateMachineRunId component1() @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component2() @@ -1463,10 +1469,10 @@ public final class net.corda.core.messaging.StateMachineTransactionMapping exten public int hashCode() public String toString() ## -public abstract class net.corda.core.messaging.StateMachineUpdate extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.messaging.StateMachineUpdate extends java.lang.Object @org.jetbrains.annotations.NotNull public abstract net.corda.core.flows.StateMachineRunId getId() ## -public static final class net.corda.core.messaging.StateMachineUpdate$Added extends net.corda.core.messaging.StateMachineUpdate +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.messaging.StateMachineUpdate$Added extends net.corda.core.messaging.StateMachineUpdate public (net.corda.core.messaging.StateMachineInfo) @org.jetbrains.annotations.NotNull public final net.corda.core.messaging.StateMachineInfo component1() @org.jetbrains.annotations.NotNull public final net.corda.core.messaging.StateMachineUpdate$Added copy(net.corda.core.messaging.StateMachineInfo) @@ -1476,7 +1482,7 @@ public static final class net.corda.core.messaging.StateMachineUpdate$Added exte public int hashCode() public String toString() ## -public static final class net.corda.core.messaging.StateMachineUpdate$Removed extends net.corda.core.messaging.StateMachineUpdate +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.messaging.StateMachineUpdate$Removed extends net.corda.core.messaging.StateMachineUpdate public (net.corda.core.flows.StateMachineRunId, net.corda.core.utilities.Try) @org.jetbrains.annotations.NotNull public final net.corda.core.flows.StateMachineRunId component1() @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.Try component2() @@ -1487,11 +1493,11 @@ public static final class net.corda.core.messaging.StateMachineUpdate$Removed ex public int hashCode() public String toString() ## -public interface net.corda.core.node.AppServiceHub extends net.corda.core.node.ServiceHub +@net.corda.core.DoNotImplement public interface net.corda.core.node.AppServiceHub extends net.corda.core.node.ServiceHub @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) ## -public final class net.corda.core.node.NodeInfo extends java.lang.Object +@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() @org.jetbrains.annotations.NotNull public final List component2() @@ -1508,7 +1514,7 @@ public final class net.corda.core.node.NodeInfo extends java.lang.Object public final boolean isLegalIdentity(net.corda.core.identity.Party) public String toString() ## -public interface net.corda.core.node.ServiceHub extends net.corda.core.node.ServicesForResolution +@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) @org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.SerializeAsToken cordaService(Class) @@ -1532,28 +1538,28 @@ public interface net.corda.core.node.ServiceHub extends net.corda.core.node.Serv @org.jetbrains.annotations.NotNull public abstract net.corda.core.transactions.SignedTransaction signInitialTransaction(net.corda.core.transactions.TransactionBuilder, java.security.PublicKey) @org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.StateAndRef toStateAndRef(net.corda.core.contracts.StateRef) ## -public interface net.corda.core.node.ServicesForResolution extends net.corda.core.node.StateLoader +@net.corda.core.DoNotImplement public interface net.corda.core.node.ServicesForResolution extends net.corda.core.node.StateLoader @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.AttachmentStorage getAttachments() @org.jetbrains.annotations.NotNull public abstract net.corda.core.cordapp.CordappProvider getCordappProvider() @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.IdentityService getIdentityService() ## -public interface net.corda.core.node.StateLoader +@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 interface net.corda.core.node.services.AttachmentStorage +@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.Nullable public abstract net.corda.core.contracts.Attachment openAttachment(net.corda.core.crypto.SecureHash) ## public final class net.corda.core.node.services.AttachmentStorageKt extends java.lang.Object ## -public interface net.corda.core.node.services.ContractUpgradeService +@net.corda.core.DoNotImplement public interface net.corda.core.node.services.ContractUpgradeService @org.jetbrains.annotations.Nullable public abstract String getAuthorisedContractUpgrade(net.corda.core.contracts.StateRef) public abstract void removeAuthorisedContractUpgrade(net.corda.core.contracts.StateRef) public abstract void storeAuthorisedContractUpgrade(net.corda.core.contracts.StateRef, Class) ## public @interface net.corda.core.node.services.CordaService ## -public interface net.corda.core.node.services.IdentityService +@net.corda.core.DoNotImplement public interface net.corda.core.node.services.IdentityService public abstract void assertOwnership(net.corda.core.identity.Party, net.corda.core.identity.AnonymousParty) @org.jetbrains.annotations.Nullable public abstract net.corda.core.identity.PartyAndCertificate certificateFromKey(java.security.PublicKey) @org.jetbrains.annotations.NotNull public abstract Iterable getAllIdentities() @@ -1568,7 +1574,7 @@ public interface net.corda.core.node.services.IdentityService @org.jetbrains.annotations.Nullable public abstract net.corda.core.identity.Party wellKnownPartyFromAnonymous(net.corda.core.identity.AbstractParty) @org.jetbrains.annotations.Nullable public abstract net.corda.core.identity.Party wellKnownPartyFromX500Name(net.corda.core.identity.CordaX500Name) ## -public interface net.corda.core.node.services.KeyManagementService +@net.corda.core.DoNotImplement public interface net.corda.core.node.services.KeyManagementService @org.jetbrains.annotations.NotNull public abstract Iterable filterMyKeys(Iterable) @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public abstract java.security.PublicKey freshKey() @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public abstract net.corda.core.identity.PartyAndCertificate freshKeyAndCert(net.corda.core.identity.PartyAndCertificate, boolean) @@ -1576,12 +1582,46 @@ public interface net.corda.core.node.services.KeyManagementService @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.TransactionSignature sign(net.corda.core.crypto.SignableData, java.security.PublicKey) @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.DigitalSignature$WithKey sign(byte[], java.security.PublicKey) ## -public interface net.corda.core.node.services.NetworkMapCache +@net.corda.core.DoNotImplement public interface net.corda.core.node.services.NetworkMapCache extends net.corda.core.node.services.NetworkMapCacheBase + @org.jetbrains.annotations.Nullable public abstract net.corda.core.node.NodeInfo getNodeByLegalIdentity(net.corda.core.identity.AbstractParty) +## +@net.corda.core.serialization.CordaSerializable public abstract static class net.corda.core.node.services.NetworkMapCache$MapChange extends java.lang.Object + @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.NodeInfo getNode() +## +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.NetworkMapCache$MapChange$Added extends net.corda.core.node.services.NetworkMapCache$MapChange + public (net.corda.core.node.NodeInfo) + @org.jetbrains.annotations.NotNull public final net.corda.core.node.NodeInfo component1() + @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.NetworkMapCache$MapChange$Added copy(net.corda.core.node.NodeInfo) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public net.corda.core.node.NodeInfo getNode() + public int hashCode() + public String toString() +## +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.NetworkMapCache$MapChange$Modified extends net.corda.core.node.services.NetworkMapCache$MapChange + public (net.corda.core.node.NodeInfo, net.corda.core.node.NodeInfo) + @org.jetbrains.annotations.NotNull public final net.corda.core.node.NodeInfo component1() + @org.jetbrains.annotations.NotNull public final net.corda.core.node.NodeInfo component2() + @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.NetworkMapCache$MapChange$Modified copy(net.corda.core.node.NodeInfo, net.corda.core.node.NodeInfo) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public net.corda.core.node.NodeInfo getNode() + @org.jetbrains.annotations.NotNull public final net.corda.core.node.NodeInfo getPreviousNode() + public int hashCode() + public String toString() +## +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.NetworkMapCache$MapChange$Removed extends net.corda.core.node.services.NetworkMapCache$MapChange + public (net.corda.core.node.NodeInfo) + @org.jetbrains.annotations.NotNull public final net.corda.core.node.NodeInfo component1() + @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.NetworkMapCache$MapChange$Removed copy(net.corda.core.node.NodeInfo) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public net.corda.core.node.NodeInfo getNode() + public int hashCode() + public String toString() +## +@net.corda.core.DoNotImplement public interface net.corda.core.node.services.NetworkMapCacheBase public abstract void clearNetworkMapCache() @org.jetbrains.annotations.NotNull public abstract List getAllNodes() @org.jetbrains.annotations.NotNull public abstract rx.Observable getChanged() @org.jetbrains.annotations.Nullable public abstract net.corda.core.node.NodeInfo getNodeByAddress(net.corda.core.utilities.NetworkHostAndPort) - @org.jetbrains.annotations.Nullable public abstract net.corda.core.node.NodeInfo getNodeByLegalIdentity(net.corda.core.identity.AbstractParty) @org.jetbrains.annotations.Nullable public abstract net.corda.core.node.NodeInfo getNodeByLegalName(net.corda.core.identity.CordaX500Name) @org.jetbrains.annotations.NotNull public abstract net.corda.core.concurrent.CordaFuture getNodeReady() @org.jetbrains.annotations.NotNull public abstract List getNodesByLegalIdentityKey(java.security.PublicKey) @@ -1595,39 +1635,7 @@ public interface net.corda.core.node.services.NetworkMapCache public abstract boolean isValidatingNotary(net.corda.core.identity.Party) @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.DataFeed track() ## -public abstract static class net.corda.core.node.services.NetworkMapCache$MapChange extends java.lang.Object - @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.NodeInfo getNode() -## -public static final class net.corda.core.node.services.NetworkMapCache$MapChange$Added extends net.corda.core.node.services.NetworkMapCache$MapChange - public (net.corda.core.node.NodeInfo) - @org.jetbrains.annotations.NotNull public final net.corda.core.node.NodeInfo component1() - @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.NetworkMapCache$MapChange$Added copy(net.corda.core.node.NodeInfo) - public boolean equals(Object) - @org.jetbrains.annotations.NotNull public net.corda.core.node.NodeInfo getNode() - public int hashCode() - public String toString() -## -public static final class net.corda.core.node.services.NetworkMapCache$MapChange$Modified extends net.corda.core.node.services.NetworkMapCache$MapChange - public (net.corda.core.node.NodeInfo, net.corda.core.node.NodeInfo) - @org.jetbrains.annotations.NotNull public final net.corda.core.node.NodeInfo component1() - @org.jetbrains.annotations.NotNull public final net.corda.core.node.NodeInfo component2() - @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.NetworkMapCache$MapChange$Modified copy(net.corda.core.node.NodeInfo, net.corda.core.node.NodeInfo) - public boolean equals(Object) - @org.jetbrains.annotations.NotNull public net.corda.core.node.NodeInfo getNode() - @org.jetbrains.annotations.NotNull public final net.corda.core.node.NodeInfo getPreviousNode() - public int hashCode() - public String toString() -## -public static final class net.corda.core.node.services.NetworkMapCache$MapChange$Removed extends net.corda.core.node.services.NetworkMapCache$MapChange - public (net.corda.core.node.NodeInfo) - @org.jetbrains.annotations.NotNull public final net.corda.core.node.NodeInfo component1() - @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.NetworkMapCache$MapChange$Removed copy(net.corda.core.node.NodeInfo) - public boolean equals(Object) - @org.jetbrains.annotations.NotNull public net.corda.core.node.NodeInfo getNode() - public int hashCode() - public String toString() -## -public abstract class net.corda.core.node.services.NotaryService extends net.corda.core.serialization.SingletonSerializeAsToken +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.node.services.NotaryService extends net.corda.core.serialization.SingletonSerializeAsToken public () @org.jetbrains.annotations.NotNull public abstract net.corda.core.flows.FlowLogic createServiceFlow(net.corda.core.flows.FlowSession) @org.jetbrains.annotations.NotNull public abstract java.security.PublicKey getNotaryIdentityKey() @@ -1658,7 +1666,7 @@ public static final class net.corda.core.node.services.PartyInfo$SingleNode exte public int hashCode() public String toString() ## -public final class net.corda.core.node.services.StatesNotAvailableException extends net.corda.core.flows.FlowException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.services.StatesNotAvailableException extends net.corda.core.flows.FlowException public (String, Throwable) @org.jetbrains.annotations.Nullable public Throwable getCause() @org.jetbrains.annotations.Nullable public String getMessage() @@ -1670,15 +1678,15 @@ public final class net.corda.core.node.services.TimeWindowChecker extends java.l @org.jetbrains.annotations.NotNull public final java.time.Clock getClock() public final boolean isValid(net.corda.core.contracts.TimeWindow) ## -public interface net.corda.core.node.services.TransactionStorage +@net.corda.core.DoNotImplement public interface net.corda.core.node.services.TransactionStorage @org.jetbrains.annotations.Nullable public abstract net.corda.core.transactions.SignedTransaction getTransaction(net.corda.core.crypto.SecureHash) @org.jetbrains.annotations.NotNull public abstract rx.Observable getUpdates() @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.DataFeed track() ## -public interface net.corda.core.node.services.TransactionVerifierService +@net.corda.core.DoNotImplement public interface net.corda.core.node.services.TransactionVerifierService @org.jetbrains.annotations.NotNull public abstract net.corda.core.concurrent.CordaFuture verify(net.corda.core.transactions.LedgerTransaction) ## -public abstract class net.corda.core.node.services.TrustedAuthorityNotaryService extends net.corda.core.node.services.NotaryService +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.node.services.TrustedAuthorityNotaryService extends net.corda.core.node.services.NotaryService public () public final void commitInputStates(List, net.corda.core.crypto.SecureHash, net.corda.core.identity.Party) @org.jetbrains.annotations.NotNull protected org.slf4j.Logger getLog() @@ -1688,14 +1696,14 @@ public abstract class net.corda.core.node.services.TrustedAuthorityNotaryService @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.DigitalSignature$WithKey sign(byte[]) public final void validateTimeWindow(net.corda.core.contracts.TimeWindow) ## -public final class net.corda.core.node.services.UniquenessException extends net.corda.core.CordaException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.services.UniquenessException extends net.corda.core.CordaException public (net.corda.core.node.services.UniquenessProvider$Conflict) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.UniquenessProvider$Conflict getError() ## public interface net.corda.core.node.services.UniquenessProvider public abstract void commit(List, net.corda.core.crypto.SecureHash, net.corda.core.identity.Party) ## -public static final class net.corda.core.node.services.UniquenessProvider$Conflict extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.UniquenessProvider$Conflict extends java.lang.Object public (Map) @org.jetbrains.annotations.NotNull public final Map component1() @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.UniquenessProvider$Conflict copy(Map) @@ -1704,7 +1712,7 @@ public static final class net.corda.core.node.services.UniquenessProvider$Confli public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.UniquenessProvider$ConsumingTx extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.UniquenessProvider$ConsumingTx extends java.lang.Object public (net.corda.core.crypto.SecureHash, int, net.corda.core.identity.Party) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component1() public final int component2() @@ -1717,10 +1725,10 @@ public static final class net.corda.core.node.services.UniquenessProvider$Consum public int hashCode() public String toString() ## -public final class net.corda.core.node.services.UnknownAnonymousPartyException extends net.corda.core.CordaException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.services.UnknownAnonymousPartyException extends net.corda.core.CordaException public (String) ## -public final class net.corda.core.node.services.Vault extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.services.Vault extends java.lang.Object public (Iterable) @org.jetbrains.annotations.NotNull public final Iterable getStates() public static final net.corda.core.node.services.Vault$Companion Companion @@ -1729,7 +1737,7 @@ public static final class net.corda.core.node.services.Vault$Companion extends j @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.Vault$Update getNoNotaryUpdate() @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.Vault$Update getNoUpdate() ## -public static final class net.corda.core.node.services.Vault$Page extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.Vault$Page extends java.lang.Object public (List, List, long, net.corda.core.node.services.Vault$StateStatus, List) @org.jetbrains.annotations.NotNull public final List component1() @org.jetbrains.annotations.NotNull public final List component2() @@ -1746,7 +1754,7 @@ public static final class net.corda.core.node.services.Vault$Page extends java.l public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.Vault$StateMetadata extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.Vault$StateMetadata extends java.lang.Object public (net.corda.core.contracts.StateRef, String, java.time.Instant, java.time.Instant, net.corda.core.node.services.Vault$StateStatus, net.corda.core.identity.AbstractParty, String, java.time.Instant) @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.StateRef component1() @org.jetbrains.annotations.NotNull public final String component2() @@ -1769,12 +1777,12 @@ public static final class net.corda.core.node.services.Vault$StateMetadata exten public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.Vault$StateStatus extends java.lang.Enum +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.Vault$StateStatus extends java.lang.Enum protected (String, int) public static net.corda.core.node.services.Vault$StateStatus valueOf(String) public static net.corda.core.node.services.Vault$StateStatus[] values() ## -public static final class net.corda.core.node.services.Vault$Update extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.Vault$Update extends java.lang.Object public (Set, Set, UUID, net.corda.core.node.services.Vault$UpdateType) @org.jetbrains.annotations.NotNull public final Set component1() @org.jetbrains.annotations.NotNull public final Set component2() @@ -1792,15 +1800,15 @@ public static final class net.corda.core.node.services.Vault$Update extends java @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.Vault$Update plus(net.corda.core.node.services.Vault$Update) @org.jetbrains.annotations.NotNull public String toString() ## -public static final class net.corda.core.node.services.Vault$UpdateType extends java.lang.Enum +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.Vault$UpdateType extends java.lang.Enum protected (String, int) public static net.corda.core.node.services.Vault$UpdateType valueOf(String) public static net.corda.core.node.services.Vault$UpdateType[] values() ## -public final class net.corda.core.node.services.VaultQueryException extends net.corda.core.flows.FlowException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.services.VaultQueryException extends net.corda.core.flows.FlowException public (String) ## -public interface net.corda.core.node.services.VaultService +@net.corda.core.DoNotImplement public interface net.corda.core.node.services.VaultService @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.Vault$Page _queryBy(net.corda.core.node.services.vault.QueryCriteria, net.corda.core.node.services.vault.PageSpecification, net.corda.core.node.services.vault.Sort, Class) @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.DataFeed _trackBy(net.corda.core.node.services.vault.QueryCriteria, net.corda.core.node.services.vault.PageSpecification, net.corda.core.node.services.vault.Sort, Class) public abstract void addNoteToTransaction(net.corda.core.crypto.SecureHash, String) @@ -1824,22 +1832,22 @@ public interface net.corda.core.node.services.VaultService ## public final class net.corda.core.node.services.VaultServiceKt extends java.lang.Object ## -public final class net.corda.core.node.services.vault.AggregateFunctionType extends java.lang.Enum +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.services.vault.AggregateFunctionType extends java.lang.Enum protected (String, int) public static net.corda.core.node.services.vault.AggregateFunctionType valueOf(String) public static net.corda.core.node.services.vault.AggregateFunctionType[] values() ## -public final class net.corda.core.node.services.vault.BinaryComparisonOperator extends java.lang.Enum implements net.corda.core.node.services.vault.Operator +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.node.services.vault.BinaryComparisonOperator extends java.lang.Enum implements net.corda.core.node.services.vault.Operator protected (String, int) public static net.corda.core.node.services.vault.BinaryComparisonOperator valueOf(String) public static net.corda.core.node.services.vault.BinaryComparisonOperator[] values() ## -public final class net.corda.core.node.services.vault.BinaryLogicalOperator extends java.lang.Enum implements net.corda.core.node.services.vault.Operator +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.node.services.vault.BinaryLogicalOperator extends java.lang.Enum implements net.corda.core.node.services.vault.Operator protected (String, int) public static net.corda.core.node.services.vault.BinaryLogicalOperator valueOf(String) public static net.corda.core.node.services.vault.BinaryLogicalOperator[] values() ## -public final class net.corda.core.node.services.vault.Builder extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.services.vault.Builder extends java.lang.Object @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression avg(reflect.Field) @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression avg(reflect.Field, List) @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression avg(reflect.Field, List, net.corda.core.node.services.vault.Sort$Direction) @@ -1902,21 +1910,21 @@ public final class net.corda.core.node.services.vault.Builder extends java.lang. @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression sum(kotlin.reflect.KProperty1, List, net.corda.core.node.services.vault.Sort$Direction) public static final net.corda.core.node.services.vault.Builder INSTANCE ## -public final class net.corda.core.node.services.vault.CollectionOperator extends java.lang.Enum implements net.corda.core.node.services.vault.Operator +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.node.services.vault.CollectionOperator extends java.lang.Enum implements net.corda.core.node.services.vault.Operator protected (String, int) public static net.corda.core.node.services.vault.CollectionOperator valueOf(String) public static net.corda.core.node.services.vault.CollectionOperator[] values() ## -public final class net.corda.core.node.services.vault.Column extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.services.vault.Column extends java.lang.Object public (String, Class) public (reflect.Field) public (kotlin.reflect.KProperty1) @org.jetbrains.annotations.NotNull public final Class getDeclaringClass() @org.jetbrains.annotations.NotNull public final String getName() ## -public abstract class net.corda.core.node.services.vault.ColumnPredicate extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.node.services.vault.ColumnPredicate extends java.lang.Object ## -public static final class net.corda.core.node.services.vault.ColumnPredicate$AggregateFunction extends net.corda.core.node.services.vault.ColumnPredicate +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.ColumnPredicate$AggregateFunction extends net.corda.core.node.services.vault.ColumnPredicate public (net.corda.core.node.services.vault.AggregateFunctionType) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.AggregateFunctionType component1() @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.ColumnPredicate$AggregateFunction copy(net.corda.core.node.services.vault.AggregateFunctionType) @@ -1925,7 +1933,7 @@ public static final class net.corda.core.node.services.vault.ColumnPredicate$Agg public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.vault.ColumnPredicate$Between extends net.corda.core.node.services.vault.ColumnPredicate +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.ColumnPredicate$Between extends net.corda.core.node.services.vault.ColumnPredicate public (Comparable, Comparable) @org.jetbrains.annotations.NotNull public final Comparable component1() @org.jetbrains.annotations.NotNull public final Comparable component2() @@ -1936,7 +1944,7 @@ public static final class net.corda.core.node.services.vault.ColumnPredicate$Bet public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.vault.ColumnPredicate$BinaryComparison extends net.corda.core.node.services.vault.ColumnPredicate +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.ColumnPredicate$BinaryComparison extends net.corda.core.node.services.vault.ColumnPredicate public (net.corda.core.node.services.vault.BinaryComparisonOperator, Comparable) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.BinaryComparisonOperator component1() @org.jetbrains.annotations.NotNull public final Comparable component2() @@ -1947,7 +1955,7 @@ public static final class net.corda.core.node.services.vault.ColumnPredicate$Bin public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.vault.ColumnPredicate$CollectionExpression extends net.corda.core.node.services.vault.ColumnPredicate +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.ColumnPredicate$CollectionExpression extends net.corda.core.node.services.vault.ColumnPredicate public (net.corda.core.node.services.vault.CollectionOperator, Collection) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.CollectionOperator component1() @org.jetbrains.annotations.NotNull public final Collection component2() @@ -1958,7 +1966,7 @@ public static final class net.corda.core.node.services.vault.ColumnPredicate$Col public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.vault.ColumnPredicate$EqualityComparison extends net.corda.core.node.services.vault.ColumnPredicate +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.ColumnPredicate$EqualityComparison extends net.corda.core.node.services.vault.ColumnPredicate public (net.corda.core.node.services.vault.EqualityComparisonOperator, Object) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.EqualityComparisonOperator component1() public final Object component2() @@ -1969,7 +1977,7 @@ public static final class net.corda.core.node.services.vault.ColumnPredicate$Equ public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.vault.ColumnPredicate$Likeness extends net.corda.core.node.services.vault.ColumnPredicate +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.ColumnPredicate$Likeness extends net.corda.core.node.services.vault.ColumnPredicate public (net.corda.core.node.services.vault.LikenessOperator, String) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.LikenessOperator component1() @org.jetbrains.annotations.NotNull public final String component2() @@ -1980,7 +1988,7 @@ public static final class net.corda.core.node.services.vault.ColumnPredicate$Lik public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.vault.ColumnPredicate$NullExpression extends net.corda.core.node.services.vault.ColumnPredicate +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.ColumnPredicate$NullExpression extends net.corda.core.node.services.vault.ColumnPredicate public (net.corda.core.node.services.vault.NullOperator) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.NullOperator component1() @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.ColumnPredicate$NullExpression copy(net.corda.core.node.services.vault.NullOperator) @@ -1989,9 +1997,9 @@ public static final class net.corda.core.node.services.vault.ColumnPredicate$Nul public int hashCode() public String toString() ## -public abstract class net.corda.core.node.services.vault.CriteriaExpression extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.node.services.vault.CriteriaExpression extends java.lang.Object ## -public static final class net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression extends net.corda.core.node.services.vault.CriteriaExpression +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression extends net.corda.core.node.services.vault.CriteriaExpression public (net.corda.core.node.services.vault.Column, net.corda.core.node.services.vault.ColumnPredicate, List, net.corda.core.node.services.vault.Sort$Direction) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.Column component1() @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.ColumnPredicate component2() @@ -2006,7 +2014,7 @@ public static final class net.corda.core.node.services.vault.CriteriaExpression$ public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.vault.CriteriaExpression$BinaryLogical extends net.corda.core.node.services.vault.CriteriaExpression +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.CriteriaExpression$BinaryLogical extends net.corda.core.node.services.vault.CriteriaExpression public (net.corda.core.node.services.vault.CriteriaExpression, net.corda.core.node.services.vault.CriteriaExpression, net.corda.core.node.services.vault.BinaryLogicalOperator) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.CriteriaExpression component1() @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.CriteriaExpression component2() @@ -2019,7 +2027,7 @@ public static final class net.corda.core.node.services.vault.CriteriaExpression$ public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression extends net.corda.core.node.services.vault.CriteriaExpression +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression extends net.corda.core.node.services.vault.CriteriaExpression public (net.corda.core.node.services.vault.Column, net.corda.core.node.services.vault.ColumnPredicate) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.Column component1() @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.ColumnPredicate component2() @@ -2030,7 +2038,7 @@ public static final class net.corda.core.node.services.vault.CriteriaExpression$ public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.vault.CriteriaExpression$Not extends net.corda.core.node.services.vault.CriteriaExpression +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.CriteriaExpression$Not extends net.corda.core.node.services.vault.CriteriaExpression public (net.corda.core.node.services.vault.CriteriaExpression) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.CriteriaExpression component1() @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.CriteriaExpression$Not copy(net.corda.core.node.services.vault.CriteriaExpression) @@ -2039,12 +2047,12 @@ public static final class net.corda.core.node.services.vault.CriteriaExpression$ public int hashCode() public String toString() ## -public final class net.corda.core.node.services.vault.EqualityComparisonOperator extends java.lang.Enum implements net.corda.core.node.services.vault.Operator +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.node.services.vault.EqualityComparisonOperator extends java.lang.Enum implements net.corda.core.node.services.vault.Operator protected (String, int) public static net.corda.core.node.services.vault.EqualityComparisonOperator valueOf(String) public static net.corda.core.node.services.vault.EqualityComparisonOperator[] values() ## -public interface net.corda.core.node.services.vault.IQueryCriteriaParser +@net.corda.core.DoNotImplement public interface net.corda.core.node.services.vault.IQueryCriteriaParser @org.jetbrains.annotations.NotNull public abstract Collection parse(net.corda.core.node.services.vault.QueryCriteria, net.corda.core.node.services.vault.Sort) @org.jetbrains.annotations.NotNull public abstract Collection parseAnd(net.corda.core.node.services.vault.QueryCriteria, net.corda.core.node.services.vault.QueryCriteria) @org.jetbrains.annotations.NotNull public abstract Collection parseCriteria(net.corda.core.node.services.vault.QueryCriteria$CommonQueryCriteria) @@ -2054,19 +2062,19 @@ public interface net.corda.core.node.services.vault.IQueryCriteriaParser @org.jetbrains.annotations.NotNull public abstract Collection parseCriteria(net.corda.core.node.services.vault.QueryCriteria$VaultQueryCriteria) @org.jetbrains.annotations.NotNull public abstract Collection parseOr(net.corda.core.node.services.vault.QueryCriteria, net.corda.core.node.services.vault.QueryCriteria) ## -public final class net.corda.core.node.services.vault.LikenessOperator extends java.lang.Enum implements net.corda.core.node.services.vault.Operator +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.node.services.vault.LikenessOperator extends java.lang.Enum implements net.corda.core.node.services.vault.Operator protected (String, int) public static net.corda.core.node.services.vault.LikenessOperator valueOf(String) public static net.corda.core.node.services.vault.LikenessOperator[] values() ## -public final class net.corda.core.node.services.vault.NullOperator extends java.lang.Enum implements net.corda.core.node.services.vault.Operator +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.node.services.vault.NullOperator extends java.lang.Enum implements net.corda.core.node.services.vault.Operator protected (String, int) public static net.corda.core.node.services.vault.NullOperator valueOf(String) public static net.corda.core.node.services.vault.NullOperator[] values() ## -public interface net.corda.core.node.services.vault.Operator +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public interface net.corda.core.node.services.vault.Operator ## -public final class net.corda.core.node.services.vault.PageSpecification extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.services.vault.PageSpecification extends java.lang.Object public () public (int, int) public final int component1() @@ -2079,18 +2087,18 @@ public final class net.corda.core.node.services.vault.PageSpecification extends public final boolean isDefault() public String toString() ## -public abstract class net.corda.core.node.services.vault.QueryCriteria extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.node.services.vault.QueryCriteria extends java.lang.Object @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.QueryCriteria and(net.corda.core.node.services.vault.QueryCriteria) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.QueryCriteria or(net.corda.core.node.services.vault.QueryCriteria) @org.jetbrains.annotations.NotNull public abstract Collection visit(net.corda.core.node.services.vault.IQueryCriteriaParser) ## -public abstract static class net.corda.core.node.services.vault.QueryCriteria$CommonQueryCriteria extends net.corda.core.node.services.vault.QueryCriteria +@net.corda.core.serialization.CordaSerializable public abstract static class net.corda.core.node.services.vault.QueryCriteria$CommonQueryCriteria extends net.corda.core.node.services.vault.QueryCriteria public () @org.jetbrains.annotations.Nullable public abstract Set getContractStateTypes() @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.Vault$StateStatus getStatus() @org.jetbrains.annotations.NotNull public Collection visit(net.corda.core.node.services.vault.IQueryCriteriaParser) ## -public static final class net.corda.core.node.services.vault.QueryCriteria$FungibleAssetQueryCriteria extends net.corda.core.node.services.vault.QueryCriteria$CommonQueryCriteria +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.QueryCriteria$FungibleAssetQueryCriteria extends net.corda.core.node.services.vault.QueryCriteria$CommonQueryCriteria public () public (List) public (List, List) @@ -2119,7 +2127,7 @@ public static final class net.corda.core.node.services.vault.QueryCriteria$Fungi public String toString() @org.jetbrains.annotations.NotNull public Collection visit(net.corda.core.node.services.vault.IQueryCriteriaParser) ## -public static final class net.corda.core.node.services.vault.QueryCriteria$LinearStateQueryCriteria extends net.corda.core.node.services.vault.QueryCriteria$CommonQueryCriteria +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.QueryCriteria$LinearStateQueryCriteria extends net.corda.core.node.services.vault.QueryCriteria$CommonQueryCriteria public () public (List) public (List, List) @@ -2143,7 +2151,7 @@ public static final class net.corda.core.node.services.vault.QueryCriteria$Linea public String toString() @org.jetbrains.annotations.NotNull public Collection visit(net.corda.core.node.services.vault.IQueryCriteriaParser) ## -public static final class net.corda.core.node.services.vault.QueryCriteria$SoftLockingCondition extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.QueryCriteria$SoftLockingCondition extends java.lang.Object public (net.corda.core.node.services.vault.QueryCriteria$SoftLockingType, List) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.QueryCriteria$SoftLockingType component1() @org.jetbrains.annotations.NotNull public final List component2() @@ -2154,12 +2162,12 @@ public static final class net.corda.core.node.services.vault.QueryCriteria$SoftL public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.vault.QueryCriteria$SoftLockingType extends java.lang.Enum +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.QueryCriteria$SoftLockingType extends java.lang.Enum protected (String, int) public static net.corda.core.node.services.vault.QueryCriteria$SoftLockingType valueOf(String) public static net.corda.core.node.services.vault.QueryCriteria$SoftLockingType[] values() ## -public static final class net.corda.core.node.services.vault.QueryCriteria$TimeCondition extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.QueryCriteria$TimeCondition extends java.lang.Object public (net.corda.core.node.services.vault.QueryCriteria$TimeInstantType, net.corda.core.node.services.vault.ColumnPredicate) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.QueryCriteria$TimeInstantType component1() @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.ColumnPredicate component2() @@ -2170,12 +2178,12 @@ public static final class net.corda.core.node.services.vault.QueryCriteria$TimeC public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.vault.QueryCriteria$TimeInstantType extends java.lang.Enum +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.QueryCriteria$TimeInstantType extends java.lang.Enum protected (String, int) public static net.corda.core.node.services.vault.QueryCriteria$TimeInstantType valueOf(String) public static net.corda.core.node.services.vault.QueryCriteria$TimeInstantType[] values() ## -public static final class net.corda.core.node.services.vault.QueryCriteria$VaultCustomQueryCriteria extends net.corda.core.node.services.vault.QueryCriteria$CommonQueryCriteria +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.QueryCriteria$VaultCustomQueryCriteria extends net.corda.core.node.services.vault.QueryCriteria$CommonQueryCriteria public (net.corda.core.node.services.vault.CriteriaExpression) public (net.corda.core.node.services.vault.CriteriaExpression, net.corda.core.node.services.Vault$StateStatus) public (net.corda.core.node.services.vault.CriteriaExpression, net.corda.core.node.services.Vault$StateStatus, Set) @@ -2191,7 +2199,7 @@ public static final class net.corda.core.node.services.vault.QueryCriteria$Vault public String toString() @org.jetbrains.annotations.NotNull public Collection visit(net.corda.core.node.services.vault.IQueryCriteriaParser) ## -public static final class net.corda.core.node.services.vault.QueryCriteria$VaultQueryCriteria extends net.corda.core.node.services.vault.QueryCriteria$CommonQueryCriteria +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.QueryCriteria$VaultQueryCriteria extends net.corda.core.node.services.vault.QueryCriteria$CommonQueryCriteria public () public (net.corda.core.node.services.Vault$StateStatus) public (net.corda.core.node.services.Vault$StateStatus, Set) @@ -2226,7 +2234,7 @@ public final class net.corda.core.node.services.vault.QueryCriteriaUtils extends public static final int DEFAULT_PAGE_SIZE = 200 public static final int MAX_PAGE_SIZE = 2147483647 ## -public final class net.corda.core.node.services.vault.Sort extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.services.vault.Sort extends java.lang.Object public (Collection) @org.jetbrains.annotations.NotNull public final Collection component1() @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.Sort copy(Collection) @@ -2235,33 +2243,33 @@ public final class net.corda.core.node.services.vault.Sort extends java.lang.Obj public int hashCode() public String toString() ## -public static interface net.corda.core.node.services.vault.Sort$Attribute +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public static interface net.corda.core.node.services.vault.Sort$Attribute ## -public static final class net.corda.core.node.services.vault.Sort$CommonStateAttribute extends java.lang.Enum implements net.corda.core.node.services.vault.Sort$Attribute +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public static final class net.corda.core.node.services.vault.Sort$CommonStateAttribute extends java.lang.Enum implements net.corda.core.node.services.vault.Sort$Attribute protected (String, int, String, String) @org.jetbrains.annotations.Nullable public final String getAttributeChild() @org.jetbrains.annotations.NotNull public final String getAttributeParent() public static net.corda.core.node.services.vault.Sort$CommonStateAttribute valueOf(String) public static net.corda.core.node.services.vault.Sort$CommonStateAttribute[] values() ## -public static final class net.corda.core.node.services.vault.Sort$Direction extends java.lang.Enum +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.Sort$Direction extends java.lang.Enum protected (String, int) public static net.corda.core.node.services.vault.Sort$Direction valueOf(String) public static net.corda.core.node.services.vault.Sort$Direction[] values() ## -public static final class net.corda.core.node.services.vault.Sort$FungibleStateAttribute extends java.lang.Enum implements net.corda.core.node.services.vault.Sort$Attribute +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public static final class net.corda.core.node.services.vault.Sort$FungibleStateAttribute extends java.lang.Enum implements net.corda.core.node.services.vault.Sort$Attribute protected (String, int, String) @org.jetbrains.annotations.NotNull public final String getAttributeName() public static net.corda.core.node.services.vault.Sort$FungibleStateAttribute valueOf(String) public static net.corda.core.node.services.vault.Sort$FungibleStateAttribute[] values() ## -public static final class net.corda.core.node.services.vault.Sort$LinearStateAttribute extends java.lang.Enum implements net.corda.core.node.services.vault.Sort$Attribute +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public static final class net.corda.core.node.services.vault.Sort$LinearStateAttribute extends java.lang.Enum implements net.corda.core.node.services.vault.Sort$Attribute protected (String, int, String) @org.jetbrains.annotations.NotNull public final String getAttributeName() public static net.corda.core.node.services.vault.Sort$LinearStateAttribute valueOf(String) public static net.corda.core.node.services.vault.Sort$LinearStateAttribute[] values() ## -public static final class net.corda.core.node.services.vault.Sort$SortColumn extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.Sort$SortColumn extends java.lang.Object public (net.corda.core.node.services.vault.SortAttribute, net.corda.core.node.services.vault.Sort$Direction) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.SortAttribute component1() @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.Sort$Direction component2() @@ -2272,15 +2280,15 @@ public static final class net.corda.core.node.services.vault.Sort$SortColumn ext public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.vault.Sort$VaultStateAttribute extends java.lang.Enum implements net.corda.core.node.services.vault.Sort$Attribute +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public static final class net.corda.core.node.services.vault.Sort$VaultStateAttribute extends java.lang.Enum implements net.corda.core.node.services.vault.Sort$Attribute protected (String, int, String) @org.jetbrains.annotations.NotNull public final String getAttributeName() public static net.corda.core.node.services.vault.Sort$VaultStateAttribute valueOf(String) public static net.corda.core.node.services.vault.Sort$VaultStateAttribute[] values() ## -public abstract class net.corda.core.node.services.vault.SortAttribute extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.node.services.vault.SortAttribute extends java.lang.Object ## -public static final class net.corda.core.node.services.vault.SortAttribute$Custom extends net.corda.core.node.services.vault.SortAttribute +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.SortAttribute$Custom extends net.corda.core.node.services.vault.SortAttribute public (Class, String) @org.jetbrains.annotations.NotNull public final Class component1() @org.jetbrains.annotations.NotNull public final String component2() @@ -2291,7 +2299,7 @@ public static final class net.corda.core.node.services.vault.SortAttribute$Custo public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.vault.SortAttribute$Standard extends net.corda.core.node.services.vault.SortAttribute +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.SortAttribute$Standard extends net.corda.core.node.services.vault.SortAttribute public (net.corda.core.node.services.vault.Sort$Attribute) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.Sort$Attribute component1() @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.SortAttribute$Standard copy(net.corda.core.node.services.vault.Sort$Attribute) @@ -2306,7 +2314,7 @@ public final class net.corda.core.schemas.CommonSchema extends java.lang.Object public final class net.corda.core.schemas.CommonSchemaV1 extends net.corda.core.schemas.MappedSchema public static final net.corda.core.schemas.CommonSchemaV1 INSTANCE ## -public static class net.corda.core.schemas.CommonSchemaV1$FungibleState extends net.corda.core.schemas.PersistentState +@javax.persistence.MappedSuperclass @net.corda.core.serialization.CordaSerializable public static class net.corda.core.schemas.CommonSchemaV1$FungibleState extends net.corda.core.schemas.PersistentState public (Set, net.corda.core.identity.AbstractParty, long, net.corda.core.identity.AbstractParty, byte[]) @org.jetbrains.annotations.NotNull public final net.corda.core.identity.AbstractParty getIssuer() @org.jetbrains.annotations.NotNull public final byte[] getIssuerRef() @@ -2319,7 +2327,7 @@ public static class net.corda.core.schemas.CommonSchemaV1$FungibleState extends public final void setParticipants(Set) public final void setQuantity(long) ## -public static class net.corda.core.schemas.CommonSchemaV1$LinearState extends net.corda.core.schemas.PersistentState +@javax.persistence.MappedSuperclass @net.corda.core.serialization.CordaSerializable public static class net.corda.core.schemas.CommonSchemaV1$LinearState extends net.corda.core.schemas.PersistentState public (Set, String, UUID) public (net.corda.core.contracts.UniqueIdentifier, Set) @org.jetbrains.annotations.Nullable public final String getExternalId() @@ -2342,7 +2350,7 @@ public final class net.corda.core.schemas.NodeInfoSchema extends java.lang.Objec public final class net.corda.core.schemas.NodeInfoSchemaV1 extends net.corda.core.schemas.MappedSchema public static final net.corda.core.schemas.NodeInfoSchemaV1 INSTANCE ## -public static final class net.corda.core.schemas.NodeInfoSchemaV1$DBHostAndPort extends java.lang.Object +@javax.persistence.Entity public static final class net.corda.core.schemas.NodeInfoSchemaV1$DBHostAndPort extends java.lang.Object public () public (net.corda.core.schemas.NodeInfoSchemaV1$PKHostAndPort) @org.jetbrains.annotations.NotNull public final net.corda.core.schemas.NodeInfoSchemaV1$DBHostAndPort copy(net.corda.core.schemas.NodeInfoSchemaV1$PKHostAndPort) @@ -2355,7 +2363,7 @@ public static final class net.corda.core.schemas.NodeInfoSchemaV1$DBHostAndPort public static final class net.corda.core.schemas.NodeInfoSchemaV1$DBHostAndPort$Companion extends java.lang.Object @org.jetbrains.annotations.NotNull public final net.corda.core.schemas.NodeInfoSchemaV1$DBHostAndPort fromHostAndPort(net.corda.core.utilities.NetworkHostAndPort) ## -public static final class net.corda.core.schemas.NodeInfoSchemaV1$DBPartyAndCertificate extends java.lang.Object +@javax.persistence.Entity @javax.persistence.Table public static final class net.corda.core.schemas.NodeInfoSchemaV1$DBPartyAndCertificate extends java.lang.Object public () public (String, String, byte[], boolean, Set) public (net.corda.core.identity.PartyAndCertificate, boolean) @@ -2366,14 +2374,14 @@ public static final class net.corda.core.schemas.NodeInfoSchemaV1$DBPartyAndCert @org.jetbrains.annotations.NotNull public final net.corda.core.schemas.NodeInfoSchemaV1$DBPartyAndCertificate copy(String, String, byte[], boolean, Set) public boolean equals(Object) @org.jetbrains.annotations.NotNull public final String getName() - @org.jetbrains.annotations.NotNull public final String getOwningKey() + @org.jetbrains.annotations.NotNull public final String getOwningKeyHash() @org.jetbrains.annotations.NotNull public final byte[] getPartyCertBinary() public int hashCode() public final boolean isMain() @org.jetbrains.annotations.NotNull public final net.corda.core.identity.PartyAndCertificate toLegalIdentityAndCert() public String toString() ## -public static final class net.corda.core.schemas.NodeInfoSchemaV1$PKHostAndPort extends java.lang.Object implements java.io.Serializable +@javax.persistence.Embeddable public static final class net.corda.core.schemas.NodeInfoSchemaV1$PKHostAndPort extends java.lang.Object implements java.io.Serializable public () public (String, Integer) @org.jetbrains.annotations.Nullable public final String component1() @@ -2385,7 +2393,7 @@ public static final class net.corda.core.schemas.NodeInfoSchemaV1$PKHostAndPort public int hashCode() public String toString() ## -public static final class net.corda.core.schemas.NodeInfoSchemaV1$PersistentNodeInfo extends java.lang.Object +@javax.persistence.Entity @javax.persistence.Table public static final class net.corda.core.schemas.NodeInfoSchemaV1$PersistentNodeInfo extends java.lang.Object public () public (int, List, List, int, long) @org.jetbrains.annotations.NotNull public final List getAddresses() @@ -2396,13 +2404,13 @@ public static final class net.corda.core.schemas.NodeInfoSchemaV1$PersistentNode public final void setId(int) @org.jetbrains.annotations.NotNull public final net.corda.core.node.NodeInfo toNodeInfo() ## -public class net.corda.core.schemas.PersistentState extends java.lang.Object implements net.corda.core.schemas.StatePersistable +@javax.persistence.MappedSuperclass @net.corda.core.serialization.CordaSerializable public class net.corda.core.schemas.PersistentState extends java.lang.Object implements net.corda.core.schemas.StatePersistable public () public (net.corda.core.schemas.PersistentStateRef) @org.jetbrains.annotations.Nullable public final net.corda.core.schemas.PersistentStateRef getStateRef() public final void setStateRef(net.corda.core.schemas.PersistentStateRef) ## -public final class net.corda.core.schemas.PersistentStateRef extends java.lang.Object implements java.io.Serializable +@javax.persistence.Embeddable public final class net.corda.core.schemas.PersistentStateRef extends java.lang.Object implements java.io.Serializable public () public (String, Integer) public (net.corda.core.contracts.StateRef) @@ -2417,7 +2425,7 @@ public final class net.corda.core.schemas.PersistentStateRef extends java.lang.O public final void setTxId(String) public String toString() ## -public interface net.corda.core.schemas.QueryableState extends net.corda.core.contracts.ContractState +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.schemas.QueryableState extends net.corda.core.contracts.ContractState @org.jetbrains.annotations.NotNull public abstract net.corda.core.schemas.PersistentState generateMappedObject(net.corda.core.schemas.MappedSchema) @org.jetbrains.annotations.NotNull public abstract Iterable supportedSchemas() ## @@ -2431,10 +2439,21 @@ public @interface net.corda.core.serialization.CordaSerializable public @interface net.corda.core.serialization.DeprecatedConstructorForDeserialization public abstract int version() ## -public final class net.corda.core.serialization.MissingAttachmentsException extends net.corda.core.CordaException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.serialization.MissingAttachmentsException extends net.corda.core.CordaException public (List) @org.jetbrains.annotations.NotNull public final List getIds() ## +public final class net.corda.core.serialization.ObjectWithCompatibleContext extends java.lang.Object + public (Object, net.corda.core.serialization.SerializationContext) + @org.jetbrains.annotations.NotNull public final Object component1() + @org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializationContext component2() + @org.jetbrains.annotations.NotNull public final net.corda.core.serialization.ObjectWithCompatibleContext copy(Object, net.corda.core.serialization.SerializationContext) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializationContext getContext() + @org.jetbrains.annotations.NotNull public final Object getObj() + public int hashCode() + public String toString() +## public final class net.corda.core.serialization.SerializationAPIKt extends java.lang.Object @org.jetbrains.annotations.NotNull public static final net.corda.core.serialization.SerializedBytes serialize(Object, net.corda.core.serialization.SerializationFactory, net.corda.core.serialization.SerializationContext) ## @@ -2476,6 +2495,7 @@ public abstract class net.corda.core.serialization.SerializationFactory extends public () public final Object asCurrent(kotlin.jvm.functions.Function1) @org.jetbrains.annotations.NotNull public abstract Object deserialize(net.corda.core.utilities.ByteSequence, Class, net.corda.core.serialization.SerializationContext) + @org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.ObjectWithCompatibleContext deserializeWithCompatibleContext(net.corda.core.utilities.ByteSequence, Class, net.corda.core.serialization.SerializationContext) @org.jetbrains.annotations.Nullable public final net.corda.core.serialization.SerializationContext getCurrentContext() @org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializationContext getDefaultContext() @org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.SerializedBytes serialize(Object, net.corda.core.serialization.SerializationContext) @@ -2492,7 +2512,7 @@ public interface net.corda.core.serialization.SerializationToken public interface net.corda.core.serialization.SerializationWhitelist @org.jetbrains.annotations.NotNull public abstract List getWhitelist() ## -public interface net.corda.core.serialization.SerializeAsToken +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.serialization.SerializeAsToken @org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.SerializationToken toToken(net.corda.core.serialization.SerializeAsTokenContext) ## public interface net.corda.core.serialization.SerializeAsTokenContext @@ -2500,7 +2520,7 @@ public interface net.corda.core.serialization.SerializeAsTokenContext @org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.SerializeAsToken getSingleton(String) public abstract void putSingleton(net.corda.core.serialization.SerializeAsToken) ## -public final class net.corda.core.serialization.SerializedBytes extends net.corda.core.utilities.OpaqueBytes +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.serialization.SerializedBytes extends net.corda.core.utilities.OpaqueBytes public (byte[]) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getHash() ## @@ -2512,11 +2532,11 @@ public final class net.corda.core.serialization.SingletonSerializationToken exte public static final class net.corda.core.serialization.SingletonSerializationToken$Companion extends java.lang.Object @org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SingletonSerializationToken singletonSerializationToken(Class) ## -public abstract class net.corda.core.serialization.SingletonSerializeAsToken extends java.lang.Object implements net.corda.core.serialization.SerializeAsToken +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.serialization.SingletonSerializeAsToken extends java.lang.Object implements net.corda.core.serialization.SerializeAsToken public () @org.jetbrains.annotations.NotNull public net.corda.core.serialization.SingletonSerializationToken toToken(net.corda.core.serialization.SerializeAsTokenContext) ## -public abstract class net.corda.core.transactions.BaseTransaction extends java.lang.Object implements net.corda.core.contracts.NamedByHash +@net.corda.core.DoNotImplement public abstract class net.corda.core.transactions.BaseTransaction extends java.lang.Object implements net.corda.core.contracts.NamedByHash public () protected void checkBaseInvariants() @org.jetbrains.annotations.NotNull public final List filterOutRefs(Class, function.Predicate) @@ -2534,21 +2554,21 @@ public abstract class net.corda.core.transactions.BaseTransaction extends java.l @org.jetbrains.annotations.NotNull public final List outputsOfType(Class) @org.jetbrains.annotations.NotNull public String toString() ## -public class net.corda.core.transactions.ComponentGroup extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public class net.corda.core.transactions.ComponentGroup extends java.lang.Object public (int, List) @org.jetbrains.annotations.NotNull public List getComponents() public int getGroupIndex() ## -public final class net.corda.core.transactions.ComponentVisibilityException extends net.corda.core.CordaException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.transactions.ComponentVisibilityException extends net.corda.core.CordaException public (net.corda.core.crypto.SecureHash, String) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getId() @org.jetbrains.annotations.NotNull public final String getReason() ## -public abstract class net.corda.core.transactions.CoreTransaction extends net.corda.core.transactions.BaseTransaction +@net.corda.core.DoNotImplement public abstract class net.corda.core.transactions.CoreTransaction extends net.corda.core.transactions.BaseTransaction public () @org.jetbrains.annotations.NotNull public abstract List getInputs() ## -public final class net.corda.core.transactions.FilteredComponentGroup extends net.corda.core.transactions.ComponentGroup +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.transactions.FilteredComponentGroup extends net.corda.core.transactions.ComponentGroup public (int, List, List, net.corda.core.crypto.PartialMerkleTree) public final int component1() @org.jetbrains.annotations.NotNull public final List component2() @@ -2563,7 +2583,7 @@ public final class net.corda.core.transactions.FilteredComponentGroup extends ne public int hashCode() public String toString() ## -public final class net.corda.core.transactions.FilteredTransaction extends net.corda.core.transactions.TraversableTransaction +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.transactions.FilteredTransaction extends net.corda.core.transactions.TraversableTransaction @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 boolean checkWithFun(kotlin.jvm.functions.Function1) @@ -2576,17 +2596,17 @@ public final class net.corda.core.transactions.FilteredTransaction extends net.c public static final class net.corda.core.transactions.FilteredTransaction$Companion extends java.lang.Object @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.FilteredTransaction buildFilteredTransaction(net.corda.core.transactions.WireTransaction, function.Predicate) ## -public final class net.corda.core.transactions.FilteredTransactionVerificationException extends net.corda.core.CordaException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.transactions.FilteredTransactionVerificationException extends net.corda.core.CordaException public (net.corda.core.crypto.SecureHash, String) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getId() @org.jetbrains.annotations.NotNull public final String getReason() ## -public abstract class net.corda.core.transactions.FullTransaction extends net.corda.core.transactions.BaseTransaction +@net.corda.core.DoNotImplement public abstract class net.corda.core.transactions.FullTransaction extends net.corda.core.transactions.BaseTransaction public () protected void checkBaseInvariants() @org.jetbrains.annotations.NotNull public abstract List getInputs() ## -public final class net.corda.core.transactions.LedgerTransaction extends net.corda.core.transactions.FullTransaction +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.transactions.LedgerTransaction extends net.corda.core.transactions.FullTransaction public (List, List, List, List, net.corda.core.crypto.SecureHash, net.corda.core.identity.Party, net.corda.core.contracts.TimeWindow, net.corda.core.contracts.PrivacySalt) @org.jetbrains.annotations.NotNull public final List commandsOfType(Class) @org.jetbrains.annotations.NotNull public final List component1() @@ -2639,11 +2659,11 @@ public static final class net.corda.core.transactions.LedgerTransaction$InOutGro public int hashCode() public String toString() ## -public final class net.corda.core.transactions.MissingContractAttachments extends net.corda.core.flows.FlowException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.transactions.MissingContractAttachments extends net.corda.core.flows.FlowException public (List) @org.jetbrains.annotations.NotNull public final List getStates() ## -public final class net.corda.core.transactions.NotaryChangeLedgerTransaction extends net.corda.core.transactions.FullTransaction implements net.corda.core.transactions.TransactionWithSignatures +@net.corda.core.DoNotImplement public final class net.corda.core.transactions.NotaryChangeLedgerTransaction extends net.corda.core.transactions.FullTransaction implements net.corda.core.transactions.TransactionWithSignatures public (List, net.corda.core.identity.Party, net.corda.core.identity.Party, net.corda.core.crypto.SecureHash, List) public void checkSignaturesAreValid() @org.jetbrains.annotations.NotNull public final List component1() @@ -2666,7 +2686,7 @@ public final class net.corda.core.transactions.NotaryChangeLedgerTransaction ext public String toString() public void verifyRequiredSignatures() ## -public final class net.corda.core.transactions.NotaryChangeWireTransaction extends net.corda.core.transactions.CoreTransaction +@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) @org.jetbrains.annotations.NotNull public final List component1() @org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party component2() @@ -2682,7 +2702,7 @@ public final class net.corda.core.transactions.NotaryChangeWireTransaction exten @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.NotaryChangeLedgerTransaction resolve(net.corda.core.node.ServiceHub, List) public String toString() ## -public final class net.corda.core.transactions.SignedTransaction extends java.lang.Object implements net.corda.core.transactions.TransactionWithSignatures +@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 public (net.corda.core.serialization.SerializedBytes, List) public (net.corda.core.transactions.CoreTransaction, List) @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.FilteredTransaction buildFilteredTransaction(function.Predicate) @@ -2717,7 +2737,7 @@ public final class net.corda.core.transactions.SignedTransaction extends java.la @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.SignedTransaction withAdditionalSignatures(Iterable) public static final net.corda.core.transactions.SignedTransaction$Companion Companion ## -public static final class net.corda.core.transactions.SignedTransaction$SignaturesMissingException extends java.security.SignatureException implements net.corda.core.CordaThrowable, net.corda.core.contracts.NamedByHash +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.transactions.SignedTransaction$SignaturesMissingException extends java.security.SignatureException implements net.corda.core.CordaThrowable, net.corda.core.contracts.NamedByHash public (Set, List, net.corda.core.crypto.SecureHash) public void addSuppressed(Throwable[]) @org.jetbrains.annotations.NotNull public final List getDescriptions() @@ -2768,7 +2788,7 @@ public class net.corda.core.transactions.TransactionBuilder extends java.lang.Ob @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.WireTransaction toWireTransaction(net.corda.core.node.ServicesForResolution) public final void verify(net.corda.core.node.ServiceHub) ## -public interface net.corda.core.transactions.TransactionWithSignatures extends net.corda.core.contracts.NamedByHash +@net.corda.core.DoNotImplement public interface net.corda.core.transactions.TransactionWithSignatures extends net.corda.core.contracts.NamedByHash public abstract void checkSignaturesAreValid() @org.jetbrains.annotations.NotNull public abstract List getKeyDescriptions(Set) @org.jetbrains.annotations.NotNull public abstract Set getMissingSigners() @@ -2776,7 +2796,7 @@ public interface net.corda.core.transactions.TransactionWithSignatures extends n @org.jetbrains.annotations.NotNull public abstract List getSigs() public abstract void verifyRequiredSignatures() ## -public abstract class net.corda.core.transactions.TraversableTransaction extends net.corda.core.transactions.CoreTransaction +@net.corda.core.DoNotImplement public abstract class net.corda.core.transactions.TraversableTransaction extends net.corda.core.transactions.CoreTransaction public (List) @org.jetbrains.annotations.NotNull public final List getAttachments() @org.jetbrains.annotations.NotNull public final List getAvailableComponentGroups() @@ -2787,7 +2807,7 @@ public abstract class net.corda.core.transactions.TraversableTransaction extends @org.jetbrains.annotations.NotNull public List getOutputs() @org.jetbrains.annotations.Nullable public final net.corda.core.contracts.TimeWindow getTimeWindow() ## -public final class net.corda.core.transactions.WireTransaction extends net.corda.core.transactions.TraversableTransaction +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.transactions.WireTransaction extends net.corda.core.transactions.TraversableTransaction @kotlin.Deprecated public (List, List, List, List, net.corda.core.identity.Party, net.corda.core.contracts.TimeWindow, net.corda.core.contracts.PrivacySalt) public (List, net.corda.core.contracts.PrivacySalt) @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.FilteredTransaction buildFilteredTransaction(function.Predicate) @@ -2811,7 +2831,7 @@ public final class net.corda.core.utilities.ByteArrays extends java.lang.Object @org.jetbrains.annotations.NotNull public static final net.corda.core.utilities.ByteSequence sequence(byte[], int, int) @org.jetbrains.annotations.NotNull public static final String toHexString(byte[]) ## -public abstract class net.corda.core.utilities.ByteSequence extends java.lang.Object implements java.lang.Comparable +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.utilities.ByteSequence extends java.lang.Object implements java.lang.Comparable public int compareTo(net.corda.core.utilities.ByteSequence) @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ByteSequence copy() public boolean equals(Object) @@ -2867,7 +2887,7 @@ public final class net.corda.core.utilities.KotlinUtilsKt extends java.lang.Obje public static final void trace(org.slf4j.Logger, kotlin.jvm.functions.Function0) @org.jetbrains.annotations.NotNull public static final net.corda.core.utilities.PropertyDelegate transient(kotlin.jvm.functions.Function0) ## -public final class net.corda.core.utilities.NetworkHostAndPort extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.utilities.NetworkHostAndPort extends java.lang.Object public (String, int) @org.jetbrains.annotations.NotNull public final String component1() public final int component2() @@ -2921,7 +2941,7 @@ public static final class net.corda.core.utilities.NonEmptySet$iterator$1 extend public Object next() public void remove() ## -public class net.corda.core.utilities.OpaqueBytes extends net.corda.core.utilities.ByteSequence +@net.corda.core.serialization.CordaSerializable public class net.corda.core.utilities.OpaqueBytes extends net.corda.core.utilities.ByteSequence public (byte[]) @org.jetbrains.annotations.NotNull public final byte[] getBytes() public int getOffset() @@ -2930,13 +2950,13 @@ public class net.corda.core.utilities.OpaqueBytes extends net.corda.core.utiliti ## public static final class net.corda.core.utilities.OpaqueBytes$Companion extends java.lang.Object ## -public final class net.corda.core.utilities.OpaqueBytesSubSequence extends net.corda.core.utilities.ByteSequence +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.utilities.OpaqueBytesSubSequence extends net.corda.core.utilities.ByteSequence public (byte[], int, int) @org.jetbrains.annotations.NotNull public byte[] getBytes() public int getOffset() public int getSize() ## -public final class net.corda.core.utilities.ProgressTracker extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.utilities.ProgressTracker extends java.lang.Object public final void endWithError(Throwable) @org.jetbrains.annotations.NotNull public final List getAllSteps() @org.jetbrains.annotations.NotNull public final rx.Observable getChanges() @@ -2952,9 +2972,9 @@ public final class net.corda.core.utilities.ProgressTracker extends java.lang.Ob public final void setChildProgressTracker(net.corda.core.utilities.ProgressTracker$Step, net.corda.core.utilities.ProgressTracker) public final void setCurrentStep(net.corda.core.utilities.ProgressTracker$Step) ## -public abstract static class net.corda.core.utilities.ProgressTracker$Change extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public abstract static class net.corda.core.utilities.ProgressTracker$Change extends java.lang.Object ## -public static final class net.corda.core.utilities.ProgressTracker$Change$Position extends net.corda.core.utilities.ProgressTracker$Change +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.utilities.ProgressTracker$Change$Position extends net.corda.core.utilities.ProgressTracker$Change public (net.corda.core.utilities.ProgressTracker, net.corda.core.utilities.ProgressTracker$Step) @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ProgressTracker component1() @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ProgressTracker$Step component2() @@ -2965,7 +2985,7 @@ public static final class net.corda.core.utilities.ProgressTracker$Change$Positi public int hashCode() @org.jetbrains.annotations.NotNull public String toString() ## -public static final class net.corda.core.utilities.ProgressTracker$Change$Rendering extends net.corda.core.utilities.ProgressTracker$Change +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.utilities.ProgressTracker$Change$Rendering extends net.corda.core.utilities.ProgressTracker$Change public (net.corda.core.utilities.ProgressTracker, net.corda.core.utilities.ProgressTracker$Step) @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ProgressTracker component1() @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ProgressTracker$Step component2() @@ -2976,7 +2996,7 @@ public static final class net.corda.core.utilities.ProgressTracker$Change$Render public int hashCode() @org.jetbrains.annotations.NotNull public String toString() ## -public static final class net.corda.core.utilities.ProgressTracker$Change$Structural extends net.corda.core.utilities.ProgressTracker$Change +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.utilities.ProgressTracker$Change$Structural extends net.corda.core.utilities.ProgressTracker$Change public (net.corda.core.utilities.ProgressTracker, net.corda.core.utilities.ProgressTracker$Step) @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ProgressTracker component1() @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ProgressTracker$Step component2() @@ -2987,25 +3007,25 @@ public static final class net.corda.core.utilities.ProgressTracker$Change$Struct public int hashCode() @org.jetbrains.annotations.NotNull public String toString() ## -public static final class net.corda.core.utilities.ProgressTracker$DONE extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.utilities.ProgressTracker$DONE extends net.corda.core.utilities.ProgressTracker$Step public boolean equals(Object) public static final net.corda.core.utilities.ProgressTracker$DONE INSTANCE ## -public static class net.corda.core.utilities.ProgressTracker$Step extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public static class net.corda.core.utilities.ProgressTracker$Step extends java.lang.Object public (String) @org.jetbrains.annotations.Nullable public net.corda.core.utilities.ProgressTracker childProgressTracker() @org.jetbrains.annotations.NotNull public rx.Observable getChanges() @org.jetbrains.annotations.NotNull public Map getExtraAuditData() @org.jetbrains.annotations.NotNull public String getLabel() ## -public static final class net.corda.core.utilities.ProgressTracker$UNSTARTED extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.utilities.ProgressTracker$UNSTARTED extends net.corda.core.utilities.ProgressTracker$Step public boolean equals(Object) public static final net.corda.core.utilities.ProgressTracker$UNSTARTED INSTANCE ## public interface net.corda.core.utilities.PropertyDelegate public abstract Object getValue(Object, kotlin.reflect.KProperty) ## -public abstract class net.corda.core.utilities.Try extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.utilities.Try extends java.lang.Object @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.Try combine(net.corda.core.utilities.Try, kotlin.jvm.functions.Function2) @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.Try flatMap(kotlin.jvm.functions.Function1) public abstract Object getOrThrow() @@ -3018,7 +3038,7 @@ public abstract class net.corda.core.utilities.Try extends java.lang.Object public static final class net.corda.core.utilities.Try$Companion extends java.lang.Object @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.Try on(kotlin.jvm.functions.Function0) ## -public static final class net.corda.core.utilities.Try$Failure extends net.corda.core.utilities.Try +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.utilities.Try$Failure extends net.corda.core.utilities.Try public (Throwable) @org.jetbrains.annotations.NotNull public final Throwable component1() @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.Try$Failure copy(Throwable) @@ -3030,7 +3050,7 @@ public static final class net.corda.core.utilities.Try$Failure extends net.corda public boolean isSuccess() @org.jetbrains.annotations.NotNull public String toString() ## -public static final class net.corda.core.utilities.Try$Success extends net.corda.core.utilities.Try +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.utilities.Try$Success extends net.corda.core.utilities.Try public (Object) public final Object component1() @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.Try$Success copy(Object) @@ -3182,7 +3202,7 @@ public abstract static class net.corda.client.jackson.JacksonSupport$WireTransac @com.fasterxml.jackson.annotation.JsonIgnore @org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.MerkleTree getMerkleTree() @com.fasterxml.jackson.annotation.JsonIgnore @org.jetbrains.annotations.NotNull public abstract List getOutputStates() ## -public class net.corda.client.jackson.StringToMethodCallParser extends java.lang.Object +@javax.annotation.concurrent.ThreadSafe public class net.corda.client.jackson.StringToMethodCallParser extends java.lang.Object public (Class) public (Class, com.fasterxml.jackson.databind.ObjectMapper) public (kotlin.reflect.KClass) @@ -3254,7 +3274,7 @@ public final class net.corda.client.rpc.CordaRPCConnection extends java.lang.Obj public final class net.corda.client.rpc.PermissionException extends net.corda.core.CordaRuntimeException public (String) ## -public interface net.corda.client.rpc.RPCConnection extends java.io.Closeable +@net.corda.core.DoNotImplement public interface net.corda.client.rpc.RPCConnection extends java.io.Closeable public abstract void forceClose() @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.RPCOps getProxy() public abstract int getServerProtocolVersion() diff --git a/.ci/check-api-changes.sh b/.ci/check-api-changes.sh index e9ac25cf6d..72ae5f9d80 100755 --- a/.ci/check-api-changes.sh +++ b/.ci/check-api-changes.sh @@ -1,4 +1,5 @@ #!/bin/bash +set +o posix echo "Starting API Diff" @@ -11,7 +12,7 @@ if [ ! -f $apiCurrent ]; then fi # Remove the two header lines from the diff output. -diffContents=`diff -u $apiCurrent $APIHOME/../build/api/api-corda-*.txt | tail --lines=+3` +diffContents=`diff -u $apiCurrent $APIHOME/../build/api/api-corda-*.txt | tail -n +3` echo "Diff contents:" echo "$diffContents" echo @@ -30,7 +31,15 @@ if [ $removalCount -gt 0 ]; then fi # Adding new abstract methods could also break the API. -newAbstracts=$(echo "$diffContents" | grep "^+" | grep "\(public\|protected\) abstract") +# However, first exclude anything with the @DoNotImplement annotation. + +function forUserImpl() { + awk '/DoNotImplement/,/^##/{ next }{ print }' $1 +} + +userDiffContents=`diff -u <(forUserImpl $apiCurrent) <(forUserImpl $APIHOME/../build/api/api-corda-*.txt) | tail -n +3` + +newAbstracts=$(echo "$userDiffContents" | grep "^+" | grep "\(public\|protected\) abstract") abstractCount=`grep -v "^$" < + + + + + diff --git a/.idea/runConfigurations/BankOfCordaDriverKt___Issue_Web.xml b/.idea/runConfigurations/BankOfCordaDriverKt___Issue_Web.xml new file mode 100644 index 0000000000..321d3d2d06 --- /dev/null +++ b/.idea/runConfigurations/BankOfCordaDriverKt___Issue_Web.xml @@ -0,0 +1,15 @@ + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/BankOfCordaDriverKt___Run_Stack.xml b/.idea/runConfigurations/BankOfCordaDriverKt___Run_Stack.xml new file mode 100644 index 0000000000..ea61b6ef8d --- /dev/null +++ b/.idea/runConfigurations/BankOfCordaDriverKt___Run_Stack.xml @@ -0,0 +1,15 @@ + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index ca333dcec6..8b11a16368 100644 --- a/build.gradle +++ b/build.gradle @@ -41,9 +41,11 @@ buildscript { ext.jansi_version = '1.14' ext.hibernate_version = '5.2.6.Final' ext.h2_version = '1.4.194' // Update docs if renamed or removed. + ext.postgresql_version = '42.1.4' ext.rxjava_version = '1.2.4' ext.dokka_version = '0.9.14' ext.eddsa_version = '0.2.0' + ext.dependency_checker_version = '3.0.1' // Update 121 is required for ObjectInputFilter and at time of writing 131 was latest: ext.java8_minUpdateVersion = '131' @@ -66,6 +68,7 @@ buildscript { classpath "org.jetbrains.dokka:dokka-gradle-plugin:${dokka_version}" classpath "org.ajoberstar:grgit:1.1.0" classpath "net.i2p.crypto:eddsa:$eddsa_version" // Needed for ServiceIdentityGenerator in the build environment. + classpath "org.owasp:dependency-check-gradle:${dependency_checker_version}" } } @@ -100,7 +103,13 @@ allprojects { apply plugin: 'kotlin' apply plugin: 'java' apply plugin: 'jacoco' + apply plugin: 'org.owasp.dependencycheck' + dependencyCheck { + suppressionFile = '.ci/dependency-checker/suppressedLibraries.xml' + cveValidForHours = 1 + format = 'ALL' + } sourceCompatibility = 1.8 targetCompatibility = 1.8 diff --git a/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt b/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt index d8595e6bd9..c9ee7af180 100644 --- a/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt +++ b/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt @@ -1,7 +1,7 @@ package net.corda.client.jackson import com.fasterxml.jackson.databind.SerializationFeature -import com.nhaarman.mockito_kotlin.mock +import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever import net.corda.core.contracts.Amount import net.corda.core.cordapp.CordappProvider @@ -9,10 +9,7 @@ import net.corda.core.crypto.* import net.corda.core.node.ServiceHub import net.corda.core.transactions.SignedTransaction import net.corda.finance.USD -import net.corda.testing.ALICE_PUBKEY -import net.corda.testing.DUMMY_NOTARY -import net.corda.testing.MINI_CORP -import net.corda.testing.TestDependencyInjectionBase +import net.corda.testing.* import net.corda.testing.contracts.DummyContract import org.junit.Before import org.junit.Test @@ -32,9 +29,9 @@ class JacksonSupportTest : TestDependencyInjectionBase() { @Before fun setup() { - services = mock() - cordappProvider = mock() - whenever(services.cordappProvider).thenReturn(cordappProvider) + services = rigorousMock() + cordappProvider = rigorousMock() + doReturn(cordappProvider).whenever(services).cordappProvider } @Test @@ -91,8 +88,7 @@ class JacksonSupportTest : TestDependencyInjectionBase() { @Test fun writeTransaction() { val attachmentRef = SecureHash.randomSHA256() - whenever(cordappProvider.getContractAttachmentID(DummyContract.PROGRAM_ID)) - .thenReturn(attachmentRef) + doReturn(attachmentRef).whenever(cordappProvider).getContractAttachmentID(DummyContract.PROGRAM_ID) fun makeDummyTx(): SignedTransaction { val wtx = DummyContract.generateInitial(1, DUMMY_NOTARY, MINI_CORP.ref(1)) .toWireTransaction(services) 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 277769354c..9479ffe36a 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 @@ -2,6 +2,7 @@ package net.corda.client.rpc 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.FlowProgressHandle import net.corda.core.messaging.StateMachineUpdate @@ -143,7 +144,7 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C } } val nodeIdentity = node.info.chooseIdentity() - node.services.startFlow(CashIssueFlow(2000.DOLLARS, OpaqueBytes.of(0), nodeIdentity), FlowInitiator.Shell).resultFuture.getOrThrow() + node.services.startFlow(CashIssueFlow(2000.DOLLARS, OpaqueBytes.of(0), nodeIdentity), FlowInitiator.Shell).flatMap { it.resultFuture }.getOrThrow() proxy.startFlow(::CashIssueFlow, 123.DOLLARS, OpaqueBytes.of(0), 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 7ccf1913d5..a25881f4ec 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 @@ -92,6 +92,7 @@ class RPCStabilityTests { startAndStop() } val numberOfThreadsAfter = waitUntilNumberOfThreadsStable(executor) + assertTrue(numberOfThreadsBefore >= numberOfThreadsAfter) executor.shutdownNow() } diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/RPCConnection.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/RPCConnection.kt index ae53adee53..bb6a7b165a 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/RPCConnection.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/RPCConnection.kt @@ -1,5 +1,6 @@ package net.corda.client.rpc +import net.corda.core.DoNotImplement import net.corda.core.messaging.RPCOps import java.io.Closeable @@ -10,6 +11,7 @@ import java.io.Closeable * [Closeable.close] may be used to shut down the connection and release associated resources. It is an * alias for [notifyServerAndClose]. */ +@DoNotImplement interface RPCConnection : Closeable { /** * Holds a synthetic class that automatically forwards method calls to the server, and returns the response. diff --git a/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt b/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt index 8030469fe5..27fb5b6791 100644 --- a/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt +++ b/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt @@ -13,10 +13,10 @@ class SwapIdentitiesFlowTests { @Test fun `issue key`() { // We run this in parallel threads to help catch any race conditions that may exist. - val mockNet = MockNetwork(false, true) + val mockNet = MockNetwork(threadPerNode = true) // Set up values we'll need - val notaryNode = mockNet.createNotaryNode() + mockNet.createNotaryNode() val aliceNode = mockNet.createPartyNode(ALICE.name) val bobNode = mockNet.createPartyNode(BOB.name) val alice = aliceNode.info.singleIdentity() @@ -53,7 +53,7 @@ class SwapIdentitiesFlowTests { @Test fun `verifies identity name`() { // We run this in parallel threads to help catch any race conditions that may exist. - val mockNet = MockNetwork(false, true) + val mockNet = MockNetwork(threadPerNode = true) // Set up values we'll need val notaryNode = mockNet.createNotaryNode(DUMMY_NOTARY.name) @@ -78,7 +78,7 @@ class SwapIdentitiesFlowTests { @Test fun `verifies signature`() { // We run this in parallel threads to help catch any race conditions that may exist. - val mockNet = MockNetwork(false, true) + val mockNet = MockNetwork(threadPerNode = true) // Set up values we'll need val notaryNode = mockNet.createNotaryNode(DUMMY_NOTARY.name) diff --git a/constants.properties b/constants.properties index 48f942803c..c85d51a756 100644 --- a/constants.properties +++ b/constants.properties @@ -1,4 +1,4 @@ -gradlePluginsVersion=2.0.4 +gradlePluginsVersion=2.0.6 kotlinVersion=1.1.50 guavaVersion=21.0 bouncycastleVersion=1.57 diff --git a/core/src/main/kotlin/net/corda/core/DoNotImplement.kt b/core/src/main/kotlin/net/corda/core/DoNotImplement.kt new file mode 100644 index 0000000000..6800a92f0c --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/DoNotImplement.kt @@ -0,0 +1,18 @@ +package net.corda.core + +import java.lang.annotation.Inherited + +/** + * This annotation is for interfaces and abstract classes that provide Corda functionality + * to user applications. Future versions of Corda may add new methods to such interfaces and + * classes, but will not remove or modify existing methods. + * + * Adding new methods does not break Corda's API compatibility guarantee because applications + * should not implement or extend anything annotated with [DoNotImplement]. These classes are + * only meant to be implemented by Corda itself. + */ +@Retention(AnnotationRetention.BINARY) +@Target(AnnotationTarget.CLASS) +@MustBeDocumented +@Inherited +annotation class DoNotImplement \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/contracts/Attachment.kt b/core/src/main/kotlin/net/corda/core/contracts/Attachment.kt index b55055e529..e55a91d546 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/Attachment.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/Attachment.kt @@ -2,6 +2,7 @@ package net.corda.core.contracts import net.corda.core.identity.Party import net.corda.core.internal.extractFile +import net.corda.core.serialization.CordaSerializable import java.io.FileNotFoundException import java.io.InputStream import java.io.OutputStream @@ -17,6 +18,7 @@ import java.util.jar.JarInputStream * - Legal documents * - Facts generated by oracles which might be reused a lot */ +@CordaSerializable interface Attachment : NamedByHash { fun open(): InputStream fun openAsJAR(): JarInputStream { diff --git a/core/src/main/kotlin/net/corda/core/contracts/ComponentGroupEnum.kt b/core/src/main/kotlin/net/corda/core/contracts/ComponentGroupEnum.kt index c32792a998..514abb986a 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/ComponentGroupEnum.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/ComponentGroupEnum.kt @@ -10,5 +10,6 @@ enum class ComponentGroupEnum { COMMANDS_GROUP, // ordinal = 2. ATTACHMENTS_GROUP, // ordinal = 3. NOTARY_GROUP, // ordinal = 4. - TIMEWINDOW_GROUP // ordinal = 5. + TIMEWINDOW_GROUP, // ordinal = 5. + SIGNERS_GROUP // ordinal = 6. } diff --git a/core/src/main/kotlin/net/corda/core/contracts/TransactionVerificationException.kt b/core/src/main/kotlin/net/corda/core/contracts/TransactionVerificationException.kt index eb935965b3..26145b38a1 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/TransactionVerificationException.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/TransactionVerificationException.kt @@ -19,7 +19,7 @@ sealed class TransactionVerificationException(val txId: SecureHash, message: Str class ContractConstraintRejection(txId: SecureHash, contractClass: String) : TransactionVerificationException(txId, "Contract constraints failed for $contractClass", null) - class MissingAttachmentRejection(txId: SecureHash, contractClass: String) + class MissingAttachmentRejection(txId: SecureHash, val contractClass: String) : TransactionVerificationException(txId, "Contract constraints failed, could not find attachment for: $contractClass", null) class ContractCreationError(txId: SecureHash, contractClass: String, cause: Throwable) diff --git a/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt b/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt index 8f7f5f8d56..f4fe71ead0 100644 --- a/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt +++ b/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt @@ -1,5 +1,6 @@ package net.corda.core.cordapp +import net.corda.core.DoNotImplement import net.corda.core.flows.FlowLogic import net.corda.core.schemas.MappedSchema import net.corda.core.serialization.SerializationWhitelist @@ -17,13 +18,14 @@ import java.net.URL * @property contractClassNames List of contracts * @property initiatedFlows List of initiatable flow classes * @property rpcFlows List of RPC initiable flows classes - * @property serviceFlows List of [CordaService] initiable flows classes + * @property serviceFlows List of [net.corda.core.node.services.CordaService] initiable flows classes * @property schedulableFlows List of flows startable by the scheduler - * @property servies List of RPC services + * @property services List of RPC services * @property serializationWhitelists List of Corda plugin registries * @property customSchemas List of custom schemas * @property jarPath The path to the JAR for this CorDapp */ +@DoNotImplement interface Cordapp { val name: String val contractClassNames: List diff --git a/core/src/main/kotlin/net/corda/core/cordapp/CordappProvider.kt b/core/src/main/kotlin/net/corda/core/cordapp/CordappProvider.kt index 289e606ac4..bf7864ee95 100644 --- a/core/src/main/kotlin/net/corda/core/cordapp/CordappProvider.kt +++ b/core/src/main/kotlin/net/corda/core/cordapp/CordappProvider.kt @@ -1,11 +1,13 @@ package net.corda.core.cordapp +import net.corda.core.DoNotImplement import net.corda.core.contracts.ContractClassName import net.corda.core.node.services.AttachmentId /** * Provides access to what the node knows about loaded applications. */ +@DoNotImplement interface CordappProvider { /** * Exposes the current CorDapp context which will contain information and configuration of the CorDapp that diff --git a/core/src/main/kotlin/net/corda/core/crypto/PartialMerkleTree.kt b/core/src/main/kotlin/net/corda/core/crypto/PartialMerkleTree.kt index 77e06bf1c2..b46eb7f631 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/PartialMerkleTree.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/PartialMerkleTree.kt @@ -158,4 +158,41 @@ class PartialMerkleTree(val root: PartialTree) { return false return (verifyRoot == merkleRootHash) } + + /** + * Method to return the index of the input leaf in the partial Merkle tree structure. + * @param leaf the component hash to check. + * @return leaf-index of this component (starting from zero). + * @throws MerkleTreeException if the provided hash is not in the tree. + */ + @Throws(MerkleTreeException::class) + internal fun leafIndex(leaf: SecureHash): Int { + // Special handling if the tree consists of one node only. + if (root is PartialTree.IncludedLeaf && root.hash == leaf) return 0 + val flagPath = mutableListOf() + if (!leafIndexHelper(leaf, this.root, flagPath)) throw MerkleTreeException("The provided hash $leaf is not in the tree.") + return indexFromFlagPath(flagPath) + } + + // Helper function to compute the path. False means go to the left and True to the right. + // Because the path is updated recursively, the path is returned in reverse order. + private fun leafIndexHelper(leaf: SecureHash, node: PartialTree, path: MutableList): Boolean { + if (node is PartialTree.IncludedLeaf) { + return node.hash == leaf + } else if (node is PartialTree.Node) { + if (leafIndexHelper(leaf, node.left, path)) { + path.add(false) + return true + } + if (leafIndexHelper(leaf, node.right, path)) { + path.add(true) + return true + } + } + return false + } + + // Return the leaf index from the path boolean list. + private fun indexFromFlagPath(pathList: List) = + pathList.mapIndexed { index, value -> if (value) (1 shl index) else 0 }.sum() } diff --git a/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt b/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt index 0239c81f20..322a9f9925 100644 --- a/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt +++ b/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt @@ -41,6 +41,16 @@ import java.time.Instant * and request they start their counterpart flow, then make sure it's annotated with [InitiatingFlow]. This annotation * also has a version property to allow you to version your flow and enables a node to restrict support for the flow to * that particular version. + * + * Functions that suspend the flow (including all functions on [FlowSession]) accept a [maySkipCheckpoint] parameter + * defaulting to false, false meaning a checkpoint should always be created on suspend. This parameter may be set to + * true which allows the implementation to potentially optimise away the checkpoint, saving a roundtrip to the database. + * + * This option however comes with a big warning sign: Setting the parameter to true requires the flow's code to be + * replayable from the previous checkpoint (or start of flow) up until the next checkpoint (or end of flow) in order to + * prepare for hard failures. As suspending functions always commit the flow's database transaction regardless of this + * parameter the flow must be prepared for scenarios where a previous running of the flow *already committed its + * relevant database transactions*. Only set this option to true if you know what you're doing. */ abstract class FlowLogic { /** This is where you should log things to. */ @@ -123,7 +133,7 @@ abstract class FlowLogic { */ @Deprecated("Use FlowSession.getFlowInfo()", level = DeprecationLevel.WARNING) @Suspendable - fun getFlowInfo(otherParty: Party): FlowInfo = stateMachine.getFlowInfo(otherParty, flowUsedForSessions) + fun getFlowInfo(otherParty: Party): FlowInfo = stateMachine.getFlowInfo(otherParty, flowUsedForSessions, maySkipCheckpoint = false) /** * Serializes and queues the given [payload] object for sending to the [otherParty]. Suspends until a response @@ -157,7 +167,7 @@ abstract class FlowLogic { @Deprecated("Use FlowSession.sendAndReceive()", level = DeprecationLevel.WARNING) @Suspendable open fun sendAndReceive(receiveType: Class, otherParty: Party, payload: Any): UntrustworthyData { - return stateMachine.sendAndReceive(receiveType, otherParty, payload, flowUsedForSessions) + return stateMachine.sendAndReceive(receiveType, otherParty, payload, flowUsedForSessions, retrySend = false, maySkipCheckpoint = false) } /** @@ -171,17 +181,17 @@ abstract class FlowLogic { */ @Deprecated("Use FlowSession.sendAndReceiveWithRetry()", level = DeprecationLevel.WARNING) internal inline fun sendAndReceiveWithRetry(otherParty: Party, payload: Any): UntrustworthyData { - return stateMachine.sendAndReceive(R::class.java, otherParty, payload, flowUsedForSessions, retrySend = true) + return stateMachine.sendAndReceive(R::class.java, otherParty, payload, flowUsedForSessions, retrySend = true, maySkipCheckpoint = false) } @Suspendable internal fun FlowSession.sendAndReceiveWithRetry(receiveType: Class, payload: Any): UntrustworthyData { - return stateMachine.sendAndReceive(receiveType, counterparty, payload, flowUsedForSessions, retrySend = true) + return stateMachine.sendAndReceive(receiveType, counterparty, payload, flowUsedForSessions, retrySend = true, maySkipCheckpoint = false) } @Suspendable internal inline fun FlowSession.sendAndReceiveWithRetry(payload: Any): UntrustworthyData { - return stateMachine.sendAndReceive(R::class.java, counterparty, payload, flowUsedForSessions, retrySend = true) + return stateMachine.sendAndReceive(R::class.java, counterparty, payload, flowUsedForSessions, retrySend = true, maySkipCheckpoint = false) } /** @@ -206,7 +216,7 @@ abstract class FlowLogic { @Deprecated("Use FlowSession.receive()", level = DeprecationLevel.WARNING) @Suspendable open fun receive(receiveType: Class, otherParty: Party): UntrustworthyData { - return stateMachine.receive(receiveType, otherParty, flowUsedForSessions) + return stateMachine.receive(receiveType, otherParty, flowUsedForSessions, maySkipCheckpoint = false) } /** Suspends until a message has been received for each session in the specified [sessions]. @@ -250,7 +260,9 @@ abstract class FlowLogic { */ @Deprecated("Use FlowSession.send()", level = DeprecationLevel.WARNING) @Suspendable - open fun send(otherParty: Party, payload: Any) = stateMachine.send(otherParty, payload, flowUsedForSessions) + open fun send(otherParty: Party, payload: Any) { + stateMachine.send(otherParty, payload, flowUsedForSessions, maySkipCheckpoint = false) + } /** * Invokes the given subflow. This function returns once the subflow completes successfully with the result @@ -342,7 +354,10 @@ abstract class FlowLogic { * valid by the local node, but that doesn't imply the vault will consider it relevant. */ @Suspendable - fun waitForLedgerCommit(hash: SecureHash): SignedTransaction = stateMachine.waitForLedgerCommit(hash, this) + @JvmOverloads + fun waitForLedgerCommit(hash: SecureHash, maySkipCheckpoint: Boolean = false): SignedTransaction { + return stateMachine.waitForLedgerCommit(hash, this, maySkipCheckpoint = maySkipCheckpoint) + } /** * Returns a shallow copy of the Quasar stack frames at the time of call to [flowStackSnapshot]. Use this to inspect diff --git a/core/src/main/kotlin/net/corda/core/flows/FlowLogicRef.kt b/core/src/main/kotlin/net/corda/core/flows/FlowLogicRef.kt index 0b90909180..a3043709d0 100644 --- a/core/src/main/kotlin/net/corda/core/flows/FlowLogicRef.kt +++ b/core/src/main/kotlin/net/corda/core/flows/FlowLogicRef.kt @@ -1,6 +1,6 @@ package net.corda.core.flows -import net.corda.core.crypto.SecureHash +import net.corda.core.DoNotImplement import net.corda.core.serialization.CordaSerializable /** @@ -8,6 +8,7 @@ import net.corda.core.serialization.CordaSerializable * Typically this would be used from within the nextScheduledActivity method of a QueryableState to specify * the flow to run at the scheduled time. */ +@DoNotImplement interface FlowLogicRefFactory { fun create(flowClass: Class>, vararg args: Any?): FlowLogicRef } @@ -24,4 +25,5 @@ class IllegalFlowLogicException(type: Class<*>, msg: String) : IllegalArgumentEx */ // TODO: align this with the existing [FlowRef] in the bank-side API (probably replace some of the API classes) @CordaSerializable +@DoNotImplement interface FlowLogicRef \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/flows/FlowSession.kt b/core/src/main/kotlin/net/corda/core/flows/FlowSession.kt index f5589f96c4..b1782f5424 100644 --- a/core/src/main/kotlin/net/corda/core/flows/FlowSession.kt +++ b/core/src/main/kotlin/net/corda/core/flows/FlowSession.kt @@ -1,6 +1,7 @@ package net.corda.core.flows import co.paralleluniverse.fibers.Suspendable +import net.corda.core.DoNotImplement import net.corda.core.identity.Party import net.corda.core.utilities.UntrustworthyData @@ -41,6 +42,7 @@ import net.corda.core.utilities.UntrustworthyData * will become * otherSideSession.send(something) */ +@DoNotImplement abstract class FlowSession { /** * The [Party] on the other side of this session. In the case of a session created by [FlowLogic.initiateFlow] @@ -52,8 +54,20 @@ abstract class FlowSession { * Returns a [FlowInfo] object describing the flow [counterparty] is using. With [FlowInfo.flowVersion] it * provides the necessary information needed for the evolution of flows and enabling backwards compatibility. * - * This method can be called before any send or receive has been done with [counterparty]. In such a case this will force - * them to start their flow. + * This method can be called before any send or receive has been done with [counterparty]. In such a case this will + * force them to start their flow. + * + * @param maySkipCheckpoint setting it to true indicates to the platform that it may optimise away the checkpoint. + */ + @Suspendable + abstract fun getCounterpartyFlowInfo(maySkipCheckpoint: Boolean): FlowInfo + + /** + * Returns a [FlowInfo] object describing the flow [counterparty] is using. With [FlowInfo.flowVersion] it + * provides the necessary information needed for the evolution of flows and enabling backwards compatibility. + * + * This method can be called before any send or receive has been done with [counterparty]. In such a case this will + * force them to start their flow. */ @Suspendable abstract fun getCounterpartyFlowInfo(): FlowInfo @@ -78,8 +92,26 @@ abstract class FlowSession { /** * Serializes and queues the given [payload] object for sending to the [counterparty]. Suspends until a response - * is received, which must be of the given [receiveType]. Remember that when receiving data from other parties the data - * should not be trusted until it's been thoroughly verified for consistency and that all expectations are + * is received, which must be of the given [receiveType]. Remember that when receiving data from other parties the + * data should not be trusted until it's been thoroughly verified for consistency and that all expectations are + * satisfied, as a malicious peer may send you subtly corrupted data in order to exploit your code. + * + * Note that this function is not just a simple send+receive pair: it is more efficient and more correct to + * use this when you expect to do a message swap than do use [send] and then [receive] in turn. + * + * @param maySkipCheckpoint setting it to true indicates to the platform that it may optimise away the checkpoint. + * @return an [UntrustworthyData] wrapper around the received object. + */ + @Suspendable + abstract fun sendAndReceive( + receiveType: Class, + payload: Any, maySkipCheckpoint: Boolean + ): UntrustworthyData + + /** + * Serializes and queues the given [payload] object for sending to the [counterparty]. Suspends until a response + * is received, which must be of the given [receiveType]. Remember that when receiving data from other parties the + * data should not be trusted until it's been thoroughly verified for consistency and that all expectations are * satisfied, as a malicious peer may send you subtly corrupted data in order to exploit your code. * * Note that this function is not just a simple send+receive pair: it is more efficient and more correct to @@ -102,6 +134,19 @@ abstract class FlowSession { return receive(R::class.java) } + /** + * Suspends until [counterparty] sends us a message of type [receiveType]. + * + * Remember that when receiving data from other parties the data should not be trusted until it's been thoroughly + * verified for consistency and that all expectations are satisfied, as a malicious peer may send you subtly + * corrupted data in order to exploit your code. + * + * @param maySkipCheckpoint setting it to true indicates to the platform that it may optimise away the checkpoint. + * @return an [UntrustworthyData] wrapper around the received object. + */ + @Suspendable + abstract fun receive(receiveType: Class, maySkipCheckpoint: Boolean): UntrustworthyData + /** * Suspends until [counterparty] sends us a message of type [receiveType]. * @@ -114,6 +159,18 @@ abstract class FlowSession { @Suspendable abstract fun receive(receiveType: Class): UntrustworthyData + /** + * Queues the given [payload] for sending to the [counterparty] and continues without suspending. + * + * Note that the other party may receive the message at some arbitrary later point or not at all: if [counterparty] + * is offline then message delivery will be retried until it comes back or until the message is older than the + * network's event horizon time. + * + * @param maySkipCheckpoint setting it to true indicates to the platform that it may optimise away the checkpoint. + */ + @Suspendable + abstract fun send(payload: Any, maySkipCheckpoint: Boolean) + /** * Queues the given [payload] for sending to the [counterparty] and continues without suspending. * diff --git a/core/src/main/kotlin/net/corda/core/identity/AbstractParty.kt b/core/src/main/kotlin/net/corda/core/identity/AbstractParty.kt index 8f03ed640b..a24096f973 100644 --- a/core/src/main/kotlin/net/corda/core/identity/AbstractParty.kt +++ b/core/src/main/kotlin/net/corda/core/identity/AbstractParty.kt @@ -1,5 +1,6 @@ package net.corda.core.identity +import net.corda.core.DoNotImplement import net.corda.core.contracts.PartyAndReference import net.corda.core.serialization.CordaSerializable import net.corda.core.utilities.OpaqueBytes @@ -10,6 +11,7 @@ import java.security.PublicKey * the party. In most cases [Party] or [AnonymousParty] should be used, depending on use-case. */ @CordaSerializable +@DoNotImplement abstract class AbstractParty(val owningKey: PublicKey) { /** Anonymised parties do not include any detail apart from owning key, so equality is dependent solely on the key */ override fun equals(other: Any?): Boolean = other === this || other is AbstractParty && other.owningKey == owningKey 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 a2b0e2fd15..5e4f3e490a 100644 --- a/core/src/main/kotlin/net/corda/core/internal/FlowStateMachine.kt +++ b/core/src/main/kotlin/net/corda/core/internal/FlowStateMachine.kt @@ -15,7 +15,7 @@ import java.time.Instant /** This is an internal interface that is implemented by code in the node module. You should look at [FlowLogic]. */ interface FlowStateMachine { @Suspendable - fun getFlowInfo(otherParty: Party, sessionFlow: FlowLogic<*>): FlowInfo + fun getFlowInfo(otherParty: Party, sessionFlow: FlowLogic<*>, maySkipCheckpoint: Boolean): FlowInfo @Suspendable fun initiateFlow(otherParty: Party, sessionFlow: FlowLogic<*>): FlowSession @@ -25,16 +25,17 @@ interface FlowStateMachine { otherParty: Party, payload: Any, sessionFlow: FlowLogic<*>, - retrySend: Boolean = false): UntrustworthyData + retrySend: Boolean, + maySkipCheckpoint: Boolean): UntrustworthyData @Suspendable - fun receive(receiveType: Class, otherParty: Party, sessionFlow: FlowLogic<*>): UntrustworthyData + fun receive(receiveType: Class, otherParty: Party, sessionFlow: FlowLogic<*>, maySkipCheckpoint: Boolean): UntrustworthyData @Suspendable - fun send(otherParty: Party, payload: Any, sessionFlow: FlowLogic<*>) + fun send(otherParty: Party, payload: Any, sessionFlow: FlowLogic<*>, maySkipCheckpoint: Boolean) @Suspendable - fun waitForLedgerCommit(hash: SecureHash, sessionFlow: FlowLogic<*>): SignedTransaction + fun waitForLedgerCommit(hash: SecureHash, sessionFlow: FlowLogic<*>, maySkipCheckpoint: Boolean): SignedTransaction @Suspendable fun sleepUntil(until: Instant) 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 d29cc7a193..3e87d4ba39 100644 --- a/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt +++ b/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt @@ -24,7 +24,6 @@ import rx.Observable import java.io.InputStream import java.security.PublicKey import java.time.Instant -import java.util.* @CordaSerializable data class StateMachineInfo( diff --git a/core/src/main/kotlin/net/corda/core/messaging/FlowHandle.kt b/core/src/main/kotlin/net/corda/core/messaging/FlowHandle.kt index 166825de0c..ee241d1924 100644 --- a/core/src/main/kotlin/net/corda/core/messaging/FlowHandle.kt +++ b/core/src/main/kotlin/net/corda/core/messaging/FlowHandle.kt @@ -1,5 +1,6 @@ package net.corda.core.messaging +import net.corda.core.DoNotImplement import net.corda.core.concurrent.CordaFuture import net.corda.core.flows.StateMachineRunId import net.corda.core.serialization.CordaSerializable @@ -11,6 +12,7 @@ import rx.Observable * @property id The started state machine's ID. * @property returnValue A [CordaFuture] of the flow's return value. */ +@DoNotImplement interface FlowHandle : AutoCloseable { val id: StateMachineRunId val returnValue: CordaFuture diff --git a/core/src/main/kotlin/net/corda/core/messaging/RPCOps.kt b/core/src/main/kotlin/net/corda/core/messaging/RPCOps.kt index 6a3eaa4c1d..e6aa0b79a4 100644 --- a/core/src/main/kotlin/net/corda/core/messaging/RPCOps.kt +++ b/core/src/main/kotlin/net/corda/core/messaging/RPCOps.kt @@ -1,9 +1,12 @@ package net.corda.core.messaging +import net.corda.core.DoNotImplement + /** * Base interface that all RPC servers must implement. Note: in Corda there's only one RPC interface. This base * interface is here in case we split the RPC system out into a separate library one day. */ +@DoNotImplement interface RPCOps { /** Returns the RPC protocol version. Exists since version 0 so guaranteed to be present. */ val protocolVersion: Int diff --git a/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt b/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt index 7114e6c843..ce08e56881 100644 --- a/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt +++ b/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt @@ -1,5 +1,6 @@ package net.corda.core.node +import net.corda.core.DoNotImplement import net.corda.core.contracts.* import net.corda.core.cordapp.CordappProvider import net.corda.core.crypto.Crypto @@ -19,6 +20,7 @@ import java.time.Clock /** * Part of [ServiceHub]. */ +@DoNotImplement interface StateLoader { /** * Given a [StateRef] loads the referenced transaction and looks up the specified output [ContractState]. @@ -164,7 +166,7 @@ interface ServiceHub : ServicesForResolution { @Throws(TransactionResolutionException::class) fun toStateAndRef(stateRef: StateRef): StateAndRef { val stx = validatedTransactions.getTransaction(stateRef.txhash) ?: throw TransactionResolutionException(stateRef.txhash) - return stx.resolveBaseTransaction(this).outRef(stateRef.index) + return stx.resolveBaseTransaction(this).outRef(stateRef.index) } private val legalIdentityKey: PublicKey get() = this.myInfo.legalIdentitiesAndCerts.first().owningKey diff --git a/core/src/main/kotlin/net/corda/core/node/services/AttachmentStorage.kt b/core/src/main/kotlin/net/corda/core/node/services/AttachmentStorage.kt index 4e038925c1..ed13d5edd5 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/AttachmentStorage.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/AttachmentStorage.kt @@ -1,5 +1,6 @@ package net.corda.core.node.services +import net.corda.core.DoNotImplement import net.corda.core.contracts.Attachment import net.corda.core.crypto.SecureHash import java.io.IOException @@ -11,6 +12,7 @@ typealias AttachmentId = SecureHash /** * An attachment store records potentially large binary objects, identified by their hash. */ +@DoNotImplement interface AttachmentStorage { /** * Returns a handle to a locally stored attachment, or null if it's not known. The handle can be used to open diff --git a/core/src/main/kotlin/net/corda/core/node/services/ContractUpgradeService.kt b/core/src/main/kotlin/net/corda/core/node/services/ContractUpgradeService.kt index 42d1689796..b489027fc6 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/ContractUpgradeService.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/ContractUpgradeService.kt @@ -1,5 +1,6 @@ package net.corda.core.node.services +import net.corda.core.DoNotImplement import net.corda.core.contracts.StateRef import net.corda.core.contracts.UpgradedContract import net.corda.core.flows.ContractUpgradeFlow @@ -9,6 +10,7 @@ import net.corda.core.flows.ContractUpgradeFlow * a specified and mutually agreed (amongst participants) contract version. * See also [ContractUpgradeFlow] to understand the workflow associated with contract upgrades. */ +@DoNotImplement interface ContractUpgradeService { /** Get contracts we would be willing to upgrade the suggested contract to. */ diff --git a/core/src/main/kotlin/net/corda/core/node/services/IdentityService.kt b/core/src/main/kotlin/net/corda/core/node/services/IdentityService.kt index 489e316e01..1aa8e35f24 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/IdentityService.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/IdentityService.kt @@ -1,6 +1,7 @@ package net.corda.core.node.services import net.corda.core.CordaException +import net.corda.core.DoNotImplement import net.corda.core.contracts.PartyAndReference import net.corda.core.identity.* import java.security.InvalidAlgorithmParameterException @@ -16,6 +17,7 @@ import java.security.cert.* * whereas confidential identities are distributed only on a need to know basis (typically between parties in * a transaction being built). See [NetworkMapCache] for retrieving well known identities from the network map. */ +@DoNotImplement interface IdentityService { val trustRoot: X509Certificate val trustAnchor: TrustAnchor diff --git a/core/src/main/kotlin/net/corda/core/node/services/KeyManagementService.kt b/core/src/main/kotlin/net/corda/core/node/services/KeyManagementService.kt index ed2d106bae..7b4801b112 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/KeyManagementService.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/KeyManagementService.kt @@ -1,6 +1,7 @@ package net.corda.core.node.services import co.paralleluniverse.fibers.Suspendable +import net.corda.core.DoNotImplement import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.SignableData import net.corda.core.crypto.TransactionSignature @@ -11,6 +12,7 @@ import java.security.PublicKey * The KMS is responsible for storing and using private keys to sign things. An implementation of this may, for example, * call out to a hardware security module that enforces various auditing and frequency-of-use requirements. */ +@DoNotImplement interface KeyManagementService { /** * Returns a snapshot of the current signing [PublicKey]s. diff --git a/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt b/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt index 792baadb39..b5c415c578 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt @@ -1,5 +1,6 @@ package net.corda.core.node.services +import net.corda.core.DoNotImplement import net.corda.core.concurrent.CordaFuture import net.corda.core.identity.AbstractParty import net.corda.core.identity.CordaX500Name @@ -18,8 +19,7 @@ import java.security.PublicKey * from an authoritative service, and adds easy lookup of the data stored within it. Generally it would be initialised * with a specified network map service, which it fetches data from and then subscribes to updates of. */ -interface NetworkMapCache { - +interface NetworkMapCache : NetworkMapCacheBase { @CordaSerializable sealed class MapChange { abstract val node: NodeInfo @@ -29,6 +29,23 @@ interface NetworkMapCache { data class Modified(override val node: NodeInfo, val previousNode: NodeInfo) : MapChange() } + /** + * Look up the node info for a specific party. Will attempt to de-anonymise the party if applicable; if the party + * is anonymised and the well known party cannot be resolved, it is impossible ot identify the node and therefore this + * returns null. + * Notice that when there are more than one node for a given party (in case of distributed services) first service node + * found will be returned. See also: [NetworkMapCache.getNodesByLegalIdentityKey]. + * + * @param party party to retrieve node information for. + * @return the node for the identity, or null if the node could not be found. This does not necessarily mean there is + * no node for the party, only that this cache is unaware of it. + */ + fun getNodeByLegalIdentity(party: AbstractParty): NodeInfo? +} + +/** Subset of [NetworkMapCache] that doesn't depend on an [IdentityService]. */ +@DoNotImplement +interface NetworkMapCacheBase { // DOCSTART 1 /** * A list of notary services available on the network. @@ -40,7 +57,7 @@ interface NetworkMapCache { // DOCEND 1 /** Tracks changes to the network map cache. */ - val changed: Observable + val changed: Observable /** Future to track completion of the NetworkMapService registration. */ val nodeReady: CordaFuture @@ -48,20 +65,7 @@ interface NetworkMapCache { * Atomically get the current party nodes and a stream of updates. Note that the Observable buffers updates until the * first subscriber is registered so as to avoid racing with early updates. */ - fun track(): DataFeed, MapChange> - - /** - * Look up the node info for a specific party. Will attempt to de-anonymise the party if applicable; if the party - * is anonymised and the well known party cannot be resolved, it is impossible ot identify the node and therefore this - * returns null. - * Notice that when there are more than one node for a given party (in case of distributed services) first service node - * found will be returned. See also: [getNodesByLegalIdentityKey]. - * - * @param party party to retrieve node information for. - * @return the node for the identity, or null if the node could not be found. This does not necessarily mean there is - * no node for the party, only that this cache is unaware of it. - */ - fun getNodeByLegalIdentity(party: AbstractParty): NodeInfo? + fun track(): DataFeed, NetworkMapCache.MapChange> /** * Look up the node info for a legal name. diff --git a/core/src/main/kotlin/net/corda/core/node/services/TransactionStorage.kt b/core/src/main/kotlin/net/corda/core/node/services/TransactionStorage.kt index 8173616eda..9b6b713ed2 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/TransactionStorage.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/TransactionStorage.kt @@ -1,5 +1,6 @@ package net.corda.core.node.services +import net.corda.core.DoNotImplement import net.corda.core.crypto.SecureHash import net.corda.core.messaging.DataFeed import net.corda.core.transactions.SignedTransaction @@ -8,6 +9,7 @@ import rx.Observable /** * Thread-safe storage of transactions. */ +@DoNotImplement interface TransactionStorage { /** * Return the transaction with the given [id], or null if no such transaction exists. diff --git a/core/src/main/kotlin/net/corda/core/node/services/TransactionVerifierService.kt b/core/src/main/kotlin/net/corda/core/node/services/TransactionVerifierService.kt index 8cc5c7af65..d72eec72f2 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/TransactionVerifierService.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/TransactionVerifierService.kt @@ -1,5 +1,6 @@ package net.corda.core.node.services +import net.corda.core.DoNotImplement import net.corda.core.concurrent.CordaFuture import net.corda.core.transactions.LedgerTransaction @@ -7,6 +8,7 @@ import net.corda.core.transactions.LedgerTransaction * Provides verification service. The implementation may be a simple in-memory verify() call or perhaps an IPC/RPC. * @suppress */ +@DoNotImplement interface TransactionVerifierService { /** * @param transaction The transaction to be verified. diff --git a/core/src/main/kotlin/net/corda/core/node/services/VaultService.kt b/core/src/main/kotlin/net/corda/core/node/services/VaultService.kt index 38545ac754..2440d80f77 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/VaultService.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/VaultService.kt @@ -1,6 +1,7 @@ package net.corda.core.node.services import co.paralleluniverse.fibers.Suspendable +import net.corda.core.DoNotImplement import net.corda.core.concurrent.CordaFuture import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash @@ -14,7 +15,6 @@ import net.corda.core.serialization.CordaSerializable import net.corda.core.toFuture import net.corda.core.utilities.NonEmptySet import rx.Observable -import rx.subjects.PublishSubject import java.time.Instant import java.util.* @@ -151,6 +151,7 @@ class Vault(val states: Iterable>) { * * Note that transactions we've seen are held by the storage service, not the vault. */ +@DoNotImplement interface VaultService { /** * Prefer the use of [updates] unless you know why you want to use this instead. diff --git a/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteria.kt b/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteria.kt index 1e5eeb858f..208109f3b4 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteria.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteria.kt @@ -2,6 +2,7 @@ package net.corda.core.node.services.vault +import net.corda.core.DoNotImplement import net.corda.core.contracts.ContractState import net.corda.core.contracts.StateRef import net.corda.core.contracts.UniqueIdentifier @@ -144,6 +145,7 @@ sealed class QueryCriteria { infix fun or(criteria: QueryCriteria): QueryCriteria = OrComposition(this, criteria) } +@DoNotImplement interface IQueryCriteriaParser { fun parseCriteria(criteria: QueryCriteria.CommonQueryCriteria): Collection fun parseCriteria(criteria: QueryCriteria.FungibleAssetQueryCriteria): Collection diff --git a/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteriaUtils.kt b/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteriaUtils.kt index 85d7292061..098a5a07e2 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteriaUtils.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteriaUtils.kt @@ -2,6 +2,7 @@ package net.corda.core.node.services.vault +import net.corda.core.DoNotImplement import net.corda.core.internal.uncheckedCast import net.corda.core.schemas.PersistentState import net.corda.core.serialization.CordaSerializable @@ -10,6 +11,7 @@ import kotlin.reflect.KProperty1 import kotlin.reflect.jvm.javaGetter @CordaSerializable +@DoNotImplement interface Operator enum class BinaryLogicalOperator : Operator { @@ -138,6 +140,7 @@ data class Sort(val columns: Collection) { } @CordaSerializable + @DoNotImplement interface Attribute enum class CommonStateAttribute(val attributeParent: String, val attributeChild: String?) : Attribute { diff --git a/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt b/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt index a91636ddc7..2b5705a187 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt @@ -8,6 +8,8 @@ import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.sequence import java.sql.Blob +data class ObjectWithCompatibleContext(val obj: T, val context: SerializationContext) + /** * An abstraction for serializing and deserializing objects, with support for versioning of the wire format via * a header / prefix in the bytes. @@ -22,6 +24,16 @@ abstract class SerializationFactory { */ abstract fun deserialize(byteSequence: ByteSequence, clazz: Class, context: SerializationContext): T + /** + * Deserialize the bytes in to an object, using the prefixed bytes to determine the format. + * + * @param byteSequence The bytes to deserialize, including a format header prefix. + * @param clazz The class or superclass or the object to be deserialized, or [Any] or [Object] if unknown. + * @param context A context that configures various parameters to deserialization. + * @return deserialized object along with [SerializationContext] to identify encoding used. + */ + abstract fun deserializeWithCompatibleContext(byteSequence: ByteSequence, clazz: Class, context: SerializationContext): ObjectWithCompatibleContext + /** * Serialize an object to bytes using the preferred serialization format version from the context. * @@ -87,6 +99,8 @@ abstract class SerializationFactory { } } +typealias VersionHeader = ByteSequence + /** * Parameters to serialization and deserialization. */ @@ -94,7 +108,7 @@ interface SerializationContext { /** * When serializing, use the format this header sequence represents. */ - val preferredSerializationVersion: ByteSequence + val preferredSerializationVersion: VersionHeader /** * The class loader to use for deserialization. */ @@ -147,7 +161,7 @@ interface SerializationContext { /** * Helper method to return a new context based on this context but with serialization using the format this header sequence represents. */ - fun withPreferredSerializationVersion(versionHeader: ByteSequence): SerializationContext + fun withPreferredSerializationVersion(versionHeader: VersionHeader): SerializationContext /** * The use case that we are serializing for, since it influences the implementations chosen. @@ -174,6 +188,15 @@ inline fun ByteSequence.deserialize(serializationFactory: Seri return serializationFactory.deserialize(this, T::class.java, context) } +/** + * Additionally returns [SerializationContext] which was used for encoding. + * It might be helpful to know [SerializationContext] to use the same encoding in the reply. + */ +inline fun ByteSequence.deserializeWithCompatibleContext(serializationFactory: SerializationFactory = SerializationFactory.defaultFactory, + context: SerializationContext = serializationFactory.defaultContext): ObjectWithCompatibleContext { + return serializationFactory.deserializeWithCompatibleContext(this, T::class.java, context) +} + /** * Convenience extension method for deserializing SerializedBytes with type matching, utilising the defaults. */ diff --git a/core/src/main/kotlin/net/corda/core/transactions/BaseTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/BaseTransaction.kt index 0d5a798d9f..14483543b2 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/BaseTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/BaseTransaction.kt @@ -1,5 +1,6 @@ package net.corda.core.transactions +import net.corda.core.DoNotImplement import net.corda.core.contracts.* import net.corda.core.identity.Party import net.corda.core.internal.castIfPossible @@ -10,6 +11,7 @@ import java.util.function.Predicate /** * An abstract class defining fields shared by all transaction types in the system. */ +@DoNotImplement abstract class BaseTransaction : NamedByHash { /** The inputs of this transaction. Note that in BaseTransaction subclasses the type of this list may change! */ abstract val inputs: List<*> diff --git a/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt index 8f2c476004..ba7cb8de1f 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt @@ -25,7 +25,7 @@ abstract class TraversableTransaction(open val componentGroups: List> = deserialiseComponentGroup(ComponentGroupEnum.OUTPUTS_GROUP, { SerializedBytes>(it).deserialize(context = SerializationFactory.defaultFactory.defaultContext.withAttachmentsClassLoader(attachments)) }) /** Ordered list of ([CommandData], [PublicKey]) pairs that instruct the contracts what to do. */ - val commands: List> = deserialiseComponentGroup(ComponentGroupEnum.COMMANDS_GROUP, { SerializedBytes>(it).deserialize(context = SerializationFactory.defaultFactory.defaultContext.withAttachmentsClassLoader(attachments)) }) + val commands: List> = deserialiseCommands() override val notary: Party? = let { val notaries: List = deserialiseComponentGroup(ComponentGroupEnum.NOTARY_GROUP, { SerializedBytes(it).deserialize() }) @@ -74,6 +74,31 @@ abstract class TraversableTransaction(open val componentGroups: List> { + // TODO: we could avoid deserialising unrelated signers. + // However, current approach ensures the transaction is not malformed + // and it will throw if any of the signers objects is not List of public keys). + val signersList = deserialiseComponentGroup(ComponentGroupEnum.SIGNERS_GROUP, { SerializedBytes>(it).deserialize() }) + val commandDataList = deserialiseComponentGroup(ComponentGroupEnum.COMMANDS_GROUP, { SerializedBytes(it).deserialize(context = SerializationFactory.defaultFactory.defaultContext.withAttachmentsClassLoader(attachments)) }) + val group = componentGroups.firstOrNull { it.groupIndex == ComponentGroupEnum.COMMANDS_GROUP.ordinal } + if (group is FilteredComponentGroup) { + check(commandDataList.size <= signersList.size) { "Invalid Transaction. Less Signers (${signersList.size}) than CommandData (${commandDataList.size}) objects" } + val componentHashes = group.components.mapIndexed { index, component -> componentHash(group.nonces[index], component) } + val leafIndices = componentHashes.map { group.partialMerkleTree.leafIndex(it) } + if (leafIndices.isNotEmpty()) + check(leafIndices.max()!! < signersList.size) { "Invalid Transaction. A command with no corresponding signer detected" } + return commandDataList.mapIndexed { index, commandData -> Command(commandData, signersList[leafIndices[index]]) } + } else { + // It is a WireTransaction + // or a FilteredTransaction with no Commands (in which case group is null). + check(commandDataList.size == signersList.size) { "Invalid Transaction. Sizes of CommandData (${commandDataList.size}) and Signers (${signersList.size}) do not match" } + return commandDataList.mapIndexed { index, commandData -> Command(commandData, signersList[index]) } + } + } } /** @@ -111,11 +136,12 @@ class FilteredTransaction private constructor( val filteredSerialisedComponents: MutableMap> = hashMapOf() val filteredComponentNonces: MutableMap> = hashMapOf() val filteredComponentHashes: MutableMap> = hashMapOf() // Required for partial Merkle tree generation. + var signersIncluded = false fun filter(t: T, componentGroupIndex: Int, internalIndex: Int) { if (filtering.test(t)) { val group = filteredSerialisedComponents[componentGroupIndex] - // Because the filter passed, we know there is a match. We also use first vs single as the init function + // Because the filter passed, we know there is a match. We also use first Vs single as the init function // of WireTransaction ensures there are no duplicated groups. val serialisedComponent = wtx.componentGroups.first { it.groupIndex == componentGroupIndex }.components[internalIndex] if (group == null) { @@ -132,6 +158,17 @@ class FilteredTransaction private constructor( filteredComponentNonces[componentGroupIndex]!!.add(wtx.availableComponentNonces[componentGroupIndex]!![internalIndex]) filteredComponentHashes[componentGroupIndex]!!.add(wtx.availableComponentHashes[componentGroupIndex]!![internalIndex]) } + // If at least one command is visible, then all command-signers should be visible as well. + // This is required for visibility purposes, see FilteredTransaction.checkAllCommandsVisible() for more details. + if (componentGroupIndex == ComponentGroupEnum.COMMANDS_GROUP.ordinal && !signersIncluded) { + signersIncluded = true + val signersGroupIndex = ComponentGroupEnum.SIGNERS_GROUP.ordinal + // There exist commands, thus the signers group is not empty. + val signersGroupComponents = wtx.componentGroups.first { it.groupIndex == signersGroupIndex } + filteredSerialisedComponents.put(signersGroupIndex, signersGroupComponents.components.toMutableList()) + filteredComponentNonces.put(signersGroupIndex, wtx.availableComponentNonces[signersGroupIndex]!!.toMutableList()) + filteredComponentHashes.put(signersGroupIndex, wtx.availableComponentHashes[signersGroupIndex]!!.toMutableList()) + } } } @@ -142,6 +179,10 @@ class FilteredTransaction private constructor( wtx.attachments.forEachIndexed { internalIndex, it -> filter(it, ComponentGroupEnum.ATTACHMENTS_GROUP.ordinal, internalIndex) } if (wtx.notary != null) filter(wtx.notary, ComponentGroupEnum.NOTARY_GROUP.ordinal, 0) if (wtx.timeWindow != null) filter(wtx.timeWindow, ComponentGroupEnum.TIMEWINDOW_GROUP.ordinal, 0) + // It is highlighted that because there is no a signers property in TraversableTransaction, + // one cannot specifically filter them in or out. + // The above is very important to ensure someone won't filter out the signers component group if at least one + // command is included in a FilteredTransaction. // It's sometimes possible that when we receive a WireTransaction for which there is a new or more unknown component groups, // we decide to filter and attach this field to a FilteredTransaction. @@ -207,7 +248,9 @@ class FilteredTransaction private constructor( /** * Function that checks if all of the components in a particular group are visible. * This functionality is required on non-Validating Notaries to check that all inputs are visible. - * It might also be applied in Oracles, where an Oracle should know it can see all commands. + * It might also be applied in Oracles or any other entity requiring [Command] visibility, but because this method + * cannot distinguish between related and unrelated to the signer [Command]s, one should use the + * [checkCommandVisibility] method, which is specifically designed for [Command] visibility purposes. * The logic behind this algorithm is that we check that the root of the provided group partialMerkleTree matches with the * root of a fullMerkleTree if computed using all visible components. * Note that this method is usually called after or before [verify], to also ensure that the provided partial Merkle @@ -229,18 +272,54 @@ class FilteredTransaction private constructor( visibilityCheck(group.groupIndex < groupHashes.size) { "There is no matching component group hash for group ${group.groupIndex}" } val groupPartialRoot = groupHashes[group.groupIndex] val groupFullRoot = MerkleTree.getMerkleTree(group.components.mapIndexed { index, component -> componentHash(group.nonces[index], component) }).hash - visibilityCheck(groupPartialRoot == groupFullRoot) { "The partial Merkle tree root does not match with the received root for group ${group.groupIndex}" } + visibilityCheck(groupPartialRoot == groupFullRoot) { "Some components for group ${group.groupIndex} are not visible" } + // Verify the top level Merkle tree from groupHashes. + visibilityCheck(MerkleTree.getMerkleTree(groupHashes).hash == id) { "Transaction is malformed. Top level Merkle tree cannot be verified against transaction's id" } } } - inline private fun verificationCheck(value: Boolean, lazyMessage: () -> Any): Unit { + /** + * Function that checks if all of the commands that should be signed by the input public key are visible. + * This functionality is required from Oracles to check that all of the commands they should sign are visible. + * This algorithm uses the [ComponentGroupEnum.SIGNERS_GROUP] to count how many commands should be signed by the + * input [PublicKey] and it then matches it with the size of received [commands]. + * Note that this method does not throw if there are no commands for this key to sign in the original [WireTransaction]. + * @param publicKey signer's [PublicKey] + * @throws ComponentVisibilityException if not all of the related commands are visible. + */ + @Throws(ComponentVisibilityException::class) + fun checkCommandVisibility(publicKey: PublicKey) { + val commandSigners = componentGroups.firstOrNull { it.groupIndex == ComponentGroupEnum.SIGNERS_GROUP.ordinal } + val expectedNumOfCommands = expectedNumOfCommands(publicKey, commandSigners) + val receivedForThisKeyNumOfCommands = commands.filter { publicKey in it.signers }.size + visibilityCheck(expectedNumOfCommands == receivedForThisKeyNumOfCommands) { "$expectedNumOfCommands commands were expected, but received $receivedForThisKeyNumOfCommands" } + } + + // Function to return number of expected commands to sign. + private fun expectedNumOfCommands(publicKey: PublicKey, commandSigners: ComponentGroup?): Int { + checkAllComponentsVisible(ComponentGroupEnum.SIGNERS_GROUP) + if (commandSigners == null) return 0 + fun signersKeys (internalIndex: Int, opaqueBytes: OpaqueBytes): List { + try { + return SerializedBytes>(opaqueBytes.bytes).deserialize() + } catch (e: Exception) { + throw Exception("Malformed transaction, signers at index $internalIndex cannot be deserialised", e) + } + } + + return commandSigners.components + .mapIndexed { internalIndex, opaqueBytes -> signersKeys(internalIndex, opaqueBytes) } + .filter { signers -> publicKey in signers }.size + } + + inline private fun verificationCheck(value: Boolean, lazyMessage: () -> Any) { if (!value) { val message = lazyMessage() throw FilteredTransactionVerificationException(id, message.toString()) } } - inline private fun visibilityCheck(value: Boolean, lazyMessage: () -> Any): Unit { + inline private fun visibilityCheck(value: Boolean, lazyMessage: () -> Any) { if (!value) { val message = lazyMessage() throw ComponentVisibilityException(id, message.toString()) diff --git a/core/src/main/kotlin/net/corda/core/transactions/TransactionWithSignatures.kt b/core/src/main/kotlin/net/corda/core/transactions/TransactionWithSignatures.kt index ab25d68064..6835e39686 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/TransactionWithSignatures.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/TransactionWithSignatures.kt @@ -1,5 +1,6 @@ package net.corda.core.transactions +import net.corda.core.DoNotImplement import net.corda.core.contracts.NamedByHash import net.corda.core.crypto.TransactionSignature import net.corda.core.crypto.isFulfilledBy @@ -10,6 +11,7 @@ import java.security.PublicKey import java.security.SignatureException /** An interface for transactions containing signatures, with logic for signature verification */ +@DoNotImplement interface TransactionWithSignatures : NamedByHash { val sigs: List diff --git a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt index 2325344286..7112369667 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt @@ -6,9 +6,10 @@ import net.corda.core.crypto.* import net.corda.core.identity.Party import net.corda.core.internal.Emoji import net.corda.core.node.ServicesForResolution -import net.corda.core.serialization.* -import net.corda.core.utilities.OpaqueBytes import net.corda.core.node.services.AttachmentId +import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.serialize +import net.corda.core.utilities.OpaqueBytes import java.security.PublicKey import java.security.SignatureException import java.util.function.Predicate @@ -213,10 +214,14 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr val componentGroupMap: MutableList = mutableListOf() if (inputs.isNotEmpty()) componentGroupMap.add(ComponentGroup(INPUTS_GROUP.ordinal, inputs.map { it.serialize() })) if (outputs.isNotEmpty()) componentGroupMap.add(ComponentGroup(OUTPUTS_GROUP.ordinal, outputs.map { it.serialize() })) - if (commands.isNotEmpty()) componentGroupMap.add(ComponentGroup(COMMANDS_GROUP.ordinal, commands.map { it.serialize() })) + // Adding commandData only to the commands group. Signers are added in their own group. + if (commands.isNotEmpty()) componentGroupMap.add(ComponentGroup(COMMANDS_GROUP.ordinal, commands.map { it.value.serialize() })) if (attachments.isNotEmpty()) componentGroupMap.add(ComponentGroup(ATTACHMENTS_GROUP.ordinal, attachments.map { it.serialize() })) if (notary != null) componentGroupMap.add(ComponentGroup(NOTARY_GROUP.ordinal, listOf(notary.serialize()))) if (timeWindow != null) componentGroupMap.add(ComponentGroup(TIMEWINDOW_GROUP.ordinal, listOf(timeWindow.serialize()))) + // Adding signers to their own group. This is required for command visibility purposes: a party receiving + // a FilteredTransaction can now verify it sees all the commands it should sign. + if (commands.isNotEmpty()) componentGroupMap.add(ComponentGroup(SIGNERS_GROUP.ordinal, commands.map { it.signers.serialize() })) return componentGroupMap } } diff --git a/core/src/test/kotlin/net/corda/core/concurrent/ConcurrencyUtilsTest.kt b/core/src/test/kotlin/net/corda/core/concurrent/ConcurrencyUtilsTest.kt index a0b4f62453..830398964c 100644 --- a/core/src/test/kotlin/net/corda/core/concurrent/ConcurrencyUtilsTest.kt +++ b/core/src/test/kotlin/net/corda/core/concurrent/ConcurrencyUtilsTest.kt @@ -3,6 +3,7 @@ package net.corda.core.concurrent import com.nhaarman.mockito_kotlin.* import net.corda.core.internal.concurrent.openFuture import net.corda.core.utilities.getOrThrow +import net.corda.testing.rigorousMock import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Test import org.slf4j.Logger @@ -16,7 +17,10 @@ class ConcurrencyUtilsTest { private val f1 = openFuture() private val f2 = openFuture() private var invocations = 0 - private val log = mock() + private val log = rigorousMock().also { + doNothing().whenever(it).error(any(), any()) + } + @Test fun `firstOf short circuit`() { // Order not significant in this case: diff --git a/core/src/test/kotlin/net/corda/core/contracts/CompatibleTransactionTests.kt b/core/src/test/kotlin/net/corda/core/contracts/CompatibleTransactionTests.kt index 7ed31d3d38..b50aa66b99 100644 --- a/core/src/test/kotlin/net/corda/core/contracts/CompatibleTransactionTests.kt +++ b/core/src/test/kotlin/net/corda/core/contracts/CompatibleTransactionTests.kt @@ -1,13 +1,9 @@ package net.corda.core.contracts import net.corda.core.contracts.ComponentGroupEnum.* -import net.corda.core.crypto.MerkleTree -import net.corda.core.crypto.SecureHash -import net.corda.core.crypto.secureRandomBytes +import net.corda.core.crypto.* import net.corda.core.serialization.serialize -import net.corda.core.transactions.ComponentGroup -import net.corda.core.transactions.ComponentVisibilityException -import net.corda.core.transactions.WireTransaction +import net.corda.core.transactions.* import net.corda.core.utilities.OpaqueBytes import net.corda.testing.* import net.corda.testing.contracts.DummyContract @@ -34,22 +30,24 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() { private val inputGroup by lazy { ComponentGroup(INPUTS_GROUP.ordinal, inputs.map { it.serialize() }) } private val outputGroup by lazy { ComponentGroup(OUTPUTS_GROUP.ordinal, outputs.map { it.serialize() }) } - private val commandGroup by lazy { ComponentGroup(COMMANDS_GROUP.ordinal, commands.map { it.serialize() }) } + private val commandGroup by lazy { ComponentGroup(COMMANDS_GROUP.ordinal, commands.map { it.value.serialize() }) } private val attachmentGroup by lazy { ComponentGroup(ATTACHMENTS_GROUP.ordinal, attachments.map { it.serialize() }) } // The list is empty. private val notaryGroup by lazy { ComponentGroup(NOTARY_GROUP.ordinal, listOf(notary.serialize())) } private val timeWindowGroup by lazy { ComponentGroup(TIMEWINDOW_GROUP.ordinal, listOf(timeWindow.serialize())) } + private val signersGroup by lazy { ComponentGroup(SIGNERS_GROUP.ordinal, commands.map { it.signers.serialize() }) } - private val newUnknownComponentGroup = ComponentGroup(20, listOf(OpaqueBytes(secureRandomBytes(4)), OpaqueBytes(secureRandomBytes(8)))) - private val newUnknownComponentEmptyGroup = ComponentGroup(21, emptyList()) + private val newUnknownComponentGroup = ComponentGroup(100, listOf(OpaqueBytes(secureRandomBytes(4)), OpaqueBytes(secureRandomBytes(8)))) + private val newUnknownComponentEmptyGroup = ComponentGroup(101, emptyList()) // Do not add attachments (empty list). private val componentGroupsA by lazy { listOf( - inputGroup, - outputGroup, - commandGroup, - notaryGroup, - timeWindowGroup + inputGroup, + outputGroup, + commandGroup, + notaryGroup, + timeWindowGroup, + signersGroup ) } private val wireTransactionA by lazy { WireTransaction(componentGroups = componentGroupsA, privacySalt = privacySalt) } @@ -74,7 +72,8 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() { commandGroup, attachmentGroup, notaryGroup, - timeWindowGroup + timeWindowGroup, + signersGroup ) assertFails { WireTransaction(componentGroups = componentGroupsEmptyAttachment, privacySalt = privacySalt) } @@ -86,7 +85,8 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() { outputGroup, commandGroup, notaryGroup, - timeWindowGroup + timeWindowGroup, + signersGroup ) val wireTransaction1ShuffledInputs = WireTransaction(componentGroups = componentGroupsB, privacySalt = privacySalt) // The ID has changed due to change of the internal ordering in inputs. @@ -106,7 +106,8 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() { inputGroup, commandGroup, notaryGroup, - timeWindowGroup + timeWindowGroup, + signersGroup ) assertEquals(wireTransactionA, WireTransaction(componentGroups = shuffledComponentGroupsA, privacySalt = privacySalt)) } @@ -123,7 +124,8 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() { commandGroup, ComponentGroup(ATTACHMENTS_GROUP.ordinal, inputGroup.components), notaryGroup, - timeWindowGroup + timeWindowGroup, + signersGroup ) assertFails { WireTransaction(componentGroupsB, privacySalt) } @@ -134,7 +136,8 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() { commandGroup, // First commandsGroup. commandGroup, // Second commandsGroup. notaryGroup, - timeWindowGroup + timeWindowGroup, + signersGroup ) assertFails { WireTransaction(componentGroupsDuplicatedCommands, privacySalt) } @@ -144,7 +147,8 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() { outputGroup, commandGroup, notaryGroup, - timeWindowGroup + timeWindowGroup, + signersGroup ) assertFails { WireTransaction(componentGroupsC, privacySalt) } @@ -154,23 +158,24 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() { commandGroup, notaryGroup, timeWindowGroup, - newUnknownComponentGroup // A new unknown component with ordinal 20 that we cannot process. + signersGroup, + newUnknownComponentGroup // A new unknown component with ordinal 100 that we cannot process. ) // The old client (receiving more component types than expected) is still compatible. val wireTransactionCompatibleA = WireTransaction(componentGroupsCompatibleA, privacySalt) assertEquals(wireTransactionCompatibleA.availableComponentGroups, wireTransactionA.availableComponentGroups) // The known components are the same. assertNotEquals(wireTransactionCompatibleA, wireTransactionA) // But obviously, its Merkle root has changed Vs wireTransactionA (which doesn't include this extra component). - assertEquals(6, wireTransactionCompatibleA.componentGroups.size) - // The old client will trhow if receiving an empty component (even if this unknown). + // The old client will throw if receiving an empty component (even if this is unknown). val componentGroupsCompatibleEmptyNew = listOf( inputGroup, outputGroup, commandGroup, notaryGroup, timeWindowGroup, - newUnknownComponentEmptyGroup // A new unknown component with ordinal 21 that we cannot process. + signersGroup, + newUnknownComponentEmptyGroup // A new unknown component with ordinal 101 that we cannot process. ) assertFails { WireTransaction(componentGroupsCompatibleEmptyNew, privacySalt) } } @@ -179,7 +184,9 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() { fun `FilteredTransaction constructors and compatibility`() { // Filter out all of the components. val ftxNothing = wireTransactionA.buildFilteredTransaction(Predicate { false }) // Nothing filtered. - assertEquals(6, ftxNothing.groupHashes.size) // Although nothing filtered, we still receive the group hashes for the top level Merkle tree. + // Although nothing filtered, we still receive the group hashes for the top level Merkle tree. + // Note that attachments are not sent, but group hashes include the allOnesHash flag for the attachment group hash; that's why we expect +1 group hashes. + assertEquals(wireTransactionA.componentGroups.size + 1, ftxNothing.groupHashes.size) ftxNothing.verify() // Include all of the components. @@ -191,6 +198,7 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() { ftxAll.checkAllComponentsVisible(ATTACHMENTS_GROUP) ftxAll.checkAllComponentsVisible(NOTARY_GROUP) ftxAll.checkAllComponentsVisible(TIMEWINDOW_GROUP) + ftxAll.checkAllComponentsVisible(SIGNERS_GROUP) // Filter inputs only. fun filtering(elem: Any): Boolean { @@ -222,12 +230,14 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() { assertNotNull(ftxOneInput.filteredComponentGroups.firstOrNull { it.groupIndex == INPUTS_GROUP.ordinal }!!.partialMerkleTree) // And the Merkle tree. // The old client (receiving more component types than expected) is still compatible. - val componentGroupsCompatibleA = listOf(inputGroup, + val componentGroupsCompatibleA = listOf( + inputGroup, outputGroup, commandGroup, notaryGroup, timeWindowGroup, - newUnknownComponentGroup // A new unknown component with ordinal 10,000 that we cannot process. + signersGroup, + newUnknownComponentGroup // A new unknown component with ordinal 100 that we cannot process. ) val wireTransactionCompatibleA = WireTransaction(componentGroupsCompatibleA, privacySalt) val ftxCompatible = wireTransactionCompatibleA.buildFilteredTransaction(Predicate(::filtering)) @@ -245,9 +255,288 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() { ftxCompatibleAll.verify() assertEquals(wireTransactionCompatibleA.id, ftxCompatibleAll.id) - // Check we received the last (6th) element that we cannot process (backwards compatibility). - assertEquals(6, ftxCompatibleAll.filteredComponentGroups.size) + // Check we received the last element that we cannot process (backwards compatibility). + assertEquals(wireTransactionCompatibleA.componentGroups.size, ftxCompatibleAll.filteredComponentGroups.size) + // Hide one component group only. + // Filter inputs only. + fun filterOutInputs(elem: Any): Boolean { + return when (elem) { + is StateRef -> false + else -> true + } + } + val ftxCompatibleNoInputs = wireTransactionCompatibleA.buildFilteredTransaction(Predicate(::filterOutInputs)) + ftxCompatibleNoInputs.verify() + assertFailsWith { ftxCompatibleNoInputs.checkAllComponentsVisible(INPUTS_GROUP) } + assertEquals(wireTransactionCompatibleA.componentGroups.size - 1, ftxCompatibleNoInputs.filteredComponentGroups.size) + assertEquals(wireTransactionCompatibleA.componentGroups.map { it.groupIndex }.max()!!, ftxCompatibleNoInputs.groupHashes.size - 1) + } + + @Test + fun `Command visibility tests`() { + // 1st and 3rd commands require a signature from KEY_1. + val twoCommandsforKey1 = listOf(dummyCommand(DUMMY_KEY_1.public, DUMMY_KEY_2.public), dummyCommand(DUMMY_KEY_2.public), dummyCommand(DUMMY_KEY_1.public)) + val componentGroups = listOf( + inputGroup, + outputGroup, + ComponentGroup(COMMANDS_GROUP.ordinal, twoCommandsforKey1.map { it.value.serialize() }), + notaryGroup, + timeWindowGroup, + ComponentGroup(SIGNERS_GROUP.ordinal, twoCommandsforKey1.map { it.signers.serialize() }), + newUnknownComponentGroup // A new unknown component with ordinal 100 that we cannot process. + ) + val wtx = WireTransaction(componentGroups = componentGroups, privacySalt = PrivacySalt()) + + // Filter all commands. + fun filterCommandsOnly(elem: Any): Boolean { + return when (elem) { + is Command<*> -> true // Even if one Command is filtered, all signers are automatically filtered as well + else -> false + } + } + + // Filter out commands only. + fun filterOutCommands(elem: Any): Boolean { + return when (elem) { + is Command<*> -> false + else -> true + } + } + + // Filter KEY_1 commands. + fun filterKEY1Commands(elem: Any): Boolean { + return when (elem) { + is Command<*> -> DUMMY_KEY_1.public in elem.signers + else -> false + } + } + + // Filter only one KEY_1 command. + fun filterTwoSignersCommands(elem: Any): Boolean { + return when (elem) { + is Command<*> -> elem.signers.size == 2 // dummyCommand(DUMMY_KEY_1.public) is filtered out. + else -> false + } + } + + // Again filter only one KEY_1 command. + fun filterSingleSignersCommands(elem: Any): Boolean { + return when (elem) { + is Command<*> -> elem.signers.size == 1 // dummyCommand(DUMMY_KEY_1.public, DUMMY_KEY_2.public) is filtered out. + else -> false + } + } + + val allCommandsFtx = wtx.buildFilteredTransaction(Predicate(::filterCommandsOnly)) + val noCommandsFtx = wtx.buildFilteredTransaction(Predicate(::filterOutCommands)) + val key1CommandsFtx = wtx.buildFilteredTransaction(Predicate(::filterKEY1Commands)) + val oneKey1CommandFtxA = wtx.buildFilteredTransaction(Predicate(::filterTwoSignersCommands)) + val oneKey1CommandFtxB = wtx.buildFilteredTransaction(Predicate(::filterSingleSignersCommands)) + + allCommandsFtx.checkCommandVisibility(DUMMY_KEY_1.public) + assertFailsWith { noCommandsFtx.checkCommandVisibility(DUMMY_KEY_1.public) } + key1CommandsFtx.checkCommandVisibility(DUMMY_KEY_1.public) + assertFailsWith { oneKey1CommandFtxA.checkCommandVisibility(DUMMY_KEY_1.public) } + assertFailsWith { oneKey1CommandFtxB.checkCommandVisibility(DUMMY_KEY_1.public) } + + allCommandsFtx.checkAllComponentsVisible(SIGNERS_GROUP) + assertFailsWith { noCommandsFtx.checkAllComponentsVisible(SIGNERS_GROUP) } // If we filter out all commands, signers are not sent as well. + key1CommandsFtx.checkAllComponentsVisible(SIGNERS_GROUP) // If at least one Command is visible, then all Signers are visible. + oneKey1CommandFtxA.checkAllComponentsVisible(SIGNERS_GROUP) // If at least one Command is visible, then all Signers are visible. + oneKey1CommandFtxB.checkAllComponentsVisible(SIGNERS_GROUP) // If at least one Command is visible, then all Signers are visible. + + // We don't send a list of signers. + val componentGroupsCompatible = listOf( + inputGroup, + outputGroup, + ComponentGroup(COMMANDS_GROUP.ordinal, twoCommandsforKey1.map { it.value.serialize() }), + notaryGroup, + timeWindowGroup, + // ComponentGroup(SIGNERS_GROUP.ordinal, twoCommandsforKey1.map { it.signers.serialize() }), + newUnknownComponentGroup // A new unknown component with ordinal 100 that we cannot process. + ) + + // Invalid Transaction. Sizes of CommandData and Signers (empty) do not match. + assertFailsWith { WireTransaction(componentGroups = componentGroupsCompatible, privacySalt = PrivacySalt()) } + + // We send smaller list of signers. + val componentGroupsLessSigners = listOf( + inputGroup, + outputGroup, + ComponentGroup(COMMANDS_GROUP.ordinal, twoCommandsforKey1.map { it.value.serialize() }), + notaryGroup, + timeWindowGroup, + ComponentGroup(SIGNERS_GROUP.ordinal, twoCommandsforKey1.map { it.signers.serialize() }.subList(0, 1)), // Send first signer only. + newUnknownComponentGroup // A new unknown component with ordinal 100 that we cannot process. + ) + + // Invalid Transaction. Sizes of CommandData and Signers (empty) do not match. + assertFailsWith { WireTransaction(componentGroups = componentGroupsLessSigners, privacySalt = PrivacySalt()) } + + // Test if there is no command to sign. + val commandsNoKey1= listOf(dummyCommand(DUMMY_KEY_2.public)) + + val componentGroupsNoKey1ToSign = listOf( + inputGroup, + outputGroup, + ComponentGroup(COMMANDS_GROUP.ordinal, commandsNoKey1.map { it.value.serialize() }), + notaryGroup, + timeWindowGroup, + ComponentGroup(SIGNERS_GROUP.ordinal, commandsNoKey1.map { it.signers.serialize() }), + newUnknownComponentGroup // A new unknown component with ordinal 100 that we cannot process. + ) + + val wtxNoKey1 = WireTransaction(componentGroups = componentGroupsNoKey1ToSign, privacySalt = PrivacySalt()) + val allCommandsNoKey1Ftx= wtxNoKey1.buildFilteredTransaction(Predicate(::filterCommandsOnly)) + allCommandsNoKey1Ftx.checkCommandVisibility(DUMMY_KEY_1.public) // This will pass, because there are indeed no commands to sign in the original transaction. + } + + @Test + fun `FilteredTransaction signer manipulation tests`() { + // Required to call the private constructor. + val ftxConstructor = FilteredTransaction::class.java.declaredConstructors[1] + ftxConstructor.isAccessible = true + + // 1st and 3rd commands require a signature from KEY_1. + val twoCommandsforKey1 = listOf(dummyCommand(DUMMY_KEY_1.public, DUMMY_KEY_2.public), dummyCommand(DUMMY_KEY_2.public), dummyCommand(DUMMY_KEY_1.public)) + val componentGroups = listOf( + inputGroup, + outputGroup, + ComponentGroup(COMMANDS_GROUP.ordinal, twoCommandsforKey1.map { it.value.serialize() }), + notaryGroup, + timeWindowGroup, + ComponentGroup(SIGNERS_GROUP.ordinal, twoCommandsforKey1.map { it.signers.serialize() }) + ) + val wtx = WireTransaction(componentGroups = componentGroups, privacySalt = PrivacySalt()) + + // Filter KEY_1 commands (commands 1 and 3). + fun filterKEY1Commands(elem: Any): Boolean { + return when (elem) { + is Command<*> -> DUMMY_KEY_1.public in elem.signers + else -> false + } + } + + // Filter KEY_2 commands (commands 1 and 2). + fun filterKEY2Commands(elem: Any): Boolean { + return when (elem) { + is Command<*> -> DUMMY_KEY_2.public in elem.signers + else -> false + } + } + + val key1CommandsFtx = wtx.buildFilteredTransaction(Predicate(::filterKEY1Commands)) + val key2CommandsFtx = wtx.buildFilteredTransaction(Predicate(::filterKEY2Commands)) + + // val commandDataComponents = key1CommandsFtx.filteredComponentGroups[0].components + val commandDataHashes = wtx.availableComponentHashes[ComponentGroupEnum.COMMANDS_GROUP.ordinal]!! + val noLastCommandDataPMT = PartialMerkleTree.build( + MerkleTree.getMerkleTree(commandDataHashes), + commandDataHashes.subList(0, 1) + ) + val noLastCommandDataComponents = key1CommandsFtx.filteredComponentGroups[0].components.subList(0, 1) + val noLastCommandDataNonces = key1CommandsFtx.filteredComponentGroups[0].nonces.subList(0, 1) + val noLastCommandDataGroup = FilteredComponentGroup( + ComponentGroupEnum.COMMANDS_GROUP.ordinal, + noLastCommandDataComponents, + noLastCommandDataNonces, + noLastCommandDataPMT + ) + + val signerComponents = key1CommandsFtx.filteredComponentGroups[1].components + val signerHashes = wtx.availableComponentHashes[ComponentGroupEnum.SIGNERS_GROUP.ordinal]!! + val noLastSignerPMT = PartialMerkleTree.build( + MerkleTree.getMerkleTree(signerHashes), + signerHashes.subList(0, 2) + ) + val noLastSignerComponents = key1CommandsFtx.filteredComponentGroups[1].components.subList(0, 2) + val noLastSignerNonces = key1CommandsFtx.filteredComponentGroups[1].nonces.subList(0, 2) + val noLastSignerGroup = FilteredComponentGroup( + ComponentGroupEnum.SIGNERS_GROUP.ordinal, + noLastSignerComponents, + noLastSignerNonces, + noLastSignerPMT + ) + val noLastSignerGroupSamePartialTree = FilteredComponentGroup( + ComponentGroupEnum.SIGNERS_GROUP.ordinal, + noLastSignerComponents, + noLastSignerNonces, + key1CommandsFtx.filteredComponentGroups[1].partialMerkleTree) // We don't update that, so we can catch the index mismatch. + + val updatedFilteredComponentsNoSignersKey2 = listOf(key2CommandsFtx.filteredComponentGroups[0], noLastSignerGroup) + val updatedFilteredComponentsNoSignersKey2SamePMT = listOf(key2CommandsFtx.filteredComponentGroups[0], noLastSignerGroupSamePartialTree) + + // There are only two components in key1CommandsFtx (commandData and signers). + assertEquals(2, key1CommandsFtx.componentGroups.size) + + // Remove last signer for which there is a pointer from a visible commandData. This is the case of Key1. + // This will result to an invalid transaction. + // A command with no corresponding signer detected + // because the pointer of CommandData (3rd leaf) cannot find a corresponding (3rd) signer. + val updatedFilteredComponentsNoSignersKey1SamePMT = listOf(key1CommandsFtx.filteredComponentGroups[0], noLastSignerGroupSamePartialTree) + assertFails { ftxConstructor.newInstance(key1CommandsFtx.id, updatedFilteredComponentsNoSignersKey1SamePMT, key1CommandsFtx.groupHashes) } + + // Remove both last signer (KEY1) and related command. + // Update partial Merkle tree for signers. + val updatedFilteredComponentsNoLastCommandAndSigners = listOf(noLastCommandDataGroup, noLastSignerGroup) + val ftxNoLastCommandAndSigners = ftxConstructor.newInstance(key1CommandsFtx.id, updatedFilteredComponentsNoLastCommandAndSigners, key1CommandsFtx.groupHashes) as FilteredTransaction + // verify() will pass as the transaction is well-formed. + ftxNoLastCommandAndSigners.verify() + // checkCommandVisibility() will not pass, because checkAllComponentsVisible(ComponentGroupEnum.SIGNERS_GROUP) will fail. + assertFailsWith { ftxNoLastCommandAndSigners.checkCommandVisibility(DUMMY_KEY_1.public) } + + // Remove last signer for which there is no pointer from a visible commandData. This is the case of Key2. + // Do not change partial Merkle tree for signers. + // This time the object can be constructed as there is no pointer mismatch. + val ftxNoLastSigner = ftxConstructor.newInstance(key2CommandsFtx.id, updatedFilteredComponentsNoSignersKey2SamePMT, key2CommandsFtx.groupHashes) as FilteredTransaction + // verify() will fail as we didn't change the partial Merkle tree. + assertFailsWith { ftxNoLastSigner.verify() } + // checkCommandVisibility() will not pass. + assertFailsWith { ftxNoLastSigner.checkCommandVisibility(DUMMY_KEY_2.public) } + + // Remove last signer for which there is no pointer from a visible commandData. This is the case of Key2. + // Update partial Merkle tree for signers. + val ftxNoLastSignerB = ftxConstructor.newInstance(key2CommandsFtx.id, updatedFilteredComponentsNoSignersKey2, key2CommandsFtx.groupHashes) as FilteredTransaction + // verify() will pass, the transaction is well-formed. + ftxNoLastSignerB.verify() + // But, checkAllComponentsVisible() will not pass. + assertFailsWith { ftxNoLastSignerB.checkCommandVisibility(DUMMY_KEY_2.public) } + + // Modify last signer (we have a pointer from commandData). + // Update partial Merkle tree for signers. + val alterSignerComponents = signerComponents.subList(0, 2) + signerComponents[1] // Third one is removed and the 2nd command is added twice. + val alterSignersHashes = wtx.availableComponentHashes[ComponentGroupEnum.SIGNERS_GROUP.ordinal]!!.subList(0, 2) + componentHash(key1CommandsFtx.filteredComponentGroups[1].nonces[2], alterSignerComponents[2]) + val alterMTree = MerkleTree.getMerkleTree(alterSignersHashes) + val alterSignerPMTK = PartialMerkleTree.build( + alterMTree, + alterSignersHashes + ) + + val alterSignerGroup = FilteredComponentGroup( + ComponentGroupEnum.SIGNERS_GROUP.ordinal, + alterSignerComponents, + key1CommandsFtx.filteredComponentGroups[1].nonces, + alterSignerPMTK + ) + val alterFilteredComponents = listOf(key1CommandsFtx.filteredComponentGroups[0], alterSignerGroup) + + // Do not update groupHashes. + val ftxAlterSigner = ftxConstructor.newInstance(key1CommandsFtx.id, alterFilteredComponents, key1CommandsFtx.groupHashes) as FilteredTransaction + // Visible components in signers group cannot be verified against their partial Merkle tree. + assertFailsWith { ftxAlterSigner.verify() } + // Also, checkAllComponentsVisible() will not pass (groupHash matching will fail). + assertFailsWith { ftxAlterSigner.checkCommandVisibility(DUMMY_KEY_1.public) } + + // Update groupHashes. + val ftxAlterSignerB = ftxConstructor.newInstance(key1CommandsFtx.id, alterFilteredComponents, key1CommandsFtx.groupHashes.subList(0, 6) + alterMTree.hash) as FilteredTransaction + // Visible components in signers group cannot be verified against their partial Merkle tree. + assertFailsWith { ftxAlterSignerB.verify() } + // Also, checkAllComponentsVisible() will not pass (top level Merkle tree cannot be verified against transaction's id). + assertFailsWith { ftxAlterSignerB.checkCommandVisibility(DUMMY_KEY_1.public) } + + ftxConstructor.isAccessible = false } } + diff --git a/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt b/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt index a82e698a97..e476f64cb5 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt @@ -1,6 +1,5 @@ package net.corda.core.crypto - import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash.Companion.zeroHash import net.corda.core.identity.Party @@ -14,10 +13,12 @@ import net.corda.testing.* import org.junit.Test import java.security.PublicKey import java.util.function.Predicate +import java.util.stream.IntStream +import kotlin.streams.toList import kotlin.test.* class PartialMerkleTreeTest : TestDependencyInjectionBase() { - val nodes = "abcdef" + private val nodes = "abcdef" private val hashed = nodes.map { initialiseTestSerialization() try { @@ -115,16 +116,18 @@ class PartialMerkleTreeTest : TestDependencyInjectionBase() { val d = testTx.serialize().deserialize() assertEquals(testTx.id, d.id) - val mt = testTx.buildFilteredTransaction(Predicate(::filtering)) + val ftx = testTx.buildFilteredTransaction(Predicate(::filtering)) - assertEquals(4, mt.filteredComponentGroups.size) - assertEquals(1, mt.inputs.size) - assertEquals(0, mt.attachments.size) - assertEquals(1, mt.outputs.size) - assertEquals(1, mt.commands.size) - assertNull(mt.notary) - assertNotNull(mt.timeWindow) - mt.verify() + // We expect 5 and not 4 component groups, because there is at least one command in the ftx and thus, + // the signers component is also sent (required for visibility purposes). + assertEquals(5, ftx.filteredComponentGroups.size) + assertEquals(1, ftx.inputs.size) + assertEquals(0, ftx.attachments.size) + assertEquals(1, ftx.outputs.size) + assertEquals(1, ftx.commands.size) + assertNull(ftx.notary) + assertNotNull(ftx.timeWindow) + ftx.verify() } @Test @@ -246,4 +249,50 @@ class PartialMerkleTreeTest : TestDependencyInjectionBase() { privacySalt = privacySalt ) } + + @Test + fun `Find leaf index`() { + // A Merkle tree with 20 leaves. + val sampleLeaves = IntStream.rangeClosed(0, 19).toList().map { SecureHash.sha256(it.toString()) } + val merkleTree = MerkleTree.getMerkleTree(sampleLeaves) + + // Provided hashes are not in the tree. + assertFailsWith { PartialMerkleTree.build(merkleTree, listOf(SecureHash.sha256("20"))) } + // One of the provided hashes is not in the tree. + assertFailsWith { PartialMerkleTree.build(merkleTree, listOf(SecureHash.sha256("20"), SecureHash.sha256("1"), SecureHash.sha256("5"))) } + + val pmt = PartialMerkleTree.build(merkleTree, listOf(SecureHash.sha256("1"), SecureHash.sha256("5"), SecureHash.sha256("0"), SecureHash.sha256("19"))) + // First leaf. + assertEquals(0, pmt.leafIndex(SecureHash.sha256("0"))) + // Second leaf. + assertEquals(1, pmt.leafIndex(SecureHash.sha256("1"))) + // A random leaf. + assertEquals(5, pmt.leafIndex(SecureHash.sha256("5"))) + // The last leaf. + assertEquals(19, pmt.leafIndex(SecureHash.sha256("19"))) + // The provided hash is not in the tree. + assertFailsWith { pmt.leafIndex(SecureHash.sha256("10")) } + // The provided hash is not in the tree (using a leaf that didn't exist in the original Merkle tree). + assertFailsWith { pmt.leafIndex(SecureHash.sha256("30")) } + + val pmtFirstElementOnly = PartialMerkleTree.build(merkleTree, listOf(SecureHash.sha256("0"))) + assertEquals(0, pmtFirstElementOnly.leafIndex(SecureHash.sha256("0"))) + // The provided hash is not in the tree. + assertFailsWith { pmtFirstElementOnly.leafIndex(SecureHash.sha256("10")) } + + val pmtLastElementOnly = PartialMerkleTree.build(merkleTree, listOf(SecureHash.sha256("19"))) + assertEquals(19, pmtLastElementOnly.leafIndex(SecureHash.sha256("19"))) + // The provided hash is not in the tree. + assertFailsWith { pmtLastElementOnly.leafIndex(SecureHash.sha256("10")) } + + val pmtOneElement = PartialMerkleTree.build(merkleTree, listOf(SecureHash.sha256("5"))) + assertEquals(5, pmtOneElement.leafIndex(SecureHash.sha256("5"))) + // The provided hash is not in the tree. + assertFailsWith { pmtOneElement.leafIndex(SecureHash.sha256("10")) } + + val pmtAllIncluded = PartialMerkleTree.build(merkleTree, sampleLeaves) + for (i in 0..19) assertEquals(i, pmtAllIncluded.leafIndex(SecureHash.sha256(i.toString()))) + // The provided hash is not in the tree (using a leaf that didn't exist in the original Merkle tree). + assertFailsWith { pmtAllIncluded.leafIndex(SecureHash.sha256("30")) } + } } 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 aae7f6fc0d..81e67b3b6e 100644 --- a/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt @@ -7,24 +7,21 @@ import net.corda.core.crypto.sha256 import net.corda.core.identity.Party import net.corda.core.internal.FetchAttachmentsFlow import net.corda.core.internal.FetchDataFlow -import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.utilities.getOrThrow import net.corda.node.internal.StartedNode -import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.persistence.NodeAttachmentService -import net.corda.nodeapi.internal.ServiceInfo import net.corda.testing.ALICE import net.corda.testing.ALICE_NAME import net.corda.testing.BOB import net.corda.testing.node.MockNetwork +import net.corda.testing.node.MockNodeArgs +import net.corda.testing.node.MockNodeParameters import net.corda.testing.singleIdentity import org.junit.After import org.junit.Before import org.junit.Test import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream -import java.math.BigInteger -import java.security.KeyPair import java.util.jar.JarOutputStream import java.util.zip.ZipEntry import kotlin.test.assertEquals @@ -60,7 +57,6 @@ class AttachmentTests { // Ensure that registration was successful before progressing any further mockNet.runNetwork() - aliceNode.internals.ensureRegistered() val alice = aliceNode.info.singleIdentity() aliceNode.internals.registerInitiatedFlow(FetchAttachmentsResponse::class.java) @@ -98,7 +94,6 @@ class AttachmentTests { // Ensure that registration was successful before progressing any further mockNet.runNetwork() - aliceNode.internals.ensureRegistered() aliceNode.internals.registerInitiatedFlow(FetchAttachmentsResponse::class.java) bobNode.internals.registerInitiatedFlow(FetchAttachmentsResponse::class.java) @@ -116,20 +111,15 @@ class AttachmentTests { @Test fun `malicious response`() { // Make a node that doesn't do sanity checking at load time. - val aliceNode = mockNet.createNotaryNode(legalName = ALICE.name, nodeFactory = object : MockNetwork.Factory { - override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, - id: Int, notaryIdentity: Pair?, - entropyRoot: BigInteger): MockNetwork.MockNode { - return object : MockNetwork.MockNode(config, network, networkMapAddr, id, notaryIdentity, entropyRoot) { + val aliceNode = mockNet.createNotaryNode(MockNodeParameters(legalName = ALICE.name), nodeFactory = object : MockNetwork.Factory { + override fun create(args: MockNodeArgs): MockNetwork.MockNode { + return object : MockNetwork.MockNode(args) { override fun start() = super.start().apply { attachments.checkAttachmentsOnLoad = false } } } }, validating = false) - val bobNode = mockNet.createNode(legalName = BOB.name) - - // Ensure that registration was successful before progressing any further + val bobNode = mockNet.createNode(MockNodeParameters(legalName = BOB.name)) mockNet.runNetwork() - aliceNode.internals.ensureRegistered() val alice = aliceNode.services.myInfo.identityFromX500Name(ALICE_NAME) aliceNode.internals.registerInitiatedFlow(FetchAttachmentsResponse::class.java) diff --git a/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt b/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt index a0b2c0020c..9ecea5940b 100644 --- a/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt @@ -44,7 +44,6 @@ class CollectSignaturesFlowTests { bobNode = mockNet.createPartyNode(BOB.name) charlieNode = mockNet.createPartyNode(CHARLIE.name) mockNet.runNetwork() - aliceNode.internals.ensureRegistered() alice = aliceNode.info.singleIdentity() bob = bobNode.info.singleIdentity() charlie = charlieNode.info.singleIdentity() diff --git a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt index 782d3ee35f..19b93ba663 100644 --- a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt +++ b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt @@ -47,7 +47,6 @@ class ContractUpgradeFlowTest { // Process registration mockNet.runNetwork() - aliceNode.internals.ensureRegistered() notary = notaryNode.services.getDefaultNotary() } @@ -119,7 +118,7 @@ class ContractUpgradeFlowTest { return startRpcClient( rpcAddress = startRpcServer( rpcUser = user, - ops = CordaRPCOpsImpl(node.services, node.smm, node.database) + ops = CordaRPCOpsImpl(node.services, node.smm, node.database, node.services) ).get().broker.hostAndPort!!, username = user.username, password = user.password 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 bdc76e1d83..1bc496db6c 100644 --- a/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt @@ -6,7 +6,7 @@ import net.corda.core.utilities.getOrThrow import net.corda.finance.POUNDS import net.corda.finance.contracts.asset.Cash import net.corda.finance.issuedBy -import net.corda.node.services.api.ServiceHubInternal +import net.corda.node.services.api.StartedNodeServices import net.corda.testing.* import net.corda.testing.node.MockNetwork import org.junit.After @@ -17,8 +17,8 @@ import kotlin.test.assertFailsWith class FinalityFlowTests { private lateinit var mockNet: MockNetwork - private lateinit var aliceServices: ServiceHubInternal - private lateinit var bobServices: ServiceHubInternal + private lateinit var aliceServices: StartedNodeServices + private lateinit var bobServices: StartedNodeServices private lateinit var alice: Party private lateinit var bob: Party private lateinit var notary: Party @@ -30,7 +30,6 @@ class FinalityFlowTests { val aliceNode = mockNet.createPartyNode(ALICE_NAME) val bobNode = mockNet.createPartyNode(BOB_NAME) mockNet.runNetwork() - aliceNode.internals.ensureRegistered() aliceServices = aliceNode.services bobServices = bobNode.services alice = aliceNode.info.singleIdentity() diff --git a/core/src/test/kotlin/net/corda/core/internal/concurrent/CordaFutureImplTest.kt b/core/src/test/kotlin/net/corda/core/internal/concurrent/CordaFutureImplTest.kt index 8b9efe991a..6ca2319239 100644 --- a/core/src/test/kotlin/net/corda/core/internal/concurrent/CordaFutureImplTest.kt +++ b/core/src/test/kotlin/net/corda/core/internal/concurrent/CordaFutureImplTest.kt @@ -3,6 +3,7 @@ package net.corda.core.internal.concurrent import com.nhaarman.mockito_kotlin.* import net.corda.core.concurrent.CordaFuture import net.corda.core.utilities.getOrThrow +import net.corda.testing.rigorousMock import org.assertj.core.api.Assertions import org.junit.Test import org.slf4j.Logger @@ -31,7 +32,7 @@ class CordaFutureTest { fun `if a listener fails its throwable is logged`() { val f = CordaFutureImpl() val x = Exception() - val log = mock() + val log = rigorousMock() val flag = AtomicBoolean() f.thenImpl(log) { throw x } f.thenImpl(log) { flag.set(true) } // Must not be affected by failure of previous listener. @@ -57,7 +58,7 @@ class CordaFutureTest { Assertions.assertThatThrownBy { g.getOrThrow() }.isSameAs(x) } run { - val block = mock<(Any?) -> Any?>() + val block = rigorousMock<(Any?) -> Any?>() val f = CordaFutureImpl() val g = f.map(block) val x = Exception() @@ -90,7 +91,7 @@ class CordaFutureTest { Assertions.assertThatThrownBy { g.getOrThrow() }.isSameAs(x) } run { - val block = mock<(Any?) -> CordaFuture<*>>() + val block = rigorousMock<(Any?) -> CordaFuture<*>>() val f = CordaFutureImpl() val g = f.flatMap(block) val x = Exception() @@ -102,7 +103,8 @@ class CordaFutureTest { @Test fun `andForget works`() { - val log = mock() + val log = rigorousMock() + doNothing().whenever(log).error(any(), any()) val throwable = Exception("Boom") val executor = Executors.newSingleThreadExecutor() executor.fork { throw throwable }.andForget(log) 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 f6d1eec6b2..8057164bcf 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt @@ -9,24 +9,21 @@ import net.corda.core.flows.InitiatingFlow import net.corda.core.flows.TestDataVendingFlow import net.corda.core.internal.FetchAttachmentsFlow import net.corda.core.internal.FetchDataFlow -import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.unwrap import net.corda.node.internal.InitiatedFlowFactory import net.corda.node.internal.StartedNode -import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.persistence.NodeAttachmentService import net.corda.node.utilities.currentDBSession -import net.corda.nodeapi.internal.ServiceInfo import net.corda.testing.chooseIdentity import net.corda.testing.node.MockNetwork +import net.corda.testing.node.MockNodeArgs +import net.corda.testing.node.MockNodeParameters import org.junit.After import org.junit.Before import org.junit.Test import java.io.ByteArrayOutputStream -import java.math.BigInteger import java.nio.charset.StandardCharsets.UTF_8 -import java.security.KeyPair import java.util.zip.ZipEntry import java.util.zip.ZipOutputStream import kotlin.test.assertEquals @@ -74,7 +71,6 @@ class AttachmentSerializationTest { client = mockNet.createNode() client.internals.disableDBCloseOnStop() // Otherwise the in-memory database may disappear (taking the checkpoint with it) while we reboot the client. mockNet.runNetwork() - server.internals.ensureRegistered() } @After @@ -160,10 +156,9 @@ class AttachmentSerializationTest { private fun rebootClientAndGetAttachmentContent(checkAttachmentsOnLoad: Boolean = true): String { client.dispose() - client = mockNet.createNode(client.internals.id, object : MockNetwork.Factory { - override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, - id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): MockNetwork.MockNode { - return object : MockNetwork.MockNode(config, network, networkMapAddr, id, notaryIdentity, entropyRoot) { + client = mockNet.createNode(MockNodeParameters(client.internals.id), object : MockNetwork.Factory { + override fun create(args: MockNodeArgs): MockNetwork.MockNode { + return object : MockNetwork.MockNode(args) { override fun start() = super.start().apply { attachments.checkAttachmentsOnLoad = checkAttachmentsOnLoad } } } diff --git a/core/src/test/kotlin/net/corda/core/utilities/KotlinUtilsTest.kt b/core/src/test/kotlin/net/corda/core/utilities/KotlinUtilsTest.kt index 22ef438a6b..b4ef01dbbc 100644 --- a/core/src/test/kotlin/net/corda/core/utilities/KotlinUtilsTest.kt +++ b/core/src/test/kotlin/net/corda/core/utilities/KotlinUtilsTest.kt @@ -6,6 +6,7 @@ import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.nodeapi.internal.serialization.KRYO_CHECKPOINT_CONTEXT +import net.corda.nodeapi.internal.serialization.KRYO_P2P_CONTEXT import net.corda.testing.TestDependencyInjectionBase import org.assertj.core.api.Assertions.assertThat import org.junit.Rule @@ -26,7 +27,7 @@ class KotlinUtilsTest : TestDependencyInjectionBase() { } @Test - fun `checkpointing a transient property with non-capturing lamba`() { + fun `checkpointing a transient property with non-capturing lambda`() { val original = NonCapturingTransientProperty() val originalVal = original.transientVal val copy = original.serialize(context = KRYO_CHECKPOINT_CONTEXT).deserialize(context = KRYO_CHECKPOINT_CONTEXT) @@ -36,15 +37,15 @@ class KotlinUtilsTest : TestDependencyInjectionBase() { } @Test - fun `serialise transient property with non-capturing lamba`() { + fun `serialise transient property with non-capturing lambda`() { expectedEx.expect(KryoException::class.java) expectedEx.expectMessage("is not annotated or on the whitelist, so cannot be used in serialization") val original = NonCapturingTransientProperty() - original.serialize() + original.serialize(context = KRYO_P2P_CONTEXT) } @Test - fun `deserialise transient property with non-capturing lamba`() { + fun `deserialise transient property with non-capturing lambda`() { expectedEx.expect(KryoException::class.java) expectedEx.expectMessage("is not annotated or on the whitelist, so cannot be used in serialization") val original = NonCapturingTransientProperty() @@ -52,7 +53,7 @@ class KotlinUtilsTest : TestDependencyInjectionBase() { } @Test - fun `checkpointing a transient property with capturing lamba`() { + fun `checkpointing a transient property with capturing lambda`() { val original = CapturingTransientProperty("Hello") val originalVal = original.transientVal val copy = original.serialize(context = KRYO_CHECKPOINT_CONTEXT).deserialize(context = KRYO_CHECKPOINT_CONTEXT) @@ -63,15 +64,15 @@ class KotlinUtilsTest : TestDependencyInjectionBase() { } @Test - fun `serialise transient property with capturing lamba`() { + fun `serialise transient property with capturing lambda`() { expectedEx.expect(KryoException::class.java) expectedEx.expectMessage("is not annotated or on the whitelist, so cannot be used in serialization") val original = CapturingTransientProperty("Hello") - original.serialize() + original.serialize(context = KRYO_P2P_CONTEXT) } @Test - fun `deserialise transient property with capturing lamba`() { + fun `deserialise transient property with capturing lambda`() { expectedEx.expect(KryoException::class.java) expectedEx.expectMessage("is not annotated or on the whitelist, so cannot be used in serialization") val original = CapturingTransientProperty("Hello") diff --git a/docs/build.gradle b/docs/build.gradle index 542cc7380f..a0d60a371e 100644 --- a/docs/build.gradle +++ b/docs/build.gradle @@ -5,13 +5,17 @@ dependencies { compile rootProject } +ext { + // TODO: Add '../client/jfx/src/main/kotlin' and '../client/mock/src/main/kotlin' if we decide to make them into public API + dokkaSourceDirs = files('../core/src/main/kotlin', '../client/rpc/src/main/kotlin', '../finance/src/main/kotlin', '../client/jackson/src/main/kotlin', + '../testing/test-utils/src/main/kotlin', '../testing/node-driver/src/main/kotlin') +} + dokka { moduleName = 'corda' outputDirectory = file("${rootProject.rootDir}/docs/build/html/api/kotlin") processConfigurations = ['compile'] - // TODO: Re-add '../testing/node-driver/src/main/kotlin', '../testing/test-utils/src/main/kotlin' when they're API stable - // TODO: Add '../client/jfx/src/main/kotlin' and '../client/mock/src/main/kotlin' if we decide to make them into public API - sourceDirs = files('../core/src/main/kotlin', '../client/rpc/src/main/kotlin', '../finance/src/main/kotlin', '../client/jackson/src/main/kotlin') + sourceDirs = dokkaSourceDirs includes = ['packages.md'] jdkVersion = 8 @@ -31,8 +35,7 @@ task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaTask) { outputFormat = "javadoc" outputDirectory = file("${rootProject.rootDir}/docs/build/html/api/javadoc") processConfigurations = ['compile'] - // TODO: Make this a copy of the list above programmatically. - sourceDirs = files('../core/src/main/kotlin', '../client/rpc/src/main/kotlin', '../finance/src/main/kotlin', '../client/jackson/src/main/kotlin') + sourceDirs = dokkaSourceDirs includes = ['packages.md'] jdkVersion = 8 diff --git a/docs/source/api-persistence.rst b/docs/source/api-persistence.rst index add8f1b785..c2c6e88e46 100644 --- a/docs/source/api-persistence.rst +++ b/docs/source/api-persistence.rst @@ -81,10 +81,10 @@ Custom schema registration Custom contract schemas are automatically registered at startup time for CorDapps. The node bootstrap process will scan for schemas (any class that extends the ``MappedSchema`` interface) in the `plugins` configuration directory in your CorDapp jar. -For testing purposes it is necessary to manually register custom schemas as follows: +For testing purposes it is necessary to manually register the packages containing custom schemas as follows: -- Tests using ``MockNetwork`` and ``MockNode`` must explicitly register custom schemas using the `registerCustomSchemas()` method of ``MockNode`` -- Tests using ``MockServices`` must explicitly register schemas using `customSchemas` attribute of the ``MockServices`` `makeTestDatabaseAndMockServices()` helper method. +- Tests using ``MockNetwork`` and ``MockNode`` must explicitly register packages using the `cordappPackages` parameter of ``MockNetwork`` +- Tests using ``MockServices`` must explicitly register packages using the `cordappPackages` parameter of the ``MockServices`` `makeTestDatabaseAndMockServices()` helper method. .. note:: Tests using the `DriverDSL` will automatically register your custom schemas if they are in the same project structure as the driver call. diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 480f187ce6..aa7af701b7 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -11,6 +11,8 @@ UNRELEASED * ``OpaqueBytes.bytes`` now returns a clone of its underlying ``ByteArray``, and has been redeclared as ``final``. This is a minor change to the public API, but is required to ensure that classes like ``SecureHash`` are immutable. +* Experimental support for PostgreSQL: CashSelection done using window functions + * ``FlowLogic`` now exposes a series of function called ``receiveAll(...)`` allowing to join ``receive(...)`` instructions. * Renamed "plugins" directory on nodes to "cordapps" @@ -28,7 +30,7 @@ UNRELEASED * ``Cordapp`` now has a name field for identifying CorDapps and all CorDapp names are printed to console at startup. -* Enums now respsect the whitelist applied to the Serializer factory serializing / deserializing them. If the enum isn't +* Enums now respect the whitelist applied to the Serializer factory serializing / deserializing them. If the enum isn't either annotated with the @CordaSerializable annotation or explicitly whitelisted then a NotSerializableException is thrown. @@ -56,6 +58,17 @@ UNRELEASED * ``TimeWindow`` now has a ``length`` property that returns the length of the time-window, or ``null`` if the time-window is open-ended. +* A new ``SIGNERS_GROUP`` with ordinal 6 has been added to ``ComponentGroupEnum`` that corresponds to the ``Command`` + signers. + +* ``PartialMerkleTree`` is equipped with a ``leafIndex`` function that returns the index of a hash (leaf) in the + partial Merkle tree structure. + +* A new function ``checkCommandVisibility(publicKey: PublicKey)`` has been added to ``FilteredTransaction`` to check + if every command that a signer should receive (e.g. an Oracle) is indeed visible. + +* Change the AMQP serialiser to use the oficially assigned R3 identifier rather than a placeholder. + .. _changelog_v1: Release 1.0 diff --git a/docs/source/corda-api.rst b/docs/source/corda-api.rst index 8b5dd94053..bb973b0f05 100644 --- a/docs/source/corda-api.rst +++ b/docs/source/corda-api.rst @@ -91,3 +91,14 @@ The following modules are available but we do not commit to their stability or c Future releases will reject any CorDapps that use types from these packages. .. warning:: The web server module will be removed in future. You should call Corda nodes through RPC from your web server of choice e.g., Spring Boot, Vertx, Undertow. + +The ``@DoNotImplement`` annotation +---------------------------------- + +Certain interfaces and abstract classes within the Corda API have been annotated +as ``@DoNotImplement``. While we undertake not to remove or modify any of these classes' existing +functionality, the annotation is a warning that we may need to extend them in future versions of Corda. +Cordapp developers should therefore just use these classes "as is", and *not* attempt to extend or implement any of them themselves. + +This annotation is inherited by subclasses and subinterfaces. + diff --git a/docs/source/corda-configuration-file.rst b/docs/source/corda-configuration-file.rst index 721e6ff74b..6b964d60a6 100644 --- a/docs/source/corda-configuration-file.rst +++ b/docs/source/corda-configuration-file.rst @@ -155,6 +155,16 @@ path to the node's base directory. :certificateSigningService: Certificate Signing Server address. It is used by the certificate signing request utility to obtain SSL certificate. (See :doc:`permissioning` for more information.) +:jvmArgs: An optional list of JVM args, as strings, which replace those inherited from the command line when launching via ``corda.jar`` + only. e.g. ``jvmArgs = [ "-Xmx220m", "-Xms220m", "-XX:+UseG1GC" ]`` + +:systemProperties: An optional map of additional system properties to be set when launching via ``corda.jar`` only. Keys and values + of the map should be strings. e.g. ``systemProperties = { visualvm.display.name = FooBar }`` + +:jarDirs: An optional list of file system directories containing JARs to include in the classpath when launching via ``corda.jar`` only. + Each should be a string. Only the JARs in the directories are added, not the directories themselves. This is useful + for including JDBC drivers and the like. e.g. ``jarDirs = [ 'lib' ]`` + :relay: If provided, the node will attempt to tunnel inbound connections via an external relay. The relay's address will be advertised to the network map service instead of the provided ``p2pAddress``. diff --git a/docs/source/example-code/build.gradle b/docs/source/example-code/build.gradle index 0568552add..6cad970f10 100644 --- a/docs/source/example-code/build.gradle +++ b/docs/source/example-code/build.gradle @@ -1,7 +1,6 @@ apply plugin: 'kotlin' apply plugin: 'application' apply plugin: 'net.corda.plugins.cordformation' -apply plugin: 'net.corda.plugins.cordapp' apply plugin: 'net.corda.plugins.quasar-utils' repositories { 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 new file mode 100644 index 0000000000..9e86b57aab --- /dev/null +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/mocknetwork/TutorialMockNetwork.kt @@ -0,0 +1,111 @@ +package net.corda.docs.tutorial.mocknetwork + +import co.paralleluniverse.fibers.Suspendable +import net.corda.core.contracts.requireThat +import net.corda.core.flows.FlowLogic +import net.corda.core.flows.FlowSession +import net.corda.core.flows.InitiatedBy +import net.corda.core.flows.InitiatingFlow +import net.corda.core.identity.Party +import net.corda.core.messaging.MessageRecipients +import net.corda.core.serialization.deserialize +import net.corda.core.serialization.serialize +import net.corda.core.utilities.getOrThrow +import net.corda.core.utilities.unwrap +import net.corda.node.internal.StartedNode +import net.corda.node.services.messaging.Message +import net.corda.node.services.statemachine.SessionData +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 org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.ExpectedException + +class TutorialMockNetwork { + + @InitiatingFlow + class FlowA(private val otherParty: Party) : FlowLogic() { + + @Suspendable + override fun call() { + val session = initiateFlow(otherParty) + + session.receive().unwrap { + requireThat { "Expected to receive 1" using (it == 1) } + } + + session.receive().unwrap { + requireThat { "Expected to receive 2" using (it == 2) } + } + } + } + + @InitiatedBy(FlowA::class) + class FlowB(private val session: FlowSession) : FlowLogic() { + + @Suspendable + override fun call() { + session.send(1) + session.send(2) + } + } + + lateinit private var mockNet: MockNetwork + lateinit private var notary: StartedNode + lateinit private var nodeA: StartedNode + lateinit private var nodeB: StartedNode + + @Rule + @JvmField + val expectedEx: ExpectedException = ExpectedException.none() + + @Before + fun setUp() { + mockNet = MockNetwork() + notary = mockNet.createNotaryNode() + nodeA = mockNet.createPartyNode() + nodeB = mockNet.createPartyNode() + + nodeB.registerInitiatedFlow(FlowB::class.java) + + mockNet.runNetwork() + } + + @After + fun tearDown() { + mockNet.stopNodes() + } + + @Test + fun `fail if initiated doesn't send back 1 on first result`() { + + // DOCSTART 1 + // modify message if it's 1 + nodeB.setMessagingServiceSpy(object : MessagingServiceSpy(nodeB.network) { + + override fun send(message: Message, target: MessageRecipients, retryId: Long?, sequenceKey: Any, acknowledgementHandler: (() -> Unit)?) { + val messageData = message.data.deserialize() + + if (messageData is SessionData && messageData.payload.deserialize() == 1) { + val alteredMessageData = SessionData(messageData.recipientSessionId, 99.serialize()).serialize().bytes + messagingService.send(InMemoryMessagingNetwork.InMemoryMessage(message.topicSession, alteredMessageData, message.uniqueMessageId), target, retryId) + } else { + messagingService.send(message, target, retryId) + } + } + }) + // DOCEND 1 + + val initiatingReceiveFlow = nodeA.services.startFlow(FlowA(nodeB.info.legalIdentities.first())) + + mockNet.runNetwork() + + expectedEx.expect(IllegalArgumentException::class.java) + expectedEx.expectMessage("Expected to receive 1") + initiatingReceiveFlow.resultFuture.getOrThrow() + } +} \ No newline at end of file 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 1ec43e7271..9690a5ab41 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 @@ -27,12 +27,18 @@ class CustomVaultQueryTest { @Before fun setup() { - mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.finance.contracts.asset", CashSchemaV1::class.packageName)) + mockNet = MockNetwork( + threadPerNode = true, + cordappPackages = listOf( + "net.corda.finance.contracts.asset", + CashSchemaV1::class.packageName, + "net.corda.docs" + ) + ) mockNet.createNotaryNode(legalName = DUMMY_NOTARY.name) nodeA = mockNet.createPartyNode() nodeB = mockNet.createPartyNode() nodeA.internals.registerInitiatedFlow(TopupIssuerFlow.TopupIssuer::class.java) - nodeA.internals.installCordaService(CustomVaultQuery.Service::class.java) notary = nodeA.services.getDefaultNotary() } 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 f7b99ead79..833f4c140d 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 @@ -9,7 +9,7 @@ import net.corda.core.node.services.queryBy 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.ServiceHubInternal +import net.corda.node.services.api.StartedNodeServices import net.corda.testing.* import net.corda.testing.node.MockNetwork import org.junit.After @@ -19,8 +19,8 @@ import kotlin.test.assertEquals class WorkflowTransactionBuildTutorialTest { lateinit var mockNet: MockNetwork - lateinit var aliceServices: ServiceHubInternal - lateinit var bobServices: ServiceHubInternal + lateinit var aliceServices: StartedNodeServices + lateinit var bobServices: StartedNodeServices lateinit var alice: Party lateinit var bob: Party diff --git a/docs/source/flow-testing.rst b/docs/source/flow-testing.rst index 967dbe9877..8e131b94ff 100644 --- a/docs/source/flow-testing.rst +++ b/docs/source/flow-testing.rst @@ -63,4 +63,17 @@ transaction as shown here. With regards to initiated flows (see :doc:`flow-state-machines` for information on initiated and initiating flows), the full node automatically registers them by scanning the CorDapp jars. In a unit test environment this is not possible so -``MockNode`` has the ``registerInitiatedFlow`` method to manually register an initiated flow. \ No newline at end of file +``MockNode`` has the ``registerInitiatedFlow`` method to manually register an initiated flow. + +MockNetwork message manipulation +-------------------------------- +The MockNetwork has the ability to manipulate message streams. You can use this to test your flows behaviour on corrupted, +or malicious data received. + +Message modification example in ``TutorialMockNetwork.kt``: + +.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/mocknetwork/TutorialMockNetwork.kt + :language: kotlin + :start-after: DOCSTART 1 + :end-before: DOCEND 1 + :dedent: 8 diff --git a/docs/source/key-concepts-contract-constraints.rst b/docs/source/key-concepts-contract-constraints.rst index edd4c6694d..35db806193 100644 --- a/docs/source/key-concepts-contract-constraints.rst +++ b/docs/source/key-concepts-contract-constraints.rst @@ -95,9 +95,9 @@ to specify JAR URLs in the case that the CorDapp(s) involved in testing already MockNetwork/MockNode ******************** -The most simple way to ensure that a vanilla instance of a MockNode generates the correct CorDapps is to make a call -to ``setCordappPackages`` before the MockNetwork/Node are created and then ``unsetCordappPackages`` after the test -has finished. These calls will cause the ``AbstractNode`` to use the named packages as sources for CorDapps. All files +The most simple way to ensure that a vanilla instance of a MockNode generates the correct CorDapps is to use the +``cordappPackages`` constructor parameter (Kotlin) or the ``setCordappPackages`` method on ``MockNetworkParameters`` (Java) +when creating the MockNetwork. This will cause the ``AbstractNode`` to use the named packages as sources for CorDapps. All files within those packages will be zipped into a JAR and added to the attachment store and loaded as CorDapps by the ``CordappLoader``. An example of this usage would be: @@ -108,17 +108,7 @@ within those packages will be zipped into a JAR and added to the attachment stor @Before void setup() { - // The ordering of the two below lines is important - if the MockNetwork is created before the nodes and network - // are created the CorDapps will not be loaded into the MockNodes correctly. - setCordappPackages(Arrays.asList("com.domain.cordapp")) - network = new MockNetwork() - } - - @After - void teardown() { - // This must be called at the end otherwise the global state set by setCordappPackages may leak into future - // tests in the same test runner environment. - unsetCordappPackages() + network = new MockNetwork(new MockNetworkParameters().setCordappPackages(Arrays.asList("com.domain.cordapp"))) } ... // Your tests go here diff --git a/docs/source/network-map.rst b/docs/source/network-map.rst new file mode 100644 index 0000000000..d8ee82a2cf --- /dev/null +++ b/docs/source/network-map.rst @@ -0,0 +1,38 @@ +Network Map +=========== + +Protocol Design +--------------- +The node info publishing protocol: + +* Create a ``NodeInfo`` object, and sign it to create a ``SignedData`` object. TODO: We will need list of signatures in ``SignedData`` to support multiple node identities in the future. + +* Serialise the signed data and POST the data to the network map server. + +* The network map server validates the signature and acknowledges the registration with a HTTP 200 response, it will return HTTP 400 "Bad Request" if the data failed validation or if the public key wasn't registered with the network. + +* The network map server will sign and distribute the new network map periodically. + +Node side network map update protocol: + +* The Corda node will query the network map service periodically according to the ``Expires`` attribute in the HTTP header. + +* The network map service returns a signed ``NetworkMap`` object, containing list of node info hashes and the network parameters hashes. + +* The node updates its local copy of ``NodeInfos`` if it is different from the newly downloaded ``NetworkMap``. + +Network Map service REST API: + ++----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Request method | Path | Description | ++================+===================================+========================================================================================================================================================+ +| POST | /api/network-map/publish | Publish new ``NodeInfo`` to the network map service, the legal identity in ``NodeInfo`` must match with the identity registered with the doorman. | ++----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ +| GET | /api/network-map | Retrieve ``NetworkMap`` from the server, the ``NetworkMap`` object contains list of node info hashes and NetworkParameters hash. | ++----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ +| GET | /api/network-map/node-info/{hash} | Retrieve ``NodeInfo`` object with the same hash. | ++----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ +| GET | /api/network-map/parameters/{hash}| Retrieve ``NetworkParameters`` object with the same hash. | ++----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ + +TODO: Access control of the network map will be added in the future. \ No newline at end of file diff --git a/docs/source/upgrade-notes.rst b/docs/source/upgrade-notes.rst index 0040c48e05..1dcb0d72b4 100644 --- a/docs/source/upgrade-notes.rst +++ b/docs/source/upgrade-notes.rst @@ -25,6 +25,19 @@ versions you are currently using are still in force. We also strongly recommend cross referencing with the :doc:`changelog` to confirm changes. +UNRELEASED +---------- + +Testing +^^^^^^^ + +* The registration mechanism for CorDapps in ``MockNetwork`` unit tests has changed. + + It is now done via the ``cordappPackages`` constructor parameter of MockNetwork. + This takes a list of `String` values which should be the + package names of the CorDapps containing the contract verification code you wish to load. + The ``unsetCordappPackages`` method is now redundant and has been removed. + :ref:`Milestone 14 ` ------------ diff --git a/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionPostgreSQLImpl.kt b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionPostgreSQLImpl.kt new file mode 100644 index 0000000000..d8154336e3 --- /dev/null +++ b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionPostgreSQLImpl.kt @@ -0,0 +1,83 @@ +package net.corda.finance.contracts.asset.cash.selection + +import net.corda.core.contracts.Amount +import net.corda.core.identity.AbstractParty +import net.corda.core.identity.Party +import net.corda.core.utilities.OpaqueBytes +import net.corda.core.utilities.debug +import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.toBase58String +import java.sql.Connection +import java.sql.DatabaseMetaData +import java.sql.ResultSet +import java.util.* + +class CashSelectionPostgreSQLImpl : AbstractCashSelection() { + + companion object { + val JDBC_DRIVER_NAME = "PostgreSQL JDBC Driver" + val log = loggerFor() + } + + override fun isCompatible(metadata: DatabaseMetaData): Boolean { + return metadata.driverName == JDBC_DRIVER_NAME + } + + override fun toString() = "${this::class.java} for $JDBC_DRIVER_NAME" + + // This is using PostgreSQL window functions for selecting a minimum set of rows that match a request amount of coins: + // 1) This may also be possible with user-defined functions (e.g. using PL/pgSQL) + // 2) The window function accumulated column (`total`) does not include the current row (starts from 0) and cannot + // appear in the WHERE clause, hence restricting row selection and adjusting the returned total in the outer query. + // 3) Currently (version 9.6), FOR UPDATE cannot be specified with window functions + override fun executeQuery(connection: Connection, amount: Amount, lockId: UUID, notary: Party?, + onlyFromIssuerParties: Set, withIssuerRefs: Set) : ResultSet { + val selectJoin = """SELECT nested.transaction_id, nested.output_index, nested.contract_state, nested.pennies, + nested.total+nested.pennies as total_pennies, nested.lock_id + FROM + (SELECT vs.transaction_id, vs.output_index, vs.contract_state, ccs.pennies, + coalesce((SUM(ccs.pennies) OVER (PARTITION BY 1 ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING)), 0) + AS total, vs.lock_id + FROM vault_states AS vs, contract_cash_states AS ccs + WHERE vs.transaction_id = ccs.transaction_id AND vs.output_index = ccs.output_index + AND vs.state_status = 0 + AND ccs.ccy_code = ? + AND (vs.lock_id = ? OR vs.lock_id is null) + """ + + (if (notary != null) + " AND vs.notary_name = ?" else "") + + (if (onlyFromIssuerParties.isNotEmpty()) + " AND ccs.issuer_key = ANY (?)" else "") + + (if (withIssuerRefs.isNotEmpty()) + " AND ccs.issuer_ref = ANY (?)" else "") + + """) + nested WHERE nested.total < ? + """ + + val statement = connection.prepareStatement(selectJoin) + statement.setString(1, amount.token.toString()) + statement.setString(2, lockId.toString()) + var paramOffset = 0 + if (notary != null) { + statement.setString(3, notary.name.toString()) + paramOffset += 1 + } + if (onlyFromIssuerParties.isNotEmpty()) { + val issuerKeys = connection.createArrayOf("VARCHAR", onlyFromIssuerParties.map + { it.owningKey.toBase58String() }.toTypedArray()) + statement.setArray(3 + paramOffset, issuerKeys) + paramOffset += 1 + } + if (withIssuerRefs.isNotEmpty()) { + val issuerRefs = connection.createArrayOf("BYTEA", withIssuerRefs.map + { it.bytes }.toTypedArray()) + statement.setArray(3 + paramOffset, issuerRefs) + paramOffset += 1 + } + statement.setLong(3 + paramOffset, amount.quantity) + log.debug { statement.toString() } + + return statement.executeQuery() + } + +} diff --git a/finance/src/main/resources/META-INF/services/net.corda.finance.contracts.asset.cash.selection.AbstractCashSelection b/finance/src/main/resources/META-INF/services/net.corda.finance.contracts.asset.cash.selection.AbstractCashSelection index 9ac2461b3c..a4feda21cf 100644 --- a/finance/src/main/resources/META-INF/services/net.corda.finance.contracts.asset.cash.selection.AbstractCashSelection +++ b/finance/src/main/resources/META-INF/services/net.corda.finance.contracts.asset.cash.selection.AbstractCashSelection @@ -1,2 +1,3 @@ net.corda.finance.contracts.asset.cash.selection.CashSelectionH2Impl net.corda.finance.contracts.asset.cash.selection.CashSelectionMySQLImpl +net.corda.finance.contracts.asset.cash.selection.CashSelectionPostgreSQLImpl \ No newline at end of file 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 ef85dead0a..2a69ac0631 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 @@ -1,11 +1,14 @@ package net.corda.finance.contracts.asset +import net.corda.core.internal.packageName import net.corda.core.utilities.getOrThrow import net.corda.finance.DOLLARS import net.corda.finance.flows.CashException import net.corda.finance.flows.CashPaymentFlow +import net.corda.finance.schemas.CashSchemaV1 import net.corda.testing.chooseIdentity import net.corda.testing.node.MockNetwork +import net.corda.testing.node.MockNodeParameters import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Test @@ -14,15 +17,13 @@ class CashSelectionH2Test { @Test fun `check does not hold connection over retries`() { - val mockNet = MockNetwork(threadPerNode = true) + val mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.finance.contracts.asset", CashSchemaV1::class.packageName)) try { val notaryNode = mockNet.createNotaryNode() - val bankA = mockNet.createNode(configOverrides = { existingConfig -> + val bankA = mockNet.createNode(MockNodeParameters(configOverrides = { existingConfig -> // Tweak connections to be minimal to make this easier (1 results in a hung node during start up, so use 2 connections). existingConfig.dataSourceProperties.setProperty("maximumPoolSize", "2") - existingConfig - }) - + })) mockNet.startNodes() // Start more cash spends than we have connections. If spend leaks a connection on retry, we will run out of connections. diff --git a/gradle-plugins/api-scanner/src/main/java/net/corda/plugins/ScanApi.java b/gradle-plugins/api-scanner/src/main/java/net/corda/plugins/ScanApi.java index f0220add42..eb2b7e5599 100644 --- a/gradle-plugins/api-scanner/src/main/java/net/corda/plugins/ScanApi.java +++ b/gradle-plugins/api-scanner/src/main/java/net/corda/plugins/ScanApi.java @@ -16,6 +16,7 @@ import org.gradle.api.tasks.TaskAction; import java.io.*; import java.lang.annotation.Annotation; +import java.lang.annotation.Inherited; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; @@ -25,7 +26,7 @@ import java.net.URLClassLoader; import java.util.*; import java.util.stream.StreamSupport; -import static java.util.Collections.unmodifiableSet; +import static java.util.Collections.*; import static java.util.stream.Collectors.*; @SuppressWarnings("unused") @@ -228,9 +229,19 @@ public class ScanApi extends DefaultTask { private void writeClass(PrintWriter writer, ClassInfo classInfo, int modifiers) { if (classInfo.isAnnotation()) { + /* + * Annotation declaration. + */ writer.append(Modifier.toString(modifiers & INTERFACE_MASK)); writer.append(" @interface ").print(classInfo); } else if (classInfo.isStandardClass()) { + /* + * Class declaration. + */ + List annotationNames = toNames(readClassAnnotationsFor(classInfo)); + if (!annotationNames.isEmpty()) { + writer.append(asAnnotations(annotationNames)); + } writer.append(Modifier.toString(modifiers & CLASS_MASK)); writer.append(" class ").print(classInfo); Set superclasses = classInfo.getDirectSuperclasses(); @@ -242,6 +253,13 @@ public class ScanApi extends DefaultTask { writer.append(" implements ").print(stringOf(interfaces)); } } else { + /* + * Interface declaration. + */ + List annotationNames = toNames(readInterfaceAnnotationsFor(classInfo)); + if (!annotationNames.isEmpty()) { + writer.append(asAnnotations(annotationNames)); + } writer.append(Modifier.toString(modifiers & INTERFACE_MASK)); writer.append(" interface ").print(classInfo); Set superinterfaces = classInfo.getDirectSuperinterfaces(); @@ -253,7 +271,7 @@ public class ScanApi extends DefaultTask { } private void writeMethods(PrintWriter writer, List methods) { - Collections.sort(methods); + sort(methods); for (MethodInfo method : methods) { if (isVisible(method.getAccessFlags()) // Only public and protected methods && isValid(method.getAccessFlags(), METHOD_MASK) // Excludes bridge and synthetic methods @@ -264,7 +282,7 @@ public class ScanApi extends DefaultTask { } private void writeFields(PrintWriter output, List fields) { - Collections.sort(fields); + sort(fields); for (FieldInfo field : fields) { if (isVisible(field.getAccessFlags()) && isValid(field.getAccessFlags(), FIELD_MASK)) { output.append(" ").println(field); @@ -286,6 +304,36 @@ public class ScanApi extends DefaultTask { return 0; } + private List toNames(Collection classes) { + return classes.stream() + .map(ClassInfo::toString) + .filter(ScanApi::isApplicationClass) + .collect(toList()); + } + + private Set readClassAnnotationsFor(ClassInfo classInfo) { + Set annotations = new HashSet<>(classInfo.getAnnotations()); + annotations.addAll(selectInheritedAnnotations(classInfo.getSuperclasses())); + annotations.addAll(selectInheritedAnnotations(classInfo.getImplementedInterfaces())); + return annotations; + } + + private Set readInterfaceAnnotationsFor(ClassInfo classInfo) { + Set annotations = new HashSet<>(classInfo.getAnnotations()); + annotations.addAll(selectInheritedAnnotations(classInfo.getSuperinterfaces())); + return annotations; + } + + /** + * Returns those annotations which have themselves been annotated as "Inherited". + */ + private List selectInheritedAnnotations(Collection classes) { + return classes.stream() + .flatMap(cls -> cls.getAnnotations().stream()) + .filter(ann -> ann.hasMetaAnnotation(Inherited.class.getName())) + .collect(toList()); + } + private MethodInfo filterAnnotationsFor(MethodInfo method) { return new MethodInfo( method.getClassName(), @@ -319,6 +367,14 @@ public class ScanApi extends DefaultTask { return items.stream().map(ClassInfo::toString).collect(joining(", ")); } + private static String asAnnotations(Collection items) { + return items.stream().collect(joining(" @", "@", " ")); + } + + private static boolean isApplicationClass(String typeName) { + return !typeName.startsWith("java.") && !typeName.startsWith("kotlin."); + } + private static URL toURL(File file) throws MalformedURLException { return file.toURI().toURL(); } diff --git a/gradle-plugins/cordapp/src/main/kotlin/net/corda/plugins/CordappPlugin.kt b/gradle-plugins/cordapp/src/main/kotlin/net/corda/plugins/CordappPlugin.kt index ce65e18e5f..e307a7b162 100644 --- a/gradle-plugins/cordapp/src/main/kotlin/net/corda/plugins/CordappPlugin.kt +++ b/gradle-plugins/cordapp/src/main/kotlin/net/corda/plugins/CordappPlugin.kt @@ -29,7 +29,7 @@ class CordappPlugin : Plugin { private fun configureCordappJar(project: Project) { // Note: project.afterEvaluate did not have full dependency resolution completed, hence a task is used instead val task = project.task("configureCordappFatJar") - val jarTask = project.tasks.single { it.name == "jar" } as Jar + val jarTask = project.tasks.getByName("jar") as Jar task.doLast { jarTask.from(getDirectNonCordaDependencies(project).map { project.zipTree(it)}).apply { exclude("META-INF/*.SF") @@ -71,6 +71,4 @@ class CordappPlugin : Plugin { } return filteredDeps.map { runtimeConfiguration.files(it) }.flatten().toSet() } - - private fun Project.configuration(name: String): Configuration = configurations.single { it.name == name } } diff --git a/gradle-plugins/cordapp/src/main/kotlin/net/corda/plugins/Utils.kt b/gradle-plugins/cordapp/src/main/kotlin/net/corda/plugins/Utils.kt index 33e552ca7d..d7200ba9f8 100644 --- a/gradle-plugins/cordapp/src/main/kotlin/net/corda/plugins/Utils.kt +++ b/gradle-plugins/cordapp/src/main/kotlin/net/corda/plugins/Utils.kt @@ -1,7 +1,17 @@ package net.corda.plugins import org.gradle.api.Project +import org.gradle.api.Task import org.gradle.api.artifacts.Configuration +import org.gradle.api.plugins.ExtraPropertiesExtension + +/** + * Mimics the "project.ext" functionality in groovy which provides a direct + * accessor to the "ext" extention (See: ExtraPropertiesExtension) + */ +@Suppress("UNCHECKED_CAST") +fun Project.ext(name: String): T = (extensions.findByName("ext") as ExtraPropertiesExtension).get(name) as T +fun Project.configuration(name: String): Configuration = configurations.single { it.name == name } class Utils { companion object { @@ -14,4 +24,5 @@ class Utils { } } } + } \ No newline at end of file diff --git a/gradle-plugins/cordformation/build.gradle b/gradle-plugins/cordformation/build.gradle index 828647d434..94eb8f3b7b 100644 --- a/gradle-plugins/cordformation/build.gradle +++ b/gradle-plugins/cordformation/build.gradle @@ -8,7 +8,6 @@ buildscript { } } -apply plugin: 'groovy' apply plugin: 'kotlin' apply plugin: 'net.corda.plugins.publish-utils' @@ -34,8 +33,8 @@ sourceSets { dependencies { compile gradleApi() - compile localGroovy() compile project(":cordapp") + compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" noderunner "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" diff --git a/gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Cordform.groovy b/gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Cordform.groovy deleted file mode 100644 index 7baf20a45a..0000000000 --- a/gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Cordform.groovy +++ /dev/null @@ -1,153 +0,0 @@ -package net.corda.plugins - -import static org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME -import net.corda.cordform.CordformContext -import net.corda.cordform.CordformDefinition -import org.apache.tools.ant.filters.FixCrLfFilter -import org.gradle.api.DefaultTask -import org.gradle.api.plugins.JavaPluginConvention -import org.gradle.api.tasks.TaskAction -import java.nio.file.Path -import java.nio.file.Paths - -/** - * Creates nodes based on the configuration of this task in the gradle configuration DSL. - * - * See documentation for examples. - */ -class Cordform extends DefaultTask { - /** - * Optionally the name of a CordformDefinition subclass to which all configuration will be delegated. - */ - String definitionClass - protected def directory = Paths.get("build", "nodes") - private def nodes = new ArrayList() - - /** - * Set the directory to install nodes into. - * - * @param directory The directory the nodes will be installed into. - * @return - */ - void directory(String directory) { - this.directory = Paths.get(directory) - } - - /** - * Add a node configuration. - * - * @param configureClosure A node configuration that will be deployed. - */ - void node(Closure configureClosure) { - nodes << (Node) project.configure(new Node(project), configureClosure) - } - - /** - * Returns a node by name. - * - * @param name The name of the node as specified in the node configuration DSL. - * @return A node instance. - */ - private Node getNodeByName(String name) { - for (Node node : nodes) { - if (node.name == name) { - return node - } - } - - return null - } - - /** - * Installs the run script into the nodes directory. - */ - private void installRunScript() { - project.copy { - from Cordformation.getPluginFile(project, "net/corda/plugins/runnodes.jar") - fileMode 0755 - into "${directory}/" - } - - project.copy { - from Cordformation.getPluginFile(project, "net/corda/plugins/runnodes") - // Replaces end of line with lf to avoid issues with the bash interpreter and Windows style line endings. - filter(FixCrLfFilter.class, eol: FixCrLfFilter.CrLf.newInstance("lf")) - fileMode 0755 - into "${directory}/" - } - - project.copy { - from Cordformation.getPluginFile(project, "net/corda/plugins/runnodes.bat") - into "${directory}/" - } - } - - /** - * The definitionClass needn't be compiled until just before our build method, so we load it manually via sourceSets.main.runtimeClasspath. - */ - private CordformDefinition loadCordformDefinition() { - def plugin = project.convention.getPlugin(JavaPluginConvention.class) - def classpath = plugin.sourceSets.getByName(MAIN_SOURCE_SET_NAME).runtimeClasspath - URL[] urls = classpath.files.collect { it.toURI().toURL() } - (CordformDefinition) new URLClassLoader(urls, CordformDefinition.classLoader).loadClass(definitionClass).newInstance() - } - - /** - * This task action will create and install the nodes based on the node configurations added. - */ - @TaskAction - void build() { - initializeConfiguration() - installRunScript() - nodes.each { - it.build() - } - generateNodeInfos() - } - - private initializeConfiguration() { - if (null != definitionClass) { - def cd = loadCordformDefinition() - cd.nodeConfigurers.each { nc -> - node { Node it -> - nc.accept it - it.rootDir directory - } - } - cd.setup new CordformContext() { - Path baseDirectory(String nodeName) { - project.projectDir.toPath().resolve(getNodeByName(nodeName).nodeDir.toPath()) - } - } - } else { - nodes.each { - it.rootDir directory - } - } - } - - Path fullNodePath(Node node) { - return project.projectDir.toPath().resolve(node.nodeDir.toPath()) - } - - private generateNodeInfos() { - nodes.each { Node node -> - def process = new ProcessBuilder("java", "-jar", Node.NODEJAR_NAME, "--just-generate-node-info") - .directory(fullNodePath(node).toFile()) - .redirectErrorStream(true) - .start() - .waitFor() - } - for (source in nodes) { - for (destination in nodes) { - if (source.nodeDir != destination.nodeDir) { - project.copy { - from fullNodePath(source).toString() - include 'nodeInfo-*' - into fullNodePath(destination).resolve(Node.NODE_INFO_DIRECTORY).toString() - } - } - } - } - } -} diff --git a/gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Cordformation.groovy b/gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Cordformation.groovy deleted file mode 100644 index eeb4443801..0000000000 --- a/gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Cordformation.groovy +++ /dev/null @@ -1,28 +0,0 @@ -package net.corda.plugins - -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.api.artifacts.Configuration - -/** - * The Cordformation plugin deploys nodes to a directory in a state ready to be used by a developer for experimentation, - * testing, and debugging. It will prepopulate several fields in the configuration and create a simple node runner. - */ -class Cordformation implements Plugin { - /** - * Gets a resource file from this plugin's JAR file. - * - * @param project The project environment this plugin executes in. - * @param filePathInJar The file in the JAR, relative to root, you wish to access. - * @return A file handle to the file in the JAR. - */ - protected static File getPluginFile(Project project, String filePathInJar) { - return project.rootProject.resources.text.fromArchiveEntry(project.rootProject.buildscript.configurations.classpath.find { - it.name.contains('cordformation') - }, filePathInJar).asFile() - } - - void apply(Project project) { - Utils.createCompileConfiguration("cordapp", project) - } -} diff --git a/gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Node.groovy b/gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Node.groovy deleted file mode 100644 index 8f6dcea295..0000000000 --- a/gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Node.groovy +++ /dev/null @@ -1,268 +0,0 @@ -package net.corda.plugins - -import com.typesafe.config.* -import net.corda.cordform.CordformNode -import org.bouncycastle.asn1.x500.X500Name -import org.bouncycastle.asn1.x500.style.BCStyle -import org.gradle.api.Project -import java.nio.charset.StandardCharsets -import java.nio.file.Files -import java.nio.file.Path - -/** - * Represents a node that will be installed. - */ -class Node extends CordformNode { - static final String NODEJAR_NAME = 'corda.jar' - static final String WEBJAR_NAME = 'corda-webserver.jar' - - /** - * Set the list of CorDapps to install to the cordapps directory. Each cordapp is a fully qualified Maven - * dependency name, eg: com.example:product-name:0.1 - * - * @note Your app will be installed by default and does not need to be included here. - */ - protected List cordapps = [] - - protected File nodeDir - private Project project - - /** - * Sets whether this node will use HTTPS communication. - * - * @param isHttps True if this node uses HTTPS communication. - */ - void https(Boolean isHttps) { - config = config.withValue("useHTTPS", ConfigValueFactory.fromAnyRef(isHttps)) - } - - /** - * Sets the H2 port for this node - */ - void h2Port(Integer h2Port) { - config = config.withValue("h2port", ConfigValueFactory.fromAnyRef(h2Port)) - } - - void useTestClock(Boolean useTestClock) { - config = config.withValue("useTestClock", ConfigValueFactory.fromAnyRef(useTestClock)) - } - - /** - * Set the HTTP web server port for this node. - * - * @param webPort The web port number for this node. - */ - void webPort(Integer webPort) { - config = config.withValue("webAddress", - ConfigValueFactory.fromAnyRef("$DEFAULT_HOST:$webPort".toString())) - } - - /** - * Set the network map address for this node. - * - * @warning This should not be directly set unless you know what you are doing. Use the networkMapName in the - * Cordform task instead. - * @param networkMapAddress Network map node address. - * @param networkMapLegalName Network map node legal name. - */ - void networkMapAddress(String networkMapAddress, String networkMapLegalName) { - def networkMapService = new HashMap() - networkMapService.put("address", networkMapAddress) - networkMapService.put("legalName", networkMapLegalName) - config = config.withValue("networkMapService", ConfigValueFactory.fromMap(networkMapService)) - } - - /** - * Set the SSHD port for this node. - * - * @param sshdPort The SSHD port. - */ - void sshdPort(Integer sshdPort) { - config = config.withValue("sshdAddress", - ConfigValueFactory.fromAnyRef("$DEFAULT_HOST:$sshdPort".toString())) - } - - Node(Project project) { - this.project = project - } - - protected void rootDir(Path rootDir) { - def dirName - try { - X500Name x500Name = new X500Name(name) - dirName = x500Name.getRDNs(BCStyle.O).getAt(0).getFirst().getValue().toString() - } catch(IllegalArgumentException ignore) { - // Can't parse as an X500 name, use the full string - dirName = name - } - nodeDir = new File(rootDir.toFile(), dirName.replaceAll("\\s","")) - } - - protected void build() { - configureProperties() - installCordaJar() - if (config.hasPath("webAddress")) { - installWebserverJar() - } - installBuiltCordapp() - installCordapps() - installConfig() - appendOptionalConfig() - } - - /** - * Get the artemis address for this node. - * - * @return This node's P2P address. - */ - String getP2PAddress() { - return config.getString("p2pAddress") - } - - private void configureProperties() { - config = config.withValue("rpcUsers", ConfigValueFactory.fromIterable(rpcUsers)) - if (notary) { - config = config.withValue("notary", ConfigValueFactory.fromMap(notary)) - } - if (extraConfig) { - config = config.withFallback(ConfigFactory.parseMap(extraConfig)) - } - } - - /** - * Installs the corda fat JAR to the node directory. - */ - private void installCordaJar() { - def cordaJar = verifyAndGetCordaJar() - project.copy { - from cordaJar - into nodeDir - rename cordaJar.name, NODEJAR_NAME - fileMode 0755 - } - } - - /** - * Installs the corda webserver JAR to the node directory - */ - private void installWebserverJar() { - def webJar = verifyAndGetWebserverJar() - project.copy { - from webJar - into nodeDir - rename webJar.name, WEBJAR_NAME - } - } - - /** - * Installs this project's cordapp to this directory. - */ - private void installBuiltCordapp() { - def cordappsDir = new File(nodeDir, "cordapps") - project.copy { - from project.jar - into cordappsDir - } - } - - /** - * Installs other cordapps to this node's cordapps directory. - */ - private void installCordapps() { - def cordappsDir = new File(nodeDir, "cordapps") - def cordapps = getCordappList() - project.copy { - from cordapps - into cordappsDir - } - } - - /** - * Installs the configuration file to this node's directory and detokenises it. - */ - private void installConfig() { - def configFileText = config.root().render(new ConfigRenderOptions(false, false, true, false)).split("\n").toList() - - // Need to write a temporary file first to use the project.copy, which resolves directories correctly. - def tmpDir = new File(project.buildDir, "tmp") - def tmpConfFile = new File(tmpDir, 'node.conf') - Files.write(tmpConfFile.toPath(), configFileText, StandardCharsets.UTF_8) - - project.copy { - from tmpConfFile - into nodeDir - } - } - - /** - * Appends installed config file with properties from an optional file. - */ - private void appendOptionalConfig() { - final configFileProperty = "configFile" - File optionalConfig - if (project.findProperty(configFileProperty)) { //provided by -PconfigFile command line property when running Gradle task - optionalConfig = new File(project.findProperty(configFileProperty)) - } else if (config.hasPath(configFileProperty)) { - optionalConfig = new File(config.getString(configFileProperty)) - } - if (optionalConfig) { - if (!optionalConfig.exists()) { - println "$configFileProperty '$optionalConfig' not found" - } else { - def confFile = new File(project.buildDir.getPath() + "/../" + nodeDir, 'node.conf') - optionalConfig.withInputStream { - input -> confFile << input - } - } - } - } - - /** - * Find the corda JAR amongst the dependencies. - * - * @return A file representing the Corda JAR. - */ - private File verifyAndGetCordaJar() { - def maybeCordaJAR = project.configurations.runtime.filter { - it.toString().contains("corda-${project.corda_release_version}.jar") || it.toString().contains("corda-enterprise-${project.corda_release_version}.jar") - } - if (maybeCordaJAR.size() == 0) { - throw new RuntimeException("No Corda Capsule JAR found. Have you deployed the Corda project to Maven? Looked for \"corda-${project.corda_release_version}.jar\"") - } else { - def cordaJar = maybeCordaJAR.getSingleFile() - assert(cordaJar.isFile()) - return cordaJar - } - } - - /** - * Find the corda JAR amongst the dependencies - * - * @return A file representing the Corda webserver JAR - */ - private File verifyAndGetWebserverJar() { - def maybeJar = project.configurations.runtime.filter { - it.toString().contains("corda-webserver-${project.corda_release_version}.jar") - } - if (maybeJar.size() == 0) { - throw new RuntimeException("No Corda Webserver JAR found. Have you deployed the Corda project to Maven? Looked for \"corda-webserver-${project.corda_release_version}.jar\"") - } else { - def jar = maybeJar.getSingleFile() - assert(jar.isFile()) - return jar - } - } - - /** - * Gets a list of cordapps based on what dependent cordapps were specified. - * - * @return List of this node's cordapps. - */ - private Collection getCordappList() { - // Cordapps can sometimes contain a GString instance which fails the equality test with the Java string - List cordapps = this.cordapps.collect { it.toString() } - return project.configurations.cordapp.files { - cordapps.contains(it.group + ":" + it.name + ":" + it.version) - } - } -} diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt new file mode 100644 index 0000000000..5193703c59 --- /dev/null +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt @@ -0,0 +1,198 @@ +package net.corda.plugins + +import groovy.lang.Closure +import net.corda.cordform.CordformDefinition +import net.corda.cordform.CordformNode +import org.apache.tools.ant.filters.FixCrLfFilter +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.plugins.JavaPluginConvention +import org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME +import org.gradle.api.tasks.TaskAction +import java.io.File +import java.net.URLClassLoader +import java.nio.file.Path +import java.nio.file.Paths +import java.util.concurrent.TimeUnit + +/** + * Creates nodes based on the configuration of this task in the gradle configuration DSL. + * + * See documentation for examples. + */ +@Suppress("unused") +open class Cordform : DefaultTask() { + /** + * Optionally the name of a CordformDefinition subclass to which all configuration will be delegated. + */ + @Suppress("MemberVisibilityCanPrivate") + var definitionClass: String? = null + private var directory = Paths.get("build", "nodes") + private val nodes = mutableListOf() + + /** + * Set the directory to install nodes into. + * + * @param directory The directory the nodes will be installed into. + */ + fun directory(directory: String) { + this.directory = Paths.get(directory) + } + + /** + * Add a node configuration. + * + * @param configureClosure A node configuration that will be deployed. + */ + @Suppress("MemberVisibilityCanPrivate") + fun node(configureClosure: Closure) { + nodes += project.configure(Node(project), configureClosure) as Node + } + + /** + * Add a node configuration + * + * @param configureFunc A node configuration that will be deployed + */ + @Suppress("MemberVisibilityCanPrivate") + fun node(configureFunc: Node.() -> Any?): Node { + val node = Node(project).apply { configureFunc() } + nodes += node + return node + } + + /** + * Returns a node by name. + * + * @param name The name of the node as specified in the node configuration DSL. + * @return A node instance. + */ + private fun getNodeByName(name: String): Node? = nodes.firstOrNull { it.name == name } + + /** + * Installs the run script into the nodes directory. + */ + private fun installRunScript() { + project.copy { + it.apply { + from(Cordformation.getPluginFile(project, "net/corda/plugins/runnodes.jar")) + fileMode = Cordformation.executableFileMode + into("$directory/") + } + } + + project.copy { + it.apply { + from(Cordformation.getPluginFile(project, "net/corda/plugins/runnodes")) + // Replaces end of line with lf to avoid issues with the bash interpreter and Windows style line endings. + filter(mapOf("eol" to FixCrLfFilter.CrLf.newInstance("lf")), FixCrLfFilter::class.java) + fileMode = Cordformation.executableFileMode + into("$directory/") + } + } + + project.copy { + it.apply { + from(Cordformation.getPluginFile(project, "net/corda/plugins/runnodes.bat")) + into("$directory/") + } + } + } + + /** + * The definitionClass needn't be compiled until just before our build method, so we load it manually via sourceSets.main.runtimeClasspath. + */ + private fun loadCordformDefinition(): CordformDefinition { + val plugin = project.convention.getPlugin(JavaPluginConvention::class.java) + val classpath = plugin.sourceSets.getByName(MAIN_SOURCE_SET_NAME).runtimeClasspath + val urls = classpath.files.map { it.toURI().toURL() }.toTypedArray() + return URLClassLoader(urls, CordformDefinition::class.java.classLoader) + .loadClass(definitionClass) + .asSubclass(CordformDefinition::class.java) + .newInstance() + } + + /** + * This task action will create and install the nodes based on the node configurations added. + */ + @Suppress("unused") + @TaskAction + fun build() { + project.logger.info("Running Cordform task") + initializeConfiguration() + installRunScript() + nodes.forEach(Node::build) + generateAndInstallNodeInfos() + } + + private fun initializeConfiguration() { + if (definitionClass != null) { + val cd = loadCordformDefinition() + cd.nodeConfigurers.forEach { + val node = node { } + it.accept(node) + node.rootDir(directory) + } + cd.setup { nodeName -> project.projectDir.toPath().resolve(getNodeByName(nodeName)!!.nodeDir.toPath()) } + } else { + nodes.forEach { + it.rootDir(directory) + } + } + } + + private fun fullNodePath(node: Node): Path = project.projectDir.toPath().resolve(node.nodeDir.toPath()) + + private fun generateAndInstallNodeInfos() { + generateNodeInfos() + installNodeInfos() + } + + private fun generateNodeInfos() { + project.logger.info("Generating node infos") + val generateTimeoutSeconds = 60L + val processes = nodes.map { node -> + project.logger.info("Generating node info for ${fullNodePath(node)}") + val logDir = File(fullNodePath(node).toFile(), "logs") + logDir.mkdirs() // Directory may not exist at this point + Pair(node, ProcessBuilder("java", "-jar", Node.nodeJarName, "--just-generate-node-info") + .directory(fullNodePath(node).toFile()) + .redirectErrorStream(true) + // InheritIO causes hangs on windows due the gradle buffer also not being flushed. + // Must redirect to output or logger (node log is still written, this is just startup banner) + .redirectOutput(File(logDir, "generate-info-log.txt")) + .start()) + } + try { + processes.parallelStream().forEach { (node, process) -> + if (!process.waitFor(generateTimeoutSeconds, TimeUnit.SECONDS)) { + throw GradleException("Node took longer $generateTimeoutSeconds seconds than too to generate node info - see node log at ${fullNodePath(node)}/logs") + } else if (process.exitValue() != 0) { + throw GradleException("Node exited with ${process.exitValue()} when generating node infos - see node log at ${fullNodePath(node)}/logs") + } + } + } finally { + // This will be a no-op on success - abort remaining on failure + processes.forEach { + it.second.destroyForcibly() + } + } + } + + private fun installNodeInfos() { + project.logger.info("Installing node infos") + for (source in nodes) { + for (destination in nodes) { + if (source.nodeDir != destination.nodeDir) { + project.copy { + it.apply { + from(fullNodePath(source).toString()) + include("nodeInfo-*") + into(fullNodePath(destination).resolve(CordformNode.NODE_INFO_DIRECTORY).toString()) + } + } + } + } + } + } +} diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordformation.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordformation.kt new file mode 100644 index 0000000000..b0e09ad2da --- /dev/null +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordformation.kt @@ -0,0 +1,33 @@ +package net.corda.plugins + +import org.gradle.api.Plugin +import org.gradle.api.Project +import java.io.File + +/** + * The Cordformation plugin deploys nodes to a directory in a state ready to be used by a developer for experimentation, + * testing, and debugging. It will prepopulate several fields in the configuration and create a simple node runner. + */ +class Cordformation : Plugin { + internal companion object { + /** + * Gets a resource file from this plugin's JAR file. + * + * @param project The project environment this plugin executes in. + * @param filePathInJar The file in the JAR, relative to root, you wish to access. + * @return A file handle to the file in the JAR. + */ + fun getPluginFile(project: Project, filePathInJar: String): File { + val archive: File? = project.rootProject.buildscript.configurations.single { it.name == "classpath" }.find { + it.name.contains("cordformation") + } + return project.rootProject.resources.text.fromArchiveEntry(archive, filePathInJar).asFile() + } + + val executableFileMode = "0755".toInt(8) + } + + override fun apply(project: Project) { + Utils.createCompileConfiguration("cordapp", project) + } +} diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt new file mode 100644 index 0000000000..b0660a9245 --- /dev/null +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt @@ -0,0 +1,290 @@ +package net.corda.plugins + +import com.typesafe.config.* +import net.corda.cordform.CordformNode +import org.bouncycastle.asn1.x500.X500Name +import org.bouncycastle.asn1.x500.RDN +import org.bouncycastle.asn1.x500.style.BCStyle +import org.gradle.api.Project +import java.io.File +import java.nio.charset.StandardCharsets +import java.nio.file.Files +import java.nio.file.Path + +/** + * Represents a node that will be installed. + */ +class Node(private val project: Project) : CordformNode() { + companion object { + @JvmStatic + val nodeJarName = "corda.jar" + @JvmStatic + val webJarName = "corda-webserver.jar" + private val configFileProperty = "configFile" + } + + /** + * Set the list of CorDapps to install to the plugins directory. Each cordapp is a fully qualified Maven + * dependency name, eg: com.example:product-name:0.1 + * + * @note Your app will be installed by default and does not need to be included here. + * @note Type is any due to gradle's use of "GStrings" - each value will have "toString" called on it + */ + var cordapps = mutableListOf() + + private val releaseVersion = project.rootProject.ext("corda_release_version") + internal lateinit var nodeDir: File + + /** + * Sets whether this node will use HTTPS communication. + * + * @param isHttps True if this node uses HTTPS communication. + */ + fun https(isHttps: Boolean) { + config = config.withValue("useHTTPS", ConfigValueFactory.fromAnyRef(isHttps)) + } + + /** + * Sets the H2 port for this node + */ + fun h2Port(h2Port: Int) { + config = config.withValue("h2port", ConfigValueFactory.fromAnyRef(h2Port)) + } + + fun useTestClock(useTestClock: Boolean) { + config = config.withValue("useTestClock", ConfigValueFactory.fromAnyRef(useTestClock)) + } + + /** + * Set the HTTP web server port for this node. + * + * @param webPort The web port number for this node. + */ + fun webPort(webPort: Int) { + config = config.withValue("webAddress", + ConfigValueFactory.fromAnyRef("$DEFAULT_HOST:$webPort")) + } + + /** + * Set the network map address for this node. + * + * @warning This should not be directly set unless you know what you are doing. Use the networkMapName in the + * Cordform task instead. + * @param networkMapAddress Network map node address. + * @param networkMapLegalName Network map node legal name. + */ + fun networkMapAddress(networkMapAddress: String, networkMapLegalName: String) { + val networkMapService = mutableMapOf() + networkMapService.put("address", networkMapAddress) + networkMapService.put("legalName", networkMapLegalName) + config = config.withValue("networkMapService", ConfigValueFactory.fromMap(networkMapService)) + } + + /** + * Set the SSHD port for this node. + * + * @param sshdPort The SSHD port. + */ + fun sshdPort(sshdPort: Int) { + config = config.withValue("sshdAddress", + ConfigValueFactory.fromAnyRef("$DEFAULT_HOST:$sshdPort")) + } + + internal fun build() { + configureProperties() + installCordaJar() + if (config.hasPath("webAddress")) { + installWebserverJar() + } + installBuiltCordapp() + installCordapps() + installConfig() + appendOptionalConfig() + } + + /** + * Get the artemis address for this node. + * + * @return This node's P2P address. + */ + fun getP2PAddress(): String { + return config.getString("p2pAddress") + } + + internal fun rootDir(rootDir: Path) { + if(name == null) { + project.logger.error("Node has a null name - cannot create node") + throw IllegalStateException("Node has a null name - cannot create node") + } + + val dirName = try { + val o = X500Name(name).getRDNs(BCStyle.O) + if (o.size > 0) { + o.first().first.value.toString() + } else { + name + } + } catch(_ : IllegalArgumentException) { + // Can't parse as an X500 name, use the full string + name + } + nodeDir = File(rootDir.toFile(), dirName.replace("\\s", "")) + } + + private fun configureProperties() { + config = config.withValue("rpcUsers", ConfigValueFactory.fromIterable(rpcUsers)) + if (notary != null) { + config = config.withValue("notary", ConfigValueFactory.fromMap(notary)) + } + if (extraConfig != null) { + config = config.withFallback(ConfigFactory.parseMap(extraConfig)) + } + } + + /** + * Installs the corda fat JAR to the node directory. + */ + private fun installCordaJar() { + val cordaJar = verifyAndGetCordaJar() + project.copy { + it.apply { + from(cordaJar) + into(nodeDir) + rename(cordaJar.name, nodeJarName) + fileMode = Cordformation.executableFileMode + } + } + } + + /** + * Installs the corda webserver JAR to the node directory + */ + private fun installWebserverJar() { + val webJar = verifyAndGetWebserverJar() + project.copy { + it.apply { + from(webJar) + into(nodeDir) + rename(webJar.name, webJarName) + } + } + } + + /** + * Installs this project's cordapp to this directory. + */ + private fun installBuiltCordapp() { + val cordappsDir = File(nodeDir, "cordapps") + project.copy { + it.apply { + from(project.tasks.getByName("jar")) + into(cordappsDir) + } + } + } + + /** + * Installs other cordapps to this node's cordapps directory. + */ + private fun installCordapps() { + val cordappsDir = File(nodeDir, "cordapps") + val cordapps = getCordappList() + project.copy { + it.apply { + from(cordapps) + into(cordappsDir) + } + } + } + + /** + * Installs the configuration file to this node's directory and detokenises it. + */ + private fun installConfig() { + val options = ConfigRenderOptions.defaults().setOriginComments(false).setComments(false).setFormatted(false).setJson(false) + val configFileText = config.root().render(options).split("\n").toList() + + // Need to write a temporary file first to use the project.copy, which resolves directories correctly. + val tmpDir = File(project.buildDir, "tmp") + val tmpConfFile = File(tmpDir, "node.conf") + Files.write(tmpConfFile.toPath(), configFileText, StandardCharsets.UTF_8) + + project.copy { + it.apply { + from(tmpConfFile) + into(nodeDir) + } + } + } + + /** + * Appends installed config file with properties from an optional file. + */ + private fun appendOptionalConfig() { + val optionalConfig: File? = when { + project.findProperty(configFileProperty) != null -> //provided by -PconfigFile command line property when running Gradle task + File(project.findProperty(configFileProperty) as String) + config.hasPath(configFileProperty) -> File(config.getString(configFileProperty)) + else -> null + } + + if (optionalConfig != null) { + if (!optionalConfig.exists()) { + project.logger.error("$configFileProperty '$optionalConfig' not found") + } else { + val confFile = File(project.buildDir.path + "/../" + nodeDir, "node.conf") + confFile.appendBytes(optionalConfig.readBytes()) + } + } + } + + /** + * Find the corda JAR amongst the dependencies. + * + * @return A file representing the Corda JAR. + */ + private fun verifyAndGetCordaJar(): File { + val maybeCordaJAR = project.configuration("runtime").filter { + it.toString().contains("corda-$releaseVersion.jar") || it.toString().contains("corda-enterprise-$releaseVersion.jar") + } + if (maybeCordaJAR.isEmpty) { + throw RuntimeException("No Corda Capsule JAR found. Have you deployed the Corda project to Maven? Looked for \"corda-$releaseVersion.jar\"") + } else { + val cordaJar = maybeCordaJAR.singleFile + assert(cordaJar.isFile) + return cordaJar + } + } + + /** + * Find the corda JAR amongst the dependencies + * + * @return A file representing the Corda webserver JAR + */ + private fun verifyAndGetWebserverJar(): File { + val maybeJar = project.configuration("runtime").filter { + it.toString().contains("corda-webserver-$releaseVersion.jar") + } + if (maybeJar.isEmpty) { + throw RuntimeException("No Corda Webserver JAR found. Have you deployed the Corda project to Maven? Looked for \"corda-webserver-$releaseVersion.jar\"") + } else { + val jar = maybeJar.singleFile + assert(jar.isFile) + return jar + } + } + + /** + * Gets a list of cordapps based on what dependent cordapps were specified. + * + * @return List of this node's cordapps. + */ + private fun getCordappList(): Collection { + // Cordapps can sometimes contain a GString instance which fails the equality test with the Java string + @Suppress("RemoveRedundantCallsOfConversionMethods") + val cordapps: List = cordapps.map { it.toString() } + return project.configuration("cordapp").files { + cordapps.contains(it.group + ":" + it.name + ":" + it.version) + } + } +} diff --git a/node-api/build.gradle b/node-api/build.gradle index be014cdc80..a9384bd4ab 100644 --- a/node-api/build.gradle +++ b/node-api/build.gradle @@ -18,6 +18,8 @@ dependencies { // TODO: Remove this dependency and the code that requires it compile "commons-fileupload:commons-fileupload:$fileupload_version" + compile "net.corda.plugins:cordform-common:$gradle_plugins_version" + // TypeSafe Config: for simple and human friendly config files. compile "com.typesafe:config:$typesafe_config_version" diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/NodeInfoFilesCopier.kt b/node-api/src/main/kotlin/net/corda/nodeapi/NodeInfoFilesCopier.kt new file mode 100644 index 0000000000..aadc49f4c3 --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/NodeInfoFilesCopier.kt @@ -0,0 +1,165 @@ +package net.corda.nodeapi + +import net.corda.cordform.CordformNode +import net.corda.core.internal.ThreadBox +import net.corda.core.internal.createDirectories +import net.corda.core.internal.isRegularFile +import net.corda.core.internal.list +import net.corda.core.utilities.loggerFor +import rx.Observable +import rx.Scheduler +import rx.Subscription +import rx.schedulers.Schedulers +import java.io.IOException +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.StandardCopyOption.COPY_ATTRIBUTES +import java.nio.file.StandardCopyOption.REPLACE_EXISTING +import java.nio.file.attribute.BasicFileAttributes +import java.nio.file.attribute.FileTime +import java.util.concurrent.TimeUnit + +/** + * Utility class which copies nodeInfo files across a set of running nodes. + * + * This class will create paths that it needs to poll and to where it needs to copy files in case those + * don't exist yet. + */ +class NodeInfoFilesCopier(scheduler: Scheduler = Schedulers.io()) : AutoCloseable { + + companion object { + private val log = loggerFor() + const val NODE_INFO_FILE_NAME_PREFIX = "nodeInfo-" + } + + private val nodeDataMapBox = ThreadBox(mutableMapOf()) + /** + * Whether the NodeInfoFilesCopier is closed. When the NodeInfoFilesCopier is closed it will stop polling the + * filesystem and all the public methods except [#close] will throw. + */ + private var closed = false + private val subscription: Subscription + + init { + this.subscription = Observable.interval(5, TimeUnit.SECONDS, scheduler) + .subscribe { poll() } + } + + /** + * @param nodeDir a path to be watched for NodeInfos + * Add a path of a node which is about to be started. + * Its nodeInfo file will be copied to other nodes' additional-node-infos directory, and conversely, + * other nodes' nodeInfo files will be copied to this node additional-node-infos directory. + */ + fun addConfig(nodeDir: Path) { + require(!closed) { "NodeInfoFilesCopier is already closed" } + nodeDataMapBox.locked { + val newNodeFile = NodeData(nodeDir) + put(nodeDir, newNodeFile) + + for (previouslySeenFile in allPreviouslySeenFiles()) { + atomicCopy(previouslySeenFile, newNodeFile.additionalNodeInfoDirectory.resolve(previouslySeenFile.fileName)) + } + log.info("Now watching: $nodeDir") + } + } + + /** + * @param nodeConfig the configuration to be removed. + * Remove the configuration of a node which is about to be stopped or already stopped. + * No files written by that node will be copied to other nodes, nor files from other nodes will be copied to this + * one. + */ + fun removeConfig(nodeDir: Path) { + require(!closed) { "NodeInfoFilesCopier is already closed" } + nodeDataMapBox.locked { + remove(nodeDir) ?: return + log.info("Stopped watching: $nodeDir") + } + } + + fun reset() { + require(!closed) { "NodeInfoFilesCopier is already closed" } + nodeDataMapBox.locked { + clear() + } + } + + /** + * Stops polling the filesystem. + * This function can be called as many times as one wants. + */ + override fun close() { + if (!closed) { + closed = true + subscription.unsubscribe() + } + } + + private fun allPreviouslySeenFiles() = nodeDataMapBox.alreadyLocked { values.flatMap { it.previouslySeenFiles.keys } } + + private fun poll() { + nodeDataMapBox.locked { + for (nodeData in values) { + nodeData.nodeDir.list { paths -> + paths.filter { it.isRegularFile() } + .filter { it.fileName.toString().startsWith(NODE_INFO_FILE_NAME_PREFIX) } + .forEach { path -> processPath(nodeData, path) } + } + } + } + } + + // Takes a path under nodeData config dir and decides whether the file represented by that path needs to + // be copied. + private fun processPath(nodeData: NodeData, path: Path) { + nodeDataMapBox.alreadyLocked { + val newTimestamp = Files.readAttributes(path, BasicFileAttributes::class.java).lastModifiedTime() + val previousTimestamp = nodeData.previouslySeenFiles.put(path, newTimestamp) ?: FileTime.fromMillis(-1) + if (newTimestamp > previousTimestamp) { + for (destination in this.values.filter { it.nodeDir != nodeData.nodeDir }.map { it.additionalNodeInfoDirectory }) { + val fullDestinationPath = destination.resolve(path.fileName) + atomicCopy(path, fullDestinationPath) + } + } + } + } + + private fun atomicCopy(source: Path, destination: Path) { + val tempDestination = try { + Files.createTempFile(destination.parent, "", null) + } catch (exception: IOException) { + log.warn("Couldn't create a temporary file to copy $source", exception) + throw exception + } + try { + // First copy the file to a temporary file within the appropriate directory. + Files.copy(source, tempDestination, COPY_ATTRIBUTES, REPLACE_EXISTING) + } catch (exception: IOException) { + log.warn("Couldn't copy $source to $tempDestination.", exception) + Files.delete(tempDestination) + throw exception + } + try { + // Then rename it to the desired name. This way the file 'appears' on the filesystem as an atomic operation. + Files.move(tempDestination, destination, REPLACE_EXISTING) + } catch (exception: IOException) { + log.warn("Couldn't move $tempDestination to $destination.", exception) + Files.delete(tempDestination) + throw exception + } + } + + /** + * Convenience holder for all the paths and files relative to a single node. + */ + private class NodeData(val nodeDir: Path) { + val additionalNodeInfoDirectory: Path = nodeDir.resolve(CordformNode.NODE_INFO_DIRECTORY) + // Map from Path to its lastModifiedTime. + val previouslySeenFiles = mutableMapOf() + + init { + additionalNodeInfoDirectory.createDirectories() + } + } +} diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/VerifierApi.kt b/node-api/src/main/kotlin/net/corda/nodeapi/VerifierApi.kt index eee653b30f..ca9f03cc07 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/VerifierApi.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/VerifierApi.kt @@ -1,8 +1,8 @@ package net.corda.nodeapi -import net.corda.core.serialization.deserialize -import net.corda.core.serialization.serialize +import net.corda.core.serialization.* import net.corda.core.transactions.LedgerTransaction +import net.corda.core.utilities.sequence import org.apache.activemq.artemis.api.core.SimpleString import org.apache.activemq.artemis.api.core.client.ClientMessage import org.apache.activemq.artemis.reader.MessageUtil @@ -20,12 +20,15 @@ object VerifierApi { val responseAddress: SimpleString ) { companion object { - fun fromClientMessage(message: ClientMessage): VerificationRequest { - return VerificationRequest( + fun fromClientMessage(message: ClientMessage): ObjectWithCompatibleContext { + val bytes = ByteArray(message.bodySize).apply { message.bodyBuffer.readBytes(this) } + val bytesSequence = bytes.sequence() + val (transaction, context) = bytesSequence.deserializeWithCompatibleContext() + val request = VerificationRequest( message.getLongProperty(VERIFICATION_ID_FIELD_NAME), - ByteArray(message.bodySize).apply { message.bodyBuffer.readBytes(this) }.deserialize(), - MessageUtil.getJMSReplyTo(message) - ) + transaction, + MessageUtil.getJMSReplyTo(message)) + return ObjectWithCompatibleContext(request, context) } } @@ -49,11 +52,11 @@ object VerifierApi { } } - fun writeToClientMessage(message: ClientMessage) { + fun writeToClientMessage(message: ClientMessage, context: SerializationContext) { message.putLongProperty(VERIFICATION_ID_FIELD_NAME, verificationId) if (exception != null) { - message.putBytesProperty(RESULT_EXCEPTION_FIELD_NAME, exception.serialize().bytes) + message.putBytesProperty(RESULT_EXCEPTION_FIELD_NAME, exception.serialize(context = context).bytes) } } } -} +} \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/GeneratedAttachment.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/GeneratedAttachment.kt index e42f18ef4b..82601202e6 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/GeneratedAttachment.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/GeneratedAttachment.kt @@ -3,6 +3,6 @@ package net.corda.nodeapi.internal.serialization import net.corda.core.crypto.sha256 import net.corda.core.internal.AbstractAttachment -class GeneratedAttachment(bytes: ByteArray) : AbstractAttachment({ bytes }) { +class GeneratedAttachment(val bytes: ByteArray) : AbstractAttachment({ bytes }) { override val id = bytes.sha256() } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationScheme.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationScheme.kt index 1e7c257a1d..a6be235f88 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationScheme.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationScheme.kt @@ -19,6 +19,7 @@ import net.corda.core.serialization.* import net.corda.core.utilities.ByteSequence import net.corda.core.utilities.OpaqueBytes import net.corda.nodeapi.internal.AttachmentsClassLoader +import org.slf4j.LoggerFactory import java.io.ByteArrayOutputStream import java.io.NotSerializableException import java.util.* @@ -37,7 +38,7 @@ object NotSupportedSerializationScheme : SerializationScheme { override fun serialize(obj: T, context: SerializationContext): SerializedBytes = doThrow() } -data class SerializationContextImpl(override val preferredSerializationVersion: ByteSequence, +data class SerializationContextImpl(override val preferredSerializationVersion: VersionHeader, override val deserializationClassLoader: ClassLoader, override val whitelist: ClassWhitelist, override val properties: Map, @@ -88,36 +89,54 @@ data class SerializationContextImpl(override val preferredSerializationVersion: }) } - override fun withPreferredSerializationVersion(versionHeader: ByteSequence) = copy(preferredSerializationVersion = versionHeader) + override fun withPreferredSerializationVersion(versionHeader: VersionHeader) = copy(preferredSerializationVersion = versionHeader) } private const val HEADER_SIZE: Int = 8 +fun ByteSequence.obtainHeaderSignature(): VersionHeader = take(HEADER_SIZE).copy() + open class SerializationFactoryImpl : SerializationFactory() { private val creator: List = Exception().stackTrace.asList() private val registeredSchemes: MutableCollection = Collections.synchronizedCollection(mutableListOf()) + private val logger = LoggerFactory.getLogger(javaClass) + // TODO: This is read-mostly. Probably a faster implementation to be found. private val schemes: ConcurrentHashMap, SerializationScheme> = ConcurrentHashMap() - private fun schemeFor(byteSequence: ByteSequence, target: SerializationContext.UseCase): SerializationScheme { + private fun schemeFor(byteSequence: ByteSequence, target: SerializationContext.UseCase): Pair { // truncate sequence to 8 bytes, and make sure it's a copy to avoid holding onto large ByteArrays - return schemes.computeIfAbsent(byteSequence.take(HEADER_SIZE).copy() to target) { + val lookupKey = byteSequence.obtainHeaderSignature() to target + val scheme = schemes.computeIfAbsent(lookupKey) { registeredSchemes .filter { scheme -> scheme.canDeserializeVersion(it.first, it.second) } .forEach { return@computeIfAbsent it } + logger.warn("Cannot find serialization scheme for: $lookupKey, registeredSchemes are: $registeredSchemes") NotSupportedSerializationScheme } + return scheme to lookupKey.first } @Throws(NotSerializableException::class) override fun deserialize(byteSequence: ByteSequence, clazz: Class, context: SerializationContext): T { - return asCurrent { withCurrentContext(context) { schemeFor(byteSequence, context.useCase).deserialize(byteSequence, clazz, context) } } + return asCurrent { withCurrentContext(context) { schemeFor(byteSequence, context.useCase).first.deserialize(byteSequence, clazz, context) } } + } + + @Throws(NotSerializableException::class) + override fun deserializeWithCompatibleContext(byteSequence: ByteSequence, clazz: Class, context: SerializationContext): ObjectWithCompatibleContext { + return asCurrent { + withCurrentContext(context) { + val (scheme, versionHeader) = schemeFor(byteSequence, context.useCase) + val deserializedObject = scheme.deserialize(byteSequence, clazz, context) + ObjectWithCompatibleContext(deserializedObject, context.withPreferredSerializationVersion(versionHeader)) + } + } } override fun serialize(obj: T, context: SerializationContext): SerializedBytes { - return asCurrent { withCurrentContext(context) { schemeFor(context.preferredSerializationVersion, context.useCase).serialize(obj, context) } } + return asCurrent { withCurrentContext(context) { schemeFor(context.preferredSerializationVersion, context.useCase).first.serialize(obj, context) } } } fun registerScheme(scheme: SerializationScheme) { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt index adb0c44f0c..cfb505ab6c 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt @@ -18,8 +18,15 @@ import java.util.* import net.corda.nodeapi.internal.serialization.carpenter.Field as CarpenterField import net.corda.nodeapi.internal.serialization.carpenter.Schema as CarpenterSchema -// TODO: get an assigned number as per AMQP spec -const val DESCRIPTOR_TOP_32BITS: Long = 0xc0da0000 +/** + * R3 AMQP assigned enterprise number + * + * see [here](https://www.iana.org/assignments/enterprise-numbers/enterprise-numbers) + * + * Repeated here for brevity: + * 50530 - R3 - Mike Hearn - mike&r3.com + */ +const val DESCRIPTOR_TOP_32BITS: Long = 0xc5620000 const val DESCRIPTOR_DOMAIN: String = "net.corda" diff --git a/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeInfoFilesCopierTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/NodeInfoFilesCopierTest.kt similarity index 73% rename from tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeInfoFilesCopierTest.kt rename to node-api/src/test/kotlin/net/corda/nodeapi/NodeInfoFilesCopierTest.kt index 557bd7095b..562717e641 100644 --- a/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeInfoFilesCopierTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/NodeInfoFilesCopierTest.kt @@ -1,8 +1,6 @@ -package net.corda.demobench.model +package net.corda.nodeapi import net.corda.cordform.CordformNode -import net.corda.core.identity.CordaX500Name -import net.corda.core.utilities.NetworkHostAndPort import net.corda.testing.eventually import org.junit.Before import org.junit.Rule @@ -30,17 +28,13 @@ class NodeInfoFilesCopierTest { private const val NODE_2_PATH = "node2" private val content = "blah".toByteArray(Charsets.UTF_8) - private val GOOD_NODE_INFO_NAME = "nodeInfo-test" - private val GOOD_NODE_INFO_NAME_2 = "nodeInfo-anotherNode" + private val GOOD_NODE_INFO_NAME = "${NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX}test" + private val GOOD_NODE_INFO_NAME_2 = "${NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX}anotherNode" private val BAD_NODE_INFO_NAME = "something" - private val legalName = CordaX500Name(organisation = ORGANIZATION, locality = "Nowhere", country = "GB") - private val hostAndPort = NetworkHostAndPort("localhost", 1) } private fun nodeDir(nodeBaseDir : String) = rootPath.resolve(nodeBaseDir).resolve(ORGANIZATION.toLowerCase()) - private val node1Config by lazy { createConfig(NODE_1_PATH) } - private val node2Config by lazy { createConfig(NODE_2_PATH) } private val node1RootPath by lazy { nodeDir(NODE_1_PATH) } private val node2RootPath by lazy { nodeDir(NODE_2_PATH) } private val node1AdditionalNodeInfoPath by lazy { node1RootPath.resolve(CordformNode.NODE_INFO_DIRECTORY) } @@ -56,7 +50,7 @@ class NodeInfoFilesCopierTest { @Test fun `files created before a node is started are copied to that node`() { // Configure the first node. - nodeInfoFilesCopier.addConfig(node1Config) + nodeInfoFilesCopier.addConfig(node1RootPath) // Ensure directories are created. advanceTime() @@ -65,7 +59,7 @@ class NodeInfoFilesCopierTest { Files.write(node1RootPath.resolve(BAD_NODE_INFO_NAME), content) // Configure the second node. - nodeInfoFilesCopier.addConfig(node2Config) + nodeInfoFilesCopier.addConfig(node2RootPath) advanceTime() eventually(Duration.ofMinutes(1)) { @@ -77,8 +71,8 @@ class NodeInfoFilesCopierTest { @Test fun `polling of running nodes`() { // Configure 2 nodes. - nodeInfoFilesCopier.addConfig(node1Config) - nodeInfoFilesCopier.addConfig(node2Config) + nodeInfoFilesCopier.addConfig(node1RootPath) + nodeInfoFilesCopier.addConfig(node2RootPath) advanceTime() // Create 2 files, one of which to be copied, in a node root path. @@ -95,8 +89,8 @@ class NodeInfoFilesCopierTest { @Test fun `remove nodes`() { // Configure 2 nodes. - nodeInfoFilesCopier.addConfig(node1Config) - nodeInfoFilesCopier.addConfig(node2Config) + nodeInfoFilesCopier.addConfig(node1RootPath) + nodeInfoFilesCopier.addConfig(node2RootPath) advanceTime() // Create a file, in node 2 root path. @@ -104,7 +98,7 @@ class NodeInfoFilesCopierTest { advanceTime() // Remove node 2 - nodeInfoFilesCopier.removeConfig(node2Config) + nodeInfoFilesCopier.removeConfig(node2RootPath) // Create another file in node 2 directory. Files.write(node2RootPath.resolve(GOOD_NODE_INFO_NAME_2), content) @@ -119,8 +113,8 @@ class NodeInfoFilesCopierTest { @Test fun `clear`() { // Configure 2 nodes. - nodeInfoFilesCopier.addConfig(node1Config) - nodeInfoFilesCopier.addConfig(node2Config) + nodeInfoFilesCopier.addConfig(node1RootPath) + nodeInfoFilesCopier.addConfig(node2RootPath) advanceTime() nodeInfoFilesCopier.reset() @@ -142,15 +136,4 @@ class NodeInfoFilesCopierTest { val onlyFileName = Files.list(path).toList().first().fileName.toString() assertEquals(filename, onlyFileName) } - - private fun createConfig(relativePath: String) = - NodeConfigWrapper(rootPath.resolve(relativePath), - NodeConfig(myLegalName = legalName, - p2pAddress = hostAndPort, - rpcAddress = hostAndPort, - webAddress = hostAndPort, - h2port = -1, - notary = null, - networkMapService = null, - rpcUsers = listOf())) } \ No newline at end of file diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderTests.kt index 5fd6b46f21..b2cd36ff1f 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderTests.kt @@ -1,6 +1,6 @@ package net.corda.nodeapi.internal -import com.nhaarman.mockito_kotlin.mock +import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash @@ -17,10 +17,7 @@ import net.corda.nodeapi.DummyContractBackdoor import net.corda.nodeapi.internal.serialization.SerializeAsTokenContextImpl import net.corda.nodeapi.internal.serialization.attachmentsClassLoaderEnabledPropertyName import net.corda.nodeapi.internal.serialization.withTokenContext -import net.corda.testing.DUMMY_NOTARY -import net.corda.testing.MEGA_CORP -import net.corda.testing.TestDependencyInjectionBase -import net.corda.testing.kryoSpecific +import net.corda.testing.* import net.corda.testing.node.MockAttachmentStorage import net.corda.testing.node.MockServices import org.apache.commons.io.IOUtils @@ -41,8 +38,8 @@ class AttachmentsClassLoaderTests : TestDependencyInjectionBase() { private const val ISOLATED_CONTRACT_CLASS_NAME = "net.corda.finance.contracts.isolated.AnotherDummyContract" private fun SerializationContext.withAttachmentStorage(attachmentStorage: AttachmentStorage): SerializationContext { - val serviceHub = mock() - whenever(serviceHub.attachments).thenReturn(attachmentStorage) + val serviceHub = rigorousMock() + doReturn(attachmentStorage).whenever(serviceHub).attachments return this.withServiceHub(serviceHub) } diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolverTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolverTests.kt index e684ef1f29..04f4c69122 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolverTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolverTests.kt @@ -5,15 +5,13 @@ import com.esotericsoftware.kryo.io.Input import com.esotericsoftware.kryo.io.Output import com.esotericsoftware.kryo.util.DefaultClassResolver import com.esotericsoftware.kryo.util.MapReferenceResolver -import com.nhaarman.mockito_kotlin.mock -import com.nhaarman.mockito_kotlin.verify -import com.nhaarman.mockito_kotlin.whenever +import com.nhaarman.mockito_kotlin.* import net.corda.core.node.services.AttachmentStorage import net.corda.core.serialization.* -import net.corda.core.utilities.ByteSequence import net.corda.nodeapi.internal.AttachmentsClassLoader import net.corda.nodeapi.internal.AttachmentsClassLoaderTests import net.corda.testing.node.MockAttachmentStorage +import net.corda.testing.rigorousMock import org.junit.Rule import org.junit.Test import org.junit.rules.ExpectedException @@ -108,16 +106,6 @@ class CordaClassResolverTests { val emptyMapClass = mapOf().javaClass } - val factory: SerializationFactory = object : SerializationFactory() { - override fun deserialize(byteSequence: ByteSequence, clazz: Class, context: SerializationContext): T { - TODO("not implemented") - } - - override fun serialize(obj: T, context: SerializationContext): SerializedBytes { - TODO("not implemented") - } - } - private val emptyWhitelistContext: SerializationContext = SerializationContextImpl(KryoHeaderV0_1, this.javaClass.classLoader, EmptyWhitelist, emptyMap(), true, SerializationContext.UseCase.P2P) private val allButBlacklistedContext: SerializationContext = SerializationContextImpl(KryoHeaderV0_1, this.javaClass.classLoader, AllButBlacklisted, emptyMap(), true, SerializationContext.UseCase.P2P) @@ -252,10 +240,11 @@ class CordaClassResolverTests { @Test fun `Kotlin EmptyList registers as Java emptyList`() { val javaEmptyListClass = Collections.emptyList().javaClass - val kryo = mock() + val kryo = rigorousMock() val resolver = CordaClassResolver(allButBlacklistedContext).apply { setKryo(kryo) } - whenever(kryo.getDefaultSerializer(javaEmptyListClass)).thenReturn(DefaultSerializableSerializer()) - + doReturn(DefaultSerializableSerializer()).whenever(kryo).getDefaultSerializer(javaEmptyListClass) + doReturn(false).whenever(kryo).references + doReturn(false).whenever(kryo).references = any() val registration = resolver.registerImplicit(emptyListClass) assertNotNull(registration) assertEquals(javaEmptyListClass, registration.type) @@ -273,10 +262,11 @@ class CordaClassResolverTests { @Test fun `Kotlin EmptySet registers as Java emptySet`() { val javaEmptySetClass = Collections.emptySet().javaClass - val kryo = mock() + val kryo = rigorousMock() val resolver = CordaClassResolver(allButBlacklistedContext).apply { setKryo(kryo) } - whenever(kryo.getDefaultSerializer(javaEmptySetClass)).thenReturn(DefaultSerializableSerializer()) - + doReturn(DefaultSerializableSerializer()).whenever(kryo).getDefaultSerializer(javaEmptySetClass) + doReturn(false).whenever(kryo).references + doReturn(false).whenever(kryo).references = any() val registration = resolver.registerImplicit(emptySetClass) assertNotNull(registration) assertEquals(javaEmptySetClass, registration.type) @@ -294,10 +284,11 @@ class CordaClassResolverTests { @Test fun `Kotlin EmptyMap registers as Java emptyMap`() { val javaEmptyMapClass = Collections.emptyMap().javaClass - val kryo = mock() + val kryo = rigorousMock() val resolver = CordaClassResolver(allButBlacklistedContext).apply { setKryo(kryo) } - whenever(kryo.getDefaultSerializer(javaEmptyMapClass)).thenReturn(DefaultSerializableSerializer()) - + doReturn(DefaultSerializableSerializer()).whenever(kryo).getDefaultSerializer(javaEmptyMapClass) + doReturn(false).whenever(kryo).references + doReturn(false).whenever(kryo).references = any() val registration = resolver.registerImplicit(emptyMapClass) assertNotNull(registration) assertEquals(javaEmptyMapClass, registration.type) diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SerializationTokenTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SerializationTokenTest.kt index 909f672fde..64b2040642 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SerializationTokenTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SerializationTokenTest.kt @@ -3,10 +3,10 @@ package net.corda.nodeapi.internal.serialization import com.esotericsoftware.kryo.Kryo import com.esotericsoftware.kryo.KryoException import com.esotericsoftware.kryo.io.Output -import com.nhaarman.mockito_kotlin.mock import net.corda.core.serialization.* import net.corda.core.utilities.OpaqueBytes import net.corda.testing.TestDependencyInjectionBase +import net.corda.testing.rigorousMock import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Test @@ -35,8 +35,7 @@ class SerializationTokenTest : TestDependencyInjectionBase() { override fun equals(other: Any?) = other is LargeTokenizable && other.bytes.size == this.bytes.size } - private fun serializeAsTokenContext(toBeTokenized: Any) = SerializeAsTokenContextImpl(toBeTokenized, factory, context, mock()) - + private fun serializeAsTokenContext(toBeTokenized: Any) = SerializeAsTokenContextImpl(toBeTokenized, factory, context, rigorousMock()) @Test fun `write token and read tokenizable`() { val tokenizableBefore = LargeTokenizable() diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt index cdf690029c..9c4a949384 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt @@ -30,6 +30,7 @@ class EvolvabilityTests { // data class C (val a: Int, val b: Int) // val sc = SerializationOutput(sf).serialize(C(A, B)) // f.writeBytes(sc.bytes) + // println (path) // new version of the class, in this case the order of the parameters has been swapped data class C(val b: Int, val a: Int) @@ -54,6 +55,7 @@ class EvolvabilityTests { // data class C (val a: Int, val b: String) // val sc = SerializationOutput(sf).serialize(C(A, B)) // f.writeBytes(sc.bytes) + // println (path) // new version of the class, in this case the order of the parameters has been swapped data class C(val b: String, val a: Int) @@ -78,7 +80,6 @@ class EvolvabilityTests { // val sc = SerializationOutput(sf).serialize(C(A)) // f.writeBytes(sc.bytes) // println ("Path = $path") - data class C(val a: Int, val b: Int?) val sc2 = f.readBytes() @@ -300,9 +301,6 @@ class EvolvabilityTests { val path2 = EvolvabilityTests::class.java.getResource("EvolvabilityTests.multiVersion.2") val path3 = EvolvabilityTests::class.java.getResource("EvolvabilityTests.multiVersion.3") - @Suppress("UNUSED_VARIABLE") - val f = File(path1.toURI()) - val a = 100 val b = 200 val c = 300 @@ -312,14 +310,24 @@ class EvolvabilityTests { // // Version 1: // data class C (val a: Int, val b: Int) + // + // val scc = SerializationOutput(sf).serialize(C(a, b)) + // File(path1.toURI()).writeBytes(scc.bytes) + // println ("Path = $path1") + // // Version 2 - add param c // data class C (val c: Int, val b: Int, val a: Int) + // + // val scc = SerializationOutput(sf).serialize(C(c, b, a)) + // File(path2.toURI()).writeBytes(scc.bytes) + // println ("Path = $path2") + // // Version 3 - add param d // data class C (val b: Int, val c: Int, val d: Int, val a: Int) // // val scc = SerializationOutput(sf).serialize(C(b, c, d, a)) - // f.writeBytes(scc.bytes) - // println ("Path = $path1") + // File(path3.toURI()).writeBytes(scc.bytes) + // println ("Path = $path3") @Suppress("UNUSED") data class C(val e: Int, val c: Int, val b: Int, val a: Int, val d: Int) { @@ -409,14 +417,24 @@ class EvolvabilityTests { // // Version 1: // data class C (val a: Int, val b: Int, val c: Int) + // + // val scc = SerializationOutput(sf).serialize(C(a, b, c)) + // File(path1.toURI()).writeBytes(scc.bytes) + // println ("Path = $path1") + // // Version 2 - add param c // data class C (val b: Int, val c: Int, val d: Int, val e: Int) + // + // val scc = SerializationOutput(sf).serialize(C(b, c, d, e)) + // File(path2.toURI()).writeBytes(scc.bytes) + // println ("Path = $path2") + // // Version 3 - add param d // data class C (val b: Int, val c: Int, val d: Int, val e: Int, val f: Int) // // val scc = SerializationOutput(sf).serialize(C(b, c, d, e, f)) - // File(path1.toURI()).writeBytes(scc.bytes) - // println ("Path = $path1") + // File(path3.toURI()).writeBytes(scc.bytes) + // println ("Path = $path3") @Suppress("UNUSED") data class C(val b: Int, val c: Int, val d: Int, val e: Int, val f: Int, val g: Int) { diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addAdditionalParam b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addAdditionalParam index 06c32d9eff45607d1bf17103d69f1d624f52ba21..e9accefe39961b96b51b5e54340ad82b03a92a81 100644 GIT binary patch delta 58 zcmZo=YGo2g&M!(yWMp7qXaHh{qe&A5OofgnF)$t2#du&lBV05*nF%Pj#~4I@6afI! C6A~@} delta 58 zcmZo=YGo2g&M!(yWMp7qXaHh{1Ggp$m4i!rP%V4oS9eRx{%QTs1X1JL?({_ delta 130 zcmZo;Zetcm&M!(yWMp7qXaHh{1Ggp$crpRWXekg`o(CeI8w1J7GK^9I2W~O291vt* q$e6T{t+=EpGcVoM!NJi9rWY!ij7w4gs550Db7o$N>q15Yphf^At12e| diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltConstructor b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltConstructor index 6daa3c7714a93d9bdc22deece8c77a0b0948c54f..a7e207ac7570921b04699d400c231783cff4597f 100644 GIT binary patch delta 83 zcmbQsG?z&rIlm|+k&%Icp#g{)jwVeMuogO+#K3gm4C8^5jBwG|Qg)yy^8q!ci83k^ d|8j`|c`OG6*cUP;E@aNkD{)=OXyD-J1OVqL7@hzC delta 83 zcmbQsG?z&rIlm|+k&%Icp#g{)4&0h3U@dgu76a3PGmHmLGQve;OWA><%m>t%Cd#Nx d{L3W<8r=W@ diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltConstructorUnAnnotated b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltConstructorUnAnnotated index e47d5270f863e3b84da683b9a81d3b5659d2938f..e712d58560a6edc527cc909c5da1b97046d640f0 100644 GIT binary patch delta 75 zcmZ3%w1P<>Ilm|+k&%Icp#g{)jwVeMuogO+#K3gmI^%&WjBwG|1}31~ePc189LoU# V_Jxdz3z;+XN?aE*8aOyQ0RT%g7asrs delta 75 zcmZ3%w1P<>Ilm|+k&%Icp#g{)4&0h3U@dgu76a3P>x>7kFv3M+8<>D{_l?DXax4b~ V*cUP;E@aNkD{)=OXyD-J1OT2j8D0PY diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructor b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructor index 290fad125aaea3e7124f33638b8fc97fc291de78..87506ae2030620b373c093466c52b7e777c09ea0 100644 GIT binary patch delta 145 zcmZo>Ze|up&M!(yWMp7qXaHh{qe&A5g5{1TF)&?VU|{r~MPnV)(WJ?Y zjK=;zg)9dI*cUP;E@aNkD{)=OXyD-J1XBo=OTr;12-KInkgd3+C^IkJ)d8px0Jlvf AbN~PV delta 145 zcmZo>Ze|up&M!(yWMp7qXaHh{1Ggp$1j`+`#lUodfq_vRD8dM0@PQa`6^(UF2X0Mf zWHj~%Dr7kzz`l?%aUpYNUWw~MMgs>&CzwL0ToMjBL7=|mg>1zoMVWc&t`0zr0Jldc AnE(I) diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructorAndRemoval b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructorAndRemoval index 769ea67e2d69fd636d0493e3a9fa5ff08f793b3b..922eecc33539c7e8c483ad07ad2090138c8a0055 100644 GIT binary patch delta 145 zcmbQtJegS_Ilm|+k&%Icp#g{)jwVeM2$nmV#K3fcfq_vSD8dM0$blGe6^(sNN0TNC zG8+2>6|x)R|lX*0O4&U A)Bpeg delta 145 zcmbQtJegS_Ilm|+k&%Icp#g{)4&0h35G;4#76a1-1_nlTpa>&~AqQf>RW$Z79k?}F zkkQy5sF3A=0Q*A5#D&b6c_pq384Vm9onQ)~a!EMk1cCaJ7qS(X6lLb6yE*_h0s!G> BC;0#X diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.changeSubType b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.changeSubType index 1158e284fe5773c1747e0d43d496368a0d81793d..96e11c7f09fa2c31a82b92521617dbaca74dd69e 100644 GIT binary patch delta 121 zcmaFC@`6PmIlm|+k&%Icp#g{)jwVeM$Y27JRZ<{w8i+lK(OB+i5(CQt0rrKAi3^!C r^GaM7G8#BII>Gcz4rI(=11e!XuyW#pz{vtkyg+S}<(Z7du$hi{hldC diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersion.1 b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersion.1 index 46714457ecb568c3ca2adfa3b814dd0fedfc1797..2cca4802acc3dc849eea285c27201058c1b816fb 100644 GIT binary patch delta 103 zcmZ3+w2Vn0Ilm|+k&%Icp#g{)jwVeMa1c70#K3gm0^@t%Cd#Nx pe9y%MRQX%V2q?yKK!ANAW70zA%)AoUg^UIcj!rOLP`N~8IRFLPCVv0` diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersion.3 b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersion.3 index 6b3e9a3a37306afea3aae8325fb986fd638ef465..1e36577ca927ec4b8c0ad0f9d9a5a54794c47338 100644 GIT binary patch delta 112 zcmeyu^o2bjBwHP3?`u5MPomp9LoU# f_Jxdz3z;+XN?aE*8aOyQ!PG(JlJLkSBg+8*HR&dJ delta 131 zcmcb_bcsnIIlm|+k&%Icp#g{)4&0h3;3{iYx~J$zC7R delta 107 zcmeBR?qC*3&M!(yWMp7qXaHh{1Ggp$_%Z>>cqtHBod+Uc8w1J73XCA3$u^8qMh9*& fupAIzU&xrUkU2B2#C0K~frFzH%mk=hDzY2^s#GIu diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersionWithRemoval.3 b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersionWithRemoval.3 index f8a77054703c2a53b92506918766efe1f7c73876..c1dcb85ab809850fa86c4efbc57ab4bfcb907f05 100644 GIT binary patch delta 157 zcmZ3%2aGBp{ArE%K-pN3n_d6 delta 157 zcmZ3&~VG3fvRW#%=0o8pl y2B~8?Ai%zmF=-)lW?qTwLPi4zM<lXV!SL6j$> dwAj%k29^T?>9eqoqJ(c@l`cZ44wQ3owF&ChIUtgD6i% dX|V&h7+4MnurFjxS;(B3SK_*m(ZIpc2>?sl9=QMj diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.simpleOrderSwapDifferentType b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.simpleOrderSwapDifferentType index 2e6a7098f682d45036947aa23143678a6ab348b2..6d81269d3e1d392570390edef6cda5d01aa3b645 100644 GIT binary patch delta 104 zcmdnaw4F&HIlm|+k&%Icp#g{)jwVeMa1c70#K3gmA>)C2jBwGUTqdC0MPp;29LoU# o_Jxdz3z;+XN?aE*8aOyQ!PEie1c7o%3)zZGiZb)kT^)du0B1uVWB>pF delta 104 zcmdnaw4F&HIlm|+k&%Icp#g{)4&0h3;2?D176a3Phl~gAF~UWYa+!c~7mba9ax4b~ p*cUP;E@aNkD{)=OXyD-J1XBl;69md7Eo3V$Day=CcXa?t0ssY6A{GDu diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.simpleOrderSwapSameType b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.simpleOrderSwapSameType index 6e04f4bdf329090688724dc39dab89427dbdae27..b96b0ce60ecd349703faf4b3e207ec9d54d41f87 100644 GIT binary patch delta 81 zcmZ3-w2nz2Ilm|+k&%Icp#g{)jwVeMuoXI*#K3gmHsgUCjBwHT3?`u5Nn;TCPf84^ Yg5`h!`$EQ~h0K|GC9Vq@4ICVu0QR;S+yDRo delta 81 zcmZ3-w2nz2Ilm|+k&%Icp#g{)4&0h3U@LUs76a3P+l&WpFv3OSGnjyKCyhbmKPfSw Z3YG%`>() for (i in 1..10) { val time = Stopwatch.createStarted().apply { 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 9a6a5c3112..3605ab14a4 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 @@ -1,5 +1,6 @@ package net.corda.node.services +import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever import net.corda.core.contracts.AlwaysAcceptAttachmentConstraint import net.corda.core.contracts.ContractState @@ -29,6 +30,7 @@ import net.corda.testing.contracts.DummyContract import net.corda.testing.dummyCommand import net.corda.testing.getDefaultNotary import net.corda.testing.node.MockNetwork +import net.corda.testing.node.MockNodeParameters import org.junit.After import org.junit.Test import java.nio.file.Paths @@ -56,10 +58,10 @@ class BFTNotaryServiceTests { clusterName) val clusterAddresses = replicaIds.map { NetworkHostAndPort("localhost", 11000 + it * 10) } replicaIds.forEach { replicaId -> - mockNet.createNode(configOverrides = { + mockNet.createNode(MockNodeParameters(configOverrides = { val notary = NotaryConfig(validating = false, bftSMaRt = BFTSMaRtConfiguration(replicaId, clusterAddresses, exposeRaces = exposeRaces)) - whenever(it.notary).thenReturn(notary) - }) + doReturn(notary).whenever(it).notary + })) } mockNet.runNetwork() // Exchange initial network map registration messages. } diff --git a/node/src/integration-test/kotlin/net/corda/node/services/network/NodeInfoWatcherTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/network/NodeInfoWatcherTest.kt index 97e2deb364..d307a02705 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/network/NodeInfoWatcherTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/network/NodeInfoWatcherTest.kt @@ -8,6 +8,7 @@ import net.corda.core.internal.div import net.corda.core.node.NodeInfo import net.corda.core.node.services.KeyManagementService import net.corda.node.services.identity.InMemoryIdentityService +import net.corda.nodeapi.NodeInfoFilesCopier import net.corda.testing.ALICE import net.corda.testing.ALICE_KEY import net.corda.testing.DEV_TRUST_ROOT @@ -42,7 +43,6 @@ class NodeInfoWatcherTest : NodeBasedTest() { lateinit var nodeInfoWatcher: NodeInfoWatcher companion object { - val nodeInfoFileRegex = Regex("nodeInfo\\-.*") val nodeInfo = NodeInfo(listOf(), listOf(getTestPartyAndCertificate(ALICE)), 0, 0) } @@ -56,13 +56,14 @@ class NodeInfoWatcherTest : NodeBasedTest() { @Test fun `save a NodeInfo`() { - assertEquals(0, folder.root.list().filter { it.matches(nodeInfoFileRegex) }.size) + assertEquals(0, + folder.root.list().filter { it.startsWith(NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX) }.size) NodeInfoWatcher.saveToFile(folder.root.toPath(), nodeInfo, keyManagementService) - val nodeInfoFiles = folder.root.list().filter { it.matches(nodeInfoFileRegex) } + val nodeInfoFiles = folder.root.list().filter { it.startsWith(NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX) } assertEquals(1, nodeInfoFiles.size) val fileName = nodeInfoFiles.first() - assertTrue(fileName.matches(nodeInfoFileRegex)) + assertTrue(fileName.startsWith(NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX)) val file = (folder.root.path / fileName).toFile() // Just check that something is written, another tests verifies that the written value can be read back. assertThat(contentOf(file)).isNotEmpty() diff --git a/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt index fae4c25c51..00671b35a9 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt @@ -46,7 +46,7 @@ class PersistentNetworkMapCacheTest : NodeBasedTest() { @Test fun `get nodes by owning key and by name, no network map service`() { val alice = startNodesWithPort(listOf(ALICE), noNetworkMap = true)[0] - val netCache = alice.services.networkMapCache as PersistentNetworkMapCache + val netCache = alice.services.networkMapCache alice.database.transaction { val res = netCache.getNodeByLegalIdentity(alice.info.chooseIdentity()) assertEquals(alice.info, res) @@ -58,7 +58,7 @@ class PersistentNetworkMapCacheTest : NodeBasedTest() { @Test fun `get nodes by address no network map service`() { val alice = startNodesWithPort(listOf(ALICE), noNetworkMap = true)[0] - val netCache = alice.services.networkMapCache as PersistentNetworkMapCache + val netCache = alice.services.networkMapCache alice.database.transaction { val res = netCache.getNodeByAddress(alice.info.addresses[0]) assertEquals(alice.info, res) diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/P2PSecurityTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/P2PSecurityTest.kt index 317252e531..07e396de2b 100644 --- a/node/src/integration-test/kotlin/net/corda/services/messaging/P2PSecurityTest.kt +++ b/node/src/integration-test/kotlin/net/corda/services/messaging/P2PSecurityTest.kt @@ -1,5 +1,6 @@ package net.corda.services.messaging +import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever import net.corda.core.concurrent.CordaFuture import net.corda.core.crypto.random63BitValue @@ -61,8 +62,8 @@ class P2PSecurityTest : NodeBasedTest() { val config = testNodeConfiguration( baseDirectory = baseDirectory(legalName), myLegalName = legalName).also { - whenever(it.networkMapService).thenReturn(NetworkMapInfo(networkMapNode.internals.configuration.p2pAddress, networkMapNode.info.chooseIdentity().name)) - whenever(it.activeMQServer).thenReturn(ActiveMqServerConfiguration(BridgeConfiguration(1001, 2, 3.4))) + doReturn(NetworkMapInfo(networkMapNode.internals.configuration.p2pAddress, networkMapNode.info.chooseIdentity().name)).whenever(it).networkMapService + doReturn(ActiveMqServerConfiguration(BridgeConfiguration(1001, 2, 3.4))).whenever(it).activeMQServer } config.configureWithDevSSLCertificate() // This creates the node's TLS cert with the CN as the legal name return SimpleNode(config, trustRoot = trustRoot).apply { start() } @@ -73,6 +74,6 @@ class P2PSecurityTest : NodeBasedTest() { val nodeInfo = NodeInfo(listOf(MOCK_HOST_AND_PORT), listOf(legalIdentity), 1, serial = 1) val registration = NodeRegistration(nodeInfo, System.currentTimeMillis(), AddOrRemove.ADD, Instant.MAX) val request = RegistrationRequest(registration.toWire(keyService, identity.public), network.myAddress) - return network.sendRequest(NetworkMapService.REGISTER_TOPIC, request, networkMapNode.network.myAddress) + return network.sendRequest(NetworkMapService.REGISTER_TOPIC, request, networkMapNode.network.myAddress) } } diff --git a/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt b/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt index 0e8f062c9c..9a71e5c60f 100644 --- a/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt @@ -7,6 +7,7 @@ import net.corda.core.flows.FlowLogic import net.corda.core.flows.StartableByRPC import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party +import net.corda.core.internal.concurrent.transpose import net.corda.core.messaging.startFlow import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentState @@ -21,37 +22,56 @@ import net.corda.node.services.FlowPermissions import net.corda.nodeapi.User import net.corda.testing.DUMMY_NOTARY import net.corda.testing.chooseIdentity +import net.corda.testing.driver.DriverDSLExposedInterface +import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.driver +import org.junit.Assume import org.junit.Test import java.lang.management.ManagementFactory import javax.persistence.Column import javax.persistence.Entity import javax.persistence.Table import kotlin.test.assertEquals +import kotlin.test.assertNotNull class NodeStatePersistenceTests { @Test fun `persistent state survives node restart`() { + // Temporary disable this test when executed on Windows. It is known to be sporadically failing. + // More investigation is needed to establish why. + Assume.assumeFalse(System.getProperty("os.name").toLowerCase().startsWith("win")) + val user = User("mark", "dadada", setOf(FlowPermissions.startFlowPermission())) val message = Message("Hello world!") driver(isDebug = true, startNodesInProcess = isQuasarAgentSpecified()) { - startNotaryNode(DUMMY_NOTARY.name, validating = false).getOrThrow() - var nodeHandle = startNode(rpcUsers = listOf(user)).getOrThrow() - val nodeName = nodeHandle.nodeInfo.chooseIdentity().name - nodeHandle.rpcClientToNode().start(user.username, user.password).use { - it.proxy.startFlow(::SendMessageFlow, message).returnValue.getOrThrow() - } - nodeHandle.stop().getOrThrow() + val (nodeName, notaryNodeHandle) = { + val notaryNodeHandle = startNotaryNode(DUMMY_NOTARY.name, validating = false).getOrThrow() + val nodeHandle = startNode(rpcUsers = listOf(user)).getOrThrow() + ensureAcquainted(notaryNodeHandle, nodeHandle) + val nodeName = nodeHandle.nodeInfo.chooseIdentity().name + nodeHandle.rpcClientToNode().start(user.username, user.password).use { + it.proxy.startFlow(::SendMessageFlow, message).returnValue.getOrThrow() + } + nodeHandle.stop().getOrThrow() + nodeName to notaryNodeHandle + }() - nodeHandle = startNode(providedName = nodeName, rpcUsers = listOf(user)).getOrThrow() + val nodeHandle = startNode(providedName = nodeName, rpcUsers = listOf(user)).getOrThrow() + ensureAcquainted(notaryNodeHandle, nodeHandle) nodeHandle.rpcClientToNode().start(user.username, user.password).use { val page = it.proxy.vaultQuery(MessageState::class.java) - val retrievedMessage = page.states.singleOrNull()?.state?.data?.message + val stateAndRef = page.states.singleOrNull() + assertNotNull(stateAndRef) + val retrievedMessage = stateAndRef!!.state.data.message assertEquals(message, retrievedMessage) } } } + + private fun DriverDSLExposedInterface.ensureAcquainted(one: NodeHandle, another: NodeHandle) { + listOf(one.pollUntilKnowsAbout(another), another.pollUntilKnowsAbout(one)).transpose().getOrThrow() + } } fun isQuasarAgentSpecified(): Boolean { @@ -95,7 +115,7 @@ object MessageSchemaV1 : MappedSchema( ) : PersistentState() } -val MESSAGE_CONTRACT_PROGRAM_ID = "net.corda.test.node.MessageContract" +const val MESSAGE_CONTRACT_PROGRAM_ID = "net.corda.test.node.MessageContract" open class MessageContract : Contract { override fun verify(tx: LedgerTransaction) { diff --git a/node/src/main/java/CordaCaplet.java b/node/src/main/java/CordaCaplet.java index fa39580fa7..e42e5acd80 100644 --- a/node/src/main/java/CordaCaplet.java +++ b/node/src/main/java/CordaCaplet.java @@ -2,18 +2,73 @@ // must also be in the default package. When using Kotlin there are a whole host of exceptions // trying to construct this from Capsule, so it is written in Java. -import sun.misc.*; +import com.typesafe.config.*; +import sun.misc.Signal; +import sun.misc.SignalHandler; -import java.io.*; -import java.nio.file.*; +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.*; public class CordaCaplet extends Capsule { + private Config nodeConfig = null; + private String baseDir = null; + protected CordaCaplet(Capsule pred) { super(pred); } + private Config parseConfigFile(List args) { + String baseDirOption = getOption(args, "--base-directory"); + this.baseDir = Paths.get((baseDirOption == null) ? "." : baseDirOption).toAbsolutePath().normalize().toString(); + String config = getOption(args, "--config-file"); + File configFile = (config == null) ? new File(baseDir, "node.conf") : new File(config); + try { + ConfigParseOptions parseOptions = ConfigParseOptions.defaults().setAllowMissing(false); + Config defaultConfig = ConfigFactory.parseResources("reference.conf", parseOptions); + Config baseDirectoryConfig = ConfigFactory.parseMap(Collections.singletonMap("baseDirectory", baseDir)); + Config nodeConfig = ConfigFactory.parseFile(configFile, parseOptions); + return baseDirectoryConfig.withFallback(nodeConfig).withFallback(defaultConfig).resolve(); + } catch (ConfigException e) { + log(LOG_QUIET, e); + return ConfigFactory.empty(); + } + } + + private String getOption(List args, String option) { + final String lowerCaseOption = option.toLowerCase(); + int index = 0; + for (String arg : args) { + if (arg.toLowerCase().equals(lowerCaseOption)) { + if (index < args.size() - 1) { + return args.get(index + 1); + } else { + return null; + } + } + index++; + } + return null; + } + + @Override + protected ProcessBuilder prelaunch(List jvmArgs, List args) { + nodeConfig = parseConfigFile(args); + return super.prelaunch(jvmArgs, args); + } + + // Add working directory variable to capsules string replacement variables. + @Override + protected String getVarValue(String var) { + if (var.equals("baseDirectory")) { + return baseDir; + } else { + return super.getVarValue(var); + } + } + /** * Overriding the Caplet classpath generation via the intended interface in Capsule. */ @@ -25,18 +80,55 @@ public class CordaCaplet extends Capsule { if (ATTR_APP_CLASS_PATH == attr) { T cp = super.attribute(attr); - (new File("cordapps")).mkdir(); - augmentClasspath((List) cp, "cordapps"); - augmentClasspath((List) cp, "plugins"); + (new File(baseDir, "cordapps")).mkdir(); + augmentClasspath((List) cp, new File(baseDir, "cordapps")); + augmentClasspath((List) cp, new File(baseDir, "plugins")); + // Add additional directories of JARs to the classpath (at the end). e.g. for JDBC drivers + try { + List jarDirs = nodeConfig.getStringList("jarDirs"); + log(LOG_VERBOSE, "Configured JAR directories = " + jarDirs); + for (String jarDir : jarDirs) { + augmentClasspath((List) cp, new File(jarDir)); + } + } catch (ConfigException.Missing e) { + // Ignore since it's ok to be Missing. Other errors would be unexpected. + } catch (ConfigException e) { + log(LOG_QUIET, e); + } return cp; - } - return super.attribute(attr); + } else if (ATTR_JVM_ARGS == attr) { + // Read JVM args from the config if specified, else leave alone. + List jvmArgs = new ArrayList<>((List) super.attribute(attr)); + try { + List configJvmArgs = nodeConfig.getStringList("jvmArgs"); + jvmArgs.clear(); + jvmArgs.addAll(configJvmArgs); + log(LOG_VERBOSE, "Configured JVM args = " + jvmArgs); + } catch (ConfigException.Missing e) { + // Ignore since it's ok to be Missing. Other errors would be unexpected. + } catch (ConfigException e) { + log(LOG_QUIET, e); + } + return (T) jvmArgs; + } else if (ATTR_SYSTEM_PROPERTIES == attr) { + // Add system properties, if specified, from the config. + Map systemProps = new LinkedHashMap<>((Map) super.attribute(attr)); + try { + Config overrideSystemProps = nodeConfig.getConfig("systemProperties"); + log(LOG_VERBOSE, "Configured system properties = " + overrideSystemProps); + for (Map.Entry entry : overrideSystemProps.entrySet()) { + systemProps.put(entry.getKey(), entry.getValue().unwrapped().toString()); + } + } catch (ConfigException.Missing e) { + // Ignore since it's ok to be Missing. Other errors would be unexpected. + } catch (ConfigException e) { + log(LOG_QUIET, e); + } + return (T) systemProps; + } else return super.attribute(attr); } - // TODO: Make directory configurable via the capsule manifest. - // TODO: Add working directory variable to capsules string replacement variables. - private void augmentClasspath(List classpath, String dirName) { - File dir = new File(dirName); + private void augmentClasspath(List classpath, File dir) { if (dir.exists()) { File[] files = dir.listFiles(); for (File file : files) { 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 16829873aa..f7adde2604 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -11,26 +11,23 @@ import net.corda.core.flows.* import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate -import net.corda.core.internal.VisibleForTesting -import net.corda.core.internal.cert +import net.corda.core.internal.* import net.corda.core.internal.concurrent.doneFuture import net.corda.core.internal.concurrent.flatMap import net.corda.core.internal.concurrent.openFuture -import net.corda.core.internal.toX509CertHolder -import net.corda.core.internal.uncheckedCast import net.corda.core.messaging.* import net.corda.core.node.AppServiceHub import net.corda.core.node.NodeInfo import net.corda.core.node.ServiceHub import net.corda.core.node.StateLoader import net.corda.core.node.services.* -import net.corda.core.node.services.NetworkMapCache.MapChange import net.corda.core.serialization.SerializationWhitelist import net.corda.core.serialization.SerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.debug +import net.corda.core.utilities.getOrThrow import net.corda.node.VersionInfo import net.corda.node.internal.classloading.requireAnnotation import net.corda.node.internal.cordapp.CordappLoader @@ -78,6 +75,7 @@ import java.security.cert.CertificateFactory import java.security.cert.X509Certificate import java.sql.Connection import java.time.Clock +import java.util.* import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ExecutorService import java.util.concurrent.TimeUnit.SECONDS @@ -108,7 +106,7 @@ abstract class AbstractNode(config: NodeConfiguration, private class StartedNodeImpl( override val internals: N, - override val services: ServiceHubInternalImpl, + services: ServiceHubInternalImpl, override val info: NodeInfo, override val checkpointStorage: CheckpointStorage, override val smm: StateMachineManager, @@ -116,8 +114,11 @@ abstract class AbstractNode(config: NodeConfiguration, override val inNodeNetworkMapService: NetworkMapService, override val network: MessagingService, override val database: CordaPersistence, - override val rpcOps: CordaRPCOps) : StartedNode - + override val rpcOps: CordaRPCOps, + flowStarter: FlowStarter, + internal val schedulerService: NodeSchedulerService) : StartedNode { + override val services: StartedNodeServices = object : StartedNodeServices, ServiceHubInternal by services, FlowStarter by flowStarter {} + } // TODO: Persist this, as well as whether the node is registered. /** * Sequence number of changes sent to the network map service, when registering/de-registering this node. @@ -138,10 +139,12 @@ abstract class AbstractNode(config: NodeConfiguration, protected val services: ServiceHubInternal get() = _services private lateinit var _services: ServiceHubInternalImpl protected lateinit var legalIdentity: PartyAndCertificate + private lateinit var allIdentities: List protected lateinit var info: NodeInfo protected var myNotaryIdentity: PartyAndCertificate? = null protected lateinit var checkpointStorage: CheckpointStorage protected lateinit var smm: StateMachineManager + private lateinit var tokenizableServices: List protected lateinit var attachments: NodeAttachmentService protected lateinit var inNodeNetworkMapService: NetworkMapService protected lateinit var network: MessagingService @@ -167,8 +170,8 @@ abstract class AbstractNode(config: NodeConfiguration, @Volatile private var _started: StartedNode? = null /** The implementation of the [CordaRPCOps] interface used by this node. */ - open fun makeRPCOps(): CordaRPCOps { - return CordaRPCOpsImpl(services, smm, database) + open fun makeRPCOps(flowStarter: FlowStarter): CordaRPCOps { + return CordaRPCOpsImpl(services, smm, database, flowStarter) } private fun saveOwnNodeInfo() { @@ -190,7 +193,8 @@ abstract class AbstractNode(config: NodeConfiguration, log.info("Generating nodeInfo ...") val schemaService = makeSchemaService() initialiseDatabasePersistence(schemaService) { - makeServices(schemaService) + val transactionStorage = makeTransactionStorage() + makeServices(schemaService, transactionStorage, StateLoaderImpl(transactionStorage)) saveOwnNodeInfo() } } @@ -202,17 +206,13 @@ abstract class AbstractNode(config: NodeConfiguration, val schemaService = makeSchemaService() // Do all of this in a database transaction so anything that might need a connection has one. val startedImpl = initialiseDatabasePersistence(schemaService) { - val tokenizableServices = makeServices(schemaService) + val transactionStorage = makeTransactionStorage() + val stateLoader = StateLoaderImpl(transactionStorage) + val services = makeServices(schemaService, transactionStorage, stateLoader) saveOwnNodeInfo() - smm = StateMachineManager(services, - checkpointStorage, - serverThread, - database, - busyNodeLatch, - cordappLoader.appClassLoader) - - smm.tokenizableServices.addAll(tokenizableServices) - + smm = makeStateMachineManager() + val flowStarter = FlowStarterImpl(serverThread, smm) + val schedulerService = NodeSchedulerService(platformClock, this@AbstractNode.database, flowStarter, stateLoader, unfinishedSchedules = busyNodeLatch, serverThread = serverThread) if (serverThread is ExecutorService) { runOnStop += { // We wait here, even though any in-flight messages should have been drained away because the @@ -221,48 +221,60 @@ abstract class AbstractNode(config: NodeConfiguration, MoreExecutors.shutdownAndAwaitTermination(serverThread as ExecutorService, 50, SECONDS) } } - - makeVaultObservers() - - val rpcOps = makeRPCOps() + makeVaultObservers(schedulerService) + val rpcOps = makeRPCOps(flowStarter) startMessagingService(rpcOps) installCoreFlows() - - installCordaServices() + val cordaServices = installCordaServices(flowStarter) + tokenizableServices = services + cordaServices + schedulerService registerCordappFlows() _services.rpcFlows += cordappLoader.cordapps.flatMap { it.rpcFlows } FlowLogicRefFactoryImpl.classloader = cordappLoader.appClassLoader runOnStop += network::stop - StartedNodeImpl(this, _services, info, checkpointStorage, smm, attachments, inNodeNetworkMapService, network, database, rpcOps) + StartedNodeImpl(this, _services, info, checkpointStorage, smm, attachments, inNodeNetworkMapService, network, database, rpcOps, flowStarter, schedulerService) } // If we successfully loaded network data from database, we set this future to Unit. _nodeReadyFuture.captureLater(registerWithNetworkMapIfConfigured()) return startedImpl.apply { database.transaction { - smm.start() + smm.start(tokenizableServices) // Shut down the SMM so no Fibers are scheduled. runOnStop += { smm.stop(acceptableLiveFiberCountOnStop()) } - services.schedulerService.start() + schedulerService.start() } _started = this } } + protected open fun makeStateMachineManager(): StateMachineManager { + return StateMachineManagerImpl( + services, + checkpointStorage, + serverThread, + database, + busyNodeLatch, + cordappLoader.appClassLoader + ) + } + private class ServiceInstantiationException(cause: Throwable?) : CordaException("Service Instantiation Error", cause) - private fun installCordaServices() { + private fun installCordaServices(flowStarter: FlowStarter): List { val loadedServices = cordappLoader.cordapps.flatMap { it.services } - filterServicesToInstall(loadedServices).forEach { + return filterServicesToInstall(loadedServices).mapNotNull { try { - installCordaService(it) + installCordaService(flowStarter, it) } catch (e: NoSuchMethodException) { log.error("${it.name}, as a Corda service, must have a constructor with a single parameter of type " + ServiceHub::class.java.name) + null } catch (e: ServiceInstantiationException) { log.error("Corda service ${it.name} failed to instantiate", e.cause) + null } catch (e: Exception) { log.error("Unable to install Corda service ${it.name}", e) + null } } } @@ -274,8 +286,7 @@ abstract class AbstractNode(config: NodeConfiguration, require(customNotaryServiceList.size == 1) { "Attempting to install more than one notary service: ${customNotaryServiceList.joinToString()}" } - } - else return loadedServices - customNotaryServiceList + } else return loadedServices - customNotaryServiceList } return loadedServices } @@ -289,7 +300,7 @@ abstract class AbstractNode(config: NodeConfiguration, /** * This customizes the ServiceHub for each CordaService that is initiating flows */ - private class AppServiceHubImpl(val serviceHub: ServiceHubInternal) : AppServiceHub, ServiceHub by serviceHub { + private class AppServiceHubImpl(private val serviceHub: ServiceHub, private val flowStarter: FlowStarter) : AppServiceHub, ServiceHub by serviceHub { lateinit var serviceInstance: T override fun startTrackedFlow(flow: FlowLogic): FlowProgressHandle { val stateMachine = startFlowChecked(flow) @@ -305,38 +316,28 @@ abstract class AbstractNode(config: NodeConfiguration, return FlowHandleImpl(id = stateMachine.id, returnValue = stateMachine.resultFuture) } - private fun startFlowChecked(flow: FlowLogic): FlowStateMachineImpl { + 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 serviceHub.startFlow(flow, currentUser) + return flowStarter.startFlow(flow, currentUser).getOrThrow() } override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is AppServiceHubImpl<*>) return false - - if (serviceHub != other.serviceHub) return false - if (serviceInstance != other.serviceInstance) return false - - return true + return serviceHub == other.serviceHub + && flowStarter == other.flowStarter + && serviceInstance == other.serviceInstance } - override fun hashCode(): Int { - var result = serviceHub.hashCode() - result = 31 * result + serviceInstance.hashCode() - return result - } + override fun hashCode() = Objects.hash(serviceHub, flowStarter, serviceInstance) } - /** - * Use this method to install your Corda services in your tests. This is automatically done by the node when it - * starts up for all classes it finds which are annotated with [CordaService]. - */ - fun installCordaService(serviceClass: Class): T { + private fun installCordaService(flowStarter: FlowStarter, serviceClass: Class): T { serviceClass.requireAnnotation() val service = try { - val serviceContext = AppServiceHubImpl(services) + val serviceContext = AppServiceHubImpl(services, flowStarter) if (isNotaryService(serviceClass)) { check(myNotaryIdentity != null) { "Trying to install a notary service but no notary identity specified" } val constructor = serviceClass.getDeclaredConstructor(AppServiceHub::class.java, PublicKey::class.java).apply { isAccessible = true } @@ -357,7 +358,6 @@ abstract class AbstractNode(config: NodeConfiguration, throw ServiceInstantiationException(e.cause) } cordappServices.putInstance(serviceClass, service) - smm.tokenizableServices += service if (service is NotaryService) handleCustomNotaryService(service) @@ -365,6 +365,12 @@ abstract class AbstractNode(config: NodeConfiguration, return service } + fun findTokenizableService(clazz: Class): T? { + return tokenizableServices.firstOrNull { clazz.isAssignableFrom(it.javaClass) }?.let { uncheckedCast(it) } + } + + inline fun findTokenizableService() = findTokenizableService(T::class.java) + private fun handleCustomNotaryService(service: NotaryService) { runOnStop += service::stop service.start() @@ -467,19 +473,22 @@ abstract class AbstractNode(config: NodeConfiguration, * Builds node internal, advertised, and plugin services. * Returns a list of tokenizable services to be added to the serialisation context. */ - private fun makeServices(schemaService: SchemaService): MutableList { + private fun makeServices(schemaService: SchemaService, transactionStorage: WritableTransactionStorage, stateLoader: StateLoader): MutableList { checkpointStorage = DBCheckpointStorage() - val transactionStorage = makeTransactionStorage() val metrics = MetricRegistry() attachments = NodeAttachmentService(metrics) val cordappProvider = CordappProviderImpl(cordappLoader, attachments) - _services = ServiceHubInternalImpl(schemaService, transactionStorage, StateLoaderImpl(transactionStorage), MonitoringService(metrics), cordappProvider) + _services = ServiceHubInternalImpl(schemaService, transactionStorage, stateLoader, MonitoringService(metrics), cordappProvider) legalIdentity = obtainIdentity(notaryConfig = null) + // TODO We keep only notary identity as additional legalIdentity if we run it on a node . Multiple identities need more design thinking. + myNotaryIdentity = getNotaryIdentity() + allIdentities = listOf(legalIdentity, myNotaryIdentity).filterNotNull() network = makeMessagingService(legalIdentity) - info = makeInfo(legalIdentity) + val addresses = myAddresses() // TODO There is no support for multiple IP addresses yet. + info = NodeInfo(addresses, allIdentities, versionInfo.platformVersion, platformClock.instant().toEpochMilli()) val networkMapCache = services.networkMapCache val tokenizableServices = mutableListOf(attachments, network, services.vaultService, - services.keyManagementService, services.identityService, platformClock, services.schedulerService, + services.keyManagementService, services.identityService, platformClock, services.auditService, services.monitoringService, networkMapCache, services.schemaService, services.transactionVerifierService, services.validatedTransactions, services.contractUpgradeService, services, cordappProvider, this) @@ -489,19 +498,10 @@ abstract class AbstractNode(config: NodeConfiguration, protected open fun makeTransactionStorage(): WritableTransactionStorage = DBTransactionStorage() - private fun makeVaultObservers() { - VaultSoftLockManager(services.vaultService, smm) - ScheduledActivityObserver(services) - HibernateObserver(services.vaultService.rawUpdates, services.database.hibernateConfig) - } - - private fun makeInfo(legalIdentity: PartyAndCertificate): NodeInfo { - // TODO We keep only notary identity as additional legalIdentity if we run it on a node . Multiple identities need more design thinking. - myNotaryIdentity = getNotaryIdentity() - val allIdentitiesList = mutableListOf(legalIdentity) - myNotaryIdentity?.let { allIdentitiesList.add(it) } - val addresses = myAddresses() // TODO There is no support for multiple IP addresses yet. - return NodeInfo(addresses, allIdentitiesList, versionInfo.platformVersion, platformClock.instant().toEpochMilli()) + private fun makeVaultObservers(schedulerService: SchedulerService) { + VaultSoftLockManager.install(services.vaultService, smm) + ScheduledActivityObserver.install(services.vaultService, schedulerService) + HibernateObserver.install(services.vaultService.rawUpdates, database.hibernateConfig) } /** @@ -553,8 +553,16 @@ abstract class AbstractNode(config: NodeConfiguration, } } + private fun setupInNodeNetworkMapService(networkMapCache: NetworkMapCacheInternal) { + inNodeNetworkMapService = + if (configuration.networkMapService == null && !configuration.noNetworkMapServiceMode) + makeNetworkMapService(network, networkMapCache) + else + NullNetworkMapService + } + private fun makeNetworkServices(network: MessagingService, networkMapCache: NetworkMapCacheInternal, tokenizableServices: MutableList) { - inNodeNetworkMapService = if (configuration.networkMapService == null) makeNetworkMapService(network, networkMapCache) else NullNetworkMapService + setupInNodeNetworkMapService(networkMapCache) configuration.notary?.let { val notaryService = makeCoreNotaryService(it) tokenizableServices.add(notaryService) @@ -613,7 +621,7 @@ abstract class AbstractNode(config: NodeConfiguration, /** This is overriden by the mock node implementation to enable operation without any network map service */ protected open fun noNetworkMapConfigured(): CordaFuture { - if (services.networkMapCache.loadDBSuccess) { + if (services.networkMapCache.loadDBSuccess || configuration.noNetworkMapServiceMode) { return doneFuture(Unit) } else { // TODO: There should be a consistent approach to configuration error exceptions. @@ -664,17 +672,7 @@ abstract class AbstractNode(config: NodeConfiguration, val caCertificates: Array = listOf(legalIdentity.certificate, clientCa?.certificate?.cert) .filterNotNull() .toTypedArray() - val service = PersistentIdentityService(info.legalIdentitiesAndCerts, trustRoot = trustRoot, caCertificates = *caCertificates) - services.networkMapCache.allNodes.forEach { it.legalIdentitiesAndCerts.forEach { service.verifyAndRegisterIdentity(it) } } - services.networkMapCache.changed.subscribe { mapChange -> - // TODO how should we handle network map removal - if (mapChange is MapChange.Added) { - mapChange.node.legalIdentitiesAndCerts.forEach { - service.verifyAndRegisterIdentity(it) - } - } - } - return service + return PersistentIdentityService(allIdentities, trustRoot = trustRoot, caCertificates = *caCertificates) } protected abstract fun makeTransactionVerifierService(): TransactionVerifierService @@ -691,6 +689,7 @@ abstract class AbstractNode(config: NodeConfiguration, toRun() } runOnStop.clear() + _started = null } protected abstract fun makeMessagingService(legalIdentity: PartyAndCertificate): MessagingService @@ -758,6 +757,9 @@ abstract class AbstractNode(config: NodeConfiguration, } protected open fun generateKeyPair() = cryptoGenerateKeyPair() + protected open fun makeVaultService(keyManagementService: KeyManagementService, stateLoader: StateLoader): VaultServiceInternal { + return NodeVaultService(platformClock, keyManagementService, stateLoader, database.hibernateConfig) + } private inner class ServiceHubInternalImpl( override val schemaService: SchemaService, @@ -770,15 +772,14 @@ abstract class AbstractNode(config: NodeConfiguration, override val stateMachineRecordedTransactionMapping = DBTransactionMappingStorage() override val auditService = DummyAuditService() override val transactionVerifierService by lazy { makeTransactionVerifierService() } - override val networkMapCache by lazy { PersistentNetworkMapCache(this) } - override val vaultService by lazy { NodeVaultService(platformClock, keyManagementService, stateLoader, this@AbstractNode.database.hibernateConfig) } + override val networkMapCache by lazy { NetworkMapCacheImpl(PersistentNetworkMapCache(this@AbstractNode.database, this@AbstractNode.configuration), identityService) } + override val vaultService by lazy { makeVaultService(keyManagementService, stateLoader) } override val contractUpgradeService by lazy { ContractUpgradeServiceImpl() } // Place the long term identity key in the KMS. Eventually, this is likely going to be separated again because // the KMS is meant for derived temporary keys used in transactions, and we're not supposed to sign things with // the identity key. But the infrastructure to make that easy isn't here yet. override val keyManagementService by lazy { makeKeyManagementService(identityService) } - override val schedulerService by lazy { NodeSchedulerService(this, unfinishedSchedules = busyNodeLatch, serverThread = serverThread) } override val identityService by lazy { val trustStore = KeyStoreWrapper(configuration.trustStoreFile, configuration.trustStorePassword) val caKeyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword) @@ -798,10 +799,6 @@ abstract class AbstractNode(config: NodeConfiguration, return cordappServices.getInstance(type) ?: throw IllegalArgumentException("Corda service ${type.name} does not exist") } - override fun startFlow(logic: FlowLogic, flowInitiator: FlowInitiator, ourIdentity: Party?): FlowStateMachineImpl { - return serverThread.fetchFrom { smm.add(logic, flowInitiator, ourIdentity) } - } - override fun getFlowFactory(initiatingFlowClass: Class>): InitiatedFlowFactory<*>? { return flowFactories[initiatingFlowClass] } @@ -815,3 +812,9 @@ abstract class AbstractNode(config: NodeConfiguration, override fun jdbcSession(): Connection = database.createSession() } } + +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) } + } +} 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 ee685466d3..863835c25a 100644 --- a/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt +++ b/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt @@ -10,6 +10,7 @@ import net.corda.core.flows.StartableByRPC import net.corda.core.identity.AbstractParty import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party +import net.corda.core.internal.FlowStateMachine import net.corda.core.messaging.* import net.corda.core.node.NodeInfo import net.corda.core.node.services.NetworkMapCache @@ -18,11 +19,12 @@ import net.corda.core.node.services.vault.PageSpecification import net.corda.core.node.services.vault.QueryCriteria import net.corda.core.node.services.vault.Sort import net.corda.core.transactions.SignedTransaction +import net.corda.core.utilities.getOrThrow import net.corda.node.services.FlowPermissions.Companion.startFlowPermission +import net.corda.node.services.api.FlowStarter import net.corda.node.services.api.ServiceHubInternal import net.corda.node.services.messaging.getRpcContext import net.corda.node.services.messaging.requirePermission -import net.corda.node.services.statemachine.FlowStateMachineImpl import net.corda.node.services.statemachine.StateMachineManager import net.corda.node.utilities.CordaPersistence import rx.Observable @@ -37,7 +39,8 @@ import java.time.Instant class CordaRPCOpsImpl( private val services: ServiceHubInternal, private val smm: StateMachineManager, - private val database: CordaPersistence + private val database: CordaPersistence, + private val flowStarter: FlowStarter ) : CordaRPCOps { override fun networkMapSnapshot(): List { val (snapshot, updates) = networkMapFeed() @@ -92,7 +95,7 @@ class CordaRPCOpsImpl( return database.transaction { val (allStateMachines, changes) = smm.track() DataFeed( - allStateMachines.map { stateMachineInfoFromFlowLogic(it.logic) }, + allStateMachines.map { stateMachineInfoFromFlowLogic(it) }, changes.map { stateMachineUpdateFromStateMachineChange(it) } ) } @@ -144,13 +147,13 @@ class CordaRPCOpsImpl( return FlowHandleImpl(id = stateMachine.id, returnValue = stateMachine.resultFuture) } - private fun startFlow(logicType: Class>, args: Array): FlowStateMachineImpl { + private fun startFlow(logicType: Class>, args: Array): FlowStateMachine { require(logicType.isAnnotationPresent(StartableByRPC::class.java)) { "${logicType.name} was not designed for RPC" } val rpcContext = getRpcContext() rpcContext.requirePermission(startFlowPermission(logicType)) val currentUser = FlowInitiator.RPC(rpcContext.currentUser.username) // TODO RPC flows should have mapping user -> identity that should be resolved automatically on starting flow. - return services.invokeFlowAsync(logicType, currentUser, *args) + return flowStarter.invokeFlowAsync(logicType, currentUser, *args).getOrThrow() } override fun attachmentExists(id: SecureHash): Boolean { diff --git a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt index 3bb63727da..1a52cbcadc 100644 --- a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt +++ b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt @@ -84,6 +84,7 @@ open class NodeStartup(val args: Array) { exitProcess(1) } + logger.info("Node exiting successfully") exitProcess(0) } diff --git a/node/src/main/kotlin/net/corda/node/internal/StartedNode.kt b/node/src/main/kotlin/net/corda/node/internal/StartedNode.kt index e0320d9c62..71b80d5aea 100644 --- a/node/src/main/kotlin/net/corda/node/internal/StartedNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/StartedNode.kt @@ -7,9 +7,11 @@ import net.corda.core.flows.FlowLogic import net.corda.core.messaging.CordaRPCOps import net.corda.core.node.NodeInfo import net.corda.core.node.StateLoader +import net.corda.core.node.services.CordaService import net.corda.core.node.services.TransactionStorage +import net.corda.core.serialization.SerializeAsToken import net.corda.node.services.api.CheckpointStorage -import net.corda.node.services.api.ServiceHubInternal +import net.corda.node.services.api.StartedNodeServices import net.corda.node.services.messaging.MessagingService import net.corda.node.services.network.NetworkMapService import net.corda.node.services.persistence.NodeAttachmentService @@ -18,7 +20,7 @@ import net.corda.node.utilities.CordaPersistence interface StartedNode { val internals: N - val services: ServiceHubInternal + val services: StartedNodeServices val info: NodeInfo val checkpointStorage: CheckpointStorage val smm: StateMachineManager 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 5fdb7993a0..de12960304 100644 --- a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt +++ b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt @@ -16,9 +16,11 @@ import net.corda.core.messaging.StateMachineTransactionMapping import net.corda.core.node.NodeInfo import net.corda.core.node.ServiceHub 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.serialization.CordaSerializable 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 @@ -28,7 +30,8 @@ import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl import net.corda.node.services.statemachine.FlowStateMachineImpl import net.corda.node.utilities.CordaPersistence -interface NetworkMapCacheInternal : NetworkMapCache { +interface NetworkMapCacheInternal : NetworkMapCache, NetworkMapCacheBaseInternal +interface NetworkMapCacheBaseInternal : NetworkMapCacheBase { /** * Deregister from updates from the given map service. * @param network the network messaging service. @@ -84,7 +87,6 @@ interface ServiceHubInternal : ServiceHub { val monitoringService: MonitoringService val schemaService: SchemaService override val networkMapCache: NetworkMapCacheInternal - val schedulerService: SchedulerService val auditService: AuditService val rpcFlows: List>> val networkService: MessagingService @@ -109,18 +111,22 @@ interface ServiceHubInternal : ServiceHub { } } + fun getFlowFactory(initiatingFlowClass: Class>): InitiatedFlowFactory<*>? +} + +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")) + 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]. */ - fun startFlow(logic: FlowLogic, flowInitiator: FlowInitiator, ourIdentity: Party? = null): FlowStateMachineImpl + fun startFlow(logic: FlowLogic, flowInitiator: FlowInitiator, ourIdentity: Party? = null): CordaFuture> /** * Will check [logicType] and [args] against a whitelist and if acceptable then construct and initiate the flow. @@ -133,15 +139,14 @@ interface ServiceHubInternal : ServiceHub { fun invokeFlowAsync( logicType: Class>, flowInitiator: FlowInitiator, - vararg args: Any?): FlowStateMachineImpl { + vararg args: Any?): CordaFuture> { val logicRef = FlowLogicRefFactoryImpl.createForRPC(logicType, *args) val logic: FlowLogic = uncheckedCast(FlowLogicRefFactoryImpl.toFlowLogic(logicRef)) return startFlow(logic, flowInitiator, ourIdentity = null) } - - fun getFlowFactory(initiatingFlowClass: Class>): InitiatedFlowFactory<*>? } +interface StartedNodeServices : ServiceHubInternal, FlowStarter /** * Thread-safe storage of transactions. */ diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt index 7e52c0361c..88f2dd855e 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt @@ -20,6 +20,7 @@ interface NodeConfiguration : NodeSSLConfiguration { * service. */ val networkMapService: NetworkMapInfo? + val noNetworkMapServiceMode: Boolean val minimumPlatformVersion: Int val emailAddress: String val exportJMXto: String @@ -78,6 +79,7 @@ data class FullNodeConfiguration( override val database: Properties?, override val certificateSigningService: URL, override val networkMapService: NetworkMapInfo?, + override val noNetworkMapServiceMode: Boolean = false, override val minimumPlatformVersion: Int = 1, override val rpcUsers: List, override val verifierType: VerifierType, 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 26a8322b80..85aa36a0cb 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 @@ -1,9 +1,8 @@ package net.corda.node.services.events import co.paralleluniverse.fibers.Suspendable -import co.paralleluniverse.strands.SettableFuture as QuasarSettableFuture import com.google.common.util.concurrent.ListenableFuture -import com.google.common.util.concurrent.SettableFuture as GuavaSettableFuture +import com.google.common.util.concurrent.SettableFuture import net.corda.core.contracts.SchedulableState import net.corda.core.contracts.ScheduledActivity import net.corda.core.contracts.ScheduledStateRef @@ -13,16 +12,19 @@ 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.internal.until +import net.corda.core.node.StateLoader import net.corda.core.schemas.PersistentStateRef import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.utilities.loggerFor import net.corda.core.utilities.trace import net.corda.node.internal.MutableClock +import net.corda.node.services.api.FlowStarter import net.corda.node.services.api.SchedulerService -import net.corda.node.services.api.ServiceHubInternal import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl import net.corda.node.utilities.AffinityExecutor +import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.NODE_DATABASE_PREFIX import net.corda.node.utilities.PersistentMap import org.apache.activemq.artemis.utils.ReusableLatch @@ -34,6 +36,8 @@ import javax.annotation.concurrent.ThreadSafe import javax.persistence.Column import javax.persistence.EmbeddedId import javax.persistence.Entity +import co.paralleluniverse.strands.SettableFuture as QuasarSettableFuture +import com.google.common.util.concurrent.SettableFuture as GuavaSettableFuture /** * A first pass of a simple [SchedulerService] that works with [MutableClock]s for testing, demonstrations and simulations @@ -47,12 +51,14 @@ import javax.persistence.Entity * in the nodes, maybe we can consider multiple activities and whether the activities have been completed or not, * but that starts to sound a lot like off-ledger state. * - * @param services Core node services. * @param schedulerTimerExecutor The executor the scheduler blocks on waiting for the clock to advance to the next * activity. Only replace this for unit testing purposes. This is not the executor the [FlowLogic] is launched on. */ @ThreadSafe -class NodeSchedulerService(private val services: ServiceHubInternal, +class NodeSchedulerService(private val clock: Clock, + private val database: CordaPersistence, + private val flowStarter: FlowStarter, + private val stateLoader: StateLoader, private val schedulerTimerExecutor: Executor = Executors.newSingleThreadExecutor(), private val unfinishedSchedules: ReusableLatch = ReusableLatch(), private val serverThread: AffinityExecutor) @@ -108,8 +114,8 @@ class NodeSchedulerService(private val services: ServiceHubInternal, toPersistentEntityKey = { PersistentStateRef(it.txhash.toString(), it.index) }, fromPersistentEntity = { //TODO null check will become obsolete after making DB/JPA columns not nullable - var txId = it.output.txId ?: throw IllegalStateException("DB returned null SecureHash transactionId") - var index = it.output.index ?: throw IllegalStateException("DB returned null SecureHash index") + val txId = it.output.txId ?: throw IllegalStateException("DB returned null SecureHash transactionId") + val index = it.output.index ?: throw IllegalStateException("DB returned null SecureHash index") Pair(StateRef(SecureHash.parse(txId), index), ScheduledStateRef(StateRef(SecureHash.parse(txId), index), it.scheduledAt)) }, @@ -172,7 +178,7 @@ class NodeSchedulerService(private val services: ServiceHubInternal, mutex.locked { val previousState = scheduledStates[action.ref] scheduledStates[action.ref] = action - var previousEarliest = scheduledStatesQueue.peek() + val previousEarliest = scheduledStatesQueue.peek() scheduledStatesQueue.remove(previousState) scheduledStatesQueue.add(action) if (previousState == null) { @@ -211,7 +217,7 @@ class NodeSchedulerService(private val services: ServiceHubInternal, * cancelled then we run the scheduled action. Finally we remove that action from the scheduled actions and * recompute the next scheduled action. */ - internal fun rescheduleWakeUp() { + private fun rescheduleWakeUp() { // Note, we already have the mutex but we need the scope again here val (scheduledState, ourRescheduledFuture) = mutex.alreadyLocked { rescheduled?.cancel(false) @@ -223,7 +229,7 @@ class NodeSchedulerService(private val services: ServiceHubInternal, log.trace { "Scheduling as next $scheduledState" } // This will block the scheduler single thread until the scheduled time (returns false) OR // the Future is cancelled due to rescheduling (returns true). - if (!awaitWithDeadline(services.clock, scheduledState.scheduledAt, ourRescheduledFuture)) { + if (!awaitWithDeadline(clock, scheduledState.scheduledAt, ourRescheduledFuture)) { log.trace { "Invoking as next $scheduledState" } onTimeReached(scheduledState) } else { @@ -237,11 +243,11 @@ class NodeSchedulerService(private val services: ServiceHubInternal, serverThread.execute { var flowName: String? = "(unknown)" try { - services.database.transaction { + database.transaction { val scheduledFlow = getScheduledFlow(scheduledState) if (scheduledFlow != null) { flowName = scheduledFlow.javaClass.name - val future = services.startFlow(scheduledFlow, FlowInitiator.Scheduled(scheduledState)).resultFuture + val future = flowStarter.startFlow(scheduledFlow, FlowInitiator.Scheduled(scheduledState)).flatMap { it.resultFuture } future.then { unfinishedSchedules.countDown() } @@ -265,9 +271,9 @@ class NodeSchedulerService(private val services: ServiceHubInternal, unfinishedSchedules.countDown() scheduledStates.remove(scheduledState.ref) scheduledStatesQueue.remove(scheduledState) - } else if (scheduledActivity.scheduledAt.isAfter(services.clock.instant())) { + } else if (scheduledActivity.scheduledAt.isAfter(clock.instant())) { log.info("Scheduled state $scheduledState has rescheduled to ${scheduledActivity.scheduledAt}.") - var newState = ScheduledStateRef(scheduledState.ref, scheduledActivity.scheduledAt) + val newState = ScheduledStateRef(scheduledState.ref, scheduledActivity.scheduledAt) scheduledStates[scheduledState.ref] = newState scheduledStatesQueue.remove(scheduledState) scheduledStatesQueue.add(newState) @@ -286,7 +292,7 @@ class NodeSchedulerService(private val services: ServiceHubInternal, } private fun getScheduledActivity(scheduledState: ScheduledStateRef): ScheduledActivity? { - val txState = services.loadState(scheduledState.ref) + val txState = stateLoader.loadState(scheduledState.ref) val state = txState.data as SchedulableState return try { // This can throw as running contract code. diff --git a/node/src/main/kotlin/net/corda/node/services/events/ScheduledActivityObserver.kt b/node/src/main/kotlin/net/corda/node/services/events/ScheduledActivityObserver.kt index 3509fa2d34..3020a9e528 100644 --- a/node/src/main/kotlin/net/corda/node/services/events/ScheduledActivityObserver.kt +++ b/node/src/main/kotlin/net/corda/node/services/events/ScheduledActivityObserver.kt @@ -4,18 +4,28 @@ import net.corda.core.contracts.ContractState import net.corda.core.contracts.SchedulableState import net.corda.core.contracts.ScheduledStateRef import net.corda.core.contracts.StateAndRef -import net.corda.node.services.api.ServiceHubInternal +import net.corda.core.node.services.VaultService +import net.corda.node.services.api.SchedulerService import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl /** * This observes the vault and schedules and unschedules activities appropriately based on state production and * consumption. */ -class ScheduledActivityObserver(val services: ServiceHubInternal) { - init { - services.vaultService.rawUpdates.subscribe { (consumed, produced) -> - consumed.forEach { services.schedulerService.unscheduleStateActivity(it.ref) } - produced.forEach { scheduleStateActivity(it) } +class ScheduledActivityObserver private constructor(private val schedulerService: SchedulerService) { + companion object { + @JvmStatic + fun install(vaultService: VaultService, schedulerService: SchedulerService) { + val observer = ScheduledActivityObserver(schedulerService) + vaultService.rawUpdates.subscribe { (consumed, produced) -> + consumed.forEach { schedulerService.unscheduleStateActivity(it.ref) } + produced.forEach { observer.scheduleStateActivity(it) } + } + } + + // TODO: Beware we are calling dynamically loaded contract code inside here. + private inline fun sandbox(code: () -> T?): T? { + return code() } } @@ -23,12 +33,7 @@ class ScheduledActivityObserver(val services: ServiceHubInternal) { val producedState = produced.state.data if (producedState is SchedulableState) { val scheduledAt = sandbox { producedState.nextScheduledActivity(produced.ref, FlowLogicRefFactoryImpl)?.scheduledAt } ?: return - services.schedulerService.scheduleStateActivity(ScheduledStateRef(produced.ref, scheduledAt)) + schedulerService.scheduleStateActivity(ScheduledStateRef(produced.ref, scheduledAt)) } } - - // TODO: Beware we are calling dynamically loaded contract code inside here. - private inline fun sandbox(code: () -> T?): T? { - return code() - } } diff --git a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt index c948f27732..18d074cd68 100644 --- a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt +++ b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt @@ -11,8 +11,8 @@ import net.corda.core.node.services.UnknownAnonymousPartyException import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.utilities.debug import net.corda.core.utilities.loggerFor -import net.corda.core.utilities.MAX_HASH_HEX_SIZE import net.corda.node.utilities.AppendOnlyPersistentMap +import net.corda.node.utilities.MAX_HASH_HEX_SIZE import net.corda.node.utilities.NODE_DATABASE_PREFIX import org.bouncycastle.cert.X509CertificateHolder import java.io.ByteArrayInputStream diff --git a/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt b/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt index f885383bc8..1c19ce0acb 100644 --- a/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt +++ b/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt @@ -4,12 +4,9 @@ import net.corda.core.crypto.* import net.corda.core.identity.PartyAndCertificate import net.corda.core.node.services.IdentityService import net.corda.core.node.services.KeyManagementService -import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.SingletonSerializeAsToken -import net.corda.core.serialization.deserialize -import net.corda.core.serialization.serialize -import net.corda.core.utilities.MAX_HASH_HEX_SIZE import net.corda.node.utilities.AppendOnlyPersistentMap +import net.corda.node.utilities.MAX_HASH_HEX_SIZE import net.corda.node.utilities.NODE_DATABASE_PREFIX import org.bouncycastle.operator.ContentSigner import java.security.KeyPair diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt b/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt index a708166239..1697b49047 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt @@ -83,9 +83,40 @@ interface MessagingService { * to send an ACK message back. * * @param retryId if provided the message will be scheduled for redelivery until [cancelRedelivery] is called for this id. - * Note that this feature should only be used when the target is an idempotent distributed service, e.g. a notary. + * Note that this feature should only be used when the target is an idempotent distributed service, e.g. a notary. + * @param sequenceKey an object that may be used to enable a parallel [MessagingService] implementation. Two + * subsequent send()s with the same [sequenceKey] (up to equality) are guaranteed to be delivered in the same + * sequence the send()s were called. By default this is chosen conservatively to be [target]. + * @param acknowledgementHandler if non-null this handler will be called once the sent message has been committed by + * the broker. Note that if specified [send] itself may return earlier than the commit. */ - fun send(message: Message, target: MessageRecipients, retryId: Long? = null) + fun send( + message: Message, + target: MessageRecipients, + retryId: Long? = null, + sequenceKey: Any = target, + acknowledgementHandler: (() -> Unit)? = null + ) + + /** A message with a target and sequenceKey specified. */ + data class AddressedMessage( + val message: Message, + val target: MessageRecipients, + val retryId: Long? = null, + val sequenceKey: Any = target + ) + + /** + * Sends a list of messages to the specified recipients. This function allows for an efficient batching + * implementation. + * + * @param addressedMessages The list of messages together with the recipients, retry ids and sequence keys. + * @param retryId if provided the message will be scheduled for redelivery until [cancelRedelivery] is called for this id. + * Note that this feature should only be used when the target is an idempotent distributed service, e.g. a notary. + * @param acknowledgementHandler if non-null this handler will be called once all sent messages have been committed + * by the broker. Note that if specified [send] itself may return earlier than the commit. + */ + fun send(addressedMessages: List, acknowledgementHandler: (() -> Unit)? = null) /** Cancels the scheduled message redelivery for the specified [retryId] */ fun cancelRedelivery(retryId: Long) diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt b/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt index 5a9cd04730..401709f811 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt @@ -22,7 +22,7 @@ import net.corda.node.services.RPCUserService import net.corda.node.services.api.MonitoringService import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.VerifierType -import net.corda.node.services.statemachine.StateMachineManager +import net.corda.node.services.statemachine.StateMachineManagerImpl import net.corda.node.services.transactions.InMemoryTransactionVerifierService import net.corda.node.services.transactions.OutOfProcessTransactionVerifierService import net.corda.node.utilities.* @@ -485,7 +485,7 @@ class NodeMessagingClient(override val config: NodeConfiguration, } } - override fun send(message: Message, target: MessageRecipients, retryId: Long?) { + override fun send(message: Message, target: MessageRecipients, retryId: Long?, sequenceKey: Any, acknowledgementHandler: (() -> Unit)?) { // We have to perform sending on a different thread pool, since using the same pool for messaging and // fibers leads to Netty buffer memory leaks, caused by both Netty and Quasar fiddling with thread-locals. messagingExecutor.fetchFrom { @@ -502,7 +502,7 @@ class NodeMessagingClient(override val config: NodeConfiguration, putStringProperty(HDR_DUPLICATE_DETECTION_ID, SimpleString(message.uniqueMessageId.toString())) // For demo purposes - if set then add a delay to messages in order to demonstrate that the flows are doing as intended - if (amqDelayMillis > 0 && message.topicSession.topic == StateMachineManager.sessionTopic.topic) { + if (amqDelayMillis > 0 && message.topicSession.topic == StateMachineManagerImpl.sessionTopic.topic) { putLongProperty(HDR_SCHEDULED_DELIVERY_TIME, System.currentTimeMillis() + amqDelayMillis) } } @@ -523,6 +523,14 @@ class NodeMessagingClient(override val config: NodeConfiguration, } } } + acknowledgementHandler?.invoke() + } + + override fun send(addressedMessages: List, acknowledgementHandler: (() -> Unit)?) { + for ((message, target, retryId, sequenceKey) in addressedMessages) { + send(message, target, retryId, sequenceKey, null) + } + acknowledgementHandler?.invoke() } private fun sendWithRetry(retryCount: Int, address: String, message: ClientMessage, retryId: Long) { diff --git a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt new file mode 100644 index 0000000000..2aba89084d --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt @@ -0,0 +1,70 @@ +package net.corda.node.services.network + +import com.fasterxml.jackson.databind.ObjectMapper +import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.SignedData +import net.corda.core.node.NodeInfo +import net.corda.core.serialization.deserialize +import net.corda.core.serialization.serialize +import java.net.HttpURLConnection +import java.net.URL + +interface NetworkMapClient { + /** + * Publish node info to network map service. + */ + fun publish(signedNodeInfo: SignedData) + + /** + * Retrieve [NetworkMap] from the network map service containing list of node info hashes and network parameter hash. + */ + // TODO: Use NetworkMap object when available. + fun getNetworkMap(): List + + /** + * Retrieve [NodeInfo] from network map service using the node info hash. + */ + fun getNodeInfo(nodeInfoHash: SecureHash): NodeInfo? + + // TODO: Implement getNetworkParameter when its available. + //fun getNetworkParameter(networkParameterHash: SecureHash): NetworkParameter +} + +class HTTPNetworkMapClient(private val networkMapUrl: String) : NetworkMapClient { + override fun publish(signedNodeInfo: SignedData) { + val publishURL = URL("$networkMapUrl/publish") + val conn = publishURL.openConnection() as HttpURLConnection + conn.doOutput = true + conn.requestMethod = "POST" + conn.setRequestProperty("Content-Type", "application/octet-stream") + conn.outputStream.write(signedNodeInfo.serialize().bytes) + when (conn.responseCode) { + HttpURLConnection.HTTP_OK -> return + HttpURLConnection.HTTP_UNAUTHORIZED -> throw IllegalArgumentException(conn.errorStream.bufferedReader().readLine()) + else -> throw IllegalArgumentException("Unexpected response code ${conn.responseCode}, response error message: '${conn.errorStream.bufferedReader().readLines()}'") + } + } + + override fun getNetworkMap(): List { + val conn = URL(networkMapUrl).openConnection() as HttpURLConnection + + return when (conn.responseCode) { + HttpURLConnection.HTTP_OK -> { + val response = conn.inputStream.bufferedReader().use { it.readLine() } + ObjectMapper().readValue(response, List::class.java).map { SecureHash.parse(it.toString()) } + } + else -> throw IllegalArgumentException("Unexpected response code ${conn.responseCode}, response error message: '${conn.errorStream.bufferedReader().readLines()}'") + } + } + + override fun getNodeInfo(nodeInfoHash: SecureHash): NodeInfo? { + val nodeInfoURL = URL("$networkMapUrl/$nodeInfoHash") + val conn = nodeInfoURL.openConnection() as HttpURLConnection + + return when (conn.responseCode) { + HttpURLConnection.HTTP_OK -> conn.inputStream.readBytes().deserialize() + HttpURLConnection.HTTP_NOT_FOUND -> null + else -> throw IllegalArgumentException("Unexpected response code ${conn.responseCode}, response error message: '${conn.errorStream.bufferedReader().readLines()}'") + } + } +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt b/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt index 65ef358c6d..4bd9c72cb4 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt @@ -9,6 +9,7 @@ import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.utilities.loggerFor import net.corda.core.utilities.seconds +import net.corda.nodeapi.NodeInfoFilesCopier import rx.Observable import rx.Scheduler import rx.schedulers.Schedulers @@ -55,7 +56,8 @@ class NodeInfoWatcher(private val nodePath: Path, val serializedBytes = nodeInfo.serialize() val regSig = keyManager.sign(serializedBytes.bytes, nodeInfo.legalIdentities.first().owningKey) val signedData = SignedData(serializedBytes, regSig) - signedData.serialize().open().copyTo(path / "nodeInfo-${serializedBytes.hash}") + signedData.serialize().open().copyTo( + path / "${NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX}${serializedBytes.hash}") } catch (e: Exception) { logger.warn("Couldn't write node info to file", e) } diff --git a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt index 544d9257d6..d242f3869f 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt @@ -14,6 +14,7 @@ import net.corda.core.internal.concurrent.openFuture import net.corda.core.messaging.DataFeed import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.node.NodeInfo +import net.corda.core.node.services.IdentityService import net.corda.core.node.services.NetworkMapCache.MapChange import net.corda.core.node.services.NotaryService import net.corda.core.node.services.PartyInfo @@ -27,12 +28,14 @@ import net.corda.core.utilities.loggerFor import net.corda.core.utilities.toBase58String import net.corda.node.services.api.NetworkCacheException import net.corda.node.services.api.NetworkMapCacheInternal -import net.corda.node.services.api.ServiceHubInternal +import net.corda.node.services.api.NetworkMapCacheBaseInternal +import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.messaging.MessagingService import net.corda.node.services.messaging.createMessage import net.corda.node.services.messaging.sendRequest import net.corda.node.services.network.NetworkMapService.FetchMapResponse import net.corda.node.services.network.NetworkMapService.SubscribeResponse +import net.corda.node.utilities.* import net.corda.node.utilities.AddOrRemove import net.corda.node.utilities.bufferUntilDatabaseCommit import net.corda.node.utilities.wrapWithDatabaseTransaction @@ -45,15 +48,32 @@ import java.util.* import javax.annotation.concurrent.ThreadSafe import kotlin.collections.HashMap +class NetworkMapCacheImpl(networkMapCacheBase: NetworkMapCacheBaseInternal, private val identityService: IdentityService) : NetworkMapCacheBaseInternal by networkMapCacheBase, NetworkMapCacheInternal { + init { + networkMapCacheBase.allNodes.forEach { it.legalIdentitiesAndCerts.forEach { identityService.verifyAndRegisterIdentity(it) } } + networkMapCacheBase.changed.subscribe { mapChange -> + // TODO how should we handle network map removal + if (mapChange is MapChange.Added) { + mapChange.node.legalIdentitiesAndCerts.forEach { + identityService.verifyAndRegisterIdentity(it) + } + } + } + } + + override fun getNodeByLegalIdentity(party: AbstractParty): NodeInfo? { + val wellKnownParty = identityService.wellKnownPartyFromAnonymous(party) + return wellKnownParty?.let { + getNodesByLegalIdentityKey(it.owningKey).firstOrNull() + } + } +} + /** * Extremely simple in-memory cache of the network map. - * - * @param serviceHub an optional service hub from which we'll take the identity service. We take a service hub rather - * than the identity service directly, as this avoids problems with service start sequence (network map cache - * and identity services depend on each other). Should always be provided except for unit test cases. */ @ThreadSafe -open class PersistentNetworkMapCache(private val serviceHub: ServiceHubInternal) : SingletonSerializeAsToken(), NetworkMapCacheInternal { +open class PersistentNetworkMapCache(private val database: CordaPersistence, configuration: NodeConfiguration) : SingletonSerializeAsToken(), NetworkMapCacheBaseInternal { companion object { val logger = loggerFor() } @@ -89,12 +109,12 @@ open class PersistentNetworkMapCache(private val serviceHub: ServiceHubInternal) .sortedBy { it.name.toString() } } - private val nodeInfoSerializer = NodeInfoWatcher(serviceHub.configuration.baseDirectory, - serviceHub.configuration.additionalNodeInfoPollingFrequencyMsec) + private val nodeInfoSerializer = NodeInfoWatcher(configuration.baseDirectory, + configuration.additionalNodeInfoPollingFrequencyMsec) init { loadFromFiles() - serviceHub.database.transaction { loadFromDB(session) } + database.transaction { loadFromDB(session) } } private fun loadFromFiles() { @@ -103,7 +123,7 @@ open class PersistentNetworkMapCache(private val serviceHub: ServiceHubInternal) } override fun getPartyInfo(party: Party): PartyInfo? { - val nodes = serviceHub.database.transaction { queryByIdentityKey(session, party.owningKey) } + val nodes = database.transaction { queryByIdentityKey(session, party.owningKey) } if (nodes.size == 1 && nodes[0].isLegalIdentity(party)) { return PartyInfo.SingleNode(party, nodes[0].addresses) } @@ -118,20 +138,13 @@ open class PersistentNetworkMapCache(private val serviceHub: ServiceHubInternal) } override fun getNodeByLegalName(name: CordaX500Name): NodeInfo? = getNodesByLegalName(name).firstOrNull() - override fun getNodesByLegalName(name: CordaX500Name): List = serviceHub.database.transaction { queryByLegalName(session, name) } + override fun getNodesByLegalName(name: CordaX500Name): List = database.transaction { queryByLegalName(session, name) } override fun getNodesByLegalIdentityKey(identityKey: PublicKey): List = - serviceHub.database.transaction { queryByIdentityKey(session, identityKey) } + database.transaction { queryByIdentityKey(session, identityKey) } - override fun getNodeByLegalIdentity(party: AbstractParty): NodeInfo? { - val wellKnownParty = serviceHub.identityService.wellKnownPartyFromAnonymous(party) - return wellKnownParty?.let { - getNodesByLegalIdentityKey(it.owningKey).firstOrNull() - } - } + override fun getNodeByAddress(address: NetworkHostAndPort): NodeInfo? = database.transaction { queryByAddress(session, address) } - override fun getNodeByAddress(address: NetworkHostAndPort): NodeInfo? = serviceHub.database.transaction { queryByAddress(session, address) } - - override fun getPeerCertificateByLegalName(name: CordaX500Name): PartyAndCertificate? = serviceHub.database.transaction { queryIdentityByLegalName(session, name) } + override fun getPeerCertificateByLegalName(name: CordaX500Name): PartyAndCertificate? = database.transaction { queryIdentityByLegalName(session, name) } override fun track(): DataFeed, MapChange> { synchronized(_changed) { @@ -183,13 +196,13 @@ open class PersistentNetworkMapCache(private val serviceHub: ServiceHubInternal) val previousNode = registeredNodes.put(node.legalIdentities.first().owningKey, node) // TODO hack... we left the first one as special one if (previousNode == null) { logger.info("No previous node found") - serviceHub.database.transaction { + database.transaction { updateInfoDB(node) changePublisher.onNext(MapChange.Added(node)) } } else if (previousNode != node) { logger.info("Previous node was found as: $previousNode") - serviceHub.database.transaction { + database.transaction { updateInfoDB(node) changePublisher.onNext(MapChange.Modified(node, previousNode)) } @@ -204,7 +217,7 @@ open class PersistentNetworkMapCache(private val serviceHub: ServiceHubInternal) logger.info("Removing node with info: $node") synchronized(_changed) { registeredNodes.remove(node.legalIdentities.first().owningKey) - serviceHub.database.transaction { + database.transaction { removeInfoDB(session, node) changePublisher.onNext(MapChange.Removed(node)) } @@ -239,7 +252,7 @@ open class PersistentNetworkMapCache(private val serviceHub: ServiceHubInternal) } override val allNodes: List - get() = serviceHub.database.transaction { + get() = database.transaction { getAllInfos(session).map { it.toNodeInfo() } } @@ -288,8 +301,8 @@ open class PersistentNetworkMapCache(private val serviceHub: ServiceHubInternal) private fun updateInfoDB(nodeInfo: NodeInfo) { // TODO Temporary workaround to force isolated transaction (otherwise it causes race conditions when processing // network map registration on network map node) - serviceHub.database.dataSource.connection.use { - val session = serviceHub.database.entityManagerFactory.withOptions().connection(it.apply { + database.dataSource.connection.use { + val session = database.entityManagerFactory.withOptions().connection(it.apply { transactionIsolation = 1 }).openSession() session.use { @@ -370,7 +383,7 @@ open class PersistentNetworkMapCache(private val serviceHub: ServiceHubInternal) } override fun clearNetworkMapCache() { - serviceHub.database.transaction { + database.transaction { val result = getAllInfos(session) for (nodeInfo in result) session.remove(nodeInfo) } diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/HibernateConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/persistence/HibernateConfiguration.kt index 8fb0023b3d..589684ec01 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/HibernateConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/HibernateConfiguration.kt @@ -93,6 +93,7 @@ class HibernateConfiguration(val schemaService: SchemaService, private val datab // during schema creation / update. class NodeDatabaseConnectionProvider : ConnectionProvider { override fun closeConnection(conn: Connection) { + conn.autoCommit = false val tx = DatabaseTransactionManager.current() tx.commit() tx.close() diff --git a/node/src/main/kotlin/net/corda/node/services/schema/HibernateObserver.kt b/node/src/main/kotlin/net/corda/node/services/schema/HibernateObserver.kt index 45a3334ef1..2babcc1d70 100644 --- a/node/src/main/kotlin/net/corda/node/services/schema/HibernateObserver.kt +++ b/node/src/main/kotlin/net/corda/node/services/schema/HibernateObserver.kt @@ -3,6 +3,7 @@ package net.corda.node.services.schema import net.corda.core.contracts.ContractState import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateRef +import net.corda.core.internal.VisibleForTesting import net.corda.core.node.services.Vault import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentStateRef @@ -17,14 +18,15 @@ import rx.Observable * A vault observer that extracts Object Relational Mappings for contract states that support it, and persists them with Hibernate. */ // TODO: Manage version evolution of the schemas via additional tooling. -class HibernateObserver(vaultUpdates: Observable>, val config: HibernateConfiguration) { - +class HibernateObserver private constructor(private val config: HibernateConfiguration) { companion object { - val logger = loggerFor() - } - - init { - vaultUpdates.subscribe { persist(it.produced) } + private val log = loggerFor() + @JvmStatic + fun install(vaultUpdates: Observable>, config: HibernateConfiguration): HibernateObserver { + val observer = HibernateObserver(config) + vaultUpdates.subscribe { observer.persist(it.produced) } + return observer + } } private fun persist(produced: Set>) { @@ -33,11 +35,12 @@ class HibernateObserver(vaultUpdates: Observable>, v private fun persistState(stateAndRef: StateAndRef) { val state = stateAndRef.state.data - logger.debug { "Asked to persist state ${stateAndRef.ref}" } + log.debug { "Asked to persist state ${stateAndRef.ref}" } config.schemaService.selectSchemas(state).forEach { persistStateWithSchema(state, stateAndRef.ref, it) } } - fun persistStateWithSchema(state: ContractState, stateRef: StateRef, schema: MappedSchema) { + @VisibleForTesting + internal fun persistStateWithSchema(state: ContractState, stateRef: StateRef, schema: MappedSchema) { val sessionFactory = config.sessionFactoryForSchemas(setOf(schema)) val session = sessionFactory.withOptions(). connection(DatabaseTransactionManager.current().connection). diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowLogicRefFactoryImpl.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowLogicRefFactoryImpl.kt index a3c36fa3a4..f0f10ebed4 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowLogicRefFactoryImpl.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowLogicRefFactoryImpl.kt @@ -2,7 +2,6 @@ package net.corda.node.services.statemachine import net.corda.core.internal.VisibleForTesting import com.google.common.primitives.Primitives -import net.corda.core.cordapp.CordappContext import net.corda.core.flows.* import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.SingletonSerializeAsToken @@ -34,7 +33,7 @@ data class FlowLogicRefImpl internal constructor(val flowLogicClassName: String, */ object FlowLogicRefFactoryImpl : SingletonSerializeAsToken(), FlowLogicRefFactory { // TODO: Replace with a per app classloader/cordapp provider/cordapp loader - this will do for now - var classloader = javaClass.classLoader + var classloader: ClassLoader = javaClass.classLoader override fun create(flowClass: Class>, vararg args: Any?): FlowLogicRef { if (!flowClass.isAnnotationPresent(SchedulableFlow::class.java)) { diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowSessionImpl.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowSessionImpl.kt index 044fdc0dbe..054d7c5d01 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowSessionImpl.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowSessionImpl.kt @@ -16,28 +16,46 @@ class FlowSessionImpl( internal lateinit var sessionFlow: FlowLogic<*> @Suspendable - override fun getCounterpartyFlowInfo(): FlowInfo { - return stateMachine.getFlowInfo(counterparty, sessionFlow) + override fun getCounterpartyFlowInfo(maySkipCheckpoint: Boolean): FlowInfo { + return stateMachine.getFlowInfo(counterparty, sessionFlow, maySkipCheckpoint) } @Suspendable - override fun sendAndReceive(receiveType: Class, payload: Any): UntrustworthyData { - return stateMachine.sendAndReceive(receiveType, counterparty, payload, sessionFlow) + override fun getCounterpartyFlowInfo() = getCounterpartyFlowInfo(maySkipCheckpoint = false) + + @Suspendable + override fun sendAndReceive( + receiveType: Class, + payload: Any, + maySkipCheckpoint: Boolean + ): UntrustworthyData { + return stateMachine.sendAndReceive( + receiveType, + counterparty, + payload, + sessionFlow, + retrySend = false, + maySkipCheckpoint = maySkipCheckpoint + ) } @Suspendable - internal fun sendAndReceiveWithRetry(receiveType: Class, payload: Any): UntrustworthyData { - return stateMachine.sendAndReceive(receiveType, counterparty, payload, sessionFlow, retrySend = true) + override fun sendAndReceive(receiveType: Class, payload: Any) = sendAndReceive(receiveType, payload, maySkipCheckpoint = false) + + @Suspendable + override fun receive(receiveType: Class, maySkipCheckpoint: Boolean): UntrustworthyData { + return stateMachine.receive(receiveType, counterparty, sessionFlow, maySkipCheckpoint) } @Suspendable - override fun receive(receiveType: Class): UntrustworthyData { - return stateMachine.receive(receiveType, counterparty, sessionFlow) + override fun receive(receiveType: Class) = receive(receiveType, maySkipCheckpoint = false) + + @Suspendable + override fun send(payload: Any, maySkipCheckpoint: Boolean) { + return stateMachine.send(counterparty, payload, sessionFlow, maySkipCheckpoint) } @Suspendable - override fun send(payload: Any) { - return stateMachine.send(counterparty, payload, sessionFlow) - } + override fun send(payload: Any) = send(payload, maySkipCheckpoint = false) } 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 89f0bf6dc6..57cc99378a 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 @@ -68,12 +68,10 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, * is not necessary. */ override val logger: Logger = LoggerFactory.getLogger("net.corda.flow.$id") - - @Transient private var _resultFuture: OpenFuture? = openFuture() + @Transient private var resultFutureTransient: OpenFuture? = openFuture() + private val _resultFuture get() = resultFutureTransient ?: openFuture().also { resultFutureTransient = it } /** This future will complete when the call method returns. */ - override val resultFuture: CordaFuture - get() = _resultFuture ?: openFuture().also { _resultFuture = it } - + override val resultFuture: CordaFuture get() = _resultFuture // This state IS serialised, as we need it to know what the fiber is waiting for. internal val openSessions = HashMap, Party>, FlowSessionInternal>() internal var waitingForResponse: WaitingRequest? = null @@ -115,7 +113,7 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, recordDuration(startTime) // This is to prevent actionOnEnd being called twice if it throws an exception actionOnEnd(Try.Success(result), false) - _resultFuture?.set(result) + _resultFuture.set(result) logic.progressTracker?.currentStep = ProgressTracker.DONE logger.debug { "Flow finished with result ${result.toString().abbreviate(300)}" } } @@ -128,7 +126,7 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, private fun processException(exception: Throwable, propagated: Boolean) { actionOnEnd(Try.Failure(exception), propagated) - _resultFuture?.setException(exception) + _resultFuture.setException(exception) logic.progressTracker?.endWithError(exception) } @@ -165,7 +163,7 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, } @Suspendable - override fun getFlowInfo(otherParty: Party, sessionFlow: FlowLogic<*>): FlowInfo { + override fun getFlowInfo(otherParty: Party, sessionFlow: FlowLogic<*>, maySkipCheckpoint: Boolean): FlowInfo { val state = getConfirmedSession(otherParty, sessionFlow).state as FlowSessionState.Initiated return state.context } @@ -175,7 +173,8 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, otherParty: Party, payload: Any, sessionFlow: FlowLogic<*>, - retrySend: Boolean): UntrustworthyData { + retrySend: Boolean, + maySkipCheckpoint: Boolean): UntrustworthyData { requireNonPrimitive(receiveType) logger.debug { "sendAndReceive(${receiveType.name}, $otherParty, ${payload.toString().abbreviate(300)}) ..." } val session = getConfirmedSessionIfPresent(otherParty, sessionFlow) @@ -194,7 +193,8 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, @Suspendable override fun receive(receiveType: Class, otherParty: Party, - sessionFlow: FlowLogic<*>): UntrustworthyData { + sessionFlow: FlowLogic<*>, + maySkipCheckpoint: Boolean): UntrustworthyData { requireNonPrimitive(receiveType) logger.debug { "receive(${receiveType.name}, $otherParty) ..." } val session = getConfirmedSession(otherParty, sessionFlow) @@ -210,7 +210,7 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, } @Suspendable - override fun send(otherParty: Party, payload: Any, sessionFlow: FlowLogic<*>) { + override fun send(otherParty: Party, payload: Any, sessionFlow: FlowLogic<*>, maySkipCheckpoint: Boolean) { logger.debug { "send($otherParty, ${payload.toString().abbreviate(300)})" } val session = getConfirmedSessionIfPresent(otherParty, sessionFlow) if (session == null) { @@ -222,7 +222,7 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, } @Suspendable - override fun waitForLedgerCommit(hash: SecureHash, sessionFlow: FlowLogic<*>): SignedTransaction { + override fun waitForLedgerCommit(hash: SecureHash, sessionFlow: FlowLogic<*>, maySkipCheckpoint: Boolean): SignedTransaction { logger.debug { "waitForLedgerCommit($hash) ..." } suspend(WaitForLedgerCommit(hash, sessionFlow.stateMachine as FlowStateMachineImpl<*>)) val stx = serviceHub.validatedTransactions.getTransaction(hash) 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 74697821e9..e790d28b2c 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,68 +1,24 @@ package net.corda.node.services.statemachine -import co.paralleluniverse.fibers.Fiber -import co.paralleluniverse.fibers.FiberExecutorScheduler -import co.paralleluniverse.fibers.Suspendable -import co.paralleluniverse.fibers.instrument.SuspendableHelper -import co.paralleluniverse.strands.Strand -import com.codahale.metrics.Gauge -import com.esotericsoftware.kryo.KryoException -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.crypto.SecureHash -import net.corda.core.crypto.random63BitValue -import net.corda.core.flows.* +import net.corda.core.flows.FlowInitiator +import net.corda.core.flows.FlowLogic import net.corda.core.identity.Party -import net.corda.core.internal.* +import net.corda.core.internal.FlowStateMachine import net.corda.core.messaging.DataFeed -import net.corda.core.serialization.SerializationDefaults.CHECKPOINT_CONTEXT -import net.corda.core.serialization.SerializationDefaults.SERIALIZATION_FACTORY -import net.corda.core.serialization.SerializedBytes -import net.corda.core.serialization.deserialize -import net.corda.core.serialization.serialize import net.corda.core.utilities.Try -import net.corda.core.utilities.debug -import net.corda.core.utilities.loggerFor -import net.corda.core.utilities.trace -import net.corda.node.internal.InitiatedFlowFactory -import net.corda.node.services.api.Checkpoint -import net.corda.node.services.api.CheckpointStorage -import net.corda.node.services.api.ServiceHubInternal -import net.corda.node.services.messaging.ReceivedMessage -import net.corda.node.services.messaging.TopicSession -import net.corda.node.utilities.AffinityExecutor -import net.corda.node.utilities.CordaPersistence -import net.corda.node.utilities.bufferUntilDatabaseCommit -import net.corda.node.utilities.wrapWithDatabaseTransaction -import net.corda.nodeapi.internal.serialization.SerializeAsTokenContextImpl -import net.corda.nodeapi.internal.serialization.withTokenContext -import org.apache.activemq.artemis.utils.ReusableLatch -import org.slf4j.Logger import rx.Observable -import rx.subjects.PublishSubject -import java.io.NotSerializableException -import java.util.* -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.Executors -import java.util.concurrent.TimeUnit.SECONDS -import javax.annotation.concurrent.ThreadSafe -import kotlin.collections.ArrayList /** - * A StateMachineManager is responsible for coordination and persistence of multiple [FlowStateMachineImpl] objects. + * A StateMachineManager is responsible for coordination and persistence of multiple [FlowStateMachine] objects. * Each such object represents an instantiation of a (two-party) flow that has reached a particular point. * - * An implementation of this class will persist state machines to long term storage so they can survive process restarts - * and, if run with a single-threaded executor, will ensure no two state machines run concurrently with each other - * (bad for performance, good for programmer mental health!). + * An implementation of this interface will persist state machines to long term storage so they can survive process + * restarts and, if run with a single-threaded executor, will ensure no two state machines run concurrently with each + * other (bad for performance, good for programmer mental health!). * - * A "state machine" 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. - * - * The SMM will always invoke the flow fibers on the given [AffinityExecutor], regardless of which thread actually - * starts them via [add]. + * 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? @@ -72,588 +28,51 @@ import kotlin.collections.ArrayList * TODO: Ability to control checkpointing explicitly, for cases where you know replaying a message can't hurt * TODO: Don't store all active flows in memory, load from the database on demand. */ -@ThreadSafe -class StateMachineManager(val serviceHub: ServiceHubInternal, - val checkpointStorage: CheckpointStorage, - val executor: AffinityExecutor, - val database: CordaPersistence, - private val unfinishedFibers: ReusableLatch = ReusableLatch(), - private val classloader: ClassLoader = javaClass.classLoader) { +interface StateMachineManager { + /** + * Starts the state machine manager, loading and starting the state machines in storage. + */ + fun start(tokenizableServices: List) + /** + * Stops the state machine manager gracefully, waiting until all but [allowedUnsuspendedFiberCount] flows reach the + * next checkpoint. + */ + fun stop(allowedUnsuspendedFiberCount: Int) - inner class FiberScheduler : FiberExecutorScheduler("Same thread scheduler", executor) - - companion object { - private val logger = loggerFor() - internal val sessionTopic = TopicSession("platform.session") - - init { - Fiber.setDefaultUncaughtExceptionHandler { fiber, throwable -> - (fiber as FlowStateMachineImpl<*>).logger.warn("Caught exception from flow", throwable) - } - } - } + /** + * Starts a new flow. + * + * @param flowLogic The flow's code. + * @param flowInitiator The initiator of the flow. + */ + fun startFlow(flowLogic: FlowLogic, flowInitiator: FlowInitiator, ourIdentity: Party? = null): CordaFuture> + /** + * Represents an addition/removal of a state machine. + */ sealed class Change { abstract val logic: FlowLogic<*> - data class Add(override val logic: FlowLogic<*>) : Change() data class Removed(override val logic: FlowLogic<*>, val result: Try<*>) : Change() } - // A list of all the state machines being managed by this class. We expose snapshots of it via the stateMachines - // property. - private class InnerState { - var started = false - val stateMachines = LinkedHashMap, Checkpoint>() - val changesPublisher = PublishSubject.create()!! - val fibersWaitingForLedgerCommit = HashMultimap.create>()!! + /** + * Returns the list of live state machines and a stream of subsequent additions/removals of them. + */ + fun track(): DataFeed>, Change> - fun notifyChangeObservers(change: Change) { - changesPublisher.bufferUntilDatabaseCommit().onNext(change) - } - } + /** + * The stream of additions/removals of flows. + */ + val changes: Observable - private val scheduler = FiberScheduler() - private val mutex = ThreadBox(InnerState()) - // This thread (only enabled in dev mode) deserialises checkpoints in the background to shake out bugs in checkpoint restore. - private val checkpointCheckerThread = if (serviceHub.configuration.devMode) Executors.newSingleThreadExecutor() else null - - @Volatile private var unrestorableCheckpoints = false - - // True if we're shutting down, so don't resume anything. - @Volatile private var stopping = false - // How many Fibers are running and not suspended. If zero and stopping is true, then we are halted. - private val liveFibers = ReusableLatch() - - // Monitoring support. - private val metrics = serviceHub.monitoringService.metrics - - init { - metrics.register("Flows.InFlight", Gauge { mutex.content.stateMachines.size }) - } - - private val checkpointingMeter = metrics.meter("Flows.Checkpointing Rate") - private val totalStartedFlows = metrics.counter("Flows.Started") - private val totalFinishedFlows = metrics.counter("Flows.Finished") - - private val openSessions = ConcurrentHashMap() - private val recentlyClosedSessions = ConcurrentHashMap() - - internal val tokenizableServices = ArrayList() - // Context for tokenized services in checkpoints - private val serializationContext by lazy { - SerializeAsTokenContextImpl(tokenizableServices, SERIALIZATION_FACTORY, CHECKPOINT_CONTEXT, serviceHub) - } - - fun findServices(predicate: (Any) -> Boolean) = tokenizableServices.filter(predicate) - - /** Returns a list of all state machines executing the given flow logic at the top level (subflows do not count) */ - fun

, T> findStateMachines(flowClass: Class

): List>> { - return mutex.locked { - stateMachines.keys.mapNotNull { - flowClass.castIfPossible(it.logic)?.let { it to uncheckedCast, FlowStateMachineImpl>(it.stateMachine).resultFuture } - } - } - } + /** + * Returns the currently live flows of type [flowClass], and their corresponding result future. + */ + fun > findStateMachines(flowClass: Class): List>> + /** + * Returns all currently live flows. + */ val allStateMachines: List> - get() = mutex.locked { stateMachines.keys.map { it.logic } } - - /** - * An observable that emits triples of the changing flow, the type of change, and a process-specific ID number - * which may change across restarts. - * - * We use assignment here so that multiple subscribers share the same wrapped Observable. - */ - val changes: Observable = mutex.content.changesPublisher.wrapWithDatabaseTransaction() - - fun start() { - checkQuasarJavaAgentPresence() - restoreFibersFromCheckpoints() - listenToLedgerTransactions() - serviceHub.networkMapCache.nodeReady.then { executor.execute(this::resumeRestoredFibers) } - } - - private fun checkQuasarJavaAgentPresence() { - check(SuspendableHelper.isJavaAgentActive(), { - """Missing the '-javaagent' JVM argument. Make sure you run the tests with the Quasar java agent attached to your JVM. - #See https://docs.corda.net/troubleshooting.html - 'Fiber classes not instrumented' for more details.""".trimMargin("#") - }) - } - - private fun listenToLedgerTransactions() { - // Observe the stream of committed, validated transactions and resume fibers that are waiting for them. - serviceHub.validatedTransactions.updates.subscribe { stx -> - val hash = stx.id - val fibers: Set> = mutex.locked { fibersWaitingForLedgerCommit.removeAll(hash) } - if (fibers.isNotEmpty()) { - executor.executeASAP { - for (fiber in fibers) { - fiber.logger.trace { "Transaction $hash has committed to the ledger, resuming" } - fiber.waitingForResponse = null - resumeFiber(fiber) - } - } - } - } - } - - private fun decrementLiveFibers() { - liveFibers.countDown() - } - - private fun incrementLiveFibers() { - liveFibers.countUp() - } - - /** - * Start the shutdown process, bringing the [StateMachineManager] to a controlled stop. When this method returns, - * all Fibers have been suspended and checkpointed, or have completed. - * - * @param allowedUnsuspendedFiberCount Optional parameter is used in some tests. - */ - fun stop(allowedUnsuspendedFiberCount: Int = 0) { - require(allowedUnsuspendedFiberCount >= 0) - mutex.locked { - if (stopping) throw IllegalStateException("Already stopping!") - stopping = true - } - // Account for any expected Fibers in a test scenario. - liveFibers.countDown(allowedUnsuspendedFiberCount) - liveFibers.await() - checkpointCheckerThread?.let { MoreExecutors.shutdownAndAwaitTermination(it, 5, SECONDS) } - check(!unrestorableCheckpoints) { "Unrestorable checkpoints where created, please check the logs for details." } - } - - /** - * Atomic get snapshot + subscribe. This is needed so we don't miss updates between subscriptions to [changes] and - * calls to [allStateMachines] - */ - fun track(): DataFeed>, Change> { - return mutex.locked { - DataFeed(stateMachines.keys.toList(), changesPublisher.bufferUntilSubscribed().wrapWithDatabaseTransaction()) - } - } - - private fun restoreFibersFromCheckpoints() { - mutex.locked { - checkpointStorage.forEach { checkpoint -> - // If a flow is added before start() then don't attempt to restore it - if (!stateMachines.containsValue(checkpoint)) { - deserializeFiber(checkpoint, logger)?.let { - initFiber(it) - stateMachines[it] = checkpoint - } - } - true - } - } - } - - private fun resumeRestoredFibers() { - mutex.locked { - started = true - stateMachines.keys.forEach { resumeRestoredFiber(it) } - } - serviceHub.networkService.addMessageHandler(sessionTopic) { message, _ -> - executor.checkOnThread() - onSessionMessage(message) - } - } - - private fun resumeRestoredFiber(fiber: FlowStateMachineImpl<*>) { - fiber.openSessions.values.forEach { openSessions[it.ourSessionId] = it } - val waitingForResponse = fiber.waitingForResponse - if (waitingForResponse != null) { - if (waitingForResponse is WaitForLedgerCommit) { - val stx = database.transaction { - serviceHub.validatedTransactions.getTransaction(waitingForResponse.hash) - } - if (stx != null) { - fiber.logger.info("Resuming fiber as tx ${waitingForResponse.hash} has committed") - fiber.waitingForResponse = null - resumeFiber(fiber) - } else { - fiber.logger.info("Restored, pending on ledger commit of ${waitingForResponse.hash}") - mutex.locked { fibersWaitingForLedgerCommit.put(waitingForResponse.hash, fiber) } - } - } else { - fiber.logger.info("Restored, pending on receive") - } - } else { - resumeFiber(fiber) - } - } - - private fun onSessionMessage(message: ReceivedMessage) { - val sessionMessage = try { - message.data.deserialize() - } catch (ex: Exception) { - logger.error("Received corrupt SessionMessage data from ${message.peer}") - return - } - val sender = serviceHub.networkMapCache.getPeerByLegalName(message.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") - } - } - - private fun onExistingSessionMessage(message: ExistingSessionMessage, sender: Party) { - val session = openSessions[message.recipientSessionId] - if (session != null) { - session.fiber.logger.trace { "Received $message on $session from $sender" } - if (session.retryable) { - if (message is SessionConfirm && session.state is FlowSessionState.Initiated) { - session.fiber.logger.trace { "Ignoring duplicate confirmation for session ${session.ourSessionId} – session is idempotent" } - return - } - if (message !is SessionConfirm) { - serviceHub.networkService.cancelRedelivery(session.ourSessionId) - } - } - if (message is SessionEnd) { - openSessions.remove(message.recipientSessionId) - } - session.receivedMessages += ReceivedSessionMessage(sender, message) - if (resumeOnMessage(message, session)) { - // It's important that we reset here and not after the fiber's resumed, in case we receive another message - // before then. - session.fiber.waitingForResponse = null - updateCheckpoint(session.fiber) - session.fiber.logger.trace { "Resuming due to $message" } - resumeFiber(session.fiber) - } - } else { - val peerParty = recentlyClosedSessions.remove(message.recipientSessionId) - if (peerParty != null) { - if (message is SessionConfirm) { - logger.trace { "Received session confirmation but associated fiber has already terminated, so sending session end" } - sendSessionMessage(peerParty, NormalSessionEnd(message.initiatedSessionId)) - } else { - logger.trace { "Ignoring session end message for already closed session: $message" } - } - } else { - logger.warn("Received a session message for unknown session: $message, from $sender") - } - } - } - - // We resume the fiber if it's received a response for which it was waiting for or it's waiting for a ledger - // commit but a counterparty flow has ended with an error (in which case our flow also has to end) - private fun resumeOnMessage(message: ExistingSessionMessage, session: FlowSessionInternal): Boolean { - val waitingForResponse = session.fiber.waitingForResponse - return waitingForResponse?.shouldResume(message, session) ?: false - } - - private fun onSessionInit(sessionInit: SessionInit, receivedMessage: ReceivedMessage, sender: Party) { - logger.trace { "Received $sessionInit from $sender" } - val senderSessionId = sessionInit.initiatorSessionId - - fun sendSessionReject(message: String) = sendSessionMessage(sender, SessionReject(senderSessionId, message)) - - val (session, initiatedFlowFactory) = try { - val initiatedFlowFactory = getInitiatedFlowFactory(sessionInit) - val flowSession = FlowSessionImpl(sender) - val flow = initiatedFlowFactory.createFlow(flowSession) - val senderFlowVersion = when (initiatedFlowFactory) { - is InitiatedFlowFactory.Core -> receivedMessage.platformVersion // The flow version for the core flows is the platform version - is InitiatedFlowFactory.CorDapp -> sessionInit.flowVersion - } - val session = FlowSessionInternal( - flow, - flowSession, - random63BitValue(), - sender, - FlowSessionState.Initiated(sender, senderSessionId, FlowInfo(senderFlowVersion, sessionInit.appName))) - if (sessionInit.firstPayload != null) { - 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)) - flowSession.sessionFlow = flow - flowSession.stateMachine = fiber - fiber.openSessions[Pair(flow, sender)] = session - updateCheckpoint(fiber) - session to initiatedFlowFactory - } catch (e: SessionRejectException) { - logger.warn("${e.logMessage}: $sessionInit") - sendSessionReject(e.rejectMessage) - return - } catch (e: Exception) { - logger.warn("Couldn't start flow session from $sessionInit", e) - sendSessionReject("Unable to establish session") - return - } - - val (ourFlowVersion, appName) = when (initiatedFlowFactory) { - // The flow version for the core flows is the platform version - is InitiatedFlowFactory.Core -> serviceHub.myInfo.platformVersion to "corda" - is InitiatedFlowFactory.CorDapp -> initiatedFlowFactory.flowVersion to initiatedFlowFactory.appName - } - - sendSessionMessage(sender, SessionConfirm(senderSessionId, session.ourSessionId, ourFlowVersion, appName), session.fiber) - session.fiber.logger.debug { "Initiated by $sender using ${sessionInit.initiatingFlowClass}" } - session.fiber.logger.trace { "Initiated from $sessionInit on $session" } - resumeFiber(session.fiber) - } - - private fun getInitiatedFlowFactory(sessionInit: SessionInit): InitiatedFlowFactory<*> { - val initiatingFlowClass = try { - Class.forName(sessionInit.initiatingFlowClass, true, classloader).asSubclass(FlowLogic::class.java) - } catch (e: ClassNotFoundException) { - throw SessionRejectException("Don't know ${sessionInit.initiatingFlowClass}") - } catch (e: ClassCastException) { - throw SessionRejectException("${sessionInit.initiatingFlowClass} is not a flow") - } - return serviceHub.getFlowFactory(initiatingFlowClass) ?: - throw SessionRejectException("$initiatingFlowClass is not registered") - } - - private fun serializeFiber(fiber: FlowStateMachineImpl<*>): SerializedBytes> { - return fiber.serialize(context = CHECKPOINT_CONTEXT.withTokenContext(serializationContext)) - } - - private fun deserializeFiber(checkpoint: Checkpoint, logger: Logger): FlowStateMachineImpl<*>? { - return try { - checkpoint.serializedFiber.deserialize(context = CHECKPOINT_CONTEXT.withTokenContext(serializationContext)).apply { - fromCheckpoint = true - } - } catch (t: Throwable) { - logger.error("Encountered unrestorable checkpoint!", t) - null - } - } - - private fun createFiber(logic: FlowLogic, flowInitiator: FlowInitiator, ourIdentity: Party? = null): FlowStateMachineImpl { - val fsm = FlowStateMachineImpl( - StateMachineRunId.createRandom(), - logic, - scheduler, - flowInitiator, - ourIdentity ?: serviceHub.myInfo.legalIdentities[0]) - initFiber(fsm) - return fsm - } - - private fun initFiber(fiber: FlowStateMachineImpl<*>) { - verifyFlowLogicIsSuspendable(fiber.logic) - 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!") - fiber.actionOnSuspend = { ioRequest -> - updateCheckpoint(fiber) - // We commit on the fibers transaction that was copied across ThreadLocals during suspend - // This will free up the ThreadLocal so on return the caller can carry on with other transactions - fiber.commitTransaction() - processIORequest(ioRequest) - decrementLiveFibers() - } - fiber.actionOnEnd = { result, propagated -> - try { - mutex.locked { - stateMachines.remove(fiber)?.let { checkpointStorage.removeCheckpoint(it) } - notifyChangeObservers(Change.Removed(fiber.logic, result)) - } - endAllFiberSessions(fiber, result, propagated) - } finally { - fiber.commitTransaction() - decrementLiveFibers() - totalFinishedFlows.inc() - unfinishedFibers.countDown() - } - } - mutex.locked { - totalStartedFlows.inc() - unfinishedFibers.countUp() - notifyChangeObservers(Change.Add(fiber.logic)) - } - } - - private fun verifyFlowLogicIsSuspendable(logic: FlowLogic) { - // Quasar requires (in Java 8) that at least the call method be annotated suspendable. Unfortunately, it's - // easy to forget to add this when creating a new flow, so we check here to give the user a better error. - // - // The Kotlin compiler can sometimes generate a synthetic bridge method from a single call declaration, which - // forwards to the void method and then returns Unit. However annotations do not get copied across to this - // bridge, so we have to do a more complex scan here. - val call = logic.javaClass.methods.first { !it.isSynthetic && it.name == "call" && it.parameterCount == 0 } - if (call.getAnnotation(Suspendable::class.java) == null) { - throw FlowException("${logic.javaClass.name}.call() is not annotated as @Suspendable. Please fix this.") - } - } - - 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) - true - } else { - false - } - } - } - - private fun FlowSessionInternal.endSession(exception: Throwable?, propagated: Boolean) { - val initiatedState = state as? FlowSessionState.Initiated ?: return - val sessionEnd = if (exception == null) { - NormalSessionEnd(initiatedState.peerSessionId) - } else { - val errorResponse = if (exception is FlowException && (!propagated || initiatingParty != null)) { - // Only propagate this FlowException if our local flow threw it or it was propagated to us and we only - // pass it down invocation chain to the flow that initiated us, not to flows we've started sessions with. - exception - } else { - null - } - ErrorSessionEnd(initiatedState.peerSessionId, errorResponse) - } - sendSessionMessage(initiatedState.peerParty, sessionEnd, fiber) - recentlyClosedSessions[ourSessionId] = initiatedState.peerParty - } - - /** - * Kicks off a brand new state machine of the given class. - * The state machine will be persisted when it suspends, with automated restart if the StateMachineManager is - * restarted with checkpointed state machines in the storage service. - * - * Note that you must be on the [executor] thread. - */ - fun add(logic: FlowLogic, flowInitiator: FlowInitiator, ourIdentity: Party? = null): FlowStateMachineImpl { - // TODO: Check that logic has @Suspendable on its call method. - executor.checkOnThread() - val fiber = database.transaction { - val fiber = createFiber(logic, flowInitiator, ourIdentity) - updateCheckpoint(fiber) - fiber - } - // If we are not started then our checkpoint will be picked up during start - mutex.locked { - if (started) { - resumeFiber(fiber) - } - } - return fiber - } - - private fun updateCheckpoint(fiber: FlowStateMachineImpl<*>) { - check(fiber.state != Strand.State.RUNNING) { "Fiber cannot be running when checkpointing" } - val newCheckpoint = Checkpoint(serializeFiber(fiber)) - val previousCheckpoint = mutex.locked { stateMachines.put(fiber, newCheckpoint) } - if (previousCheckpoint != null) { - checkpointStorage.removeCheckpoint(previousCheckpoint) - } - checkpointStorage.addCheckpoint(newCheckpoint) - checkpointingMeter.mark() - - checkpointCheckerThread?.execute { - // Immediately check that the checkpoint is valid by deserialising it. The idea is to plug any holes we have - // in our testing by failing any test where unrestorable checkpoints are created. - if (deserializeFiber(newCheckpoint, fiber.logger) == null) { - unrestorableCheckpoints = true - } - } - } - - private fun resumeFiber(fiber: FlowStateMachineImpl<*>) { - // Avoid race condition when setting stopping to true and then checking liveFibers - incrementLiveFibers() - if (!stopping) { - executor.executeASAP { - fiber.resume(scheduler) - } - } else { - fiber.logger.trace("Not resuming as SMM is stopping.") - decrementLiveFibers() - } - } - - private fun processIORequest(ioRequest: FlowIORequest) { - executor.checkOnThread() - when (ioRequest) { - is SendRequest -> processSendRequest(ioRequest) - is WaitForLedgerCommit -> processWaitForCommitRequest(ioRequest) - is Sleep -> processSleepRequest(ioRequest) - } - } - - private fun processSendRequest(ioRequest: SendRequest) { - val retryId = if (ioRequest.message is SessionInit) { - with(ioRequest.session) { - openSessions[ourSessionId] = this - if (retryable) ourSessionId else null - } - } else null - sendSessionMessage(ioRequest.session.state.sendToParty, ioRequest.message, ioRequest.session.fiber, retryId) - if (ioRequest !is ReceiveRequest<*>) { - // We sent a message, but don't expect a response, so re-enter the continuation to let it keep going. - resumeFiber(ioRequest.session.fiber) - } - } - - private fun processWaitForCommitRequest(ioRequest: WaitForLedgerCommit) { - // Is it already committed? - val stx = database.transaction { - serviceHub.validatedTransactions.getTransaction(ioRequest.hash) - } - if (stx != null) { - resumeFiber(ioRequest.fiber) - } else { - // No, then register to wait. - // - // We assume this code runs on the server thread, which is the only place transactions are committed - // currently. When we liberalise our threading somewhat, handing of wait requests will need to be - // reworked to make the wait atomic in another way. Otherwise there is a race between checking the - // database and updating the waiting list. - mutex.locked { - fibersWaitingForLedgerCommit[ioRequest.hash] += ioRequest.fiber - } - } - } - - private fun processSleepRequest(ioRequest: Sleep) { - // Resume the fiber now we have checkpointed, so we can sleep on the Fiber. - resumeFiber(ioRequest.fiber) - } - - private fun sendSessionMessage(party: Party, message: SessionMessage, fiber: FlowStateMachineImpl<*>? = null, retryId: Long? = null) { - val partyInfo = serviceHub.networkMapCache.getPartyInfo(party) - ?: throw IllegalArgumentException("Don't know about party $party") - val address = serviceHub.networkService.getAddressOfParty(partyInfo) - val logger = fiber?.logger ?: logger - logger.trace { "Sending $message to party $party @ $address" + if (retryId != null) " with retry $retryId" else "" } - - val serialized = try { - message.serialize() - } catch (e: Exception) { - when (e) { - // Handling Kryo and AMQP serialization problems. Unfortunately the two exception types do not share much of a common exception interface. - is KryoException, - is NotSerializableException -> { - if (message !is ErrorSessionEnd || message.errorResponse == null) throw e - logger.warn("Something in ${message.errorResponse.javaClass.name} is not serialisable. " + - "Instead sending back an exception which is serialisable to ensure session end occurs properly.", e) - // The subclass may have overridden toString so we use that - val exMessage = message.errorResponse.let { if (it.javaClass != FlowException::class.java) it.toString() else it.message } - message.copy(errorResponse = FlowException(exMessage)).serialize() - } - else -> throw e - } - } - - serviceHub.networkService.apply { - send(createMessage(sessionTopic, serialized.bytes), address, retryId = retryId) - } - } -} - -class SessionRejectException(val rejectMessage: String, val logMessage: String) : CordaException(rejectMessage) { - constructor(message: String) : this(message, message) -} +} \ No newline at end of file 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 new file mode 100644 index 0000000000..7fdd7f920c --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt @@ -0,0 +1,634 @@ +package net.corda.node.services.statemachine + +import co.paralleluniverse.fibers.Fiber +import co.paralleluniverse.fibers.FiberExecutorScheduler +import co.paralleluniverse.fibers.Suspendable +import co.paralleluniverse.fibers.instrument.SuspendableHelper +import co.paralleluniverse.strands.Strand +import com.codahale.metrics.Gauge +import com.esotericsoftware.kryo.KryoException +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.crypto.SecureHash +import net.corda.core.crypto.random63BitValue +import net.corda.core.flows.* +import net.corda.core.identity.Party +import net.corda.core.internal.* +import net.corda.core.internal.concurrent.doneFuture +import net.corda.core.messaging.DataFeed +import net.corda.core.serialization.SerializationDefaults.CHECKPOINT_CONTEXT +import net.corda.core.serialization.SerializationDefaults.SERIALIZATION_FACTORY +import net.corda.core.serialization.SerializedBytes +import net.corda.core.serialization.deserialize +import net.corda.core.serialization.serialize +import net.corda.core.utilities.Try +import net.corda.core.utilities.debug +import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.trace +import net.corda.node.internal.InitiatedFlowFactory +import net.corda.node.services.api.Checkpoint +import net.corda.node.services.api.CheckpointStorage +import net.corda.node.services.api.ServiceHubInternal +import net.corda.node.services.messaging.ReceivedMessage +import net.corda.node.services.messaging.TopicSession +import net.corda.node.utilities.AffinityExecutor +import net.corda.node.utilities.CordaPersistence +import net.corda.node.utilities.bufferUntilDatabaseCommit +import net.corda.node.utilities.wrapWithDatabaseTransaction +import net.corda.nodeapi.internal.serialization.SerializeAsTokenContextImpl +import net.corda.nodeapi.internal.serialization.withTokenContext +import org.apache.activemq.artemis.utils.ReusableLatch +import org.slf4j.Logger +import rx.Observable +import rx.subjects.PublishSubject +import java.io.NotSerializableException +import java.util.* +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit.SECONDS +import javax.annotation.concurrent.ThreadSafe + +/** + * The StateMachineManagerImpl will always invoke the flow fibers on the given [AffinityExecutor], regardless of which + * thread actually starts them via [startFlow]. + */ +@ThreadSafe +class StateMachineManagerImpl( + val serviceHub: ServiceHubInternal, + val checkpointStorage: CheckpointStorage, + val executor: AffinityExecutor, + val database: CordaPersistence, + private val unfinishedFibers: ReusableLatch = ReusableLatch(), + private val classloader: ClassLoader = StateMachineManagerImpl::class.java.classLoader +) : StateMachineManager { + inner class FiberScheduler : FiberExecutorScheduler("Same thread scheduler", executor) + + companion object { + private val logger = loggerFor() + internal val sessionTopic = TopicSession("platform.session") + + init { + Fiber.setDefaultUncaughtExceptionHandler { fiber, throwable -> + (fiber as FlowStateMachineImpl<*>).logger.warn("Caught exception from flow", throwable) + } + } + } + + // A list of all the state machines being managed by this class. We expose snapshots of it via the stateMachines + // property. + private class InnerState { + var started = false + val stateMachines = LinkedHashMap, Checkpoint>() + val changesPublisher = PublishSubject.create()!! + val fibersWaitingForLedgerCommit = HashMultimap.create>()!! + + fun notifyChangeObservers(change: StateMachineManager.Change) { + changesPublisher.bufferUntilDatabaseCommit().onNext(change) + } + } + + private val scheduler = FiberScheduler() + private val mutex = ThreadBox(InnerState()) + // This thread (only enabled in dev mode) deserialises checkpoints in the background to shake out bugs in checkpoint restore. + private val checkpointCheckerThread = if (serviceHub.configuration.devMode) Executors.newSingleThreadExecutor() else null + + @Volatile private var unrestorableCheckpoints = false + + // True if we're shutting down, so don't resume anything. + @Volatile private var stopping = false + // How many Fibers are running and not suspended. If zero and stopping is true, then we are halted. + private val liveFibers = ReusableLatch() + + // Monitoring support. + private val metrics = serviceHub.monitoringService.metrics + + init { + metrics.register("Flows.InFlight", Gauge { mutex.content.stateMachines.size }) + } + + private val checkpointingMeter = metrics.meter("Flows.Checkpointing Rate") + private val totalStartedFlows = metrics.counter("Flows.Started") + private val totalFinishedFlows = metrics.counter("Flows.Finished") + + private val openSessions = ConcurrentHashMap() + private val recentlyClosedSessions = ConcurrentHashMap() + + // Context for tokenized services in checkpoints + private lateinit var tokenizableServices: List + private val serializationContext by lazy { + SerializeAsTokenContextImpl(tokenizableServices, SERIALIZATION_FACTORY, CHECKPOINT_CONTEXT, serviceHub) + } + + /** Returns a list of all state machines executing the given flow logic at the top level (subflows do not count) */ + override fun > findStateMachines(flowClass: Class): List>> { + return mutex.locked { + stateMachines.keys.mapNotNull { + flowClass.castIfPossible(it.logic)?.let { it to uncheckedCast, FlowStateMachineImpl<*>>(it.stateMachine).resultFuture } + } + } + } + + override val allStateMachines: List> + get() = mutex.locked { stateMachines.keys.map { it.logic } } + + /** + * An observable that emits triples of the changing flow, the type of change, and a process-specific ID number + * which may change across restarts. + * + * We use assignment here so that multiple subscribers share the same wrapped Observable. + */ + override val changes: Observable = mutex.content.changesPublisher.wrapWithDatabaseTransaction() + + override fun start(tokenizableServices: List) { + this.tokenizableServices = tokenizableServices + checkQuasarJavaAgentPresence() + restoreFibersFromCheckpoints() + listenToLedgerTransactions() + serviceHub.networkMapCache.nodeReady.then { executor.execute(this::resumeRestoredFibers) } + } + + private fun checkQuasarJavaAgentPresence() { + check(SuspendableHelper.isJavaAgentActive(), { + """Missing the '-javaagent' JVM argument. Make sure you run the tests with the Quasar java agent attached to your JVM. + #See https://docs.corda.net/troubleshooting.html - 'Fiber classes not instrumented' for more details.""".trimMargin("#") + }) + } + + private fun listenToLedgerTransactions() { + // Observe the stream of committed, validated transactions and resume fibers that are waiting for them. + serviceHub.validatedTransactions.updates.subscribe { stx -> + val hash = stx.id + val fibers: Set> = mutex.locked { fibersWaitingForLedgerCommit.removeAll(hash) } + if (fibers.isNotEmpty()) { + executor.executeASAP { + for (fiber in fibers) { + fiber.logger.trace { "Transaction $hash has committed to the ledger, resuming" } + fiber.waitingForResponse = null + resumeFiber(fiber) + } + } + } + } + } + + private fun decrementLiveFibers() { + liveFibers.countDown() + } + + private fun incrementLiveFibers() { + liveFibers.countUp() + } + + /** + * Start the shutdown process, bringing the [StateMachineManagerImpl] to a controlled stop. When this method returns, + * all Fibers have been suspended and checkpointed, or have completed. + * + * @param allowedUnsuspendedFiberCount Optional parameter is used in some tests. + */ + override fun stop(allowedUnsuspendedFiberCount: Int) { + require(allowedUnsuspendedFiberCount >= 0) + mutex.locked { + if (stopping) throw IllegalStateException("Already stopping!") + stopping = true + } + // Account for any expected Fibers in a test scenario. + liveFibers.countDown(allowedUnsuspendedFiberCount) + liveFibers.await() + checkpointCheckerThread?.let { MoreExecutors.shutdownAndAwaitTermination(it, 5, SECONDS) } + check(!unrestorableCheckpoints) { "Unrestorable checkpoints where created, please check the logs for details." } + } + + /** + * Atomic get snapshot + subscribe. This is needed so we don't miss updates between subscriptions to [changes] and + * calls to [allStateMachines] + */ + override fun track(): DataFeed>, StateMachineManager.Change> { + return mutex.locked { + DataFeed(stateMachines.keys.map { it.logic }, changesPublisher.bufferUntilSubscribed().wrapWithDatabaseTransaction()) + } + } + + private fun restoreFibersFromCheckpoints() { + mutex.locked { + checkpointStorage.forEach { checkpoint -> + // If a flow is added before start() then don't attempt to restore it + if (!stateMachines.containsValue(checkpoint)) { + deserializeFiber(checkpoint, logger)?.let { + initFiber(it) + stateMachines[it] = checkpoint + } + } + true + } + } + } + + private fun resumeRestoredFibers() { + mutex.locked { + started = true + stateMachines.keys.forEach { resumeRestoredFiber(it) } + } + serviceHub.networkService.addMessageHandler(sessionTopic) { message, _ -> + executor.checkOnThread() + onSessionMessage(message) + } + } + + private fun resumeRestoredFiber(fiber: FlowStateMachineImpl<*>) { + fiber.openSessions.values.forEach { openSessions[it.ourSessionId] = it } + val waitingForResponse = fiber.waitingForResponse + if (waitingForResponse != null) { + if (waitingForResponse is WaitForLedgerCommit) { + val stx = database.transaction { + serviceHub.validatedTransactions.getTransaction(waitingForResponse.hash) + } + if (stx != null) { + fiber.logger.info("Resuming fiber as tx ${waitingForResponse.hash} has committed") + fiber.waitingForResponse = null + resumeFiber(fiber) + } else { + fiber.logger.info("Restored, pending on ledger commit of ${waitingForResponse.hash}") + mutex.locked { fibersWaitingForLedgerCommit.put(waitingForResponse.hash, fiber) } + } + } else { + fiber.logger.info("Restored, pending on receive") + } + } else { + resumeFiber(fiber) + } + } + + private fun onSessionMessage(message: ReceivedMessage) { + val sessionMessage = try { + message.data.deserialize() + } catch (ex: Exception) { + logger.error("Received corrupt SessionMessage data from ${message.peer}") + return + } + val sender = serviceHub.networkMapCache.getPeerByLegalName(message.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") + } + } + + private fun onExistingSessionMessage(message: ExistingSessionMessage, sender: Party) { + val session = openSessions[message.recipientSessionId] + if (session != null) { + session.fiber.logger.trace { "Received $message on $session from $sender" } + if (session.retryable) { + if (message is SessionConfirm && session.state is FlowSessionState.Initiated) { + session.fiber.logger.trace { "Ignoring duplicate confirmation for session ${session.ourSessionId} – session is idempotent" } + return + } + if (message !is SessionConfirm) { + serviceHub.networkService.cancelRedelivery(session.ourSessionId) + } + } + if (message is SessionEnd) { + openSessions.remove(message.recipientSessionId) + } + session.receivedMessages += ReceivedSessionMessage(sender, message) + if (resumeOnMessage(message, session)) { + // It's important that we reset here and not after the fiber's resumed, in case we receive another message + // before then. + session.fiber.waitingForResponse = null + updateCheckpoint(session.fiber) + session.fiber.logger.trace { "Resuming due to $message" } + resumeFiber(session.fiber) + } + } else { + val peerParty = recentlyClosedSessions.remove(message.recipientSessionId) + if (peerParty != null) { + if (message is SessionConfirm) { + logger.trace { "Received session confirmation but associated fiber has already terminated, so sending session end" } + sendSessionMessage(peerParty, NormalSessionEnd(message.initiatedSessionId)) + } else { + logger.trace { "Ignoring session end message for already closed session: $message" } + } + } else { + logger.warn("Received a session message for unknown session: $message, from $sender") + } + } + } + + // We resume the fiber if it's received a response for which it was waiting for or it's waiting for a ledger + // commit but a counterparty flow has ended with an error (in which case our flow also has to end) + private fun resumeOnMessage(message: ExistingSessionMessage, session: FlowSessionInternal): Boolean { + val waitingForResponse = session.fiber.waitingForResponse + return waitingForResponse?.shouldResume(message, session) ?: false + } + + private fun onSessionInit(sessionInit: SessionInit, receivedMessage: ReceivedMessage, sender: Party) { + logger.trace { "Received $sessionInit from $sender" } + val senderSessionId = sessionInit.initiatorSessionId + + fun sendSessionReject(message: String) = sendSessionMessage(sender, SessionReject(senderSessionId, message)) + + val (session, initiatedFlowFactory) = try { + val initiatedFlowFactory = getInitiatedFlowFactory(sessionInit) + val flowSession = FlowSessionImpl(sender) + val flow = initiatedFlowFactory.createFlow(flowSession) + val senderFlowVersion = when (initiatedFlowFactory) { + is InitiatedFlowFactory.Core -> receivedMessage.platformVersion // The flow version for the core flows is the platform version + is InitiatedFlowFactory.CorDapp -> sessionInit.flowVersion + } + val session = FlowSessionInternal( + flow, + flowSession, + random63BitValue(), + sender, + FlowSessionState.Initiated(sender, senderSessionId, FlowInfo(senderFlowVersion, sessionInit.appName))) + if (sessionInit.firstPayload != null) { + 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)) + flowSession.sessionFlow = flow + flowSession.stateMachine = fiber + fiber.openSessions[Pair(flow, sender)] = session + updateCheckpoint(fiber) + session to initiatedFlowFactory + } catch (e: SessionRejectException) { + logger.warn("${e.logMessage}: $sessionInit") + sendSessionReject(e.rejectMessage) + return + } catch (e: Exception) { + logger.warn("Couldn't start flow session from $sessionInit", e) + sendSessionReject("Unable to establish session") + return + } + + val (ourFlowVersion, appName) = when (initiatedFlowFactory) { + // The flow version for the core flows is the platform version + is InitiatedFlowFactory.Core -> serviceHub.myInfo.platformVersion to "corda" + is InitiatedFlowFactory.CorDapp -> initiatedFlowFactory.flowVersion to initiatedFlowFactory.appName + } + + sendSessionMessage(sender, SessionConfirm(senderSessionId, session.ourSessionId, ourFlowVersion, appName), session.fiber) + session.fiber.logger.debug { "Initiated by $sender using ${sessionInit.initiatingFlowClass}" } + session.fiber.logger.trace { "Initiated from $sessionInit on $session" } + resumeFiber(session.fiber) + } + + private fun getInitiatedFlowFactory(sessionInit: SessionInit): InitiatedFlowFactory<*> { + val initiatingFlowClass = try { + Class.forName(sessionInit.initiatingFlowClass, true, classloader).asSubclass(FlowLogic::class.java) + } catch (e: ClassNotFoundException) { + throw SessionRejectException("Don't know ${sessionInit.initiatingFlowClass}") + } catch (e: ClassCastException) { + throw SessionRejectException("${sessionInit.initiatingFlowClass} is not a flow") + } + return serviceHub.getFlowFactory(initiatingFlowClass) ?: + throw SessionRejectException("$initiatingFlowClass is not registered") + } + + private fun serializeFiber(fiber: FlowStateMachineImpl<*>): SerializedBytes> { + return fiber.serialize(context = CHECKPOINT_CONTEXT.withTokenContext(serializationContext)) + } + + private fun deserializeFiber(checkpoint: Checkpoint, logger: Logger): FlowStateMachineImpl<*>? { + return try { + checkpoint.serializedFiber.deserialize(context = CHECKPOINT_CONTEXT.withTokenContext(serializationContext)).apply { + fromCheckpoint = true + } + } catch (t: Throwable) { + logger.error("Encountered unrestorable checkpoint!", t) + null + } + } + + private fun createFiber(logic: FlowLogic, flowInitiator: FlowInitiator, ourIdentity: Party? = null): FlowStateMachineImpl { + val fsm = FlowStateMachineImpl( + StateMachineRunId.createRandom(), + logic, + scheduler, + flowInitiator, + ourIdentity ?: serviceHub.myInfo.legalIdentities[0]) + initFiber(fsm) + return fsm + } + + private fun initFiber(fiber: FlowStateMachineImpl<*>) { + verifyFlowLogicIsSuspendable(fiber.logic) + 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!") + fiber.actionOnSuspend = { ioRequest -> + updateCheckpoint(fiber) + // We commit on the fibers transaction that was copied across ThreadLocals during suspend + // This will free up the ThreadLocal so on return the caller can carry on with other transactions + fiber.commitTransaction() + processIORequest(ioRequest) + decrementLiveFibers() + } + fiber.actionOnEnd = { result, propagated -> + try { + mutex.locked { + stateMachines.remove(fiber)?.let { checkpointStorage.removeCheckpoint(it) } + notifyChangeObservers(StateMachineManager.Change.Removed(fiber.logic, result)) + } + endAllFiberSessions(fiber, result, propagated) + } finally { + fiber.commitTransaction() + decrementLiveFibers() + totalFinishedFlows.inc() + unfinishedFibers.countDown() + } + } + mutex.locked { + totalStartedFlows.inc() + unfinishedFibers.countUp() + notifyChangeObservers(StateMachineManager.Change.Add(fiber.logic)) + } + } + + private fun verifyFlowLogicIsSuspendable(logic: FlowLogic) { + // Quasar requires (in Java 8) that at least the call method be annotated suspendable. Unfortunately, it's + // easy to forget to add this when creating a new flow, so we check here to give the user a better error. + // + // The Kotlin compiler can sometimes generate a synthetic bridge method from a single call declaration, which + // forwards to the void method and then returns Unit. However annotations do not get copied across to this + // bridge, so we have to do a more complex scan here. + val call = logic.javaClass.methods.first { !it.isSynthetic && it.name == "call" && it.parameterCount == 0 } + if (call.getAnnotation(Suspendable::class.java) == null) { + throw FlowException("${logic.javaClass.name}.call() is not annotated as @Suspendable. Please fix this.") + } + } + + 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) + true + } else { + false + } + } + } + + private fun FlowSessionInternal.endSession(exception: Throwable?, propagated: Boolean) { + val initiatedState = state as? FlowSessionState.Initiated ?: return + val sessionEnd = if (exception == null) { + NormalSessionEnd(initiatedState.peerSessionId) + } else { + val errorResponse = if (exception is FlowException && (!propagated || initiatingParty != null)) { + // Only propagate this FlowException if our local flow threw it or it was propagated to us and we only + // pass it down invocation chain to the flow that initiated us, not to flows we've started sessions with. + exception + } else { + null + } + ErrorSessionEnd(initiatedState.peerSessionId, errorResponse) + } + sendSessionMessage(initiatedState.peerParty, sessionEnd, fiber) + recentlyClosedSessions[ourSessionId] = initiatedState.peerParty + } + + /** + * Kicks off a brand new state machine of the given class. + * The state machine will be persisted when it suspends, with automated restart if the StateMachineManager is + * restarted with checkpointed state machines in the storage service. + * + * Note that you must be on the [executor] thread. + */ + override fun startFlow(flowLogic: FlowLogic, flowInitiator: FlowInitiator, ourIdentity: Party?): CordaFuture> { + // TODO: Check that logic has @Suspendable on its call method. + executor.checkOnThread() + val fiber = database.transaction { + val fiber = createFiber(flowLogic, flowInitiator, ourIdentity) + updateCheckpoint(fiber) + fiber + } + // If we are not started then our checkpoint will be picked up during start + mutex.locked { + if (started) { + resumeFiber(fiber) + } + } + return doneFuture(fiber) + } + + private fun updateCheckpoint(fiber: FlowStateMachineImpl<*>) { + check(fiber.state != Strand.State.RUNNING) { "Fiber cannot be running when checkpointing" } + val newCheckpoint = Checkpoint(serializeFiber(fiber)) + val previousCheckpoint = mutex.locked { stateMachines.put(fiber, newCheckpoint) } + if (previousCheckpoint != null) { + checkpointStorage.removeCheckpoint(previousCheckpoint) + } + checkpointStorage.addCheckpoint(newCheckpoint) + checkpointingMeter.mark() + + checkpointCheckerThread?.execute { + // Immediately check that the checkpoint is valid by deserialising it. The idea is to plug any holes we have + // in our testing by failing any test where unrestorable checkpoints are created. + if (deserializeFiber(newCheckpoint, fiber.logger) == null) { + unrestorableCheckpoints = true + } + } + } + + private fun resumeFiber(fiber: FlowStateMachineImpl<*>) { + // Avoid race condition when setting stopping to true and then checking liveFibers + incrementLiveFibers() + if (!stopping) { + executor.executeASAP { + fiber.resume(scheduler) + } + } else { + fiber.logger.trace("Not resuming as SMM is stopping.") + decrementLiveFibers() + } + } + + private fun processIORequest(ioRequest: FlowIORequest) { + executor.checkOnThread() + when (ioRequest) { + is SendRequest -> processSendRequest(ioRequest) + is WaitForLedgerCommit -> processWaitForCommitRequest(ioRequest) + is Sleep -> processSleepRequest(ioRequest) + } + } + + private fun processSendRequest(ioRequest: SendRequest) { + val retryId = if (ioRequest.message is SessionInit) { + with(ioRequest.session) { + openSessions[ourSessionId] = this + if (retryable) ourSessionId else null + } + } else null + sendSessionMessage(ioRequest.session.state.sendToParty, ioRequest.message, ioRequest.session.fiber, retryId) + if (ioRequest !is ReceiveRequest<*>) { + // We sent a message, but don't expect a response, so re-enter the continuation to let it keep going. + resumeFiber(ioRequest.session.fiber) + } + } + + private fun processWaitForCommitRequest(ioRequest: WaitForLedgerCommit) { + // Is it already committed? + val stx = database.transaction { + serviceHub.validatedTransactions.getTransaction(ioRequest.hash) + } + if (stx != null) { + resumeFiber(ioRequest.fiber) + } else { + // No, then register to wait. + // + // We assume this code runs on the server thread, which is the only place transactions are committed + // currently. When we liberalise our threading somewhat, handing of wait requests will need to be + // reworked to make the wait atomic in another way. Otherwise there is a race between checking the + // database and updating the waiting list. + mutex.locked { + fibersWaitingForLedgerCommit[ioRequest.hash] += ioRequest.fiber + } + } + } + + private fun processSleepRequest(ioRequest: Sleep) { + // Resume the fiber now we have checkpointed, so we can sleep on the Fiber. + resumeFiber(ioRequest.fiber) + } + + private fun sendSessionMessage(party: Party, message: SessionMessage, fiber: FlowStateMachineImpl<*>? = null, retryId: Long? = null) { + val partyInfo = serviceHub.networkMapCache.getPartyInfo(party) + ?: throw IllegalArgumentException("Don't know about party $party") + val address = serviceHub.networkService.getAddressOfParty(partyInfo) + val logger = fiber?.logger ?: logger + logger.trace { "Sending $message to party $party @ $address" + if (retryId != null) " with retry $retryId" else "" } + + val serialized = try { + message.serialize() + } catch (e: Exception) { + when (e) { + // Handling Kryo and AMQP serialization problems. Unfortunately the two exception types do not share much of a common exception interface. + is KryoException, + is NotSerializableException -> { + if (message !is ErrorSessionEnd || message.errorResponse == null) throw e + logger.warn("Something in ${message.errorResponse.javaClass.name} is not serialisable. " + + "Instead sending back an exception which is serialisable to ensure session end occurs properly.", e) + // The subclass may have overridden toString so we use that + val exMessage = message.errorResponse.let { if (it.javaClass != FlowException::class.java) it.toString() else it.message } + message.copy(errorResponse = FlowException(exMessage)).serialize() + } + else -> throw e + } + } + + serviceHub.networkService.apply { + send(createMessage(sessionTopic, serialized.bytes), address, retryId = retryId) + } + } +} + +class SessionRejectException(val rejectMessage: String, val logMessage: String) : CordaException(rejectMessage) { + constructor(message: String) : this(message, message) +} 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 d5a189b0ee..934cc2cb83 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 @@ -269,7 +269,7 @@ class NodeVaultService(private val clock: Clock, private val keyManagementServic update.where(stateStatusPredication, lockIdPredicate, *commonPredicates) } if (updatedRows > 0 && updatedRows == stateRefs.size) { - log.trace("Reserving soft lock states for $lockId: $stateRefs") + log.trace { "Reserving soft lock states for $lockId: $stateRefs" } FlowStateMachineImpl.currentStateMachine()?.hasSoftLockedStates = true } else { // revert partial soft locks @@ -280,7 +280,7 @@ class NodeVaultService(private val clock: Clock, private val keyManagementServic update.where(lockUpdateTime, lockIdPredicate, *commonPredicates) } if (revertUpdatedRows > 0) { - log.trace("Reverting $revertUpdatedRows partially soft locked states for $lockId") + log.trace { "Reverting $revertUpdatedRows partially soft locked states for $lockId" } } throw StatesNotAvailableException("Attempted to reserve $stateRefs for $lockId but only $updatedRows rows available") } @@ -309,7 +309,7 @@ class NodeVaultService(private val clock: Clock, private val keyManagementServic update.where(*commonPredicates) } if (update > 0) { - log.trace("Releasing $update soft locked states for $lockId") + log.trace { "Releasing $update soft locked states for $lockId" } } } else { try { @@ -320,7 +320,7 @@ class NodeVaultService(private val clock: Clock, private val keyManagementServic update.where(*commonPredicates, stateRefsPredicate) } if (updatedRows > 0) { - log.trace("Releasing $updatedRows soft locked states for $lockId and stateRefs $stateRefs") + log.trace { "Releasing $updatedRows soft locked states for $lockId and stateRefs $stateRefs" } } } catch (e: Exception) { log.error("""soft lock update error attempting to release states for $lockId and $stateRefs") diff --git a/node/src/main/kotlin/net/corda/node/services/vault/VaultSoftLockManager.kt b/node/src/main/kotlin/net/corda/node/services/vault/VaultSoftLockManager.kt index 2d063a1468..8bf233589b 100644 --- a/node/src/main/kotlin/net/corda/node/services/vault/VaultSoftLockManager.kt +++ b/node/src/main/kotlin/net/corda/node/services/vault/VaultSoftLockManager.kt @@ -1,8 +1,8 @@ package net.corda.node.services.vault +import net.corda.core.contracts.FungibleAsset import net.corda.core.contracts.StateRef import net.corda.core.flows.FlowLogic -import net.corda.core.flows.StateMachineRunId import net.corda.core.node.services.VaultService import net.corda.core.utilities.NonEmptySet import net.corda.core.utilities.loggerFor @@ -12,50 +12,50 @@ import net.corda.node.services.statemachine.FlowStateMachineImpl import net.corda.node.services.statemachine.StateMachineManager import java.util.* -class VaultSoftLockManager(val vault: VaultService, smm: StateMachineManager) { - - private companion object { - val log = loggerFor() - } - - init { - smm.changes.subscribe { change -> - if (change is StateMachineManager.Change.Removed && (FlowStateMachineImpl.currentStateMachine())?.hasSoftLockedStates == true) { - log.trace { "Remove flow name ${change.logic.javaClass} with id $change.id" } - unregisterSoftLocks(change.logic.runId, change.logic) +class VaultSoftLockManager private constructor(private val vault: VaultService) { + companion object { + private val log = loggerFor() + @JvmStatic + fun install(vault: VaultService, smm: StateMachineManager) { + val manager = VaultSoftLockManager(vault) + smm.changes.subscribe { change -> + if (change is StateMachineManager.Change.Removed) { + val logic = change.logic + // Don't run potentially expensive query if the flow didn't lock any states: + if ((logic.stateMachine as FlowStateMachineImpl<*>).hasSoftLockedStates) { + manager.unregisterSoftLocks(logic.runId.uuid, logic) + } + } } - } - - // Discussion - // - // The intent of the following approach is to support what might be a common pattern in a flow: - // 1. Create state - // 2. Do something with state - // without possibility of another flow intercepting the state between 1 and 2, - // since we cannot lock the state before it exists. e.g. Issue and then Move some Cash. - // - // The downside is we could have a long running flow that holds a lock for a long period of time. - // However, the lock can be programmatically released, like any other soft lock, - // should we want a long running flow that creates a visible state mid way through. - - vault.rawUpdates.subscribe { (_, produced, flowId) -> - flowId?.let { - if (produced.isNotEmpty()) { - registerSoftLocks(flowId, (produced.map { it.ref }).toNonEmptySet()) + // Discussion + // + // The intent of the following approach is to support what might be a common pattern in a flow: + // 1. Create state + // 2. Do something with state + // without possibility of another flow intercepting the state between 1 and 2, + // since we cannot lock the state before it exists. e.g. Issue and then Move some Cash. + // + // The downside is we could have a long running flow that holds a lock for a long period of time. + // However, the lock can be programmatically released, like any other soft lock, + // should we want a long running flow that creates a visible state mid way through. + vault.rawUpdates.subscribe { (_, produced, flowId) -> + if (flowId != null) { + val fungible = produced.filter { it.state.data is FungibleAsset<*> } + if (fungible.isNotEmpty()) { + manager.registerSoftLocks(flowId, fungible.map { it.ref }.toNonEmptySet()) + } } } } } private fun registerSoftLocks(flowId: UUID, stateRefs: NonEmptySet) { - log.trace("Reserving soft locks for flow id $flowId and states $stateRefs") + log.trace { "Reserving soft locks for flow id $flowId and states $stateRefs" } vault.softLockReserve(flowId, stateRefs) } - private fun unregisterSoftLocks(id: StateMachineRunId, logic: FlowLogic<*>) { - val flowClassName = logic.javaClass.simpleName - log.trace("Releasing soft locks for flow $flowClassName with flow id ${id.uuid}") - vault.softLockRelease(id.uuid) - + private fun unregisterSoftLocks(flowId: UUID, logic: FlowLogic<*>) { + log.trace { "Releasing soft locks for flow ${logic.javaClass.simpleName} with flow id $flowId" } + vault.softLockRelease(flowId) } } \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/shell/InteractiveShell.kt b/node/src/main/kotlin/net/corda/node/shell/InteractiveShell.kt index a671fdb1dd..d818e30b8f 100644 --- a/node/src/main/kotlin/net/corda/node/shell/InteractiveShell.kt +++ b/node/src/main/kotlin/net/corda/node/shell/InteractiveShell.kt @@ -7,23 +7,21 @@ import com.fasterxml.jackson.databind.* import com.fasterxml.jackson.databind.module.SimpleModule import com.fasterxml.jackson.dataformat.yaml.YAMLFactory import com.google.common.io.Closeables +import net.corda.client.jackson.JacksonSupport +import net.corda.client.jackson.StringToMethodCallParser +import net.corda.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.FlowStateMachine +import net.corda.core.internal.* import net.corda.core.internal.concurrent.OpenFuture import net.corda.core.internal.concurrent.openFuture -import net.corda.core.internal.createDirectories -import net.corda.core.internal.div -import net.corda.core.internal.write -import net.corda.core.internal.* import net.corda.core.messaging.CordaRPCOps +import net.corda.core.messaging.DataFeed import net.corda.core.messaging.StateMachineUpdate +import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.loggerFor -import net.corda.client.jackson.JacksonSupport -import net.corda.client.jackson.StringToMethodCallParser -import net.corda.core.CordaException import net.corda.node.internal.Node import net.corda.node.internal.StartedNode import net.corda.node.services.messaging.CURRENT_RPC_CONTEXT @@ -200,7 +198,7 @@ object InteractiveShell { } private fun createOutputMapper(factory: JsonFactory): ObjectMapper { - return JacksonSupport.createNonRpcMapper(factory).apply({ + return JacksonSupport.createNonRpcMapper(factory).apply { // Register serializers for stateful objects from libraries that are special to the RPC system and don't // make sense to print out to the screen. For classes we own, annotations can be used instead. val rpcModule = SimpleModule() @@ -210,7 +208,7 @@ object InteractiveShell { disable(SerializationFeature.FAIL_ON_EMPTY_BEANS) enable(SerializationFeature.INDENT_OUTPUT) - }) + } } // TODO: This should become the default renderer rather than something used specifically by commands. @@ -237,7 +235,7 @@ object InteractiveShell { val clazz: Class> = uncheckedCast(matches.single()) try { // TODO Flow invocation should use startFlowDynamic. - val fsm = runFlowFromString({ node.services.startFlow(it, FlowInitiator.Shell) }, inputData, clazz) + val fsm = runFlowFromString({ node.services.startFlow(it, FlowInitiator.Shell).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) @@ -397,7 +395,7 @@ object InteractiveShell { } private fun printAndFollowRPCResponse(response: Any?, toStream: PrintWriter): CordaFuture? { - val printerFun = { obj: Any? -> yamlMapper.writeValueAsString(obj) } + val printerFun = yamlMapper::writeValueAsString toStream.println(printerFun(response)) toStream.flush() return maybeFollow(response, printerFun, toStream) @@ -443,13 +441,9 @@ object InteractiveShell { val observable: Observable<*> = when (response) { is Observable<*> -> response - is Pair<*, *> -> when { - response.first is Observable<*> -> response.first as Observable<*> - response.second is Observable<*> -> response.second as Observable<*> - else -> null - } - else -> null - } ?: return null + is DataFeed<*, *> -> response.updates + else -> return null + } val subscriber = PrintingSubscriber(printerFun, toStream) uncheckedCast(observable).subscribe(subscriber) @@ -500,8 +494,8 @@ object InteractiveShell { gen.writeString("") } else { val path = Paths.get(toPath) - path.write { value.copyTo(it) } - gen.writeString("") + value.copyTo(path) + gen.writeString("") } } finally { try { diff --git a/node/src/main/kotlin/net/corda/node/utilities/CordaPersistence.kt b/node/src/main/kotlin/net/corda/node/utilities/CordaPersistence.kt index 77055c6073..a8c85d105d 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/CordaPersistence.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/CordaPersistence.kt @@ -20,6 +20,14 @@ import java.util.concurrent.CopyOnWriteArrayList */ const val NODE_DATABASE_PREFIX = "node_" +/** + * The maximum supported field-size for hash HEX-encoded outputs (e.g. database fields). + * This value is enough to support hash functions with outputs up to 512 bits (e.g. SHA3-512), in which + * case 128 HEX characters are required. + * 130 was selected instead of 128, to allow for 2 extra characters that will be used as hash-scheme identifiers. + */ +internal const val MAX_HASH_HEX_SIZE = 130 + //HikariDataSource implements Closeable which allows CordaPersistence to be Closeable class CordaPersistence(var dataSource: HikariDataSource, private val schemaService: SchemaService, private val createIdentityService: () -> IdentityService, databaseProperties: Properties) : Closeable { diff --git a/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt b/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt index 0020970296..ae39004b31 100644 --- a/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt +++ b/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt @@ -29,9 +29,12 @@ import net.corda.node.services.FlowPermissions.Companion.startFlowPermission 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.* +import net.corda.testing.chooseIdentity +import net.corda.testing.expect +import net.corda.testing.expectEvents 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 @@ -65,14 +68,13 @@ class CordaRPCOpsImplTest { mockNet = MockNetwork(cordappPackages = listOf("net.corda.finance.contracts.asset")) aliceNode = mockNet.createNode() notaryNode = mockNet.createNotaryNode(validating = false) - rpc = CordaRPCOpsImpl(aliceNode.services, aliceNode.smm, aliceNode.database) + rpc = CordaRPCOpsImpl(aliceNode.services, aliceNode.smm, aliceNode.database, aliceNode.services) CURRENT_RPC_CONTEXT.set(RpcContext(User("user", "pwd", permissions = setOf( startFlowPermission(), startFlowPermission() )))) mockNet.runNetwork() - mockNet.networkMapNode.internals.ensureRegistered() notary = rpc.notaryIdentities().first() } diff --git a/node/src/test/kotlin/net/corda/node/InteractiveShellTest.kt b/node/src/test/kotlin/net/corda/node/InteractiveShellTest.kt index 54117732cf..622fc9667c 100644 --- a/node/src/test/kotlin/net/corda/node/InteractiveShellTest.kt +++ b/node/src/test/kotlin/net/corda/node/InteractiveShellTest.kt @@ -1,7 +1,6 @@ package net.corda.node import com.fasterxml.jackson.dataformat.yaml.YAMLFactory -import com.nhaarman.mockito_kotlin.mock import net.corda.client.jackson.JacksonSupport import net.corda.core.contracts.Amount import net.corda.core.crypto.SecureHash @@ -17,6 +16,7 @@ import net.corda.testing.MEGA_CORP import net.corda.testing.MEGA_CORP_IDENTITY import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices.Companion.makeTestIdentityService +import net.corda.testing.rigorousMock import org.junit.After import org.junit.Before import org.junit.Test @@ -52,7 +52,7 @@ class InteractiveShellTest { private fun check(input: String, expected: String) { var output: DummyFSM? = null InteractiveShell.runFlowFromString({ DummyFSM(it as FlowA).apply { output = this } }, input, FlowA::class.java, om) - assertEquals(expected, output!!.flowA.a, input) + assertEquals(expected, output!!.logic.a, input) } @Test @@ -83,5 +83,5 @@ class InteractiveShellTest { @Test fun party() = check("party: \"${MEGA_CORP.name}\"", MEGA_CORP.name.toString()) - class DummyFSM(val flowA: FlowA) : FlowStateMachine by mock() + class DummyFSM(override val logic: FlowA) : FlowStateMachine by rigorousMock() } diff --git a/node/src/test/kotlin/net/corda/node/internal/cordapp/CordappProviderImplTests.kt b/node/src/test/kotlin/net/corda/node/internal/cordapp/CordappProviderImplTests.kt index 9f7833f928..af830013d4 100644 --- a/node/src/test/kotlin/net/corda/node/internal/cordapp/CordappProviderImplTests.kt +++ b/node/src/test/kotlin/net/corda/node/internal/cordapp/CordappProviderImplTests.kt @@ -1,6 +1,5 @@ package net.corda.node.internal.cordapp -import com.nhaarman.mockito_kotlin.mock import net.corda.core.node.services.AttachmentStorage import net.corda.testing.node.MockAttachmentStorage import org.junit.Assert @@ -40,7 +39,7 @@ class CordappProviderImplTests { @Test fun `test that we find a cordapp class that is loaded into the store`() { val loader = CordappLoader.createDevMode(listOf(isolatedJAR)) - val provider = CordappProviderImpl(loader, mock()) + val provider = CordappProviderImpl(loader, attachmentStore) val className = "net.corda.finance.contracts.isolated.AnotherDummyContract" val expected = provider.cordapps.first() diff --git a/node/src/test/kotlin/net/corda/node/messaging/InMemoryMessagingTests.kt b/node/src/test/kotlin/net/corda/node/messaging/InMemoryMessagingTests.kt index b3a3fb32e4..f5e842347e 100644 --- a/node/src/test/kotlin/net/corda/node/messaging/InMemoryMessagingTests.kt +++ b/node/src/test/kotlin/net/corda/node/messaging/InMemoryMessagingTests.kt @@ -47,23 +47,17 @@ class InMemoryMessagingTests { @Test fun basics() { - val node1 = mockNet.networkMapNode + val node1 = mockNet.createNode() val node2 = mockNet.createNode() val node3 = mockNet.createNode() val bits = "test-content".toByteArray() var finalDelivery: Message? = null - - with(node2) { - node2.network.addMessageHandler { msg, _ -> - node2.network.send(msg, node3.network.myAddress) - } + node2.network.addMessageHandler { msg, _ -> + node2.network.send(msg, node3.network.myAddress) } - - with(node3) { - node2.network.addMessageHandler { msg, _ -> - finalDelivery = msg - } + node3.network.addMessageHandler { msg, _ -> + finalDelivery = msg } // Node 1 sends a message and it should end up in finalDelivery, after we run the network @@ -76,7 +70,7 @@ class InMemoryMessagingTests { @Test fun broadcast() { - val node1 = mockNet.networkMapNode + val node1 = mockNet.createNode() val node2 = mockNet.createNode() val node3 = mockNet.createNode() @@ -95,7 +89,7 @@ class InMemoryMessagingTests { */ @Test fun `skip unhandled messages`() { - val node1 = mockNet.networkMapNode + val node1 = mockNet.createNode() val node2 = mockNet.createNode() var received = 0 diff --git a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt index 93edb33b92..c5f893965e 100644 --- a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt @@ -13,7 +13,6 @@ import net.corda.core.internal.FlowStateMachine import net.corda.core.internal.concurrent.map import net.corda.core.internal.rootCause import net.corda.core.messaging.DataFeed -import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.messaging.StateMachineTransactionMapping import net.corda.core.node.services.Vault import net.corda.core.serialization.CordaSerializable @@ -35,17 +34,12 @@ import net.corda.finance.flows.TwoPartyTradeFlow.Buyer import net.corda.finance.flows.TwoPartyTradeFlow.Seller import net.corda.node.internal.StartedNode import net.corda.node.services.api.WritableTransactionStorage -import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.persistence.DBTransactionStorage import net.corda.node.services.persistence.checkpoints import net.corda.node.utilities.CordaPersistence -import net.corda.nodeapi.internal.ServiceInfo import net.corda.testing.* import net.corda.testing.contracts.fillWithSomeTestCash -import net.corda.testing.node.InMemoryMessagingNetwork -import net.corda.testing.node.MockNetwork -import net.corda.testing.node.MockServices -import net.corda.testing.node.pumpReceive +import net.corda.testing.node.* import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Before @@ -55,8 +49,6 @@ import org.junit.runners.Parameterized import rx.Observable import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream -import java.math.BigInteger -import java.security.KeyPair import java.util.* import java.util.jar.JarOutputStream import java.util.zip.ZipEntry @@ -75,7 +67,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { companion object { private val cordappPackages = listOf("net.corda.finance.contracts") @JvmStatic - @Parameterized.Parameters + @Parameterized.Parameters(name = "Anonymous = {0}") fun data(): Collection { return listOf(true, false) } @@ -99,7 +91,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { // We run this in parallel threads to help catch any race conditions that may exist. The other tests // we run in the unit test thread exclusively to speed things up, ensure deterministic results and // allow interruption half way through. - mockNet = MockNetwork(false, true, cordappPackages = cordappPackages) + mockNet = MockNetwork(threadPerNode = true, cordappPackages = cordappPackages) ledger(MockServices(cordappPackages), initialiseSerialization = false) { val notaryNode = mockNet.createNotaryNode() val aliceNode = mockNet.createPartyNode(ALICE_NAME) @@ -149,7 +141,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { @Test(expected = InsufficientBalanceException::class) fun `trade cash for commercial paper fails using soft locking`() { - mockNet = MockNetwork(false, true, cordappPackages = cordappPackages) + mockNet = MockNetwork(threadPerNode = true, cordappPackages = cordappPackages) ledger(MockServices(cordappPackages), initialiseSerialization = false) { val notaryNode = mockNet.createNotaryNode() val aliceNode = mockNet.createPartyNode(ALICE_NAME) @@ -205,7 +197,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { @Test fun `shutdown and restore`() { - mockNet = MockNetwork(false, cordappPackages = cordappPackages) + mockNet = MockNetwork(cordappPackages = cordappPackages) ledger(MockServices(cordappPackages), initialiseSerialization = false) { val notaryNode = mockNet.createNotaryNode() val aliceNode = mockNet.createPartyNode(ALICE_NAME) @@ -268,13 +260,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { // ... bring the node back up ... the act of constructing the SMM will re-register the message handlers // that Bob was waiting on before the reboot occurred. - bobNode = mockNet.createNode(bobAddr.id, object : MockNetwork.Factory { - override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, - id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): MockNetwork.MockNode { - return MockNetwork.MockNode(config, network, networkMapAddr, bobAddr.id, notaryIdentity, entropyRoot) - } - }, BOB_NAME) - + bobNode = mockNet.createNode(MockNodeParameters(bobAddr.id, BOB_NAME)) // Find the future representing the result of this state machine again. val bobFuture = bobNode.smm.findStateMachines(BuyerAcceptor::class.java).single().second @@ -308,31 +294,26 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { // of gets and puts. private fun makeNodeWithTracking(name: CordaX500Name): StartedNode { // Create a node in the mock network ... - return mockNet.createNode(nodeFactory = object : MockNetwork.Factory { - override fun create(config: NodeConfiguration, - network: MockNetwork, - networkMapAddr: SingleMessageRecipient?, - id: Int, notaryIdentity: Pair?, - entropyRoot: BigInteger): MockNetwork.MockNode { - return object : MockNetwork.MockNode(config, network, networkMapAddr, id, notaryIdentity, entropyRoot) { + return mockNet.createNode(MockNodeParameters(legalName = name), nodeFactory = object : MockNetwork.Factory { + override fun create(args: MockNodeArgs): MockNetwork.MockNode { + return object : MockNetwork.MockNode(args) { // That constructs a recording tx storage override fun makeTransactionStorage(): WritableTransactionStorage { return RecordingTransactionStorage(database, super.makeTransactionStorage()) } } } - }, legalName = name) + }) } @Test fun `check dependencies of sale asset are resolved`() { - mockNet = MockNetwork(false, cordappPackages = cordappPackages) + mockNet = MockNetwork(cordappPackages = cordappPackages) val notaryNode = mockNet.createNotaryNode() val aliceNode = makeNodeWithTracking(ALICE_NAME) val bobNode = makeNodeWithTracking(BOB_NAME) val bankNode = makeNodeWithTracking(BOC_NAME) mockNet.runNetwork() - notaryNode.internals.ensureRegistered() val notary = aliceNode.services.getDefaultNotary() val alice = aliceNode.info.singleIdentity() val bob = bobNode.info.singleIdentity() @@ -433,14 +414,13 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { @Test fun `track works`() { - mockNet = MockNetwork(false, cordappPackages = cordappPackages) + mockNet = MockNetwork(cordappPackages = cordappPackages) val notaryNode = mockNet.createNotaryNode() val aliceNode = makeNodeWithTracking(ALICE_NAME) val bobNode = makeNodeWithTracking(BOB_NAME) val bankNode = makeNodeWithTracking(BOC_NAME) mockNet.runNetwork() - notaryNode.internals.ensureRegistered() val notary = aliceNode.services.getDefaultNotary() val alice: Party = aliceNode.info.singleIdentity() val bank: Party = bankNode.info.singleIdentity() @@ -515,7 +495,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { @Test fun `dependency with error on buyer side`() { - mockNet = MockNetwork(false, cordappPackages = cordappPackages) + mockNet = MockNetwork(cordappPackages = cordappPackages) ledger(MockServices(cordappPackages), initialiseSerialization = false) { runWithError(true, false, "at least one cash input") } @@ -523,7 +503,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { @Test fun `dependency with error on seller side`() { - mockNet = MockNetwork(false, cordappPackages = cordappPackages) + mockNet = MockNetwork(cordappPackages = cordappPackages) ledger(MockServices(cordappPackages), initialiseSerialization = false) { runWithError(false, true, "Issuances have a time-window") } @@ -596,7 +576,6 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { val bankNode = mockNet.createPartyNode(BOC_NAME) mockNet.runNetwork() - notaryNode.internals.ensureRegistered() val notary = aliceNode.services.getDefaultNotary() val alice = aliceNode.info.singleIdentity() val bob = bobNode.info.singleIdentity() diff --git a/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt b/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt index 58aea5fac8..1beceb0855 100644 --- a/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt @@ -13,7 +13,6 @@ import net.corda.core.transactions.WireTransaction import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.seconds import net.corda.node.internal.StartedNode -import net.corda.node.services.api.ServiceHubInternal import net.corda.testing.* import net.corda.testing.contracts.DummyContract import net.corda.testing.node.MockNetwork @@ -43,7 +42,6 @@ class NotaryChangeTests { clientNodeB = mockNet.createNode() newNotaryNode = mockNet.createNotaryNode(legalName = DUMMY_NOTARY.name.copy(organisation = "Dummy Notary 2")) mockNet.runNetwork() // Clear network map registration messages - oldNotaryNode.internals.ensureRegistered() oldNotaryParty = newNotaryNode.services.networkMapCache.getNotary(DUMMY_NOTARY_SERVICE_NAME)!! newNotaryParty = newNotaryNode.services.networkMapCache.getNotary(DUMMY_NOTARY_SERVICE_NAME.copy(organisation = "Dummy Notary 2"))!! } 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 d54bfa8754..446976faf2 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 @@ -1,6 +1,8 @@ package net.corda.node.services.events import co.paralleluniverse.fibers.Suspendable +import com.codahale.metrics.MetricRegistry +import com.nhaarman.mockito_kotlin.* import net.corda.core.contracts.* import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogicRef @@ -8,35 +10,40 @@ import net.corda.core.flows.FlowLogicRefFactory import net.corda.core.identity.AbstractParty import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party +import net.corda.core.node.NodeInfo import net.corda.core.node.ServiceHub import net.corda.core.serialization.SingletonSerializeAsToken +import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.days +import net.corda.node.internal.FlowStarterImpl +import net.corda.node.internal.StateLoaderImpl import net.corda.node.internal.cordapp.CordappLoader import net.corda.node.internal.cordapp.CordappProviderImpl -import net.corda.node.services.api.VaultServiceInternal +import net.corda.node.services.api.MonitoringService +import net.corda.node.services.api.ServiceHubInternal import net.corda.node.services.identity.InMemoryIdentityService +import net.corda.node.services.network.NetworkMapCacheImpl import net.corda.node.services.persistence.DBCheckpointStorage import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl import net.corda.node.services.statemachine.StateMachineManager +import net.corda.node.services.statemachine.StateMachineManagerImpl import net.corda.node.services.vault.NodeVaultService -import net.corda.node.testing.MockServiceHubInternal import net.corda.node.utilities.AffinityExecutor import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.configureDatabase import net.corda.testing.* import net.corda.testing.contracts.DummyContract -import net.corda.testing.node.InMemoryMessagingNetwork -import net.corda.testing.node.MockKeyManagementService +import net.corda.testing.node.* import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties import net.corda.testing.node.MockServices.Companion.makeTestIdentityService -import net.corda.testing.node.TestClock import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Before import org.junit.Test import java.nio.file.Paths +import java.security.PublicKey import java.time.Clock import java.time.Instant import java.util.concurrent.CountDownLatch @@ -45,20 +52,26 @@ import java.util.concurrent.TimeUnit import kotlin.test.assertTrue class NodeSchedulerServiceTest : SingletonSerializeAsToken() { + companion object { + private val myInfo = NodeInfo(listOf(MOCK_HOST_AND_PORT), listOf(DUMMY_IDENTITY_1), 1, serial = 1L) + } + private val realClock: Clock = Clock.systemUTC() private val stoppedClock: Clock = Clock.fixed(realClock.instant(), realClock.zone) private val testClock = TestClock(stoppedClock) private val schedulerGatedExecutor = AffinityExecutor.Gate(true) - private lateinit var services: MockServiceHubInternal + abstract class Services : ServiceHubInternal, TestReference + private lateinit var services: Services private lateinit var scheduler: NodeSchedulerService private lateinit var smmExecutor: AffinityExecutor.ServiceAffinityExecutor private lateinit var database: CordaPersistence private lateinit var countDown: CountDownLatch private lateinit var smmHasRemovedAllFlows: CountDownLatch - + private lateinit var kms: MockKeyManagementService + private lateinit var mockSMM: StateMachineManager var calls: Int = 0 /** @@ -80,35 +93,35 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() { val databaseProperties = makeTestDatabaseProperties() database = configureDatabase(dataSourceProps, databaseProperties, ::makeTestIdentityService) val identityService = InMemoryIdentityService(trustRoot = DEV_TRUST_ROOT) - val kms = MockKeyManagementService(identityService, ALICE_KEY) - + kms = MockKeyManagementService(identityService, ALICE_KEY) + val configuration = testNodeConfiguration(Paths.get("."), CordaX500Name("Alice", "London", "GB")) + val validatedTransactions = MockTransactionStorage() + val stateLoader = StateLoaderImpl(validatedTransactions) database.transaction { - val nullIdentity = CordaX500Name(organisation = "None", locality = "None", country = "GB") - val mockMessagingService = InMemoryMessagingNetwork(false).InMemoryMessaging( - false, - InMemoryMessagingNetwork.PeerHandle(0, nullIdentity), - AffinityExecutor.ServiceAffinityExecutor("test", 1), - database) - services = object : MockServiceHubInternal( - database, - testNodeConfiguration(Paths.get("."), CordaX500Name(organisation = "Alice", locality = "London", country = "GB")), - overrideClock = testClock, - keyManagement = kms, - network = mockMessagingService), TestReference { - override val vaultService: VaultServiceInternal = NodeVaultService(testClock, kms, stateLoader, database.hibernateConfig) - override val testReference = this@NodeSchedulerServiceTest - override val cordappProvider = CordappProviderImpl(CordappLoader.createWithTestPackages(listOf("net.corda.testing.contracts")), attachments) + services = rigorousMock().also { + doReturn(configuration).whenever(it).configuration + doReturn(MonitoringService(MetricRegistry())).whenever(it).monitoringService + doReturn(validatedTransactions).whenever(it).validatedTransactions + doReturn(NetworkMapCacheImpl(MockNetworkMapCache(database, configuration), identityService)).whenever(it).networkMapCache + doCallRealMethod().whenever(it).signInitialTransaction(any(), any()) + doReturn(myInfo).whenever(it).myInfo + doReturn(kms).whenever(it).keyManagementService + doReturn(CordappProviderImpl(CordappLoader.createWithTestPackages(listOf("net.corda.testing.contracts")), MockAttachmentStorage())).whenever(it).cordappProvider + doCallRealMethod().whenever(it).recordTransactions(any()) + doCallRealMethod().whenever(it).recordTransactions(any(), any()) + doCallRealMethod().whenever(it).recordTransactions(any(), any>()) + doReturn(NodeVaultService(testClock, kms, stateLoader, database.hibernateConfig)).whenever(it).vaultService + doReturn(this@NodeSchedulerServiceTest).whenever(it).testReference } smmExecutor = AffinityExecutor.ServiceAffinityExecutor("test", 1) - scheduler = NodeSchedulerService(services, schedulerGatedExecutor, serverThread = smmExecutor) - val mockSMM = StateMachineManager(services, DBCheckpointStorage(), smmExecutor, database) + mockSMM = StateMachineManagerImpl(services, DBCheckpointStorage(), smmExecutor, database) + scheduler = NodeSchedulerService(testClock, database, FlowStarterImpl(smmExecutor, mockSMM), stateLoader, schedulerGatedExecutor, serverThread = smmExecutor) mockSMM.changes.subscribe { change -> if (change is StateMachineManager.Change.Removed && mockSMM.allStateMachines.isEmpty()) { smmHasRemovedAllFlows.countDown() } } - mockSMM.start() - services.smm = mockSMM + mockSMM.start(emptyList()) scheduler.start() } } @@ -116,7 +129,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() { @After fun tearDown() { // We need to make sure the StateMachineManager is done before shutting down executors. - if (services.smm.allStateMachines.isNotEmpty()) { + if (mockSMM.allStateMachines.isNotEmpty()) { smmHasRemovedAllFlows.await() } smmExecutor.shutdown() @@ -125,6 +138,8 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() { resetTestSerialization() } + // Ignore IntelliJ when it says these properties can be private, if they are we cannot serialise them + // in AMQP. class TestState(val flowLogicRef: FlowLogicRef, val instant: Instant, val myIdentity: Party) : LinearState, SchedulableState { override val participants: List get() = listOf(myIdentity) @@ -136,7 +151,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() { } } - class TestFlowLogic(val increment: Int = 1) : FlowLogic() { + class TestFlowLogic(private val increment: Int = 1) : FlowLogic() { @Suspendable override fun call() { (serviceHub as TestReference).testReference.calls += increment @@ -279,8 +294,8 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() { var scheduledRef: ScheduledStateRef? = null database.transaction { apply { - val freshKey = services.keyManagementService.freshKey() - val state = TestState(FlowLogicRefFactoryImpl.createForRPC(TestFlowLogic::class.java, increment), instant, services.myInfo.chooseIdentity()) + val freshKey = kms.freshKey() + val state = TestState(FlowLogicRefFactoryImpl.createForRPC(TestFlowLogic::class.java, increment), instant, myInfo.chooseIdentity()) val builder = TransactionBuilder(null).apply { addOutputState(state, DummyContract.PROGRAM_ID, DUMMY_NOTARY) addCommand(Command(), freshKey) 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 be2fc12b96..9999b927e0 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 @@ -16,8 +16,11 @@ import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.getOrThrow import net.corda.node.internal.StartedNode import net.corda.node.services.statemachine.StateMachineManager -import net.corda.testing.* +import net.corda.testing.DUMMY_NOTARY +import net.corda.testing.chooseIdentity import net.corda.testing.contracts.DummyContract +import net.corda.testing.dummyCommand +import net.corda.testing.getDefaultNotary import net.corda.testing.node.MockNetwork import org.junit.After import org.junit.Assert.* @@ -96,8 +99,6 @@ class ScheduledFlowTests { val a = mockNet.createUnstartedNode() val b = mockNet.createUnstartedNode() - notaryNode.internals.ensureRegistered() - mockNet.startNodes() nodeA = a.started!! nodeB = b.started!! 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 418322b589..b7fdbd40eb 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 @@ -12,10 +12,10 @@ import net.corda.node.services.RPCUserServiceImpl 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.PersistentNetworkMapCache import net.corda.node.services.network.NetworkMapService import net.corda.node.services.transactions.PersistentUniquenessProvider -import net.corda.node.testing.MockServiceHubInternal import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.configureDatabase @@ -57,7 +57,7 @@ class ArtemisMessagingTests : TestDependencyInjectionBase() { var messagingClient: NodeMessagingClient? = null var messagingServer: ArtemisMessagingServer? = null - lateinit var networkMapCache: PersistentNetworkMapCache + lateinit var networkMapCache: NetworkMapCacheImpl val rpcOps = object : RPCOps { override val protocolVersion: Int get() = throw UnsupportedOperationException() @@ -73,7 +73,7 @@ class ArtemisMessagingTests : TestDependencyInjectionBase() { LogHelper.setLevel(PersistentUniquenessProvider::class) database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), ::makeTestIdentityService) networkMapRegistrationFuture = doneFuture(Unit) - networkMapCache = PersistentNetworkMapCache(serviceHub = object : MockServiceHubInternal(database, config) {}) + networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, config), rigorousMock()) } @After diff --git a/node/src/test/kotlin/net/corda/node/services/network/AbstractNetworkMapServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/network/AbstractNetworkMapServiceTest.kt deleted file mode 100644 index b3b9a9f769..0000000000 --- a/node/src/test/kotlin/net/corda/node/services/network/AbstractNetworkMapServiceTest.kt +++ /dev/null @@ -1,281 +0,0 @@ -package net.corda.node.services.network - -import net.corda.core.concurrent.CordaFuture -import net.corda.core.identity.CordaX500Name -import net.corda.core.messaging.SingleMessageRecipient -import net.corda.core.node.NodeInfo -import net.corda.core.serialization.deserialize -import net.corda.core.utilities.getOrThrow -import net.corda.node.internal.StartedNode -import net.corda.node.services.api.NetworkMapCacheInternal -import net.corda.node.services.config.NodeConfiguration -import net.corda.node.services.messaging.MessagingService -import net.corda.node.services.messaging.send -import net.corda.node.services.messaging.sendRequest -import net.corda.node.services.network.AbstractNetworkMapServiceTest.Changed.Added -import net.corda.node.services.network.AbstractNetworkMapServiceTest.Changed.Removed -import net.corda.node.services.network.NetworkMapService.* -import net.corda.node.services.network.NetworkMapService.Companion.FETCH_TOPIC -import net.corda.node.services.network.NetworkMapService.Companion.PUSH_ACK_TOPIC -import net.corda.node.services.network.NetworkMapService.Companion.PUSH_TOPIC -import net.corda.node.services.network.NetworkMapService.Companion.QUERY_TOPIC -import net.corda.node.services.network.NetworkMapService.Companion.REGISTER_TOPIC -import net.corda.node.services.network.NetworkMapService.Companion.SUBSCRIPTION_TOPIC -import net.corda.node.utilities.AddOrRemove -import net.corda.node.utilities.AddOrRemove.ADD -import net.corda.node.utilities.AddOrRemove.REMOVE -import net.corda.nodeapi.internal.ServiceInfo -import net.corda.testing.* -import net.corda.testing.node.MockNetwork -import net.corda.testing.node.MockNetwork.MockNode -import org.assertj.core.api.Assertions.assertThat -import org.junit.After -import org.junit.Before -import org.junit.Test -import java.math.BigInteger -import java.security.KeyPair -import java.time.Instant -import java.util.* -import java.util.concurrent.LinkedBlockingQueue - -abstract class AbstractNetworkMapServiceTest { - lateinit var mockNet: MockNetwork - lateinit var mapServiceNode: StartedNode - lateinit var alice: StartedNode - - companion object { - val subscriberLegalName = CordaX500Name(organisation = "Subscriber", locality = "New York", country = "US") - } - - @Before - fun setup() { - mockNet = MockNetwork(defaultFactory = nodeFactory) - mapServiceNode = mockNet.networkMapNode - alice = mockNet.createNode(nodeFactory = nodeFactory, legalName = ALICE.name) - mockNet.runNetwork() - lastSerial = System.currentTimeMillis() - } - - @After - fun tearDown() { - mockNet.stopNodes() - } - - protected abstract val nodeFactory: MockNetwork.Factory<*> - - protected abstract val networkMapService: S - - // For persistent service, switch out the implementation for a newly instantiated one so we can check the state is preserved. - protected abstract fun swizzle() - - @Test - fun `all nodes register themselves`() { - // setup has run the network and so we immediately expect the network map service to be correctly populated - assertThat(alice.fetchMap()).containsOnly(Added(mapServiceNode), Added(alice)) - assertThat(alice.identityQuery()).isEqualTo(alice.info) - assertThat(mapServiceNode.identityQuery()).isEqualTo(mapServiceNode.info) - } - - @Test - fun `re-register the same node`() { - val response = alice.registration(ADD) - swizzle() - assertThat(response.getOrThrow().error).isNull() - assertThat(alice.fetchMap()).containsOnly(Added(mapServiceNode), Added(alice)) // Confirm it's a no-op - } - - @Test - fun `re-register with smaller serial value`() { - val response = alice.registration(ADD, serial = 1) - swizzle() - assertThat(response.getOrThrow().error).isNotNull() // Make sure send error message is sent back - assertThat(alice.fetchMap()).containsOnly(Added(mapServiceNode), Added(alice)) // Confirm it's a no-op - } - - @Test - fun `de-register node`() { - val response = alice.registration(REMOVE) - swizzle() - assertThat(response.getOrThrow().error).isNull() - assertThat(alice.fetchMap()).containsOnly(Added(mapServiceNode), Removed(alice)) - swizzle() - assertThat(alice.identityQuery()).isNull() - assertThat(mapServiceNode.identityQuery()).isEqualTo(mapServiceNode.info) - } - - @Test - fun `de-register same node again`() { - alice.registration(REMOVE) - val response = alice.registration(REMOVE) - swizzle() - assertThat(response.getOrThrow().error).isNotNull() // Make sure send error message is sent back - assertThat(alice.fetchMap()).containsOnly(Added(mapServiceNode), Removed(alice)) - } - - @Test - fun `de-register unknown node`() { - val bob = newNodeSeparateFromNetworkMap(BOB.name) - val response = bob.registration(REMOVE) - swizzle() - assertThat(response.getOrThrow().error).isNotNull() // Make sure send error message is sent back - assertThat(alice.fetchMap()).containsOnly(Added(mapServiceNode), Added(alice)) - } - - @Test - fun `subscribed while new node registers`() { - val updates = alice.subscribe() - swizzle() - val bob = addNewNodeToNetworkMap(BOB.name) - swizzle() - val update = updates.single() - assertThat(update.mapVersion).isEqualTo(networkMapService.mapVersion) - assertThat(update.wireReg.verified().toChanged()).isEqualTo(Added(bob.info)) - } - - @Test - fun `subscribed while node de-registers`() { - val bob = addNewNodeToNetworkMap(BOB.name) - val updates = alice.subscribe() - bob.registration(REMOVE) - swizzle() - assertThat(updates.map { it.wireReg.verified().toChanged() }).containsOnly(Removed(bob.info)) - } - - @Test - fun unsubscribe() { - val updates = alice.subscribe() - val bob = addNewNodeToNetworkMap(BOB.name) - alice.unsubscribe() - addNewNodeToNetworkMap(CHARLIE.name) - swizzle() - assertThat(updates.map { it.wireReg.verified().toChanged() }).containsOnly(Added(bob.info)) - } - - @Test - fun `surpass unacknowledged update limit`() { - val subscriber = newNodeSeparateFromNetworkMap(subscriberLegalName) - val updates = subscriber.subscribe() - val bob = addNewNodeToNetworkMap(BOB.name) - var serial = updates.first().wireReg.verified().serial - repeat(networkMapService.maxUnacknowledgedUpdates) { - bob.registration(ADD, serial = ++serial) - swizzle() - } - // We sent maxUnacknowledgedUpdates + 1 updates - the last one will be missed - assertThat(updates).hasSize(networkMapService.maxUnacknowledgedUpdates) - } - - @Test - fun `delay sending update ack until just before unacknowledged update limit`() { - val subscriber = newNodeSeparateFromNetworkMap(subscriberLegalName) - val updates = subscriber.subscribe() - val bob = addNewNodeToNetworkMap(BOB.name) - var serial = updates.first().wireReg.verified().serial - repeat(networkMapService.maxUnacknowledgedUpdates - 1) { - bob.registration(ADD, serial = ++serial) - swizzle() - } - // Subscriber will receive maxUnacknowledgedUpdates updates before sending ack - subscriber.ackUpdate(updates.last().mapVersion) - swizzle() - bob.registration(ADD, serial = ++serial) - assertThat(updates).hasSize(networkMapService.maxUnacknowledgedUpdates + 1) - assertThat(updates.last().wireReg.verified().serial).isEqualTo(serial) - } - - private fun StartedNode<*>.fetchMap(subscribe: Boolean = false, ifChangedSinceVersion: Int? = null): List { - val request = FetchMapRequest(subscribe, ifChangedSinceVersion, network.myAddress) - val response = services.networkService.sendRequest(FETCH_TOPIC, request, mapServiceNode.network.myAddress) - mockNet.runNetwork() - return response.getOrThrow().nodes?.map { it.toChanged() } ?: emptyList() - } - - private fun NodeRegistration.toChanged(): Changed = when (type) { - ADD -> Added(node) - REMOVE -> Removed(node) - } - - private fun StartedNode<*>.identityQuery(): NodeInfo? { - val request = QueryIdentityRequest(services.myInfo.chooseIdentityAndCert(), network.myAddress) - val response = services.networkService.sendRequest(QUERY_TOPIC, request, mapServiceNode.network.myAddress) - mockNet.runNetwork() - return response.getOrThrow().node - } - - private var lastSerial = Long.MIN_VALUE - - private fun StartedNode<*>.registration(addOrRemove: AddOrRemove, - serial: Long? = null): CordaFuture { - val distinctSerial = if (serial == null) { - ++lastSerial - } else { - lastSerial = serial - serial - } - val expires = Instant.now() + NetworkMapService.DEFAULT_EXPIRATION_PERIOD - val nodeRegistration = NodeRegistration(info, distinctSerial, addOrRemove, expires) - val request = RegistrationRequest(nodeRegistration.toWire(services.keyManagementService, info.chooseIdentity().owningKey), network.myAddress) - val response = services.networkService.sendRequest(REGISTER_TOPIC, request, mapServiceNode.network.myAddress) - mockNet.runNetwork() - return response - } - - private fun StartedNode<*>.subscribe(): Queue { - val request = SubscribeRequest(true, network.myAddress) - val updates = LinkedBlockingQueue() - services.networkService.addMessageHandler(PUSH_TOPIC) { message, _ -> - updates += message.data.deserialize() - } - val response = services.networkService.sendRequest(SUBSCRIPTION_TOPIC, request, mapServiceNode.network.myAddress) - mockNet.runNetwork() - assertThat(response.getOrThrow().confirmed).isTrue() - return updates - } - - private fun StartedNode<*>.unsubscribe() { - val request = SubscribeRequest(false, network.myAddress) - val response = services.networkService.sendRequest(SUBSCRIPTION_TOPIC, request, mapServiceNode.network.myAddress) - mockNet.runNetwork() - assertThat(response.getOrThrow().confirmed).isTrue() - } - - private fun StartedNode<*>.ackUpdate(mapVersion: Int) { - val request = UpdateAcknowledge(mapVersion, services.networkService.myAddress) - services.networkService.send(PUSH_ACK_TOPIC, MessagingService.DEFAULT_SESSION_ID, request, mapServiceNode.network.myAddress) - mockNet.runNetwork() - } - - private fun addNewNodeToNetworkMap(legalName: CordaX500Name): StartedNode { - val node = mockNet.createNode(legalName = legalName) - mockNet.runNetwork() - lastSerial = System.currentTimeMillis() - return node - } - - private fun newNodeSeparateFromNetworkMap(legalName: CordaX500Name): StartedNode { - return mockNet.createNode(legalName = legalName, nodeFactory = NoNMSNodeFactory) - } - - sealed class Changed { - data class Added(val node: NodeInfo) : Changed() { - constructor(node: StartedNode<*>) : this(node.info) - } - - data class Removed(val node: NodeInfo) : Changed() { - constructor(node: StartedNode<*>) : this(node.info) - } - } - - private object NoNMSNodeFactory : MockNetwork.Factory { - override fun create(config: NodeConfiguration, - network: MockNetwork, - networkMapAddr: SingleMessageRecipient?, - id: Int, - notaryIdentity: Pair?, - entropyRoot: BigInteger): MockNode { - return object : MockNode(config, network, null, id, notaryIdentity, entropyRoot) { - override fun makeNetworkMapService(network: MessagingService, networkMapCache: NetworkMapCacheInternal) = NullNetworkMapService - } - } - } -} diff --git a/node/src/test/kotlin/net/corda/node/services/network/HTTPNetworkMapClientTest.kt b/node/src/test/kotlin/net/corda/node/services/network/HTTPNetworkMapClientTest.kt new file mode 100644 index 0000000000..058762bb7f --- /dev/null +++ b/node/src/test/kotlin/net/corda/node/services/network/HTTPNetworkMapClientTest.kt @@ -0,0 +1,154 @@ +package net.corda.node.services.network + +import com.fasterxml.jackson.databind.ObjectMapper +import net.corda.core.crypto.* +import net.corda.core.identity.CordaX500Name +import net.corda.core.identity.PartyAndCertificate +import net.corda.core.node.NodeInfo +import net.corda.core.serialization.deserialize +import net.corda.core.serialization.serialize +import net.corda.core.utilities.NetworkHostAndPort +import net.corda.node.utilities.CertificateType +import net.corda.node.utilities.X509Utilities +import net.corda.testing.TestDependencyInjectionBase +import org.assertj.core.api.Assertions.assertThat +import org.bouncycastle.asn1.x500.X500Name +import org.bouncycastle.cert.X509CertificateHolder +import org.eclipse.jetty.server.Server +import org.eclipse.jetty.server.ServerConnector +import org.eclipse.jetty.server.handler.HandlerCollection +import org.eclipse.jetty.servlet.ServletContextHandler +import org.eclipse.jetty.servlet.ServletHolder +import org.glassfish.jersey.server.ResourceConfig +import org.glassfish.jersey.servlet.ServletContainer +import org.junit.After +import org.junit.Before +import org.junit.Test +import java.io.ByteArrayInputStream +import java.io.InputStream +import java.net.InetSocketAddress +import java.security.cert.CertPath +import java.security.cert.Certificate +import java.security.cert.CertificateFactory +import java.security.cert.X509Certificate +import javax.ws.rs.* +import javax.ws.rs.core.MediaType +import javax.ws.rs.core.Response +import javax.ws.rs.core.Response.ok +import kotlin.test.assertEquals + +class HTTPNetworkMapClientTest : TestDependencyInjectionBase() { + private lateinit var server: Server + + private lateinit var networkMapClient: NetworkMapClient + private val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + private val rootCACert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Corda Node Root CA", organisation = "R3 LTD", locality = "London", country = "GB"), rootCAKey) + private val intermediateCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + private val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootCAKey, X500Name("CN=Corda Node Intermediate CA,L=London"), intermediateCAKey.public) + + @Before + fun setUp() { + server = Server(InetSocketAddress("localhost", 0)).apply { + handler = HandlerCollection().apply { + addHandler(ServletContextHandler().apply { + contextPath = "/" + val resourceConfig = ResourceConfig().apply { + // Add your API provider classes (annotated for JAX-RS) here + register(MockNetworkMapServer()) + } + val jerseyServlet = ServletHolder(ServletContainer(resourceConfig)).apply { initOrder = 0 }// Initialise at server start + addServlet(jerseyServlet, "/api/*") + }) + } + } + server.start() + + while (!server.isStarted) { + Thread.sleep(100) + } + + val hostAndPort = server.connectors.mapNotNull { it as? ServerConnector }.first() + networkMapClient = HTTPNetworkMapClient("http://${hostAndPort.host}:${hostAndPort.localPort}/api/network-map") + } + + @After + fun tearDown() { + server.stop() + } + + @Test + fun `registered node is added to the network map`() { + // Create node info. + val signedNodeInfo = createNodeInfo("Test1") + val nodeInfo = signedNodeInfo.verified() + + networkMapClient.publish(signedNodeInfo) + + val nodeInfoHash = nodeInfo.serialize().sha256() + + assertThat(networkMapClient.getNetworkMap()).containsExactly(nodeInfoHash) + assertEquals(nodeInfo, networkMapClient.getNodeInfo(nodeInfoHash)) + + val signedNodeInfo2 = createNodeInfo("Test2") + val nodeInfo2 = signedNodeInfo2.verified() + networkMapClient.publish(signedNodeInfo2) + + val nodeInfoHash2 = nodeInfo2.serialize().sha256() + assertThat(networkMapClient.getNetworkMap()).containsExactly(nodeInfoHash, nodeInfoHash2) + assertEquals(nodeInfo2, networkMapClient.getNodeInfo(nodeInfoHash2)) + } + + private fun createNodeInfo(organisation: String): SignedData { + val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val clientCert = X509Utilities.createCertificate(CertificateType.CLIENT_CA, intermediateCACert, intermediateCAKey, CordaX500Name(organisation = organisation, locality = "London", country = "GB"), keyPair.public) + val certPath = buildCertPath(clientCert.toX509Certificate(), intermediateCACert.toX509Certificate(), rootCACert.toX509Certificate()) + val nodeInfo = NodeInfo(listOf(NetworkHostAndPort("my.$organisation.com", 1234)), listOf(PartyAndCertificate(certPath)), 1, serial = 1L) + + // Create digital signature. + val digitalSignature = DigitalSignature.WithKey(keyPair.public, Crypto.doSign(keyPair.private, nodeInfo.serialize().bytes)) + + return SignedData(nodeInfo.serialize(), digitalSignature) + } +} + +@Path("network-map") +// This is a stub implementation of the network map rest API. +internal class MockNetworkMapServer { + private val nodeInfos = mutableMapOf() + @POST + @Path("publish") + @Consumes(MediaType.APPLICATION_OCTET_STREAM) + fun publishNodeInfo(input: InputStream): Response { + val registrationData = input.readBytes().deserialize>() + val nodeInfo = registrationData.verified() + val nodeInfoHash = nodeInfo.serialize().sha256() + nodeInfos.put(nodeInfoHash, nodeInfo) + return ok().build() + } + + @GET + @Produces(MediaType.APPLICATION_JSON) + fun getNetworkMap(): Response { + return Response.ok(ObjectMapper().writeValueAsString(nodeInfos.keys.map { it.toString() })).build() + } + + @GET + @Path("{var}") + @Produces(MediaType.APPLICATION_OCTET_STREAM) + fun getNodeInfo(@PathParam("var") nodeInfoHash: String): Response { + val nodeInfo = nodeInfos[SecureHash.parse(nodeInfoHash)] + return if (nodeInfo != null) { + Response.ok(nodeInfo.serialize().bytes) + } else { + Response.status(Response.Status.NOT_FOUND) + }.build() + } +} + +private fun buildCertPath(vararg certificates: Certificate): CertPath { + return CertificateFactory.getInstance("X509").generateCertPath(certificates.asList()) +} + +private fun X509CertificateHolder.toX509Certificate(): X509Certificate { + return CertificateFactory.getInstance("X509").generateCertificate(ByteArrayInputStream(encoded)) as X509Certificate +} \ No newline at end of file diff --git a/node/src/test/kotlin/net/corda/node/services/network/InMemoryNetworkMapServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/network/InMemoryNetworkMapServiceTest.kt deleted file mode 100644 index c6d8566560..0000000000 --- a/node/src/test/kotlin/net/corda/node/services/network/InMemoryNetworkMapServiceTest.kt +++ /dev/null @@ -1,9 +0,0 @@ -package net.corda.node.services.network - -import net.corda.testing.node.MockNetwork - -class InMemoryNetworkMapServiceTest : AbstractNetworkMapServiceTest() { - override val nodeFactory get() = MockNetwork.DefaultFactory - override val networkMapService: InMemoryNetworkMapService get() = mapServiceNode.inNodeNetworkMapService as InMemoryNetworkMapService - override fun swizzle() = Unit -} diff --git a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt index 10aa2c62ac..b493466645 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt @@ -1,49 +1,34 @@ package net.corda.node.services.network import net.corda.core.node.services.NetworkMapCache -import net.corda.core.utilities.getOrThrow import net.corda.testing.ALICE import net.corda.testing.BOB import net.corda.testing.chooseIdentity import net.corda.testing.node.MockNetwork +import net.corda.testing.node.MockNodeParameters import org.assertj.core.api.Assertions.assertThat import org.junit.After -import org.junit.Before import org.junit.Test import java.math.BigInteger import kotlin.test.assertEquals class NetworkMapCacheTest { - lateinit var mockNet: MockNetwork - - @Before - fun setUp() { - mockNet = MockNetwork() - } + val mockNet: MockNetwork = MockNetwork() @After fun teardown() { mockNet.stopNodes() } - @Test - fun registerWithNetwork() { - mockNet.createNotaryNode() - val aliceNode = mockNet.createPartyNode(ALICE.name) - val future = aliceNode.services.networkMapCache.addMapService(aliceNode.network, mockNet.networkMapNode.network.myAddress, false, null) - mockNet.runNetwork() - future.getOrThrow() - } - @Test fun `key collision`() { val entropy = BigInteger.valueOf(24012017L) - val aliceNode = mockNet.createNode(nodeFactory = MockNetwork.DefaultFactory, legalName = ALICE.name, entropyRoot = entropy) + val aliceNode = mockNet.createNode(MockNodeParameters(legalName = ALICE.name, entropyRoot = entropy)) mockNet.runNetwork() // Node A currently knows only about itself, so this returns node A assertEquals(aliceNode.services.networkMapCache.getNodesByLegalIdentityKey(aliceNode.info.chooseIdentity().owningKey).singleOrNull(), aliceNode.info) - val bobNode = mockNet.createNode(nodeFactory = MockNetwork.DefaultFactory, legalName = BOB.name, entropyRoot = entropy) + val bobNode = mockNet.createNode(MockNodeParameters(legalName = BOB.name, entropyRoot = entropy)) assertEquals(aliceNode.info.chooseIdentity(), bobNode.info.chooseIdentity()) aliceNode.services.networkMapCache.addNode(bobNode.info) @@ -83,7 +68,7 @@ class NetworkMapCacheTest { val aliceNode = mockNet.createPartyNode(ALICE.name) val notaryLegalIdentity = notaryNode.info.chooseIdentity() val alice = aliceNode.info.chooseIdentity() - val notaryCache = notaryNode.services.networkMapCache as PersistentNetworkMapCache + val notaryCache = notaryNode.services.networkMapCache mockNet.runNetwork() notaryNode.database.transaction { assertThat(notaryCache.getNodeByLegalIdentity(alice) != null) diff --git a/node/src/test/kotlin/net/corda/node/services/network/PersistentNetworkMapServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/network/PersistentNetworkMapServiceTest.kt deleted file mode 100644 index 612c8e943a..0000000000 --- a/node/src/test/kotlin/net/corda/node/services/network/PersistentNetworkMapServiceTest.kt +++ /dev/null @@ -1,56 +0,0 @@ -package net.corda.node.services.network - -import net.corda.core.messaging.SingleMessageRecipient -import net.corda.node.services.api.NetworkMapCacheInternal -import net.corda.node.services.config.NodeConfiguration -import net.corda.node.services.messaging.MessagingService -import net.corda.nodeapi.internal.ServiceInfo -import net.corda.testing.node.MockNetwork -import net.corda.testing.node.MockNetwork.MockNode -import java.math.BigInteger -import java.security.KeyPair - -/** - * This class mirrors [InMemoryNetworkMapServiceTest] but switches in a [PersistentNetworkMapService] and - * repeatedly replaces it with new instances to check that the service correctly restores the most recent state. - */ -class PersistentNetworkMapServiceTest : AbstractNetworkMapServiceTest() { - - override val nodeFactory: MockNetwork.Factory<*> get() = NodeFactory - - override val networkMapService: PersistentNetworkMapService - get() = (mapServiceNode.inNodeNetworkMapService as SwizzleNetworkMapService).delegate - - override fun swizzle() { - mapServiceNode.database.transaction { - (mapServiceNode.inNodeNetworkMapService as SwizzleNetworkMapService).swizzle() - } - } - - private object NodeFactory : MockNetwork.Factory { - override fun create(config: NodeConfiguration, - network: MockNetwork, - networkMapAddr: SingleMessageRecipient?, - id: Int, - notaryIdentity: Pair?, - entropyRoot: BigInteger): MockNode { - return object : MockNode(config, network, networkMapAddr, id, notaryIdentity, entropyRoot) { - override fun makeNetworkMapService(network: MessagingService, networkMapCache: NetworkMapCacheInternal) = SwizzleNetworkMapService(network, networkMapCache) - } - } - } - - /** - * We use a special [NetworkMapService] that allows us to switch in a new instance at any time to check that the - * state within it is correctly restored. - */ - private class SwizzleNetworkMapService(private val delegateFactory: () -> PersistentNetworkMapService) : NetworkMapService { - constructor(network: MessagingService, networkMapCache: NetworkMapCacheInternal) : this({ PersistentNetworkMapService(network, networkMapCache, 1) }) - - var delegate = delegateFactory() - fun swizzle() { - delegate.unregisterNetworkHandlers() - delegate = delegateFactory() - } - } -} diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt b/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt index 2b9ff96f1c..160ea87bf3 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt @@ -45,7 +45,7 @@ class DBTransactionStorageTests : TestDependencyInjectionBase() { override val vaultService: VaultServiceInternal get() { val vaultService = NodeVaultService(clock, keyManagementService, stateLoader, database.hibernateConfig) - hibernatePersister = HibernateObserver(vaultService.rawUpdates, database.hibernateConfig) + hibernatePersister = HibernateObserver.install(vaultService.rawUpdates, database.hibernateConfig) return vaultService } diff --git a/node/src/test/kotlin/net/corda/node/services/schema/HibernateObserverTests.kt b/node/src/test/kotlin/net/corda/node/services/schema/HibernateObserverTests.kt index 33e8bcdad4..30d877ab93 100644 --- a/node/src/test/kotlin/net/corda/node/services/schema/HibernateObserverTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/schema/HibernateObserverTests.kt @@ -69,8 +69,7 @@ class HibernateObserverTests { } } val database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), ::makeTestIdentityService, schemaService) - @Suppress("UNUSED_VARIABLE") - val observer = HibernateObserver(rawUpdatesPublisher, database.hibernateConfig) + HibernateObserver.install(rawUpdatesPublisher, database.hibernateConfig) database.transaction { rawUpdatesPublisher.onNext(Vault.Update(emptySet(), setOf(StateAndRef(TransactionState(TestState(), DummyContract.PROGRAM_ID, MEGA_CORP), StateRef(SecureHash.sha256("dummy"), 0))))) val parentRowCountResult = DatabaseTransactionManager.current().connection.prepareStatement("select count(*) from Parents").executeQuery() 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 e65592f70b..044c953422 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 @@ -33,12 +33,14 @@ import net.corda.testing.node.InMemoryMessagingNetwork.MessageTransfer 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.node.MockNodeParameters import net.corda.testing.node.pumpReceive import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy import org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType import org.junit.After import org.junit.Before +import org.junit.Ignore import org.junit.Test import rx.Notification import rx.Observable @@ -64,14 +66,16 @@ class FlowFrameworkTests { private lateinit var alice: Party private lateinit var bob: Party + private fun StartedNode<*>.flushSmm() { + (this.smm as StateMachineManagerImpl).executor.flush() + } + @Before fun start() { mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin(), cordappPackages = listOf("net.corda.finance.contracts", "net.corda.testing.contracts")) - aliceNode = mockNet.createNode(legalName = ALICE_NAME) - bobNode = mockNet.createNode(legalName = BOB_NAME) - + aliceNode = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME)) + bobNode = mockNet.createNode(MockNodeParameters(legalName = BOB_NAME)) mockNet.runNetwork() - aliceNode.internals.ensureRegistered() // We intentionally create our own notary and ignore the one provided by the network // Note that these notaries don't operate correctly as they don't share their state. They are only used for testing @@ -152,45 +156,19 @@ class FlowFrameworkTests { assertEquals(true, flow.flowStarted) // Now we should have run the flow } - @Test - fun `flow added before network map will be init checkpointed`() { - var charlieNode = mockNet.createNode() //create vanilla node - val flow = NoOpFlow() - charlieNode.services.startFlow(flow) - assertEquals(false, flow.flowStarted) // Not started yet as no network activity has been allowed yet - charlieNode.internals.disableDBCloseOnStop() - charlieNode.services.networkMapCache.clearNetworkMapCache() // zap persisted NetworkMapCache to force use of network. - charlieNode.dispose() - - charlieNode = mockNet.createNode(charlieNode.internals.id) - val restoredFlow = charlieNode.getSingleFlow().first - assertEquals(false, restoredFlow.flowStarted) // Not started yet as no network activity has been allowed yet - mockNet.runNetwork() // Allow network map messages to flow - charlieNode.smm.executor.flush() - assertEquals(true, restoredFlow.flowStarted) // Now we should have run the flow and hopefully cleared the init checkpoint - charlieNode.internals.disableDBCloseOnStop() - charlieNode.services.networkMapCache.clearNetworkMapCache() // zap persisted NetworkMapCache to force use of network. - charlieNode.dispose() - - // Now it is completed the flow should leave no Checkpoint. - charlieNode = mockNet.createNode(charlieNode.internals.id) - mockNet.runNetwork() // Allow network map messages to flow - charlieNode.smm.executor.flush() - assertTrue(charlieNode.smm.findStateMachines(NoOpFlow::class.java).isEmpty()) - } - @Test fun `flow loaded from checkpoint will respond to messages from before start`() { aliceNode.registerFlowFactory(ReceiveFlow::class) { InitiatedSendFlow("Hello", it) } bobNode.services.startFlow(ReceiveFlow(alice).nonTerminating()) // Prepare checkpointed receive flow // Make sure the add() has finished initial processing. - bobNode.smm.executor.flush() + bobNode.flushSmm() bobNode.internals.disableDBCloseOnStop() bobNode.dispose() // kill receiver val restoredFlow = bobNode.restartAndGetRestoredFlow() assertThat(restoredFlow.receivedPayloads[0]).isEqualTo("Hello") } + @Ignore("Some changes in startup order make this test's assumptions fail.") @Test fun `flow with send will resend on interrupted restart`() { val payload = random63BitValue() @@ -198,8 +176,7 @@ class FlowFrameworkTests { var sentCount = 0 mockNet.messagingNetwork.sentMessages.toSessionTransfers().filter { it.isPayloadTransfer }.forEach { sentCount++ } - - val charlieNode = mockNet.createNode(legalName = CHARLIE_NAME) + val charlieNode = mockNet.createNode(MockNodeParameters(legalName = CHARLIE_NAME)) val secondFlow = charlieNode.registerFlowFactory(PingPongFlow::class) { PingPongFlow(it, payload2) } mockNet.runNetwork() val charlie = charlieNode.info.singleIdentity() @@ -210,7 +187,7 @@ class FlowFrameworkTests { assertEquals(1, bobNode.checkpointStorage.checkpoints().size) } // Make sure the add() has finished initial processing. - bobNode.smm.executor.flush() + bobNode.flushSmm() bobNode.internals.disableDBCloseOnStop() // Restart node and thus reload the checkpoint and resend the message with same UUID bobNode.dispose() @@ -218,12 +195,12 @@ class FlowFrameworkTests { assertEquals(1, bobNode.checkpointStorage.checkpoints().size) // confirm checkpoint bobNode.services.networkMapCache.clearNetworkMapCache() } - val node2b = mockNet.createNode(bobNode.internals.id) + val node2b = mockNet.createNode(MockNodeParameters(bobNode.internals.id)) bobNode.internals.manuallyCloseDB() val (firstAgain, fut1) = node2b.getSingleFlow() // Run the network which will also fire up the second flow. First message should get deduped. So message data stays in sync. mockNet.runNetwork() - node2b.smm.executor.flush() + node2b.flushSmm() fut1.getOrThrow() val receivedCount = receivedSessionMessages.count { it.isPayloadTransfer } @@ -245,7 +222,7 @@ class FlowFrameworkTests { @Test fun `sending to multiple parties`() { - val charlieNode = mockNet.createNode(legalName = CHARLIE_NAME) + val charlieNode = mockNet.createNode(MockNodeParameters(legalName = CHARLIE_NAME)) mockNet.runNetwork() val charlie = charlieNode.info.singleIdentity() bobNode.registerFlowFactory(SendFlow::class) { InitiatedReceiveFlow(it).nonTerminating() } @@ -278,7 +255,7 @@ class FlowFrameworkTests { @Test fun `receiving from multiple parties`() { - val charlieNode = mockNet.createNode(legalName = CHARLIE_NAME) + val charlieNode = mockNet.createNode(MockNodeParameters(legalName = CHARLIE_NAME)) mockNet.runNetwork() val charlie = charlieNode.info.singleIdentity() val bobPayload = "Test 1" @@ -432,7 +409,7 @@ class FlowFrameworkTests { @Test fun `FlowException propagated in invocation chain`() { - val charlieNode = mockNet.createNode(legalName = CHARLIE_NAME) + val charlieNode = mockNet.createNode(MockNodeParameters(legalName = CHARLIE_NAME)) mockNet.runNetwork() val charlie = charlieNode.info.singleIdentity() @@ -447,7 +424,7 @@ class FlowFrameworkTests { @Test fun `FlowException thrown and there is a 3rd unrelated party flow`() { - val charlieNode = mockNet.createNode(legalName = CHARLIE_NAME) + val charlieNode = mockNet.createNode(MockNodeParameters(legalName = CHARLIE_NAME)) mockNet.runNetwork() val charlie = charlieNode.info.singleIdentity() @@ -696,7 +673,7 @@ class FlowFrameworkTests { private inline fun > StartedNode.restartAndGetRestoredFlow() = internals.run { disableDBCloseOnStop() // Handover DB to new node copy stop() - val newNode = mockNet.createNode(id) + val newNode = mockNet.createNode(MockNodeParameters(id)) newNode.internals.acceptableLiveFiberCountOnStop = 1 manuallyCloseDB() mockNet.runNetwork() // allow NetworkMapService messages to stabilise and thus start the state machine @@ -731,7 +708,7 @@ class FlowFrameworkTests { private fun StartedNode<*>.sendSessionMessage(message: SessionMessage, destination: Party) { services.networkService.apply { val address = getAddressOfParty(PartyInfo.SingleNode(destination, emptyList())) - send(createMessage(StateMachineManager.sessionTopic, message.serialize().bytes), address) + send(createMessage(StateMachineManagerImpl.sessionTopic, message.serialize().bytes), address) } } @@ -755,7 +732,7 @@ class FlowFrameworkTests { } private fun Observable.toSessionTransfers(): Observable { - return filter { it.message.topicSession == StateMachineManager.sessionTopic }.map { + return filter { it.message.topicSession == StateMachineManagerImpl.sessionTopic }.map { val from = it.sender.id val message = it.message.data.deserialize() SessionTransfer(from, sanitise(message), it.recipients) 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 ec8155a18e..2510497167 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 @@ -13,10 +13,11 @@ import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.seconds -import net.corda.node.services.api.ServiceHubInternal +import net.corda.node.services.api.StartedNodeServices import net.corda.testing.* import net.corda.testing.contracts.DummyContract import net.corda.testing.node.MockNetwork +import net.corda.testing.node.MockNodeParameters import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Before @@ -28,8 +29,8 @@ import kotlin.test.assertFailsWith class NotaryServiceTests { lateinit var mockNet: MockNetwork - lateinit var notaryServices: ServiceHubInternal - lateinit var aliceServices: ServiceHubInternal + lateinit var notaryServices: StartedNodeServices + lateinit var aliceServices: StartedNodeServices lateinit var notary: Party lateinit var alice: Party @@ -37,9 +38,8 @@ class NotaryServiceTests { fun setup() { mockNet = MockNetwork(cordappPackages = listOf("net.corda.testing.contracts")) val notaryNode = mockNet.createNotaryNode(legalName = DUMMY_NOTARY.name, validating = false) - aliceServices = mockNet.createNode(legalName = ALICE_NAME).services + aliceServices = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME)).services mockNet.runNetwork() // Clear network map registration messages - notaryNode.internals.ensureRegistered() notaryServices = notaryNode.services notary = notaryServices.getDefaultNotary() alice = aliceServices.myInfo.singleIdentity() 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 3700848e47..1a1c74ea07 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 @@ -13,11 +13,12 @@ import net.corda.core.node.ServiceHub import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.getOrThrow -import net.corda.node.services.api.ServiceHubInternal +import net.corda.node.services.api.StartedNodeServices import net.corda.node.services.issueInvalidState import net.corda.testing.* import net.corda.testing.contracts.DummyContract import net.corda.testing.node.MockNetwork +import net.corda.testing.node.MockNodeParameters import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Before @@ -28,8 +29,8 @@ import kotlin.test.assertFailsWith class ValidatingNotaryServiceTests { lateinit var mockNet: MockNetwork - lateinit var notaryServices: ServiceHubInternal - lateinit var aliceServices: ServiceHubInternal + lateinit var notaryServices: StartedNodeServices + lateinit var aliceServices: StartedNodeServices lateinit var notary: Party lateinit var alice: Party @@ -37,9 +38,8 @@ class ValidatingNotaryServiceTests { fun setup() { mockNet = MockNetwork(cordappPackages = listOf("net.corda.testing.contracts")) val notaryNode = mockNet.createNotaryNode(legalName = DUMMY_NOTARY.name) - val aliceNode = mockNet.createNode(legalName = ALICE_NAME) + val aliceNode = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME)) mockNet.runNetwork() // Clear network map registration messages - notaryNode.internals.ensureRegistered() notaryServices = notaryNode.services aliceServices = aliceNode.services notary = notaryServices.getDefaultNotary() 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 new file mode 100644 index 0000000000..ae1ac4da8f --- /dev/null +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt @@ -0,0 +1,162 @@ +package net.corda.node.services.vault + +import co.paralleluniverse.fibers.Suspendable +import com.nhaarman.mockito_kotlin.* +import net.corda.core.contracts.* +import net.corda.core.flows.FinalityFlow +import net.corda.core.flows.FlowLogic +import net.corda.core.flows.FlowSession +import net.corda.core.flows.InitiatingFlow +import net.corda.core.identity.AbstractParty +import net.corda.core.internal.FlowStateMachine +import net.corda.core.internal.packageName +import net.corda.core.internal.uncheckedCast +import net.corda.core.node.StateLoader +import net.corda.core.node.services.KeyManagementService +import net.corda.core.node.services.queryBy +import net.corda.core.node.services.vault.QueryCriteria.SoftLockingCondition +import net.corda.core.node.services.vault.QueryCriteria.SoftLockingType.LOCKED_ONLY +import net.corda.core.node.services.vault.QueryCriteria.VaultQueryCriteria +import net.corda.core.transactions.LedgerTransaction +import net.corda.core.transactions.TransactionBuilder +import net.corda.core.utilities.NonEmptySet +import net.corda.core.utilities.OpaqueBytes +import net.corda.core.utilities.getOrThrow +import net.corda.core.utilities.unwrap +import net.corda.node.internal.InitiatedFlowFactory +import net.corda.node.services.api.VaultServiceInternal +import net.corda.testing.chooseIdentity +import net.corda.testing.node.MockNetwork +import net.corda.testing.rigorousMock +import net.corda.testing.node.MockNodeArgs +import net.corda.testing.node.MockNodeParameters +import org.junit.After +import org.junit.Test +import java.util.* +import java.util.concurrent.atomic.AtomicBoolean +import kotlin.reflect.jvm.jvmName +import kotlin.test.assertEquals + +class NodePair(private val mockNet: MockNetwork) { + private class ServerLogic(private val session: FlowSession, private val running: AtomicBoolean) : FlowLogic() { + @Suspendable + override fun call() { + running.set(true) + session.receive().unwrap { assertEquals("ping", it) } + session.send("pong") + } + } + + @InitiatingFlow + abstract class AbstractClientLogic(nodePair: NodePair) : FlowLogic() { + protected val server = nodePair.server.info.chooseIdentity() + protected abstract fun callImpl(): T + @Suspendable + override fun call() = callImpl().also { + initiateFlow(server).sendAndReceive("ping").unwrap { assertEquals("pong", it) } + } + } + + private val serverRunning = AtomicBoolean() + val server = mockNet.createNode() + var client = mockNet.createNode().apply { + internals.disableDBCloseOnStop() // Otherwise the in-memory database may disappear (taking the checkpoint with it) while we reboot the client. + } + private set + + fun communicate(clientLogic: AbstractClientLogic, rebootClient: Boolean): FlowStateMachine { + server.internals.internalRegisterFlowFactory(AbstractClientLogic::class.java, InitiatedFlowFactory.Core { ServerLogic(it, serverRunning) }, ServerLogic::class.java, false) + client.services.startFlow(clientLogic) + while (!serverRunning.get()) mockNet.runNetwork(1) + if (rebootClient) { + client.dispose() + client = mockNet.createNode(MockNodeParameters(client.internals.id)) + } + return uncheckedCast(client.smm.allStateMachines.single().stateMachine) + } +} + +class VaultSoftLockManagerTest { + private val mockVault = rigorousMock().also { + doNothing().whenever(it).softLockRelease(any(), anyOrNull()) + } + private val mockNet = MockNetwork(cordappPackages = listOf(ContractImpl::class.packageName), defaultFactory = object : MockNetwork.Factory { + override fun create(args: MockNodeArgs): MockNetwork.MockNode { + return object : MockNetwork.MockNode(args) { + override fun makeVaultService(keyManagementService: KeyManagementService, stateLoader: StateLoader): VaultServiceInternal { + val realVault = super.makeVaultService(keyManagementService, stateLoader) + return object : VaultServiceInternal by realVault { + override fun softLockRelease(lockId: UUID, stateRefs: NonEmptySet?) { + mockVault.softLockRelease(lockId, stateRefs) // No need to also call the real one for these tests. + } + } + } + } + } + }) + private val nodePair = NodePair(mockNet) + @After + fun tearDown() { + mockNet.stopNodes() + } + + object CommandDataImpl : CommandData + class ClientLogic(nodePair: NodePair, val state: ContractState) : NodePair.AbstractClientLogic>(nodePair) { + override fun callImpl() = run { + subFlow(FinalityFlow(serviceHub.signInitialTransaction(TransactionBuilder(notary = ourIdentity).apply { + addOutputState(state, ContractImpl::class.jvmName) + addCommand(CommandDataImpl, ourIdentity.owningKey) + }))) + serviceHub.vaultService.queryBy(VaultQueryCriteria(softLockingCondition = SoftLockingCondition(LOCKED_ONLY))).states.map { + it.state.data + } + } + } + + private abstract class ParticipantState(override val participants: List) : ContractState + + private class PlainOldState(participants: List) : ParticipantState(participants) { + constructor(nodePair: NodePair) : this(listOf(nodePair.client.info.chooseIdentity())) + } + + private class FungibleAssetImpl(participants: List) : ParticipantState(participants), FungibleAsset { + constructor(nodePair: NodePair) : this(listOf(nodePair.client.info.chooseIdentity())) + + override val owner get() = participants[0] + override fun withNewOwner(newOwner: AbstractParty) = throw UnsupportedOperationException() + override val amount get() = Amount(1, Issued(PartyAndReference(owner, OpaqueBytes.of(1)), Unit)) + override val exitKeys get() = throw UnsupportedOperationException() + override fun withNewOwnerAndAmount(newAmount: Amount>, newOwner: AbstractParty) = throw UnsupportedOperationException() + override fun equals(other: Any?) = other is FungibleAssetImpl && participants == other.participants + override fun hashCode() = participants.hashCode() + } + + class ContractImpl : Contract { + override fun verify(tx: LedgerTransaction) {} + } + + private fun run(expectSoftLock: Boolean, state: ContractState, checkpoint: Boolean) { + val fsm = nodePair.communicate(ClientLogic(nodePair, state), checkpoint) + mockNet.runNetwork() + if (expectSoftLock) { + assertEquals(listOf(state), fsm.resultFuture.getOrThrow()) + verify(mockVault).softLockRelease(fsm.id.uuid, null) + } else { + assertEquals(emptyList(), fsm.resultFuture.getOrThrow()) + // In this case we don't want softLockRelease called so that we avoid its expensive query, even after restore from checkpoint. + } + verifyNoMoreInteractions(mockVault) + } + + @Test + fun `plain old state is not soft locked`() = run(false, PlainOldState(nodePair), false) + + @Test + fun `plain old state is not soft locked with checkpoint`() = run(false, PlainOldState(nodePair), true) + + @Test + fun `fungible asset is soft locked`() = run(true, FungibleAssetImpl(nodePair), false) + + @Test + fun `fungible asset is soft locked with checkpoint`() = run(true, FungibleAssetImpl(nodePair), true) +} diff --git a/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkisRegistrationHelperTest.kt b/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkisRegistrationHelperTest.kt index 54ad6a4310..8454a03cd9 100644 --- a/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkisRegistrationHelperTest.kt +++ b/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkisRegistrationHelperTest.kt @@ -1,8 +1,6 @@ package net.corda.node.utilities.registration -import com.nhaarman.mockito_kotlin.any -import com.nhaarman.mockito_kotlin.eq -import com.nhaarman.mockito_kotlin.mock +import com.nhaarman.mockito_kotlin.* import net.corda.core.crypto.Crypto import net.corda.core.crypto.SecureHash import net.corda.core.identity.CordaX500Name @@ -11,6 +9,7 @@ import net.corda.node.utilities.X509Utilities import net.corda.node.utilities.getX509Certificate import net.corda.node.utilities.loadKeyStore import net.corda.testing.ALICE +import net.corda.testing.rigorousMock import net.corda.testing.testNodeConfiguration import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.style.BCStyle @@ -38,10 +37,9 @@ class NetworkRegistrationHelperTest { .map { CordaX500Name(commonName = it, organisation = "R3 Ltd", locality = "London", country = "GB") } val certs = identities.stream().map { X509Utilities.createSelfSignedCACertificate(it, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)) } .map { it.cert }.toTypedArray() - - val certService: NetworkRegistrationService = mock { - on { submitRequest(any()) }.then { id } - on { retrieveCertificates(eq(id)) }.then { certs } + val certService = rigorousMock().also { + doReturn(id).whenever(it).submitRequest(any()) + doReturn(certs).whenever(it).retrieveCertificates(eq(id)) } val config = testNodeConfiguration( diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashSelectionH2Test.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashSelectionH2Test.kt index 8929387a9e..5aa10b3179 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashSelectionH2Test.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashSelectionH2Test.kt @@ -6,6 +6,7 @@ import com.r3.corda.enterprise.perftestcordapp.flows.CashException import com.r3.corda.enterprise.perftestcordapp.flows.CashPaymentFlow import net.corda.testing.chooseIdentity import net.corda.testing.node.MockNetwork +import net.corda.testing.node.MockNodeParameters import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Test @@ -17,11 +18,11 @@ class CashSelectionH2Test { val mockNet = MockNetwork(threadPerNode = true) try { val notaryNode = mockNet.createNotaryNode() - val bankA = mockNet.createNode(configOverrides = { existingConfig -> + val bankA = mockNet.createNode(MockNodeParameters(configOverrides = { existingConfig -> // Tweak connections to be minimal to make this easier (1 results in a hung node during start up, so use 2 connections). existingConfig.dataSourceProperties.setProperty("maximumPoolSize", "2") existingConfig - }) + })) mockNet.startNodes() diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlowTest.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlowTest.kt index ee6fc4e0e4..e050e98239 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlowTest.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlowTest.kt @@ -4,6 +4,14 @@ package com.r3.corda.enterprise.perftestcordapp.flows // from net.corda.node.messaging import co.paralleluniverse.fibers.Suspendable +import com.r3.corda.enterprise.perftestcordapp.DOLLARS +import com.r3.corda.enterprise.perftestcordapp.`issued by` +import com.r3.corda.enterprise.perftestcordapp.contracts.CommercialPaper +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.CASH +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.Cash +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.fillWithSomeTestCash +import com.r3.corda.enterprise.perftestcordapp.flows.TwoPartyTradeFlow.Buyer +import com.r3.corda.enterprise.perftestcordapp.flows.TwoPartyTradeFlow.Seller import net.corda.core.concurrent.CordaFuture import net.corda.core.contracts.* import net.corda.core.crypto.* @@ -16,7 +24,6 @@ import net.corda.core.internal.FlowStateMachine import net.corda.core.internal.concurrent.map import net.corda.core.internal.rootCause import net.corda.core.messaging.DataFeed -import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.messaging.StateMachineTransactionMapping import net.corda.core.node.services.Vault import net.corda.core.serialization.CordaSerializable @@ -29,38 +36,24 @@ import net.corda.core.utilities.days import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.toNonEmptySet import net.corda.core.utilities.unwrap -import com.r3.corda.enterprise.perftestcordapp.DOLLARS -import com.r3.corda.enterprise.perftestcordapp.`issued by` -import com.r3.corda.enterprise.perftestcordapp.contracts.CommercialPaper -import com.r3.corda.enterprise.perftestcordapp.contracts.asset.CASH -import com.r3.corda.enterprise.perftestcordapp.contracts.asset.Cash -import com.r3.corda.enterprise.perftestcordapp.flows.TwoPartyTradeFlow.Buyer -import com.r3.corda.enterprise.perftestcordapp.flows.TwoPartyTradeFlow.Seller import net.corda.node.internal.StartedNode -import net.corda.node.services.api.WritableTransactionStorage -import net.corda.node.services.config.NodeConfiguration -import net.corda.node.services.persistence.DBTransactionStorage -import net.corda.node.utilities.CordaPersistence -import net.corda.nodeapi.internal.ServiceInfo -import net.corda.testing.* -import com.r3.corda.enterprise.perftestcordapp.contracts.asset.fillWithSomeTestCash import net.corda.node.services.api.Checkpoint import net.corda.node.services.api.CheckpointStorage -import net.corda.testing.node.InMemoryMessagingNetwork -import net.corda.testing.node.MockNetwork -import net.corda.testing.node.MockServices -import net.corda.testing.node.pumpReceive +import net.corda.node.services.api.WritableTransactionStorage +import net.corda.node.services.persistence.DBTransactionStorage +import net.corda.node.utilities.CordaPersistence +import net.corda.testing.* +import net.corda.testing.node.* import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Before +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized import rx.Observable import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream -import java.math.BigInteger -import java.security.KeyPair import java.util.* import java.util.jar.JarOutputStream import java.util.zip.ZipEntry @@ -88,6 +81,7 @@ internal fun CheckpointStorage.checkpoints(): List { * * We assume that Alice and Bob already found each other via some market, and have agreed the details already. */ +@Ignore @RunWith(Parameterized::class) class TwoPartyTradeFlowTests(val anonymous: Boolean) { companion object { @@ -117,7 +111,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { // We run this in parallel threads to help catch any race conditions that may exist. The other tests // we run in the unit test thread exclusively to speed things up, ensure deterministic results and // allow interruption half way through. - mockNet = MockNetwork(false, true, cordappPackages = cordappPackages) + mockNet = MockNetwork(networkSendManuallyPumped = true, cordappPackages = cordappPackages) ledger(MockServices(cordappPackages), initialiseSerialization = false) { val notaryNode = mockNet.createNotaryNode() val aliceNode = mockNet.createPartyNode(ALICE_NAME) @@ -167,7 +161,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { @Test(expected = InsufficientBalanceException::class) fun `trade cash for commercial paper fails using soft locking`() { - mockNet = MockNetwork(false, true, cordappPackages = cordappPackages) + mockNet = MockNetwork(networkSendManuallyPumped = true, cordappPackages = cordappPackages) ledger(MockServices(cordappPackages), initialiseSerialization = false) { val notaryNode = mockNet.createNotaryNode() val aliceNode = mockNet.createPartyNode(ALICE_NAME) @@ -223,7 +217,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { @Test fun `shutdown and restore`() { - mockNet = MockNetwork(false, cordappPackages = cordappPackages) + mockNet = MockNetwork(cordappPackages = cordappPackages) ledger(MockServices(cordappPackages), initialiseSerialization = false) { val notaryNode = mockNet.createNotaryNode() val aliceNode = mockNet.createPartyNode(ALICE_NAME) @@ -232,7 +226,6 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { aliceNode.internals.disableDBCloseOnStop() bobNode.internals.disableDBCloseOnStop() - val bobAddr = bobNode.network.myAddress as InMemoryMessagingNetwork.PeerHandle mockNet.runNetwork() // Clear network map registration messages val notary = notaryNode.services.getDefaultNotary() @@ -286,12 +279,11 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { // ... bring the node back up ... the act of constructing the SMM will re-register the message handlers // that Bob was waiting on before the reboot occurred. - bobNode = mockNet.createNode(bobAddr.id, object : MockNetwork.Factory { - override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, - id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): MockNetwork.MockNode { - return MockNetwork.MockNode(config, network, networkMapAddr, bobAddr.id, notaryIdentity, entropyRoot) + bobNode = mockNet.createNode(MockNodeParameters(legalName = BOB_NAME), nodeFactory = object : MockNetwork.Factory { + override fun create(args: MockNodeArgs): MockNetwork.MockNode { + return MockNetwork.MockNode(args) } - }, BOB_NAME) + }) // Find the future representing the result of this state machine again. val bobFuture = bobNode.smm.findStateMachines(BuyerAcceptor::class.java).single().second @@ -326,31 +318,26 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { // of gets and puts. private fun makeNodeWithTracking(name: CordaX500Name): StartedNode { // Create a node in the mock network ... - return mockNet.createNode(nodeFactory = object : MockNetwork.Factory { - override fun create(config: NodeConfiguration, - network: MockNetwork, - networkMapAddr: SingleMessageRecipient?, - id: Int, notaryIdentity: Pair?, - entropyRoot: BigInteger): MockNetwork.MockNode { - return object : MockNetwork.MockNode(config, network, networkMapAddr, id, notaryIdentity, entropyRoot) { + return mockNet.createNode(MockNodeParameters(legalName = name), nodeFactory = object : MockNetwork.Factory { + override fun create(args: MockNodeArgs): MockNetwork.MockNode { + return object : MockNetwork.MockNode(args) { // That constructs a recording tx storage override fun makeTransactionStorage(): WritableTransactionStorage { return RecordingTransactionStorage(database, super.makeTransactionStorage()) } } } - }, legalName = name) + }) } @Test fun `check dependencies of sale asset are resolved`() { - mockNet = MockNetwork(false, cordappPackages = cordappPackages) + mockNet = MockNetwork(cordappPackages = cordappPackages) val notaryNode = mockNet.createNotaryNode() val aliceNode = makeNodeWithTracking(ALICE_NAME) val bobNode = makeNodeWithTracking(BOB_NAME) val bankNode = makeNodeWithTracking(BOC_NAME) mockNet.runNetwork() - notaryNode.internals.ensureRegistered() val notary = aliceNode.services.getDefaultNotary() val alice = aliceNode.info.singleIdentity() val bob = bobNode.info.singleIdentity() @@ -451,14 +438,13 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { @Test fun `track works`() { - mockNet = MockNetwork(false, cordappPackages = cordappPackages) + mockNet = MockNetwork(cordappPackages = cordappPackages) val notaryNode = mockNet.createNotaryNode() val aliceNode = makeNodeWithTracking(ALICE_NAME) val bobNode = makeNodeWithTracking(BOB_NAME) val bankNode = makeNodeWithTracking(BOC_NAME) mockNet.runNetwork() - notaryNode.internals.ensureRegistered() val notary = aliceNode.services.getDefaultNotary() val alice: Party = aliceNode.info.singleIdentity() val bank: Party = bankNode.info.singleIdentity() @@ -533,7 +519,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { @Test fun `dependency with error on buyer side`() { - mockNet = MockNetwork(false, cordappPackages = cordappPackages) + mockNet = MockNetwork(cordappPackages = cordappPackages) ledger(MockServices(cordappPackages), initialiseSerialization = false) { runWithError(true, false, "at least one cash input") } @@ -541,7 +527,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { @Test fun `dependency with error on seller side`() { - mockNet = MockNetwork(false, cordappPackages = cordappPackages) + mockNet = MockNetwork(cordappPackages = cordappPackages) ledger(MockServices(cordappPackages), initialiseSerialization = false) { runWithError(false, true, "Issuances have a time-window") } @@ -614,7 +600,6 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { val bankNode = mockNet.createPartyNode(BOC_NAME) mockNet.runNetwork() - notaryNode.internals.ensureRegistered() val notary = aliceNode.services.getDefaultNotary() val alice = aliceNode.info.singleIdentity() val bob = bobNode.info.singleIdentity() diff --git a/samples/bank-of-corda-demo/build.gradle b/samples/bank-of-corda-demo/build.gradle index be0c49807d..d95d203fc4 100644 --- a/samples/bank-of-corda-demo/build.gradle +++ b/samples/bank-of-corda-demo/build.gradle @@ -55,7 +55,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { notary = [validating : true] p2pPort 10002 rpcPort 10003 - cordapps = ["net.corda:finance:$corda_release_version"] + cordapps = ["$project.group:finance:$corda_release_version"] } node { name "O=BankOfCorda,L=London,C=GB" @@ -63,7 +63,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { p2pPort 10005 rpcPort 10006 webPort 10007 - cordapps = ["net.corda:finance:$corda_release_version"] + cordapps = ["$project.group:finance:$corda_release_version"] rpcUsers = [ ['username' : "bankUser", 'password' : "test", @@ -79,7 +79,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { p2pPort 10008 rpcPort 10009 webPort 10010 - cordapps = ["net.corda:finance:$corda_release_version"] + cordapps = ["$project.group:finance:$corda_release_version"] rpcUsers = [ ['username' : "bigCorpUser", 'password' : "test", diff --git a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt index 81dbf12166..b526e580fe 100644 --- a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt +++ b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt @@ -5,6 +5,7 @@ import net.corda.bank.api.BankOfCordaClientApi import net.corda.bank.api.BankOfCordaWebApi.IssueRequestParams import net.corda.core.identity.CordaX500Name import net.corda.core.utilities.NetworkHostAndPort +import net.corda.finance.flows.CashConfigDataFlow import net.corda.finance.flows.CashExitFlow import net.corda.finance.flows.CashIssueAndPaymentFlow import net.corda.finance.flows.CashPaymentFlow @@ -23,8 +24,8 @@ fun main(args: Array) { BankOfCordaDriver().main(args) } -val BANK_USERNAME = "bankUser" -val BIGCORP_USERNAME = "bigCorpUser" +const val BANK_USERNAME = "bankUser" +const val BIGCORP_USERNAME = "bigCorpUser" val BIGCORP_LEGAL_NAME = CordaX500Name(organisation = "BigCorporation", locality = "New York", country = "US") @@ -53,41 +54,52 @@ private class BankOfCordaDriver { // The ISSUE_CASH will request some Cash from the ISSUER on behalf of Big Corporation node val role = options.valueOf(roleArg)!! - val requestParams = IssueRequestParams(options.valueOf(quantity), options.valueOf(currency), BIGCORP_LEGAL_NAME, - "1", BOC.name, DUMMY_NOTARY.name.copy(commonName = ValidatingNotaryService.id)) - try { when (role) { Role.ISSUER -> { driver(dsl = { + startNotaryNode(providedName = DUMMY_NOTARY.name, validating = true) val bankUser = User( BANK_USERNAME, "test", permissions = setOf( + startFlowPermission(), + startFlowPermission(), + startFlowPermission(), startFlowPermission(), - startFlowPermission())) - val bigCorpUser = User(BIGCORP_USERNAME, "test", - permissions = setOf( - startFlowPermission())) - startNotaryNode(DUMMY_NOTARY.name, validating = true) + startFlowPermission() + )) val bankOfCorda = startNode( providedName = BOC.name, rpcUsers = listOf(bankUser)) + val bigCorpUser = User(BIGCORP_USERNAME, "test", + permissions = setOf( + startFlowPermission(), + startFlowPermission())) startNode(providedName = BIGCORP_LEGAL_NAME, rpcUsers = listOf(bigCorpUser)) startWebserver(bankOfCorda.get()) waitForAllNodesToFinish() - }, isDebug = true) + }, isDebug = true, extraCordappPackagesToScan = listOf("net.corda.finance.contracts.asset")) } - Role.ISSUE_CASH_RPC -> { - println("Requesting Cash via RPC ...") - val result = BankOfCordaClientApi(NetworkHostAndPort("localhost", 10006)).requestRPCIssue(requestParams) - println("Success!! You transaction receipt is ${result.tx.id}") - } - Role.ISSUE_CASH_WEB -> { - println("Requesting Cash via Web ...") - val result = BankOfCordaClientApi(NetworkHostAndPort("localhost", 10007)).requestWebIssue(requestParams) - if (result) - println("Successfully processed Cash Issue request") + else -> { + val requestParams = IssueRequestParams(options.valueOf(quantity), options.valueOf(currency), BIGCORP_LEGAL_NAME, + "1", BOC.name, DUMMY_NOTARY.name.copy(commonName = ValidatingNotaryService.id)) + when(role) { + Role.ISSUE_CASH_RPC -> { + println("Requesting Cash via RPC ...") + val result = BankOfCordaClientApi(NetworkHostAndPort("localhost", 10004)).requestRPCIssue(requestParams) + println("Success!! You transaction receipt is ${result.tx.id}") + } + Role.ISSUE_CASH_WEB -> { + println("Requesting Cash via Web ...") + val result = BankOfCordaClientApi(NetworkHostAndPort("localhost", 10005)).requestWebIssue(requestParams) + if (result) + println("Successfully processed Cash Issue request") + } + else -> { + throw IllegalArgumentException("Unrecognized role: " + role) + } + } } } } catch (e: Exception) { diff --git a/samples/irs-demo/build.gradle b/samples/irs-demo/build.gradle index abbfaf3772..bcb20c214a 100644 --- a/samples/irs-demo/build.gradle +++ b/samples/irs-demo/build.gradle @@ -57,7 +57,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { p2pPort 10002 rpcPort 10003 webPort 10004 - cordapps = ["net.corda:finance:$corda_release_version"] + cordapps = ["$project.group:finance:$corda_release_version"] useTestClock true } node { @@ -65,7 +65,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { p2pPort 10005 rpcPort 10006 webPort 10007 - cordapps = ["net.corda:finance:$corda_release_version"] + cordapps = ["$project.group:finance:$corda_release_version"] useTestClock true } node { @@ -73,7 +73,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { p2pPort 10008 rpcPort 10009 webPort 10010 - cordapps = ["net.corda:finance:$corda_release_version"] + cordapps = ["$project.group:finance:$corda_release_version"] useTestClock true } } diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt b/samples/irs-demo/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt index 579de8cde6..bd694bd41b 100644 --- a/samples/irs-demo/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt +++ b/samples/irs-demo/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt @@ -145,7 +145,7 @@ object NodeInterestRates { } require(ftx.checkWithFun(::check)) - + ftx.checkCommandVisibility(services.myInfo.legalIdentities.first().owningKey) // It all checks out, so we can return a signature. // // Note that we will happily sign an invalid transaction, as we are only being presented with a filtered diff --git a/samples/irs-demo/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt b/samples/irs-demo/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt index d43192cdf8..40b84bd462 100644 --- a/samples/irs-demo/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt +++ b/samples/irs-demo/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt @@ -200,13 +200,13 @@ class NodeInterestRatesTest : TestDependencyInjectionBase() { @Test fun `network tearoff`() { - val mockNet = MockNetwork(initialiseSerialization = false, cordappPackages = listOf("net.corda.finance.contracts")) + val mockNet = MockNetwork(initialiseSerialization = false, cordappPackages = listOf("net.corda.finance.contracts", "net.corda.irs")) val n1 = mockNet.createNotaryNode() val oracleNode = mockNet.createNode().apply { internals.registerInitiatedFlow(NodeInterestRates.FixQueryHandler::class.java) internals.registerInitiatedFlow(NodeInterestRates.FixSignHandler::class.java) database.transaction { - internals.installCordaService(NodeInterestRates.Oracle::class.java).knownFixes = TEST_DATA + internals.findTokenizableService(NodeInterestRates.Oracle::class.java)!!.knownFixes = TEST_DATA } } val tx = makePartialTX() 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 0df1f7cdc2..95b203c7a2 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 @@ -45,7 +45,7 @@ class IRSSimulation(networkSendManuallyPumped: Boolean, runAsync: Boolean, laten private val executeOnNextIteration = Collections.synchronizedList(LinkedList<() -> Unit>()) override fun startMainSimulation(): CompletableFuture { - om = JacksonSupport.createInMemoryMapper(InMemoryIdentityService((banks + regulators + networkMap.internals + ratesOracle).flatMap { it.started!!.info.legalIdentitiesAndCerts }, trustRoot = DEV_TRUST_ROOT)) + om = JacksonSupport.createInMemoryMapper(InMemoryIdentityService((banks + regulators + ratesOracle).flatMap { it.started!!.info.legalIdentitiesAndCerts }, trustRoot = DEV_TRUST_ROOT)) registerFinanceJSONMappers(om) return startIRSDealBetween(0, 1).thenCompose { diff --git a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt index b9abcd6538..157f93eb58 100644 --- a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt +++ b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt @@ -1,26 +1,23 @@ package net.corda.netmap.simulation +import com.nhaarman.mockito_kotlin.doReturn +import com.nhaarman.mockito_kotlin.whenever import net.corda.core.flows.FlowLogic import net.corda.core.identity.CordaX500Name import net.corda.core.internal.uncheckedCast -import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.utilities.ProgressTracker import net.corda.finance.utils.CityDatabase import net.corda.finance.utils.WorldMapLocation import net.corda.irs.api.NodeInterestRates import net.corda.node.internal.StartedNode -import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.statemachine.StateMachineManager -import net.corda.nodeapi.internal.ServiceInfo -import net.corda.testing.* -import net.corda.testing.node.InMemoryMessagingNetwork -import net.corda.testing.node.MockNetwork -import net.corda.testing.node.TestClock -import net.corda.testing.node.setTo +import net.corda.testing.DUMMY_NOTARY +import net.corda.testing.DUMMY_REGULATOR +import net.corda.testing.node.* +import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import rx.Observable import rx.subjects.PublishSubject import java.math.BigInteger -import java.security.KeyPair import java.time.LocalDate import java.time.LocalDateTime import java.time.ZoneOffset @@ -38,6 +35,13 @@ import java.util.concurrent.Future abstract class Simulation(val networkSendManuallyPumped: Boolean, runAsync: Boolean, latencyInjector: InMemoryMessagingNetwork.LatencyCalculator?) { + companion object { + private val defaultParams // The get() is necessary so that entropyRoot isn't shared. + get() = MockNodeParameters(configOverrides = { + doReturn(makeTestDataSourceProperties(it.myLegalName.organisation)).whenever(it).dataSourceProperties + }) + } + init { if (!runAsync && latencyInjector != null) throw IllegalArgumentException("The latency injector is only useful when using manual pumping.") @@ -46,79 +50,29 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean, val bankLocations = listOf(Pair("London", "GB"), Pair("Frankfurt", "DE"), Pair("Rome", "IT")) // This puts together a mock network of SimulatedNodes. - - open class SimulatedNode(config: NodeConfiguration, mockNet: MockNetwork, networkMapAddress: SingleMessageRecipient?, - id: Int, notaryIdentity: Pair?, - entropyRoot: BigInteger) - : MockNetwork.MockNode(config, mockNet, networkMapAddress, id, notaryIdentity, entropyRoot) { + open class SimulatedNode(args: MockNodeArgs) : MockNetwork.MockNode(args) { override val started: StartedNode? get() = uncheckedCast(super.started) override fun findMyLocation(): WorldMapLocation? { return configuration.myLegalName.locality.let { CityDatabase[it] } } } - inner class BankFactory : MockNetwork.Factory { - var counter = 0 - - override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, - id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): SimulatedNode { - val letter = 'A' + counter - val (city, country) = bankLocations[counter++ % bankLocations.size] - - val cfg = testNodeConfiguration( - baseDirectory = config.baseDirectory, - myLegalName = CordaX500Name(organisation = "Bank $letter", locality = city, country = country)) - return SimulatedNode(cfg, network, networkMapAddr, id, notaryIdentity, entropyRoot) - } - - fun createAll(): List { - return bankLocations.mapIndexed { i, _ -> - // Use deterministic seeds so the simulation is stable. Needed so that party owning keys are stable. - mockNet.createUnstartedNode(nodeFactory = this, entropyRoot = BigInteger.valueOf(i.toLong())) - } - } - } - - val bankFactory = BankFactory() - - object NetworkMapNodeFactory : MockNetwork.Factory { - override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, - id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): SimulatedNode { - val cfg = testNodeConfiguration( - baseDirectory = config.baseDirectory, - myLegalName = DUMMY_MAP.name) - return object : SimulatedNode(cfg, network, networkMapAddr, id, notaryIdentity, entropyRoot) {} - } - } - - object NotaryNodeFactory : MockNetwork.Factory { - override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, - id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): SimulatedNode { - requireNotNull(config.notary) - val cfg = testNodeConfiguration( - baseDirectory = config.baseDirectory, - myLegalName = DUMMY_NOTARY.name, - notaryConfig = config.notary) - return SimulatedNode(cfg, network, networkMapAddr, id, notaryIdentity, entropyRoot) - } + private object SimulatedNodeFactory : MockNetwork.Factory { + override fun create(args: MockNodeArgs) = SimulatedNode(args) } object RatesOracleFactory : MockNetwork.Factory { // TODO: Make a more realistic legal name val RATES_SERVICE_NAME = CordaX500Name(organisation = "Rates Service Provider", locality = "Madrid", country = "ES") - override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, - id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): SimulatedNode { - val cfg = testNodeConfiguration( - baseDirectory = config.baseDirectory, - myLegalName = RATES_SERVICE_NAME) - return object : SimulatedNode(cfg, network, networkMapAddr, id, notaryIdentity, entropyRoot) { + override fun create(args: MockNodeArgs): SimulatedNode { + return object : SimulatedNode(args) { override fun start() = super.start().apply { registerInitiatedFlow(NodeInterestRates.FixQueryHandler::class.java) registerInitiatedFlow(NodeInterestRates.FixSignHandler::class.java) javaClass.classLoader.getResourceAsStream("net/corda/irs/simulation/example.rates.txt").use { database.transaction { - installCordaService(NodeInterestRates.Oracle::class.java).uploadFixes(it.reader().readText()) + findTokenizableService(NodeInterestRates.Oracle::class.java)!!.uploadFixes(it.reader().readText()) } } } @@ -126,31 +80,23 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean, } } - object RegulatorFactory : MockNetwork.Factory { - override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, - id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): SimulatedNode { - val cfg = testNodeConfiguration( - baseDirectory = config.baseDirectory, - myLegalName = DUMMY_REGULATOR.name) - return object : SimulatedNode(cfg, network, networkMapAddr, id, notaryIdentity, entropyRoot) { - // TODO: Regulatory nodes don't actually exist properly, this is a last minute demo request. - // So we just fire a message at a node that doesn't know how to handle it, and it'll ignore it. - // But that's fine for visualisation purposes. - } - } - } - - val mockNet = MockNetwork(networkSendManuallyPumped, runAsync, cordappPackages = listOf("net.corda.irs.contract", "net.corda.finance.contract")) - // This one must come first. - val networkMap = mockNet.startNetworkMapNode(nodeFactory = NetworkMapNodeFactory) - val notary = mockNet.createNotaryNode(validating = false, nodeFactory = NotaryNodeFactory) - val regulators = listOf(mockNet.createUnstartedNode(nodeFactory = RegulatorFactory)) - val ratesOracle = mockNet.createUnstartedNode(nodeFactory = RatesOracleFactory) - + val mockNet = MockNetwork( + networkSendManuallyPumped = networkSendManuallyPumped, + threadPerNode = runAsync, + cordappPackages = listOf("net.corda.irs.contract", "net.corda.finance.contract", "net.corda.irs")) + val notary = mockNet.createNotaryNode(defaultParams.copy(legalName = DUMMY_NOTARY.name), false, SimulatedNodeFactory) + // TODO: Regulatory nodes don't actually exist properly, this is a last minute demo request. + // So we just fire a message at a node that doesn't know how to handle it, and it'll ignore it. + // But that's fine for visualisation purposes. + val regulators = listOf(mockNet.createUnstartedNode(defaultParams.copy(legalName = DUMMY_REGULATOR.name), SimulatedNodeFactory)) + val ratesOracle = mockNet.createUnstartedNode(defaultParams.copy(legalName = RatesOracleFactory.RATES_SERVICE_NAME), RatesOracleFactory) // All nodes must be in one of these two lists for the purposes of the visualiser tool. - val serviceProviders: List = listOf(notary.internals, ratesOracle, networkMap.internals) - val banks: List = bankFactory.createAll() - + val serviceProviders: List = listOf(notary.internals, ratesOracle) + val banks: List = bankLocations.mapIndexed { i, (city, country) -> + val legalName = CordaX500Name(organisation = "Bank ${'A' + i}", locality = city, country = country) + // Use deterministic seeds so the simulation is stable. Needed so that party owning keys are stable. + mockNet.createUnstartedNode(defaultParams.copy(legalName = legalName, entropyRoot = BigInteger.valueOf(i.toLong())), SimulatedNodeFactory) + } val clocks = (serviceProviders + regulators + banks).map { it.platformClock as TestClock } // These are used from the network visualiser tool. diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt index 92669b5714..b3e295d1cb 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt @@ -19,14 +19,14 @@ import net.corda.testing.internal.demorun.notary import net.corda.testing.internal.demorun.rpcUsers import net.corda.testing.internal.demorun.runNodes -fun main(args: Array) = BFTNotaryCordform.runNodes() +fun main(args: Array) = BFTNotaryCordform().runNodes() private val clusterSize = 4 // Minimum size that tolerates a faulty replica. private val notaryNames = createNotaryNames(clusterSize) // This is not the intended final design for how to use CordformDefinition, please treat this as experimental and DO // NOT use this as a design to copy. -object BFTNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") { +class BFTNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") { private val clusterName = CordaX500Name(BFTNonValidatingNotaryService.id, "BFT", "Zurich", "CH") init { diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/Clean.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/Clean.kt index 1bab32c9a9..7ab6fff629 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/Clean.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/Clean.kt @@ -3,7 +3,7 @@ package net.corda.notarydemo import net.corda.testing.internal.demorun.clean fun main(args: Array) { - listOf(SingleNotaryCordform, RaftNotaryCordform, BFTNotaryCordform).forEach { + listOf(SingleNotaryCordform(), RaftNotaryCordform(), BFTNotaryCordform()).forEach { it.clean() } } diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/CustomNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/CustomNotaryCordform.kt index 900150da69..3cd43d7a0e 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/CustomNotaryCordform.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/CustomNotaryCordform.kt @@ -9,9 +9,9 @@ import net.corda.testing.BOB import net.corda.testing.DUMMY_NOTARY import net.corda.testing.internal.demorun.* -fun main(args: Array) = CustomNotaryCordform.runNodes() +fun main(args: Array) = CustomNotaryCordform().runNodes() -object CustomNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") { +class CustomNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") { init { node { name(ALICE.name) diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt index 198867dcf1..ff128c6edf 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt @@ -14,7 +14,7 @@ import net.corda.testing.ALICE import net.corda.testing.BOB import net.corda.testing.internal.demorun.* -fun main(args: Array) = RaftNotaryCordform.runNodes() +fun main(args: Array) = RaftNotaryCordform().runNodes() internal fun createNotaryNames(clusterSize: Int) = (0 until clusterSize).map { CordaX500Name("Notary Service $it", "Zurich", "CH") } @@ -22,7 +22,7 @@ private val notaryNames = createNotaryNames(3) // This is not the intended final design for how to use CordformDefinition, please treat this as experimental and DO // NOT use this as a design to copy. -object RaftNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") { +class RaftNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") { private val clusterName = CordaX500Name(RaftValidatingNotaryService.id, "Raft", "Zurich", "CH") init { diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/SingleNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/SingleNotaryCordform.kt index 22cc63540d..8e349494b8 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/SingleNotaryCordform.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/SingleNotaryCordform.kt @@ -17,13 +17,13 @@ import net.corda.testing.internal.demorun.notary import net.corda.testing.internal.demorun.rpcUsers import net.corda.testing.internal.demorun.runNodes -fun main(args: Array) = SingleNotaryCordform.runNodes() +fun main(args: Array) = SingleNotaryCordform().runNodes() val notaryDemoUser = User("demou", "demop", setOf(startFlowPermission(), startFlowPermission())) // This is not the intended final design for how to use CordformDefinition, please treat this as experimental and DO // NOT use this as a design to copy. -object SingleNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") { +class SingleNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") { init { node { name(ALICE.name) diff --git a/samples/simm-valuation-demo/build.gradle b/samples/simm-valuation-demo/build.gradle index 5ad72562d2..832e2eb283 100644 --- a/samples/simm-valuation-demo/build.gradle +++ b/samples/simm-valuation-demo/build.gradle @@ -68,14 +68,14 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { name "O=Notary Service,L=Zurich,C=CH" notary = [validating : true] p2pPort 10002 - cordapps = ["net.corda:finance:$corda_release_version"] + cordapps = ["$project.group:finance:$corda_release_version"] } node { name "O=Bank A,L=London,C=GB" p2pPort 10004 webPort 10005 rpcPort 10006 - cordapps = ["net.corda:finance:$corda_release_version"] + cordapps = ["$project.group:finance:$corda_release_version"] rpcUsers = ext.rpcUsers } node { @@ -83,7 +83,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { p2pPort 10007 webPort 10008 rpcPort 10009 - cordapps = ["net.corda:finance:$corda_release_version"] + cordapps = ["$project.group:finance:$corda_release_version"] rpcUsers = ext.rpcUsers } node { @@ -91,7 +91,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { p2pPort 10010 webPort 10011 rpcPort 10012 - cordapps = ["net.corda:finance:$corda_release_version"] + cordapps = ["$project.group:finance:$corda_release_version"] rpcUsers = ext.rpcUsers } } diff --git a/samples/trader-demo/build.gradle b/samples/trader-demo/build.gradle index e3d363e516..392ecd60ac 100644 --- a/samples/trader-demo/build.gradle +++ b/samples/trader-demo/build.gradle @@ -55,27 +55,27 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { name "O=Notary Service,L=Zurich,C=CH" notary = [validating : true] p2pPort 10002 - cordapps = ["net.corda:finance:$corda_release_version"] + cordapps = ["$project.group:finance:$corda_release_version"] } node { name "O=Bank A,L=London,C=GB" p2pPort 10005 rpcPort 10006 - cordapps = ["net.corda:finance:$corda_release_version"] + cordapps = ["$project.group:finance:$corda_release_version"] rpcUsers = ext.rpcUsers } node { name "O=Bank B,L=New York,C=US" p2pPort 10008 rpcPort 10009 - cordapps = ["net.corda:finance:$corda_release_version"] + cordapps = ["$project.group:finance:$corda_release_version"] rpcUsers = ext.rpcUsers } node { name "O=BankOfCorda,L=New York,C=US" p2pPort 10011 rpcPort 10012 - cordapps = ["net.corda:finance:$corda_release_version"] + cordapps = ["$project.group:finance:$corda_release_version"] rpcUsers = ext.rpcUsers } } diff --git a/testing/node-driver/src/main/kotlin/net/corda/node/testing/MockServiceHubInternal.kt b/testing/node-driver/src/main/kotlin/net/corda/node/testing/MockServiceHubInternal.kt deleted file mode 100644 index 1188e1571e..0000000000 --- a/testing/node-driver/src/main/kotlin/net/corda/node/testing/MockServiceHubInternal.kt +++ /dev/null @@ -1,90 +0,0 @@ -package net.corda.node.testing - -import com.codahale.metrics.MetricRegistry -import net.corda.core.flows.FlowInitiator -import net.corda.core.flows.FlowLogic -import net.corda.core.identity.Party -import net.corda.core.node.NodeInfo -import net.corda.core.node.StateLoader -import net.corda.core.node.services.* -import net.corda.core.serialization.SerializeAsToken -import net.corda.node.internal.InitiatedFlowFactory -import net.corda.node.internal.StateLoaderImpl -import net.corda.node.internal.cordapp.CordappLoader -import net.corda.node.internal.cordapp.CordappProviderImpl -import net.corda.node.internal.cordapp.CordappProviderInternal -import net.corda.node.serialization.NodeClock -import net.corda.node.services.api.* -import net.corda.node.services.config.NodeConfiguration -import net.corda.node.services.messaging.MessagingService -import net.corda.node.services.statemachine.FlowStateMachineImpl -import net.corda.node.services.statemachine.StateMachineManager -import net.corda.node.services.transactions.InMemoryTransactionVerifierService -import net.corda.node.utilities.CordaPersistence -import net.corda.testing.DUMMY_IDENTITY_1 -import net.corda.testing.MOCK_HOST_AND_PORT -import net.corda.testing.MOCK_IDENTITY_SERVICE -import net.corda.testing.node.MockAttachmentStorage -import net.corda.testing.node.MockNetworkMapCache -import net.corda.testing.node.MockStateMachineRecordedTransactionMappingStorage -import net.corda.testing.node.MockTransactionStorage -import java.nio.file.Paths -import java.sql.Connection -import java.time.Clock - -open class MockServiceHubInternal( - override val database: CordaPersistence, - override val configuration: NodeConfiguration, - val customVault: VaultServiceInternal? = null, - val keyManagement: KeyManagementService? = null, - val network: MessagingService? = null, - val identity: IdentityService? = MOCK_IDENTITY_SERVICE, - override val attachments: AttachmentStorage = MockAttachmentStorage(), - override val validatedTransactions: WritableTransactionStorage = MockTransactionStorage(), - override val stateMachineRecordedTransactionMapping: StateMachineRecordedTransactionMappingStorage = MockStateMachineRecordedTransactionMappingStorage(), - val mapCache: NetworkMapCacheInternal? = null, - val scheduler: SchedulerService? = null, - val overrideClock: Clock? = NodeClock(), - val customContractUpgradeService: ContractUpgradeService? = null, - val customTransactionVerifierService: TransactionVerifierService? = InMemoryTransactionVerifierService(2), - override val cordappProvider: CordappProviderInternal = CordappProviderImpl(CordappLoader.createDefault(Paths.get(".")), attachments), - protected val stateLoader: StateLoaderImpl = StateLoaderImpl(validatedTransactions) -) : ServiceHubInternal, StateLoader by stateLoader { - override val transactionVerifierService: TransactionVerifierService - get() = customTransactionVerifierService ?: throw UnsupportedOperationException() - override val vaultService: VaultServiceInternal - get() = customVault ?: throw UnsupportedOperationException() - override val contractUpgradeService: ContractUpgradeService - get() = customContractUpgradeService ?: throw UnsupportedOperationException() - override val keyManagementService: KeyManagementService - get() = keyManagement ?: throw UnsupportedOperationException() - override val identityService: IdentityService - get() = identity ?: throw UnsupportedOperationException() - override val networkService: MessagingService - get() = network ?: throw UnsupportedOperationException() - override val networkMapCache: NetworkMapCacheInternal - get() = mapCache ?: MockNetworkMapCache(this) - override val schedulerService: SchedulerService - get() = scheduler ?: throw UnsupportedOperationException() - override val clock: Clock - get() = overrideClock ?: throw UnsupportedOperationException() - override val myInfo: NodeInfo - get() = NodeInfo(listOf(MOCK_HOST_AND_PORT), listOf(DUMMY_IDENTITY_1), 1, serial = 1L) // Required to get a dummy platformVersion when required for tests. - override val monitoringService: MonitoringService = MonitoringService(MetricRegistry()) - override val rpcFlows: List>> - get() = throw UnsupportedOperationException() - override val schemaService get() = throw UnsupportedOperationException() - override val auditService: AuditService = DummyAuditService() - - lateinit var smm: StateMachineManager - - override fun cordaService(type: Class): T = throw UnsupportedOperationException() - - override fun startFlow(logic: FlowLogic, flowInitiator: FlowInitiator, ourIdentity: Party?): FlowStateMachineImpl { - return smm.executor.fetchFrom { smm.add(logic, flowInitiator, ourIdentity) } - } - - override fun getFlowFactory(initiatingFlowClass: Class>): InitiatedFlowFactory<*>? = null - - override fun jdbcSession(): Connection = database.createSession() -} 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 551a8075b8..768b7771c6 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 @@ -2,14 +2,16 @@ package net.corda.testing -import com.nhaarman.mockito_kotlin.spy +import com.nhaarman.mockito_kotlin.doCallRealMethod +import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever import net.corda.core.identity.CordaX500Name import net.corda.core.node.ServiceHub import net.corda.core.transactions.TransactionBuilder +import net.corda.node.services.config.CertChainPolicyConfig import net.corda.node.services.config.NodeConfiguration -import net.corda.node.services.config.NotaryConfig import net.corda.node.services.config.VerifierType +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 @@ -54,26 +56,31 @@ fun transaction( fun testNodeConfiguration( baseDirectory: Path, - myLegalName: CordaX500Name, - notaryConfig: NotaryConfig? = null): NodeConfiguration { + myLegalName: CordaX500Name): NodeConfiguration { abstract class MockableNodeConfiguration : NodeConfiguration // Otherwise Mockito is defeated by val getters. - - val nc = spy() - whenever(nc.baseDirectory).thenReturn(baseDirectory) - whenever(nc.myLegalName).thenReturn(myLegalName) - whenever(nc.minimumPlatformVersion).thenReturn(1) - whenever(nc.keyStorePassword).thenReturn("cordacadevpass") - whenever(nc.trustStorePassword).thenReturn("trustpass") - whenever(nc.rpcUsers).thenReturn(emptyList()) - whenever(nc.notary).thenReturn(notaryConfig) - whenever(nc.dataSourceProperties).thenReturn(makeTestDataSourceProperties(myLegalName.organisation)) - whenever(nc.database).thenReturn(makeTestDatabaseProperties()) - whenever(nc.emailAddress).thenReturn("") - whenever(nc.exportJMXto).thenReturn("") - whenever(nc.devMode).thenReturn(true) - whenever(nc.certificateSigningService).thenReturn(URL("http://localhost")) - whenever(nc.certificateChainCheckPolicies).thenReturn(emptyList()) - whenever(nc.verifierType).thenReturn(VerifierType.InMemory) - whenever(nc.messageRedeliveryDelaySeconds).thenReturn(5) - return nc + return rigorousMock().also { + doReturn(true).whenever(it).noNetworkMapServiceMode + doReturn(baseDirectory).whenever(it).baseDirectory + doReturn(myLegalName).whenever(it).myLegalName + doReturn(1).whenever(it).minimumPlatformVersion + doReturn("cordacadevpass").whenever(it).keyStorePassword + doReturn("trustpass").whenever(it).trustStorePassword + doReturn(emptyList()).whenever(it).rpcUsers + doReturn(null).whenever(it).notary + doReturn(makeTestDataSourceProperties(myLegalName.organisation)).whenever(it).dataSourceProperties + doReturn(makeTestDatabaseProperties()).whenever(it).database + doReturn("").whenever(it).emailAddress + doReturn("").whenever(it).exportJMXto + doReturn(true).whenever(it).devMode + doReturn(URL("http://localhost")).whenever(it).certificateSigningService + doReturn(emptyList()).whenever(it).certificateChainCheckPolicies + doReturn(VerifierType.InMemory).whenever(it).verifierType + doReturn(5).whenever(it).messageRedeliveryDelaySeconds + doReturn(0L).whenever(it).additionalNodeInfoPollingFrequencyMsec + doReturn(null).whenever(it).networkMapService + doCallRealMethod().whenever(it).certificatesDirectory + doCallRealMethod().whenever(it).trustStoreFile + doCallRealMethod().whenever(it).sslKeystore + doCallRealMethod().whenever(it).nodeKeystore + } } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/RPCDriver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/RPCDriver.kt index 73edc52dc8..f5375bd355 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/RPCDriver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/RPCDriver.kt @@ -228,7 +228,6 @@ fun rpcDriver( extraSystemProperties: Map = emptyMap(), useTestClock: Boolean = false, initialiseSerialization: Boolean = true, - networkMapStartStrategy: NetworkMapStartStrategy = NetworkMapStartStrategy.Dedicated(startAutomatically = false), startNodesInProcess: Boolean = false, extraCordappPackagesToScan: List = emptyList(), dsl: RPCDriverExposedDSLInterface.() -> A @@ -240,7 +239,6 @@ fun rpcDriver( extraSystemProperties = extraSystemProperties, driverDirectory = driverDirectory.toAbsolutePath(), useTestClock = useTestClock, - networkMapStartStrategy = networkMapStartStrategy, isDebug = isDebug, startNodesInProcess = startNodesInProcess, extraCordappPackagesToScan = extraCordappPackagesToScan diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt index c44b9a88a9..ebde49f03d 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -8,7 +8,6 @@ import com.typesafe.config.ConfigRenderOptions import net.corda.client.rpc.CordaRPCClient import net.corda.cordform.CordformContext import net.corda.cordform.CordformNode -import net.corda.cordform.NodeDefinition import net.corda.core.CordaException import net.corda.core.concurrent.CordaFuture import net.corda.core.concurrent.firstOf @@ -20,6 +19,8 @@ import net.corda.core.internal.div import net.corda.core.internal.times import net.corda.core.messaging.CordaRPCOps import net.corda.core.node.NodeInfo +import net.corda.core.node.services.NetworkMapCache +import net.corda.core.toFuture import net.corda.core.utilities.* import net.corda.node.internal.Node import net.corda.node.internal.NodeStartup @@ -28,6 +29,7 @@ import net.corda.node.internal.cordapp.CordappLoader import net.corda.node.services.config.* import net.corda.node.services.network.NetworkMapService import net.corda.node.utilities.ServiceIdentityGenerator +import net.corda.nodeapi.NodeInfoFilesCopier import net.corda.nodeapi.User import net.corda.nodeapi.config.parseAs import net.corda.nodeapi.config.toConfig @@ -37,6 +39,8 @@ import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO import okhttp3.OkHttpClient import okhttp3.Request import org.slf4j.Logger +import rx.Observable +import rx.observables.ConnectableObservable import java.io.File import java.net.* import java.nio.file.Path @@ -150,14 +154,6 @@ interface DriverDSLExposedInterface : CordformContext { */ fun startWebserver(handle: NodeHandle, maximumHeapSize: String): CordaFuture - /** - * Starts a network map service node. Note that only a single one should ever be running, so you will probably want - * to set networkMapStartStrategy to Dedicated(false) in your [driver] call. - * @param startInProcess Determines if the node should be started inside this process. If null the Driver-level - * value will be used. - */ - fun startDedicatedNetworkMapService(startInProcess: Boolean? = null, maximumHeapSize: String = "200m"): CordaFuture - fun waitForAllNodesToFinish() /** @@ -212,13 +208,15 @@ sealed class NodeHandle { override val configuration: FullNodeConfiguration, override val webAddress: NetworkHostAndPort, val debugPort: Int?, - val process: Process + val process: Process, + private val onStopCallback: () -> Unit ) : NodeHandle() { override fun stop(): CordaFuture { with(process) { destroy() waitFor() } + onStopCallback() return doneFuture(Unit) } } @@ -229,7 +227,8 @@ sealed class NodeHandle { override val configuration: FullNodeConfiguration, override val webAddress: NetworkHostAndPort, val node: StartedNode, - val nodeThread: Thread + val nodeThread: Thread, + private val onStopCallback: () -> Unit ) : NodeHandle() { override fun stop(): CordaFuture { node.dispose() @@ -237,6 +236,7 @@ sealed class NodeHandle { interrupt() join() } + onStopCallback() return doneFuture(Unit) } } @@ -273,9 +273,8 @@ sealed class PortAllocation { } } -/** - * Helper builder for configuring a [Node] from Java. - */ +/** Helper builder for configuring a [Node] from Java. */ +@Suppress("unused") data class NodeParameters( val providedName: CordaX500Name? = null, val rpcUsers: List = emptyList(), @@ -319,7 +318,6 @@ data class NodeParameters( * @param debugPortAllocation The port allocation strategy to use for jvm debugging. Defaults to incremental. * @param extraSystemProperties A Map of extra system properties which will be given to each new node. Defaults to empty. * @param useTestClock If true the test clock will be used in Node. - * @param networkMapStartStrategy Determines whether a network map node is started automatically. * @param startNodesInProcess Provides the default behaviour of whether new nodes should start inside this process or * not. Note that this may be overridden in [DriverDSLExposedInterface.startNode]. * @param dsl The dsl itself. @@ -334,7 +332,7 @@ fun driver( extraSystemProperties: Map = defaultParameters.extraSystemProperties, useTestClock: Boolean = defaultParameters.useTestClock, initialiseSerialization: Boolean = defaultParameters.initialiseSerialization, - networkMapStartStrategy: NetworkMapStartStrategy = defaultParameters.networkMapStartStrategy, + startNodesInProcess: Boolean = defaultParameters.startNodesInProcess, extraCordappPackagesToScan: List = defaultParameters.extraCordappPackagesToScan, dsl: DriverDSLExposedInterface.() -> A @@ -347,7 +345,6 @@ fun driver( driverDirectory = driverDirectory.toAbsolutePath(), useTestClock = useTestClock, isDebug = isDebug, - networkMapStartStrategy = networkMapStartStrategy, startNodesInProcess = startNodesInProcess, extraCordappPackagesToScan = extraCordappPackagesToScan ), @@ -371,9 +368,8 @@ fun driver( return driver(defaultParameters = parameters, dsl = dsl) } -/** - * Helper builder for configuring a [driver] from Java. - */ +/** Helper builder for configuring a [driver] from Java. */ +@Suppress("unused") data class DriverParameters( val isDebug: Boolean = false, val driverDirectory: Path = Paths.get("build", getTimestampAsDirectoryName()), @@ -382,7 +378,6 @@ data class DriverParameters( val extraSystemProperties: Map = emptyMap(), val useTestClock: Boolean = false, val initialiseSerialization: Boolean = true, - val networkMapStartStrategy: NetworkMapStartStrategy = NetworkMapStartStrategy.Dedicated(startAutomatically = true), val startNodesInProcess: Boolean = false, val extraCordappPackagesToScan: List = emptyList() ) { @@ -393,7 +388,6 @@ data class DriverParameters( fun setExtraSystemProperties(extraSystemProperties: Map) = copy(extraSystemProperties = extraSystemProperties) fun setUseTestClock(useTestClock: Boolean) = copy(useTestClock = useTestClock) fun setInitialiseSerialization(initialiseSerialization: Boolean) = copy(initialiseSerialization = initialiseSerialization) - fun setNetworkMapStartStrategy(networkMapStartStrategy: NetworkMapStartStrategy) = copy(networkMapStartStrategy = networkMapStartStrategy) fun setStartNodesInProcess(startNodesInProcess: Boolean) = copy(startNodesInProcess = startNodesInProcess) fun setExtraCordappPackagesToScan(extraCordappPackagesToScan: List) = copy(extraCordappPackagesToScan = extraCordappPackagesToScan) } @@ -608,11 +602,9 @@ class DriverDSL( val driverDirectory: Path, val useTestClock: Boolean, val isDebug: Boolean, - val networkMapStartStrategy: NetworkMapStartStrategy, val startNodesInProcess: Boolean, extraCordappPackagesToScan: List ) : DriverDSLInternalInterface { - private val dedicatedNetworkMapAddress = portAllocation.nextHostAndPort() private var _executorService: ScheduledExecutorService? = null val executorService get() = _executorService!! private var _shutdownManager: ShutdownManager? = null @@ -620,6 +612,12 @@ class DriverDSL( private val databaseNamesByNode = mutableMapOf() val systemProperties by lazy { System.getProperties().toList().map { it.first.toString() to it.second.toString() }.toMap() + extraSystemProperties } private val cordappPackages = extraCordappPackagesToScan + getCallerPackage() + // TODO: this object will copy NodeInfo files from started nodes to other nodes additional-node-infos/ + // This uses the FileSystem and adds a delay (~5 seconds) given by the time we wait before polling the file system. + // Investigate whether we can avoid that. + private val nodeInfoFilesCopier = NodeInfoFilesCopier() + // Map from a nodes legal name to an observable emitting the number of nodes in its network map. + private val countObservables = mutableMapOf>() class State { val processes = ArrayList>() @@ -676,25 +674,6 @@ class DriverDSL( } } - private fun networkMapServiceConfigLookup(networkMapCandidates: List): (CordaX500Name) -> Map? { - return networkMapStartStrategy.run { - when (this) { - is NetworkMapStartStrategy.Dedicated -> { - serviceConfig(dedicatedNetworkMapAddress).let { - { _: CordaX500Name -> it } - } - } - is NetworkMapStartStrategy.Nominated -> { - serviceConfig(networkMapCandidates.single { - it.name == legalName.toString() - }.config.getString("p2pAddress").let(NetworkHostAndPort.Companion::parse)).let { - { nodeName: CordaX500Name -> if (nodeName == legalName) null else it } - } - } - } - } - } - override fun startNode( defaultParameters: NodeParameters, providedName: CordaX500Name?, @@ -710,10 +689,6 @@ class DriverDSL( val webAddress = portAllocation.nextHostAndPort() // TODO: Derive name from the full picked name, don't just wrap the common name val name = providedName ?: CordaX500Name(organisation = "${oneOf(names).organisation}-${p2pAddress.port}", locality = "London", country = "GB") - val networkMapServiceConfigLookup = networkMapServiceConfigLookup(listOf(object : NodeDefinition { - override fun getName() = name.toString() - override fun getConfig() = configOf("p2pAddress" to p2pAddress.toString()) - })) val config = ConfigHelper.loadConfig( baseDirectory = baseDirectory(name), allowMissingConfig = true, @@ -722,10 +697,10 @@ class DriverDSL( "p2pAddress" to p2pAddress.toString(), "rpcAddress" to rpcAddress.toString(), "webAddress" to webAddress.toString(), - "networkMapService" to networkMapServiceConfigLookup(name), "useTestClock" to useTestClock, "rpcUsers" to if (rpcUsers.isEmpty()) defaultRpcUserList else rpcUsers.map { it.toConfig().root().unwrapped() }, - "verifierType" to verifierType.name + "verifierType" to verifierType.name, + "noNetworkMapServiceMode" to true ) + customOverrides ) return startNodeInternal(name, config, webAddress, startInSameProcess, maximumHeapSize, logLevel) @@ -741,7 +716,6 @@ class DriverDSL( } override fun startNodes(nodes: List, startInSameProcess: Boolean?, maximumHeapSize: String): List> { - val networkMapServiceConfigLookup = networkMapServiceConfigLookup(nodes) return nodes.map { node -> portAllocation.nextHostAndPort() // rpcAddress val webAddress = portAllocation.nextHostAndPort() @@ -752,8 +726,8 @@ class DriverDSL( baseDirectory = baseDirectory(name), allowMissingConfig = true, configOverrides = node.config + notary + mapOf( - "networkMapService" to networkMapServiceConfigLookup(name), - "rpcUsers" to if (rpcUsers.isEmpty()) defaultRpcUserList else rpcUsers + "rpcUsers" to if (rpcUsers.isEmpty()) defaultRpcUserList else rpcUsers, + "noNetworkMapServiceMode" to true ) ) startNodeInternal(name, config, webAddress, startInSameProcess, maximumHeapSize) @@ -839,9 +813,7 @@ class DriverDSL( override fun start() { _executorService = Executors.newScheduledThreadPool(2, ThreadFactoryBuilder().setNameFormat("driver-pool-thread-%d").build()) _shutdownManager = ShutdownManager(executorService) - if (networkMapStartStrategy.startDedicated) { - startDedicatedNetworkMapService().andForget(log) // Allow it to start concurrently with other nodes. - } + shutdownManager.registerShutdown { nodeInfoFilesCopier.close() } } fun baseDirectory(nodeName: CordaX500Name): Path { @@ -852,29 +824,51 @@ class DriverDSL( override fun baseDirectory(nodeName: String): Path = baseDirectory(CordaX500Name.parse(nodeName)) - override fun startDedicatedNetworkMapService(startInProcess: Boolean?, maximumHeapSize: String): CordaFuture { - val webAddress = portAllocation.nextHostAndPort() - val rpcAddress = portAllocation.nextHostAndPort() - val networkMapLegalName = networkMapStartStrategy.legalName - - val config = ConfigHelper.loadConfig( - baseDirectory = baseDirectory(networkMapLegalName), - allowMissingConfig = true, - configOverrides = configOf( - "myLegalName" to networkMapLegalName.toString(), - // TODO: remove the webAddress as NMS doesn't need to run a web server. This will cause all - // node port numbers to be shifted, so all demos and docs need to be updated accordingly. - "webAddress" to webAddress.toString(), - "rpcAddress" to rpcAddress.toString(), - "rpcUsers" to defaultRpcUserList, - "p2pAddress" to dedicatedNetworkMapAddress.toString(), - "useTestClock" to useTestClock) - ) - return startNodeInternal(networkMapLegalName, config, webAddress, startInProcess, maximumHeapSize) + /** + * @param initial number of nodes currently in the network map of a running node. + * @param networkMapCacheChangeObservable an observable returning the updates to the node network map. + * @return a [ConnectableObservable] which emits a new [Int] every time the number of registered nodes changes + * the initial value emitted is always [initial] + */ + private fun nodeCountObservable(initial: Int, networkMapCacheChangeObservable: Observable): + ConnectableObservable { + val count = AtomicInteger(initial) + return networkMapCacheChangeObservable.map { it -> + when (it) { + is NetworkMapCache.MapChange.Added -> count.incrementAndGet() + is NetworkMapCache.MapChange.Removed -> count.decrementAndGet() + is NetworkMapCache.MapChange.Modified -> count.get() + } + }.startWith(initial).replay() } - private fun startNodeInternal(name: CordaX500Name, config: Config, webAddress: NetworkHostAndPort, startInProcess: Boolean?, maximumHeapSize: String, logLevel: String? = null): CordaFuture { + /** + * @param rpc the [CordaRPCOps] of a newly started node. + * @return a [CordaFuture] which resolves when every node started by driver has in its network map a number of nodes + * equal to the number of running nodes. The future will yield the number of connected nodes. + */ + private fun allNodesConnected(rpc: CordaRPCOps): CordaFuture { + val (snapshot, updates) = rpc.networkMapFeed() + val counterObservable = nodeCountObservable(snapshot.size, updates) + countObservables.put(rpc.nodeInfo().legalIdentities.first().name, counterObservable) + /* TODO: this might not always be the exact number of nodes one has to wait for, + * for example in the following sequence + * 1 start 3 nodes in order, A, B, C. + * 2 before the future returned by this function resolves, kill B + * At that point this future won't ever resolve as it will wait for nodes to know 3 other nodes. + */ + val requiredNodes = countObservables.size + // This is an observable which yield the minimum number of nodes in each node network map. + val smallestSeenNetworkMapSize = Observable.combineLatest(countObservables.values.toList()) { args : Array -> + args.map { it as Int }.min() ?: 0 + } + val future = smallestSeenNetworkMapSize.filter { it >= requiredNodes }.toFuture() + counterObservable.connect() + return future + } + + private fun startNodeInternal(name: CordaX500Name,config: Config, webAddress: NetworkHostAndPort, startInProcess: Boolean?, maximumHeapSize: String, logLevel: String? = null): CordaFuture { val globalDataSourceProperties = mutableMapOf() val overriddenDatasourceUrl = systemProperties["dataSourceProperties.dataSource.url"] @@ -882,9 +876,13 @@ class DriverDSL( val connectionString = overriddenDatasourceUrl + "/" + databaseNamesByNode.computeIfAbsent(name, { UUID.randomUUID().toString() }) globalDataSourceProperties["dataSourceProperties.dataSource.url"] = connectionString } - val enhancedConfig = config + globalDataSourceProperties + val enhancedConfig = config+ globalDataSourceProperties val nodeConfiguration = (enhancedConfig).parseAs() - + nodeInfoFilesCopier.addConfig(nodeConfiguration.baseDirectory) + val onNodeExit: () -> Unit = { + nodeInfoFilesCopier.removeConfig(nodeConfiguration.baseDirectory) + countObservables.remove(nodeConfiguration.myLegalName) + } if (startInProcess ?: startNodesInProcess) { val nodeAndThreadFuture = startInProcessNode(executorService, nodeConfiguration, enhancedConfig, cordappPackages) shutdownManager.registerShutdown( @@ -897,8 +895,8 @@ class DriverDSL( ) return nodeAndThreadFuture.flatMap { (node, thread) -> establishRpc(nodeConfiguration, openFuture()).flatMap { rpc -> - rpc.waitUntilNetworkReady().map { - NodeHandle.InProcess(rpc.nodeInfo(), rpc, nodeConfiguration, webAddress, node, thread) + allNodesConnected(rpc).map { + NodeHandle.InProcess(rpc.nodeInfo(), rpc, nodeConfiguration, webAddress, node, thread, onNodeExit) } } } @@ -913,7 +911,7 @@ class DriverDSL( establishRpc(nodeConfiguration, processDeathFuture).flatMap { rpc -> // Call waitUntilNetworkReady in background in case RPC is failing over: val forked = executorService.fork { - rpc.waitUntilNetworkReady() + allNodesConnected(rpc) } val networkMapFuture = forked.flatMap { it } firstOf(processDeathFuture, networkMapFuture) { @@ -922,7 +920,8 @@ class DriverDSL( } processDeathFuture.cancel(false) log.info("Node handle is ready. NodeInfo: ${rpc.nodeInfo()}, WebAddress: ${webAddress}") - NodeHandle.OutOfProcess(rpc.nodeInfo(), rpc, nodeConfiguration, webAddress, debugPort, process) + NodeHandle.OutOfProcess(rpc.nodeInfo(), rpc, nodeConfiguration, webAddress, debugPort, process, + onNodeExit) } } } @@ -977,7 +976,7 @@ class DriverDSL( logLevel: String? = null ): CordaFuture { val processFuture = executorService.fork { - log.info("Starting out-of-process Node ${nodeConf.myLegalName.organisation}") + log.info("Starting out-of-process Node ${nodeConf.myLegalName.organisation}, debug port is " + debugPort ?: "not enabled") // Write node.conf writeConfig(nodeConf.baseDirectory, "node.conf", config) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/NetworkMapStartStrategy.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/NetworkMapStartStrategy.kt deleted file mode 100644 index 0086884c77..0000000000 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/NetworkMapStartStrategy.kt +++ /dev/null @@ -1,23 +0,0 @@ -package net.corda.testing.driver - -import net.corda.core.identity.CordaX500Name -import net.corda.core.utilities.NetworkHostAndPort -import net.corda.testing.DUMMY_MAP - -sealed class NetworkMapStartStrategy { - internal abstract val startDedicated: Boolean - internal abstract val legalName: CordaX500Name - internal fun serviceConfig(address: NetworkHostAndPort) = mapOf( - "address" to address.toString(), - "legalName" to legalName.toString() - ) - - class Dedicated(startAutomatically: Boolean) : NetworkMapStartStrategy() { - override val startDedicated = startAutomatically - override val legalName = DUMMY_MAP.name - } - - class Nominated(override val legalName: CordaX500Name) : NetworkMapStartStrategy() { - override val startDedicated = false - } -} diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt index a44cf1c4a5..406e72dda0 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt @@ -279,7 +279,7 @@ class InMemoryMessagingNetwork( _sentMessages.onNext(transfer) } - private data class InMemoryMessage(override val topicSession: TopicSession, + data class InMemoryMessage(override val topicSession: TopicSession, override val data: ByteArray, override val uniqueMessageId: UUID, override val debugTimestamp: Instant = Instant.now()) : Message { @@ -363,14 +363,22 @@ class InMemoryMessagingNetwork( state.locked { check(handlers.remove(registration as Handler)) } } - override fun send(message: Message, target: MessageRecipients, retryId: Long?) { + override fun send(message: Message, target: MessageRecipients, retryId: Long?, sequenceKey: Any, acknowledgementHandler: (() -> Unit)?) { check(running) msgSend(this, message, target) + acknowledgementHandler?.invoke() if (!sendManuallyPumped) { pumpSend(false) } } + override fun send(addressedMessages: List, acknowledgementHandler: (() -> Unit)?) { + for ((message, target, retryId, sequenceKey) in addressedMessages) { + send(message, target, retryId, sequenceKey, null) + } + acknowledgementHandler?.invoke() + } + override fun stop() { if (backgroundThread != null) { backgroundThread.interrupt() diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetworkMapCache.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetworkMapCache.kt index 1b82ca04c1..889f9cfd3b 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetworkMapCache.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetworkMapCache.kt @@ -7,9 +7,9 @@ import net.corda.core.identity.Party import net.corda.core.node.NodeInfo import net.corda.core.node.services.NetworkMapCache import net.corda.core.utilities.NetworkHostAndPort -import net.corda.core.utilities.NonEmptySet -import net.corda.node.services.api.ServiceHubInternal +import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.network.PersistentNetworkMapCache +import net.corda.node.utilities.CordaPersistence import net.corda.testing.getTestPartyAndCertificate import rx.Observable import rx.subjects.PublishSubject @@ -18,7 +18,7 @@ import java.math.BigInteger /** * Network map cache with no backing map service. */ -class MockNetworkMapCache(serviceHub: ServiceHubInternal) : PersistentNetworkMapCache(serviceHub) { +class MockNetworkMapCache(database: CordaPersistence, configuration: NodeConfiguration) : PersistentNetworkMapCache(database, configuration) { private companion object { val BANK_C = getTestPartyAndCertificate(CordaX500Name(organisation = "Bank C", locality = "London", country = "GB"), entropyToKeyPair(BigInteger.valueOf(1000)).public) val BANK_D = getTestPartyAndCertificate(CordaX500Name(organisation = "Bank D", locality = "London", country = "GB"), entropyToKeyPair(BigInteger.valueOf(2000)).public) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt index ae810b581a..71a9148900 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt @@ -2,13 +2,12 @@ package net.corda.testing.node import com.google.common.jimfs.Configuration.unix import com.google.common.jimfs.Jimfs +import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever import net.corda.core.crypto.entropyToKeyPair import net.corda.core.crypto.random63BitValue import net.corda.core.identity.CordaX500Name -import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate -import net.corda.core.internal.cert import net.corda.core.internal.concurrent.doneFuture import net.corda.core.internal.createDirectories import net.corda.core.internal.createDirectory @@ -18,7 +17,7 @@ import net.corda.core.messaging.RPCOps import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.node.services.IdentityService import net.corda.core.node.services.KeyManagementService -import net.corda.core.node.services.NetworkMapCache +import net.corda.core.node.services.PartyInfo import net.corda.core.serialization.SerializationWhitelist import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.getOrThrow @@ -32,9 +31,8 @@ import net.corda.node.services.api.SchemaService import net.corda.node.services.config.BFTSMaRtConfiguration import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.NotaryConfig -import net.corda.node.services.identity.PersistentIdentityService import net.corda.node.services.keys.E2ETestKeyManagementService -import net.corda.node.services.messaging.MessagingService +import net.corda.node.services.messaging.* import net.corda.node.services.network.InMemoryNetworkMapService import net.corda.node.services.network.NetworkMapService import net.corda.node.services.transactions.BFTNonValidatingNotaryService @@ -42,11 +40,13 @@ import net.corda.node.services.transactions.BFTSMaRt import net.corda.node.services.transactions.InMemoryTransactionVerifierService import net.corda.node.utilities.AffinityExecutor import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor -import net.corda.node.utilities.CertificateAndKeyPair import net.corda.nodeapi.internal.ServiceInfo -import net.corda.testing.* +import net.corda.testing.DUMMY_NOTARY +import net.corda.testing.initialiseTestSerialization import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties +import net.corda.testing.resetTestSerialization +import net.corda.testing.testNodeConfiguration import org.apache.activemq.artemis.utils.ReusableLatch import org.slf4j.Logger import java.io.Closeable @@ -54,7 +54,7 @@ import java.math.BigInteger import java.nio.file.Path import java.security.KeyPair import java.security.PublicKey -import java.security.cert.X509Certificate +import java.util.* import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicInteger @@ -62,6 +62,51 @@ fun StartedNode.pumpReceive(block: Boolean = false): InMem return (network as InMemoryMessagingNetwork.InMemoryMessaging).pumpReceive(block) } +/** Helper builder for configuring a [MockNetwork] from Java. */ +@Suppress("unused") +data class MockNetworkParameters( + val networkSendManuallyPumped: Boolean = false, + val threadPerNode: Boolean = false, + val servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = InMemoryMessagingNetwork.ServicePeerAllocationStrategy.Random(), + val defaultFactory: MockNetwork.Factory<*> = MockNetwork.DefaultFactory, + val initialiseSerialization: Boolean = true, + val cordappPackages: List = emptyList()) { + fun setNetworkSendManuallyPumped(networkSendManuallyPumped: Boolean) = copy(networkSendManuallyPumped = networkSendManuallyPumped) + fun setThreadPerNode(threadPerNode: Boolean) = copy(threadPerNode = threadPerNode) + fun setServicePeerAllocationStrategy(servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy) = copy(servicePeerAllocationStrategy = servicePeerAllocationStrategy) + fun setDefaultFactory(defaultFactory: MockNetwork.Factory<*>) = copy(defaultFactory = defaultFactory) + fun setInitialiseSerialization(initialiseSerialization: Boolean) = copy(initialiseSerialization = initialiseSerialization) + fun setCordappPackages(cordappPackages: List) = copy(cordappPackages = cordappPackages) +} + +/** + * @param notaryIdentity a set of service entries to use in place of the node's default service entries, + * for example where a node's service is part of a cluster. + * @param entropyRoot the initial entropy value to use when generating keys. Defaults to an (insecure) random value, + * but can be overridden to cause nodes to have stable or colliding identity/service keys. + * @param configOverrides add/override behaviour of the [NodeConfiguration] mock object. + */ +@Suppress("unused") +data class MockNodeParameters( + val forcedID: Int? = null, + val legalName: CordaX500Name? = null, + val notaryIdentity: Pair? = null, + val entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), + val configOverrides: (NodeConfiguration) -> Any? = {}) { + fun setForcedID(forcedID: Int?) = copy(forcedID = forcedID) + fun setLegalName(legalName: CordaX500Name?) = copy(legalName = legalName) + fun setNotaryIdentity(notaryIdentity: Pair?) = copy(notaryIdentity = notaryIdentity) + fun setEntropyRoot(entropyRoot: BigInteger) = copy(entropyRoot = entropyRoot) + fun setConfigOverrides(configOverrides: (NodeConfiguration) -> Any?) = copy(configOverrides = configOverrides) +} + +data class MockNodeArgs( + val config: NodeConfiguration, + val network: MockNetwork, + val id: Int, + val notaryIdentity: Pair?, + val entropyRoot: BigInteger) + /** * A mock node brings up a suite of in-memory services in a fast manner suitable for unit testing. * Components that do IO are either swapped out for mocks, or pointed to a [Jimfs] in memory filesystem or an in @@ -75,17 +120,16 @@ fun StartedNode.pumpReceive(block: Boolean = false): InMem * * LogHelper.setLevel("+messages") */ -class MockNetwork(private val networkSendManuallyPumped: Boolean = false, - private val threadPerNode: Boolean = false, - servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = - InMemoryMessagingNetwork.ServicePeerAllocationStrategy.Random(), - private val defaultFactory: Factory<*> = MockNetwork.DefaultFactory, - private val initialiseSerialization: Boolean = true, - private val cordappPackages: List = emptyList()) : Closeable { - companion object { - // TODO In future PR we're removing the concept of network map node so the details of this mock are not important. - val MOCK_NET_MAP = Party(CordaX500Name(organisation = "Mock Network Map", locality = "Madrid", country = "ES"), DUMMY_KEY_1.public) - } +class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParameters(), + private val networkSendManuallyPumped: Boolean = defaultParameters.networkSendManuallyPumped, + private val threadPerNode: Boolean = defaultParameters.threadPerNode, + servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = defaultParameters.servicePeerAllocationStrategy, + private val defaultFactory: Factory<*> = defaultParameters.defaultFactory, + private val initialiseSerialization: Boolean = defaultParameters.initialiseSerialization, + private val cordappPackages: List = defaultParameters.cordappPackages) : Closeable { + /** Helper constructor for creating a [MockNetwork] with custom parameters from Java. */ + constructor(parameters: MockNetworkParameters) : this(defaultParameters = parameters) + var nextNodeId = 0 private set private val filesystem = Jimfs.newFileSystem(unix()) @@ -97,9 +141,6 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false, /** A read only view of the current set of executing nodes. */ val nodes: List get() = _nodes - private var _networkMapNode: StartedNode? = null - val networkMapNode: StartedNode get() = _networkMapNode ?: startNetworkMapNode() - init { if (initialiseSerialization) initialiseTestSerialization() filesystem.getPath("/nodes").createDirectory() @@ -107,21 +148,11 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false, /** Allows customisation of how nodes are created. */ interface Factory { - /** - * @param notaryIdentity is an additional override to use in place of the node's default notary service, - * main usage is for when the node is part of a notary cluster. - * @param entropyRoot the initial entropy value to use when generating keys. Defaults to an (insecure) random value, - * but can be overriden to cause nodes to have stable or colliding identity/service keys. - */ - fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, - id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): N + fun create(args: MockNodeArgs): N } object DefaultFactory : Factory { - override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, - id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): MockNode { - return MockNode(config, network, networkMapAddr, id, notaryIdentity, entropyRoot) - } + override fun create(args: MockNodeArgs) = MockNode(args) } /** @@ -147,19 +178,17 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false, } } - /** - * @param notaryIdentity is an additional override to use in place of the node's default notary service, - * main usage is for when the node is part of a notary cluster. - * @param entropyRoot the initial entropy value to use when generating keys. Defaults to an (insecure) random value, - * but can be overriden to cause nodes to have stable or colliding identity/service keys. - */ - open class MockNode(config: NodeConfiguration, - val mockNet: MockNetwork, - override val networkMapAddress: SingleMessageRecipient?, - val id: Int, - internal val notaryIdentity: Pair?, - val entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue())) : - AbstractNode(config, TestClock(), MOCK_VERSION_INFO, CordappLoader.createDefaultWithTestPackages(config, mockNet.cordappPackages), mockNet.busyLatch) { + open class MockNode(args: MockNodeArgs) : AbstractNode( + args.config, + TestClock(), + MOCK_VERSION_INFO, + CordappLoader.createDefaultWithTestPackages(args.config, args.network.cordappPackages), + args.network.busyLatch) { + val mockNet = args.network + override val networkMapAddress = null + val id = args.id + internal val notaryIdentity = args.notaryIdentity + val entropyRoot = args.entropyRoot var counter = entropyRoot override val log: Logger = loggerFor() override val serverThread: AffinityExecutor = @@ -187,24 +216,8 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false, .getOrThrow() } - override fun makeIdentityService(trustRoot: X509Certificate, - clientCa: CertificateAndKeyPair?, - legalIdentity: PartyAndCertificate): IdentityService { - val caCertificates: Array = listOf(legalIdentity.certificate, clientCa?.certificate?.cert) - .filterNotNull() - .toTypedArray() - val identityService = PersistentIdentityService(info.legalIdentitiesAndCerts, - trustRoot = trustRoot, caCertificates = *caCertificates) - services.networkMapCache.allNodes.forEach { it.legalIdentitiesAndCerts.forEach { identityService.verifyAndRegisterIdentity(it) } } - services.networkMapCache.changed.subscribe { mapChange -> - // TODO how should we handle network map removal - if (mapChange is NetworkMapCache.MapChange.Added) { - mapChange.node.legalIdentitiesAndCerts.forEach { - identityService.verifyAndRegisterIdentity(it) - } - } - } - return identityService + fun setMessagingServiceSpy(messagingServiceSpy: MessagingServiceSpy) { + network = messagingServiceSpy } override fun makeKeyManagementService(identityService: IdentityService): KeyManagementService { @@ -262,6 +275,8 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false, dbCloser = null } + fun hasDBConnection() = dbCloser != null + // You can change this from zero if you have custom [FlowLogic] that park themselves. e.g. [StateMachineManagerTests] var acceptableLiveFiberCountOnStop: Int = 0 @@ -275,92 +290,40 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false, throw IllegalStateException("Unable to enumerate all nodes in BFT cluster.") } clusterNodes.forEach { - val notaryService = it.started!!.smm.findServices { it is BFTNonValidatingNotaryService }.single() as BFTNonValidatingNotaryService + val notaryService = it.findTokenizableService(BFTNonValidatingNotaryService::class.java)!! notaryService.waitUntilReplicaHasInitialized() } } } } - - /** - * Makes sure that the [MockNode] is correctly registered on the [MockNetwork] - * Please note that [MockNetwork.runNetwork] should be invoked to ensure that all the pending registration requests - * were duly processed - */ - fun ensureRegistered() { - _nodeReadyFuture.getOrThrow() - } } - fun startNetworkMapNode(nodeFactory: Factory? = null): StartedNode { - check(_networkMapNode == null) { "Trying to start more than one network map node" } - return uncheckedCast(createNodeImpl(networkMapAddress = null, - forcedID = null, - nodeFactory = nodeFactory ?: defaultFactory, - legalName = MOCK_NET_MAP.name, - notaryIdentity = null, - entropyRoot = BigInteger.valueOf(random63BitValue()), - configOverrides = {}, - start = true - ).started!!.apply { - _networkMapNode = this - }) - } - - fun createUnstartedNode(forcedID: Int? = null, - legalName: CordaX500Name? = null, notaryIdentity: Pair? = null, - entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), - configOverrides: (NodeConfiguration) -> Any? = {}): MockNode { - return createUnstartedNode(forcedID, defaultFactory, legalName, notaryIdentity, entropyRoot, configOverrides = configOverrides) - } - - fun createUnstartedNode(forcedID: Int? = null, nodeFactory: Factory, - legalName: CordaX500Name? = null, notaryIdentity: Pair? = null, - entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), - configOverrides: (NodeConfiguration) -> Any? = {}): N { - val networkMapAddress = networkMapNode.network.myAddress - return createNodeImpl(networkMapAddress, forcedID, nodeFactory, false, legalName, notaryIdentity, entropyRoot, configOverrides) - } - - /** - * Returns a node, optionally created by the passed factory method. - * @param notaryIdentity a set of service entries to use in place of the node's default service entries, - * for example where a node's service is part of a cluster. - * @param entropyRoot the initial entropy value to use when generating keys. Defaults to an (insecure) random value, - * but can be overridden to cause nodes to have stable or colliding identity/service keys. - * @param configOverrides add/override behaviour of the [NodeConfiguration] mock object. - */ - fun createNode(forcedID: Int? = null, - legalName: CordaX500Name? = null, notaryIdentity: Pair? = null, - entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), - configOverrides: (NodeConfiguration) -> Any? = {}): StartedNode { - return createNode(forcedID, defaultFactory, legalName, notaryIdentity, entropyRoot, configOverrides = configOverrides) + fun createUnstartedNode(parameters: MockNodeParameters = MockNodeParameters()) = createUnstartedNode(parameters, defaultFactory) + fun createUnstartedNode(parameters: MockNodeParameters = MockNodeParameters(), nodeFactory: Factory): N { + return createNodeImpl(parameters, nodeFactory, false) } + fun createNode(parameters: MockNodeParameters = MockNodeParameters()): StartedNode = createNode(parameters, defaultFactory) /** Like the other [createNode] but takes a [Factory] and propagates its [MockNode] subtype. */ - fun createNode(forcedID: Int? = null, nodeFactory: Factory, - legalName: CordaX500Name? = null, notaryIdentity: Pair? = null, - entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), - configOverrides: (NodeConfiguration) -> Any? = {}): StartedNode { - val networkMapAddress = networkMapNode.network.myAddress - return uncheckedCast(createNodeImpl(networkMapAddress, forcedID, nodeFactory, true, legalName, notaryIdentity, entropyRoot, configOverrides).started)!! + fun createNode(parameters: MockNodeParameters = MockNodeParameters(), nodeFactory: Factory): StartedNode { + val node: StartedNode = uncheckedCast(createNodeImpl(parameters, nodeFactory, true).started)!! + ensureAllNetworkMapCachesHaveAllNodeInfos() + return node } - private fun createNodeImpl(networkMapAddress: SingleMessageRecipient?, forcedID: Int?, nodeFactory: Factory, - start: Boolean, legalName: CordaX500Name?, notaryIdentity: Pair?, - entropyRoot: BigInteger, - configOverrides: (NodeConfiguration) -> Any?): N { - val id = forcedID ?: nextNodeId++ + private fun createNodeImpl(parameters: MockNodeParameters, nodeFactory: Factory, start: Boolean): N { + val id = parameters.forcedID ?: nextNodeId++ val config = testNodeConfiguration( baseDirectory = baseDirectory(id).createDirectories(), - myLegalName = legalName ?: CordaX500Name(organisation = "Mock Company $id", locality = "London", country = "GB")).also { - whenever(it.dataSourceProperties).thenReturn(makeTestDataSourceProperties("node_${id}_net_$networkId")) - configOverrides(it) + myLegalName = parameters.legalName ?: CordaX500Name(organisation = "Mock Company $id", locality = "London", country = "GB")).also { + doReturn(makeTestDataSourceProperties("node_${id}_net_$networkId")).whenever(it).dataSourceProperties + parameters.configOverrides(it) } - return nodeFactory.create(config, this, networkMapAddress, id, notaryIdentity, entropyRoot).apply { + return nodeFactory.create(MockNodeArgs(config, this, id, parameters.notaryIdentity, parameters.entropyRoot)).apply { if (start) { start() - if (threadPerNode && networkMapAddress != null) nodeReadyFuture.getOrThrow() // XXX: What about manually-started nodes? + if (threadPerNode) nodeReadyFuture.getOrThrow() // XXX: What about manually-started nodes? + ensureAllNetworkMapCachesHaveAllNodeInfos() } _nodes.add(this) } @@ -376,6 +339,7 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false, */ @JvmOverloads fun runNetwork(rounds: Int = -1) { + ensureAllNetworkMapCachesHaveAllNodeInfos() check(!networkSendManuallyPumped) fun pumpAll() = messagingNetwork.endpoints.map { it.pumpReceive(false) } @@ -391,23 +355,24 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false, @JvmOverloads fun createNotaryNode(legalName: CordaX500Name = DUMMY_NOTARY.name, validating: Boolean = true): StartedNode { - return createNode(legalName = legalName, configOverrides = { - whenever(it.notary).thenReturn(NotaryConfig(validating)) - }) + return createNode(MockNodeParameters(legalName = legalName, configOverrides = { + doReturn(NotaryConfig(validating)).whenever(it).notary + })) } - fun createNotaryNode(legalName: CordaX500Name = DUMMY_NOTARY.name, + fun createNotaryNode(parameters: MockNodeParameters = MockNodeParameters(legalName = DUMMY_NOTARY.name), validating: Boolean = true, nodeFactory: Factory): StartedNode { - return createNode(legalName = legalName, nodeFactory = nodeFactory, configOverrides = { - whenever(it.notary).thenReturn(NotaryConfig(validating)) - }) + return createNode(parameters.copy(configOverrides = { + doReturn(NotaryConfig(validating)).whenever(it).notary + parameters.configOverrides(it) + }), nodeFactory) } @JvmOverloads fun createPartyNode(legalName: CordaX500Name? = null, notaryIdentity: Pair? = null): StartedNode { - return createNode(legalName = legalName, notaryIdentity = notaryIdentity) + return createNode(MockNodeParameters(legalName = legalName, notaryIdentity = notaryIdentity)) } @Suppress("unused") // This is used from the network visualiser tool. @@ -422,9 +387,21 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false, } } + private fun ensureAllNetworkMapCachesHaveAllNodeInfos() { + val infos = nodes.mapNotNull { it.started?.info } + nodes.filter { it.hasDBConnection() } + .mapNotNull { it.started?.services?.networkMapCache } + .forEach { + for (nodeInfo in infos) { + it.addNode(nodeInfo) + } + } + } + fun startNodes() { require(nodes.isNotEmpty()) nodes.forEach { it.started ?: it.start() } + ensureAllNetworkMapCachesHaveAllNodeInfos() } fun stopNodes() { @@ -450,4 +427,16 @@ fun network(nodesCount: Int, action: MockNetwork.(nodes: List it.createPartyNode() } action(it, nodes, notary) } +} + +/** + * Extend this class in order to intercept and modify messages passing through the [MessagingService] when using the [InMemoryNetwork]. + */ +open class MessagingServiceSpy(val messagingService: MessagingService) : MessagingService by messagingService + +/** + * Attach a [MessagingServiceSpy] to the [MockNode] allowing interception and modification of messages. + */ +fun StartedNode.setMessagingServiceSpy(messagingServiceSpy: MessagingServiceSpy) { + internals.setMessagingServiceSpy(messagingServiceSpy) } \ No newline at end of file diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt index 8dc4ddebb4..7ec6b3bdd0 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt @@ -30,6 +30,7 @@ import net.corda.node.services.persistence.HibernateConfiguration import net.corda.node.services.persistence.InMemoryStateMachineRecordedTransactionMappingStorage import net.corda.node.services.schema.HibernateObserver import net.corda.node.services.schema.NodeSchemaService +import net.corda.node.services.statemachine.FlowStateMachineImpl import net.corda.node.services.transactions.InMemoryTransactionVerifierService import net.corda.node.services.vault.NodeVaultService import net.corda.node.utilities.CordaPersistence @@ -179,7 +180,7 @@ open class MockServices( fun makeVaultService(hibernateConfig: HibernateConfiguration): VaultServiceInternal { val vaultService = NodeVaultService(Clock.systemUTC(), keyManagementService, stateLoader, hibernateConfig) - hibernatePersister = HibernateObserver(vaultService.rawUpdates, hibernateConfig) + hibernatePersister = HibernateObserver.install(vaultService.rawUpdates, hibernateConfig) return vaultService } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/SimpleNode.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/SimpleNode.kt index 3b7bf9130e..6dad146916 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/SimpleNode.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/SimpleNode.kt @@ -14,8 +14,8 @@ import net.corda.node.services.identity.InMemoryIdentityService import net.corda.node.services.keys.E2ETestKeyManagementService import net.corda.node.services.messaging.ArtemisMessagingServer import net.corda.node.services.messaging.NodeMessagingClient +import net.corda.node.services.network.NetworkMapCacheImpl import net.corda.node.services.schema.NodeSchemaService -import net.corda.node.testing.MockServiceHubInternal import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.configureDatabase @@ -42,7 +42,7 @@ class SimpleNode(val config: NodeConfiguration, val address: NetworkHostAndPort val executor = ServiceAffinityExecutor(config.myLegalName.organisation, 1) // TODO: We should have a dummy service hub rather than change behaviour in tests val broker = ArtemisMessagingServer(config, address.port, rpcAddress.port, - MockNetworkMapCache(serviceHub = object : MockServiceHubInternal(database = database, configuration = config) {}), userService) + NetworkMapCacheImpl(MockNetworkMapCache(database, config), identityService), userService) val networkMapRegistrationFuture = openFuture() val network = database.transaction { NodeMessagingClient( diff --git a/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt b/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt index ae43e144db..28d1afb9bf 100644 --- a/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt +++ b/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt @@ -96,8 +96,10 @@ class NodeProcess( private fun startNode(nodeDir: Path): Process { val builder = ProcessBuilder() - .command(javaPath.toString(), "-jar", cordaJar.toString()) + .command(javaPath.toString(), "-Dcapsule.log=verbose", "-jar", cordaJar.toString()) .directory(nodeDir.toFile()) + .redirectError(ProcessBuilder.Redirect.INHERIT) + .redirectOutput(ProcessBuilder.Redirect.INHERIT) builder.environment().putAll(mapOf( "CAPSULE_CACHE_DIR" to (buildDirectory / "capsule").toString() diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt index 71be78c0bd..a1cda49a22 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt @@ -3,6 +3,7 @@ package net.corda.testing +import com.nhaarman.mockito_kotlin.mock import net.corda.core.contracts.StateRef import net.corda.core.crypto.SecureHash import net.corda.core.crypto.generateKeyPair @@ -24,6 +25,8 @@ import net.corda.node.utilities.CertificateType import net.corda.node.utilities.X509Utilities import net.corda.nodeapi.config.SSLConfiguration import net.corda.nodeapi.internal.serialization.AMQP_ENABLED +import org.mockito.internal.stubbing.answers.ThrowsException +import org.mockito.stubbing.Answer import java.nio.file.Files import java.security.KeyPair import java.security.PublicKey @@ -177,3 +180,19 @@ fun NodeInfo.singleIdentityAndCert(): PartyAndCertificate = legalIdentitiesAndCe fun NodeInfo.singleIdentity(): Party = singleIdentityAndCert().party /** Returns the identity of the first notary found on the network */ fun ServiceHub.getDefaultNotary(): Party = networkMapCache.notaryIdentities.first() + +/** + * A method on a mock was called, but no behaviour was previously specified for that method. + * You can use [com.nhaarman.mockito_kotlin.doReturn] or similar to specify behaviour, see Mockito documentation for details. + */ +class UndefinedMockBehaviorException(message: String) : RuntimeException(message) + +/** + * Create a Mockito mock that has [UndefinedMockBehaviorException] as the default behaviour of all methods. + * @param T the type to mock. Note if you want to use [com.nhaarman.mockito_kotlin.doCallRealMethod] on a Kotlin interface, + * it won't work unless you mock a (trivial) abstract implementation of that interface instead. + */ +inline fun rigorousMock() = mock(Answer { + // Use ThrowsException to hack the stack trace, and lazily so we can customise the message: + ThrowsException(UndefinedMockBehaviorException("Please specify what should happen when '${it.method}' is called, or don't call it.")).answer(it) +}) diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt index 891808e4b5..830316435e 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt @@ -103,6 +103,10 @@ class TestSerializationFactory : SerializationFactory() { return delegate!!.deserialize(byteSequence, clazz, context) } + override fun deserializeWithCompatibleContext(byteSequence: ByteSequence, clazz: Class, context: SerializationContext): ObjectWithCompatibleContext { + return delegate!!.deserializeWithCompatibleContext(byteSequence, clazz, context) + } + override fun serialize(obj: T, context: SerializationContext): SerializedBytes { return delegate!!.serialize(obj, context) } @@ -147,7 +151,7 @@ class TestSerializationContext : SerializationContext { return TestSerializationContext().apply { delegate = this@TestSerializationContext.delegate!!.withWhitelisted(clazz) } } - override fun withPreferredSerializationVersion(versionHeader: ByteSequence): SerializationContext { + override fun withPreferredSerializationVersion(versionHeader: VersionHeader): SerializationContext { return TestSerializationContext().apply { delegate = this@TestSerializationContext.delegate!!.withPreferredSerializationVersion(versionHeader) } } diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/model/DemoBenchNodeInfoFilesCopier.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/model/DemoBenchNodeInfoFilesCopier.kt new file mode 100644 index 0000000000..a0818c3532 --- /dev/null +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/model/DemoBenchNodeInfoFilesCopier.kt @@ -0,0 +1,35 @@ +package net.corda.demobench.model + +import net.corda.nodeapi.NodeInfoFilesCopier +import rx.Scheduler +import rx.schedulers.Schedulers +import tornadofx.* + +/** + * Utility class which copies nodeInfo files across a set of running nodes. + * + * This class will create paths that it needs to poll and to where it needs to copy files in case those + * don't exist yet. + */ +class DemoBenchNodeInfoFilesCopier(scheduler: Scheduler = Schedulers.io()): Controller() { + + private val nodeInfoFilesCopier = NodeInfoFilesCopier(scheduler) + + /** + * @param nodeConfig the configuration to be added. + * Add a [NodeConfig] for a node which is about to be started. + * Its nodeInfo file will be copied to other nodes' additional-node-infos directory, and conversely, + * other nodes' nodeInfo files will be copied to this node additional-node-infos directory. + */ + fun addConfig(nodeConfig: NodeConfigWrapper) : Unit = nodeInfoFilesCopier.addConfig(nodeConfig.nodeDir) + + /** + * @param nodeConfig the configuration to be removed. + * Remove the configuration of a node which is about to be stopped or already stopped. + * No files written by that node will be copied to other nodes, nor files from other nodes will be copied to this + * one. + */ + fun removeConfig(nodeConfig: NodeConfigWrapper) : Unit = nodeInfoFilesCopier.removeConfig(nodeConfig.nodeDir) + + fun reset() : Unit = nodeInfoFilesCopier.reset() +} \ No newline at end of file diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt index ddd8010bb4..2830c969b8 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt @@ -28,7 +28,7 @@ class NodeController(check: atRuntime = ::checkExists) : Controller() { private val jvm by inject() private val cordappController by inject() - private val nodeInfoFilesCopier by inject() + private val nodeInfoFilesCopier by inject() private var baseDir: Path = baseDirFor(ManagementFactory.getRuntimeMXBean().startTime) private val cordaPath: Path = jvm.applicationDir.resolve("corda").resolve("corda.jar") diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeInfoFilesCopier.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeInfoFilesCopier.kt deleted file mode 100644 index ed69e38b93..0000000000 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeInfoFilesCopier.kt +++ /dev/null @@ -1,133 +0,0 @@ -package net.corda.demobench.model - -import net.corda.cordform.CordformNode -import net.corda.core.internal.createDirectories -import net.corda.core.internal.isRegularFile -import net.corda.core.internal.list -import rx.Observable -import rx.Scheduler -import rx.schedulers.Schedulers -import tornadofx.* -import java.io.IOException -import java.nio.file.Files -import java.nio.file.Path -import java.nio.file.StandardCopyOption.COPY_ATTRIBUTES -import java.nio.file.StandardCopyOption.REPLACE_EXISTING -import java.nio.file.attribute.BasicFileAttributes -import java.nio.file.attribute.FileTime -import java.util.concurrent.TimeUnit -import java.util.logging.Level - -/** - * Utility class which copies nodeInfo files across a set of running nodes. - * - * This class will create paths that it needs to poll and to where it needs to copy files in case those - * don't exist yet. - */ -class NodeInfoFilesCopier(scheduler: Scheduler = Schedulers.io()): Controller() { - - private val nodeDataMap = mutableMapOf() - - init { - Observable.interval(5, TimeUnit.SECONDS, scheduler) - .subscribe { poll() } - } - - /** - * @param nodeConfig the configuration to be added. - * Add a [NodeConfig] for a node which is about to be started. - * Its nodeInfo file will be copied to other nodes' additional-node-infos directory, and conversely, - * other nodes' nodeInfo files will be copied to this node additional-node-infos directory. - */ - @Synchronized - fun addConfig(nodeConfig: NodeConfigWrapper) { - val newNodeFile = NodeData(nodeConfig.nodeDir) - nodeDataMap[nodeConfig.nodeDir] = newNodeFile - - for (previouslySeenFile in allPreviouslySeenFiles()) { - copy(previouslySeenFile, newNodeFile.destination.resolve(previouslySeenFile.fileName)) - } - log.info("Now watching: ${nodeConfig.nodeDir}") - } - - /** - * @param nodeConfig the configuration to be removed. - * Remove the configuration of a node which is about to be stopped or already stopped. - * No files written by that node will be copied to other nodes, nor files from other nodes will be copied to this - * one. - */ - @Synchronized - fun removeConfig(nodeConfig: NodeConfigWrapper) { - nodeDataMap.remove(nodeConfig.nodeDir) ?: return - log.info("Stopped watching: ${nodeConfig.nodeDir}") - } - - @Synchronized - fun reset() { - nodeDataMap.clear() - } - - private fun allPreviouslySeenFiles() = nodeDataMap.values.map { it.previouslySeenFiles.keys }.flatten() - - @Synchronized - private fun poll() { - for (nodeData in nodeDataMap.values) { - nodeData.nodeDir.list { paths -> - paths.filter { it.isRegularFile() } - .filter { it.fileName.toString().startsWith("nodeInfo-") } - .forEach { path -> processPath(nodeData, path) } - } - } - } - - // Takes a path under nodeData config dir and decides whether the file represented by that path needs to - // be copied. - private fun processPath(nodeData: NodeData, path: Path) { - val newTimestamp = Files.readAttributes(path, BasicFileAttributes::class.java).lastModifiedTime() - val previousTimestamp = nodeData.previouslySeenFiles.put(path, newTimestamp) ?: FileTime.fromMillis(-1) - if (newTimestamp > previousTimestamp) { - for (destination in nodeDataMap.values.filter { it.nodeDir != nodeData.nodeDir }.map { it.destination }) { - val fullDestinationPath = destination.resolve(path.fileName) - copy(path, fullDestinationPath) - } - } - } - - private fun copy(source: Path, destination: Path) { - val tempDestination = try { - Files.createTempFile(destination.parent, ".", null) - } catch (exception: IOException) { - log.log(Level.WARNING, "Couldn't create a temporary file to copy $source", exception) - throw exception - } - try { - // First copy the file to a temporary file within the appropriate directory. - Files.copy(source, tempDestination, COPY_ATTRIBUTES, REPLACE_EXISTING) - } catch (exception: IOException) { - log.log(Level.WARNING, "Couldn't copy $source to $tempDestination.", exception) - Files.delete(tempDestination) - throw exception - } - try { - // Then rename it to the desired name. This way the file 'appears' on the filesystem as an atomic operation. - Files.move(tempDestination, destination, REPLACE_EXISTING) - } catch (exception: IOException) { - log.log(Level.WARNING, "Couldn't move $tempDestination to $destination.", exception) - Files.delete(tempDestination) - throw exception - } - } - - /** - * Convenience holder for all the paths and files relative to a single node. - */ - private class NodeData(val nodeDir: Path) { - val destination: Path = nodeDir.resolve(CordformNode.NODE_INFO_DIRECTORY) - // Map from Path to its lastModifiedTime. - val previouslySeenFiles = mutableMapOf() - - init { - destination.createDirectories() - } - } -} diff --git a/tools/demobench/src/test/kotlin/net/corda/demobench/pty/ZeroFilterTest.kt b/tools/demobench/src/test/kotlin/net/corda/demobench/pty/ZeroFilterTest.kt index 3f9bb93d17..41ccda83bf 100644 --- a/tools/demobench/src/test/kotlin/net/corda/demobench/pty/ZeroFilterTest.kt +++ b/tools/demobench/src/test/kotlin/net/corda/demobench/pty/ZeroFilterTest.kt @@ -1,9 +1,12 @@ package net.corda.demobench.pty +import com.nhaarman.mockito_kotlin.doReturn +import com.nhaarman.mockito_kotlin.verify +import com.nhaarman.mockito_kotlin.whenever +import net.corda.testing.rigorousMock import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test -import org.mockito.Mockito.* import java.io.ByteArrayOutputStream import java.io.OutputStream import java.nio.charset.StandardCharsets.UTF_8 @@ -15,10 +18,8 @@ class ZeroFilterTest { @Before fun setup() { output = ByteArrayOutputStream() - - val process = mock(Process::class.java) - `when`(process.outputStream).thenReturn(output) - + val process = rigorousMock() + doReturn(output).whenever(process).outputStream filter = process.zeroFiltered().outputStream verify(process).outputStream } diff --git a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt index d711acd838..e36854d54c 100644 --- a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt +++ b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt @@ -74,7 +74,6 @@ fun verifierDriver( debugPortAllocation: PortAllocation = PortAllocation.Incremental(5005), extraSystemProperties: Map = emptyMap(), useTestClock: Boolean = false, - networkMapStartStrategy: NetworkMapStartStrategy = NetworkMapStartStrategy.Dedicated(startAutomatically = false), startNodesInProcess: Boolean = false, extraCordappPackagesToScan: List = emptyList(), dsl: VerifierExposedDSLInterface.() -> A @@ -86,7 +85,6 @@ fun verifierDriver( extraSystemProperties = extraSystemProperties, driverDirectory = driverDirectory.toAbsolutePath(), useTestClock = useTestClock, - networkMapStartStrategy = networkMapStartStrategy, isDebug = isDebug, startNodesInProcess = startNodesInProcess, extraCordappPackagesToScan = extraCordappPackagesToScan diff --git a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierTests.kt b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierTests.kt index 05bfc06673..dc228fd6f6 100644 --- a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierTests.kt +++ b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierTests.kt @@ -1,5 +1,6 @@ package net.corda.verifier +import net.corda.core.contracts.TransactionVerificationException import net.corda.core.internal.concurrent.map import net.corda.core.internal.concurrent.transpose import net.corda.core.messaging.startFlow @@ -12,12 +13,13 @@ import net.corda.finance.flows.CashIssueFlow import net.corda.finance.flows.CashPaymentFlow import net.corda.node.services.config.VerifierType import net.corda.testing.ALICE +import net.corda.testing.ALICE_NAME import net.corda.testing.DUMMY_NOTARY -import net.corda.testing.* -import net.corda.testing.driver.NetworkMapStartStrategy +import net.corda.testing.DUMMY_NOTARY_SERVICE_NAME import org.junit.Test import java.util.* import java.util.concurrent.atomic.AtomicInteger +import kotlin.test.assertTrue import kotlin.test.assertNotNull class VerifierTests { @@ -50,6 +52,21 @@ class VerifierTests { } } + @Test + fun `single verification fails`() { + verifierDriver(extraCordappPackagesToScan = listOf("net.corda.finance.contracts")) { + val aliceFuture = startVerificationRequestor(ALICE.name) + // Generate transactions as per usual, but then remove attachments making transaction invalid. + val transactions = generateTransactions(1).map { it.copy(attachments = emptyList()) } + val alice = aliceFuture.get() + startVerifier(alice) + alice.waitUntilNumberOfVerifiers(1) + val verificationRejection = transactions.map { alice.verifyTransaction(it) }.transpose().get().single() + assertTrue { verificationRejection is TransactionVerificationException.MissingAttachmentRejection} + assertTrue { verificationRejection!!.message!!.contains("Contract constraints failed, could not find attachment") } + } + } + @Test fun `multiple verifiers work with requestor`() { verifierDriver { @@ -112,10 +129,7 @@ class VerifierTests { @Test fun `single verifier works with a node`() { - verifierDriver( - networkMapStartStrategy = NetworkMapStartStrategy.Dedicated(startAutomatically = true), - extraCordappPackagesToScan = listOf("net.corda.finance.contracts") - ) { + verifierDriver(extraCordappPackagesToScan = listOf("net.corda.finance.contracts")) { val aliceFuture = startNode(providedName = ALICE.name) val notaryFuture = startNotaryNode(DUMMY_NOTARY.name, verifierType = VerifierType.OutOfProcess) val aliceNode = aliceFuture.get() diff --git a/verifier/src/main/kotlin/net/corda/verifier/Verifier.kt b/verifier/src/main/kotlin/net/corda/verifier/Verifier.kt index aaf0c3ac73..22ff4e9f60 100644 --- a/verifier/src/main/kotlin/net/corda/verifier/Verifier.kt +++ b/verifier/src/main/kotlin/net/corda/verifier/Verifier.kt @@ -17,17 +17,16 @@ import net.corda.nodeapi.VerifierApi.VERIFICATION_REQUESTS_QUEUE_NAME import net.corda.nodeapi.config.NodeSSLConfiguration import net.corda.nodeapi.config.getValue import net.corda.nodeapi.internal.addShutdownHook -import net.corda.nodeapi.internal.serialization.AbstractKryoSerializationScheme -import net.corda.nodeapi.internal.serialization.KRYO_P2P_CONTEXT -import net.corda.nodeapi.internal.serialization.KryoHeaderV0_1 -import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl +import net.corda.nodeapi.internal.serialization.* +import net.corda.nodeapi.internal.serialization.amqp.AmqpHeaderV1_0 +import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory import org.apache.activemq.artemis.api.core.client.ActiveMQClient import java.nio.file.Path import java.nio.file.Paths data class VerifierConfiguration( override val baseDirectory: Path, - val config: Config + val config: Config // NB: This property is being used via reflection. ) : NodeSSLConfiguration { val nodeHostAndPort: NetworkHostAndPort by config override val keyStorePassword: String by config @@ -66,7 +65,7 @@ class Verifier { val consumer = session.createConsumer(VERIFICATION_REQUESTS_QUEUE_NAME) val replyProducer = session.createProducer() consumer.setMessageHandler { - val request = VerifierApi.VerificationRequest.fromClientMessage(it) + val (request, context) = VerifierApi.VerificationRequest.fromClientMessage(it) log.debug { "Received verification request with id ${request.verificationId}" } val error = try { request.transaction.verify() @@ -77,7 +76,7 @@ class Verifier { } val reply = session.createMessage(false) val response = VerifierApi.VerificationResponse(request.verificationId, error) - response.writeToClientMessage(reply) + response.writeToClientMessage(reply, context) replyProducer.send(request.responseAddress, reply) it.acknowledge() } @@ -88,13 +87,18 @@ class Verifier { private fun initialiseSerialization() { SerializationDefaults.SERIALIZATION_FACTORY = SerializationFactoryImpl().apply { - registerScheme(KryoVerifierSerializationScheme()) + registerScheme(KryoVerifierSerializationScheme) + registerScheme(AMQPVerifierSerializationScheme) } + /** + * Even though default context is set to Kryo P2P, the encoding will be adjusted depending on the incoming + * request received, see use of [context] in [main] method. + */ SerializationDefaults.P2P_CONTEXT = KRYO_P2P_CONTEXT } } - class KryoVerifierSerializationScheme : AbstractKryoSerializationScheme() { + private object KryoVerifierSerializationScheme : AbstractKryoSerializationScheme() { override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean { return byteSequence == KryoHeaderV0_1 && target == SerializationContext.UseCase.P2P } @@ -102,4 +106,13 @@ class Verifier { override fun rpcClientKryoPool(context: SerializationContext) = throw UnsupportedOperationException() override fun rpcServerKryoPool(context: SerializationContext) = throw UnsupportedOperationException() } + + private object AMQPVerifierSerializationScheme : AbstractAMQPSerializationScheme() { + override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean { + return (byteSequence == AmqpHeaderV1_0 && (target == SerializationContext.UseCase.P2P)) + } + + override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory = throw UnsupportedOperationException() + override fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory = throw UnsupportedOperationException() + } } \ No newline at end of file