diff --git a/.ci/api-current.txt b/.ci/api-current.txt index 617232ca12..12a44713fc 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -14,6 +14,8 @@ public void setMessage(String) public void setOriginalExceptionClassName(String) ## +public @interface net.corda.core.CordaInternal +## public final class net.corda.core.CordaOID extends java.lang.Object @org.jetbrains.annotations.NotNull public static final String CORDA_PLATFORM = "1.3.6.1.4.1.50530.1" public static final net.corda.core.CordaOID INSTANCE @@ -207,7 +209,7 @@ public static final class net.corda.core.context.Trace$InvocationId$Companion ex public static final class net.corda.core.context.Trace$SessionId$Companion extends java.lang.Object @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.context.Trace$SessionId newInstance(String, java.time.Instant) ## -@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement 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 ## @@ -282,14 +284,14 @@ public static final class net.corda.core.contracts.AmountTransfer$Companion exte @org.jetbrains.annotations.NotNull public abstract java.io.InputStream open() @org.jetbrains.annotations.NotNull public abstract jar.JarInputStream openAsJAR() ## -@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement 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) ## @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() ## -@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement 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 ## @@ -341,20 +343,14 @@ public final class net.corda.core.contracts.ComponentGroupEnum extends java.lang ## @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 (net.corda.core.contracts.Attachment, String, Set) - public (net.corda.core.contracts.Attachment, String, Set, String) public void extractFile(String, java.io.OutputStream) - @org.jetbrains.annotations.NotNull public final Set getAdditionalContracts() - @org.jetbrains.annotations.NotNull public final Set getAllContracts() @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.Attachment getAttachment() @org.jetbrains.annotations.NotNull public final String getContract() @org.jetbrains.annotations.NotNull public net.corda.core.crypto.SecureHash getId() @org.jetbrains.annotations.NotNull public List getSigners() public int getSize() - @org.jetbrains.annotations.Nullable public final String getUploader() @org.jetbrains.annotations.NotNull public java.io.InputStream open() @org.jetbrains.annotations.NotNull public jar.JarInputStream openAsJAR() - @org.jetbrains.annotations.NotNull public String toString() ## @net.corda.core.serialization.CordaSerializable public interface net.corda.core.contracts.ContractState @org.jetbrains.annotations.NotNull public abstract List getParticipants() @@ -370,7 +366,7 @@ public final class net.corda.core.contracts.ContractsDSL extends java.lang.Objec @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) ## -@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement 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) @@ -561,9 +557,6 @@ public final class net.corda.core.contracts.TransactionStateKt extends java.lang @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() ## -@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.contracts.TransactionVerificationException$ConflictingAttachmentsRejection extends net.corda.core.contracts.TransactionVerificationException - public (net.corda.core.crypto.SecureHash, String) -## @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) ## @@ -627,10 +620,6 @@ public static final class net.corda.core.contracts.UniqueIdentifier$Companion ex @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) ## -@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.contracts.WhitelistedByZoneAttachmentConstraint 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.WhitelistedByZoneAttachmentConstraint INSTANCE -## @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() @@ -1828,15 +1817,14 @@ public @interface net.corda.core.messaging.RPCReturnsObservables @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.FlowProgressHandle startTrackedFlow(net.corda.core.flows.FlowLogic) ## @net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.NetworkParameters extends java.lang.Object - public (int, List, int, int, java.time.Instant, int, Map) + public (int, List, int, int, java.time.Instant, int) public final int component1() @org.jetbrains.annotations.NotNull public final List component2() public final int component3() public final int component4() @org.jetbrains.annotations.NotNull public final java.time.Instant component5() public final int component6() - @org.jetbrains.annotations.NotNull public final Map component7() - @org.jetbrains.annotations.NotNull public final net.corda.core.node.NetworkParameters copy(int, List, int, int, java.time.Instant, int, Map) + @org.jetbrains.annotations.NotNull public final net.corda.core.node.NetworkParameters copy(int, List, int, int, java.time.Instant, int) public boolean equals(Object) public final int getEpoch() public final int getMaxMessageSize() @@ -1844,7 +1832,6 @@ public @interface net.corda.core.messaging.RPCReturnsObservables public final int getMinimumPlatformVersion() @org.jetbrains.annotations.NotNull public final java.time.Instant getModifiedTime() @org.jetbrains.annotations.NotNull public final List getNotaries() - @org.jetbrains.annotations.NotNull public final Map getWhitelistedContractImplementations() public int hashCode() public String toString() ## @@ -1922,9 +1909,9 @@ public final class net.corda.core.node.StatesToRecord extends java.lang.Enum ## @net.corda.core.DoNotImplement public interface net.corda.core.node.services.AttachmentStorage public abstract boolean hasAttachment(net.corda.core.crypto.SecureHash) - @kotlin.Deprecated @org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.SecureHash importAttachment(java.io.InputStream) + @org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.SecureHash importAttachment(java.io.InputStream) @org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.SecureHash importAttachment(java.io.InputStream, String, String) - @kotlin.Deprecated @org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.SecureHash importOrGetAttachment(java.io.InputStream) + @org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.SecureHash importOrGetAttachment(java.io.InputStream) @org.jetbrains.annotations.Nullable public abstract net.corda.core.contracts.Attachment openAttachment(net.corda.core.crypto.SecureHash) @org.jetbrains.annotations.NotNull public abstract List queryAttachments(net.corda.core.node.services.vault.AttachmentQueryCriteria, net.corda.core.node.services.vault.AttachmentSort) ## @@ -2886,9 +2873,6 @@ public @interface net.corda.core.serialization.CordaSerializationTransformRename public @interface net.corda.core.serialization.DeprecatedConstructorForDeserialization public abstract int version() ## -@net.corda.core.DoNotImplement public interface net.corda.core.serialization.EncodingWhitelist - public abstract boolean acceptEncoding(net.corda.core.serialization.SerializationEncoding) -## @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() @@ -2907,10 +2891,8 @@ public final class net.corda.core.serialization.ObjectWithCompatibleContext exte 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) ## -@net.corda.core.DoNotImplement public interface net.corda.core.serialization.SerializationContext +public interface net.corda.core.serialization.SerializationContext @org.jetbrains.annotations.NotNull public abstract ClassLoader getDeserializationClassLoader() - @org.jetbrains.annotations.Nullable public abstract net.corda.core.serialization.SerializationEncoding getEncoding() - @org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.EncodingWhitelist getEncodingWhitelist() public abstract boolean getObjectReferencesEnabled() @org.jetbrains.annotations.NotNull public abstract net.corda.core.utilities.ByteSequence getPreferredSerializationVersion() @org.jetbrains.annotations.NotNull public abstract Map getProperties() @@ -2918,7 +2900,6 @@ public final class net.corda.core.serialization.SerializationAPIKt extends java. @org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.ClassWhitelist getWhitelist() @org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.SerializationContext withAttachmentsClassLoader(List) @org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.SerializationContext withClassLoader(ClassLoader) - @org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.SerializationContext withEncoding(net.corda.core.serialization.SerializationEncoding) @org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.SerializationContext withPreferredSerializationVersion(net.corda.core.utilities.ByteSequence) @org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.SerializationContext withProperty(Object, Object) @org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.SerializationContext withWhitelisted(Class) @@ -2942,8 +2923,6 @@ public final class net.corda.core.serialization.SerializationDefaults extends ja @org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializationContext getSTORAGE_CONTEXT() public static final net.corda.core.serialization.SerializationDefaults INSTANCE ## -@net.corda.core.DoNotImplement public interface net.corda.core.serialization.SerializationEncoding -## public abstract class net.corda.core.serialization.SerializationFactory extends java.lang.Object public () public final Object asCurrent(kotlin.jvm.functions.Function1) @@ -3063,7 +3042,6 @@ public static final class net.corda.core.transactions.FilteredTransaction$Compan ## @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) - 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, net.corda.core.node.NetworkParameters) @org.jetbrains.annotations.NotNull public final List commandsOfType(Class) @org.jetbrains.annotations.NotNull public final List component1() @org.jetbrains.annotations.NotNull public final List component2() @@ -3074,7 +3052,6 @@ public static final class net.corda.core.transactions.FilteredTransaction$Compan @org.jetbrains.annotations.Nullable public final net.corda.core.contracts.TimeWindow component7() @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.PrivacySalt component8() @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.LedgerTransaction copy(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 net.corda.core.transactions.LedgerTransaction copy(List, List, List, List, net.corda.core.crypto.SecureHash, net.corda.core.identity.Party, net.corda.core.contracts.TimeWindow, net.corda.core.contracts.PrivacySalt, net.corda.core.node.NetworkParameters) public boolean equals(Object) @org.jetbrains.annotations.NotNull public final List filterCommands(Class, function.Predicate) @org.jetbrains.annotations.NotNull public final List filterInRefs(Class, function.Predicate) @@ -4141,7 +4118,7 @@ public class net.corda.testing.node.MockServices extends java.lang.Object implem @org.jetbrains.annotations.NotNull public static final net.corda.node.VersionInfo getMOCK_VERSION_INFO() @org.jetbrains.annotations.NotNull public net.corda.core.node.NodeInfo getMyInfo() @org.jetbrains.annotations.NotNull public net.corda.core.node.services.NetworkMapCache getNetworkMapCache() - @org.jetbrains.annotations.NotNull public final net.corda.core.node.NetworkParameters getNetworkParameters() + @org.jetbrains.annotations.NotNull public net.corda.core.node.NetworkParameters getNetworkParameters() @org.jetbrains.annotations.NotNull public net.corda.core.node.services.TransactionVerifierService getTransactionVerifierService() @org.jetbrains.annotations.NotNull public net.corda.node.services.api.WritableTransactionStorage getValidatedTransactions() @org.jetbrains.annotations.NotNull public net.corda.core.node.services.VaultService getVaultService() @@ -4787,7 +4764,6 @@ public final class net.corda.testing.services.MockAttachmentStorage extends net. public boolean hasAttachment(net.corda.core.crypto.SecureHash) @org.jetbrains.annotations.NotNull public net.corda.core.crypto.SecureHash importAttachment(java.io.InputStream) @org.jetbrains.annotations.NotNull public net.corda.core.crypto.SecureHash importAttachment(java.io.InputStream, String, String) - @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash importContractAttachment(List, String, java.io.InputStream) @org.jetbrains.annotations.NotNull public net.corda.core.crypto.SecureHash importOrGetAttachment(java.io.InputStream) @org.jetbrains.annotations.Nullable public net.corda.core.contracts.Attachment openAttachment(net.corda.core.crypto.SecureHash) @org.jetbrains.annotations.NotNull public List queryAttachments(net.corda.core.node.services.vault.AttachmentQueryCriteria, net.corda.core.node.services.vault.AttachmentSort) diff --git a/build.gradle b/build.gradle index 0531166061..57211a69d0 100644 --- a/build.gradle +++ b/build.gradle @@ -123,6 +123,7 @@ ext { apply plugin: 'project-report' apply plugin: 'com.github.ben-manes.versions' apply plugin: 'net.corda.plugins.publish-utils' +apply plugin: 'net.corda.plugins.cordformation' apply plugin: 'maven-publish' apply plugin: 'com.jfrog.artifactory' @@ -305,6 +306,31 @@ tasks.withType(Test) { reports.html.destination = file("${reporting.baseDir}/${name}") } +task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { + directory "./build/nodes" + node { + name "O=Controller,OU=corda,L=London,C=GB" + notary = [validating : true] + p2pPort 10002 + cordapps = [] + } + node { + name "O=Bank A,OU=corda,L=London,C=GB" + p2pPort 10012 + rpcPort 10013 + webPort 10014 + cordapps = [] + } + node { + name "O=Bank B,OU=corda,L=London,C=GB" + p2pAddress "localhost:10007" + rpcAddress "localhost:10008" + webAddress "localhost:10009" + cordapps = [] + } +} + +// User and key are commented out to prevent accidental pushes of R3 Corda to public repos. DO NOT UNCOMMENT. bintrayConfig { // user = System.getenv('CORDA_BINTRAY_USER') // key = System.getenv('CORDA_BINTRAY_KEY') 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 14889e536d..41a41ed327 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 @@ -10,7 +10,6 @@ import net.corda.core.identity.CordaX500Name import net.corda.core.node.ServiceHub import net.corda.core.transactions.SignedTransaction import net.corda.finance.USD -import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.contracts.DummyContract import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.DUMMY_NOTARY_NAME @@ -102,7 +101,6 @@ class JacksonSupportTest { fun writeTransaction() { val attachmentRef = SecureHash.randomSHA256() doReturn(attachmentRef).whenever(cordappProvider).getContractAttachmentID(DummyContract.PROGRAM_ID) - doReturn(testNetworkParameters()).whenever(services).networkParameters fun makeDummyTx(): SignedTransaction { val wtx = DummyContract.generateInitial(1, DUMMY_NOTARY, MINI_CORP.ref(1)) .toWireTransaction(services) diff --git a/constants.properties b/constants.properties index c134a442b1..49ea962adb 100644 --- a/constants.properties +++ b/constants.properties @@ -1,4 +1,4 @@ -gradlePluginsVersion=4.0.2 +gradlePluginsVersion=4.0.0 kotlinVersion=1.2.20 platformVersion=2 guavaVersion=21.0 diff --git a/core/src/main/kotlin/net/corda/core/contracts/AttachmentConstraint.kt b/core/src/main/kotlin/net/corda/core/contracts/AttachmentConstraint.kt index 9d0b745bf7..2f0135ddd1 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/AttachmentConstraint.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/AttachmentConstraint.kt @@ -1,14 +1,10 @@ package net.corda.core.contracts -import net.corda.core.DoNotImplement -import net.corda.core.contracts.AlwaysAcceptAttachmentConstraint.isSatisfiedBy import net.corda.core.crypto.SecureHash -import net.corda.core.internal.AttachmentWithContext import net.corda.core.serialization.CordaSerializable /** Constrain which contract-code-containing attachment can be used with a [ContractState]. */ @CordaSerializable -@DoNotImplement interface AttachmentConstraint { /** Returns whether the given contract attachment can be used with the [ContractState] associated with this constraint object. */ fun isSatisfiedBy(attachment: Attachment): Boolean @@ -24,20 +20,6 @@ data class HashAttachmentConstraint(val attachmentId: SecureHash) : AttachmentCo override fun isSatisfiedBy(attachment: Attachment) = attachment.id == attachmentId } -/** - * An [AttachmentConstraint] that verifies that the hash of the attachment is in the network parameters whitelist. - * See: [net.corda.core.node.NetworkParameters.whitelistedContractImplementations] - * It allows for centralized control over the cordapps that can be used. - */ -object WhitelistedByZoneAttachmentConstraint : AttachmentConstraint { - override fun isSatisfiedBy(attachment: Attachment): Boolean { - return if (attachment is AttachmentWithContext) { - val whitelist = attachment.whitelistedContractImplementations ?: throw IllegalStateException("Unable to verify WhitelistedByZoneAttachmentConstraint - whitelist not specified") - attachment.id in (whitelist[attachment.stateContract] ?: emptyList()) - } else false - } -} - /** * This [AttachmentConstraint] is a convenience class that will be automatically resolved to a [HashAttachmentConstraint]. * The resolution occurs in [TransactionBuilder.toWireTransaction] and uses the [TransactionState.contract] value diff --git a/core/src/main/kotlin/net/corda/core/contracts/ContractAttachment.kt b/core/src/main/kotlin/net/corda/core/contracts/ContractAttachment.kt index e5ab36de93..161d58ea62 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/ContractAttachment.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/ContractAttachment.kt @@ -6,15 +6,7 @@ import net.corda.core.serialization.CordaSerializable * Wrap an attachment in this if it is to be used as an executable contract attachment * * @property attachment The attachment representing the contract JAR - * @property contract The contract name contained within the JAR. A Contract attachment has to contain at least 1 contract. - * @property additionalContracts Additional contract names contained within the JAR. + * @property contract The contract name contained within the JAR */ @CordaSerializable -class ContractAttachment @JvmOverloads constructor (val attachment: Attachment, val contract: ContractClassName, val additionalContracts: Set = emptySet(), val uploader: String? = null) : Attachment by attachment { - - val allContracts: Set get() = additionalContracts + contract - - override fun toString(): String { - return "ContractAttachment(attachment=${attachment.id}, contracts='${allContracts}', uploader='${uploader}')" - } -} \ No newline at end of file +class ContractAttachment(val attachment: Attachment, val contract: ContractClassName) : Attachment by attachment 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 9f706479e1..26145b38a1 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/TransactionVerificationException.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/TransactionVerificationException.kt @@ -22,9 +22,6 @@ sealed class TransactionVerificationException(val txId: SecureHash, message: Str class MissingAttachmentRejection(txId: SecureHash, val contractClass: String) : TransactionVerificationException(txId, "Contract constraints failed, could not find attachment for: $contractClass", null) - class ConflictingAttachmentsRejection(txId: SecureHash, contractClass: String) - : TransactionVerificationException(txId, "Contract constraints failed for: $contractClass, because multiple attachments providing this contract were attached.", null) - class ContractCreationError(txId: SecureHash, contractClass: String, cause: Throwable) : TransactionVerificationException(txId, "Contract verification failed: ${cause.message}, could not create contract class: $contractClass", cause) 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 24951edec0..f02117bb7c 100644 --- a/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt +++ b/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt @@ -189,7 +189,7 @@ abstract class FlowLogic { open fun receiveAll(receiveType: Class, sessions: List, maySkipCheckpoint: Boolean = false): List> { enforceNoPrimitiveInReceive(listOf(receiveType)) enforceNoDuplicates(sessions) - return castMapValuesToKnownType(receiveAllMap(associateSessionsToReceiveType(receiveType, sessions))) + return castMapValuesToKnownType(receiveAllMap(associateSessionsToReceiveType(receiveType, sessions), maySkipCheckpoint)) } /** diff --git a/core/src/main/kotlin/net/corda/core/internal/AbstractAttachment.kt b/core/src/main/kotlin/net/corda/core/internal/AbstractAttachment.kt index adbc815537..d48b98f290 100644 --- a/core/src/main/kotlin/net/corda/core/internal/AbstractAttachment.kt +++ b/core/src/main/kotlin/net/corda/core/internal/AbstractAttachment.kt @@ -13,13 +13,6 @@ import java.security.CodeSigner import java.security.cert.X509Certificate import java.util.jar.JarInputStream -// Possible attachment uploaders -const val DEPLOYED_CORDAPP_UPLOADER = "app" -const val RPC_UPLOADER = "rpc" -const val TEST_UPLOADER = "test" -const val P2P_UPLOADER = "p2p" -const val UNKNOWN_UPLOADER = "unknown" - abstract class AbstractAttachment(dataLoader: () -> ByteArray) : Attachment { companion object { fun SerializeAsTokenContext.attachmentDataLoader(id: SecureHash): () -> ByteArray { diff --git a/core/src/main/kotlin/net/corda/core/internal/AttachmentWithContext.kt b/core/src/main/kotlin/net/corda/core/internal/AttachmentWithContext.kt deleted file mode 100644 index 677ca29a8d..0000000000 --- a/core/src/main/kotlin/net/corda/core/internal/AttachmentWithContext.kt +++ /dev/null @@ -1,22 +0,0 @@ -package net.corda.core.internal - -import net.corda.core.contracts.Attachment -import net.corda.core.contracts.ContractAttachment -import net.corda.core.contracts.ContractClassName -import net.corda.core.node.services.AttachmentId - -/** - * Used only for passing to the Attachment constraint verification. - */ -class AttachmentWithContext( - val contractAttachment: ContractAttachment, - val stateContract: ContractClassName, - /** Required for verifying [WhitelistedByZoneAttachmentConstraint] */ - val whitelistedContractImplementations: Map>? -) : Attachment by contractAttachment { - init { - require(stateContract in contractAttachment.allContracts) { - "This AttachmentWithContext was not initialised properly" - } - } -} \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/internal/FetchDataFlow.kt b/core/src/main/kotlin/net/corda/core/internal/FetchDataFlow.kt index 294451cbd7..1db993649a 100644 --- a/core/src/main/kotlin/net/corda/core/internal/FetchDataFlow.kt +++ b/core/src/main/kotlin/net/corda/core/internal/FetchDataFlow.kt @@ -148,7 +148,7 @@ class FetchAttachmentsFlow(requests: Set, override fun maybeWriteToDisk(downloaded: List) { for (attachment in downloaded) { - serviceHub.attachments.importAttachment(attachment.open(), "$P2P_UPLOADER:${otherSideSession.counterparty.name}", null) + serviceHub.attachments.importAttachment(attachment.open()) } } diff --git a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt index ac4dabae8d..8a17ec5966 100644 --- a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt @@ -147,8 +147,6 @@ inline fun Path.readLines(charset: Charset = UTF_8, block: (Stream) fun Path.readAllLines(charset: Charset = UTF_8): List = Files.readAllLines(this, charset) fun Path.writeLines(lines: Iterable, charset: Charset = UTF_8, vararg options: OpenOption): Path = Files.write(this, lines, charset, *options) -inline fun Path.readObject(): T = readAll().deserialize() - fun InputStream.copyTo(target: Path, vararg options: CopyOption): Long = Files.copy(this, target, *options) fun String.abbreviate(maxWidth: Int): String = if (length <= maxWidth) this else take(maxWidth - 1) + "…" @@ -306,8 +304,8 @@ fun uncheckedCast(obj: T) = obj as U fun Iterable>.toMultiMap(): Map> = this.groupBy({ it.first }) { it.second } /** Provide access to internal method for AttachmentClassLoaderTests */ -fun TransactionBuilder.toWireTransaction(services: ServicesForResolution, serializationContext: SerializationContext): WireTransaction { - return toWireTransactionWithContext(services, serializationContext) +fun TransactionBuilder.toWireTransaction(cordappProvider: CordappProvider, serializationContext: SerializationContext): WireTransaction { + return toWireTransactionWithContext(cordappProvider, serializationContext) } /** Provide access to internal method for AttachmentClassLoaderTests */ @@ -379,14 +377,15 @@ inline fun SerializedBytes.sign(signer: (SerializedBytes) -> Dig return SignedData(this, signer(this)) } -fun SerializedBytes.sign(keyPair: KeyPair): SignedData = SignedData(this, keyPair.sign(this.bytes)) +inline fun SerializedBytes.sign(keyPair: KeyPair): SignedData { + return SignedData(this, keyPair.sign(this.bytes)) +} -fun ByteBuffer.copyBytes(): ByteArray = ByteArray(remaining()).also { get(it) } +fun ByteBuffer.copyBytes() = ByteArray(remaining()).also { get(it) } fun createCordappContext(cordapp: Cordapp, attachmentId: SecureHash?, classLoader: ClassLoader, config: CordappConfig): CordappContext { return CordappContext(cordapp, attachmentId, classLoader, config) } - /** Verifies that the correct notarisation request was signed by the counterparty. */ fun NotaryFlow.Service.validateRequest(request: NotarisationRequest, signature: NotarisationRequestSignature) { val requestingParty = otherSideSession.counterparty @@ -402,4 +401,4 @@ fun NotarisationRequest.generateSignature(serviceHub: ServiceHub): NotarisationR keyManagementService.sign(serializedRequest, myLegalIdentity) } return NotarisationRequestSignature(signature, serviceHub.myInfo.platformVersion) -} +} \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt b/core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt index 41091074a0..cca7c1433b 100644 --- a/core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt +++ b/core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt @@ -1,7 +1,6 @@ package net.corda.core.node import net.corda.core.identity.Party -import net.corda.core.node.services.AttachmentId import net.corda.core.serialization.CordaSerializable import java.time.Instant @@ -10,13 +9,11 @@ import java.time.Instant * correctly interoperate with each other. * @property minimumPlatformVersion Minimum version of Corda platform that is required for nodes in the network. * @property notaries List of well known and trusted notary identities with information on validation type. - * @property maxMessageSize This is currently ignored. However, it will be wired up in a future release. + * @property maxMessageSize Maximum P2P message sent over the wire in bytes. * @property maxTransactionSize Maximum permitted transaction size in bytes. * @property modifiedTime Last modification time of network parameters set. * @property epoch Version number of the network parameters. Starting from 1, this will always increment on each new set * of parameters. - * @property whitelistedContractImplementations List of whitelisted jars containing contract code for each contract class. - * This will be used by [net.corda.core.contracts.WhitelistedByZoneAttachmentConstraint]. Read more about contract constraints here: */ // TODO Add eventHorizon - how many days a node can be offline before being automatically ejected from the network. // It needs separate design. @@ -27,8 +24,7 @@ data class NetworkParameters( val maxMessageSize: Int, val maxTransactionSize: Int, val modifiedTime: Instant, - val epoch: Int, - val whitelistedContractImplementations: Map> + val epoch: Int ) { init { require(minimumPlatformVersion > 0) { "minimumPlatformVersion must be at least 1" } 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 81e657358e..9332587f7a 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/AttachmentStorage.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/AttachmentStorage.kt @@ -33,7 +33,6 @@ interface AttachmentStorage { * @throws IllegalArgumentException if the given byte stream is empty or a [java.util.jar.JarInputStream]. * @throws IOException if something went wrong. */ - @Deprecated("More attachment information is required", replaceWith = ReplaceWith("importAttachment(jar, uploader, filename)")) @Throws(FileAlreadyExistsException::class, IOException::class) fun importAttachment(jar: InputStream): AttachmentId @@ -44,14 +43,13 @@ interface AttachmentStorage { * @param filename Name of the file */ @Throws(FileAlreadyExistsException::class, IOException::class) - fun importAttachment(jar: InputStream, uploader: String, filename: String?): AttachmentId + fun importAttachment(jar: InputStream, uploader: String, filename: String): AttachmentId /** * Inserts or returns Attachment Id of attachment. Does not throw an exception if already uploaded. * @param jar [InputStream] of Jar file * @return [AttachmentId] of uploaded attachment */ - @Deprecated("More attachment information is required", replaceWith = ReplaceWith("importAttachment(jar, uploader, filename)")) fun importOrGetAttachment(jar: InputStream): AttachmentId /** 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 b8e90fc8c2..82ee0d402a 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt @@ -1,6 +1,5 @@ package net.corda.core.serialization -import net.corda.core.DoNotImplement import net.corda.core.crypto.SecureHash import net.corda.core.crypto.sha256 import net.corda.core.serialization.internal.effectiveSerializationEnv @@ -100,22 +99,14 @@ abstract class SerializationFactory { } } typealias SerializationMagic = ByteSequence -@DoNotImplement -interface SerializationEncoding - /** * Parameters to serialization and deserialization. */ -@DoNotImplement interface SerializationContext { /** * When serializing, use the format this header sequence represents. */ val preferredSerializationVersion: SerializationMagic - /** - * If non-null, apply this encoding (typically compression) when serializing. - */ - val encoding: SerializationEncoding? /** * The class loader to use for deserialization. */ @@ -124,10 +115,6 @@ interface SerializationContext { * A whitelist that contains (mostly for security purposes) which classes can be serialized and deserialized. */ val whitelist: ClassWhitelist - /** - * A whitelist that determines (mostly for security purposes) whether a particular encoding may be used when deserializing. - */ - val encodingWhitelist: EncodingWhitelist /** * A map of any addition properties specific to the particular use case. */ @@ -174,11 +161,6 @@ interface SerializationContext { */ fun withPreferredSerializationVersion(magic: SerializationMagic): SerializationContext - /** - * A shallow copy of this context but with the given (possibly null) encoding. - */ - fun withEncoding(encoding: SerializationEncoding?): SerializationContext - /** * The use case that we are serializing for, since it influences the implementations chosen. */ @@ -250,8 +232,3 @@ class SerializedBytes(bytes: ByteArray) : OpaqueBytes(bytes) { interface ClassWhitelist { fun hasListed(type: Class<*>): Boolean } - -@DoNotImplement -interface EncodingWhitelist { - fun acceptEncoding(encoding: SerializationEncoding): Boolean -} diff --git a/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt index dfda034eec..1881431114 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt @@ -3,11 +3,9 @@ package net.corda.core.transactions import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash import net.corda.core.identity.Party -import net.corda.core.internal.AttachmentWithContext import net.corda.core.internal.UpgradeCommand import net.corda.core.internal.castIfPossible import net.corda.core.internal.uncheckedCast -import net.corda.core.node.NetworkParameters import net.corda.core.serialization.CordaSerializable import net.corda.core.utilities.Try import java.security.PublicKey @@ -29,7 +27,7 @@ import java.util.function.Predicate // currently sends this across to out-of-process verifiers. We'll need to change that first. // DOCSTART 1 @CordaSerializable -data class LedgerTransaction @JvmOverloads constructor( +data class LedgerTransaction( /** The resolved input states which will be consumed/invalidated by the execution of this transaction. */ override val inputs: List>, override val outputs: List>, @@ -41,8 +39,7 @@ data class LedgerTransaction @JvmOverloads constructor( override val id: SecureHash, override val notary: Party?, val timeWindow: TimeWindow?, - val privacySalt: PrivacySalt, - private val networkParameters: NetworkParameters? = null + val privacySalt: PrivacySalt ) : FullTransaction() { //DOCEND 1 init { @@ -90,29 +87,17 @@ data class LedgerTransaction @JvmOverloads constructor( /** * Verify that all contract constraints are valid for each state before running any contract code * - * In case the transaction was created on this node then the attachments will contain the hash of the current cordapp jars. - * In case this verifies an older transaction or one originated on a different node, then this verifies that the attachments - * are valid. - * * @throws TransactionVerificationException if the constraints fail to verify */ private fun verifyConstraints() { val contractAttachments = attachments.filterIsInstance() (inputs.map { it.state } + outputs).forEach { state -> - val stateAttachments = contractAttachments.filter { state.contract in it.allContracts } - if (stateAttachments.isEmpty()) throw TransactionVerificationException.MissingAttachmentRejection(id, state.contract) + // Ordering of attachments matters - if two attachments contain the same named contract then the second + // will be shadowed by the first. + val contractAttachment = contractAttachments.find { it.contract == state.contract } + ?: throw TransactionVerificationException.MissingAttachmentRejection(id, state.contract) - val uniqueAttachmentsForStateContract = stateAttachments.distinctBy { it.id } - - // In case multiple attachments have been added for the same contract, fail because this transaction will not be able to be verified - // because it will break the no-overlap rule that we have implemented in our Classloaders - if (uniqueAttachmentsForStateContract.size > 1) { - throw TransactionVerificationException.ConflictingAttachmentsRejection(id, state.contract) - } - - val contractAttachment = uniqueAttachmentsForStateContract.first() - val constraintAttachment = AttachmentWithContext(contractAttachment, state.contract, networkParameters?.whitelistedContractImplementations) - if (!state.constraint.isSatisfiedBy(constraintAttachment)) { + if (!state.constraint.isSatisfiedBy(contractAttachment)) { throw TransactionVerificationException.ContractConstraintRejection(id, state.contract) } } @@ -418,14 +403,5 @@ data class LedgerTransaction @JvmOverloads constructor( * @throws IllegalArgumentException if no item matches the id. */ fun getAttachment(id: SecureHash): Attachment = attachments.first { it.id == id } - - fun copy(inputs: List>, - outputs: List>, - commands: List>, - attachments: List, - id: SecureHash, - notary: Party?, - timeWindow: TimeWindow?, - privacySalt: PrivacySalt - ) = copy(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, null) } + diff --git a/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt b/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt index 3204e15580..231f563314 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt @@ -8,10 +8,8 @@ import net.corda.core.crypto.SignableData import net.corda.core.crypto.SignatureMetadata import net.corda.core.identity.Party import net.corda.core.internal.FlowStateMachine -import net.corda.core.node.NetworkParameters import net.corda.core.node.ServiceHub import net.corda.core.node.ServicesForResolution -import net.corda.core.node.services.AttachmentId import net.corda.core.node.services.KeyManagementService import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationFactory @@ -19,7 +17,6 @@ import java.security.PublicKey import java.time.Duration import java.time.Instant import java.util.* -import kotlin.collections.ArrayList /** * A TransactionBuilder is a transaction class that's mutable (unlike the others which are all immutable). It is @@ -45,24 +42,18 @@ open class TransactionBuilder( ) { constructor(notary: Party) : this(notary, (Strand.currentStrand() as? FlowStateMachine<*>)?.id?.uuid ?: UUID.randomUUID()) - private val inputsWithTransactionState = arrayListOf>() - /** * Creates a copy of the builder. */ - fun copy(): TransactionBuilder { - val t = TransactionBuilder( - notary = notary, - inputs = ArrayList(inputs), - attachments = ArrayList(attachments), - outputs = ArrayList(outputs), - commands = ArrayList(commands), - window = window, - privacySalt = privacySalt - ) - t.inputsWithTransactionState.addAll(this.inputsWithTransactionState) - return t - } + fun copy() = TransactionBuilder( + notary = notary, + inputs = ArrayList(inputs), + attachments = ArrayList(attachments), + outputs = ArrayList(outputs), + commands = ArrayList(commands), + window = window, + privacySalt = privacySalt + ) // DOCSTART 1 /** A more convenient way to add items to this transaction that calls the add* methods for you based on type */ @@ -92,52 +83,31 @@ open class TransactionBuilder( * @returns A new [WireTransaction] that will be unaffected by further changes to this [TransactionBuilder]. */ @Throws(MissingContractAttachments::class) - fun toWireTransaction(services: ServicesForResolution): WireTransaction = toWireTransactionWithContext(services) + fun toWireTransaction(services: ServicesForResolution): WireTransaction = toWireTransactionWithContext(services.cordappProvider) - internal fun toWireTransactionWithContext(services: ServicesForResolution, serializationContext: SerializationContext? = null): WireTransaction { - - // Resolves the AutomaticHashConstraints to HashAttachmentConstraints or WhitelistedByZoneAttachmentConstraint based on a global parameter. - // The AutomaticHashConstraint allows for less boiler plate when constructing transactions since for the typical case the named contract + internal fun toWireTransactionWithContext(cordappProvider: CordappProvider, serializationContext: SerializationContext? = null): WireTransaction { + // Resolves the AutomaticHashConstraints to HashAttachmentConstraints for convenience. The AutomaticHashConstraint + // allows for less boiler plate when constructing transactions since for the typical case the named contract // will be available when building the transaction. In exceptional cases the TransactionStates must be created // with an explicit [AttachmentConstraint] val resolvedOutputs = outputs.map { state -> - when { - state.constraint !is AutomaticHashConstraint -> state - useWhitelistedByZoneAttachmentConstraint(state.contract, services.networkParameters) -> state.copy(constraint = WhitelistedByZoneAttachmentConstraint) - else -> services.cordappProvider.getContractAttachmentID(state.contract)?.let { + if (state.constraint is AutomaticHashConstraint) { + cordappProvider.getContractAttachmentID(state.contract)?.let { state.copy(constraint = HashAttachmentConstraint(it)) } ?: throw MissingContractAttachments(listOf(state)) + } else { + state } } - return SerializationFactory.defaultFactory.withCurrentContext(serializationContext) { - WireTransaction(WireTransaction.createComponentGroups(inputStates(), resolvedOutputs, commands, attachments + makeContractAttachments(services.cordappProvider), notary, window), privacySalt) + WireTransaction(WireTransaction.createComponentGroups(inputs, resolvedOutputs, commands, attachments, notary, window), privacySalt) } } - private fun useWhitelistedByZoneAttachmentConstraint(contractClassName: ContractClassName, networkParameters: NetworkParameters): Boolean { - return contractClassName in networkParameters.whitelistedContractImplementations.keys - } - - /** - * The attachments added to the current transaction contain only the hashes of the current cordapps. - * NOT the hashes of the cordapps that were used when the input states were created ( in case they changed in the meantime) - * TODO - review this logic - */ - private fun makeContractAttachments(cordappProvider: CordappProvider): List { - return (inputsWithTransactionState + outputs).map { state -> - cordappProvider.getContractAttachmentID(state.contract) - ?: throw MissingContractAttachments(listOf(state)) - }.distinct() - } - @Throws(AttachmentResolutionException::class, TransactionResolutionException::class) fun toLedgerTransaction(services: ServiceHub) = toWireTransaction(services).toLedgerTransaction(services) - internal fun toLedgerTransactionWithContext(services: ServicesForResolution, serializationContext: SerializationContext): LedgerTransaction { - return toWireTransactionWithContext(services, serializationContext).toLedgerTransaction(services) - } - + internal fun toLedgerTransactionWithContext(services: ServicesForResolution, serializationContext: SerializationContext) = toWireTransactionWithContext(services.cordappProvider, serializationContext).toLedgerTransaction(services) @Throws(AttachmentResolutionException::class, TransactionResolutionException::class, TransactionVerificationException::class) fun verify(services: ServiceHub) { toLedgerTransaction(services).verify() @@ -147,7 +117,6 @@ open class TransactionBuilder( val notary = stateAndRef.state.notary require(notary == this.notary) { "Input state requires notary \"$notary\" which does not match the transaction notary \"${this.notary}\"." } inputs.add(stateAndRef.ref) - inputsWithTransactionState.add(stateAndRef.state) return this } 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 46deccf2c6..dbf27539df 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt @@ -5,7 +5,6 @@ import net.corda.core.contracts.ComponentGroupEnum.* import net.corda.core.crypto.* import net.corda.core.identity.Party import net.corda.core.internal.Emoji -import net.corda.core.node.NetworkParameters import net.corda.core.node.ServicesForResolution import net.corda.core.node.services.AttachmentId import net.corda.core.serialization.CordaSerializable @@ -89,7 +88,8 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr resolveIdentity = { services.identityService.partyFromKey(it) }, resolveAttachment = { services.attachments.openAttachment(it) }, resolveStateRef = { services.loadState(it) }, - networkParameters = services.networkParameters + resolveContractAttachment = { services.cordappProvider.getContractAttachmentID(it.contract) }, + maxTransactionSize = services.networkParameters.maxTransactionSize ) } @@ -108,16 +108,17 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr resolveStateRef: (StateRef) -> TransactionState<*>?, resolveContractAttachment: (TransactionState) -> AttachmentId? ): LedgerTransaction { - return toLedgerTransactionInternal(resolveIdentity, resolveAttachment, resolveStateRef, null) + return toLedgerTransactionInternal(resolveIdentity, resolveAttachment, resolveStateRef, resolveContractAttachment, 10485760) } private fun toLedgerTransactionInternal( resolveIdentity: (PublicKey) -> Party?, resolveAttachment: (SecureHash) -> Attachment?, resolveStateRef: (StateRef) -> TransactionState<*>?, - networkParameters: NetworkParameters? + resolveContractAttachment: (TransactionState) -> AttachmentId?, + maxTransactionSize: Int ): LedgerTransaction { - // Look up public keys to authenticated identities. + // Look up public keys to authenticated identities. This is just a stub placeholder and will all change in future. val authenticatedArgs = commands.map { val parties = it.signers.mapNotNull { pk -> resolveIdentity(pk) } CommandWithParties(it.signers, parties, it.value) @@ -125,9 +126,12 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr val resolvedInputs = inputs.map { ref -> resolveStateRef(ref)?.let { StateAndRef(it, ref) } ?: throw TransactionResolutionException(ref.txhash) } - val attachments = attachments.map { resolveAttachment(it) ?: throw AttachmentResolutionException(it) } - val ltx = LedgerTransaction(resolvedInputs, outputs, authenticatedArgs, attachments, id, notary, timeWindow, privacySalt, networkParameters) - checkTransactionSize(ltx, networkParameters?.maxTransactionSize ?: 10485760) + // Open attachments specified in this transaction. If we haven't downloaded them, we fail. + val contractAttachments = findAttachmentContracts(resolvedInputs, resolveContractAttachment, resolveAttachment) + // Order of attachments is important since contracts may refer to indexes so only append automatic attachments + val attachments = (attachments.map { resolveAttachment(it) ?: throw AttachmentResolutionException(it) } + contractAttachments).distinct() + val ltx = LedgerTransaction(resolvedInputs, outputs, authenticatedArgs, attachments, id, notary, timeWindow, privacySalt) + checkTransactionSize(ltx, maxTransactionSize) return ltx } @@ -139,9 +143,8 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr remainingTransactionSize -= size } - // Check attachments size first as they are most likely to go over the limit. With ContractAttachment instances - // it's likely that the same underlying Attachment CorDapp will occur more than once so we dedup on the attachment id. - ltx.attachments.distinctBy { it.id }.forEach { minus(it.size) } + // Check attachment size first as they are most likely to go over the limit. + ltx.attachments.associateBy(Attachment::id).values.forEach { minus(it.size) } minus(ltx.inputs.serialize().size) minus(ltx.commands.serialize().size) minus(ltx.outputs.serialize().size) @@ -262,6 +265,19 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr return buf.toString() } + private fun findAttachmentContracts(resolvedInputs: List>, + resolveContractAttachment: (TransactionState) -> AttachmentId?, + resolveAttachment: (SecureHash) -> Attachment? + ): List { + val contractAttachments = (outputs + resolvedInputs.map { it.state }).map { Pair(it, resolveContractAttachment(it)) } + val missingAttachments = contractAttachments.filter { it.second == null } + return if (missingAttachments.isEmpty()) { + contractAttachments.map { ContractAttachment(resolveAttachment(it.second!!) ?: throw AttachmentResolutionException(it.second!!), it.first.contract) } + } else { + throw MissingContractAttachments(missingAttachments.map { it.first }) + } + } + override fun equals(other: Any?): Boolean { if (other is WireTransaction) { return (this.id == other.id) diff --git a/core/src/test/kotlin/net/corda/core/contracts/DummyContractV2Tests.kt b/core/src/test/kotlin/net/corda/core/contracts/DummyContractV2Tests.kt index be99697d27..be483b9e0d 100644 --- a/core/src/test/kotlin/net/corda/core/contracts/DummyContractV2Tests.kt +++ b/core/src/test/kotlin/net/corda/core/contracts/DummyContractV2Tests.kt @@ -1,11 +1,9 @@ package net.corda.core.contracts -import com.nhaarman.mockito_kotlin.any import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever import net.corda.core.cordapp.CordappProvider import net.corda.core.crypto.SecureHash -import net.corda.core.crypto.SecureHash.Companion.allOnesHash import net.corda.core.internal.UpgradeCommand import net.corda.core.node.ServicesForResolution import net.corda.testing.contracts.DummyContract @@ -36,9 +34,7 @@ class DummyContractV2Tests { @Test fun `upgrade from v1`() { val services = rigorousMock().also { - doReturn(rigorousMock().also { - doReturn(allOnesHash).whenever(it).getContractAttachmentID(any()) - }).whenever(it).cordappProvider + doReturn(rigorousMock()).whenever(it).cordappProvider } val contractUpgrade = DummyContractV2() val v1State = TransactionState(DummyContract.SingleOwnerState(0, ALICE), DummyContract.PROGRAM_ID, DUMMY_NOTARY, constraint = AlwaysAcceptAttachmentConstraint) diff --git a/core/src/test/kotlin/net/corda/core/flows/FlowTestsUtils.kt b/core/src/test/kotlin/net/corda/core/flows/FlowTestsUtils.kt index 1c5934aadb..99322028af 100644 --- a/core/src/test/kotlin/net/corda/core/flows/FlowTestsUtils.kt +++ b/core/src/test/kotlin/net/corda/core/flows/FlowTestsUtils.kt @@ -5,6 +5,7 @@ import net.corda.core.utilities.UntrustworthyData import net.corda.core.utilities.unwrap import net.corda.node.internal.InitiatedFlowFactory import net.corda.node.internal.StartedNode +import net.corda.testing.node.StartedMockNode import net.corda.testing.node.internal.InternalMockNetwork import kotlin.reflect.KClass diff --git a/core/src/test/kotlin/net/corda/core/transactions/LedgerTransactionQueryTests.kt b/core/src/test/kotlin/net/corda/core/transactions/LedgerTransactionQueryTests.kt index f91eb9c344..9482764d99 100644 --- a/core/src/test/kotlin/net/corda/core/transactions/LedgerTransactionQueryTests.kt +++ b/core/src/test/kotlin/net/corda/core/transactions/LedgerTransactionQueryTests.kt @@ -10,6 +10,7 @@ import net.corda.core.identity.Party import net.corda.node.services.api.IdentityServiceInternal import net.corda.testing.contracts.DummyContract import net.corda.testing.core.* +import net.corda.testing.internal.MockCordappProvider import net.corda.testing.internal.rigorousMock import net.corda.testing.node.MockServices import org.junit.Before @@ -29,7 +30,7 @@ class LedgerTransactionQueryTests { @JvmField val testSerialization = SerializationEnvironmentRule() private val keyPair = generateKeyPair() - private val services = MockServices(listOf("net.corda.testing.contracts"), CordaX500Name("MegaCorp", "London", "GB"), + private val services = MockServices(emptyList(), CordaX500Name("MegaCorp", "London", "GB"), rigorousMock().also { doReturn(null).whenever(it).partyFromKey(keyPair.public) }, keyPair) diff --git a/core/src/test/kotlin/net/corda/core/transactions/TransactionEncumbranceTests.kt b/core/src/test/kotlin/net/corda/core/transactions/TransactionEncumbranceTests.kt index efdfd28256..6b7e98617f 100644 --- a/core/src/test/kotlin/net/corda/core/transactions/TransactionEncumbranceTests.kt +++ b/core/src/test/kotlin/net/corda/core/transactions/TransactionEncumbranceTests.kt @@ -65,7 +65,7 @@ class TransactionEncumbranceTests { } } - private val ledgerServices = MockServices(listOf("net.corda.core.transactions", "net.corda.finance.contracts.asset"), MEGA_CORP.name, + private val ledgerServices = MockServices(emptyList(), MEGA_CORP.name, rigorousMock().also { doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY) }) diff --git a/core/src/test/kotlin/net/corda/core/transactions/TransactionTests.kt b/core/src/test/kotlin/net/corda/core/transactions/TransactionTests.kt index cbda60638f..cd234f1138 100644 --- a/core/src/test/kotlin/net/corda/core/transactions/TransactionTests.kt +++ b/core/src/test/kotlin/net/corda/core/transactions/TransactionTests.kt @@ -1,7 +1,5 @@ package net.corda.core.transactions -import com.nhaarman.mockito_kotlin.doReturn -import com.nhaarman.mockito_kotlin.whenever import net.corda.core.contracts.* import net.corda.core.crypto.* import net.corda.core.crypto.CompositeKey @@ -114,9 +112,7 @@ class TransactionTests { val inputs = emptyList>() val outputs = listOf(baseOutState, baseOutState.copy(notary = ALICE), baseOutState.copy(notary = BOB)) val commands = emptyList>() - val attachments = listOf(ContractAttachment(rigorousMock().also { - doReturn(SecureHash.zeroHash).whenever(it).id - }, DummyContract.PROGRAM_ID)) + val attachments = listOf(ContractAttachment(rigorousMock(), DummyContract.PROGRAM_ID)) val id = SecureHash.randomSHA256() val timeWindow: TimeWindow? = null val privacySalt: PrivacySalt = PrivacySalt() diff --git a/docs/source/api-flows.rst b/docs/source/api-flows.rst index 6d77139704..fc5fef703d 100644 --- a/docs/source/api-flows.rst +++ b/docs/source/api-flows.rst @@ -264,13 +264,6 @@ In order to create a communication session between your initiator flow and the r * ``sendAndReceive(receiveType: Class, payload: Any): R`` * Sends the ``payload`` object and receives an object of type ``receiveType`` back -In addition ``FlowLogic`` provides functions that batch receives: -* ``receiveAllMap(sessions: Map>): Map>`` - * Receives from all ``FlowSession``s specified in the passed in map. The received types may differ. -* ``receiveAll(receiveType: Class, sessions: List): List>`` - * Receives from all ``FlowSession``s specified in the passed in list. The received types must be the same. - -The batched functions are implemented more efficiently by the flow framework. InitiateFlow ~~~~~~~~~~~~ diff --git a/docs/source/api-testing.rst b/docs/source/api-testing.rst deleted file mode 100644 index 4281d32347..0000000000 --- a/docs/source/api-testing.rst +++ /dev/null @@ -1,467 +0,0 @@ -.. highlight:: kotlin -.. raw:: html - - - - -API: Testing -============ - -.. contents:: - -Flow testing ------------- - -MockNetwork -^^^^^^^^^^^ - -Flow testing can be fully automated using a ``MockNetwork`` composed of ``StartedMockNode`` nodes. Each -``StartedMockNode`` behaves like a regular Corda node, but its services are either in-memory or mocked out. - -A ``MockNetwork`` is created as follows: - -.. container:: codeset - - .. sourcecode:: kotlin - - class FlowTests { - private lateinit var mockNet: MockNetwork - - @Before - fun setup() { - network = MockNetwork(listOf("my.cordapp.package", "my.other.cordapp.package")) - } - } - - - .. sourcecode:: java - - public class IOUFlowTests { - private MockNetwork network; - - @Before - public void setup() { - network = new MockNetwork(ImmutableList.of("my.cordapp.package", "my.other.cordapp.package")); - } - } - -The ``MockNetwork`` requires at a minimum a list of packages. Each package is packaged into a CorDapp JAR and installed -as a CorDapp on each ``StartedMockNode``. - -Configuring the ``MockNetwork`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The ``MockNetwork`` is configured automatically. You can tweak its configuration using a ``MockNetworkParameters`` -object, or by using named paramters in Kotlin: - -.. container:: codeset - - .. sourcecode:: kotlin - - val network = MockNetwork( - cordappPackages = listOf("my.cordapp.package", "my.other.cordapp.package"), - // If true then each node will be run in its own thread. This can result in race conditions in your - // code if not carefully written, but is more realistic and may help if you have flows in your app that - // do long blocking operations. - threadPerNode = false, - // The notaries to use on the mock network. By default you get one mock notary and that is usually - // sufficient. - notarySpecs = listOf(MockNetworkNotarySpec(DUMMY_NOTARY_NAME)), - // If true then messages will not be routed from sender to receiver until you use the - // [MockNetwork.runNetwork] method. This is useful for writing single-threaded unit test code that can - // examine the state of the mock network before and after a message is sent, without races and without - // the receiving node immediately sending a response. - networkSendManuallyPumped = false, - // How traffic is allocated in the case where multiple nodes share a single identity, which happens for - // notaries in a cluster. You don't normally ever need to change this: it is mostly useful for testing - // notary implementations. - servicePeerAllocationStrategy = InMemoryMessagingNetwork.ServicePeerAllocationStrategy.Random()) - - val network2 = MockNetwork(listOf("my.cordapp.package", "my.other.cordapp.package"), MockNetworkParameters( - // If true then each node will be run in its own thread. This can result in race conditions in your - // code if not carefully written, but is more realistic and may help if you have flows in your app that - // do long blocking operations. - threadPerNode = false, - // The notaries to use on the mock network. By default you get one mock notary and that is usually - // sufficient. - notarySpecs = listOf(MockNetworkNotarySpec(DUMMY_NOTARY_NAME)), - // If true then messages will not be routed from sender to receiver until you use the - // [MockNetwork.runNetwork] method. This is useful for writing single-threaded unit test code that can - // examine the state of the mock network before and after a message is sent, without races and without - // the receiving node immediately sending a response. - networkSendManuallyPumped = false, - // How traffic is allocated in the case where multiple nodes share a single identity, which happens for - // notaries in a cluster. You don't normally ever need to change this: it is mostly useful for testing - // notary implementations. - servicePeerAllocationStrategy = InMemoryMessagingNetwork.ServicePeerAllocationStrategy.Random()) - ) - - .. sourcecode:: java - - MockNetwork network = MockNetwork(ImmutableList.of("my.cordapp.package", "my.other.cordapp.package"), - new MockNetworkParameters() - // If true then each node will be run in its own thread. This can result in race conditions in - // your code if not carefully written, but is more realistic and may help if you have flows in - // your app that do long blocking operations. - .setThreadPerNode(false) - // The notaries to use on the mock network. By default you get one mock notary and that is - // usually sufficient. - .setNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(DUMMY_NOTARY_NAME))) - // If true then messages will not be routed from sender to receiver until you use the - // [MockNetwork.runNetwork] method. This is useful for writing single-threaded unit test code - // that can examine the state of the mock network before and after a message is sent, without - // races and without the receiving node immediately sending a response. - .setNetworkSendManuallyPumped(false) - // How traffic is allocated in the case where multiple nodes share a single identity, which - // happens for notaries in a cluster. You don't normally ever need to change this: it is mostly - // useful for testing notary implementations. - .setServicePeerAllocationStrategy(new InMemoryMessagingNetwork.ServicePeerAllocationStrategy.Random())); - -Adding nodes to the network -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Nodes are created on the ``MockNetwork`` using: - -.. container:: codeset - - .. sourcecode:: kotlin - - class FlowTests { - private lateinit var mockNet: MockNetwork - lateinit var nodeA: StartedMockNode - lateinit var nodeB: StartedMockNode - - @Before - fun setup() { - network = MockNetwork(listOf("my.cordapp.package", "my.other.cordapp.package")) - nodeA = network.createPartyNode() - // We can optionally give the node a name. - nodeB = network.createPartyNode(CordaX500Name("Bank B", "London", "GB")) - } - } - - - .. sourcecode:: java - - public class IOUFlowTests { - private MockNetwork network; - private StartedMockNode a; - private StartedMockNode b; - - @Before - public void setup() { - network = new MockNetwork(ImmutableList.of("my.cordapp.package", "my.other.cordapp.package")); - nodeA = network.createPartyNode(null); - // We can optionally give the node a name. - nodeB = network.createPartyNode(new CordaX500Name("Bank B", "London", "GB")); - } - } - -Registering a node's initiated flows -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Regular Corda nodes automatically register any response flows defined in their installed CorDapps. When using a -``MockNetwork``, each ``StartedMockNode`` must manually register any responder flows it wishes to use. - -Responder flows are registered as follows: - -.. container:: codeset - - .. sourcecode:: kotlin - - nodeA.registerInitiatedFlow(ExampleFlow.Acceptor::class.java) - - .. sourcecode:: java - - nodeA.registerInitiatedFlow(ExampleFlow.Acceptor.class); - -Running the network -^^^^^^^^^^^^^^^^^^^ - -Regular Corda nodes automatically process received messages. When using a ``MockNetwork`` with -``networkSendManuallyPumped`` set to ``false``, you must manually initiate the processing of received messages. - -You manually process received messages as follows: - -* ``StartedMockNode.pumpReceive`` to process a single message from the node's queue - -* ``MockNetwork.runNetwork`` to process all the messages in every node's queue. This may generate additional messages - that must in turn be processed - - * ``network.runNetwork(-1)`` (the default in Kotlin) will exchange messages until there are no further messages to - process - -Running flows -^^^^^^^^^^^^^ - -A ``StartedMockNode`` starts a flow using the ``StartedNodeServices.startFlow`` method. This method returns a future -representing the output of running the flow. - -.. container:: codeset - - .. sourcecode:: kotlin - - val signedTransactionFuture = nodeA.services.startFlow(IOUFlow(iouValue = 99, otherParty = nodeBParty)) - - .. sourcecode:: java - - CordaFuture future = startFlow(a.getServices(), new ExampleFlow.Initiator(1, nodeBParty)); - -The network must then be manually run before retrieving the future's value: - -.. container:: codeset - - .. sourcecode:: kotlin - - val signedTransactionFuture = nodeA.services.startFlow(IOUFlow(iouValue = 99, otherParty = nodeBParty)) - // Assuming network.networkSendManuallyPumped == false. - network.runNetwork() - val signedTransaction = future.get(); - - .. sourcecode:: java - - CordaFuture future = startFlow(a.getServices(), new ExampleFlow.Initiator(1, nodeBParty)); - // Assuming network.networkSendManuallyPumped == false. - network.runNetwork(); - SignedTransaction signedTransaction = future.get(); - -Accessing ``StartedMockNode`` internals -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Creating a node database transaction -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Whenever you query a node's database (e.g. to extract information from the node's vault), you must wrap the query in -a database transaction, as follows: - -.. container:: codeset - - .. sourcecode:: kotlin - - nodeA.database.transaction { - // Perform query here. - } - - .. sourcecode:: java - - node.getDatabase().transaction(tx -> { - // Perform query here. - } - -Querying a node's vault -~~~~~~~~~~~~~~~~~~~~~~~ - -Recorded states can be retrieved from the vault of a ``StartedMockNode`` using: - -.. container:: codeset - - .. sourcecode:: kotlin - - nodeA.database.transaction { - val myStates = nodeA.services.vaultService.queryBy().states - } - - .. sourcecode:: java - - node.getDatabase().transaction(tx -> { - List myStates = node.getServices().getVaultService().queryBy(MyStateType.class).getStates(); - } - -This allows you to check whether a given state has (or has not) been stored, and whether it has the correct attributes. - - -Examining a node's transaction storage -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Recorded transactions can be retrieved from the transaction storage of a ``StartedMockNode`` using: - -.. container:: codeset - - .. sourcecode:: kotlin - - val transaction = nodeA.services.validatedTransactions.getTransaction(transaction.id) - - .. sourcecode:: java - - SignedTransaction transaction = nodeA.getServices().getValidatedTransactions().getTransaction(transaction.getId()) - -This allows you to check whether a given transaction has (or has not) been stored, and whether it has the correct -attributes. - -This allows you to check whether a given state has (or has not) been stored, and whether it has the correct attributes. - -Further examples -^^^^^^^^^^^^^^^^ - -* See the flow testing tutorial :doc:`here ` -* Further examples are available in the Example CorDapp in - `Java `_ and - `Kotlin `_ - -Contract testing ----------------- - -The Corda test framework includes the ability to create a test ledger by calling the ``ledger`` function -on an implementation of the ``ServiceHub`` interface. - -MockServices -^^^^^^^^^^^^ - -A mock implementation of ``ServiceHub`` is provided in ``MockServices``. This is a minimal ``ServiceHub`` that -suffices to test contract logic. It has the ability to insert states into the vault, query the vault, and -construct and check transactions. - -.. container:: codeset - - .. literalinclude:: ../../docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt - :language: kotlin - :start-after: DOCSTART 11 - :end-before: DOCEND 11 - :dedent: 4 - - .. literalinclude:: ../../docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java - :language: java - :start-after: DOCSTART 11 - :end-before: DOCEND 11 - :dedent: 4 - - -Alternatively, there is a helper constructor which just accepts a list of ``TestIdentity``. The first identity provided is -the identity of the node whose ``ServiceHub`` is being mocked, and any subsequent identities are identities that the node -knows about. Only the calling package is scanned for cordapps and a test ``IdentityService`` is created -for you, using all the given identities. - -.. container:: codeset - - .. literalinclude:: ../../docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt - :language: kotlin - :start-after: DOCSTART 12 - :end-before: DOCEND 12 - :dedent: 4 - - .. literalinclude:: ../../docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java - :language: java - :start-after: DOCSTART 12 - :end-before: DOCEND 12 - :dedent: 4 - - -Writing tests using a test ledger -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The ``ServiceHub.ledger`` extension function allows you to create a test ledger. Within the ledger wrapper you can create -transactions using the ``transaction`` function. Within a transaction you can define the ``input`` and -``output`` states for the transaction, alongside any commands that are being executed, the ``timeWindow`` in which the -transaction has been executed, and any ``attachments``, as shown in this example test: - -.. container:: codeset - - .. literalinclude:: ../../docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt - :language: kotlin - :start-after: DOCSTART 13 - :end-before: DOCEND 13 - :dedent: 4 - - .. literalinclude:: ../../docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java - :language: java - :start-after: DOCSTART 13 - :end-before: DOCEND 13 - :dedent: 4 - -Once all the transaction components have been specified, you can run ``verifies()`` to check that the given transaction is valid. - -Checking for failure states -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -In order to test for failures, you can use the ``failsWith`` method, or in Kotlin the ``fails with`` helper method, which -assert that the transaction fails with a specific error. If you just want to assert that the transaction has failed without -verifying the message, there is also a ``fails`` method. - -.. container:: codeset - - .. literalinclude:: ../../docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt - :language: kotlin - :start-after: DOCSTART 4 - :end-before: DOCEND 4 - :dedent: 4 - - .. literalinclude:: ../../docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java - :language: java - :start-after: DOCSTART 4 - :end-before: DOCEND 4 - :dedent: 4 - -.. note:: - - The transaction DSL forces the last line of the test to be either a ``verifies`` or ``fails with`` statement. - -Testing multiple scenarios at once -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Within a single transaction block, you can assert several times that the transaction constructed so far either passes or -fails verification. For example, you could test that a contract fails to verify because it has no output states, and then -add the relevant output state and check that the contract verifies successfully, as in the following example: - -.. container:: codeset - - .. literalinclude:: ../../docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt - :language: kotlin - :start-after: DOCSTART 5 - :end-before: DOCEND 5 - :dedent: 4 - - .. literalinclude:: ../../docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java - :language: java - :start-after: DOCSTART 5 - :end-before: DOCEND 5 - :dedent: 4 - -You can also use the ``tweak`` function to create a locally scoped transaction that you can make changes to -and then return to the original, unmodified transaction. As in the following example: - -.. container:: codeset - - .. literalinclude:: ../../docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt - :language: kotlin - :start-after: DOCSTART 7 - :end-before: DOCEND 7 - :dedent: 4 - - .. literalinclude:: ../../docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java - :language: java - :start-after: DOCSTART 7 - :end-before: DOCEND 7 - :dedent: 4 - - -Chaining transactions -~~~~~~~~~~~~~~~~~~~~~ - -The following example shows that within a ``ledger``, you can create more than one ``transaction`` in order to test chains -of transactions. In addition to ``transaction``, ``unverifiedTransaction`` can be used, as in the example below, to create -transactions on the ledger without verifying them, for pre-populating the ledger with existing data. When chaining transactions, -it is important to note that even though a ``transaction`` ``verifies`` successfully, the overall ledger may not be valid. This can -be verified separately by placing a ``verifies`` or ``fails`` statement within the ``ledger`` block. - -.. container:: codeset - - .. literalinclude:: ../../docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt - :language: kotlin - :start-after: DOCSTART 9 - :end-before: DOCEND 9 - :dedent: 4 - - .. literalinclude:: ../../docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java - :language: java - :start-after: DOCSTART 9 - :end-before: DOCEND 9 - :dedent: 4 - - -Further examples -^^^^^^^^^^^^^^^^ - -* See the flow testing tutorial :doc:`here ` -* Further examples are available in the Example CorDapp in - `Java `_ and - `Kotlin `_ diff --git a/docs/source/conf.py b/docs/source/conf.py index ebf92265fb..b34fa914f9 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -46,7 +46,7 @@ master_doc = 'index' # General information about the project. project = u'R3 Corda' -copyright = u'2018, R3 Limited' +copyright = u'2017, R3 Limited' author = u'R3 DLG' # The version info for the project you're documenting, acts as replacement for diff --git a/docs/source/corda-api.rst b/docs/source/corda-api.rst index f0f803a227..aa55bfefcf 100644 --- a/docs/source/corda-api.rst +++ b/docs/source/corda-api.rst @@ -17,7 +17,6 @@ The following are the core APIs that are used in the development of CorDapps: api-service-hub api-rpc api-core-types - api-testing Before reading this page, you should be familiar with the :doc:`key concepts of Corda `. diff --git a/docs/source/deploying-a-node.rst b/docs/source/deploying-a-node.rst index 0643ff1332..6918cc743d 100644 --- a/docs/source/deploying-a-node.rst +++ b/docs/source/deploying-a-node.rst @@ -199,7 +199,7 @@ at boot, and means the Corda service stays running with no users connected to th mkdir C:\Corda wget http://jcenter.bintray.com/net/corda/corda/VERSION_NUMBER/corda-VERSION_NUMBER.jar -OutFile C:\Corda\corda.jar -2. Create a directory called ``cordapps`` in ``C:\Corda\`` and save your CorDapp jar file to it. Alternatively, +2. Create a directory called ``cordapps`` in ``/opt/corda`` and save your CorDapp jar file to it. Alternatively, download one of our `sample CorDapps `_ to the ``cordapps`` directory 3. Save the below as ``C:\Corda\node.conf``. See :doc:`corda-configuration-file` for a description of these options diff --git a/docs/source/example-code/build.gradle b/docs/source/example-code/build.gradle index d8ca430972..50464033a5 100644 --- a/docs/source/example-code/build.gradle +++ b/docs/source/example-code/build.gradle @@ -80,20 +80,14 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { name "O=Notary Service,OU=corda,L=London,C=GB" notary = [validating : true] p2pPort 10002 - rpcSettings { - address "localhost:10003" - adminAddress "localhost:10013" - } + rpcPort 10003 webPort 10004 cordapps = [] } node { name "O=Alice Corp,L=London,C=GB" p2pPort 10005 - rpcSettings { - address "localhost:10006" - adminAddress "localhost:10016" - } + rpcPort 10006 webPort 10007 cordapps = [] rpcUsers = [ diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt index 880570e2df..08827f1d30 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt @@ -27,7 +27,6 @@ import net.corda.finance.contracts.asset.Cash import net.corda.testing.contracts.DummyContract import net.corda.testing.contracts.DummyState import java.security.PublicKey -import java.security.Signature import java.time.Instant // ``InitiatorFlow`` is our first flow, and will communicate with @@ -206,19 +205,6 @@ class InitiatorFlow(val arg1: Boolean, val arg2: Int, private val counterparty: val packet3: UntrustworthyData = regulatorSession.receive() // DOCEND 06 - // We may also batch receives in order to increase performance. This - // ensures that only a single checkpoint is created for all received - // messages. - // Type-safe variant: - val signatures: List> = - receiveAll(Signature::class.java, listOf(counterpartySession, regulatorSession)) - // Dynamic variant: - val messages: Map> = - receiveAllMap(mapOf( - counterpartySession to Boolean::class.java, - regulatorSession to String::class.java - )) - /**----------------------------------- * EXTRACTING STATES FROM THE VAULT * -----------------------------------**/ diff --git a/docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java b/docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java index 0fe590c86a..0e206bf77c 100644 --- a/docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java +++ b/docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java @@ -7,66 +7,39 @@ import net.corda.core.identity.CordaX500Name; import net.corda.finance.contracts.ICommercialPaperState; import net.corda.finance.contracts.JavaCommercialPaper; import net.corda.finance.contracts.asset.Cash; -import net.corda.testing.core.TestIdentity; import net.corda.testing.node.MockServices; -import org.junit.Before; +import net.corda.testing.core.TestIdentity; import org.junit.Test; +import java.security.PublicKey; import java.time.temporal.ChronoUnit; -import static java.util.Collections.singletonList; +import static java.util.Collections.emptyList; +import static net.corda.core.crypto.Crypto.generateKeyPair; import static net.corda.finance.Currencies.DOLLARS; import static net.corda.finance.Currencies.issuedBy; import static net.corda.finance.contracts.JavaCommercialPaper.JCP_PROGRAM_ID; -import static net.corda.testing.core.TestConstants.*; import static net.corda.testing.node.MockServicesKt.makeTestIdentityService; import static net.corda.testing.node.NodeTestUtils.ledger; import static net.corda.testing.node.NodeTestUtils.transaction; +import static net.corda.testing.core.TestConstants.ALICE_NAME; +import static net.corda.testing.core.TestConstants.BOB_NAME; +import static net.corda.testing.core.TestConstants.TEST_TX_TIME; public class CommercialPaperTest { - - private static final TestIdentity alice = new TestIdentity(ALICE_NAME, 70L); - private static final TestIdentity bigCorp = new TestIdentity(new CordaX500Name("BigCorp", "New York", "GB")); - private static final TestIdentity bob = new TestIdentity(BOB_NAME, 80L); - private static final TestIdentity megaCorp = new TestIdentity(new CordaX500Name("MegaCorp", "London", "GB")); + private static final TestIdentity ALICE = new TestIdentity(ALICE_NAME, 70L); + private static final PublicKey BIG_CORP_PUBKEY = generateKeyPair().getPublic(); + private static final TestIdentity BOB = new TestIdentity(BOB_NAME, 80L); + private static final TestIdentity MEGA_CORP = new TestIdentity(new CordaX500Name("MegaCorp", "London", "GB")); private final byte[] defaultRef = {123}; - private MockServices ledgerServices; - - @Before - public void setUp() { - // DOCSTART 11 - ledgerServices = new MockServices( - // A list of packages to scan for cordapps - singletonList("net.corda.finance.contracts"), - // The identity represented by this set of mock services. Defaults to a test identity. - // You can also use the alternative parameter initialIdentityName which accepts a - // [CordaX500Name] - megaCorp, - // An implementation of [IdentityService], which contains a list of all identities known - // to the node. Use [makeTestIdentityService] which returns an implementation of - // [InMemoryIdentityService] with the given identities - makeTestIdentityService(megaCorp.getIdentity()) - ); - // DOCEND 11 - } - - @SuppressWarnings("unused") - // DOCSTART 12 - private final MockServices simpleLedgerServices = new MockServices( - // This is the identity of the node - megaCorp, - // Other identities the test node knows about - bigCorp, - alice - ); - // DOCEND 12 + private final MockServices ledgerServices = new MockServices(MEGA_CORP); // DOCSTART 1 private ICommercialPaperState getPaper() { return new JavaCommercialPaper.State( - megaCorp.ref(defaultRef), - megaCorp.getParty(), - issuedBy(DOLLARS(1000), megaCorp.ref(defaultRef)), + MEGA_CORP.ref(defaultRef), + MEGA_CORP.getParty(), + issuedBy(DOLLARS(1000), MEGA_CORP.ref(defaultRef)), TEST_TX_TIME.plus(7, ChronoUnit.DAYS) ); } @@ -96,7 +69,7 @@ public class CommercialPaperTest { ledger(ledgerServices, l -> { l.transaction(tx -> { tx.input(JCP_PROGRAM_ID, inState); - tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move()); + tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move()); tx.attachments(JCP_PROGRAM_ID); return tx.verifies(); }); @@ -112,7 +85,7 @@ public class CommercialPaperTest { ledger(ledgerServices, l -> { l.transaction(tx -> { tx.input(JCP_PROGRAM_ID, inState); - tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move()); + tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move()); tx.attachments(JCP_PROGRAM_ID); return tx.failsWith("the state is propagated"); }); @@ -123,15 +96,15 @@ public class CommercialPaperTest { // DOCSTART 5 @Test - public void simpleCPMoveSuccessAndFailure() { + public void simpleCPMoveSuccess() { ICommercialPaperState inState = getPaper(); ledger(ledgerServices, l -> { l.transaction(tx -> { tx.input(JCP_PROGRAM_ID, inState); - tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move()); + tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move()); tx.attachments(JCP_PROGRAM_ID); tx.failsWith("the state is propagated"); - tx.output(JCP_PROGRAM_ID, "alice's paper", inState.withOwner(alice.getParty())); + tx.output(JCP_PROGRAM_ID, "alice's paper", inState.withOwner(ALICE.getParty())); return tx.verifies(); }); return Unit.INSTANCE; @@ -139,24 +112,6 @@ public class CommercialPaperTest { } // DOCEND 5 - // DOCSTART 13 - @Test - public void simpleCPMoveSuccess() { - ICommercialPaperState inState = getPaper(); - ledger(ledgerServices, l -> { - l.transaction(tx -> { - tx.input(JCP_PROGRAM_ID, inState); - tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move()); - tx.attachments(JCP_PROGRAM_ID); - tx.timeWindow(TEST_TX_TIME); - tx.output(JCP_PROGRAM_ID, "alice's paper", inState.withOwner(alice.getParty())); - return tx.verifies(); - }); - return Unit.INSTANCE; - }); - } - // DOCEND 13 - // DOCSTART 6 @Test public void simpleIssuanceWithTweak() { @@ -165,11 +120,11 @@ public class CommercialPaperTest { tx.output(JCP_PROGRAM_ID, "paper", getPaper()); // Some CP is issued onto the ledger by MegaCorp. tx.attachments(JCP_PROGRAM_ID); tx.tweak(tw -> { - tw.command(bigCorp.getPublicKey(), new JavaCommercialPaper.Commands.Issue()); + tw.command(BIG_CORP_PUBKEY, new JavaCommercialPaper.Commands.Issue()); tw.timeWindow(TEST_TX_TIME); return tw.failsWith("output states are issued by a command signer"); }); - tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Issue()); + tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Issue()); tx.timeWindow(TEST_TX_TIME); return tx.verifies(); }); @@ -185,11 +140,11 @@ public class CommercialPaperTest { tx.output(JCP_PROGRAM_ID, "paper", getPaper()); // Some CP is issued onto the ledger by MegaCorp. tx.attachments(JCP_PROGRAM_ID); tx.tweak(tw -> { - tw.command(bigCorp.getPublicKey(), new JavaCommercialPaper.Commands.Issue()); + tw.command(BIG_CORP_PUBKEY, new JavaCommercialPaper.Commands.Issue()); tw.timeWindow(TEST_TX_TIME); return tw.failsWith("output states are issued by a command signer"); }); - tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Issue()); + tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Issue()); tx.timeWindow(TEST_TX_TIME); return tx.verifies(); }); @@ -199,11 +154,11 @@ public class CommercialPaperTest { // DOCSTART 8 @Test public void chainCommercialPaper() { - PartyAndReference issuer = megaCorp.ref(defaultRef); + PartyAndReference issuer = MEGA_CORP.ref(defaultRef); ledger(ledgerServices, l -> { l.unverifiedTransaction(tx -> { tx.output(Cash.PROGRAM_ID, "alice's $900", - new Cash.State(issuedBy(DOLLARS(900), issuer), alice.getParty())); + new Cash.State(issuedBy(DOLLARS(900), issuer), ALICE.getParty())); tx.attachments(Cash.PROGRAM_ID); return Unit.INSTANCE; }); @@ -211,7 +166,7 @@ public class CommercialPaperTest { // Some CP is issued onto the ledger by MegaCorp. l.transaction("Issuance", tx -> { tx.output(JCP_PROGRAM_ID, "paper", getPaper()); - tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Issue()); + tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Issue()); tx.attachments(JCP_PROGRAM_ID); tx.timeWindow(TEST_TX_TIME); return tx.verifies(); @@ -220,11 +175,11 @@ public class CommercialPaperTest { l.transaction("Trade", tx -> { tx.input("paper"); tx.input("alice's $900"); - tx.output(Cash.PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), megaCorp.getParty())); + tx.output(Cash.PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), MEGA_CORP.getParty())); JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper"); - tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(alice.getParty())); - tx.command(alice.getPublicKey(), new Cash.Commands.Move()); - tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move()); + tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(ALICE.getParty())); + tx.command(ALICE.getPublicKey(), new Cash.Commands.Move()); + tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move()); return tx.verifies(); }); return Unit.INSTANCE; @@ -235,11 +190,11 @@ public class CommercialPaperTest { // DOCSTART 9 @Test public void chainCommercialPaperDoubleSpend() { - PartyAndReference issuer = megaCorp.ref(defaultRef); + PartyAndReference issuer = MEGA_CORP.ref(defaultRef); ledger(ledgerServices, l -> { l.unverifiedTransaction(tx -> { tx.output(Cash.PROGRAM_ID, "alice's $900", - new Cash.State(issuedBy(DOLLARS(900), issuer), alice.getParty())); + new Cash.State(issuedBy(DOLLARS(900), issuer), ALICE.getParty())); tx.attachments(Cash.PROGRAM_ID); return Unit.INSTANCE; }); @@ -247,7 +202,7 @@ public class CommercialPaperTest { // Some CP is issued onto the ledger by MegaCorp. l.transaction("Issuance", tx -> { tx.output(Cash.PROGRAM_ID, "paper", getPaper()); - tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Issue()); + tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Issue()); tx.attachments(JCP_PROGRAM_ID); tx.timeWindow(TEST_TX_TIME); return tx.verifies(); @@ -256,11 +211,11 @@ public class CommercialPaperTest { l.transaction("Trade", tx -> { tx.input("paper"); tx.input("alice's $900"); - tx.output(Cash.PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), megaCorp.getParty())); + tx.output(Cash.PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), MEGA_CORP.getParty())); JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper"); - tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(alice.getParty())); - tx.command(alice.getPublicKey(), new Cash.Commands.Move()); - tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move()); + tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(ALICE.getParty())); + tx.command(ALICE.getPublicKey(), new Cash.Commands.Move()); + tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move()); return tx.verifies(); }); @@ -268,8 +223,8 @@ public class CommercialPaperTest { tx.input("paper"); JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper"); // We moved a paper to other pubkey. - tx.output(JCP_PROGRAM_ID, "bob's paper", inputPaper.withOwner(bob.getParty())); - tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move()); + tx.output(JCP_PROGRAM_ID, "bob's paper", inputPaper.withOwner(BOB.getParty())); + tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move()); return tx.verifies(); }); l.fails(); @@ -281,11 +236,11 @@ public class CommercialPaperTest { // DOCSTART 10 @Test public void chainCommercialPaperTweak() { - PartyAndReference issuer = megaCorp.ref(defaultRef); + PartyAndReference issuer = MEGA_CORP.ref(defaultRef); ledger(ledgerServices, l -> { l.unverifiedTransaction(tx -> { tx.output(Cash.PROGRAM_ID, "alice's $900", - new Cash.State(issuedBy(DOLLARS(900), issuer), alice.getParty())); + new Cash.State(issuedBy(DOLLARS(900), issuer), ALICE.getParty())); tx.attachments(Cash.PROGRAM_ID); return Unit.INSTANCE; }); @@ -293,7 +248,7 @@ public class CommercialPaperTest { // Some CP is issued onto the ledger by MegaCorp. l.transaction("Issuance", tx -> { tx.output(Cash.PROGRAM_ID, "paper", getPaper()); - tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Issue()); + tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Issue()); tx.attachments(JCP_PROGRAM_ID); tx.timeWindow(TEST_TX_TIME); return tx.verifies(); @@ -302,11 +257,11 @@ public class CommercialPaperTest { l.transaction("Trade", tx -> { tx.input("paper"); tx.input("alice's $900"); - tx.output(Cash.PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), megaCorp.getParty())); + tx.output(Cash.PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), MEGA_CORP.getParty())); JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper"); - tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(alice.getParty())); - tx.command(alice.getPublicKey(), new Cash.Commands.Move()); - tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move()); + tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(ALICE.getParty())); + tx.command(ALICE.getPublicKey(), new Cash.Commands.Move()); + tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move()); return tx.verifies(); }); @@ -315,8 +270,8 @@ public class CommercialPaperTest { tx.input("paper"); JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper"); // We moved a paper to another pubkey. - tx.output(JCP_PROGRAM_ID, "bob's paper", inputPaper.withOwner(bob.getParty())); - tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move()); + tx.output(JCP_PROGRAM_ID, "bob's paper", inputPaper.withOwner(BOB.getParty())); + tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move()); return tx.verifies(); }); lw.fails(); diff --git a/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt b/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt index 67a4c919f7..c7dc1ccbf5 100644 --- a/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt +++ b/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt @@ -13,6 +13,7 @@ import net.corda.testing.node.StartedMockNode import net.corda.testing.node.startFlow import org.junit.After import org.junit.Before +import org.junit.Ignore import org.junit.Test import kotlin.test.assertEquals @@ -36,6 +37,7 @@ class FxTransactionBuildTutorialTest { mockNet.stopNodes() } + @Ignore("Pending fix from corda") @Test fun `Run ForeignExchangeFlow to completion`() { // Use NodeA as issuer and create some dollars diff --git a/docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt b/docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt index 42d7db3d2a..3b3d5c003a 100644 --- a/docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt +++ b/docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt @@ -18,7 +18,6 @@ import net.corda.testing.core.* import net.corda.testing.internal.rigorousMock import net.corda.testing.node.MockServices import net.corda.testing.node.ledger -import net.corda.testing.node.makeTestIdentityService import net.corda.testing.node.transaction import org.junit.Rule import org.junit.Test @@ -26,46 +25,30 @@ import org.junit.Test class CommercialPaperTest { private companion object { val alice = TestIdentity(ALICE_NAME, 70) - val bob = TestIdentity(BOB_NAME, 80) - val bigCorp = TestIdentity((CordaX500Name("BigCorp", "New York", "GB"))) - val dummyNotary = TestIdentity(DUMMY_NOTARY_NAME, 20) + val BIG_CORP_PUBKEY = generateKeyPair().public + val BOB = TestIdentity(BOB_NAME, 80).party + val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB")) + val ALICE get() = alice.party + val ALICE_PUBKEY get() = alice.publicKey + val MEGA_CORP get() = megaCorp.party + val MEGA_CORP_PUBKEY get() = megaCorp.publicKey } @Rule @JvmField val testSerialization = SerializationEnvironmentRule() - // DOCSTART 11 - private val ledgerServices = MockServices( - // A list of packages to scan for cordapps - listOf("net.corda.finance.contracts"), - // The identity represented by this set of mock services. Defaults to a test identity. - // You can also use the alternative parameter initialIdentityName which accepts a - // [CordaX500Name] - megaCorp, - rigorousMock().also { - doReturn(megaCorp.party).whenever(it).partyFromKey(megaCorp.publicKey) - doReturn(null).whenever(it).partyFromKey(bigCorp.publicKey) - doReturn(null).whenever(it).partyFromKey(alice.publicKey) + private val ledgerServices = MockServices(emptyList(), MEGA_CORP.name, rigorousMock().also { + doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY) + doReturn(null).whenever(it).partyFromKey(BIG_CORP_PUBKEY) + doReturn(null).whenever(it).partyFromKey(ALICE_PUBKEY) }) - // DOCEND 11 - - // DOCSTART 12 - @Suppress("unused") - private val simpleLedgerServices = MockServices( - // This is the identity of the node - megaCorp, - // Other identities the test node knows about - bigCorp, - alice - ) - // DOCEND 12 // DOCSTART 1 fun getPaper(): ICommercialPaperState = CommercialPaper.State( - issuance = megaCorp.party.ref(123), - owner = megaCorp.party, - faceValue = 1000.DOLLARS `issued by` megaCorp.party.ref(123), + issuance = MEGA_CORP.ref(123), + owner = MEGA_CORP, + faceValue = 1000.DOLLARS `issued by` MEGA_CORP.ref(123), maturityDate = TEST_TX_TIME + 7.days ) // DOCEND 1 @@ -75,7 +58,7 @@ class CommercialPaperTest { @Test(expected = IllegalStateException::class) fun simpleCP() { val inState = getPaper() - ledgerServices.ledger(dummyNotary.party) { + ledgerServices.ledger(DUMMY_NOTARY) { transaction { attachments(CP_PROGRAM_ID) input(CP_PROGRAM_ID, inState) @@ -90,10 +73,10 @@ class CommercialPaperTest { @Test(expected = TransactionVerificationException.ContractRejection::class) fun simpleCPMove() { val inState = getPaper() - ledgerServices.ledger(dummyNotary.party) { + ledgerServices.ledger(DUMMY_NOTARY) { transaction { input(CP_PROGRAM_ID, inState) - command(megaCorp.publicKey, CommercialPaper.Commands.Move()) + command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move()) attachments(CP_PROGRAM_ID) verifies() } @@ -105,10 +88,10 @@ class CommercialPaperTest { @Test fun simpleCPMoveFails() { val inState = getPaper() - ledgerServices.ledger(dummyNotary.party) { + ledgerServices.ledger(DUMMY_NOTARY) { transaction { input(CP_PROGRAM_ID, inState) - command(megaCorp.publicKey, CommercialPaper.Commands.Move()) + command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move()) attachments(CP_PROGRAM_ID) `fails with`("the state is propagated") } @@ -118,52 +101,35 @@ class CommercialPaperTest { // DOCSTART 5 @Test - fun simpleCPMoveFailureAndSuccess() { + fun simpleCPMoveSuccess() { val inState = getPaper() - ledgerServices.ledger(dummyNotary.party) { + ledgerServices.ledger(DUMMY_NOTARY) { transaction { input(CP_PROGRAM_ID, inState) - command(megaCorp.publicKey, CommercialPaper.Commands.Move()) + command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move()) attachments(CP_PROGRAM_ID) `fails with`("the state is propagated") - output(CP_PROGRAM_ID, "alice's paper", inState.withOwner(alice.party)) + output(CP_PROGRAM_ID, "alice's paper", inState.withOwner(ALICE)) verifies() } } } // DOCEND 5 - // DOCSTART 13 - @Test - fun simpleCPMoveSuccess() { - val inState = getPaper() - ledgerServices.ledger(dummyNotary.party) { - transaction { - input(CP_PROGRAM_ID, inState) - command(megaCorp.publicKey, CommercialPaper.Commands.Move()) - attachments(CP_PROGRAM_ID) - timeWindow(TEST_TX_TIME) - output(CP_PROGRAM_ID, "alice's paper", inState.withOwner(alice.party)) - verifies() - } - } - } - // DOCEND 13 - // DOCSTART 6 @Test fun `simple issuance with tweak`() { - ledgerServices.ledger(dummyNotary.party) { + ledgerServices.ledger(DUMMY_NOTARY) { transaction { output(CP_PROGRAM_ID, "paper", getPaper()) // Some CP is issued onto the ledger by MegaCorp. attachments(CP_PROGRAM_ID) tweak { // The wrong pubkey. - command(bigCorp.publicKey, CommercialPaper.Commands.Issue()) + command(BIG_CORP_PUBKEY, CommercialPaper.Commands.Issue()) timeWindow(TEST_TX_TIME) `fails with`("output states are issued by a command signer") } - command(megaCorp.publicKey, CommercialPaper.Commands.Issue()) + command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Issue()) timeWindow(TEST_TX_TIME) verifies() } @@ -174,16 +140,16 @@ class CommercialPaperTest { // DOCSTART 7 @Test fun `simple issuance with tweak and top level transaction`() { - ledgerServices.transaction(dummyNotary.party) { + ledgerServices.transaction(DUMMY_NOTARY) { output(CP_PROGRAM_ID, "paper", getPaper()) // Some CP is issued onto the ledger by MegaCorp. attachments(CP_PROGRAM_ID) tweak { // The wrong pubkey. - command(bigCorp.publicKey, CommercialPaper.Commands.Issue()) + command(BIG_CORP_PUBKEY, CommercialPaper.Commands.Issue()) timeWindow(TEST_TX_TIME) `fails with`("output states are issued by a command signer") } - command(megaCorp.publicKey, CommercialPaper.Commands.Issue()) + command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Issue()) timeWindow(TEST_TX_TIME) verifies() } @@ -193,17 +159,17 @@ class CommercialPaperTest { // DOCSTART 8 @Test fun `chain commercial paper`() { - val issuer = megaCorp.party.ref(123) - ledgerServices.ledger(dummyNotary.party) { + val issuer = MEGA_CORP.ref(123) + ledgerServices.ledger(DUMMY_NOTARY) { unverifiedTransaction { attachments(Cash.PROGRAM_ID) - output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH issuedBy issuer ownedBy alice.party) + output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH issuedBy issuer ownedBy ALICE) } // Some CP is issued onto the ledger by MegaCorp. transaction("Issuance") { output(CP_PROGRAM_ID, "paper", getPaper()) - command(megaCorp.publicKey, CommercialPaper.Commands.Issue()) + command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Issue()) attachments(CP_PROGRAM_ID) timeWindow(TEST_TX_TIME) verifies() @@ -213,10 +179,10 @@ class CommercialPaperTest { transaction("Trade") { input("paper") input("alice's $900") - output(Cash.PROGRAM_ID, "borrowed $900", 900.DOLLARS.CASH issuedBy issuer ownedBy megaCorp.party) - output(CP_PROGRAM_ID, "alice's paper", "paper".output().withOwner(alice.party)) - command(alice.publicKey, Cash.Commands.Move()) - command(megaCorp.publicKey, CommercialPaper.Commands.Move()) + output(Cash.PROGRAM_ID, "borrowed $900", 900.DOLLARS.CASH issuedBy issuer ownedBy MEGA_CORP) + output(CP_PROGRAM_ID, "alice's paper", "paper".output().withOwner(ALICE)) + command(ALICE_PUBKEY, Cash.Commands.Move()) + command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move()) verifies() } } @@ -226,17 +192,17 @@ class CommercialPaperTest { // DOCSTART 9 @Test fun `chain commercial paper double spend`() { - val issuer = megaCorp.party.ref(123) - ledgerServices.ledger(dummyNotary.party) { + val issuer = MEGA_CORP.ref(123) + ledgerServices.ledger(DUMMY_NOTARY) { unverifiedTransaction { attachments(Cash.PROGRAM_ID) - output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH issuedBy issuer ownedBy alice.party) + output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH issuedBy issuer ownedBy ALICE) } // Some CP is issued onto the ledger by MegaCorp. transaction("Issuance") { output(CP_PROGRAM_ID, "paper", getPaper()) - command(megaCorp.publicKey, CommercialPaper.Commands.Issue()) + command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Issue()) attachments(CP_PROGRAM_ID) timeWindow(TEST_TX_TIME) verifies() @@ -245,18 +211,18 @@ class CommercialPaperTest { transaction("Trade") { input("paper") input("alice's $900") - output(Cash.PROGRAM_ID, "borrowed $900", 900.DOLLARS.CASH issuedBy issuer ownedBy megaCorp.party) - output(CP_PROGRAM_ID, "alice's paper", "paper".output().withOwner(alice.party)) - command(alice.publicKey, Cash.Commands.Move()) - command(megaCorp.publicKey, CommercialPaper.Commands.Move()) + output(Cash.PROGRAM_ID, "borrowed $900", 900.DOLLARS.CASH issuedBy issuer ownedBy MEGA_CORP) + output(CP_PROGRAM_ID, "alice's paper", "paper".output().withOwner(ALICE)) + command(ALICE_PUBKEY, Cash.Commands.Move()) + command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move()) verifies() } transaction { input("paper") // We moved a paper to another pubkey. - output(CP_PROGRAM_ID, "bob's paper", "paper".output().withOwner(bob.party)) - command(megaCorp.publicKey, CommercialPaper.Commands.Move()) + output(CP_PROGRAM_ID, "bob's paper", "paper".output().withOwner(BOB)) + command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move()) verifies() } @@ -268,17 +234,17 @@ class CommercialPaperTest { // DOCSTART 10 @Test fun `chain commercial tweak`() { - val issuer = megaCorp.party.ref(123) - ledgerServices.ledger(dummyNotary.party) { + val issuer = MEGA_CORP.ref(123) + ledgerServices.ledger(DUMMY_NOTARY) { unverifiedTransaction { attachments(Cash.PROGRAM_ID) - output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH issuedBy issuer ownedBy alice.party) + output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH issuedBy issuer ownedBy ALICE) } // Some CP is issued onto the ledger by MegaCorp. transaction("Issuance") { output(CP_PROGRAM_ID, "paper", getPaper()) - command(megaCorp.publicKey, CommercialPaper.Commands.Issue()) + command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Issue()) attachments(CP_PROGRAM_ID) timeWindow(TEST_TX_TIME) verifies() @@ -287,10 +253,10 @@ class CommercialPaperTest { transaction("Trade") { input("paper") input("alice's $900") - output(Cash.PROGRAM_ID, "borrowed $900", 900.DOLLARS.CASH issuedBy issuer ownedBy megaCorp.party) - output(CP_PROGRAM_ID, "alice's paper", "paper".output().withOwner(alice.party)) - command(alice.publicKey, Cash.Commands.Move()) - command(megaCorp.publicKey, CommercialPaper.Commands.Move()) + output(Cash.PROGRAM_ID, "borrowed $900", 900.DOLLARS.CASH issuedBy issuer ownedBy MEGA_CORP) + output(CP_PROGRAM_ID, "alice's paper", "paper".output().withOwner(ALICE)) + command(ALICE_PUBKEY, Cash.Commands.Move()) + command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move()) verifies() } @@ -298,8 +264,8 @@ class CommercialPaperTest { transaction { input("paper") // We moved a paper to another pubkey. - output(CP_PROGRAM_ID, "bob's paper", "paper".output().withOwner(bob.party)) - command(megaCorp.publicKey, CommercialPaper.Commands.Move()) + output(CP_PROGRAM_ID, "bob's paper", "paper".output().withOwner(BOB)) + command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move()) verifies() } fails() diff --git a/finance/src/test/kotlin/net/corda/finance/contracts/asset/ObligationTests.kt b/finance/src/test/kotlin/net/corda/finance/contracts/asset/ObligationTests.kt index fa32531bfd..87f481396b 100644 --- a/finance/src/test/kotlin/net/corda/finance/contracts/asset/ObligationTests.kt +++ b/finance/src/test/kotlin/net/corda/finance/contracts/asset/ObligationTests.kt @@ -88,7 +88,7 @@ class ObligationTests { doReturn(MINI_CORP).whenever(it).partyFromKey(MINI_CORP_PUBKEY) } private val mockService = MockServices(listOf("net.corda.finance.contracts.asset"), MEGA_CORP.name, identityService) - private val ledgerServices get() = MockServices(listOf("net.corda.finance.contracts.asset", "net.corda.testing.contracts"), MEGA_CORP.name, identityService) + private val ledgerServices get() = MockServices(emptyList(), MEGA_CORP.name, identityService) private fun cashObligationTestRoots( group: LedgerDSL ) = group.apply { diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkParametersConfiguration.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkParametersConfiguration.kt index 4c1ad1c19d..11acc7d736 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkParametersConfiguration.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkParametersConfiguration.kt @@ -68,7 +68,5 @@ internal fun parseNetworkParameters(configuration: NetworkParametersConfiguratio configuration.maxMessageSize, configuration.maxTransactionSize, Instant.now(), - epoch, - // TODO: Tudor, Michal - pass the actual network parameters where we figure out how - emptyMap()) + epoch) } \ No newline at end of file diff --git a/node-api/build.gradle b/node-api/build.gradle index c2a1231eb6..f59ddc8cf2 100644 --- a/node-api/build.gradle +++ b/node-api/build.gradle @@ -45,12 +45,6 @@ dependencies { compile "org.apache.curator:curator-recipes:${curator_version}" testCompile "org.apache.curator:curator-test:${curator_version}" - // FastClasspathScanner: classpath scanning - needed for the NetworkBootstraper - compile 'io.github.lukehutch:fast-classpath-scanner:2.0.21' - - // Pure-Java Snappy compression - compile 'org.iq80.snappy:snappy:0.4' - // Unit testing helpers. testCompile "junit:junit:$junit_version" testCompile "org.assertj:assertj-core:$assertj_version" diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoader.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoader.kt index a499bbfc1b..20d2efca63 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoader.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoader.kt @@ -1,9 +1,7 @@ package net.corda.nodeapi.internal import net.corda.core.contracts.Attachment -import net.corda.core.contracts.ContractAttachment import net.corda.core.crypto.SecureHash -import net.corda.core.internal.DEPLOYED_CORDAPP_UPLOADER import net.corda.core.serialization.CordaSerializable import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream @@ -33,10 +31,6 @@ class AttachmentsClassLoader(attachments: List, parent: ClassLoader } init { - require(attachments.mapNotNull { it as? ContractAttachment }.none { it.uploader != DEPLOYED_CORDAPP_UPLOADER }) { - "Attempting to load Contract Attachments downloaded from the network" - } - for (attachment in attachments) { attachment.openAsJAR().use { jar -> while (true) { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/ClassloaderUtils.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/ClassloaderUtils.kt deleted file mode 100644 index 774e30e2c0..0000000000 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/ClassloaderUtils.kt +++ /dev/null @@ -1,41 +0,0 @@ -package net.corda.nodeapi.internal - -import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner -import net.corda.core.contracts.Contract -import net.corda.core.contracts.ContractClassName -import net.corda.core.internal.copyTo -import net.corda.core.internal.deleteIfExists -import net.corda.core.internal.read -import java.io.File -import java.io.InputStream -import java.lang.reflect.Modifier -import java.net.URLClassLoader -import java.nio.file.Files -import java.nio.file.Paths -import java.nio.file.StandardCopyOption - -/** - * Scans the jar for contracts. - * @returns: found contract class names or null if none found - */ -fun scanJarForContracts(cordappJarPath: String): List { - val currentClassLoader = Contract::class.java.classLoader - val scanResult = FastClasspathScanner().addClassLoader(currentClassLoader).overrideClasspath(cordappJarPath, Paths.get(Contract::class.java.protectionDomain.codeSource.location.toURI()).toString()).scan() - val contracts = (scanResult.getNamesOfClassesImplementing(Contract::class.qualifiedName) ).distinct() - - // Only keep instantiable contracts - val classLoader = URLClassLoader(arrayOf(File(cordappJarPath).toURL()), currentClassLoader) - val concreteContracts = contracts.map(classLoader::loadClass).filter { !it.isInterface && !Modifier.isAbstract(it.modifiers) } - return concreteContracts.map { it.name } -} - -fun withContractsInJar(jarInputStream: InputStream, withContracts: (List, InputStream) -> T): T { - val tempFile = Files.createTempFile("attachment", ".jar") - try { - jarInputStream.copyTo(tempFile, StandardCopyOption.REPLACE_EXISTING) - val contracts = scanJarForContracts(tempFile.toAbsolutePath().toString()) - return tempFile.read { withContracts(contracts, it) } - } finally { - tempFile.deleteIfExists() - } -} \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/SignedNodeInfo.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/SignedNodeInfo.kt index 3dea456a05..d328674a69 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/SignedNodeInfo.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/SignedNodeInfo.kt @@ -15,6 +15,7 @@ import java.security.SignatureException * A signed [NodeInfo] object containing a signature for each identity. The list of signatures is expected * to be in the same order as the identities. */ +// TODO Move this to net.corda.nodeapi.internal.network // TODO Add signatures for composite keys. The current thinking is to make sure there is a signature for each leaf key // that the node owns. This check can only be done by the network map server as it can check with the doorman if a node // is part of a composite identity. This of course further requires the doorman being able to issue CSRs for composite diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt index cfd038a208..c8f90fcd74 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt @@ -1,33 +1,26 @@ package net.corda.nodeapi.internal.network -import com.google.common.hash.Hashing -import com.google.common.hash.HashingInputStream import com.typesafe.config.ConfigFactory import net.corda.cordform.CordformNode -import net.corda.core.crypto.SecureHash -import net.corda.core.crypto.SecureHash.Companion.parse import net.corda.core.identity.Party import net.corda.core.internal.* import net.corda.core.internal.concurrent.fork import net.corda.core.node.NetworkParameters import net.corda.core.node.NodeInfo import net.corda.core.node.NotaryInfo -import net.corda.core.node.services.AttachmentId import net.corda.core.serialization.SerializationContext +import net.corda.core.serialization.deserialize +import net.corda.nodeapi.internal.serialization.CordaSerializationMagic import net.corda.core.serialization.internal.SerializationEnvironmentImpl import net.corda.core.serialization.internal._contextSerializationEnv import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.seconds import net.corda.nodeapi.internal.SignedNodeInfo -import net.corda.nodeapi.internal.scanJarForContracts import net.corda.nodeapi.internal.serialization.AMQP_P2P_CONTEXT -import net.corda.nodeapi.internal.serialization.CordaSerializationMagic import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme import net.corda.nodeapi.internal.serialization.kryo.AbstractKryoSerializationScheme import net.corda.nodeapi.internal.serialization.kryo.kryoMagic -import java.io.File -import java.io.PrintStream import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths @@ -51,17 +44,15 @@ class NetworkBootstrapper { ) private const val LOGS_DIR_NAME = "logs" - private const val WHITELIST_FILE_NAME = "whitelist.txt" @JvmStatic fun main(args: Array) { - val baseNodeDirectory = args.firstOrNull() ?: throw IllegalArgumentException("Expecting first argument which is the nodes' parent directory") - val cordapps = if (args.size > 1) args.toList().drop(1) else null - NetworkBootstrapper().bootstrap(Paths.get(baseNodeDirectory).toAbsolutePath().normalize(), cordapps) + val arg = args.singleOrNull() ?: throw IllegalArgumentException("Expecting single argument which is the nodes' parent directory") + NetworkBootstrapper().bootstrap(Paths.get(arg).toAbsolutePath().normalize()) } } - fun bootstrap(directory: Path, cordapps: List?) { + fun bootstrap(directory: Path) { directory.createDirectories() println("Bootstrapping local network in $directory") generateDirectoriesIfNeeded(directory) @@ -78,10 +69,7 @@ class NetworkBootstrapper { println("Gathering notary identities") val notaryInfos = gatherNotaryInfos(nodeInfoFiles) println("Notary identities to be used in network-parameters file: ${notaryInfos.joinToString("; ") { it.prettyPrint() }}") - val mergedWhiteList = generateWhitelist(directory / WHITELIST_FILE_NAME, cordapps?.distinct()) - println("Updating whitelist.") - overwriteWhitelist(directory / WHITELIST_FILE_NAME, mergedWhiteList) - installNetworkParameters(notaryInfos, nodeDirs, mergedWhiteList) + installNetworkParameters(notaryInfos, nodeDirs) println("Bootstrapping complete!") } finally { _contextSerializationEnv.set(null) @@ -97,7 +85,8 @@ class NetworkBootstrapper { for (confFile in confFiles) { val nodeName = confFile.fileName.toString().removeSuffix(".conf") println("Generating directory for $nodeName") - val nodeDir = (directory / nodeName).createDirectories() + val nodeDir = (directory / nodeName) + if (!nodeDir.exists()) { nodeDir.createDirectory() } confFile.moveTo(nodeDir / "node.conf", StandardCopyOption.REPLACE_EXISTING) Files.copy(cordaJar, (nodeDir / "corda.jar"), StandardCopyOption.REPLACE_EXISTING) } @@ -161,7 +150,7 @@ class NetworkBootstrapper { if (nodeConfig.hasPath("notary")) { val validating = nodeConfig.getConfig("notary").getBoolean("validating") // And the node-info file contains the notary's identity - val nodeInfo = nodeInfoFile.readObject().verified() + val nodeInfo = nodeInfoFile.readAll().deserialize().verified() NotaryInfo(nodeInfo.notaryIdentity(), validating) } else { null @@ -169,7 +158,7 @@ class NetworkBootstrapper { }.distinct() // We need distinct as nodes part of a distributed notary share the same notary identity } - private fun installNetworkParameters(notaryInfos: List, nodeDirs: List, whitelist: Map>) { + private fun installNetworkParameters(notaryInfos: List, nodeDirs: List) { // TODO Add config for minimumPlatformVersion, maxMessageSize and maxTransactionSize val copier = NetworkParametersCopier(NetworkParameters( minimumPlatformVersion = 1, @@ -177,58 +166,12 @@ class NetworkBootstrapper { modifiedTime = Instant.now(), maxMessageSize = 10485760, maxTransactionSize = Int.MAX_VALUE, - epoch = 1, - whitelistedContractImplementations = whitelist + epoch = 1 ), overwriteFile = true) nodeDirs.forEach { copier.install(it) } } - private fun generateWhitelist(whitelistFile: Path, cordapps: List?): Map> { - val existingWhitelist = if (whitelistFile.exists()) readContractWhitelist(whitelistFile) else emptyMap() - - println("Found existing whitelist: $existingWhitelist") - - val newWhiteList = cordapps?.flatMap { cordappJarPath -> - val jarHash = getJarHash(cordappJarPath) - scanJarForContracts(cordappJarPath).map { contract -> - contract to jarHash - } - }?.toMap() ?: emptyMap() - - println("Calculating whitelist for current cordapps: $newWhiteList") - - val merged = (newWhiteList.keys + existingWhitelist.keys).map { contractClassName -> - val existing = existingWhitelist[contractClassName] ?: emptyList() - val newHash = newWhiteList[contractClassName] - contractClassName to (if (newHash == null || newHash in existing) existing else existing + newHash) - }.toMap() - - println("Final whitelist: $merged") - - return merged - } - - private fun overwriteWhitelist(whitelistFile: Path, mergedWhiteList: Map>) { - PrintStream(whitelistFile.toFile().outputStream()).use { out -> - mergedWhiteList.forEach { (contract, attachments )-> - out.println("${contract}:${attachments.joinToString(",")}") - } - } - } - - private fun getJarHash(cordappPath: String): AttachmentId = File(cordappPath).inputStream().use { jar -> - val hs = HashingInputStream(Hashing.sha256(), jar) - hs.readBytes() - SecureHash.SHA256(hs.hash().asBytes()) - } - - private fun readContractWhitelist(file: Path): Map> = file.toFile().readLines() - .map { line -> line.split(":") } - .map { (contract, attachmentIds) -> - contract to (attachmentIds.split(",").map(::parse)) - }.toMap() - private fun NotaryInfo.prettyPrint(): String = "${identity.name} (${if (validating) "" else "non-"}validating)" private fun NodeInfo.notaryIdentity(): Party { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkMap.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkMap.kt index 31314ca833..52841d2946 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkMap.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkMap.kt @@ -14,9 +14,6 @@ import java.time.Instant const val NETWORK_PARAMS_FILE_NAME = "network-parameters" const val NETWORK_PARAMS_UPDATE_FILE_NAME = "network-parameters-update" -typealias SignedNetworkMap = SignedDataWithCert -typealias SignedNetworkParameters = SignedDataWithCert - /** * Data structure representing the network map available from the HTTP network map service as a serialised blob. * @property nodeInfoHashes list of network participant's [NodeInfo] hashes diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/ClientContexts.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/ClientContexts.kt index e4e2f53417..3c2eb8fa76 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/ClientContexts.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/ClientContexts.kt @@ -18,12 +18,10 @@ val KRYO_RPC_CLIENT_CONTEXT = SerializationContextImpl(kryoMagic, GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()), emptyMap(), true, - SerializationContext.UseCase.RPCClient, - null) + SerializationContext.UseCase.RPCClient) val AMQP_RPC_CLIENT_CONTEXT = SerializationContextImpl(amqpMagic, SerializationDefaults.javaClass.classLoader, GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()), emptyMap(), true, - SerializationContext.UseCase.RPCClient, - null) + SerializationContext.UseCase.RPCClient) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/OrdinalIO.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/OrdinalIO.kt deleted file mode 100644 index 6e04d490f4..0000000000 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/OrdinalIO.kt +++ /dev/null @@ -1,31 +0,0 @@ -package net.corda.nodeapi.internal.serialization - -import java.io.EOFException -import java.io.InputStream -import java.io.OutputStream -import java.nio.ByteBuffer - -class OrdinalBits(private val ordinal: Int) { - interface OrdinalWriter { - val bits: OrdinalBits - val encodedSize get() = 1 - fun writeTo(stream: OutputStream) = stream.write(bits.ordinal) - fun putTo(buffer: ByteBuffer) = buffer.put(bits.ordinal.toByte())!! - } - - init { - require(ordinal >= 0) { "The ordinal must be non-negative." } - require(ordinal < 128) { "Consider implementing a varint encoding." } - } -} - -class OrdinalReader(private val values: Array) { - private val enumName = values[0].javaClass.simpleName - private val range = 0 until values.size - fun readFrom(stream: InputStream): E { - val ordinal = stream.read() - if (ordinal == -1) throw EOFException("Expected a $enumName ordinal.") - if (ordinal !in range) throw NoSuchElementException("No $enumName with ordinal: $ordinal") - return values[ordinal] - } -} diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationFormat.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationFormat.kt index fefdfb930f..6414efbb17 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationFormat.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationFormat.kt @@ -1,17 +1,8 @@ package net.corda.nodeapi.internal.serialization -import net.corda.core.internal.VisibleForTesting -import net.corda.core.serialization.SerializationEncoding import net.corda.core.utilities.ByteSequence import net.corda.core.utilities.OpaqueBytes -import net.corda.nodeapi.internal.serialization.OrdinalBits.OrdinalWriter -import org.iq80.snappy.SnappyFramedInputStream -import org.iq80.snappy.SnappyFramedOutputStream -import java.io.OutputStream -import java.io.InputStream import java.nio.ByteBuffer -import java.util.zip.DeflaterOutputStream -import java.util.zip.InflaterInputStream class CordaSerializationMagic(bytes: ByteArray) : OpaqueBytes(bytes) { private val bufferView = slice() @@ -19,40 +10,3 @@ class CordaSerializationMagic(bytes: ByteArray) : OpaqueBytes(bytes) { return if (data.slice(end = size) == bufferView) data.slice(size) else null } } - -enum class SectionId : OrdinalWriter { - /** Serialization data follows, and then discard the rest of the stream (if any) as legacy data may have trailing garbage. */ - DATA_AND_STOP, - /** Identical behaviour to [DATA_AND_STOP], historically used for Kryo. Do not use in new code. */ - ALT_DATA_AND_STOP, - /** The ordinal of a [CordaSerializationEncoding] follows, which should be used to decode the remainder of the stream. */ - ENCODING; - - companion object { - val reader = OrdinalReader(values()) - } - - override val bits = OrdinalBits(ordinal) -} - -enum class CordaSerializationEncoding : SerializationEncoding, OrdinalWriter { - DEFLATE { - override fun wrap(stream: OutputStream) = DeflaterOutputStream(stream) - override fun wrap(stream: InputStream) = InflaterInputStream(stream) - }, - SNAPPY { - override fun wrap(stream: OutputStream) = SnappyFramedOutputStream(stream) - override fun wrap(stream: InputStream) = SnappyFramedInputStream(stream, false) - }; - - companion object { - val reader = OrdinalReader(values()) - } - - override val bits = OrdinalBits(ordinal) - abstract fun wrap(stream: OutputStream): OutputStream - abstract fun wrap(stream: InputStream): InputStream -} - -@VisibleForTesting -internal val encodingNotPermittedFormat = "Encoding not permitted: %s" 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 a093bc871b..95dcc9b603 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 @@ -18,18 +18,13 @@ import java.util.concurrent.ExecutionException val attachmentsClassLoaderEnabledPropertyName = "attachments.class.loader.enabled" -internal object NullEncodingWhitelist : EncodingWhitelist { - override fun acceptEncoding(encoding: SerializationEncoding) = false -} +data class SerializationContextImpl(override val preferredSerializationVersion: SerializationMagic, + override val deserializationClassLoader: ClassLoader, + override val whitelist: ClassWhitelist, + override val properties: Map, + override val objectReferencesEnabled: Boolean, + override val useCase: SerializationContext.UseCase) : SerializationContext { -data class SerializationContextImpl @JvmOverloads constructor(override val preferredSerializationVersion: SerializationMagic, - override val deserializationClassLoader: ClassLoader, - override val whitelist: ClassWhitelist, - override val properties: Map, - override val objectReferencesEnabled: Boolean, - override val useCase: SerializationContext.UseCase, - override val encoding: SerializationEncoding?, - override val encodingWhitelist: EncodingWhitelist = NullEncodingWhitelist) : SerializationContext { private val cache: Cache, AttachmentsClassLoader> = CacheBuilder.newBuilder().weakValues().maximumSize(1024).build() /** @@ -75,7 +70,6 @@ data class SerializationContextImpl @JvmOverloads constructor(override val prefe } override fun withPreferredSerializationVersion(magic: SerializationMagic) = copy(preferredSerializationVersion = magic) - override fun withEncoding(encoding: SerializationEncoding?) = copy(encoding = encoding) } open class SerializationFactoryImpl : SerializationFactory() { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/ServerContexts.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/ServerContexts.kt index cc8dcfa305..a2bfa64628 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/ServerContexts.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/ServerContexts.kt @@ -27,26 +27,22 @@ val KRYO_RPC_SERVER_CONTEXT = SerializationContextImpl(kryoMagic, GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()), emptyMap(), true, - SerializationContext.UseCase.RPCServer, - null) + SerializationContext.UseCase.RPCServer) val KRYO_STORAGE_CONTEXT = SerializationContextImpl(kryoMagic, SerializationDefaults.javaClass.classLoader, AllButBlacklisted, emptyMap(), true, - SerializationContext.UseCase.Storage, - null) + SerializationContext.UseCase.Storage) val AMQP_STORAGE_CONTEXT = SerializationContextImpl(amqpMagic, SerializationDefaults.javaClass.classLoader, AllButBlacklisted, emptyMap(), true, - SerializationContext.UseCase.Storage, - null) + SerializationContext.UseCase.Storage) val AMQP_RPC_SERVER_CONTEXT = SerializationContextImpl(amqpMagic, SerializationDefaults.javaClass.classLoader, GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()), emptyMap(), true, - SerializationContext.UseCase.RPCServer, - null) + SerializationContext.UseCase.RPCServer) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SharedContexts.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SharedContexts.kt index 9620b3c999..25e4e278a1 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SharedContexts.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SharedContexts.kt @@ -20,19 +20,18 @@ val KRYO_P2P_CONTEXT = SerializationContextImpl(kryoMagic, GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()), emptyMap(), true, - SerializationContext.UseCase.P2P, - null) + SerializationContext.UseCase.P2P) val KRYO_CHECKPOINT_CONTEXT = SerializationContextImpl(kryoMagic, SerializationDefaults.javaClass.classLoader, QuasarWhitelist, emptyMap(), true, - SerializationContext.UseCase.Checkpoint, - null) + SerializationContext.UseCase.Checkpoint) val AMQP_P2P_CONTEXT = SerializationContextImpl(amqpMagic, SerializationDefaults.javaClass.classLoader, GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()), emptyMap(), true, - SerializationContext.UseCase.P2P, - null) + SerializationContext.UseCase.P2P) + + diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPStreams.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPStreams.kt deleted file mode 100644 index f45ac6d864..0000000000 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPStreams.kt +++ /dev/null @@ -1,31 +0,0 @@ -package net.corda.nodeapi.internal.serialization.amqp - -import com.esotericsoftware.kryo.io.ByteBufferInputStream -import net.corda.nodeapi.internal.serialization.kryo.ByteBufferOutputStream -import net.corda.nodeapi.internal.serialization.kryo.serializeOutputStreamPool -import java.io.InputStream -import java.io.OutputStream -import java.nio.ByteBuffer - -fun InputStream.asByteBuffer(): ByteBuffer { - return if (this is ByteBufferInputStream) { - byteBuffer // BBIS has no other state, so this is perfectly safe. - } else { - ByteBuffer.wrap(serializeOutputStreamPool.run { - copyTo(it) - it.toByteArray() - }) - } -} - -fun OutputStream.alsoAsByteBuffer(remaining: Int, task: (ByteBuffer) -> T): T { - return if (this is ByteBufferOutputStream) { - alsoAsByteBuffer(remaining, task) - } else { - serializeOutputStreamPool.run { - val result = it.alsoAsByteBuffer(remaining, task) - it.copyTo(this) - result - } - } -} diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializationInput.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializationInput.kt index cdde047ef8..da555798e1 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializationInput.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializationInput.kt @@ -1,27 +1,18 @@ package net.corda.nodeapi.internal.serialization.amqp -import com.esotericsoftware.kryo.io.ByteBufferInputStream -import net.corda.core.internal.VisibleForTesting import net.corda.core.internal.getStackTraceAsString -import net.corda.core.serialization.EncodingWhitelist import net.corda.core.serialization.SerializedBytes import net.corda.core.utilities.ByteSequence -import net.corda.nodeapi.internal.serialization.CordaSerializationEncoding -import net.corda.nodeapi.internal.serialization.NullEncodingWhitelist -import net.corda.nodeapi.internal.serialization.SectionId -import net.corda.nodeapi.internal.serialization.encodingNotPermittedFormat import org.apache.qpid.proton.amqp.Binary import org.apache.qpid.proton.amqp.DescribedType import org.apache.qpid.proton.amqp.UnsignedByte import org.apache.qpid.proton.amqp.UnsignedInteger import org.apache.qpid.proton.codec.Data -import java.io.InputStream import java.io.NotSerializableException import java.lang.reflect.ParameterizedType import java.lang.reflect.Type import java.lang.reflect.TypeVariable import java.lang.reflect.WildcardType -import java.nio.ByteBuffer data class ObjectAndEnvelope(val obj: T, val envelope: Envelope) @@ -31,8 +22,7 @@ data class ObjectAndEnvelope(val obj: T, val envelope: Envelope) * @param serializerFactory This is the factory for [AMQPSerializer] instances and can be shared across multiple * instances and threads. */ -class DeserializationInput @JvmOverloads constructor(private val serializerFactory: SerializerFactory, - private val encodingWhitelist: EncodingWhitelist = NullEncodingWhitelist) { +class DeserializationInput(internal val serializerFactory: SerializerFactory) { private val objectHistory: MutableList = mutableListOf() internal companion object { @@ -57,28 +47,6 @@ class DeserializationInput @JvmOverloads constructor(private val serializerFacto } return size + BYTES_NEEDED_TO_PEEK } - - @VisibleForTesting - @Throws(NotSerializableException::class) - internal fun withDataBytes(byteSequence: ByteSequence, encodingWhitelist: EncodingWhitelist, task: (ByteBuffer) -> T): T { - // Check that the lead bytes match expected header - val amqpSequence = amqpMagic.consume(byteSequence) ?: throw NotSerializableException("Serialization header does not match.") - var stream: InputStream = ByteBufferInputStream(amqpSequence) - try { - while (true) { - when (SectionId.reader.readFrom(stream)) { - SectionId.ENCODING -> { - val encoding = CordaSerializationEncoding.reader.readFrom(stream) - encodingWhitelist.acceptEncoding(encoding) || throw NotSerializableException(encodingNotPermittedFormat.format(encoding)) - stream = encoding.wrap(stream) - } - SectionId.DATA_AND_STOP, SectionId.ALT_DATA_AND_STOP -> return task(stream.asByteBuffer()) - } - } - } finally { - stream.close() - } - } } @Throws(NotSerializableException::class) @@ -90,12 +58,12 @@ class DeserializationInput @JvmOverloads constructor(private val serializerFacto @Throws(NotSerializableException::class) internal fun getEnvelope(byteSequence: ByteSequence): Envelope { - return withDataBytes(byteSequence, encodingWhitelist) { dataBytes -> - val data = Data.Factory.create() - val expectedSize = dataBytes.remaining() - if (data.decode(dataBytes) != expectedSize.toLong()) throw NotSerializableException("Unexpected size of data") - Envelope.get(data) - } + // Check that the lead bytes match expected header + val dataBytes = amqpMagic.consume(byteSequence) ?: throw NotSerializableException("Serialization header does not match.") + val data = Data.Factory.create() + val expectedSize = dataBytes.remaining() + if (data.decode(dataBytes) != expectedSize.toLong()) throw NotSerializableException("Unexpected size of data") + return Envelope.get(data) } @Throws(NotSerializableException::class) 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 1318d066fe..285a1f5d54 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 @@ -12,7 +12,7 @@ import net.corda.nodeapi.internal.serialization.carpenter.Field as CarpenterFiel import net.corda.nodeapi.internal.serialization.carpenter.Schema as CarpenterSchema const val DESCRIPTOR_DOMAIN: String = "net.corda" -val amqpMagic = CordaSerializationMagic("corda".toByteArray() + byteArrayOf(1, 0)) +val amqpMagic = CordaSerializationMagic("corda".toByteArray() + byteArrayOf(1, 0, 0)) /** * This and the classes below are OO representations of the AMQP XML schema described in the specification. Their diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutput.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutput.kt index 1dcf750ef5..f6b9972ec7 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutput.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutput.kt @@ -1,14 +1,10 @@ package net.corda.nodeapi.internal.serialization.amqp -import net.corda.core.serialization.SerializationEncoding import net.corda.core.serialization.SerializedBytes -import net.corda.nodeapi.internal.serialization.CordaSerializationEncoding -import net.corda.nodeapi.internal.serialization.SectionId -import net.corda.nodeapi.internal.serialization.kryo.byteArrayOutput import org.apache.qpid.proton.codec.Data import java.io.NotSerializableException -import java.io.OutputStream import java.lang.reflect.Type +import java.nio.ByteBuffer import java.util.* import kotlin.collections.LinkedHashSet @@ -23,7 +19,8 @@ data class BytesAndSchemas( * @param serializerFactory This is the factory for [AMQPSerializer] instances and can be shared across multiple * instances and threads. */ -open class SerializationOutput @JvmOverloads constructor(internal val serializerFactory: SerializerFactory, private val encoding: SerializationEncoding? = null) { +open class SerializationOutput(internal val serializerFactory: SerializerFactory) { + private val objectHistory: MutableMap = IdentityHashMap() private val serializerHistory: MutableSet> = LinkedHashSet() internal val schemaHistory: MutableSet = LinkedHashSet() @@ -70,21 +67,11 @@ open class SerializationOutput @JvmOverloads constructor(internal val serializer writeTransformSchema(TransformsSchema.build(schema, serializerFactory), this) } } - return SerializedBytes(byteArrayOutput { - var stream: OutputStream = it - try { - amqpMagic.writeTo(stream) - if (encoding != null) { - SectionId.ENCODING.writeTo(stream) - (encoding as CordaSerializationEncoding).writeTo(stream) - stream = encoding.wrap(stream) - } - SectionId.DATA_AND_STOP.writeTo(stream) - stream.alsoAsByteBuffer(data.encodedSize().toInt(), data::encode) - } finally { - stream.close() - } - }) + val bytes = ByteArray(data.encodedSize().toInt() + 8) + val buf = ByteBuffer.wrap(bytes) + amqpMagic.putTo(buf) + data.encode(buf) + return SerializedBytes(bytes) } internal fun writeObject(obj: Any, data: Data) { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/ContractAttachmentSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/ContractAttachmentSerializer.kt index 917057f428..2440114a4c 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/ContractAttachmentSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/ContractAttachmentSerializer.kt @@ -21,12 +21,12 @@ class ContractAttachmentSerializer(factory: SerializerFactory) : CustomSerialize } catch (e: Exception) { throw MissingAttachmentsException(listOf(obj.id)) } - return ContractAttachmentProxy(GeneratedAttachment(bytes), obj.contract, obj.additionalContracts, obj.uploader) + return ContractAttachmentProxy(GeneratedAttachment(bytes), obj.contract) } override fun fromProxy(proxy: ContractAttachmentProxy): ContractAttachment { - return ContractAttachment(proxy.attachment, proxy.contract, proxy.contracts, proxy.uploader) + return ContractAttachment(proxy.attachment, proxy.contract) } - data class ContractAttachmentProxy(val attachment: Attachment, val contract: ContractClassName, val contracts: Set, val uploader: String?) + data class ContractAttachmentProxy(val attachment: Attachment, val contract: ContractClassName) } \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/DefaultKryoCustomizer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/DefaultKryoCustomizer.kt index 63a08fd3c4..a8d2e48649 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/DefaultKryoCustomizer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/DefaultKryoCustomizer.kt @@ -12,7 +12,6 @@ import de.javakaffee.kryoserializers.BitSetSerializer import de.javakaffee.kryoserializers.UnmodifiableCollectionsSerializer import de.javakaffee.kryoserializers.guava.* import net.corda.core.contracts.ContractAttachment -import net.corda.core.contracts.ContractClassName import net.corda.core.contracts.PrivacySalt import net.corda.core.crypto.CompositeKey import net.corda.core.crypto.SecureHash @@ -207,34 +206,29 @@ object DefaultKryoCustomizer { output.writeBytesWithLength(buffer.toByteArray()) } output.writeString(obj.contract) - kryo.writeClassAndObject(output, obj.additionalContracts) - output.writeString(obj.uploader) } override fun read(kryo: Kryo, input: Input, type: Class): ContractAttachment { if (kryo.serializationContext() != null) { val attachmentHash = SecureHash.SHA256(input.readBytes(32)) val contract = input.readString() - val additionalContracts = kryo.readClassAndObject(input) as Set - val uploader = input.readString() + val context = kryo.serializationContext()!! val attachmentStorage = context.serviceHub.attachments val lazyAttachment = object : AbstractAttachment({ - val attachment = attachmentStorage.openAttachment(attachmentHash) - ?: throw MissingAttachmentsException(listOf(attachmentHash)) + val attachment = attachmentStorage.openAttachment(attachmentHash) ?: throw MissingAttachmentsException(listOf(attachmentHash)) attachment.open().readBytes() }) { override val id = attachmentHash } - return ContractAttachment(lazyAttachment, contract, additionalContracts, uploader) + return ContractAttachment(lazyAttachment, contract) } else { val attachment = GeneratedAttachment(input.readBytesWithLength()) val contract = input.readString() - val additionalContracts = kryo.readClassAndObject(input) as Set - val uploader = input.readString() - return ContractAttachment(attachment, contract, additionalContracts, uploader) + + return ContractAttachment(attachment, contract) } } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoSerializationScheme.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoSerializationScheme.kt index 7e1b94fffd..719d982db3 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoSerializationScheme.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoSerializationScheme.kt @@ -16,12 +16,10 @@ import net.corda.core.utilities.ByteSequence import net.corda.core.serialization.* import net.corda.nodeapi.internal.serialization.CordaSerializationMagic import net.corda.nodeapi.internal.serialization.CordaClassResolver -import net.corda.nodeapi.internal.serialization.SectionId import net.corda.nodeapi.internal.serialization.SerializationScheme -import net.corda.nodeapi.internal.serialization.* import java.security.PublicKey -val kryoMagic = CordaSerializationMagic("corda".toByteArray() + byteArrayOf(0, 0)) +val kryoMagic = CordaSerializationMagic("corda".toByteArray() + byteArrayOf(0, 0, 1)) private object AutoCloseableSerialisationDetector : Serializer() { override fun write(kryo: Kryo, output: Output, closeable: AutoCloseable) { @@ -89,25 +87,11 @@ abstract class AbstractKryoSerializationScheme : SerializationScheme { val dataBytes = kryoMagic.consume(byteSequence) ?: throw KryoException("Serialized bytes header does not match expected format.") return context.kryo { kryoInput(ByteBufferInputStream(dataBytes)) { - val result: T - loop@ while (true) { - when (SectionId.reader.readFrom(this)) { - SectionId.ENCODING -> { - val encoding = CordaSerializationEncoding.reader.readFrom(this) - context.encodingWhitelist.acceptEncoding(encoding) || throw KryoException(encodingNotPermittedFormat.format(encoding)) - substitute(encoding::wrap) - } - SectionId.DATA_AND_STOP, SectionId.ALT_DATA_AND_STOP -> { - result = if (context.objectReferencesEnabled) { - uncheckedCast(readClassAndObject(this)) - } else { - withoutReferences { uncheckedCast(readClassAndObject(this)) } - } - break@loop - } - } + if (context.objectReferencesEnabled) { + uncheckedCast(readClassAndObject(this)) + } else { + withoutReferences { uncheckedCast(readClassAndObject(this)) } } - result } } } @@ -116,12 +100,6 @@ abstract class AbstractKryoSerializationScheme : SerializationScheme { return context.kryo { SerializedBytes(kryoOutput { kryoMagic.writeTo(this) - context.encoding?.let { encoding -> - SectionId.ENCODING.writeTo(this) - (encoding as CordaSerializationEncoding).writeTo(this) - substitute(encoding::wrap) - } - SectionId.ALT_DATA_AND_STOP.writeTo(this) // Forward-compatible in null-encoding case. if (context.objectReferencesEnabled) { writeClassAndObject(this, obj) } else { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoStreams.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoStreams.kt index b1274223cc..9a34131a30 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoStreams.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoStreams.kt @@ -4,34 +4,13 @@ import com.esotericsoftware.kryo.io.Input import com.esotericsoftware.kryo.io.Output import net.corda.core.internal.LazyPool import java.io.* -import java.nio.ByteBuffer - -class ByteBufferOutputStream(size: Int) : ByteArrayOutputStream(size) { - companion object { - private val ensureCapacity = ByteArrayOutputStream::class.java.getDeclaredMethod("ensureCapacity", Int::class.java).apply { - isAccessible = true - } - } - - fun alsoAsByteBuffer(remaining: Int, task: (ByteBuffer) -> T): T { - ensureCapacity.invoke(this, count + remaining) - val buffer = ByteBuffer.wrap(buf, count, remaining) - val result = task(buffer) - count = buffer.position() - return result - } - - fun copyTo(stream: OutputStream) { - stream.write(buf, 0, count) - } -} private val serializationBufferPool = LazyPool( newInstance = { ByteArray(64 * 1024) }) -internal val serializeOutputStreamPool = LazyPool( - clear = ByteBufferOutputStream::reset, +private val serializeOutputStreamPool = LazyPool( + clear = ByteArrayOutputStream::reset, shouldReturnToPool = { it.size() < 256 * 1024 }, // Discard if it grew too large - newInstance = { ByteBufferOutputStream(64 * 1024) }) + newInstance = { ByteArrayOutputStream(64 * 1024) }) internal fun kryoInput(underlying: InputStream, task: Input.() -> T): T { return serializationBufferPool.run { @@ -43,19 +22,13 @@ internal fun kryoInput(underlying: InputStream, task: Input.() -> T): T { } internal fun kryoOutput(task: Output.() -> T): ByteArray { - return byteArrayOutput { underlying -> + return serializeOutputStreamPool.run { underlying -> serializationBufferPool.run { Output(it).use { output -> output.outputStream = underlying output.task() } } - } -} - -internal fun byteArrayOutput(task: (ByteBufferOutputStream) -> T): ByteArray { - return serializeOutputStreamPool.run { underlying -> - task(underlying) underlying.toByteArray() // Must happen after close, to allow ZIP footer to be written for example. } } diff --git a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/ForbiddenLambdaSerializationTests.java b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/ForbiddenLambdaSerializationTests.java index 123bf60e0f..265cbf098b 100644 --- a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/ForbiddenLambdaSerializationTests.java +++ b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/ForbiddenLambdaSerializationTests.java @@ -33,7 +33,7 @@ public final class ForbiddenLambdaSerializationTests { EnumSet contexts = EnumSet.complementOf(EnumSet.of(SerializationContext.UseCase.Checkpoint)); contexts.forEach(ctx -> { - SerializationContext context = new SerializationContextImpl(KryoSerializationSchemeKt.getKryoMagic(), this.getClass().getClassLoader(), AllWhitelist.INSTANCE, Maps.newHashMap(), true, ctx, null); + SerializationContext context = new SerializationContextImpl(KryoSerializationSchemeKt.getKryoMagic(), this.getClass().getClassLoader(), AllWhitelist.INSTANCE, Maps.newHashMap(), true, ctx); String value = "Hey"; Callable target = (Callable & Serializable) () -> value; @@ -55,7 +55,7 @@ public final class ForbiddenLambdaSerializationTests { EnumSet contexts = EnumSet.complementOf(EnumSet.of(SerializationContext.UseCase.Checkpoint)); contexts.forEach(ctx -> { - SerializationContext context = new SerializationContextImpl(KryoSerializationSchemeKt.getKryoMagic(), this.getClass().getClassLoader(), AllWhitelist.INSTANCE, Maps.newHashMap(), true, ctx, null); + SerializationContext context = new SerializationContextImpl(KryoSerializationSchemeKt.getKryoMagic(), this.getClass().getClassLoader(), AllWhitelist.INSTANCE, Maps.newHashMap(), true, ctx); String value = "Hey"; Callable target = () -> value; diff --git a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/LambdaCheckpointSerializationTest.java b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/LambdaCheckpointSerializationTest.java index 6482240ba9..0203c498f4 100644 --- a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/LambdaCheckpointSerializationTest.java +++ b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/LambdaCheckpointSerializationTest.java @@ -26,7 +26,7 @@ public final class LambdaCheckpointSerializationTest { @Before public void setup() { factory = testSerialization.getSerializationFactory(); - context = new SerializationContextImpl(KryoSerializationSchemeKt.getKryoMagic(), this.getClass().getClassLoader(), AllWhitelist.INSTANCE, Maps.newHashMap(), true, SerializationContext.UseCase.Checkpoint, null); + context = new SerializationContextImpl(KryoSerializationSchemeKt.getKryoMagic(), this.getClass().getClassLoader(), AllWhitelist.INSTANCE, Maps.newHashMap(), true, SerializationContext.UseCase.Checkpoint); } @Test diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderStaticContractTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderStaticContractTests.kt index 19c07d1efd..c264264bc6 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderStaticContractTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderStaticContractTests.kt @@ -13,7 +13,6 @@ import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.node.internal.cordapp.CordappLoader import net.corda.node.internal.cordapp.CordappProviderImpl -import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.core.DUMMY_NOTARY_NAME import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.TestIdentity @@ -60,8 +59,7 @@ class AttachmentsClassLoaderStaticContractTests { } private val serviceHub = rigorousMock().also { - doReturn(CordappProviderImpl(CordappLoader.createWithTestPackages(listOf("net.corda.nodeapi.internal")), MockCordappConfigProvider(), MockAttachmentStorage(), testNetworkParameters().whitelistedContractImplementations)).whenever(it).cordappProvider - doReturn(testNetworkParameters()).whenever(it).networkParameters + doReturn(CordappProviderImpl(CordappLoader.createWithTestPackages(listOf("net.corda.nodeapi.internal")), MockCordappConfigProvider(), MockAttachmentStorage())).whenever(it).cordappProvider } @Test 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 eeafa28630..5ee6355980 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 @@ -18,7 +18,6 @@ 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.common.internal.testNetworkParameters import net.corda.testing.core.DUMMY_NOTARY_NAME import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.TestIdentity @@ -59,15 +58,12 @@ class AttachmentsClassLoaderTests { @JvmField val testSerialization = SerializationEnvironmentRule() private val attachments = MockAttachmentStorage() - private val networkParameters = testNetworkParameters() - private val cordappProvider = CordappProviderImpl(CordappLoader.createDevMode(listOf(ISOLATED_CONTRACTS_JAR_PATH)), MockCordappConfigProvider(), attachments, networkParameters.whitelistedContractImplementations) + private val cordappProvider = CordappProviderImpl(CordappLoader.createDevMode(listOf(ISOLATED_CONTRACTS_JAR_PATH)), MockCordappConfigProvider(), attachments) private val cordapp get() = cordappProvider.cordapps.first() private val attachmentId get() = cordappProvider.getCordappAttachmentId(cordapp)!! private val appContext get() = cordappProvider.getAppContext(cordapp) private val serviceHub = rigorousMock().also { doReturn(attachments).whenever(it).attachments - doReturn(cordappProvider).whenever(it).cordappProvider - doReturn(networkParameters).whenever(it).networkParameters } // These ClassLoaders work together to load 'AnotherDummyContract' in a disposable way, such that even though @@ -283,7 +279,7 @@ class AttachmentsClassLoaderTests { .withClassLoader(child) val bytes = run { - val wireTransaction = tx.toWireTransaction(serviceHub, context) + val wireTransaction = tx.toWireTransaction(cordappProvider, context) wireTransaction.serialize(context = context) } val copiedWireTransaction = bytes.deserialize(context = context) @@ -307,7 +303,7 @@ class AttachmentsClassLoaderTests { val outboundContext = SerializationFactory.defaultFactory.defaultContext .withServiceHub(serviceHub) .withClassLoader(child) - val wireTransaction = tx.toWireTransaction(serviceHub, outboundContext) + val wireTransaction = tx.toWireTransaction(cordappProvider, outboundContext) wireTransaction.serialize(context = outboundContext) } // use empty attachmentStorage diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt index 8e5aa1c776..2f3e7420fd 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt @@ -319,8 +319,7 @@ class X509UtilitiesTest { AllWhitelist, emptyMap(), true, - SerializationContext.UseCase.P2P, - null) + SerializationContext.UseCase.P2P) val expected = X509Utilities.createSelfSignedCACertificate(ALICE.name.x500Principal, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)) val serialized = expected.serialize(factory, context).bytes val actual = serialized.deserialize(factory, context) @@ -335,8 +334,7 @@ class X509UtilitiesTest { AllWhitelist, emptyMap(), true, - SerializationContext.UseCase.P2P, - null) + SerializationContext.UseCase.P2P) val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val rootCACert = X509Utilities.createSelfSignedCACertificate(ALICE_NAME.x500Principal, rootCAKey) val certificate = X509Utilities.createCertificate(CertificateType.TLS, rootCACert, rootCAKey, BOB_NAME.x500Principal, BOB.publicKey) diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/ContractAttachmentSerializerTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/ContractAttachmentSerializerTest.kt index 3745da55d8..01e296324d 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/ContractAttachmentSerializerTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/ContractAttachmentSerializerTest.kt @@ -43,7 +43,6 @@ class ContractAttachmentSerializerTest { assertEquals(contractAttachment.id, deserialized.attachment.id) assertEquals(contractAttachment.contract, deserialized.contract) - assertEquals(contractAttachment.additionalContracts, deserialized.additionalContracts) assertArrayEquals(contractAttachment.open().readBytes(), deserialized.open().readBytes()) } @@ -59,7 +58,6 @@ class ContractAttachmentSerializerTest { assertEquals(contractAttachment.id, deserialized.attachment.id) assertEquals(contractAttachment.contract, deserialized.contract) - assertEquals(contractAttachment.additionalContracts, deserialized.additionalContracts) assertArrayEquals(contractAttachment.open().readBytes(), deserialized.open().readBytes()) } 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 90523d6f0e..a66f3038b4 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 @@ -6,7 +6,6 @@ import com.esotericsoftware.kryo.io.Output import com.esotericsoftware.kryo.util.DefaultClassResolver import com.esotericsoftware.kryo.util.MapReferenceResolver import com.nhaarman.mockito_kotlin.* -import net.corda.core.internal.DEPLOYED_CORDAPP_UPLOADER import net.corda.core.node.services.AttachmentStorage import net.corda.core.serialization.* import net.corda.nodeapi.internal.AttachmentsClassLoader @@ -109,8 +108,8 @@ class CordaClassResolverTests { val emptyMapClass = mapOf().javaClass } - private val emptyWhitelistContext: SerializationContext = SerializationContextImpl(kryoMagic, this.javaClass.classLoader, EmptyWhitelist, emptyMap(), true, SerializationContext.UseCase.P2P, null) - private val allButBlacklistedContext: SerializationContext = SerializationContextImpl(kryoMagic, this.javaClass.classLoader, AllButBlacklisted, emptyMap(), true, SerializationContext.UseCase.P2P, null) + private val emptyWhitelistContext: SerializationContext = SerializationContextImpl(kryoMagic, this.javaClass.classLoader, EmptyWhitelist, emptyMap(), true, SerializationContext.UseCase.P2P) + private val allButBlacklistedContext: SerializationContext = SerializationContextImpl(kryoMagic, this.javaClass.classLoader, AllButBlacklisted, emptyMap(), true, SerializationContext.UseCase.P2P) @Test fun `Annotation on enum works for specialised entries`() { CordaClassResolver(emptyWhitelistContext).getRegistration(Foo.Bar::class.java) @@ -196,7 +195,7 @@ class CordaClassResolverTests { CordaClassResolver(emptyWhitelistContext).getRegistration(DefaultSerializable::class.java) } - private fun importJar(storage: AttachmentStorage, uploader: String = DEPLOYED_CORDAPP_UPLOADER) = AttachmentsClassLoaderTests.ISOLATED_CONTRACTS_JAR_PATH.openStream().use { storage.importAttachment(it, uploader, "") } + private fun importJar(storage: AttachmentStorage) = AttachmentsClassLoaderTests.ISOLATED_CONTRACTS_JAR_PATH.openStream().use { storage.importAttachment(it) } @Test(expected = KryoException::class) fun `Annotation does not work in conjunction with AttachmentClassLoader annotation`() { @@ -207,15 +206,6 @@ class CordaClassResolverTests { CordaClassResolver(emptyWhitelistContext).getRegistration(attachedClass) } - @Test(expected = IllegalArgumentException::class) - fun `Attempt to load contract attachment with the incorrect uploader should fails with IAE`() { - val storage = MockAttachmentStorage() - val attachmentHash = importJar(storage, "some_uploader") - val classLoader = AttachmentsClassLoader(arrayOf(attachmentHash).map { storage.openAttachment(it)!! }) - val attachedClass = Class.forName("net.corda.finance.contracts.isolated.AnotherDummyContract", true, classLoader) - CordaClassResolver(emptyWhitelistContext).getRegistration(attachedClass) - } - @Test fun `Annotation is inherited from interfaces`() { CordaClassResolver(emptyWhitelistContext).getRegistration(SerializableViaInterface::class.java) diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/KryoTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/KryoTests.kt index 7150e8c566..5d54a38fbe 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/KryoTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/KryoTests.kt @@ -1,13 +1,10 @@ package net.corda.nodeapi.internal.serialization import com.esotericsoftware.kryo.Kryo -import com.esotericsoftware.kryo.KryoException import com.esotericsoftware.kryo.KryoSerializable import com.esotericsoftware.kryo.io.Input import com.esotericsoftware.kryo.io.Output import com.google.common.primitives.Ints -import com.nhaarman.mockito_kotlin.doReturn -import com.nhaarman.mockito_kotlin.whenever import net.corda.core.contracts.PrivacySalt import net.corda.core.crypto.* import net.corda.core.internal.FetchDataFlow @@ -19,29 +16,24 @@ import net.corda.node.services.persistence.NodeAttachmentService import net.corda.nodeapi.internal.serialization.kryo.kryoMagic import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.TestIdentity -import net.corda.testing.internal.rigorousMock -import org.assertj.core.api.Assertions.* +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Assert.assertArrayEquals -import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.Parameterized -import org.junit.runners.Parameterized.Parameters import org.slf4j.LoggerFactory import java.io.ByteArrayInputStream import java.io.InputStream import java.time.Instant import java.util.* -import kotlin.test.* +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNotNull +import kotlin.test.assertTrue -@RunWith(Parameterized::class) -class KryoTests(private val compression: CordaSerializationEncoding?) { +class KryoTests { companion object { private val ALICE_PUBKEY = TestIdentity(ALICE_NAME, 70).publicKey - @Parameters(name = "{0}") - @JvmStatic - fun compression() = arrayOf(null) + CordaSerializationEncoding.values() } private lateinit var factory: SerializationFactory @@ -55,11 +47,7 @@ class KryoTests(private val compression: CordaSerializationEncoding?) { AllWhitelist, emptyMap(), true, - SerializationContext.UseCase.Storage, - compression, - rigorousMock().also { - if (compression != null) doReturn(true).whenever(it).acceptEncoding(compression) - }) + SerializationContext.UseCase.Storage) } @Test @@ -271,8 +259,7 @@ class KryoTests(private val compression: CordaSerializationEncoding?) { AllWhitelist, emptyMap(), true, - SerializationContext.UseCase.P2P, - null) + SerializationContext.UseCase.P2P) pt.serialize(factory, context) } @@ -313,24 +300,4 @@ class KryoTests(private val compression: CordaSerializationEncoding?) { val exception2 = exception.serialize(factory, context).deserialize(factory, context) assertEquals(randomHash, exception2.requested) } - - @Test - fun `compression has the desired effect`() { - compression ?: return - val data = ByteArray(12345).also { Random(0).nextBytes(it) }.let { it + it } - val compressed = data.serialize(factory, context) - assertEquals(.5, compressed.size.toDouble() / data.size, .03) - assertArrayEquals(data, compressed.deserialize(factory, context)) - } - - @Test - fun `a particular encoding can be banned for deserialization`() { - compression ?: return - doReturn(false).whenever(context.encodingWhitelist).acceptEncoding(compression) - val compressed = "whatever".serialize(factory, context) - catchThrowable { compressed.deserialize(factory, context) }.run { - assertSame(KryoException::class.java, javaClass) - assertEquals(encodingNotPermittedFormat.format(compression), message) - } - } } \ No newline at end of file diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/ListsSerializationTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/ListsSerializationTest.kt index 7e1ffac95a..dc53b8fc29 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/ListsSerializationTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/ListsSerializationTest.kt @@ -69,7 +69,6 @@ class ListsSerializationTest { val serializedForm = emptyList().serialize() val output = ByteArrayOutputStream().apply { kryoMagic.writeTo(this) - SectionId.ALT_DATA_AND_STOP.writeTo(this) write(DefaultClassResolver.NAME + 2) write(nameID) write(javaEmptyListClass.name.toAscii()) diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/MapsSerializationTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/MapsSerializationTest.kt index 8efb66fffd..a76bb8a52e 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/MapsSerializationTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/MapsSerializationTest.kt @@ -79,7 +79,6 @@ class MapsSerializationTest { val serializedForm = emptyMap().serialize() val output = ByteArrayOutputStream().apply { kryoMagic.writeTo(this) - SectionId.ALT_DATA_AND_STOP.writeTo(this) write(DefaultClassResolver.NAME + 2) write(nameID) write(javaEmptyMapClass.name.toAscii()) 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 06a0f86d35..17495cb360 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 @@ -99,7 +99,6 @@ class SerializationTokenTest { val stream = ByteArrayOutputStream() Output(stream).use { kryoMagic.writeTo(it) - SectionId.ALT_DATA_AND_STOP.writeTo(it) kryo.writeClass(it, SingletonSerializeAsToken::class.java) kryo.writeObject(it, emptyList()) } diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SetsSerializationTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SetsSerializationTest.kt index 7d4a352323..edd1eabf58 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SetsSerializationTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SetsSerializationTest.kt @@ -56,7 +56,6 @@ class SetsSerializationTest { val serializedForm = emptySet().serialize() val output = ByteArrayOutputStream().apply { kryoMagic.writeTo(this) - SectionId.ALT_DATA_AND_STOP.writeTo(this) write(DefaultClassResolver.NAME + 2) write(nameID) write(javaEmptySetClass.name.toAscii()) 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 6da1845180..a23188bd02 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 @@ -524,7 +524,7 @@ class EvolvabilityTests { val resource = "networkParams.." val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party val networkParameters = NetworkParameters( - 3, listOf(NotaryInfo(DUMMY_NOTARY, false)),1000, 1000, Instant.EPOCH, 1, emptyMap()) + 3, listOf(NotaryInfo(DUMMY_NOTARY, false)),1000, 1000, Instant.EPOCH, 1 ) val sf = testDefaultFactory() sf.register(net.corda.nodeapi.internal.serialization.amqp.custom.InstantSerializer(sf)) diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt index f8aa4fc768..89b2dcbad4 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt @@ -2,8 +2,6 @@ package net.corda.nodeapi.internal.serialization.amqp -import com.nhaarman.mockito_kotlin.doReturn -import com.nhaarman.mockito_kotlin.whenever import net.corda.client.rpc.RPCException import net.corda.core.CordaRuntimeException import net.corda.core.contracts.* @@ -13,16 +11,21 @@ import net.corda.core.flows.FlowException import net.corda.core.identity.AbstractParty import net.corda.core.identity.CordaX500Name import net.corda.core.internal.AbstractAttachment -import net.corda.core.serialization.* +import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.MissingAttachmentsException +import net.corda.core.serialization.SerializationFactory import net.corda.core.transactions.LedgerTransaction import net.corda.core.utilities.OpaqueBytes -import net.corda.nodeapi.internal.serialization.* +import net.corda.nodeapi.internal.serialization.AllWhitelist +import net.corda.nodeapi.internal.serialization.EmptyWhitelist +import net.corda.nodeapi.internal.serialization.GeneratedAttachment import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory.Companion.isPrimitive +import net.corda.nodeapi.internal.serialization.amqp.custom.BigDecimalSerializer +import net.corda.nodeapi.internal.serialization.amqp.custom.CurrencySerializer import net.corda.testing.contracts.DummyContract import net.corda.testing.core.BOB_NAME import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.TestIdentity -import net.corda.testing.internal.rigorousMock import org.apache.activemq.artemis.api.core.SimpleString import org.apache.qpid.proton.amqp.* import org.apache.qpid.proton.codec.DecoderImpl @@ -32,23 +35,22 @@ import org.junit.Assert.* import org.junit.Ignore import org.junit.Rule import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.Parameterized -import org.junit.runners.Parameterized.Parameters import java.io.ByteArrayInputStream import java.io.IOException import java.io.NotSerializableException +import java.lang.reflect.Type import java.math.BigDecimal +import java.nio.ByteBuffer import java.time.* import java.time.temporal.ChronoUnit import java.util.* +import java.util.concurrent.ConcurrentHashMap import kotlin.reflect.full.superclasses import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertTrue -@RunWith(Parameterized::class) -class SerializationOutputTests(private val compression: CordaSerializationEncoding?) { +class SerializationOutputTests { private companion object { val BOB_IDENTITY = TestIdentity(BOB_NAME, 80).identity val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB")) @@ -57,9 +59,6 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi val MEGA_CORP_PUBKEY get() = megaCorp.publicKey val MINI_CORP get() = miniCorp.party val MINI_CORP_PUBKEY get() = miniCorp.publicKey - @Parameters(name = "{0}") - @JvmStatic - fun compression() = arrayOf(null) + CordaSerializationEncoding.values() } @Rule @@ -174,20 +173,16 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi } } - private val encodingWhitelist = rigorousMock().also { - if (compression != null) doReturn(true).whenever(it).acceptEncoding(compression) - } - - private fun defaultFactory() = SerializerFactory( - AllWhitelist, ClassLoader.getSystemClassLoader(), - EvolutionSerializerGetterTesting()) - private inline fun serdes(obj: T, - factory: SerializerFactory = defaultFactory(), - freshDeserializationFactory: SerializerFactory = defaultFactory(), + factory: SerializerFactory = SerializerFactory( + AllWhitelist, ClassLoader.getSystemClassLoader(), + EvolutionSerializerGetterTesting()), + freshDeserializationFactory: SerializerFactory = SerializerFactory( + AllWhitelist, ClassLoader.getSystemClassLoader(), + EvolutionSerializerGetterTesting()), expectedEqual: Boolean = true, expectDeserializedEqual: Boolean = true): T { - val ser = SerializationOutput(factory, compression) + val ser = SerializationOutput(factory) val bytes = ser.serialize(obj) val decoder = DecoderImpl().apply { @@ -203,19 +198,18 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi this.register(TransformTypes.DESCRIPTOR, TransformTypes.Companion) } EncoderImpl(decoder) - DeserializationInput.withDataBytes(bytes, encodingWhitelist) { - decoder.setByteBuffer(it) - // Check that a vanilla AMQP decoder can deserialize without schema. - val result = decoder.readObject() as Envelope - assertNotNull(result) - } - val des = DeserializationInput(freshDeserializationFactory, encodingWhitelist) + decoder.setByteBuffer(ByteBuffer.wrap(bytes.bytes, 8, bytes.size - 8)) + // Check that a vanilla AMQP decoder can deserialize without schema. + val result = decoder.readObject() as Envelope + assertNotNull(result) + + val des = DeserializationInput(freshDeserializationFactory) val desObj = des.deserialize(bytes) assertTrue(Objects.deepEquals(obj, desObj) == expectedEqual) // Now repeat with a re-used factory - val ser2 = SerializationOutput(factory, compression) - val des2 = DeserializationInput(factory, encodingWhitelist) + val ser2 = SerializationOutput(factory) + val des2 = DeserializationInput(factory) val desObj2 = des2.deserialize(ser2.serialize(obj)) assertTrue(Objects.deepEquals(obj, desObj2) == expectedEqual) assertTrue(Objects.deepEquals(desObj, desObj2) == expectDeserializedEqual) @@ -438,9 +432,9 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi @Test fun `class constructor is invoked on deserialisation`() { - compression == null || return // Manipulation of serialized bytes is invalid if they're compressed. - val ser = SerializationOutput(SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()), compression) - val des = DeserializationInput(ser.serializerFactory, encodingWhitelist) + val ser = SerializationOutput(SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())) + val des = DeserializationInput(ser.serializerFactory) + val serialisedOne = ser.serialize(NonZeroByte(1)).bytes val serialisedTwo = ser.serialize(NonZeroByte(2)).bytes @@ -1071,7 +1065,6 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi val obj2 = serdes(obj, factory, factory2, expectedEqual = false, expectDeserializedEqual = false) assertEquals(obj.id, obj2.attachment.id) assertEquals(obj.contract, obj2.contract) - assertEquals(obj.additionalContracts, obj2.additionalContracts) assertArrayEquals(obj.open().readBytes(), obj2.open().readBytes()) } @@ -1123,29 +1116,6 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi val c = C(Amount(100, BigDecimal("1.5"), Currency.getInstance("USD"))) // were the issue not fixed we'd blow up here - SerializationOutput(factory, compression).serialize(c) - } - - @Test - fun `compression has the desired effect`() { - compression ?: return - val factory = defaultFactory() - val data = ByteArray(12345).also { Random(0).nextBytes(it) }.let { it + it } - val compressed = SerializationOutput(factory, compression).serialize(data) - assertEquals(.5, compressed.size.toDouble() / data.size, .03) - assertArrayEquals(data, DeserializationInput(factory, encodingWhitelist).deserialize(compressed)) - } - - @Test - fun `a particular encoding can be banned for deserialization`() { - compression ?: return - val factory = defaultFactory() - doReturn(false).whenever(encodingWhitelist).acceptEncoding(compression) - val compressed = SerializationOutput(factory, compression).serialize("whatever") - val input = DeserializationInput(factory, encodingWhitelist) - catchThrowable { input.deserialize(compressed) }.run { - assertSame(NotSerializableException::class.java, javaClass) - assertEquals(encodingNotPermittedFormat.format(compression), message) - } + SerializationOutput(factory).serialize(c) } } \ No newline at end of file diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoStreamsTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoStreamsTest.kt index d8eedd305d..ac9779f828 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoStreamsTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoStreamsTest.kt @@ -1,16 +1,12 @@ package net.corda.nodeapi.internal.serialization.kryo -import net.corda.core.internal.declaredField -import org.assertj.core.api.Assertions.catchThrowable import org.junit.Assert.assertArrayEquals import org.junit.Test import java.io.* -import java.nio.BufferOverflowException import java.util.* import java.util.zip.DeflaterOutputStream import java.util.zip.InflaterInputStream import kotlin.test.assertEquals -import kotlin.test.assertSame class KryoStreamsTest { class NegOutputStream(private val stream: OutputStream) : OutputStream() { @@ -61,37 +57,4 @@ class KryoStreamsTest { assertEquals(-1, read()) } } - - @Test - fun `ByteBufferOutputStream works`() { - val stream = ByteBufferOutputStream(3) - stream.write("abc".toByteArray()) - val getBuf = stream.declaredField(ByteArrayOutputStream::class, "buf")::value - assertEquals(3, getBuf().size) - repeat(2) { - assertSame(BufferOverflowException::class.java, catchThrowable { - stream.alsoAsByteBuffer(9) { - it.put("0123456789".toByteArray()) - } - }.javaClass) - assertEquals(3 + 9, getBuf().size) - } - // This time make too much space: - stream.alsoAsByteBuffer(11) { - it.put("0123456789".toByteArray()) - } - stream.write("def".toByteArray()) - assertArrayEquals("abc0123456789def".toByteArray(), stream.toByteArray()) - } - - @Test - fun `ByteBufferOutputStream discards data after final position`() { - val stream = ByteBufferOutputStream(0) - stream.alsoAsByteBuffer(10) { - it.put("0123456789".toByteArray()) - it.position(5) - } - stream.write("def".toByteArray()) - assertArrayEquals("01234def".toByteArray(), stream.toByteArray()) - } } diff --git a/node/build.gradle b/node/build.gradle index 2d4347d9bc..62ffb40f0a 100644 --- a/node/build.gradle +++ b/node/build.gradle @@ -170,6 +170,9 @@ dependencies { compile 'commons-codec:commons-codec:1.10' compile 'com.github.bft-smart:library:master-v1.1-beta-g6215ec8-87' + // FastClasspathScanner: classpath scanning + compile 'io.github.lukehutch:fast-classpath-scanner:2.0.21' + // Apache Shiro: authentication, authorization and session management. compile "org.apache.shiro:shiro-core:${shiro_version}" diff --git a/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt index 48fbaa1a75..2897d60049 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt @@ -45,7 +45,7 @@ class AttachmentLoadingTests : IntegrationTest() { @JvmField val testSerialization = SerializationEnvironmentRule() private val attachments = MockAttachmentStorage() - private val provider = CordappProviderImpl(CordappLoader.createDevMode(listOf(isolatedJAR)), MockCordappConfigProvider(), attachments, testNetworkParameters().whitelistedContractImplementations) + private val provider = CordappProviderImpl(CordappLoader.createDevMode(listOf(isolatedJAR)), MockCordappConfigProvider(), attachments) private val cordapp get() = provider.cordapps.first() private val attachmentId get() = provider.getCordappAttachmentId(cordapp)!! private val appContext get() = provider.getAppContext(cordapp) 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 4af4227678..35030d4884 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 @@ -57,7 +57,7 @@ class BFTNotaryServiceTests : IntegrationTest() { @Before fun before() { - mockNet = InternalMockNetwork(listOf("net.corda.testing.contracts")) + mockNet = InternalMockNetwork(emptyList()) } @After diff --git a/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt index 5a1cf8e40d..4f08aae773 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt @@ -2,17 +2,15 @@ package net.corda.node.services.network import net.corda.cordform.CordformNode import net.corda.core.crypto.random63BitValue +import net.corda.core.internal.* import net.corda.core.internal.concurrent.transpose -import net.corda.core.internal.div -import net.corda.core.internal.exists -import net.corda.core.internal.list -import net.corda.core.internal.readObject +import net.corda.core.node.NetworkParameters import net.corda.core.node.NodeInfo +import net.corda.core.serialization.deserialize import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.seconds import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME import net.corda.testing.common.internal.testNetworkParameters -import net.corda.nodeapi.internal.network.SignedNetworkParameters import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.BOB_NAME import net.corda.testing.core.DUMMY_NOTARY_NAME @@ -72,7 +70,8 @@ class NetworkMapTest : IntegrationTest() { ) { val alice = startNode(providedName = ALICE_NAME).getOrThrow() val networkParameters = (alice.baseDirectory / NETWORK_PARAMS_FILE_NAME) - .readObject() + .readAll() + .deserialize>() .verified() // We use a random modified time above to make the network parameters unqiue so that we're sure they came // from the server 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 383ef96db4..428b317ffa 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -553,7 +553,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, checkpointStorage = DBCheckpointStorage() val metrics = MetricRegistry() attachments = NodeAttachmentService(metrics, configuration.attachmentContentCacheSizeBytes, configuration.attachmentCacheBound) - val cordappProvider = CordappProviderImpl(cordappLoader, CordappConfigFileProvider(), attachments, networkParameters.whitelistedContractImplementations) + val cordappProvider = CordappProviderImpl(cordappLoader, CordappConfigFileProvider(), attachments) val keyManagementService = makeKeyManagementService(identityService, keyPairs) _services = ServiceHubInternalImpl( identityService, 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 91195d9bff..dd43721018 100644 --- a/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt +++ b/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt @@ -14,7 +14,6 @@ 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.internal.RPC_UPLOADER import net.corda.core.internal.sign import net.corda.core.messaging.* import net.corda.core.node.NodeInfo @@ -54,7 +53,7 @@ internal class CordaRPCOpsImpl( } override fun networkParametersFeed(): DataFeed { - return services.networkMapUpdater.trackParametersUpdate() + return services.networkMapUpdater.track() } override fun acceptNewNetworkParameters(parametersHash: SecureHash) { @@ -193,7 +192,7 @@ internal class CordaRPCOpsImpl( override fun uploadAttachment(jar: InputStream): SecureHash { // TODO: this operation should not require an explicit transaction return database.transaction { - services.attachments.importAttachment(jar, RPC_UPLOADER, null) + services.attachments.importAttachment(jar) } } diff --git a/node/src/main/kotlin/net/corda/node/internal/NetworkParametersReader.kt b/node/src/main/kotlin/net/corda/node/internal/NetworkParametersReader.kt index bc5073ff4e..f34263f707 100644 --- a/node/src/main/kotlin/net/corda/node/internal/NetworkParametersReader.kt +++ b/node/src/main/kotlin/net/corda/node/internal/NetworkParametersReader.kt @@ -3,12 +3,12 @@ package net.corda.node.internal import net.corda.core.crypto.SecureHash import net.corda.core.internal.* import net.corda.core.node.NetworkParameters +import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.utilities.contextLogger import net.corda.node.services.network.NetworkMapClient import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME import net.corda.nodeapi.internal.network.NETWORK_PARAMS_UPDATE_FILE_NAME -import net.corda.nodeapi.internal.network.SignedNetworkParameters import net.corda.nodeapi.internal.network.verifiedNetworkMapCert import java.nio.file.Path import java.nio.file.StandardCopyOption @@ -26,9 +26,9 @@ class NetworkParametersReader(private val trustRoot: X509Certificate, val networkParameters by lazy { retrieveNetworkParameters() } private fun retrieveNetworkParameters(): NetworkParameters { - val advertisedParametersHash = networkMapClient?.getNetworkMap()?.payload?.networkParameterHash + val advertisedParametersHash = networkMapClient?.getNetworkMap()?.networkMap?.networkParameterHash val signedParametersFromFile = if (networkParamsFile.exists()) { - networkParamsFile.readObject() + networkParamsFile.readAll().deserialize>() } else { null } @@ -51,13 +51,13 @@ class NetworkParametersReader(private val trustRoot: X509Certificate, return parameters } - private fun readParametersUpdate(advertisedParametersHash: SecureHash, previousParametersHash: SecureHash): SignedNetworkParameters { + private fun readParametersUpdate(advertisedParametersHash: SecureHash, previousParametersHash: SecureHash): SignedDataWithCert { if (!parametersUpdateFile.exists()) { throw IllegalArgumentException("Node uses parameters with hash: $previousParametersHash " + - "but network map is advertising: $advertisedParametersHash.\n" + + "but network map is advertising: ${advertisedParametersHash}.\n" + "Please update node to use correct network parameters file.") } - val signedUpdatedParameters = parametersUpdateFile.readObject() + val signedUpdatedParameters = parametersUpdateFile.readAll().deserialize>() if (signedUpdatedParameters.raw.hash != advertisedParametersHash) { throw IllegalArgumentException("Both network parameters and network parameters update files don't match" + "parameters advertised by network map.\n" + diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderImpl.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderImpl.kt index 06efde8cd7..a25d872124 100644 --- a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderImpl.kt +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderImpl.kt @@ -1,12 +1,10 @@ package net.corda.node.internal.cordapp import com.google.common.collect.HashBiMap -import net.corda.core.contracts.ContractAttachment import net.corda.core.contracts.ContractClassName import net.corda.core.cordapp.Cordapp import net.corda.core.cordapp.CordappContext import net.corda.core.crypto.SecureHash -import net.corda.core.internal.DEPLOYED_CORDAPP_UPLOADER import net.corda.core.internal.cordapp.CordappConfigProvider import net.corda.core.internal.createCordappContext import net.corda.core.node.services.AttachmentId @@ -19,10 +17,7 @@ import java.util.concurrent.ConcurrentHashMap /** * Cordapp provider and store. For querying CorDapps for their attachment and vice versa. */ -open class CordappProviderImpl(private val cordappLoader: CordappLoader, - private val cordappConfigProvider: CordappConfigProvider, - attachmentStorage: AttachmentStorage, - private val whitelistedContractImplementations: Map>) : SingletonSerializeAsToken(), CordappProviderInternal { +open class CordappProviderImpl(private val cordappLoader: CordappLoader, private val cordappConfigProvider: CordappConfigProvider, attachmentStorage: AttachmentStorage) : SingletonSerializeAsToken(), CordappProviderInternal { companion object { private val log = loggerFor() @@ -30,34 +25,6 @@ open class CordappProviderImpl(private val cordappLoader: CordappLoader, private val contextCache = ConcurrentHashMap() - /** - * Current known CorDapps loaded on this node - */ - override val cordapps get() = cordappLoader.cordapps - private val cordappAttachments = HashBiMap.create(loadContractsIntoAttachmentStore(attachmentStorage)) - - init { - verifyInstalledCordapps(attachmentStorage) - } - - private fun verifyInstalledCordapps(attachmentStorage: AttachmentStorage) { - - if (whitelistedContractImplementations.isEmpty()) { - log.warn("The network parameters don't specify any whitelisted contract implementations. Please contact your zone operator. See https://docs.corda.net/network-map.html") - return - } - - // Verify that the installed contract classes correspond with the whitelist hash - // And warn if node is not using latest CorDapp - cordappAttachments.keys.map(attachmentStorage::openAttachment).mapNotNull { it as? ContractAttachment }.forEach { attch -> - (attch.allContracts intersect whitelistedContractImplementations.keys).forEach { contractClassName -> - when { - attch.id !in whitelistedContractImplementations[contractClassName]!! -> log.error("Contract $contractClassName found in attachment ${attch.id} is not whitelisted in the network parameters. If this is a production node contact your zone operator. See https://docs.corda.net/network-map.html") - attch.id != whitelistedContractImplementations[contractClassName]!!.last() -> log.warn("You are not using the latest CorDapp version for contract: $contractClassName. Please contact your zone operator.") - } - } - } - } override fun getAppContext(): CordappContext { // TODO: Use better supported APIs in Java 9 @@ -75,6 +42,11 @@ open class CordappProviderImpl(private val cordappLoader: CordappLoader, return getCordappForClass(contractClassName)?.let(this::getCordappAttachmentId) } + /** + * Current known CorDapps loaded on this node + */ + override val cordapps get() = cordappLoader.cordapps + private val cordappAttachments = HashBiMap.create(loadContractsIntoAttachmentStore(attachmentStorage)) /** * Gets the attachment ID of this CorDapp. Only CorDapps with contracts have an attachment ID * @@ -83,16 +55,11 @@ open class CordappProviderImpl(private val cordappLoader: CordappLoader, */ fun getCordappAttachmentId(cordapp: Cordapp): SecureHash? = cordappAttachments.inverse().get(cordapp.jarPath) - private fun loadContractsIntoAttachmentStore(attachmentStorage: AttachmentStorage): Map = - cordapps.filter { !it.contractClassNames.isEmpty() }.map { - it.jarPath.openStream().use { stream -> - try { - attachmentStorage.importAttachment(stream, DEPLOYED_CORDAPP_UPLOADER, null) - } catch (faee: java.nio.file.FileAlreadyExistsException) { - AttachmentId.parse(faee.message!!) - } - } to it.jarPath - }.toMap() + private fun loadContractsIntoAttachmentStore(attachmentStorage: AttachmentStorage): Map { + val cordappsWithAttachments = cordapps.filter { !it.contractClassNames.isEmpty() }.map { it.jarPath } + val attachmentIds = cordappsWithAttachments.map { it.openStream().use { attachmentStorage.importOrGetAttachment(it) } } + return attachmentIds.zip(cordappsWithAttachments).toMap() + } /** * Get the current cordapp context for the given CorDapp 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 77e0618134..a7a4da24de 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 @@ -196,15 +196,15 @@ data class NodeConfigurationImpl( override fun validate(): List { val errors = mutableListOf() - errors += validateRpcOptions(rpcOptions) + errors + validateRpcOptions(rpcOptions) return errors } private fun validateRpcOptions(options: NodeRpcOptions): List { val errors = mutableListOf() - if (options.address != null) { - if (!options.useSsl && options.adminAddress == null) { - errors += "'rpcSettings.adminAddress': missing. Property is mandatory when 'rpcSettings.useSsl' is false (default)." + if (!options.useSsl) { + if (options.adminAddress == null) { + errors + "'rpcSettings.adminAddress': missing. Property is mandatory when 'rpcSettings.useSsl' is false (default)." } } return errors diff --git a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt index 617a03a39f..2b56d933c4 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt @@ -1,27 +1,43 @@ package net.corda.node.services.network +import com.google.common.util.concurrent.MoreExecutors import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SignedData import net.corda.core.internal.* +import net.corda.core.messaging.DataFeed +import net.corda.core.messaging.ParametersUpdateInfo +import net.corda.core.node.NetworkParameters import net.corda.core.node.NodeInfo import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.utilities.contextLogger +import net.corda.core.utilities.minutes import net.corda.core.utilities.seconds import net.corda.core.utilities.trace +import net.corda.node.services.api.NetworkMapCacheInternal +import net.corda.node.utilities.NamedThreadFactory import net.corda.node.utilities.registration.cacheControl import net.corda.nodeapi.internal.SignedNodeInfo -import net.corda.nodeapi.internal.network.* +import net.corda.nodeapi.internal.network.NETWORK_PARAMS_UPDATE_FILE_NAME +import net.corda.nodeapi.internal.network.NetworkMap +import net.corda.nodeapi.internal.network.ParametersUpdate +import net.corda.nodeapi.internal.network.verifiedNetworkMapCert +import rx.Subscription +import rx.subjects.PublishSubject import java.io.BufferedReader +import java.io.Closeable import java.net.URL +import java.nio.file.Path +import java.nio.file.StandardCopyOption import java.security.cert.X509Certificate import java.time.Duration +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit class NetworkMapClient(compatibilityZoneURL: URL, val trustedRoot: X509Certificate) { companion object { private val logger = contextLogger() } - private val networkMapUrl = URL("$compatibilityZoneURL/network-map") fun publish(signedNodeInfo: SignedNodeInfo) { @@ -41,13 +57,10 @@ class NetworkMapClient(compatibilityZoneURL: URL, val trustedRoot: X509Certifica fun getNetworkMap(): NetworkMapResponse { logger.trace { "Fetching network map update from $networkMapUrl." } val connection = networkMapUrl.openHttpConnection() - val signedNetworkMap = connection.responseAs() + val signedNetworkMap = connection.responseAs>() val networkMap = signedNetworkMap.verifiedNetworkMapCert(trustedRoot) val timeout = connection.cacheControl().maxAgeSeconds().seconds - logger.trace { - "Fetched network map update from $networkMapUrl successfully, retrieved ${networkMap.nodeInfoHashes.size} " + - "node info hashes. Node Info hashes:\n${networkMap.nodeInfoHashes.joinToString("\n")}" - } + logger.trace { "Fetched network map update from $networkMapUrl successfully, retrieved ${networkMap.nodeInfoHashes.size} node info hashes. Node Info hashes: ${networkMap.nodeInfoHashes.joinToString("\n")}" } return NetworkMapResponse(networkMap, timeout) } @@ -59,10 +72,10 @@ class NetworkMapClient(compatibilityZoneURL: URL, val trustedRoot: X509Certifica return verifiedNodeInfo } - fun getNetworkParameters(networkParameterHash: SecureHash): SignedNetworkParameters { + fun getNetworkParameters(networkParameterHash: SecureHash): SignedDataWithCert { val url = URL("$networkMapUrl/network-parameters/$networkParameterHash") logger.trace { "Fetching network parameters: '$networkParameterHash' from $url." } - val networkParameter = url.openHttpConnection().responseAs() + val networkParameter = url.openHttpConnection().responseAs>() logger.trace { "Fetched network parameters: '$networkParameterHash' successfully. Network Parameters: $networkParameter" } return networkParameter } @@ -76,4 +89,143 @@ class NetworkMapClient(compatibilityZoneURL: URL, val trustedRoot: X509Certifica } } -data class NetworkMapResponse(val payload: NetworkMap, val cacheMaxAge: Duration) +data class NetworkMapResponse(val networkMap: NetworkMap, val cacheMaxAge: Duration) + +class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal, + private val fileWatcher: NodeInfoWatcher, + private val networkMapClient: NetworkMapClient?, + private val currentParametersHash: SecureHash, + private val baseDirectory: Path) : Closeable { + companion object { + private val logger = contextLogger() + private val retryInterval = 1.minutes + } + + private var newNetworkParameters: Pair>? = null + + fun track(): DataFeed { + val currentUpdateInfo = newNetworkParameters?.let { + ParametersUpdateInfo(it.first.newParametersHash, it.second.verified(), it.first.description, it.first.updateDeadline) + } + return DataFeed( + currentUpdateInfo, + parametersUpdatesTrack + ) + } + + private val parametersUpdatesTrack: PublishSubject = PublishSubject.create() + private val executor = Executors.newSingleThreadScheduledExecutor(NamedThreadFactory("Network Map Updater Thread", Executors.defaultThreadFactory())) + private var fileWatcherSubscription: Subscription? = null + + override fun close() { + fileWatcherSubscription?.unsubscribe() + MoreExecutors.shutdownAndAwaitTermination(executor, 50, TimeUnit.SECONDS) + } + + fun updateNodeInfo(newInfo: NodeInfo, signNodeInfo: (NodeInfo) -> SignedNodeInfo) { + val oldInfo = networkMapCache.getNodeByLegalIdentity(newInfo.legalIdentities.first()) + // Compare node info without timestamp. + if (newInfo.copy(serial = 0L) == oldInfo?.copy(serial = 0L)) return + + // Only publish and write to disk if there are changes to the node info. + val signedNodeInfo = signNodeInfo(newInfo) + networkMapCache.addNode(newInfo) + fileWatcher.saveToFile(signedNodeInfo) + + if (networkMapClient != null) { + tryPublishNodeInfoAsync(signedNodeInfo, networkMapClient) + } + } + + fun subscribeToNetworkMap() { + require(fileWatcherSubscription == null) { "Should not call this method twice." } + // Subscribe to file based networkMap + fileWatcherSubscription = fileWatcher.nodeInfoUpdates().subscribe(networkMapCache::addNode) + + if (networkMapClient == null) return + // Subscribe to remote network map if configured. + val task = object : Runnable { + override fun run() { + val nextScheduleDelay = try { + val (networkMap, cacheTimeout) = networkMapClient.getNetworkMap() + networkMap.parametersUpdate?.let { handleUpdateNetworkParameters(it) } + if (currentParametersHash != networkMap.networkParameterHash) { + // TODO This needs special handling (node omitted update process/didn't accept new parameters or didn't restart on updateDeadline) + logger.error("Node is using parameters with hash: $currentParametersHash but network map is advertising: ${networkMap.networkParameterHash}.\n" + + "Please update node to use correct network parameters file.\"") + System.exit(1) + } + val currentNodeHashes = networkMapCache.allNodeHashes + val hashesFromNetworkMap = networkMap.nodeInfoHashes + (hashesFromNetworkMap - currentNodeHashes).mapNotNull { + // Download new node info from network map + try { + networkMapClient.getNodeInfo(it) + } catch (e: Exception) { + // Failure to retrieve one node info shouldn't stop the whole update, log and return null instead. + logger.warn("Error encountered when downloading node info '$it', skipping...", e) + null + } + }.forEach { + // Add new node info to the network map cache, these could be new node info or modification of node info for existing nodes. + networkMapCache.addNode(it) + } + // Remove node info from network map. + (currentNodeHashes - hashesFromNetworkMap - fileWatcher.processedNodeInfoHashes) + .mapNotNull(networkMapCache::getNodeByHash) + .forEach(networkMapCache::removeNode) + cacheTimeout + } catch (t: Throwable) { + logger.warn("Error encountered while updating network map, will retry in ${retryInterval.seconds} seconds", t) + retryInterval + } + // Schedule the next update. + executor.schedule(this, nextScheduleDelay.toMillis(), TimeUnit.MILLISECONDS) + } + } + executor.submit(task) // The check may be expensive, so always run it in the background even the first time. + } + + private fun tryPublishNodeInfoAsync(signedNodeInfo: SignedNodeInfo, networkMapClient: NetworkMapClient) { + val task = object : Runnable { + override fun run() { + try { + networkMapClient.publish(signedNodeInfo) + } catch (t: Throwable) { + logger.warn("Error encountered while publishing node info, will retry in ${retryInterval.seconds} seconds.", t) + // TODO: Exponential backoff? + executor.schedule(this, retryInterval.toMillis(), TimeUnit.MILLISECONDS) + } + } + } + executor.submit(task) + } + + private fun handleUpdateNetworkParameters(update: ParametersUpdate) { + if (update.newParametersHash == newNetworkParameters?.first?.newParametersHash) { // This update was handled already. + return + } + val newParameters = networkMapClient?.getNetworkParameters(update.newParametersHash) + if (newParameters != null) { + logger.info("Downloaded new network parameters: $newParameters from the update: $update") + newNetworkParameters = Pair(update, newParameters) + parametersUpdatesTrack.onNext(ParametersUpdateInfo(update.newParametersHash, newParameters.verifiedNetworkMapCert(networkMapClient!!.trustedRoot), update.description, update.updateDeadline)) + } + } + + fun acceptNewNetworkParameters(parametersHash: SecureHash, sign: (SecureHash) -> SignedData) { + networkMapClient ?: throw IllegalStateException("Network parameters updates are not support without compatibility zone configured") + // TODO This scenario will happen if node was restarted and didn't download parameters yet, but we accepted them. Add persisting of newest parameters from update. + val (_, newParams) = newNetworkParameters ?: throw IllegalArgumentException("Couldn't find parameters update for the hash: $parametersHash") + val newParametersHash = newParams.verifiedNetworkMapCert(networkMapClient.trustedRoot).serialize().hash // We should check that we sign the right data structure hash. + if (parametersHash == newParametersHash) { + // The latest parameters have priority. + newParams.serialize() + .open() + .copyTo(baseDirectory / NETWORK_PARAMS_UPDATE_FILE_NAME, StandardCopyOption.REPLACE_EXISTING) + networkMapClient.ackNetworkParametersUpdate(sign(parametersHash)) + } else { + throw IllegalArgumentException("Refused to accept parameters with hash $parametersHash because network map advertises update with hash $newParametersHash. Please check newest version") + } + } +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapUpdater.kt b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapUpdater.kt deleted file mode 100644 index 414f0f15c3..0000000000 --- a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapUpdater.kt +++ /dev/null @@ -1,178 +0,0 @@ -package net.corda.node.services.network - -import com.google.common.util.concurrent.MoreExecutors -import net.corda.core.crypto.SecureHash -import net.corda.core.crypto.SignedData -import net.corda.core.internal.copyTo -import net.corda.core.internal.div -import net.corda.core.messaging.DataFeed -import net.corda.core.messaging.ParametersUpdateInfo -import net.corda.core.node.NodeInfo -import net.corda.core.serialization.serialize -import net.corda.core.utilities.contextLogger -import net.corda.core.utilities.minutes -import net.corda.node.services.api.NetworkMapCacheInternal -import net.corda.node.utilities.NamedThreadFactory -import net.corda.nodeapi.internal.SignedNodeInfo -import net.corda.nodeapi.internal.network.NETWORK_PARAMS_UPDATE_FILE_NAME -import net.corda.nodeapi.internal.network.ParametersUpdate -import net.corda.nodeapi.internal.network.SignedNetworkParameters -import net.corda.nodeapi.internal.network.verifiedNetworkMapCert -import rx.Subscription -import rx.subjects.PublishSubject -import java.nio.file.Path -import java.nio.file.StandardCopyOption -import java.time.Duration -import java.util.concurrent.Executors -import java.util.concurrent.TimeUnit - -class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal, - private val fileWatcher: NodeInfoWatcher, - private val networkMapClient: NetworkMapClient?, - private val currentParametersHash: SecureHash, - private val baseDirectory: Path -) : AutoCloseable { - companion object { - private val logger = contextLogger() - private val defaultRetryInterval = 1.minutes - } - - private val parametersUpdatesTrack: PublishSubject = PublishSubject.create() - private val executor = Executors.newSingleThreadScheduledExecutor(NamedThreadFactory("Network Map Updater Thread", Executors.defaultThreadFactory())) - private var newNetworkParameters: Pair? = null - private var fileWatcherSubscription: Subscription? = null - - override fun close() { - fileWatcherSubscription?.unsubscribe() - MoreExecutors.shutdownAndAwaitTermination(executor, 50, TimeUnit.SECONDS) - } - - fun trackParametersUpdate(): DataFeed { - val currentUpdateInfo = newNetworkParameters?.let { - ParametersUpdateInfo(it.first.newParametersHash, it.second.verified(), it.first.description, it.first.updateDeadline) - } - return DataFeed(currentUpdateInfo, parametersUpdatesTrack) - } - - fun updateNodeInfo(newInfo: NodeInfo, signer: (NodeInfo) -> SignedNodeInfo) { - val oldInfo = networkMapCache.getNodeByLegalIdentity(newInfo.legalIdentities.first()) - // Compare node info without timestamp. - if (newInfo.copy(serial = 0L) == oldInfo?.copy(serial = 0L)) return - - // Only publish and write to disk if there are changes to the node info. - val signedNodeInfo = signer(newInfo) - networkMapCache.addNode(newInfo) - fileWatcher.saveToFile(signedNodeInfo) - - if (networkMapClient != null) { - tryPublishNodeInfoAsync(signedNodeInfo, networkMapClient) - } - } - - private fun tryPublishNodeInfoAsync(signedNodeInfo: SignedNodeInfo, networkMapClient: NetworkMapClient) { - executor.submit(object : Runnable { - override fun run() { - try { - networkMapClient.publish(signedNodeInfo) - } catch (t: Throwable) { - logger.warn("Error encountered while publishing node info, will retry in $defaultRetryInterval", t) - // TODO: Exponential backoff? - executor.schedule(this, defaultRetryInterval.toMillis(), TimeUnit.MILLISECONDS) - } - } - }) - } - - fun subscribeToNetworkMap() { - require(fileWatcherSubscription == null) { "Should not call this method twice." } - // Subscribe to file based networkMap - fileWatcherSubscription = fileWatcher.nodeInfoUpdates().subscribe(networkMapCache::addNode) - - if (networkMapClient == null) return - - // Subscribe to remote network map if configured. - executor.submit(object : Runnable { - override fun run() { - val nextScheduleDelay = try { - updateNetworkMapCache(networkMapClient) - } catch (t: Throwable) { - logger.warn("Error encountered while updating network map, will retry in $defaultRetryInterval", t) - defaultRetryInterval - } - // Schedule the next update. - executor.schedule(this, nextScheduleDelay.toMillis(), TimeUnit.MILLISECONDS) - } - }) // The check may be expensive, so always run it in the background even the first time. - } - - private fun updateNetworkMapCache(networkMapClient: NetworkMapClient): Duration { - val (networkMap, cacheTimeout) = networkMapClient.getNetworkMap() - networkMap.parametersUpdate?.let { handleUpdateNetworkParameters(networkMapClient, it) } - - if (currentParametersHash != networkMap.networkParameterHash) { - // TODO This needs special handling (node omitted update process/didn't accept new parameters or didn't restart on updateDeadline) - logger.error("Node is using parameters with hash: $currentParametersHash but network map is " + - "advertising: ${networkMap.networkParameterHash}.\n" + - "Please update node to use correct network parameters file.\"") - System.exit(1) - } - - val currentNodeHashes = networkMapCache.allNodeHashes - val hashesFromNetworkMap = networkMap.nodeInfoHashes - (hashesFromNetworkMap - currentNodeHashes).mapNotNull { - // Download new node info from network map - try { - networkMapClient.getNodeInfo(it) - } catch (e: Exception) { - // Failure to retrieve one node info shouldn't stop the whole update, log and return null instead. - logger.warn("Error encountered when downloading node info '$it', skipping...", e) - null - } - }.forEach { - // Add new node info to the network map cache, these could be new node info or modification of node info for existing nodes. - networkMapCache.addNode(it) - } - - // Remove node info from network map. - (currentNodeHashes - hashesFromNetworkMap - fileWatcher.processedNodeInfoHashes) - .mapNotNull(networkMapCache::getNodeByHash) - .forEach(networkMapCache::removeNode) - - return cacheTimeout - } - - private fun handleUpdateNetworkParameters(networkMapClient: NetworkMapClient, update: ParametersUpdate) { - if (update.newParametersHash == newNetworkParameters?.first?.newParametersHash) { - // This update was handled already. - return - } - val newParameters = networkMapClient.getNetworkParameters(update.newParametersHash) - logger.info("Downloaded new network parameters: $newParameters from the update: $update") - newNetworkParameters = Pair(update, newParameters) - val updateInfo = ParametersUpdateInfo( - update.newParametersHash, - newParameters.verifiedNetworkMapCert(networkMapClient.trustedRoot), - update.description, - update.updateDeadline) - parametersUpdatesTrack.onNext(updateInfo) - } - - fun acceptNewNetworkParameters(parametersHash: SecureHash, sign: (SecureHash) -> SignedData) { - networkMapClient ?: throw IllegalStateException("Network parameters updates are not support without compatibility zone configured") - // TODO This scenario will happen if node was restarted and didn't download parameters yet, but we accepted them. - // Add persisting of newest parameters from update. - val (_, newParams) = requireNotNull(newNetworkParameters) { "Couldn't find parameters update for the hash: $parametersHash" } - // We should check that we sign the right data structure hash. - val newParametersHash = newParams.verifiedNetworkMapCert(networkMapClient.trustedRoot).serialize().hash - if (parametersHash == newParametersHash) { - // The latest parameters have priority. - newParams.serialize() - .open() - .copyTo(baseDirectory / NETWORK_PARAMS_UPDATE_FILE_NAME, StandardCopyOption.REPLACE_EXISTING) - networkMapClient.ackNetworkParametersUpdate(sign(parametersHash)) - } else { - throw IllegalArgumentException("Refused to accept parameters with hash $parametersHash because network map " + - "advertises update with hash $newParametersHash. Please check newest version") - } - } -} 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 aa55fb2552..3031349d84 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 @@ -4,11 +4,12 @@ import net.corda.cordform.CordformNode import net.corda.core.crypto.SecureHash import net.corda.core.internal.* import net.corda.core.node.NodeInfo +import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.utilities.contextLogger import net.corda.core.utilities.seconds -import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.network.NodeInfoFilesCopier +import net.corda.nodeapi.internal.SignedNodeInfo import rx.Observable import rx.Scheduler import java.io.IOException @@ -117,7 +118,7 @@ class NodeInfoWatcher(private val nodePath: Path, private fun processFile(file: Path): NodeInfo? { return try { logger.info("Reading NodeInfo from file: $file") - val signedData = file.readObject() + val signedData = file.readAll().deserialize() signedData.verified() } catch (e: Exception) { logger.warn("Exception parsing NodeInfo from file. $file", e) diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt b/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt index cd9f7d37f5..784499509d 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt @@ -8,11 +8,8 @@ import com.google.common.hash.HashingInputStream import com.google.common.io.CountingInputStream import net.corda.core.CordaRuntimeException import net.corda.core.contracts.Attachment -import net.corda.core.contracts.ContractAttachment -import net.corda.core.contracts.ContractClassName import net.corda.core.crypto.SecureHash import net.corda.core.internal.AbstractAttachment -import net.corda.core.internal.UNKNOWN_UPLOADER import net.corda.core.internal.VisibleForTesting import net.corda.core.node.services.AttachmentId import net.corda.core.node.services.AttachmentStorage @@ -27,7 +24,6 @@ import net.corda.node.utilities.NonInvalidatingWeightBasedCache import net.corda.node.utilities.defaultCordaCacheConcurrencyLevel import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX import net.corda.nodeapi.internal.persistence.currentDBSession -import net.corda.nodeapi.internal.withContractsInJar import java.io.* import java.nio.file.Paths import java.time.Instant @@ -89,14 +85,7 @@ class NodeAttachmentService( var uploader: String? = null, @Column(name = "filename", updatable = false) - var filename: String? = null, - - @ElementCollection - @Column(name = "contract_class_name") - @CollectionTable(name = "node_attachments_contract_class_name", joinColumns = arrayOf( - JoinColumn(name = "att_id", referencedColumnName = "att_id")), - foreignKey = ForeignKey(name = "FK__ctr_class__attachments")) - var contractClassNames: List? = null + var filename: String? = null ) : Serializable @VisibleForTesting @@ -207,31 +196,23 @@ class NodeAttachmentService( // If repeatedly looking for non-existing attachments becomes a performance issue, this is either indicating a // a problem somewhere else or this needs to be revisited. - private val attachmentContentCache = NonInvalidatingWeightBasedCache>>( + private val attachmentContentCache = NonInvalidatingWeightBasedCache>( maxWeight = attachmentContentCacheSize, concurrencyLevel = defaultCordaCacheConcurrencyLevel, - weigher = object : Weigher>> { - override fun weigh(key: SecureHash, value: Optional>): Int { - return key.size + if (value.isPresent) value.get().second.size else 0 + weigher = object : Weigher> { + override fun weigh(key: SecureHash, value: Optional): Int { + return key.size + if (value.isPresent) value.get().size else 0 } }, loadFunction = { Optional.ofNullable(loadAttachmentContent(it)) } ) - private fun loadAttachmentContent(id: SecureHash): Pair? { + private fun loadAttachmentContent(id: SecureHash): ByteArray? { val attachment = currentDBSession().get(NodeAttachmentService.DBAttachment::class.java, id.toString()) - ?: return null - val attachmentImpl = AttachmentImpl(id, { attachment.content }, checkAttachmentsOnLoad).let { - val contracts = attachment.contractClassNames - if (contracts != null && contracts.isNotEmpty()) { - ContractAttachment(it, contracts.first(), contracts.drop(1).toSet(), attachment.uploader) - } else { - it - } - } - return Pair(attachmentImpl, attachment.content) + return attachment?.content } + private val attachmentCache = NonInvalidatingCache>( attachmentCacheBound, defaultCordaCacheConcurrencyLevel, @@ -241,7 +222,16 @@ class NodeAttachmentService( private fun createAttachment(key: SecureHash): Attachment? { val content = attachmentContentCache.get(key) if (content.isPresent) { - return content.get().first + return AttachmentImpl( + key, + { + attachmentContentCache + .get(key) + .orElseThrow { + IllegalArgumentException("No attachement impl should have been created for non existent content") + } + }, + checkAttachmentsOnLoad) } // if no attachement has been found, we don't want to cache that - it might arrive later attachmentContentCache.invalidate(key) @@ -258,10 +248,10 @@ class NodeAttachmentService( } override fun importAttachment(jar: InputStream): AttachmentId { - return import(jar, UNKNOWN_UPLOADER, null) + return import(jar, null, null) } - override fun importAttachment(jar: InputStream, uploader: String, filename: String?): AttachmentId { + override fun importAttachment(jar: InputStream, uploader: String, filename: String): AttachmentId { return import(jar, uploader, filename) } @@ -273,39 +263,47 @@ class NodeAttachmentService( return Pair(id, bytes) } - override fun hasAttachment(attachmentId: AttachmentId): Boolean = - currentDBSession().find(NodeAttachmentService.DBAttachment::class.java, attachmentId.toString()) != null + override fun hasAttachment(attachmentId: AttachmentId): Boolean { + val session = currentDBSession() + val criteriaBuilder = session.criteriaBuilder + val criteriaQuery = criteriaBuilder.createQuery(Long::class.java) + val attachments = criteriaQuery.from(NodeAttachmentService.DBAttachment::class.java) + criteriaQuery.select(criteriaBuilder.count(criteriaQuery.from(NodeAttachmentService.DBAttachment::class.java))) + criteriaQuery.where(criteriaBuilder.equal(attachments.get(DBAttachment::attId.name), attachmentId.toString())) + return (session.createQuery(criteriaQuery).singleResult > 0) + } // TODO: PLT-147: The attachment should be randomised to prevent brute force guessing and thus privacy leaks. private fun import(jar: InputStream, uploader: String?, filename: String?): AttachmentId { - return withContractsInJar(jar) { contractClassNames, inputStream -> - require(inputStream !is JarInputStream) + require(jar !is JarInputStream) - // Read the file into RAM, hashing it to find the ID as we go. The attachment must fit into memory. - // TODO: Switch to a two-phase insert so we can handle attachments larger than RAM. - // To do this we must pipe stream into the database without knowing its hash, which we will learn only once - // the insert/upload is complete. We can then query to see if it's a duplicate and if so, erase, and if not - // set the hash field of the new attachment record. + // Read the file into RAM, hashing it to find the ID as we go. The attachment must fit into memory. + // TODO: Switch to a two-phase insert so we can handle attachments larger than RAM. + // To do this we must pipe stream into the database without knowing its hash, which we will learn only once + // the insert/upload is complete. We can then query to see if it's a duplicate and if so, erase, and if not + // set the hash field of the new attachment record. - val (id, bytes) = getAttachmentIdAndBytes(inputStream) - if (!hasAttachment(id)) { - checkIsAValidJAR(ByteArrayInputStream(bytes)) - val session = currentDBSession() - val attachment = NodeAttachmentService.DBAttachment(attId = id.toString(), content = bytes, uploader = uploader, filename = filename, contractClassNames = contractClassNames) - session.save(attachment) - attachmentCount.inc() - log.info("Stored new attachment $id") - id - } else { - throw java.nio.file.FileAlreadyExistsException(id.toString()) - } + val (id, bytes) = getAttachmentIdAndBytes(jar) + if (!hasAttachment(id)) { + checkIsAValidJAR(ByteArrayInputStream(bytes)) + val session = currentDBSession() + val attachment = NodeAttachmentService.DBAttachment(attId = id.toString(), content = bytes, uploader = uploader, filename = filename) + session.save(attachment) + attachmentCount.inc() + log.info("Stored new attachment $id") + return id + } else { + throw java.nio.file.FileAlreadyExistsException(id.toString()) } } - override fun importOrGetAttachment(jar: InputStream): AttachmentId = try { - importAttachment(jar) - } catch (faee: java.nio.file.FileAlreadyExistsException) { - AttachmentId.parse(faee.message!!) + override fun importOrGetAttachment(jar: InputStream): AttachmentId { + try { + return importAttachment(jar) + } + catch (faee: java.nio.file.FileAlreadyExistsException) { + return AttachmentId.parse(faee.message!!) + } } override fun queryAttachments(criteria: AttachmentQueryCriteria, sorting: AttachmentSort?): List { @@ -330,4 +328,5 @@ class NodeAttachmentService( return results.map { AttachmentId.parse(it.attId) } } + } diff --git a/node/src/main/resources/reference.conf b/node/src/main/resources/reference.conf index e7fbcfc57b..6669453571 100644 --- a/node/src/main/resources/reference.conf +++ b/node/src/main/resources/reference.conf @@ -1,5 +1,6 @@ myLegalName = "Vast Global MegaCorp, Ltd" emailAddress = "admin@company.com" +exportJMXto = "http" keyStorePassword = "cordacadevpass" trustStorePassword = "trustpass" dataSourceProperties = { 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 ebfb3795b2..9d12bffb05 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 @@ -2,18 +2,14 @@ package net.corda.node.internal.cordapp import com.typesafe.config.Config import com.typesafe.config.ConfigFactory -import junit.framework.Assert.assertNull import net.corda.core.internal.cordapp.CordappConfigProvider import net.corda.core.node.services.AttachmentStorage -import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.internal.MockCordappConfigProvider import net.corda.testing.services.MockAttachmentStorage import org.assertj.core.api.Assertions.assertThat -import org.junit.Assert.assertEquals -import org.junit.Assert.assertNotNull +import org.junit.Assert import org.junit.Before import org.junit.Test -import java.net.URL class CordappProviderImplTests { private companion object { @@ -29,7 +25,6 @@ class CordappProviderImplTests { } private lateinit var attachmentStore: AttachmentStorage - private val whitelistedContractImplementations = testNetworkParameters().whitelistedContractImplementations @Before fun setup() { @@ -38,40 +33,44 @@ class CordappProviderImplTests { @Test fun `isolated jar is loaded into the attachment store`() { - val provider = newCordappProvider(isolatedJAR) + val loader = CordappLoader.createDevMode(listOf(isolatedJAR)) + val provider = CordappProviderImpl(loader, stubConfigProvider, attachmentStore) val maybeAttachmentId = provider.getCordappAttachmentId(provider.cordapps.first()) - assertNotNull(maybeAttachmentId) - assertNotNull(attachmentStore.openAttachment(maybeAttachmentId!!)) + Assert.assertNotNull(maybeAttachmentId) + Assert.assertNotNull(attachmentStore.openAttachment(maybeAttachmentId!!)) } @Test fun `empty jar is not loaded into the attachment store`() { - val provider = newCordappProvider(emptyJAR) - assertNull(provider.getCordappAttachmentId(provider.cordapps.first())) + val loader = CordappLoader.createDevMode(listOf(emptyJAR)) + val provider = CordappProviderImpl(loader, stubConfigProvider, attachmentStore) + Assert.assertNull(provider.getCordappAttachmentId(provider.cordapps.first())) } @Test fun `test that we find a cordapp class that is loaded into the store`() { - val provider = newCordappProvider(isolatedJAR) + val loader = CordappLoader.createDevMode(listOf(isolatedJAR)) + val provider = CordappProviderImpl(loader, stubConfigProvider, attachmentStore) val className = "net.corda.finance.contracts.isolated.AnotherDummyContract" val expected = provider.cordapps.first() val actual = provider.getCordappForClass(className) - assertNotNull(actual) - assertEquals(expected, actual) + Assert.assertNotNull(actual) + Assert.assertEquals(expected, actual) } @Test - fun `test that we find an attachment for a cordapp contrat class`() { - val provider = newCordappProvider(isolatedJAR) + fun `test that we find an attachment for a cordapp contract class`() { + val loader = CordappLoader.createDevMode(listOf(isolatedJAR)) + val provider = CordappProviderImpl(loader, stubConfigProvider, attachmentStore) val className = "net.corda.finance.contracts.isolated.AnotherDummyContract" val expected = provider.getAppContext(provider.cordapps.first()).attachmentId val actual = provider.getContractAttachmentID(className) - assertNotNull(actual) - assertEquals(actual!!, expected) + Assert.assertNotNull(actual) + Assert.assertEquals(actual!!, expected) } @Test @@ -79,15 +78,10 @@ class CordappProviderImplTests { val configProvider = MockCordappConfigProvider() configProvider.cordappConfigs.put(isolatedCordappName, validConfig) val loader = CordappLoader.createDevMode(listOf(isolatedJAR)) - val provider = CordappProviderImpl(loader, configProvider, attachmentStore, whitelistedContractImplementations) + val provider = CordappProviderImpl(loader, configProvider, attachmentStore) val expected = provider.getAppContext(provider.cordapps.first()).config assertThat(expected.getString("key")).isEqualTo("value") } - - private fun newCordappProvider(vararg urls: URL): CordappProviderImpl { - val loader = CordappLoader.createDevMode(urls.toList()) - return CordappProviderImpl(loader, stubConfigProvider, attachmentStore, whitelistedContractImplementations) - } } diff --git a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt index c888ed6818..6582196e27 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt @@ -57,7 +57,7 @@ class NetworkMapClientTest { val nodeInfoHash = nodeInfo.serialize().sha256() - assertThat(networkMapClient.getNetworkMap().payload.nodeInfoHashes).containsExactly(nodeInfoHash) + assertThat(networkMapClient.getNetworkMap().networkMap.nodeInfoHashes).containsExactly(nodeInfoHash) assertEquals(nodeInfo, networkMapClient.getNodeInfo(nodeInfoHash)) val (nodeInfo2, signedNodeInfo2) = createNodeInfoAndSigned(BOB_NAME) @@ -65,7 +65,7 @@ class NetworkMapClientTest { networkMapClient.publish(signedNodeInfo2) val nodeInfoHash2 = nodeInfo2.serialize().sha256() - assertThat(networkMapClient.getNetworkMap().payload.nodeInfoHashes).containsExactly(nodeInfoHash, nodeInfoHash2) + assertThat(networkMapClient.getNetworkMap().networkMap.nodeInfoHashes).containsExactly(nodeInfoHash, nodeInfoHash2) assertEquals(cacheTimeout, networkMapClient.getNetworkMap().cacheMaxAge) assertEquals(nodeInfo2, networkMapClient.getNodeInfo(nodeInfoHash2)) } diff --git a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapUpdaterTest.kt b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapUpdaterTest.kt index 0647f46253..fd0214955d 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapUpdaterTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapUpdaterTest.kt @@ -15,13 +15,17 @@ import net.corda.core.internal.* import net.corda.core.messaging.ParametersUpdateInfo import net.corda.core.node.NetworkParameters import net.corda.core.node.NodeInfo +import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.utilities.millis import net.corda.node.services.api.NetworkMapCacheInternal import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.createDevNetworkMapCa import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair -import net.corda.nodeapi.internal.network.* +import net.corda.nodeapi.internal.network.NETWORK_PARAMS_UPDATE_FILE_NAME +import net.corda.nodeapi.internal.network.NetworkMap +import net.corda.nodeapi.internal.network.ParametersUpdate +import net.corda.nodeapi.internal.network.verifiedNetworkMapCert import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.core.* import net.corda.testing.internal.DEV_ROOT_CA @@ -193,7 +197,7 @@ class NetworkMapUpdaterTest { @Test fun `emit new parameters update info on parameters update from network map`() { - val paramsFeed = updater.trackParametersUpdate() + val paramsFeed = updater.track() val snapshot = paramsFeed.snapshot val updates = paramsFeed.updates.bufferUntilSubscribed() assertEquals(null, snapshot) @@ -225,7 +229,7 @@ class NetworkMapUpdaterTest { updater.acceptNewNetworkParameters(newHash, { hash -> hash.serialize().sign(keyPair)}) verify(networkMapClient).ackNetworkParametersUpdate(any()) val updateFile = baseDir / NETWORK_PARAMS_UPDATE_FILE_NAME - val signedNetworkParams = updateFile.readObject() + val signedNetworkParams = updateFile.readAll().deserialize>() val paramsFromFile = signedNetworkParams.verifiedNetworkMapCert(DEV_ROOT_CA.certificate) assertEquals(newParameters, paramsFromFile) } diff --git a/node/src/test/kotlin/net/corda/node/services/network/NetworkParametersReaderTest.kt b/node/src/test/kotlin/net/corda/node/services/network/NetworkParametersReaderTest.kt index 370657e42c..86fba98581 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/NetworkParametersReaderTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/NetworkParametersReaderTest.kt @@ -2,13 +2,15 @@ package net.corda.node.services.network import com.google.common.jimfs.Configuration import com.google.common.jimfs.Jimfs -import net.corda.core.internal.createDirectories -import net.corda.core.internal.div -import net.corda.core.internal.exists -import net.corda.core.internal.readObject +import net.corda.core.internal.* +import net.corda.core.node.NetworkParameters +import net.corda.core.serialization.deserialize import net.corda.core.utilities.seconds import net.corda.node.internal.NetworkParametersReader -import net.corda.nodeapi.internal.network.* +import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME +import net.corda.nodeapi.internal.network.NETWORK_PARAMS_UPDATE_FILE_NAME +import net.corda.nodeapi.internal.network.NetworkParametersCopier +import net.corda.nodeapi.internal.network.verifiedNetworkMapCert import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.driver.PortAllocation @@ -55,9 +57,7 @@ class NetworkParametersReaderTest { assertFalse((baseDirectory / NETWORK_PARAMS_UPDATE_FILE_NAME).exists()) assertEquals(server.networkParameters, parameters) // Parameters from update should be moved to `network-parameters` file. - val parametersFromFile = (baseDirectory / NETWORK_PARAMS_FILE_NAME) - .readObject() - .verifiedNetworkMapCert(DEV_ROOT_CA.certificate) + val parametersFromFile = (baseDirectory / NETWORK_PARAMS_FILE_NAME).readAll().deserialize>().verifiedNetworkMapCert(DEV_ROOT_CA.certificate) assertEquals(server.networkParameters, parametersFromFile) } } \ No newline at end of file diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/MaxTransactionSizeTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/MaxTransactionSizeTests.kt index 52a421a515..9280781c4f 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/MaxTransactionSizeTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/MaxTransactionSizeTests.kt @@ -21,7 +21,6 @@ import net.corda.testing.node.startFlow import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Before -import org.junit.Ignore import org.junit.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith 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 95f552138d..06fd23c188 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 @@ -78,7 +78,7 @@ open class MockServices private constructor( cordappLoader: CordappLoader, override val validatedTransactions: WritableTransactionStorage, override val identityService: IdentityService, - final override val networkParameters: NetworkParameters, + override val networkParameters: NetworkParameters, private val initialIdentity: TestIdentity, private val moreKeys: Array ) : ServiceHub, StateLoader by validatedTransactions { @@ -261,7 +261,7 @@ open class MockServices private constructor( return NodeInfo(listOf(NetworkHostAndPort("mock.node.services", 10000)), listOf(initialIdentity.identity), 1, serial = 1L) } override val transactionVerifierService: TransactionVerifierService get() = InMemoryTransactionVerifierService(2) - private val mockCordappProvider: MockCordappProvider = MockCordappProvider(cordappLoader, attachments, networkParameters.whitelistedContractImplementations) + private val mockCordappProvider: MockCordappProvider = MockCordappProvider(cordappLoader, attachments) override val cordappProvider: CordappProvider get() = mockCordappProvider internal fun makeVaultService(hibernateConfig: HibernateConfiguration, schemaService: SchemaService): VaultServiceInternal { diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt index 5e13e35abe..c191384ee8 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt @@ -18,6 +18,7 @@ import net.corda.core.messaging.CordaRPCOps import net.corda.core.node.NetworkParameters import net.corda.core.node.NotaryInfo import net.corda.core.node.services.NetworkMapCache +import net.corda.core.serialization.deserialize import net.corda.core.toFuture import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.contextLogger @@ -484,7 +485,7 @@ class DriverDSLImpl( val nodeInfoFile = config.corda.baseDirectory.list { paths -> paths.filter { it.fileName.toString().startsWith(NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX) }.findFirst().get() } - val nodeInfo = nodeInfoFile.readObject().verified() + val nodeInfo = nodeInfoFile.readAll().deserialize().verified() NotaryInfo(nodeInfo.legalIdentities[0], spec.validating) } } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt index 4d0e952ce2..dbacebb931 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt @@ -39,7 +39,7 @@ class NetworkMapServer(private val cacheTimeout: Duration, private val myHostNameValue: String = "test.host.name", vararg additionalServices: Any) : Closeable { companion object { - private val stubNetworkParameters = NetworkParameters(1, emptyList(), 10485760, Int.MAX_VALUE, Instant.now(), 10, emptyMap()) + private val stubNetworkParameters = NetworkParameters(1, emptyList(), 10485760, Int.MAX_VALUE, Instant.now(), 10) } private val server: Server diff --git a/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt b/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt index 5f86f26b34..322e9e90b7 100644 --- a/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt +++ b/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt @@ -19,7 +19,6 @@ fun testNetworkParameters( modifiedTime = modifiedTime, maxMessageSize = maxMessageSize, maxTransactionSize = maxTransactionSize, - epoch = epoch, - whitelistedContractImplementations = emptyMap() + epoch = epoch ) } \ No newline at end of file diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/MockCordappProvider.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/MockCordappProvider.kt index e04b2aa580..4638d73fb3 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/MockCordappProvider.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/MockCordappProvider.kt @@ -2,7 +2,6 @@ package net.corda.testing.internal import net.corda.core.contracts.ContractClassName import net.corda.core.cordapp.Cordapp -import net.corda.core.internal.TEST_UPLOADER import net.corda.core.internal.cordapp.CordappImpl import net.corda.core.node.services.AttachmentId import net.corda.core.node.services.AttachmentStorage @@ -15,10 +14,9 @@ import java.util.* class MockCordappProvider( cordappLoader: CordappLoader, attachmentStorage: AttachmentStorage, - whitelistedContractImplementations: Map>, - cordappConfigProvider: MockCordappConfigProvider = MockCordappConfigProvider() -) : CordappProviderImpl(cordappLoader, cordappConfigProvider, attachmentStorage, whitelistedContractImplementations) { - constructor(cordappLoader: CordappLoader, attachmentStorage: AttachmentStorage, whitelistedContractImplementations: Map>) : this(cordappLoader, attachmentStorage, whitelistedContractImplementations, MockCordappConfigProvider()) + val cordappConfigProvider: MockCordappConfigProvider = MockCordappConfigProvider() +) : CordappProviderImpl(cordappLoader, cordappConfigProvider, attachmentStorage) { + constructor(cordappLoader: CordappLoader, attachmentStorage: AttachmentStorage) : this(cordappLoader, attachmentStorage, MockCordappConfigProvider()) val cordappRegistry = mutableListOf>() @@ -35,21 +33,20 @@ class MockCordappProvider( customSchemas = emptySet(), jarPath = Paths.get("").toUri().toURL()) if (cordappRegistry.none { it.first.contractClassNames.contains(contractClassName) }) { - cordappRegistry.add(Pair(cordapp, findOrImportAttachment(listOf(contractClassName), contractClassName.toByteArray(), attachments))) + cordappRegistry.add(Pair(cordapp, findOrImportAttachment(contractClassName.toByteArray(), attachments))) } } - override fun getContractAttachmentID(contractClassName: ContractClassName): AttachmentId? = cordappRegistry.find { it.first.contractClassNames.contains(contractClassName) }?.second - ?: super.getContractAttachmentID(contractClassName) + override fun getContractAttachmentID(contractClassName: ContractClassName): AttachmentId? = cordappRegistry.find { it.first.contractClassNames.contains(contractClassName) }?.second ?: super.getContractAttachmentID(contractClassName) - private fun findOrImportAttachment(contractClassNames: List, data: ByteArray, attachments: MockAttachmentStorage): AttachmentId { + private fun findOrImportAttachment(data: ByteArray, attachments: MockAttachmentStorage): AttachmentId { val existingAttachment = attachments.files.filter { - Arrays.equals(it.value.second, data) + Arrays.equals(it.value, data) } return if (!existingAttachment.isEmpty()) { existingAttachment.keys.first() } else { - attachments.importContractAttachment(contractClassNames, TEST_UPLOADER, data.inputStream()) + attachments.importAttachment(data.inputStream()) } } } diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/services/MockAttachmentStorage.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/services/MockAttachmentStorage.kt index dc96dfcd7c..f827efb39d 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/services/MockAttachmentStorage.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/services/MockAttachmentStorage.kt @@ -1,18 +1,14 @@ package net.corda.testing.services import net.corda.core.contracts.Attachment -import net.corda.core.contracts.ContractAttachment -import net.corda.core.contracts.ContractClassName import net.corda.core.crypto.SecureHash import net.corda.core.crypto.sha256 import net.corda.core.internal.AbstractAttachment -import net.corda.core.internal.UNKNOWN_UPLOADER import net.corda.core.node.services.AttachmentId import net.corda.core.node.services.AttachmentStorage import net.corda.core.node.services.vault.AttachmentQueryCriteria import net.corda.core.node.services.vault.AttachmentSort import net.corda.core.serialization.SingletonSerializeAsToken -import net.corda.nodeapi.internal.withContractsInJar import java.io.ByteArrayOutputStream import java.io.InputStream import java.util.* @@ -28,17 +24,31 @@ class MockAttachmentStorage : AttachmentStorage, SingletonSerializeAsToken() { } } - val files = HashMap>() + override fun importAttachment(jar: InputStream): AttachmentId { + // JIS makes read()/readBytes() return bytes of the current file, but we want to hash the entire container here. + require(jar !is JarInputStream) - override fun importAttachment(jar: InputStream): AttachmentId = importAttachment(jar, UNKNOWN_UPLOADER, null) + val bytes = getBytes(jar) - override fun importAttachment(jar: InputStream, uploader: String, filename: String?): AttachmentId { - return withContractsInJar(jar) { contractClassNames, inputStream -> - importAttachmentInternal(inputStream, uploader, filename, contractClassNames) + val sha256 = bytes.sha256() + if (!files.containsKey(sha256)) { + files[sha256] = bytes } + return sha256 } - override fun openAttachment(id: SecureHash): Attachment? = files[id]?.first + override fun importAttachment(jar: InputStream, uploader: String, filename: String): AttachmentId { + return importAttachment(jar) + } + + val files = HashMap() + + private class MockAttachment(dataLoader: () -> ByteArray, override val id: SecureHash) : AbstractAttachment(dataLoader) + + override fun openAttachment(id: SecureHash): Attachment? { + val f = files[id] ?: return null + return MockAttachment({ f }, id) + } override fun queryAttachments(criteria: AttachmentQueryCriteria, sorting: AttachmentSort?): List { throw NotImplementedError("Querying for attachments not implemented") @@ -46,6 +56,11 @@ class MockAttachmentStorage : AttachmentStorage, SingletonSerializeAsToken() { override fun hasAttachment(attachmentId: AttachmentId) = files.containsKey(attachmentId) + fun getAttachmentIdAndBytes(jar: InputStream): Pair { + val bytes = getBytes(jar) + return Pair(bytes.sha256(), bytes) + } + override fun importOrGetAttachment(jar: InputStream): AttachmentId { try { return importAttachment(jar) @@ -53,25 +68,4 @@ class MockAttachmentStorage : AttachmentStorage, SingletonSerializeAsToken() { return AttachmentId.parse(faee.message!!) } } - - fun importContractAttachment(contractClassNames: List, uploader: String, jar: InputStream): AttachmentId = importAttachmentInternal(jar, uploader, null, contractClassNames) - - fun getAttachmentIdAndBytes(jar: InputStream): Pair = getBytes(jar).let { bytes -> Pair(bytes.sha256(), bytes) } - - private class MockAttachment(dataLoader: () -> ByteArray, override val id: SecureHash) : AbstractAttachment(dataLoader) - - private fun importAttachmentInternal(jar: InputStream, uploader: String, filename: String?, contractClassNames: List? = null): AttachmentId { - // JIS makes read()/readBytes() return bytes of the current file, but we want to hash the entire container here. - require(jar !is JarInputStream) - - val bytes = getBytes(jar) - - val sha256 = bytes.sha256() - if (sha256 !in files.keys) { - val baseAttachment = MockAttachment({ bytes }, sha256) - val attachment = if (contractClassNames == null || contractClassNames.isEmpty()) baseAttachment else ContractAttachment(baseAttachment, contractClassNames.first(), contractClassNames.toSet(), uploader) - files[sha256] = Pair(attachment, bytes) - } - return sha256 - } } 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 54edd309de..5ecb970fce 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 @@ -145,8 +145,7 @@ class NodeController(check: atRuntime = ::checkExists) : Controller() { modifiedTime = Instant.now(), maxMessageSize = 10485760, maxTransactionSize = Int.MAX_VALUE, - epoch = 1, - whitelistedContractImplementations = emptyMap() + epoch = 1 )) notaryIdentity = identity networkParametersCopier = parametersCopier diff --git a/tools/notaryhealthcheck/src/main/kotlin/net/corda/notaryhealthcheck/HealthCheckCordform.kt b/tools/notaryhealthcheck/src/main/kotlin/net/corda/notaryhealthcheck/HealthCheckCordform.kt index 7132ce6fcb..5358b1e55c 100644 --- a/tools/notaryhealthcheck/src/main/kotlin/net/corda/notaryhealthcheck/HealthCheckCordform.kt +++ b/tools/notaryhealthcheck/src/main/kotlin/net/corda/notaryhealthcheck/HealthCheckCordform.kt @@ -31,32 +31,20 @@ class HealthCheckCordform : CordformDefinition() { } notaryNode(0, 10008) { p2pPort(10009) - rpcSettings { - port(10010) - adminPort(10110) - } + rpcPort(10010) } notaryNode(1, 10012, 10008) { p2pPort(10013) - rpcSettings { - port(10014) - adminPort(10114) - } + rpcPort(10014) } notaryNode(2, 10016, 10008) { p2pPort(10017) - rpcSettings { - port(10018) - adminPort(10118) - } + rpcPort(10018) } node { name(CordaX500Name("R3 Notary Health Check", "London", "GB")) p2pPort(10002) - rpcSettings { - port(10003) - adminPort(10103) - } + rpcPort(10003) rpcUsers(notaryDemoUser) } } diff --git a/verifier/src/integration-test/kotlin/net/corda/verifier/GeneratedLedger.kt b/verifier/src/integration-test/kotlin/net/corda/verifier/GeneratedLedger.kt index bd33ea9efc..a07bc0c1ea 100644 --- a/verifier/src/integration-test/kotlin/net/corda/verifier/GeneratedLedger.kt +++ b/verifier/src/integration-test/kotlin/net/corda/verifier/GeneratedLedger.kt @@ -43,7 +43,7 @@ data class GeneratedLedger( private val attachmentMap: Map by lazy { attachments.associateBy(Attachment::id) } private val identityMap: Map by lazy { identities.associateBy(Party::owningKey) } private val contractAttachmentMap: Map by lazy { - attachments.mapNotNull { it as? ContractAttachment }.flatMap { attch-> attch.allContracts.map { it to attch } }.toMap() + attachments.mapNotNull { it as? ContractAttachment }.associateBy { it.contract } } private val services = object : ServicesForResolution { 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 abb0444f34..0905130562 100644 --- a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierTests.kt +++ b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierTests.kt @@ -22,7 +22,6 @@ import net.corda.testing.internal.IntegrationTestSchemas import net.corda.testing.internal.toDatabaseSchemaName import net.corda.testing.node.NotarySpec import org.junit.ClassRule -import org.junit.Ignore import org.junit.Rule import org.junit.Test import java.util.* @@ -143,7 +142,6 @@ class VerifierTests : IntegrationTest() { } } - @Ignore("CORDA-1022") @Test fun `single verifier works with a node`() { verifierDriver(DriverParameters(