diff --git a/.ci/api-current.txt b/.ci/api-current.txt index dee3db0766..6954e313f5 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -14,8 +14,6 @@ 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 @@ -209,7 +207,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 public final class net.corda.core.contracts.AlwaysAcceptAttachmentConstraint extends java.lang.Object implements net.corda.core.contracts.AttachmentConstraint +@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 public boolean isSatisfiedBy(net.corda.core.contracts.Attachment) public static final net.corda.core.contracts.AlwaysAcceptAttachmentConstraint INSTANCE ## @@ -284,14 +282,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 public interface net.corda.core.contracts.AttachmentConstraint +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement 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 public final class net.corda.core.contracts.AutomaticHashConstraint extends java.lang.Object implements net.corda.core.contracts.AttachmentConstraint +@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 public boolean isSatisfiedBy(net.corda.core.contracts.Attachment) public static final net.corda.core.contracts.AutomaticHashConstraint INSTANCE ## @@ -343,14 +341,20 @@ 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() @@ -366,7 +370,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 public final class net.corda.core.contracts.HashAttachmentConstraint extends java.lang.Object implements net.corda.core.contracts.AttachmentConstraint +@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 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) @@ -557,6 +561,9 @@ 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) ## @@ -620,6 +627,10 @@ 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() @@ -634,10 +645,25 @@ public static final class net.corda.core.contracts.UniqueIdentifier$Companion ex @org.jetbrains.annotations.NotNull public abstract List getServiceFlows() @org.jetbrains.annotations.NotNull public abstract List getServices() ## +@net.corda.core.DoNotImplement public interface net.corda.core.cordapp.CordappConfig + public abstract boolean exists(String) + @org.jetbrains.annotations.NotNull public abstract Object get(String) + public abstract boolean getBoolean(String) + public abstract double getDouble(String) + public abstract float getFloat(String) + public abstract int getInt(String) + public abstract long getLong(String) + @org.jetbrains.annotations.NotNull public abstract Number getNumber(String) + @org.jetbrains.annotations.NotNull public abstract String getString(String) +## +public final class net.corda.core.cordapp.CordappConfigException extends java.lang.Exception + public (String, Throwable) +## public final class net.corda.core.cordapp.CordappContext extends java.lang.Object public (net.corda.core.cordapp.Cordapp, net.corda.core.crypto.SecureHash, ClassLoader, net.corda.core.cordapp.CordappConfig) @org.jetbrains.annotations.Nullable public final net.corda.core.crypto.SecureHash getAttachmentId() @org.jetbrains.annotations.NotNull public final ClassLoader getClassLoader() + @org.jetbrains.annotations.NotNull public final net.corda.core.cordapp.CordappConfig getConfig() @org.jetbrains.annotations.NotNull public final net.corda.core.cordapp.Cordapp getCordapp() ## @net.corda.core.DoNotImplement public interface net.corda.core.cordapp.CordappProvider @@ -1305,6 +1331,39 @@ public @interface net.corda.core.flows.InitiatedBy public @interface net.corda.core.flows.InitiatingFlow public abstract int version() ## +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.flows.NotarisationPayload extends java.lang.Object + public (Object, net.corda.core.flows.NotarisationRequestSignature) + @org.jetbrains.annotations.NotNull public final Object component1() + @org.jetbrains.annotations.NotNull public final net.corda.core.flows.NotarisationRequestSignature component2() + @org.jetbrains.annotations.NotNull public final net.corda.core.flows.NotarisationPayload copy(Object, net.corda.core.flows.NotarisationRequestSignature) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.CoreTransaction getCoreTransaction() + @org.jetbrains.annotations.NotNull public final net.corda.core.flows.NotarisationRequestSignature getRequestSignature() + @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.SignedTransaction getSignedTransaction() + @org.jetbrains.annotations.NotNull public final Object getTransaction() + public int hashCode() + public String toString() +## +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.flows.NotarisationRequest extends java.lang.Object + public (List, net.corda.core.crypto.SecureHash) + @org.jetbrains.annotations.NotNull public final List getStatesToConsume() + @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getTransactionId() + public final void verifySignature(net.corda.core.flows.NotarisationRequestSignature, net.corda.core.identity.Party) + public static final net.corda.core.flows.NotarisationRequest$Companion Companion +## +public static final class net.corda.core.flows.NotarisationRequest$Companion extends java.lang.Object +## +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.flows.NotarisationRequestSignature extends java.lang.Object + public (net.corda.core.crypto.DigitalSignature$WithKey, int) + @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.DigitalSignature$WithKey component1() + public final int component2() + @org.jetbrains.annotations.NotNull public final net.corda.core.flows.NotarisationRequestSignature copy(net.corda.core.crypto.DigitalSignature$WithKey, int) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.DigitalSignature$WithKey getDigitalSignature() + public final int getPlatformVersion() + public int hashCode() + public String toString() +## @net.corda.core.flows.InitiatingFlow public final class net.corda.core.flows.NotaryChangeFlow extends net.corda.core.flows.AbstractStateReplacementFlow$Instigator public (net.corda.core.contracts.StateAndRef, net.corda.core.identity.Party, net.corda.core.utilities.ProgressTracker) @org.jetbrains.annotations.NotNull protected net.corda.core.flows.AbstractStateReplacementFlow$UpgradeTx assembleTx() @@ -1331,6 +1390,15 @@ public @interface net.corda.core.flows.InitiatingFlow public int hashCode() @org.jetbrains.annotations.NotNull public String toString() ## +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.NotaryError$RequestSignatureInvalid extends net.corda.core.flows.NotaryError + public (Throwable) + @org.jetbrains.annotations.NotNull public final Throwable component1() + @org.jetbrains.annotations.NotNull public final net.corda.core.flows.NotaryError$RequestSignatureInvalid copy(Throwable) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public final Throwable getCause() + public int hashCode() + @org.jetbrains.annotations.NotNull public String toString() +## @net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.NotaryError$TimeWindowInvalid extends net.corda.core.flows.NotaryError public (java.time.Instant, net.corda.core.contracts.TimeWindow) @org.jetbrains.annotations.NotNull public final java.time.Instant component1() @@ -1369,6 +1437,7 @@ public final class net.corda.core.flows.NotaryFlow extends java.lang.Object public (net.corda.core.transactions.SignedTransaction) public (net.corda.core.transactions.SignedTransaction, net.corda.core.utilities.ProgressTracker) @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public List call() + @org.jetbrains.annotations.NotNull protected final net.corda.core.identity.Party checkTransaction() @org.jetbrains.annotations.NotNull public net.corda.core.utilities.ProgressTracker getProgressTracker() @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull protected final net.corda.core.utilities.UntrustworthyData notarise(net.corda.core.identity.Party) @org.jetbrains.annotations.NotNull protected final List validateResponse(net.corda.core.utilities.UntrustworthyData, net.corda.core.identity.Party) @@ -1748,14 +1817,15 @@ 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) + public (int, List, int, int, java.time.Instant, int, Map) 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 net.corda.core.node.NetworkParameters copy(int, List, int, int, java.time.Instant, int) + @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) public boolean equals(Object) public final int getEpoch() public final int getMaxMessageSize() @@ -1763,6 +1833,7 @@ 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() ## @@ -1804,6 +1875,7 @@ public @interface net.corda.core.messaging.RPCReturnsObservables @org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.FilteredTransaction, java.security.PublicKey) @org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.SignedTransaction) @org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.SignedTransaction, java.security.PublicKey) + @org.jetbrains.annotations.NotNull public abstract net.corda.core.cordapp.CordappContext getAppContext() @org.jetbrains.annotations.NotNull public abstract java.time.Clock getClock() @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.ContractUpgradeService getContractUpgradeService() @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.KeyManagementService getKeyManagementService() @@ -1826,6 +1898,7 @@ public @interface net.corda.core.messaging.RPCReturnsObservables @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.AttachmentStorage getAttachments() @org.jetbrains.annotations.NotNull public abstract net.corda.core.cordapp.CordappProvider getCordappProvider() @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.IdentityService getIdentityService() + @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.NetworkParameters getNetworkParameters() ## @net.corda.core.DoNotImplement public interface net.corda.core.node.StateLoader @org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.TransactionState loadState(net.corda.core.contracts.StateRef) @@ -1838,9 +1911,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) - @org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.SecureHash importAttachment(java.io.InputStream) + @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, String, String) - @org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.SecureHash importOrGetAttachment(java.io.InputStream) + @kotlin.Deprecated @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) ## @@ -2968,6 +3041,7 @@ 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() @@ -2978,6 +3052,7 @@ 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) @@ -3690,6 +3765,7 @@ public final class net.corda.testing.driver.DriverParameters extends java.lang.O @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters withExtraCordappPackagesToScan(List) @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters withIsDebug(boolean) @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters withJmxPolicy(net.corda.testing.driver.JmxPolicy) + @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters withNetworkParameters(net.corda.core.node.NetworkParameters) @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters withNotarySpecs(List) @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters withPortAllocation(net.corda.testing.driver.PortAllocation) @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters withStartNodesInProcess(boolean) @@ -4014,6 +4090,7 @@ public class net.corda.testing.node.MockServices extends java.lang.Object implem @org.jetbrains.annotations.NotNull public net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.FilteredTransaction, java.security.PublicKey) @org.jetbrains.annotations.NotNull public net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.SignedTransaction) @org.jetbrains.annotations.NotNull public net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.SignedTransaction, java.security.PublicKey) + @org.jetbrains.annotations.NotNull public net.corda.core.cordapp.CordappContext getAppContext() @org.jetbrains.annotations.NotNull public final net.corda.testing.services.MockAttachmentStorage getAttachments() @org.jetbrains.annotations.NotNull public java.time.Clock getClock() @org.jetbrains.annotations.NotNull public net.corda.core.node.services.ContractUpgradeService getContractUpgradeService() @@ -4024,6 +4101,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.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() @@ -4062,6 +4140,7 @@ public static final class net.corda.testing.node.MockServicesKt$createMockCordaS @org.jetbrains.annotations.NotNull public net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.FilteredTransaction, java.security.PublicKey) @org.jetbrains.annotations.NotNull public net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.SignedTransaction) @org.jetbrains.annotations.NotNull public net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.SignedTransaction, java.security.PublicKey) + @org.jetbrains.annotations.NotNull public net.corda.core.cordapp.CordappContext getAppContext() @org.jetbrains.annotations.NotNull public net.corda.core.node.services.AttachmentStorage getAttachments() @org.jetbrains.annotations.NotNull public java.time.Clock getClock() @org.jetbrains.annotations.NotNull public net.corda.core.node.services.ContractUpgradeService getContractUpgradeService() @@ -4070,6 +4149,7 @@ public static final class net.corda.testing.node.MockServicesKt$createMockCordaS @org.jetbrains.annotations.NotNull public net.corda.core.node.services.KeyManagementService getKeyManagementService() @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 net.corda.core.node.NetworkParameters getNetworkParameters() @org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockServices getServiceHub() @org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializeAsToken getServiceInstance() @org.jetbrains.annotations.NotNull public net.corda.core.node.services.TransactionVerifierService getTransactionVerifierService() @@ -4135,6 +4215,7 @@ public final class net.corda.testing.node.StartedMockNode extends java.lang.Obje @org.jetbrains.annotations.NotNull public final rx.Observable registerInitiatedFlow(Class) public final void setMessagingServiceSpy(net.corda.testing.node.MessagingServiceSpy) public final void stop() + public final Object transaction(kotlin.jvm.functions.Function0) public static final net.corda.testing.node.StartedMockNode$Companion Companion ## public static final class net.corda.testing.node.StartedMockNode$Companion extends java.lang.Object @@ -4553,6 +4634,7 @@ public static final class net.corda.testing.dsl.TestTransactionDSLInterpreter$se @org.jetbrains.annotations.NotNull public net.corda.core.node.services.AttachmentStorage getAttachments() @org.jetbrains.annotations.NotNull public net.corda.core.cordapp.CordappProvider getCordappProvider() @org.jetbrains.annotations.NotNull public net.corda.core.node.services.IdentityService getIdentityService() + @org.jetbrains.annotations.NotNull public net.corda.core.node.NetworkParameters getNetworkParameters() @org.jetbrains.annotations.NotNull public net.corda.core.contracts.TransactionState loadState(net.corda.core.contracts.StateRef) @org.jetbrains.annotations.NotNull public Set loadStates(Set) ## @@ -4640,6 +4722,7 @@ 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 9daf267aed..436efdab22 100644 --- a/build.gradle +++ b/build.gradle @@ -116,7 +116,6 @@ 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' @@ -262,36 +261,6 @@ 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 - rpcSettings { - address "localhost:10013" - adminAddress "localhost:10023" - } - webPort 10014 - cordapps = [] - } - node { - name "O=Bank B,OU=corda,L=London,C=GB" - p2pAddress "localhost:10007" - rpcSettings { - address "localhost:10018" - adminAddress "localhost:10028" - } - webAddress "localhost:10009" - cordapps = [] - } -} - 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 41a41ed327..14889e536d 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,6 +10,7 @@ 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 @@ -101,6 +102,7 @@ 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 49ea962adb..c134a442b1 100644 --- a/constants.properties +++ b/constants.properties @@ -1,4 +1,4 @@ -gradlePluginsVersion=4.0.0 +gradlePluginsVersion=4.0.2 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 2f0135ddd1..9d0b745bf7 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/AttachmentConstraint.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/AttachmentConstraint.kt @@ -1,10 +1,14 @@ 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 @@ -20,6 +24,20 @@ 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 161d58ea62..e5ab36de93 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/ContractAttachment.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/ContractAttachment.kt @@ -6,7 +6,15 @@ 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 + * @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. */ @CordaSerializable -class ContractAttachment(val attachment: Attachment, val contract: ContractClassName) : Attachment by attachment +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 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 26145b38a1..9f706479e1 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/TransactionVerificationException.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/TransactionVerificationException.kt @@ -22,6 +22,9 @@ 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/internal/AbstractAttachment.kt b/core/src/main/kotlin/net/corda/core/internal/AbstractAttachment.kt index d48b98f290..adbc815537 100644 --- a/core/src/main/kotlin/net/corda/core/internal/AbstractAttachment.kt +++ b/core/src/main/kotlin/net/corda/core/internal/AbstractAttachment.kt @@ -13,6 +13,13 @@ 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 new file mode 100644 index 0000000000..677ca29a8d --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/internal/AttachmentWithContext.kt @@ -0,0 +1,22 @@ +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 28b44dfa4e..867a719949 100644 --- a/core/src/main/kotlin/net/corda/core/internal/FetchDataFlow.kt +++ b/core/src/main/kotlin/net/corda/core/internal/FetchDataFlow.kt @@ -147,7 +147,7 @@ class FetchAttachmentsFlow(requests: Set, override fun maybeWriteToDisk(downloaded: List) { for (attachment in downloaded) { - serviceHub.attachments.importAttachment(attachment.open()) + serviceHub.attachments.importAttachment(attachment.open(), "$P2P_UPLOADER:${otherSideSession.counterparty.name}", null) } } 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 23276c9c06..ac4dabae8d 100644 --- a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt @@ -306,8 +306,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(cordappProvider: CordappProvider, serializationContext: SerializationContext): WireTransaction { - return toWireTransactionWithContext(cordappProvider, serializationContext) +fun TransactionBuilder.toWireTransaction(services: ServicesForResolution, serializationContext: SerializationContext): WireTransaction { + return toWireTransactionWithContext(services, serializationContext) } /** Provide access to internal method for AttachmentClassLoaderTests */ 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 cca7c1433b..41091074a0 100644 --- a/core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt +++ b/core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt @@ -1,6 +1,7 @@ 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 @@ -9,11 +10,13 @@ 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 Maximum P2P message sent over the wire in bytes. + * @property maxMessageSize This is currently ignored. However, it will be wired up in a future release. * @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. @@ -24,7 +27,8 @@ data class NetworkParameters( val maxMessageSize: Int, val maxTransactionSize: Int, val modifiedTime: Instant, - val epoch: Int + val epoch: Int, + val whitelistedContractImplementations: Map> ) { 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 9332587f7a..81e657358e 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,6 +33,7 @@ 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 @@ -43,13 +44,14 @@ 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/transactions/LedgerTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt index 1881431114..dfda034eec 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt @@ -3,9 +3,11 @@ 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 @@ -27,7 +29,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( +data class LedgerTransaction @JvmOverloads constructor( /** The resolved input states which will be consumed/invalidated by the execution of this transaction. */ override val inputs: List>, override val outputs: List>, @@ -39,7 +41,8 @@ data class LedgerTransaction( override val id: SecureHash, override val notary: Party?, val timeWindow: TimeWindow?, - val privacySalt: PrivacySalt + val privacySalt: PrivacySalt, + private val networkParameters: NetworkParameters? = null ) : FullTransaction() { //DOCEND 1 init { @@ -87,17 +90,29 @@ data class LedgerTransaction( /** * 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 -> - // 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 stateAttachments = contractAttachments.filter { state.contract in it.allContracts } + if (stateAttachments.isEmpty()) throw TransactionVerificationException.MissingAttachmentRejection(id, state.contract) - if (!state.constraint.isSatisfiedBy(contractAttachment)) { + 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)) { throw TransactionVerificationException.ContractConstraintRejection(id, state.contract) } } @@ -403,5 +418,14 @@ data class LedgerTransaction( * @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 231f563314..3204e15580 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt @@ -8,8 +8,10 @@ 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 @@ -17,6 +19,7 @@ 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 @@ -42,18 +45,24 @@ 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( - notary = notary, - inputs = ArrayList(inputs), - attachments = ArrayList(attachments), - outputs = ArrayList(outputs), - commands = ArrayList(commands), - window = window, - privacySalt = privacySalt - ) + 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 + } // DOCSTART 1 /** A more convenient way to add items to this transaction that calls the add* methods for you based on type */ @@ -83,31 +92,52 @@ 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.cordappProvider) + fun toWireTransaction(services: ServicesForResolution): WireTransaction = toWireTransactionWithContext(services) - 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 + 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 // will be available when building the transaction. In exceptional cases the TransactionStates must be created // with an explicit [AttachmentConstraint] val resolvedOutputs = outputs.map { state -> - if (state.constraint is AutomaticHashConstraint) { - cordappProvider.getContractAttachmentID(state.contract)?.let { + when { + state.constraint !is AutomaticHashConstraint -> state + useWhitelistedByZoneAttachmentConstraint(state.contract, services.networkParameters) -> state.copy(constraint = WhitelistedByZoneAttachmentConstraint) + else -> services.cordappProvider.getContractAttachmentID(state.contract)?.let { state.copy(constraint = HashAttachmentConstraint(it)) } ?: throw MissingContractAttachments(listOf(state)) - } else { - state } } + return SerializationFactory.defaultFactory.withCurrentContext(serializationContext) { - WireTransaction(WireTransaction.createComponentGroups(inputs, resolvedOutputs, commands, attachments, notary, window), privacySalt) + WireTransaction(WireTransaction.createComponentGroups(inputStates(), resolvedOutputs, commands, attachments + makeContractAttachments(services.cordappProvider), 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) = toWireTransactionWithContext(services.cordappProvider, serializationContext).toLedgerTransaction(services) + internal fun toLedgerTransactionWithContext(services: ServicesForResolution, serializationContext: SerializationContext): LedgerTransaction { + return toWireTransactionWithContext(services, serializationContext).toLedgerTransaction(services) + } + @Throws(AttachmentResolutionException::class, TransactionResolutionException::class, TransactionVerificationException::class) fun verify(services: ServiceHub) { toLedgerTransaction(services).verify() @@ -117,6 +147,7 @@ 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 b583dc2623..46deccf2c6 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt @@ -5,6 +5,7 @@ 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 @@ -88,8 +89,7 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr resolveIdentity = { services.identityService.partyFromKey(it) }, resolveAttachment = { services.attachments.openAttachment(it) }, resolveStateRef = { services.loadState(it) }, - resolveContractAttachment = { services.cordappProvider.getContractAttachmentID(it.contract) }, - maxTransactionSize = services.networkParameters.maxTransactionSize + networkParameters = services.networkParameters ) } @@ -108,17 +108,16 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr resolveStateRef: (StateRef) -> TransactionState<*>?, resolveContractAttachment: (TransactionState) -> AttachmentId? ): LedgerTransaction { - return toLedgerTransactionInternal(resolveIdentity, resolveAttachment, resolveStateRef, resolveContractAttachment, 10485760) + return toLedgerTransactionInternal(resolveIdentity, resolveAttachment, resolveStateRef, null) } private fun toLedgerTransactionInternal( resolveIdentity: (PublicKey) -> Party?, resolveAttachment: (SecureHash) -> Attachment?, resolveStateRef: (StateRef) -> TransactionState<*>?, - resolveContractAttachment: (TransactionState) -> AttachmentId?, - maxTransactionSize: Int + networkParameters: NetworkParameters? ): LedgerTransaction { - // Look up public keys to authenticated identities. This is just a stub placeholder and will all change in future. + // Look up public keys to authenticated identities. val authenticatedArgs = commands.map { val parties = it.signers.mapNotNull { pk -> resolveIdentity(pk) } CommandWithParties(it.signers, parties, it.value) @@ -126,12 +125,9 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr val resolvedInputs = inputs.map { ref -> resolveStateRef(ref)?.let { StateAndRef(it, ref) } ?: throw TransactionResolutionException(ref.txhash) } - // 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) + 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) return ltx } @@ -266,19 +262,6 @@ 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 be483b9e0d..be99697d27 100644 --- a/core/src/test/kotlin/net/corda/core/contracts/DummyContractV2Tests.kt +++ b/core/src/test/kotlin/net/corda/core/contracts/DummyContractV2Tests.kt @@ -1,9 +1,11 @@ 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 @@ -34,7 +36,9 @@ class DummyContractV2Tests { @Test fun `upgrade from v1`() { val services = rigorousMock().also { - doReturn(rigorousMock()).whenever(it).cordappProvider + doReturn(rigorousMock().also { + doReturn(allOnesHash).whenever(it).getContractAttachmentID(any()) + }).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/transactions/LedgerTransactionQueryTests.kt b/core/src/test/kotlin/net/corda/core/transactions/LedgerTransactionQueryTests.kt index 9482764d99..f91eb9c344 100644 --- a/core/src/test/kotlin/net/corda/core/transactions/LedgerTransactionQueryTests.kt +++ b/core/src/test/kotlin/net/corda/core/transactions/LedgerTransactionQueryTests.kt @@ -10,7 +10,6 @@ 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 @@ -30,7 +29,7 @@ class LedgerTransactionQueryTests { @JvmField val testSerialization = SerializationEnvironmentRule() private val keyPair = generateKeyPair() - private val services = MockServices(emptyList(), CordaX500Name("MegaCorp", "London", "GB"), + private val services = MockServices(listOf("net.corda.testing.contracts"), 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 6b7e98617f..efdfd28256 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(emptyList(), MEGA_CORP.name, + private val ledgerServices = MockServices(listOf("net.corda.core.transactions", "net.corda.finance.contracts.asset"), 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 cd234f1138..cbda60638f 100644 --- a/core/src/test/kotlin/net/corda/core/transactions/TransactionTests.kt +++ b/core/src/test/kotlin/net/corda/core/transactions/TransactionTests.kt @@ -1,5 +1,7 @@ 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 @@ -112,7 +114,9 @@ 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(), DummyContract.PROGRAM_ID)) + val attachments = listOf(ContractAttachment(rigorousMock().also { + doReturn(SecureHash.zeroHash).whenever(it).id + }, DummyContract.PROGRAM_ID)) val id = SecureHash.randomSHA256() val timeWindow: TimeWindow? = null val privacySalt: PrivacySalt = PrivacySalt() 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 f531a8ce17..0fe590c86a 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,46 +7,48 @@ 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.node.MockServices; import net.corda.testing.core.TestIdentity; +import net.corda.testing.node.MockServices; +import org.junit.Before; import org.junit.Test; -import java.security.PublicKey; import java.time.temporal.ChronoUnit; -import static java.util.Collections.emptyList; -import static net.corda.core.crypto.Crypto.generateKeyPair; +import static java.util.Collections.singletonList; 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 final byte[] defaultRef = {123}; + private MockServices ledgerServices; - // DOCSTART 11 - private final MockServices ledgerServices = new MockServices( - // A list of packages to scan for cordapps - emptyList(), - // 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()) + @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 + // DOCEND 11 + } @SuppressWarnings("unused") // DOCSTART 12 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 686fac41eb..42d7db3d2a 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 @@ -1,7 +1,11 @@ package net.corda.docs.tutorial.testdsl +import com.nhaarman.mockito_kotlin.doReturn +import com.nhaarman.mockito_kotlin.whenever import net.corda.core.contracts.TransactionVerificationException +import net.corda.core.crypto.generateKeyPair import net.corda.core.identity.CordaX500Name +import net.corda.core.node.services.IdentityService import net.corda.core.utilities.days import net.corda.finance.DOLLARS import net.corda.finance.`issued by` @@ -11,6 +15,7 @@ import net.corda.finance.contracts.ICommercialPaperState import net.corda.finance.contracts.asset.CASH import net.corda.finance.contracts.asset.Cash 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 @@ -33,20 +38,20 @@ class CommercialPaperTest { // DOCSTART 11 private val ledgerServices = MockServices( // A list of packages to scan for cordapps - cordappPackages = emptyList(), + 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] - initialIdentity = 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 - identityService = makeTestIdentityService(megaCorp.identity) - ) + 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) + }) // DOCEND 11 - @Suppress("unused") // DOCSTART 12 + @Suppress("unused") private val simpleLedgerServices = MockServices( // This is the identity of the node megaCorp, 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 87f481396b..fa32531bfd 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(emptyList(), MEGA_CORP.name, identityService) + private val ledgerServices get() = MockServices(listOf("net.corda.finance.contracts.asset", "net.corda.testing.contracts"), MEGA_CORP.name, identityService) private fun cashObligationTestRoots( group: LedgerDSL ) = group.apply { diff --git a/node-api/build.gradle b/node-api/build.gradle index bf674d7b32..6cc03bd117 100644 --- a/node-api/build.gradle +++ b/node-api/build.gradle @@ -34,6 +34,9 @@ dependencies { // For AMQP serialisation. compile "org.apache.qpid:proton-j:0.21.0" + // 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' 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 20d2efca63..a499bbfc1b 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,7 +1,9 @@ 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 @@ -31,6 +33,10 @@ 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 new file mode 100644 index 0000000000..774e30e2c0 --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/ClassloaderUtils.kt @@ -0,0 +1,41 @@ +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/network/NetworkBootstrapper.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt index b186a690fa..cfd038a208 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,25 +1,33 @@ 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.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 @@ -43,15 +51,17 @@ class NetworkBootstrapper { ) private const val LOGS_DIR_NAME = "logs" + private const val WHITELIST_FILE_NAME = "whitelist.txt" @JvmStatic fun main(args: Array) { - val arg = args.singleOrNull() ?: throw IllegalArgumentException("Expecting single argument which is the nodes' parent directory") - NetworkBootstrapper().bootstrap(Paths.get(arg).toAbsolutePath().normalize()) + 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) } } - fun bootstrap(directory: Path) { + fun bootstrap(directory: Path, cordapps: List?) { directory.createDirectories() println("Bootstrapping local network in $directory") generateDirectoriesIfNeeded(directory) @@ -68,7 +78,10 @@ class NetworkBootstrapper { println("Gathering notary identities") val notaryInfos = gatherNotaryInfos(nodeInfoFiles) println("Notary identities to be used in network-parameters file: ${notaryInfos.joinToString("; ") { it.prettyPrint() }}") - installNetworkParameters(notaryInfos, nodeDirs) + val mergedWhiteList = generateWhitelist(directory / WHITELIST_FILE_NAME, cordapps?.distinct()) + println("Updating whitelist.") + overwriteWhitelist(directory / WHITELIST_FILE_NAME, mergedWhiteList) + installNetworkParameters(notaryInfos, nodeDirs, mergedWhiteList) println("Bootstrapping complete!") } finally { _contextSerializationEnv.set(null) @@ -84,8 +97,7 @@ class NetworkBootstrapper { for (confFile in confFiles) { val nodeName = confFile.fileName.toString().removeSuffix(".conf") println("Generating directory for $nodeName") - val nodeDir = (directory / nodeName) - if (!nodeDir.exists()) { nodeDir.createDirectory() } + val nodeDir = (directory / nodeName).createDirectories() confFile.moveTo(nodeDir / "node.conf", StandardCopyOption.REPLACE_EXISTING) Files.copy(cordaJar, (nodeDir / "corda.jar"), StandardCopyOption.REPLACE_EXISTING) } @@ -157,7 +169,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) { + private fun installNetworkParameters(notaryInfos: List, nodeDirs: List, whitelist: Map>) { // TODO Add config for minimumPlatformVersion, maxMessageSize and maxTransactionSize val copier = NetworkParametersCopier(NetworkParameters( minimumPlatformVersion = 1, @@ -165,12 +177,58 @@ class NetworkBootstrapper { modifiedTime = Instant.now(), maxMessageSize = 10485760, maxTransactionSize = Int.MAX_VALUE, - epoch = 1 + epoch = 1, + whitelistedContractImplementations = whitelist ), 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/serialization/amqp/custom/ContractAttachmentSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/ContractAttachmentSerializer.kt index 2440114a4c..917057f428 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) + return ContractAttachmentProxy(GeneratedAttachment(bytes), obj.contract, obj.additionalContracts, obj.uploader) } override fun fromProxy(proxy: ContractAttachmentProxy): ContractAttachment { - return ContractAttachment(proxy.attachment, proxy.contract) + return ContractAttachment(proxy.attachment, proxy.contract, proxy.contracts, proxy.uploader) } - data class ContractAttachmentProxy(val attachment: Attachment, val contract: ContractClassName) + data class ContractAttachmentProxy(val attachment: Attachment, val contract: ContractClassName, val contracts: Set, val uploader: String?) } \ 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 a8d2e48649..63a08fd3c4 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,6 +12,7 @@ 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 @@ -206,29 +207,34 @@ 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) + return ContractAttachment(lazyAttachment, contract, additionalContracts, uploader) } else { val attachment = GeneratedAttachment(input.readBytesWithLength()) val contract = input.readString() - - return ContractAttachment(attachment, contract) + val additionalContracts = kryo.readClassAndObject(input) as Set + val uploader = input.readString() + return ContractAttachment(attachment, contract, additionalContracts, uploader) } } } 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 c264264bc6..19c07d1efd 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,6 +13,7 @@ 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 @@ -59,7 +60,8 @@ class AttachmentsClassLoaderStaticContractTests { } private val serviceHub = rigorousMock().also { - doReturn(CordappProviderImpl(CordappLoader.createWithTestPackages(listOf("net.corda.nodeapi.internal")), MockCordappConfigProvider(), MockAttachmentStorage())).whenever(it).cordappProvider + doReturn(CordappProviderImpl(CordappLoader.createWithTestPackages(listOf("net.corda.nodeapi.internal")), MockCordappConfigProvider(), MockAttachmentStorage(), testNetworkParameters().whitelistedContractImplementations)).whenever(it).cordappProvider + doReturn(testNetworkParameters()).whenever(it).networkParameters } @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 5ee6355980..eeafa28630 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,6 +18,7 @@ import net.corda.nodeapi.DummyContractBackdoor import net.corda.nodeapi.internal.serialization.SerializeAsTokenContextImpl import net.corda.nodeapi.internal.serialization.attachmentsClassLoaderEnabledPropertyName import net.corda.nodeapi.internal.serialization.withTokenContext +import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.core.DUMMY_NOTARY_NAME import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.TestIdentity @@ -58,12 +59,15 @@ class AttachmentsClassLoaderTests { @JvmField val testSerialization = SerializationEnvironmentRule() private val attachments = MockAttachmentStorage() - private val cordappProvider = CordappProviderImpl(CordappLoader.createDevMode(listOf(ISOLATED_CONTRACTS_JAR_PATH)), MockCordappConfigProvider(), attachments) + private val networkParameters = testNetworkParameters() + private val cordappProvider = CordappProviderImpl(CordappLoader.createDevMode(listOf(ISOLATED_CONTRACTS_JAR_PATH)), MockCordappConfigProvider(), attachments, networkParameters.whitelistedContractImplementations) 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 @@ -279,7 +283,7 @@ class AttachmentsClassLoaderTests { .withClassLoader(child) val bytes = run { - val wireTransaction = tx.toWireTransaction(cordappProvider, context) + val wireTransaction = tx.toWireTransaction(serviceHub, context) wireTransaction.serialize(context = context) } val copiedWireTransaction = bytes.deserialize(context = context) @@ -303,7 +307,7 @@ class AttachmentsClassLoaderTests { val outboundContext = SerializationFactory.defaultFactory.defaultContext .withServiceHub(serviceHub) .withClassLoader(child) - val wireTransaction = tx.toWireTransaction(cordappProvider, outboundContext) + val wireTransaction = tx.toWireTransaction(serviceHub, outboundContext) wireTransaction.serialize(context = outboundContext) } // use empty attachmentStorage 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 01e296324d..3745da55d8 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,6 +43,7 @@ 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()) } @@ -58,6 +59,7 @@ 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 87157fdaa9..90523d6f0e 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,6 +6,7 @@ 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 @@ -195,7 +196,7 @@ class CordaClassResolverTests { CordaClassResolver(emptyWhitelistContext).getRegistration(DefaultSerializable::class.java) } - private fun importJar(storage: AttachmentStorage) = AttachmentsClassLoaderTests.ISOLATED_CONTRACTS_JAR_PATH.openStream().use { storage.importAttachment(it) } + private fun importJar(storage: AttachmentStorage, uploader: String = DEPLOYED_CORDAPP_UPLOADER) = AttachmentsClassLoaderTests.ISOLATED_CONTRACTS_JAR_PATH.openStream().use { storage.importAttachment(it, uploader, "") } @Test(expected = KryoException::class) fun `Annotation does not work in conjunction with AttachmentClassLoader annotation`() { @@ -206,6 +207,15 @@ 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/amqp/EvolvabilityTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt index b48ae16941..74b175ae48 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 @@ -523,7 +523,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 ) + 3, listOf(NotaryInfo(DUMMY_NOTARY, false)),1000, 1000, Instant.EPOCH, 1, emptyMap()) 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 6d20000170..f8aa4fc768 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 @@ -1071,6 +1071,7 @@ 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()) } diff --git a/node/build.gradle b/node/build.gradle index 8b06b452c4..13dedeea6a 100644 --- a/node/build.gradle +++ b/node/build.gradle @@ -167,9 +167,6 @@ 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 b419fdefcc..4a911cb0d3 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 @@ -49,7 +49,7 @@ class AttachmentLoadingTests { @JvmField val testSerialization = SerializationEnvironmentRule() private val attachments = MockAttachmentStorage() - private val provider = CordappProviderImpl(CordappLoader.createDevMode(listOf(isolatedJAR)), MockCordappConfigProvider(), attachments) + private val provider = CordappProviderImpl(CordappLoader.createDevMode(listOf(isolatedJAR)), MockCordappConfigProvider(), attachments, testNetworkParameters().whitelistedContractImplementations) 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 e4ca31dc7d..60a03f6822 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 @@ -48,7 +48,7 @@ class BFTNotaryServiceTests { @Before fun before() { - mockNet = InternalMockNetwork(emptyList()) + mockNet = InternalMockNetwork(listOf("net.corda.testing.contracts")) } @After 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 fbf823fba2..ca39b2b660 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -556,7 +556,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) + val cordappProvider = CordappProviderImpl(cordappLoader, CordappConfigFileProvider(), attachments, networkParameters.whitelistedContractImplementations) 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 258abd8692..6e46fb0d40 100644 --- a/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt +++ b/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt @@ -13,6 +13,7 @@ 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 @@ -189,7 +190,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) + services.attachments.importAttachment(jar, RPC_UPLOADER, null) } } 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 a25d872124..06efde8cd7 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,10 +1,12 @@ 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 @@ -17,7 +19,10 @@ 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) : SingletonSerializeAsToken(), CordappProviderInternal { +open class CordappProviderImpl(private val cordappLoader: CordappLoader, + private val cordappConfigProvider: CordappConfigProvider, + attachmentStorage: AttachmentStorage, + private val whitelistedContractImplementations: Map>) : SingletonSerializeAsToken(), CordappProviderInternal { companion object { private val log = loggerFor() @@ -25,6 +30,34 @@ open class CordappProviderImpl(private val cordappLoader: CordappLoader, private 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 @@ -42,11 +75,6 @@ open class CordappProviderImpl(private val cordappLoader: CordappLoader, private 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 * @@ -55,11 +83,16 @@ open class CordappProviderImpl(private val cordappLoader: CordappLoader, private */ fun getCordappAttachmentId(cordapp: Cordapp): SecureHash? = cordappAttachments.inverse().get(cordapp.jarPath) - 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() - } + 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() /** * Get the current cordapp context for the given CorDapp 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 784499509d..cd9f7d37f5 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,8 +8,11 @@ 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 @@ -24,6 +27,7 @@ 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 @@ -85,7 +89,14 @@ class NodeAttachmentService( var uploader: String? = null, @Column(name = "filename", updatable = false) - var filename: String? = null + 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 ) : Serializable @VisibleForTesting @@ -196,23 +207,31 @@ 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().size else 0 + weigher = object : Weigher>> { + override fun weigh(key: SecureHash, value: Optional>): Int { + return key.size + if (value.isPresent) value.get().second.size else 0 } }, loadFunction = { Optional.ofNullable(loadAttachmentContent(it)) } ) - private fun loadAttachmentContent(id: SecureHash): ByteArray? { + private fun loadAttachmentContent(id: SecureHash): Pair? { val attachment = currentDBSession().get(NodeAttachmentService.DBAttachment::class.java, id.toString()) - return attachment?.content + ?: 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) } - private val attachmentCache = NonInvalidatingCache>( attachmentCacheBound, defaultCordaCacheConcurrencyLevel, @@ -222,16 +241,7 @@ class NodeAttachmentService( private fun createAttachment(key: SecureHash): Attachment? { val content = attachmentContentCache.get(key) if (content.isPresent) { - return AttachmentImpl( - key, - { - attachmentContentCache - .get(key) - .orElseThrow { - IllegalArgumentException("No attachement impl should have been created for non existent content") - } - }, - checkAttachmentsOnLoad) + return content.get().first } // if no attachement has been found, we don't want to cache that - it might arrive later attachmentContentCache.invalidate(key) @@ -248,10 +258,10 @@ class NodeAttachmentService( } override fun importAttachment(jar: InputStream): AttachmentId { - return import(jar, null, null) + return import(jar, UNKNOWN_UPLOADER, 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) } @@ -263,47 +273,39 @@ class NodeAttachmentService( return Pair(id, bytes) } - override fun hasAttachment(attachmentId: AttachmentId): Boolean { - val session = currentDBSession() - val criteriaBuilder = session.criteriaBuilder - val criteriaQuery = criteriaBuilder.createQuery(Long::class.java) - val attachments = criteriaQuery.from(NodeAttachmentService.DBAttachment::class.java) - criteriaQuery.select(criteriaBuilder.count(criteriaQuery.from(NodeAttachmentService.DBAttachment::class.java))) - criteriaQuery.where(criteriaBuilder.equal(attachments.get(DBAttachment::attId.name), attachmentId.toString())) - return (session.createQuery(criteriaQuery).singleResult > 0) - } + override fun hasAttachment(attachmentId: AttachmentId): Boolean = + currentDBSession().find(NodeAttachmentService.DBAttachment::class.java, attachmentId.toString()) != null // TODO: PLT-147: The attachment should be randomised to prevent brute force guessing and thus privacy leaks. private fun import(jar: InputStream, uploader: String?, filename: String?): AttachmentId { - require(jar !is JarInputStream) + return withContractsInJar(jar) { contractClassNames, inputStream -> + require(inputStream !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(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()) + 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()) + } } } - override fun importOrGetAttachment(jar: InputStream): AttachmentId { - try { - return importAttachment(jar) - } - catch (faee: java.nio.file.FileAlreadyExistsException) { - return AttachmentId.parse(faee.message!!) - } + override fun importOrGetAttachment(jar: InputStream): AttachmentId = try { + importAttachment(jar) + } catch (faee: java.nio.file.FileAlreadyExistsException) { + AttachmentId.parse(faee.message!!) } override fun queryAttachments(criteria: AttachmentQueryCriteria, sorting: AttachmentSort?): List { @@ -328,5 +330,4 @@ class NodeAttachmentService( return results.map { AttachmentId.parse(it.attId) } } - } 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 9d12bffb05..ebfb3795b2 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,14 +2,18 @@ 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 +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull import org.junit.Before import org.junit.Test +import java.net.URL class CordappProviderImplTests { private companion object { @@ -25,6 +29,7 @@ class CordappProviderImplTests { } private lateinit var attachmentStore: AttachmentStorage + private val whitelistedContractImplementations = testNetworkParameters().whitelistedContractImplementations @Before fun setup() { @@ -33,44 +38,40 @@ class CordappProviderImplTests { @Test fun `isolated jar is loaded into the attachment store`() { - val loader = CordappLoader.createDevMode(listOf(isolatedJAR)) - val provider = CordappProviderImpl(loader, stubConfigProvider, attachmentStore) + val provider = newCordappProvider(isolatedJAR) val maybeAttachmentId = provider.getCordappAttachmentId(provider.cordapps.first()) - Assert.assertNotNull(maybeAttachmentId) - Assert.assertNotNull(attachmentStore.openAttachment(maybeAttachmentId!!)) + assertNotNull(maybeAttachmentId) + assertNotNull(attachmentStore.openAttachment(maybeAttachmentId!!)) } @Test fun `empty jar is not loaded into the attachment store`() { - val loader = CordappLoader.createDevMode(listOf(emptyJAR)) - val provider = CordappProviderImpl(loader, stubConfigProvider, attachmentStore) - Assert.assertNull(provider.getCordappAttachmentId(provider.cordapps.first())) + val provider = newCordappProvider(emptyJAR) + assertNull(provider.getCordappAttachmentId(provider.cordapps.first())) } @Test fun `test that we find a cordapp class that is loaded into the store`() { - val loader = CordappLoader.createDevMode(listOf(isolatedJAR)) - val provider = CordappProviderImpl(loader, stubConfigProvider, attachmentStore) + val provider = newCordappProvider(isolatedJAR) val className = "net.corda.finance.contracts.isolated.AnotherDummyContract" val expected = provider.cordapps.first() val actual = provider.getCordappForClass(className) - Assert.assertNotNull(actual) - Assert.assertEquals(expected, actual) + assertNotNull(actual) + assertEquals(expected, actual) } @Test - fun `test that we find an attachment for a cordapp contract class`() { - val loader = CordappLoader.createDevMode(listOf(isolatedJAR)) - val provider = CordappProviderImpl(loader, stubConfigProvider, attachmentStore) + fun `test that we find an attachment for a cordapp contrat class`() { + val provider = newCordappProvider(isolatedJAR) val className = "net.corda.finance.contracts.isolated.AnotherDummyContract" val expected = provider.getAppContext(provider.cordapps.first()).attachmentId val actual = provider.getContractAttachmentID(className) - Assert.assertNotNull(actual) - Assert.assertEquals(actual!!, expected) + assertNotNull(actual) + assertEquals(actual!!, expected) } @Test @@ -78,10 +79,15 @@ class CordappProviderImplTests { val configProvider = MockCordappConfigProvider() configProvider.cordappConfigs.put(isolatedCordappName, validConfig) val loader = CordappLoader.createDevMode(listOf(isolatedJAR)) - val provider = CordappProviderImpl(loader, configProvider, attachmentStore) + val provider = CordappProviderImpl(loader, configProvider, attachmentStore, whitelistedContractImplementations) 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/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 3dd0e0f8be..f553047f7c 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 @@ -62,7 +62,7 @@ open class MockServices private constructor( cordappLoader: CordappLoader, override val validatedTransactions: WritableTransactionStorage, override val identityService: IdentityService, - override val networkParameters: NetworkParameters, + final override val networkParameters: NetworkParameters, private val initialIdentity: TestIdentity, private val moreKeys: Array ) : ServiceHub, StateLoader by validatedTransactions { @@ -226,7 +226,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) + private val mockCordappProvider: MockCordappProvider = MockCordappProvider(cordappLoader, attachments, networkParameters.whitelistedContractImplementations) 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/network/NetworkMapServer.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt index dbacebb931..4d0e952ce2 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) + private val stubNetworkParameters = NetworkParameters(1, emptyList(), 10485760, Int.MAX_VALUE, Instant.now(), 10, emptyMap()) } 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 322e9e90b7..5f86f26b34 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,6 +19,7 @@ fun testNetworkParameters( modifiedTime = modifiedTime, maxMessageSize = maxMessageSize, maxTransactionSize = maxTransactionSize, - epoch = epoch + epoch = epoch, + whitelistedContractImplementations = emptyMap() ) } \ 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 4638d73fb3..e04b2aa580 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,6 +2,7 @@ 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 @@ -14,9 +15,10 @@ import java.util.* class MockCordappProvider( cordappLoader: CordappLoader, attachmentStorage: AttachmentStorage, - val cordappConfigProvider: MockCordappConfigProvider = MockCordappConfigProvider() -) : CordappProviderImpl(cordappLoader, cordappConfigProvider, attachmentStorage) { - constructor(cordappLoader: CordappLoader, attachmentStorage: AttachmentStorage) : this(cordappLoader, attachmentStorage, MockCordappConfigProvider()) + whitelistedContractImplementations: Map>, + cordappConfigProvider: MockCordappConfigProvider = MockCordappConfigProvider() +) : CordappProviderImpl(cordappLoader, cordappConfigProvider, attachmentStorage, whitelistedContractImplementations) { + constructor(cordappLoader: CordappLoader, attachmentStorage: AttachmentStorage, whitelistedContractImplementations: Map>) : this(cordappLoader, attachmentStorage, whitelistedContractImplementations, MockCordappConfigProvider()) val cordappRegistry = mutableListOf>() @@ -33,20 +35,21 @@ class MockCordappProvider( customSchemas = emptySet(), jarPath = Paths.get("").toUri().toURL()) if (cordappRegistry.none { it.first.contractClassNames.contains(contractClassName) }) { - cordappRegistry.add(Pair(cordapp, findOrImportAttachment(contractClassName.toByteArray(), attachments))) + cordappRegistry.add(Pair(cordapp, findOrImportAttachment(listOf(contractClassName), 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(data: ByteArray, attachments: MockAttachmentStorage): AttachmentId { + private fun findOrImportAttachment(contractClassNames: List, data: ByteArray, attachments: MockAttachmentStorage): AttachmentId { val existingAttachment = attachments.files.filter { - Arrays.equals(it.value, data) + Arrays.equals(it.value.second, data) } return if (!existingAttachment.isEmpty()) { existingAttachment.keys.first() } else { - attachments.importAttachment(data.inputStream()) + attachments.importContractAttachment(contractClassNames, TEST_UPLOADER, 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 f827efb39d..dc96dfcd7c 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,14 +1,18 @@ 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.* @@ -24,31 +28,17 @@ class MockAttachmentStorage : AttachmentStorage, SingletonSerializeAsToken() { } } - override fun importAttachment(jar: InputStream): AttachmentId { - // JIS makes read()/readBytes() return bytes of the current file, but we want to hash the entire container here. - require(jar !is JarInputStream) + val files = HashMap>() - val bytes = getBytes(jar) + override fun importAttachment(jar: InputStream): AttachmentId = importAttachment(jar, UNKNOWN_UPLOADER, null) - val sha256 = bytes.sha256() - if (!files.containsKey(sha256)) { - files[sha256] = bytes + override fun importAttachment(jar: InputStream, uploader: String, filename: String?): AttachmentId { + return withContractsInJar(jar) { contractClassNames, inputStream -> + importAttachmentInternal(inputStream, uploader, filename, contractClassNames) } - return sha256 } - 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 openAttachment(id: SecureHash): Attachment? = files[id]?.first override fun queryAttachments(criteria: AttachmentQueryCriteria, sorting: AttachmentSort?): List { throw NotImplementedError("Querying for attachments not implemented") @@ -56,11 +46,6 @@ 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) @@ -68,4 +53,25 @@ 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 bae600a2e0..e96c4b847d 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,7 +145,8 @@ class NodeController(check: atRuntime = ::checkExists) : Controller() { modifiedTime = Instant.now(), maxMessageSize = 10485760, maxTransactionSize = Int.MAX_VALUE, - epoch = 1 + epoch = 1, + whitelistedContractImplementations = emptyMap() )) notaryIdentity = identity networkParametersCopier = parametersCopier 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 a07bc0c1ea..bd33ea9efc 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 }.associateBy { it.contract } + attachments.mapNotNull { it as? ContractAttachment }.flatMap { attch-> attch.allContracts.map { it to attch } }.toMap() } 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 6de9c38700..16b3d138a2 100644 --- a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierTests.kt +++ b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierTests.kt @@ -17,6 +17,7 @@ import net.corda.testing.core.DUMMY_NOTARY_NAME import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.driver.internal.NodeHandleInternal import net.corda.testing.node.NotarySpec +import org.junit.Ignore import org.junit.Rule import org.junit.Test import java.util.* @@ -133,6 +134,7 @@ class VerifierTests { } } + @Ignore("CORDA-1022") @Test fun `single verifier works with a node`() { verifierDriver(