Merge pull request #483 from corda/merges/february-23-15-30

Merges/february 23 15 30
This commit is contained in:
Ben Abineri 2018-02-26 09:33:16 +00:00 committed by GitHub
commit 908a614888
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
99 changed files with 1924 additions and 676 deletions

View File

@ -14,8 +14,6 @@
public void setMessage(String) public void setMessage(String)
public void setOriginalExceptionClassName(String) public void setOriginalExceptionClassName(String)
## ##
public @interface net.corda.core.CordaInternal
##
public final class net.corda.core.CordaOID extends java.lang.Object 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" @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 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 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) @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 boolean isSatisfiedBy(net.corda.core.contracts.Attachment)
public static final net.corda.core.contracts.AlwaysAcceptAttachmentConstraint INSTANCE 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 java.io.InputStream open()
@org.jetbrains.annotations.NotNull public abstract jar.JarInputStream openAsJAR() @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) 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 @net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.AttachmentResolutionException extends net.corda.core.flows.FlowException
public <init>(net.corda.core.crypto.SecureHash) public <init>(net.corda.core.crypto.SecureHash)
@org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getHash() @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 boolean isSatisfiedBy(net.corda.core.contracts.Attachment)
public static final net.corda.core.contracts.AutomaticHashConstraint INSTANCE 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 @net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.ContractAttachment extends java.lang.Object implements net.corda.core.contracts.Attachment
public <init>(net.corda.core.contracts.Attachment, String) public <init>(net.corda.core.contracts.Attachment, String)
public <init>(net.corda.core.contracts.Attachment, String, Set)
public <init>(net.corda.core.contracts.Attachment, String, Set, String)
public void extractFile(String, java.io.OutputStream) 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 net.corda.core.contracts.Attachment getAttachment()
@org.jetbrains.annotations.NotNull public final String getContract() @org.jetbrains.annotations.NotNull public final String getContract()
@org.jetbrains.annotations.NotNull public net.corda.core.crypto.SecureHash getId() @org.jetbrains.annotations.NotNull public net.corda.core.crypto.SecureHash getId()
@org.jetbrains.annotations.NotNull public List getSigners() @org.jetbrains.annotations.NotNull public List getSigners()
public int getSize() 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 java.io.InputStream open()
@org.jetbrains.annotations.NotNull public jar.JarInputStream openAsJAR() @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 @net.corda.core.serialization.CordaSerializable public interface net.corda.core.contracts.ContractState
@org.jetbrains.annotations.NotNull public abstract List getParticipants() @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 Collection getExitKeys()
@org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.FungibleAsset withNewOwnerAndAmount(net.corda.core.contracts.Amount, net.corda.core.identity.AbstractParty) @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 <init>(net.corda.core.crypto.SecureHash) public <init>(net.corda.core.crypto.SecureHash)
@org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component1() @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component1()
@org.jetbrains.annotations.NotNull public final net.corda.core.contracts.HashAttachmentConstraint copy(net.corda.core.crypto.SecureHash) @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 @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() @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 <init>(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 @net.corda.core.serialization.CordaSerializable public static final class net.corda.core.contracts.TransactionVerificationException$ContractConstraintRejection extends net.corda.core.contracts.TransactionVerificationException
public <init>(net.corda.core.crypto.SecureHash, String) public <init>(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 String getLegacyContract()
@org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.ContractState upgrade(net.corda.core.contracts.ContractState) @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 @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 getContractClassNames()
@org.jetbrains.annotations.NotNull public abstract List getCordappClasses() @org.jetbrains.annotations.NotNull public abstract List getCordappClasses()
@ -1817,14 +1828,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) @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 @net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.NetworkParameters extends java.lang.Object
public <init>(int, List, int, int, java.time.Instant, int) public <init>(int, List, int, int, java.time.Instant, int, Map)
public final int component1() public final int component1()
@org.jetbrains.annotations.NotNull public final List component2() @org.jetbrains.annotations.NotNull public final List component2()
public final int component3() public final int component3()
public final int component4() public final int component4()
@org.jetbrains.annotations.NotNull public final java.time.Instant component5() @org.jetbrains.annotations.NotNull public final java.time.Instant component5()
public final int component6() 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 boolean equals(Object)
public final int getEpoch() public final int getEpoch()
public final int getMaxMessageSize() public final int getMaxMessageSize()
@ -1832,6 +1844,7 @@ public @interface net.corda.core.messaging.RPCReturnsObservables
public final int getMinimumPlatformVersion() public final int getMinimumPlatformVersion()
@org.jetbrains.annotations.NotNull public final java.time.Instant getModifiedTime() @org.jetbrains.annotations.NotNull public final java.time.Instant getModifiedTime()
@org.jetbrains.annotations.NotNull public final List getNotaries() @org.jetbrains.annotations.NotNull public final List getNotaries()
@org.jetbrains.annotations.NotNull public final Map getWhitelistedContractImplementations()
public int hashCode() public int hashCode()
public String toString() public String toString()
## ##
@ -1909,9 +1922,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 @net.corda.core.DoNotImplement public interface net.corda.core.node.services.AttachmentStorage
public abstract boolean hasAttachment(net.corda.core.crypto.SecureHash) 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 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.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) @org.jetbrains.annotations.NotNull public abstract List queryAttachments(net.corda.core.node.services.vault.AttachmentQueryCriteria, net.corda.core.node.services.vault.AttachmentSort)
## ##
@ -2873,6 +2886,9 @@ public @interface net.corda.core.serialization.CordaSerializationTransformRename
public @interface net.corda.core.serialization.DeprecatedConstructorForDeserialization public @interface net.corda.core.serialization.DeprecatedConstructorForDeserialization
public abstract int version() public abstract int version()
## ##
@net.corda.core.DoNotImplement public interface net.corda.core.serialization.EncodingWhitelist
public abstract boolean acceptEncoding(net.corda.core.serialization.SerializationEncoding)
##
@net.corda.core.serialization.CordaSerializable public final class net.corda.core.serialization.MissingAttachmentsException extends net.corda.core.CordaException @net.corda.core.serialization.CordaSerializable public final class net.corda.core.serialization.MissingAttachmentsException extends net.corda.core.CordaException
public <init>(List) public <init>(List)
@org.jetbrains.annotations.NotNull public final List getIds() @org.jetbrains.annotations.NotNull public final List getIds()
@ -2891,8 +2907,10 @@ public final class net.corda.core.serialization.ObjectWithCompatibleContext exte
public final class net.corda.core.serialization.SerializationAPIKt extends java.lang.Object public final class net.corda.core.serialization.SerializationAPIKt extends java.lang.Object
@org.jetbrains.annotations.NotNull public static final net.corda.core.serialization.SerializedBytes serialize(Object, net.corda.core.serialization.SerializationFactory, net.corda.core.serialization.SerializationContext) @org.jetbrains.annotations.NotNull public static final net.corda.core.serialization.SerializedBytes serialize(Object, net.corda.core.serialization.SerializationFactory, net.corda.core.serialization.SerializationContext)
## ##
public interface net.corda.core.serialization.SerializationContext @net.corda.core.DoNotImplement public interface net.corda.core.serialization.SerializationContext
@org.jetbrains.annotations.NotNull public abstract ClassLoader getDeserializationClassLoader() @org.jetbrains.annotations.NotNull public abstract ClassLoader getDeserializationClassLoader()
@org.jetbrains.annotations.Nullable public abstract net.corda.core.serialization.SerializationEncoding getEncoding()
@org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.EncodingWhitelist getEncodingWhitelist()
public abstract boolean getObjectReferencesEnabled() public abstract boolean getObjectReferencesEnabled()
@org.jetbrains.annotations.NotNull public abstract net.corda.core.utilities.ByteSequence getPreferredSerializationVersion() @org.jetbrains.annotations.NotNull public abstract net.corda.core.utilities.ByteSequence getPreferredSerializationVersion()
@org.jetbrains.annotations.NotNull public abstract Map getProperties() @org.jetbrains.annotations.NotNull public abstract Map getProperties()
@ -2900,6 +2918,7 @@ public interface net.corda.core.serialization.SerializationContext
@org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.ClassWhitelist getWhitelist() @org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.ClassWhitelist getWhitelist()
@org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.SerializationContext withAttachmentsClassLoader(List) @org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.SerializationContext withAttachmentsClassLoader(List)
@org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.SerializationContext withClassLoader(ClassLoader) @org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.SerializationContext withClassLoader(ClassLoader)
@org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.SerializationContext withEncoding(net.corda.core.serialization.SerializationEncoding)
@org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.SerializationContext withPreferredSerializationVersion(net.corda.core.utilities.ByteSequence) @org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.SerializationContext withPreferredSerializationVersion(net.corda.core.utilities.ByteSequence)
@org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.SerializationContext withProperty(Object, Object) @org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.SerializationContext withProperty(Object, Object)
@org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.SerializationContext withWhitelisted(Class) @org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.SerializationContext withWhitelisted(Class)
@ -2923,6 +2942,8 @@ public final class net.corda.core.serialization.SerializationDefaults extends ja
@org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializationContext getSTORAGE_CONTEXT() @org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializationContext getSTORAGE_CONTEXT()
public static final net.corda.core.serialization.SerializationDefaults INSTANCE public static final net.corda.core.serialization.SerializationDefaults INSTANCE
## ##
@net.corda.core.DoNotImplement public interface net.corda.core.serialization.SerializationEncoding
##
public abstract class net.corda.core.serialization.SerializationFactory extends java.lang.Object public abstract class net.corda.core.serialization.SerializationFactory extends java.lang.Object
public <init>() public <init>()
public final Object asCurrent(kotlin.jvm.functions.Function1) public final Object asCurrent(kotlin.jvm.functions.Function1)
@ -3042,6 +3063,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 @net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.transactions.LedgerTransaction extends net.corda.core.transactions.FullTransaction
public <init>(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 <init>(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 <init>(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 commandsOfType(Class)
@org.jetbrains.annotations.NotNull public final List component1() @org.jetbrains.annotations.NotNull public final List component1()
@org.jetbrains.annotations.NotNull public final List component2() @org.jetbrains.annotations.NotNull public final List component2()
@ -3052,6 +3074,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.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.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)
@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) public boolean equals(Object)
@org.jetbrains.annotations.NotNull public final List filterCommands(Class, function.Predicate) @org.jetbrains.annotations.NotNull public final List filterCommands(Class, function.Predicate)
@org.jetbrains.annotations.NotNull public final List filterInRefs(Class, function.Predicate) @org.jetbrains.annotations.NotNull public final List filterInRefs(Class, function.Predicate)
@ -4118,7 +4141,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 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.NodeInfo getMyInfo()
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.NetworkMapCache getNetworkMapCache() @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.core.node.NetworkParameters getNetworkParameters()
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.TransactionVerifierService getTransactionVerifierService() @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.node.services.api.WritableTransactionStorage getValidatedTransactions()
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.VaultService getVaultService() @org.jetbrains.annotations.NotNull public net.corda.core.node.services.VaultService getVaultService()
@ -4764,6 +4787,7 @@ public final class net.corda.testing.services.MockAttachmentStorage extends net.
public boolean hasAttachment(net.corda.core.crypto.SecureHash) 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)
@org.jetbrains.annotations.NotNull public net.corda.core.crypto.SecureHash importAttachment(java.io.InputStream, String, String) @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.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.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) @org.jetbrains.annotations.NotNull public List queryAttachments(net.corda.core.node.services.vault.AttachmentQueryCriteria, net.corda.core.node.services.vault.AttachmentSort)

View File

@ -123,7 +123,6 @@ ext {
apply plugin: 'project-report' apply plugin: 'project-report'
apply plugin: 'com.github.ben-manes.versions' apply plugin: 'com.github.ben-manes.versions'
apply plugin: 'net.corda.plugins.publish-utils' apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'net.corda.plugins.cordformation'
apply plugin: 'maven-publish' apply plugin: 'maven-publish'
apply plugin: 'com.jfrog.artifactory' apply plugin: 'com.jfrog.artifactory'
@ -306,31 +305,6 @@ tasks.withType(Test) {
reports.html.destination = file("${reporting.baseDir}/${name}") reports.html.destination = file("${reporting.baseDir}/${name}")
} }
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
directory "./build/nodes"
node {
name "O=Controller,OU=corda,L=London,C=GB"
notary = [validating : true]
p2pPort 10002
cordapps = []
}
node {
name "O=Bank A,OU=corda,L=London,C=GB"
p2pPort 10012
rpcPort 10013
webPort 10014
cordapps = []
}
node {
name "O=Bank B,OU=corda,L=London,C=GB"
p2pAddress "localhost:10007"
rpcAddress "localhost:10008"
webAddress "localhost:10009"
cordapps = []
}
}
// User and key are commented out to prevent accidental pushes of R3 Corda to public repos. DO NOT UNCOMMENT.
bintrayConfig { bintrayConfig {
// user = System.getenv('CORDA_BINTRAY_USER') // user = System.getenv('CORDA_BINTRAY_USER')
// key = System.getenv('CORDA_BINTRAY_KEY') // key = System.getenv('CORDA_BINTRAY_KEY')

View File

@ -10,6 +10,7 @@ import net.corda.core.identity.CordaX500Name
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.finance.USD import net.corda.finance.USD
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.contracts.DummyContract import net.corda.testing.contracts.DummyContract
import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.DUMMY_NOTARY_NAME import net.corda.testing.core.DUMMY_NOTARY_NAME
@ -101,6 +102,7 @@ class JacksonSupportTest {
fun writeTransaction() { fun writeTransaction() {
val attachmentRef = SecureHash.randomSHA256() val attachmentRef = SecureHash.randomSHA256()
doReturn(attachmentRef).whenever(cordappProvider).getContractAttachmentID(DummyContract.PROGRAM_ID) doReturn(attachmentRef).whenever(cordappProvider).getContractAttachmentID(DummyContract.PROGRAM_ID)
doReturn(testNetworkParameters()).whenever(services).networkParameters
fun makeDummyTx(): SignedTransaction { fun makeDummyTx(): SignedTransaction {
val wtx = DummyContract.generateInitial(1, DUMMY_NOTARY, MINI_CORP.ref(1)) val wtx = DummyContract.generateInitial(1, DUMMY_NOTARY, MINI_CORP.ref(1))
.toWireTransaction(services) .toWireTransaction(services)

View File

@ -1,4 +1,4 @@
gradlePluginsVersion=4.0.0 gradlePluginsVersion=4.0.2
kotlinVersion=1.2.20 kotlinVersion=1.2.20
platformVersion=2 platformVersion=2
guavaVersion=21.0 guavaVersion=21.0

View File

@ -1,10 +1,14 @@
package net.corda.core.contracts 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.crypto.SecureHash
import net.corda.core.internal.AttachmentWithContext
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
/** Constrain which contract-code-containing attachment can be used with a [ContractState]. */ /** Constrain which contract-code-containing attachment can be used with a [ContractState]. */
@CordaSerializable @CordaSerializable
@DoNotImplement
interface AttachmentConstraint { interface AttachmentConstraint {
/** Returns whether the given contract attachment can be used with the [ContractState] associated with this constraint object. */ /** Returns whether the given contract attachment can be used with the [ContractState] associated with this constraint object. */
fun isSatisfiedBy(attachment: Attachment): Boolean fun isSatisfiedBy(attachment: Attachment): Boolean
@ -20,6 +24,20 @@ data class HashAttachmentConstraint(val attachmentId: SecureHash) : AttachmentCo
override fun isSatisfiedBy(attachment: Attachment) = attachment.id == attachmentId 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]. * 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 * The resolution occurs in [TransactionBuilder.toWireTransaction] and uses the [TransactionState.contract] value

View File

@ -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 * 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 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 @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<ContractClassName> = emptySet(), val uploader: String? = null) : Attachment by attachment {
val allContracts: Set<ContractClassName> get() = additionalContracts + contract
override fun toString(): String {
return "ContractAttachment(attachment=${attachment.id}, contracts='${allContracts}', uploader='${uploader}')"
}
}

View File

@ -22,6 +22,9 @@ sealed class TransactionVerificationException(val txId: SecureHash, message: Str
class MissingAttachmentRejection(txId: SecureHash, val contractClass: String) class MissingAttachmentRejection(txId: SecureHash, val contractClass: String)
: TransactionVerificationException(txId, "Contract constraints failed, could not find attachment for: $contractClass", null) : 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) class ContractCreationError(txId: SecureHash, contractClass: String, cause: Throwable)
: TransactionVerificationException(txId, "Contract verification failed: ${cause.message}, could not create contract class: $contractClass", cause) : TransactionVerificationException(txId, "Contract verification failed: ${cause.message}, could not create contract class: $contractClass", cause)

View File

@ -189,7 +189,7 @@ abstract class FlowLogic<out T> {
open fun <R : Any> receiveAll(receiveType: Class<R>, sessions: List<FlowSession>, maySkipCheckpoint: Boolean = false): List<UntrustworthyData<R>> { open fun <R : Any> receiveAll(receiveType: Class<R>, sessions: List<FlowSession>, maySkipCheckpoint: Boolean = false): List<UntrustworthyData<R>> {
enforceNoPrimitiveInReceive(listOf(receiveType)) enforceNoPrimitiveInReceive(listOf(receiveType))
enforceNoDuplicates(sessions) enforceNoDuplicates(sessions)
return castMapValuesToKnownType(receiveAllMap(associateSessionsToReceiveType(receiveType, sessions), maySkipCheckpoint)) return castMapValuesToKnownType(receiveAllMap(associateSessionsToReceiveType(receiveType, sessions)))
} }
/** /**

View File

@ -13,6 +13,13 @@ import java.security.CodeSigner
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
import java.util.jar.JarInputStream 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 { abstract class AbstractAttachment(dataLoader: () -> ByteArray) : Attachment {
companion object { companion object {
fun SerializeAsTokenContext.attachmentDataLoader(id: SecureHash): () -> ByteArray { fun SerializeAsTokenContext.attachmentDataLoader(id: SecureHash): () -> ByteArray {

View File

@ -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<String, List<AttachmentId>>?
) : Attachment by contractAttachment {
init {
require(stateContract in contractAttachment.allContracts) {
"This AttachmentWithContext was not initialised properly"
}
}
}

View File

@ -148,7 +148,7 @@ class FetchAttachmentsFlow(requests: Set<SecureHash>,
override fun maybeWriteToDisk(downloaded: List<Attachment>) { override fun maybeWriteToDisk(downloaded: List<Attachment>) {
for (attachment in downloaded) { for (attachment in downloaded) {
serviceHub.attachments.importAttachment(attachment.open()) serviceHub.attachments.importAttachment(attachment.open(), "$P2P_UPLOADER:${otherSideSession.counterparty.name}", null)
} }
} }

View File

@ -147,6 +147,8 @@ inline fun <R> Path.readLines(charset: Charset = UTF_8, block: (Stream<String>)
fun Path.readAllLines(charset: Charset = UTF_8): List<String> = Files.readAllLines(this, charset) fun Path.readAllLines(charset: Charset = UTF_8): List<String> = Files.readAllLines(this, charset)
fun Path.writeLines(lines: Iterable<CharSequence>, charset: Charset = UTF_8, vararg options: OpenOption): Path = Files.write(this, lines, charset, *options) fun Path.writeLines(lines: Iterable<CharSequence>, charset: Charset = UTF_8, vararg options: OpenOption): Path = Files.write(this, lines, charset, *options)
inline fun <reified T : Any> Path.readObject(): T = readAll().deserialize()
fun InputStream.copyTo(target: Path, vararg options: CopyOption): Long = Files.copy(this, target, *options) fun InputStream.copyTo(target: Path, vararg options: CopyOption): Long = Files.copy(this, target, *options)
fun String.abbreviate(maxWidth: Int): String = if (length <= maxWidth) this else take(maxWidth - 1) + "" fun String.abbreviate(maxWidth: Int): String = if (length <= maxWidth) this else take(maxWidth - 1) + ""
@ -304,8 +306,8 @@ fun <T, U : T> uncheckedCast(obj: T) = obj as U
fun <K, V> Iterable<Pair<K, V>>.toMultiMap(): Map<K, List<V>> = this.groupBy({ it.first }) { it.second } fun <K, V> Iterable<Pair<K, V>>.toMultiMap(): Map<K, List<V>> = this.groupBy({ it.first }) { it.second }
/** Provide access to internal method for AttachmentClassLoaderTests */ /** Provide access to internal method for AttachmentClassLoaderTests */
fun TransactionBuilder.toWireTransaction(cordappProvider: CordappProvider, serializationContext: SerializationContext): WireTransaction { fun TransactionBuilder.toWireTransaction(services: ServicesForResolution, serializationContext: SerializationContext): WireTransaction {
return toWireTransactionWithContext(cordappProvider, serializationContext) return toWireTransactionWithContext(services, serializationContext)
} }
/** Provide access to internal method for AttachmentClassLoaderTests */ /** Provide access to internal method for AttachmentClassLoaderTests */
@ -377,15 +379,14 @@ inline fun <T : Any> SerializedBytes<T>.sign(signer: (SerializedBytes<T>) -> Dig
return SignedData(this, signer(this)) return SignedData(this, signer(this))
} }
inline fun <T : Any> SerializedBytes<T>.sign(keyPair: KeyPair): SignedData<T> { fun <T : Any> SerializedBytes<T>.sign(keyPair: KeyPair): SignedData<T> = SignedData(this, keyPair.sign(this.bytes))
return SignedData(this, keyPair.sign(this.bytes))
}
fun ByteBuffer.copyBytes() = ByteArray(remaining()).also { get(it) } fun ByteBuffer.copyBytes(): ByteArray = ByteArray(remaining()).also { get(it) }
fun createCordappContext(cordapp: Cordapp, attachmentId: SecureHash?, classLoader: ClassLoader, config: CordappConfig): CordappContext { fun createCordappContext(cordapp: Cordapp, attachmentId: SecureHash?, classLoader: ClassLoader, config: CordappConfig): CordappContext {
return CordappContext(cordapp, attachmentId, classLoader, config) return CordappContext(cordapp, attachmentId, classLoader, config)
} }
/** Verifies that the correct notarisation request was signed by the counterparty. */ /** Verifies that the correct notarisation request was signed by the counterparty. */
fun NotaryFlow.Service.validateRequest(request: NotarisationRequest, signature: NotarisationRequestSignature) { fun NotaryFlow.Service.validateRequest(request: NotarisationRequest, signature: NotarisationRequestSignature) {
val requestingParty = otherSideSession.counterparty val requestingParty = otherSideSession.counterparty
@ -401,4 +402,4 @@ fun NotarisationRequest.generateSignature(serviceHub: ServiceHub): NotarisationR
keyManagementService.sign(serializedRequest, myLegalIdentity) keyManagementService.sign(serializedRequest, myLegalIdentity)
} }
return NotarisationRequestSignature(signature, serviceHub.myInfo.platformVersion) return NotarisationRequestSignature(signature, serviceHub.myInfo.platformVersion)
} }

View File

@ -1,6 +1,7 @@
package net.corda.core.node package net.corda.core.node
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.node.services.AttachmentId
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import java.time.Instant import java.time.Instant
@ -9,11 +10,13 @@ import java.time.Instant
* correctly interoperate with each other. * correctly interoperate with each other.
* @property minimumPlatformVersion Minimum version of Corda platform that is required for nodes in the network. * @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 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 maxTransactionSize Maximum permitted transaction size in bytes.
* @property modifiedTime Last modification time of network parameters set. * @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 * @property epoch Version number of the network parameters. Starting from 1, this will always increment on each new set
* of parameters. * 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: <https://docs.corda.net/api-contract-constraints.html>
*/ */
// TODO Add eventHorizon - how many days a node can be offline before being automatically ejected from the network. // TODO Add eventHorizon - how many days a node can be offline before being automatically ejected from the network.
// It needs separate design. // It needs separate design.
@ -24,7 +27,8 @@ data class NetworkParameters(
val maxMessageSize: Int, val maxMessageSize: Int,
val maxTransactionSize: Int, val maxTransactionSize: Int,
val modifiedTime: Instant, val modifiedTime: Instant,
val epoch: Int val epoch: Int,
val whitelistedContractImplementations: Map<String, List<AttachmentId>>
) { ) {
init { init {
require(minimumPlatformVersion > 0) { "minimumPlatformVersion must be at least 1" } require(minimumPlatformVersion > 0) { "minimumPlatformVersion must be at least 1" }

View File

@ -33,6 +33,7 @@ interface AttachmentStorage {
* @throws IllegalArgumentException if the given byte stream is empty or a [java.util.jar.JarInputStream]. * @throws IllegalArgumentException if the given byte stream is empty or a [java.util.jar.JarInputStream].
* @throws IOException if something went wrong. * @throws IOException if something went wrong.
*/ */
@Deprecated("More attachment information is required", replaceWith = ReplaceWith("importAttachment(jar, uploader, filename)"))
@Throws(FileAlreadyExistsException::class, IOException::class) @Throws(FileAlreadyExistsException::class, IOException::class)
fun importAttachment(jar: InputStream): AttachmentId fun importAttachment(jar: InputStream): AttachmentId
@ -43,13 +44,14 @@ interface AttachmentStorage {
* @param filename Name of the file * @param filename Name of the file
*/ */
@Throws(FileAlreadyExistsException::class, IOException::class) @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. * Inserts or returns Attachment Id of attachment. Does not throw an exception if already uploaded.
* @param jar [InputStream] of Jar file * @param jar [InputStream] of Jar file
* @return [AttachmentId] of uploaded attachment * @return [AttachmentId] of uploaded attachment
*/ */
@Deprecated("More attachment information is required", replaceWith = ReplaceWith("importAttachment(jar, uploader, filename)"))
fun importOrGetAttachment(jar: InputStream): AttachmentId fun importOrGetAttachment(jar: InputStream): AttachmentId
/** /**

View File

@ -1,5 +1,6 @@
package net.corda.core.serialization package net.corda.core.serialization
import net.corda.core.DoNotImplement
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256 import net.corda.core.crypto.sha256
import net.corda.core.serialization.internal.effectiveSerializationEnv import net.corda.core.serialization.internal.effectiveSerializationEnv
@ -99,14 +100,22 @@ abstract class SerializationFactory {
} }
} }
typealias SerializationMagic = ByteSequence typealias SerializationMagic = ByteSequence
@DoNotImplement
interface SerializationEncoding
/** /**
* Parameters to serialization and deserialization. * Parameters to serialization and deserialization.
*/ */
@DoNotImplement
interface SerializationContext { interface SerializationContext {
/** /**
* When serializing, use the format this header sequence represents. * When serializing, use the format this header sequence represents.
*/ */
val preferredSerializationVersion: SerializationMagic val preferredSerializationVersion: SerializationMagic
/**
* If non-null, apply this encoding (typically compression) when serializing.
*/
val encoding: SerializationEncoding?
/** /**
* The class loader to use for deserialization. * The class loader to use for deserialization.
*/ */
@ -115,6 +124,10 @@ interface SerializationContext {
* A whitelist that contains (mostly for security purposes) which classes can be serialized and deserialized. * A whitelist that contains (mostly for security purposes) which classes can be serialized and deserialized.
*/ */
val whitelist: ClassWhitelist val whitelist: ClassWhitelist
/**
* A whitelist that determines (mostly for security purposes) whether a particular encoding may be used when deserializing.
*/
val encodingWhitelist: EncodingWhitelist
/** /**
* A map of any addition properties specific to the particular use case. * A map of any addition properties specific to the particular use case.
*/ */
@ -161,6 +174,11 @@ interface SerializationContext {
*/ */
fun withPreferredSerializationVersion(magic: SerializationMagic): SerializationContext fun withPreferredSerializationVersion(magic: SerializationMagic): SerializationContext
/**
* A shallow copy of this context but with the given (possibly null) encoding.
*/
fun withEncoding(encoding: SerializationEncoding?): SerializationContext
/** /**
* The use case that we are serializing for, since it influences the implementations chosen. * The use case that we are serializing for, since it influences the implementations chosen.
*/ */
@ -232,3 +250,8 @@ class SerializedBytes<T : Any>(bytes: ByteArray) : OpaqueBytes(bytes) {
interface ClassWhitelist { interface ClassWhitelist {
fun hasListed(type: Class<*>): Boolean fun hasListed(type: Class<*>): Boolean
} }
@DoNotImplement
interface EncodingWhitelist {
fun acceptEncoding(encoding: SerializationEncoding): Boolean
}

View File

@ -3,9 +3,11 @@ package net.corda.core.transactions
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.AttachmentWithContext
import net.corda.core.internal.UpgradeCommand import net.corda.core.internal.UpgradeCommand
import net.corda.core.internal.castIfPossible import net.corda.core.internal.castIfPossible
import net.corda.core.internal.uncheckedCast import net.corda.core.internal.uncheckedCast
import net.corda.core.node.NetworkParameters
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.utilities.Try import net.corda.core.utilities.Try
import java.security.PublicKey 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. // currently sends this across to out-of-process verifiers. We'll need to change that first.
// DOCSTART 1 // DOCSTART 1
@CordaSerializable @CordaSerializable
data class LedgerTransaction( data class LedgerTransaction @JvmOverloads constructor(
/** The resolved input states which will be consumed/invalidated by the execution of this transaction. */ /** The resolved input states which will be consumed/invalidated by the execution of this transaction. */
override val inputs: List<StateAndRef<ContractState>>, override val inputs: List<StateAndRef<ContractState>>,
override val outputs: List<TransactionState<ContractState>>, override val outputs: List<TransactionState<ContractState>>,
@ -39,7 +41,8 @@ data class LedgerTransaction(
override val id: SecureHash, override val id: SecureHash,
override val notary: Party?, override val notary: Party?,
val timeWindow: TimeWindow?, val timeWindow: TimeWindow?,
val privacySalt: PrivacySalt val privacySalt: PrivacySalt,
private val networkParameters: NetworkParameters? = null
) : FullTransaction() { ) : FullTransaction() {
//DOCEND 1 //DOCEND 1
init { init {
@ -87,17 +90,29 @@ data class LedgerTransaction(
/** /**
* Verify that all contract constraints are valid for each state before running any contract code * 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 * @throws TransactionVerificationException if the constraints fail to verify
*/ */
private fun verifyConstraints() { private fun verifyConstraints() {
val contractAttachments = attachments.filterIsInstance<ContractAttachment>() val contractAttachments = attachments.filterIsInstance<ContractAttachment>()
(inputs.map { it.state } + outputs).forEach { state -> (inputs.map { it.state } + outputs).forEach { state ->
// Ordering of attachments matters - if two attachments contain the same named contract then the second val stateAttachments = contractAttachments.filter { state.contract in it.allContracts }
// will be shadowed by the first. if (stateAttachments.isEmpty()) throw TransactionVerificationException.MissingAttachmentRejection(id, state.contract)
val contractAttachment = contractAttachments.find { it.contract == state.contract }
?: 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) throw TransactionVerificationException.ContractConstraintRejection(id, state.contract)
} }
} }
@ -403,5 +418,14 @@ data class LedgerTransaction(
* @throws IllegalArgumentException if no item matches the id. * @throws IllegalArgumentException if no item matches the id.
*/ */
fun getAttachment(id: SecureHash): Attachment = attachments.first { it.id == id } fun getAttachment(id: SecureHash): Attachment = attachments.first { it.id == id }
}
fun copy(inputs: List<StateAndRef<ContractState>>,
outputs: List<TransactionState<ContractState>>,
commands: List<CommandWithParties<CommandData>>,
attachments: List<Attachment>,
id: SecureHash,
notary: Party?,
timeWindow: TimeWindow?,
privacySalt: PrivacySalt
) = copy(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, null)
}

View File

@ -8,8 +8,10 @@ import net.corda.core.crypto.SignableData
import net.corda.core.crypto.SignatureMetadata import net.corda.core.crypto.SignatureMetadata
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.FlowStateMachine import net.corda.core.internal.FlowStateMachine
import net.corda.core.node.NetworkParameters
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
import net.corda.core.node.ServicesForResolution import net.corda.core.node.ServicesForResolution
import net.corda.core.node.services.AttachmentId
import net.corda.core.node.services.KeyManagementService import net.corda.core.node.services.KeyManagementService
import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationFactory import net.corda.core.serialization.SerializationFactory
@ -17,6 +19,7 @@ import java.security.PublicKey
import java.time.Duration import java.time.Duration
import java.time.Instant import java.time.Instant
import java.util.* 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 * 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()) constructor(notary: Party) : this(notary, (Strand.currentStrand() as? FlowStateMachine<*>)?.id?.uuid ?: UUID.randomUUID())
private val inputsWithTransactionState = arrayListOf<TransactionState<ContractState>>()
/** /**
* Creates a copy of the builder. * Creates a copy of the builder.
*/ */
fun copy() = TransactionBuilder( fun copy(): TransactionBuilder {
notary = notary, val t = TransactionBuilder(
inputs = ArrayList(inputs), notary = notary,
attachments = ArrayList(attachments), inputs = ArrayList(inputs),
outputs = ArrayList(outputs), attachments = ArrayList(attachments),
commands = ArrayList(commands), outputs = ArrayList(outputs),
window = window, commands = ArrayList(commands),
privacySalt = privacySalt window = window,
) privacySalt = privacySalt
)
t.inputsWithTransactionState.addAll(this.inputsWithTransactionState)
return t
}
// DOCSTART 1 // DOCSTART 1
/** A more convenient way to add items to this transaction that calls the add* methods for you based on type */ /** 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]. * @returns A new [WireTransaction] that will be unaffected by further changes to this [TransactionBuilder].
*/ */
@Throws(MissingContractAttachments::class) @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 { internal fun toWireTransactionWithContext(services: ServicesForResolution, 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 // 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 // will be available when building the transaction. In exceptional cases the TransactionStates must be created
// with an explicit [AttachmentConstraint] // with an explicit [AttachmentConstraint]
val resolvedOutputs = outputs.map { state -> val resolvedOutputs = outputs.map { state ->
if (state.constraint is AutomaticHashConstraint) { when {
cordappProvider.getContractAttachmentID(state.contract)?.let { 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)) state.copy(constraint = HashAttachmentConstraint(it))
} ?: throw MissingContractAttachments(listOf(state)) } ?: throw MissingContractAttachments(listOf(state))
} else {
state
} }
} }
return SerializationFactory.defaultFactory.withCurrentContext(serializationContext) { 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<AttachmentId> {
return (inputsWithTransactionState + outputs).map { state ->
cordappProvider.getContractAttachmentID(state.contract)
?: throw MissingContractAttachments(listOf(state))
}.distinct()
}
@Throws(AttachmentResolutionException::class, TransactionResolutionException::class) @Throws(AttachmentResolutionException::class, TransactionResolutionException::class)
fun toLedgerTransaction(services: ServiceHub) = toWireTransaction(services).toLedgerTransaction(services) 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) @Throws(AttachmentResolutionException::class, TransactionResolutionException::class, TransactionVerificationException::class)
fun verify(services: ServiceHub) { fun verify(services: ServiceHub) {
toLedgerTransaction(services).verify() toLedgerTransaction(services).verify()
@ -117,6 +147,7 @@ open class TransactionBuilder(
val notary = stateAndRef.state.notary val notary = stateAndRef.state.notary
require(notary == this.notary) { "Input state requires notary \"$notary\" which does not match the transaction notary \"${this.notary}\"." } require(notary == this.notary) { "Input state requires notary \"$notary\" which does not match the transaction notary \"${this.notary}\"." }
inputs.add(stateAndRef.ref) inputs.add(stateAndRef.ref)
inputsWithTransactionState.add(stateAndRef.state)
return this return this
} }

View File

@ -5,6 +5,7 @@ import net.corda.core.contracts.ComponentGroupEnum.*
import net.corda.core.crypto.* import net.corda.core.crypto.*
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.Emoji import net.corda.core.internal.Emoji
import net.corda.core.node.NetworkParameters
import net.corda.core.node.ServicesForResolution import net.corda.core.node.ServicesForResolution
import net.corda.core.node.services.AttachmentId import net.corda.core.node.services.AttachmentId
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
@ -88,8 +89,7 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
resolveIdentity = { services.identityService.partyFromKey(it) }, resolveIdentity = { services.identityService.partyFromKey(it) },
resolveAttachment = { services.attachments.openAttachment(it) }, resolveAttachment = { services.attachments.openAttachment(it) },
resolveStateRef = { services.loadState(it) }, resolveStateRef = { services.loadState(it) },
resolveContractAttachment = { services.cordappProvider.getContractAttachmentID(it.contract) }, networkParameters = services.networkParameters
maxTransactionSize = services.networkParameters.maxTransactionSize
) )
} }
@ -108,17 +108,16 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
resolveStateRef: (StateRef) -> TransactionState<*>?, resolveStateRef: (StateRef) -> TransactionState<*>?,
resolveContractAttachment: (TransactionState<ContractState>) -> AttachmentId? resolveContractAttachment: (TransactionState<ContractState>) -> AttachmentId?
): LedgerTransaction { ): LedgerTransaction {
return toLedgerTransactionInternal(resolveIdentity, resolveAttachment, resolveStateRef, resolveContractAttachment, 10485760) return toLedgerTransactionInternal(resolveIdentity, resolveAttachment, resolveStateRef, null)
} }
private fun toLedgerTransactionInternal( private fun toLedgerTransactionInternal(
resolveIdentity: (PublicKey) -> Party?, resolveIdentity: (PublicKey) -> Party?,
resolveAttachment: (SecureHash) -> Attachment?, resolveAttachment: (SecureHash) -> Attachment?,
resolveStateRef: (StateRef) -> TransactionState<*>?, resolveStateRef: (StateRef) -> TransactionState<*>?,
resolveContractAttachment: (TransactionState<ContractState>) -> AttachmentId?, networkParameters: NetworkParameters?
maxTransactionSize: Int
): LedgerTransaction { ): 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 authenticatedArgs = commands.map {
val parties = it.signers.mapNotNull { pk -> resolveIdentity(pk) } val parties = it.signers.mapNotNull { pk -> resolveIdentity(pk) }
CommandWithParties(it.signers, parties, it.value) CommandWithParties(it.signers, parties, it.value)
@ -126,12 +125,9 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
val resolvedInputs = inputs.map { ref -> val resolvedInputs = inputs.map { ref ->
resolveStateRef(ref)?.let { StateAndRef(it, ref) } ?: throw TransactionResolutionException(ref.txhash) 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 attachments = attachments.map { resolveAttachment(it) ?: throw AttachmentResolutionException(it) }
val contractAttachments = findAttachmentContracts(resolvedInputs, resolveContractAttachment, resolveAttachment) val ltx = LedgerTransaction(resolvedInputs, outputs, authenticatedArgs, attachments, id, notary, timeWindow, privacySalt, networkParameters)
// Order of attachments is important since contracts may refer to indexes so only append automatic attachments checkTransactionSize(ltx, networkParameters?.maxTransactionSize ?: 10485760)
val attachments = (attachments.map { resolveAttachment(it) ?: throw AttachmentResolutionException(it) } + contractAttachments).distinct()
val ltx = LedgerTransaction(resolvedInputs, outputs, authenticatedArgs, attachments, id, notary, timeWindow, privacySalt)
checkTransactionSize(ltx, maxTransactionSize)
return ltx return ltx
} }
@ -143,8 +139,9 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
remainingTransactionSize -= size remainingTransactionSize -= size
} }
// Check attachment size first as they are most likely to go over the limit. // Check attachments size first as they are most likely to go over the limit. With ContractAttachment instances
ltx.attachments.associateBy(Attachment::id).values.forEach { minus(it.size) } // it's likely that the same underlying Attachment CorDapp will occur more than once so we dedup on the attachment id.
ltx.attachments.distinctBy { it.id }.forEach { minus(it.size) }
minus(ltx.inputs.serialize().size) minus(ltx.inputs.serialize().size)
minus(ltx.commands.serialize().size) minus(ltx.commands.serialize().size)
minus(ltx.outputs.serialize().size) minus(ltx.outputs.serialize().size)
@ -265,19 +262,6 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
return buf.toString() return buf.toString()
} }
private fun findAttachmentContracts(resolvedInputs: List<StateAndRef<ContractState>>,
resolveContractAttachment: (TransactionState<ContractState>) -> AttachmentId?,
resolveAttachment: (SecureHash) -> Attachment?
): List<Attachment> {
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 { override fun equals(other: Any?): Boolean {
if (other is WireTransaction) { if (other is WireTransaction) {
return (this.id == other.id) return (this.id == other.id)

View File

@ -1,9 +1,11 @@
package net.corda.core.contracts package net.corda.core.contracts
import com.nhaarman.mockito_kotlin.any
import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.cordapp.CordappProvider import net.corda.core.cordapp.CordappProvider
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.SecureHash.Companion.allOnesHash
import net.corda.core.internal.UpgradeCommand import net.corda.core.internal.UpgradeCommand
import net.corda.core.node.ServicesForResolution import net.corda.core.node.ServicesForResolution
import net.corda.testing.contracts.DummyContract import net.corda.testing.contracts.DummyContract
@ -34,7 +36,9 @@ class DummyContractV2Tests {
@Test @Test
fun `upgrade from v1`() { fun `upgrade from v1`() {
val services = rigorousMock<ServicesForResolution>().also { val services = rigorousMock<ServicesForResolution>().also {
doReturn(rigorousMock<CordappProvider>()).whenever(it).cordappProvider doReturn(rigorousMock<CordappProvider>().also {
doReturn(allOnesHash).whenever(it).getContractAttachmentID(any())
}).whenever(it).cordappProvider
} }
val contractUpgrade = DummyContractV2() val contractUpgrade = DummyContractV2()
val v1State = TransactionState(DummyContract.SingleOwnerState(0, ALICE), DummyContract.PROGRAM_ID, DUMMY_NOTARY, constraint = AlwaysAcceptAttachmentConstraint) val v1State = TransactionState(DummyContract.SingleOwnerState(0, ALICE), DummyContract.PROGRAM_ID, DUMMY_NOTARY, constraint = AlwaysAcceptAttachmentConstraint)

View File

@ -5,7 +5,6 @@ import net.corda.core.utilities.UntrustworthyData
import net.corda.core.utilities.unwrap import net.corda.core.utilities.unwrap
import net.corda.node.internal.InitiatedFlowFactory import net.corda.node.internal.InitiatedFlowFactory
import net.corda.node.internal.StartedNode import net.corda.node.internal.StartedNode
import net.corda.testing.node.StartedMockNode
import net.corda.testing.node.internal.InternalMockNetwork import net.corda.testing.node.internal.InternalMockNetwork
import kotlin.reflect.KClass import kotlin.reflect.KClass

View File

@ -10,7 +10,6 @@ import net.corda.core.identity.Party
import net.corda.node.services.api.IdentityServiceInternal import net.corda.node.services.api.IdentityServiceInternal
import net.corda.testing.contracts.DummyContract import net.corda.testing.contracts.DummyContract
import net.corda.testing.core.* import net.corda.testing.core.*
import net.corda.testing.internal.MockCordappProvider
import net.corda.testing.internal.rigorousMock import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices
import org.junit.Before import org.junit.Before
@ -30,7 +29,7 @@ class LedgerTransactionQueryTests {
@JvmField @JvmField
val testSerialization = SerializationEnvironmentRule() val testSerialization = SerializationEnvironmentRule()
private val keyPair = generateKeyPair() 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<IdentityServiceInternal>().also { rigorousMock<IdentityServiceInternal>().also {
doReturn(null).whenever(it).partyFromKey(keyPair.public) doReturn(null).whenever(it).partyFromKey(keyPair.public)
}, keyPair) }, keyPair)

View File

@ -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<IdentityServiceInternal>().also { rigorousMock<IdentityServiceInternal>().also {
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY) doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
}) })

View File

@ -1,5 +1,7 @@
package net.corda.core.transactions 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.contracts.*
import net.corda.core.crypto.* import net.corda.core.crypto.*
import net.corda.core.crypto.CompositeKey import net.corda.core.crypto.CompositeKey
@ -112,7 +114,9 @@ class TransactionTests {
val inputs = emptyList<StateAndRef<*>>() val inputs = emptyList<StateAndRef<*>>()
val outputs = listOf(baseOutState, baseOutState.copy(notary = ALICE), baseOutState.copy(notary = BOB)) val outputs = listOf(baseOutState, baseOutState.copy(notary = ALICE), baseOutState.copy(notary = BOB))
val commands = emptyList<CommandWithParties<CommandData>>() val commands = emptyList<CommandWithParties<CommandData>>()
val attachments = listOf<Attachment>(ContractAttachment(rigorousMock(), DummyContract.PROGRAM_ID)) val attachments = listOf<Attachment>(ContractAttachment(rigorousMock<Attachment>().also {
doReturn(SecureHash.zeroHash).whenever(it).id
}, DummyContract.PROGRAM_ID))
val id = SecureHash.randomSHA256() val id = SecureHash.randomSHA256()
val timeWindow: TimeWindow? = null val timeWindow: TimeWindow? = null
val privacySalt: PrivacySalt = PrivacySalt() val privacySalt: PrivacySalt = PrivacySalt()

View File

@ -264,6 +264,13 @@ In order to create a communication session between your initiator flow and the r
* ``sendAndReceive(receiveType: Class<R>, payload: Any): R`` * ``sendAndReceive(receiveType: Class<R>, payload: Any): R``
* Sends the ``payload`` object and receives an object of type ``receiveType`` back * Sends the ``payload`` object and receives an object of type ``receiveType`` back
In addition ``FlowLogic`` provides functions that batch receives:
* ``receiveAllMap(sessions: Map<FlowSession, Class<out Any>>): Map<FlowSession, UntrustworthyData<Any>>``
* Receives from all ``FlowSession``s specified in the passed in map. The received types may differ.
* ``receiveAll(receiveType: Class<R>, sessions: List<FlowSession>): List<UntrustworthyData<R>>``
* Receives from all ``FlowSession``s specified in the passed in list. The received types must be the same.
The batched functions are implemented more efficiently by the flow framework.
InitiateFlow InitiateFlow
~~~~~~~~~~~~ ~~~~~~~~~~~~

467
docs/source/api-testing.rst Normal file
View File

@ -0,0 +1,467 @@
.. highlight:: kotlin
.. raw:: html
<script type="text/javascript" src="_static/jquery.js"></script>
<script type="text/javascript" src="_static/codesets.js"></script>
API: Testing
============
.. contents::
Flow testing
------------
MockNetwork
^^^^^^^^^^^
Flow testing can be fully automated using a ``MockNetwork`` composed of ``StartedMockNode`` nodes. Each
``StartedMockNode`` behaves like a regular Corda node, but its services are either in-memory or mocked out.
A ``MockNetwork`` is created as follows:
.. container:: codeset
.. sourcecode:: kotlin
class FlowTests {
private lateinit var mockNet: MockNetwork
@Before
fun setup() {
network = MockNetwork(listOf("my.cordapp.package", "my.other.cordapp.package"))
}
}
.. sourcecode:: java
public class IOUFlowTests {
private MockNetwork network;
@Before
public void setup() {
network = new MockNetwork(ImmutableList.of("my.cordapp.package", "my.other.cordapp.package"));
}
}
The ``MockNetwork`` requires at a minimum a list of packages. Each package is packaged into a CorDapp JAR and installed
as a CorDapp on each ``StartedMockNode``.
Configuring the ``MockNetwork``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``MockNetwork`` is configured automatically. You can tweak its configuration using a ``MockNetworkParameters``
object, or by using named paramters in Kotlin:
.. container:: codeset
.. sourcecode:: kotlin
val network = MockNetwork(
cordappPackages = listOf("my.cordapp.package", "my.other.cordapp.package"),
// If true then each node will be run in its own thread. This can result in race conditions in your
// code if not carefully written, but is more realistic and may help if you have flows in your app that
// do long blocking operations.
threadPerNode = false,
// The notaries to use on the mock network. By default you get one mock notary and that is usually
// sufficient.
notarySpecs = listOf(MockNetworkNotarySpec(DUMMY_NOTARY_NAME)),
// If true then messages will not be routed from sender to receiver until you use the
// [MockNetwork.runNetwork] method. This is useful for writing single-threaded unit test code that can
// examine the state of the mock network before and after a message is sent, without races and without
// the receiving node immediately sending a response.
networkSendManuallyPumped = false,
// How traffic is allocated in the case where multiple nodes share a single identity, which happens for
// notaries in a cluster. You don't normally ever need to change this: it is mostly useful for testing
// notary implementations.
servicePeerAllocationStrategy = InMemoryMessagingNetwork.ServicePeerAllocationStrategy.Random())
val network2 = MockNetwork(listOf("my.cordapp.package", "my.other.cordapp.package"), MockNetworkParameters(
// If true then each node will be run in its own thread. This can result in race conditions in your
// code if not carefully written, but is more realistic and may help if you have flows in your app that
// do long blocking operations.
threadPerNode = false,
// The notaries to use on the mock network. By default you get one mock notary and that is usually
// sufficient.
notarySpecs = listOf(MockNetworkNotarySpec(DUMMY_NOTARY_NAME)),
// If true then messages will not be routed from sender to receiver until you use the
// [MockNetwork.runNetwork] method. This is useful for writing single-threaded unit test code that can
// examine the state of the mock network before and after a message is sent, without races and without
// the receiving node immediately sending a response.
networkSendManuallyPumped = false,
// How traffic is allocated in the case where multiple nodes share a single identity, which happens for
// notaries in a cluster. You don't normally ever need to change this: it is mostly useful for testing
// notary implementations.
servicePeerAllocationStrategy = InMemoryMessagingNetwork.ServicePeerAllocationStrategy.Random())
)
.. sourcecode:: java
MockNetwork network = MockNetwork(ImmutableList.of("my.cordapp.package", "my.other.cordapp.package"),
new MockNetworkParameters()
// If true then each node will be run in its own thread. This can result in race conditions in
// your code if not carefully written, but is more realistic and may help if you have flows in
// your app that do long blocking operations.
.setThreadPerNode(false)
// The notaries to use on the mock network. By default you get one mock notary and that is
// usually sufficient.
.setNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(DUMMY_NOTARY_NAME)))
// If true then messages will not be routed from sender to receiver until you use the
// [MockNetwork.runNetwork] method. This is useful for writing single-threaded unit test code
// that can examine the state of the mock network before and after a message is sent, without
// races and without the receiving node immediately sending a response.
.setNetworkSendManuallyPumped(false)
// How traffic is allocated in the case where multiple nodes share a single identity, which
// happens for notaries in a cluster. You don't normally ever need to change this: it is mostly
// useful for testing notary implementations.
.setServicePeerAllocationStrategy(new InMemoryMessagingNetwork.ServicePeerAllocationStrategy.Random()));
Adding nodes to the network
^^^^^^^^^^^^^^^^^^^^^^^^^^^
Nodes are created on the ``MockNetwork`` using:
.. container:: codeset
.. sourcecode:: kotlin
class FlowTests {
private lateinit var mockNet: MockNetwork
lateinit var nodeA: StartedMockNode
lateinit var nodeB: StartedMockNode
@Before
fun setup() {
network = MockNetwork(listOf("my.cordapp.package", "my.other.cordapp.package"))
nodeA = network.createPartyNode()
// We can optionally give the node a name.
nodeB = network.createPartyNode(CordaX500Name("Bank B", "London", "GB"))
}
}
.. sourcecode:: java
public class IOUFlowTests {
private MockNetwork network;
private StartedMockNode a;
private StartedMockNode b;
@Before
public void setup() {
network = new MockNetwork(ImmutableList.of("my.cordapp.package", "my.other.cordapp.package"));
nodeA = network.createPartyNode(null);
// We can optionally give the node a name.
nodeB = network.createPartyNode(new CordaX500Name("Bank B", "London", "GB"));
}
}
Registering a node's initiated flows
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Regular Corda nodes automatically register any response flows defined in their installed CorDapps. When using a
``MockNetwork``, each ``StartedMockNode`` must manually register any responder flows it wishes to use.
Responder flows are registered as follows:
.. container:: codeset
.. sourcecode:: kotlin
nodeA.registerInitiatedFlow(ExampleFlow.Acceptor::class.java)
.. sourcecode:: java
nodeA.registerInitiatedFlow(ExampleFlow.Acceptor.class);
Running the network
^^^^^^^^^^^^^^^^^^^
Regular Corda nodes automatically process received messages. When using a ``MockNetwork`` with
``networkSendManuallyPumped`` set to ``false``, you must manually initiate the processing of received messages.
You manually process received messages as follows:
* ``StartedMockNode.pumpReceive`` to process a single message from the node's queue
* ``MockNetwork.runNetwork`` to process all the messages in every node's queue. This may generate additional messages
that must in turn be processed
* ``network.runNetwork(-1)`` (the default in Kotlin) will exchange messages until there are no further messages to
process
Running flows
^^^^^^^^^^^^^
A ``StartedMockNode`` starts a flow using the ``StartedNodeServices.startFlow`` method. This method returns a future
representing the output of running the flow.
.. container:: codeset
.. sourcecode:: kotlin
val signedTransactionFuture = nodeA.services.startFlow(IOUFlow(iouValue = 99, otherParty = nodeBParty))
.. sourcecode:: java
CordaFuture<SignedTransaction> future = startFlow(a.getServices(), new ExampleFlow.Initiator(1, nodeBParty));
The network must then be manually run before retrieving the future's value:
.. container:: codeset
.. sourcecode:: kotlin
val signedTransactionFuture = nodeA.services.startFlow(IOUFlow(iouValue = 99, otherParty = nodeBParty))
// Assuming network.networkSendManuallyPumped == false.
network.runNetwork()
val signedTransaction = future.get();
.. sourcecode:: java
CordaFuture<SignedTransaction> future = startFlow(a.getServices(), new ExampleFlow.Initiator(1, nodeBParty));
// Assuming network.networkSendManuallyPumped == false.
network.runNetwork();
SignedTransaction signedTransaction = future.get();
Accessing ``StartedMockNode`` internals
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Creating a node database transaction
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Whenever you query a node's database (e.g. to extract information from the node's vault), you must wrap the query in
a database transaction, as follows:
.. container:: codeset
.. sourcecode:: kotlin
nodeA.database.transaction {
// Perform query here.
}
.. sourcecode:: java
node.getDatabase().transaction(tx -> {
// Perform query here.
}
Querying a node's vault
~~~~~~~~~~~~~~~~~~~~~~~
Recorded states can be retrieved from the vault of a ``StartedMockNode`` using:
.. container:: codeset
.. sourcecode:: kotlin
nodeA.database.transaction {
val myStates = nodeA.services.vaultService.queryBy<MyStateType>().states
}
.. sourcecode:: java
node.getDatabase().transaction(tx -> {
List<MyStateType> myStates = node.getServices().getVaultService().queryBy(MyStateType.class).getStates();
}
This allows you to check whether a given state has (or has not) been stored, and whether it has the correct attributes.
Examining a node's transaction storage
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Recorded transactions can be retrieved from the transaction storage of a ``StartedMockNode`` using:
.. container:: codeset
.. sourcecode:: kotlin
val transaction = nodeA.services.validatedTransactions.getTransaction(transaction.id)
.. sourcecode:: java
SignedTransaction transaction = nodeA.getServices().getValidatedTransactions().getTransaction(transaction.getId())
This allows you to check whether a given transaction has (or has not) been stored, and whether it has the correct
attributes.
This allows you to check whether a given state has (or has not) been stored, and whether it has the correct attributes.
Further examples
^^^^^^^^^^^^^^^^
* See the flow testing tutorial :doc:`here <flow-testing>`
* Further examples are available in the Example CorDapp in
`Java <https://github.com/corda/cordapp-example/blob/release-V3/java-source/src/test/java/com/example/flow/IOUFlowTests.java>`_ and
`Kotlin <https://github.com/corda/cordapp-example/blob/release-V3/kotlin-source/src/test/kotlin/com/example/flow/IOUFlowTests.kt>`_
Contract testing
----------------
The Corda test framework includes the ability to create a test ledger by calling the ``ledger`` function
on an implementation of the ``ServiceHub`` interface.
MockServices
^^^^^^^^^^^^
A mock implementation of ``ServiceHub`` is provided in ``MockServices``. This is a minimal ``ServiceHub`` that
suffices to test contract logic. It has the ability to insert states into the vault, query the vault, and
construct and check transactions.
.. container:: codeset
.. literalinclude:: ../../docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt
:language: kotlin
:start-after: DOCSTART 11
:end-before: DOCEND 11
:dedent: 4
.. literalinclude:: ../../docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java
:language: java
:start-after: DOCSTART 11
:end-before: DOCEND 11
:dedent: 4
Alternatively, there is a helper constructor which just accepts a list of ``TestIdentity``. The first identity provided is
the identity of the node whose ``ServiceHub`` is being mocked, and any subsequent identities are identities that the node
knows about. Only the calling package is scanned for cordapps and a test ``IdentityService`` is created
for you, using all the given identities.
.. container:: codeset
.. literalinclude:: ../../docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt
:language: kotlin
:start-after: DOCSTART 12
:end-before: DOCEND 12
:dedent: 4
.. literalinclude:: ../../docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java
:language: java
:start-after: DOCSTART 12
:end-before: DOCEND 12
:dedent: 4
Writing tests using a test ledger
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The ``ServiceHub.ledger`` extension function allows you to create a test ledger. Within the ledger wrapper you can create
transactions using the ``transaction`` function. Within a transaction you can define the ``input`` and
``output`` states for the transaction, alongside any commands that are being executed, the ``timeWindow`` in which the
transaction has been executed, and any ``attachments``, as shown in this example test:
.. container:: codeset
.. literalinclude:: ../../docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt
:language: kotlin
:start-after: DOCSTART 13
:end-before: DOCEND 13
:dedent: 4
.. literalinclude:: ../../docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java
:language: java
:start-after: DOCSTART 13
:end-before: DOCEND 13
:dedent: 4
Once all the transaction components have been specified, you can run ``verifies()`` to check that the given transaction is valid.
Checking for failure states
~~~~~~~~~~~~~~~~~~~~~~~~~~~
In order to test for failures, you can use the ``failsWith`` method, or in Kotlin the ``fails with`` helper method, which
assert that the transaction fails with a specific error. If you just want to assert that the transaction has failed without
verifying the message, there is also a ``fails`` method.
.. container:: codeset
.. literalinclude:: ../../docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt
:language: kotlin
:start-after: DOCSTART 4
:end-before: DOCEND 4
:dedent: 4
.. literalinclude:: ../../docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java
:language: java
:start-after: DOCSTART 4
:end-before: DOCEND 4
:dedent: 4
.. note::
The transaction DSL forces the last line of the test to be either a ``verifies`` or ``fails with`` statement.
Testing multiple scenarios at once
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Within a single transaction block, you can assert several times that the transaction constructed so far either passes or
fails verification. For example, you could test that a contract fails to verify because it has no output states, and then
add the relevant output state and check that the contract verifies successfully, as in the following example:
.. container:: codeset
.. literalinclude:: ../../docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt
:language: kotlin
:start-after: DOCSTART 5
:end-before: DOCEND 5
:dedent: 4
.. literalinclude:: ../../docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java
:language: java
:start-after: DOCSTART 5
:end-before: DOCEND 5
:dedent: 4
You can also use the ``tweak`` function to create a locally scoped transaction that you can make changes to
and then return to the original, unmodified transaction. As in the following example:
.. container:: codeset
.. literalinclude:: ../../docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt
:language: kotlin
:start-after: DOCSTART 7
:end-before: DOCEND 7
:dedent: 4
.. literalinclude:: ../../docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java
:language: java
:start-after: DOCSTART 7
:end-before: DOCEND 7
:dedent: 4
Chaining transactions
~~~~~~~~~~~~~~~~~~~~~
The following example shows that within a ``ledger``, you can create more than one ``transaction`` in order to test chains
of transactions. In addition to ``transaction``, ``unverifiedTransaction`` can be used, as in the example below, to create
transactions on the ledger without verifying them, for pre-populating the ledger with existing data. When chaining transactions,
it is important to note that even though a ``transaction`` ``verifies`` successfully, the overall ledger may not be valid. This can
be verified separately by placing a ``verifies`` or ``fails`` statement within the ``ledger`` block.
.. container:: codeset
.. literalinclude:: ../../docs/source/example-code/src/test/kotlin/net/corda/docs/tutorial/testdsl/TutorialTestDSL.kt
:language: kotlin
:start-after: DOCSTART 9
:end-before: DOCEND 9
:dedent: 4
.. literalinclude:: ../../docs/source/example-code/src/test/java/net/corda/docs/java/tutorial/testdsl/CommercialPaperTest.java
:language: java
:start-after: DOCSTART 9
:end-before: DOCEND 9
:dedent: 4
Further examples
^^^^^^^^^^^^^^^^
* See the flow testing tutorial :doc:`here <tutorial-test-dsl>`
* Further examples are available in the Example CorDapp in
`Java <https://github.com/corda/cordapp-example/blob/release-V3/java-source/src/test/java/com/example/flow/IOUFlowTests.java>`_ and
`Kotlin <https://github.com/corda/cordapp-example/blob/release-V3/kotlin-source/src/test/kotlin/com/example/flow/IOUFlowTests.kt>`_

View File

@ -46,7 +46,7 @@ master_doc = 'index'
# General information about the project. # General information about the project.
project = u'R3 Corda' project = u'R3 Corda'
copyright = u'2017, R3 Limited' copyright = u'2018, R3 Limited'
author = u'R3 DLG' author = u'R3 DLG'
# The version info for the project you're documenting, acts as replacement for # The version info for the project you're documenting, acts as replacement for

View File

@ -17,6 +17,7 @@ The following are the core APIs that are used in the development of CorDapps:
api-service-hub api-service-hub
api-rpc api-rpc
api-core-types api-core-types
api-testing
Before reading this page, you should be familiar with the :doc:`key concepts of Corda <key-concepts>`. Before reading this page, you should be familiar with the :doc:`key concepts of Corda <key-concepts>`.

View File

@ -199,7 +199,7 @@ at boot, and means the Corda service stays running with no users connected to th
mkdir C:\Corda mkdir C:\Corda
wget http://jcenter.bintray.com/net/corda/corda/VERSION_NUMBER/corda-VERSION_NUMBER.jar -OutFile C:\Corda\corda.jar wget http://jcenter.bintray.com/net/corda/corda/VERSION_NUMBER/corda-VERSION_NUMBER.jar -OutFile C:\Corda\corda.jar
2. Create a directory called ``cordapps`` in ``/opt/corda`` and save your CorDapp jar file to it. Alternatively, 2. Create a directory called ``cordapps`` in ``C:\Corda\`` and save your CorDapp jar file to it. Alternatively,
download one of our `sample CorDapps <https://www.corda.net/samples/>`_ to the ``cordapps`` directory download one of our `sample CorDapps <https://www.corda.net/samples/>`_ to the ``cordapps`` directory
3. Save the below as ``C:\Corda\node.conf``. See :doc:`corda-configuration-file` for a description of these options 3. Save the below as ``C:\Corda\node.conf``. See :doc:`corda-configuration-file` for a description of these options

View File

@ -80,14 +80,20 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
name "O=Notary Service,OU=corda,L=London,C=GB" name "O=Notary Service,OU=corda,L=London,C=GB"
notary = [validating : true] notary = [validating : true]
p2pPort 10002 p2pPort 10002
rpcPort 10003 rpcSettings {
address "localhost:10003"
adminAddress "localhost:10013"
}
webPort 10004 webPort 10004
cordapps = [] cordapps = []
} }
node { node {
name "O=Alice Corp,L=London,C=GB" name "O=Alice Corp,L=London,C=GB"
p2pPort 10005 p2pPort 10005
rpcPort 10006 rpcSettings {
address "localhost:10006"
adminAddress "localhost:10016"
}
webPort 10007 webPort 10007
cordapps = [] cordapps = []
rpcUsers = [ rpcUsers = [

View File

@ -27,6 +27,7 @@ import net.corda.finance.contracts.asset.Cash
import net.corda.testing.contracts.DummyContract import net.corda.testing.contracts.DummyContract
import net.corda.testing.contracts.DummyState import net.corda.testing.contracts.DummyState
import java.security.PublicKey import java.security.PublicKey
import java.security.Signature
import java.time.Instant import java.time.Instant
// ``InitiatorFlow`` is our first flow, and will communicate with // ``InitiatorFlow`` is our first flow, and will communicate with
@ -205,6 +206,19 @@ class InitiatorFlow(val arg1: Boolean, val arg2: Int, private val counterparty:
val packet3: UntrustworthyData<Any> = regulatorSession.receive<Any>() val packet3: UntrustworthyData<Any> = regulatorSession.receive<Any>()
// DOCEND 06 // DOCEND 06
// We may also batch receives in order to increase performance. This
// ensures that only a single checkpoint is created for all received
// messages.
// Type-safe variant:
val signatures: List<UntrustworthyData<Signature>> =
receiveAll(Signature::class.java, listOf(counterpartySession, regulatorSession))
// Dynamic variant:
val messages: Map<FlowSession, UntrustworthyData<*>> =
receiveAllMap(mapOf(
counterpartySession to Boolean::class.java,
regulatorSession to String::class.java
))
/**----------------------------------- /**-----------------------------------
* EXTRACTING STATES FROM THE VAULT * * EXTRACTING STATES FROM THE VAULT *
-----------------------------------**/ -----------------------------------**/

View File

@ -7,39 +7,66 @@ import net.corda.core.identity.CordaX500Name;
import net.corda.finance.contracts.ICommercialPaperState; import net.corda.finance.contracts.ICommercialPaperState;
import net.corda.finance.contracts.JavaCommercialPaper; import net.corda.finance.contracts.JavaCommercialPaper;
import net.corda.finance.contracts.asset.Cash; import net.corda.finance.contracts.asset.Cash;
import net.corda.testing.node.MockServices;
import net.corda.testing.core.TestIdentity; import net.corda.testing.core.TestIdentity;
import net.corda.testing.node.MockServices;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import java.security.PublicKey;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import static java.util.Collections.emptyList; import static java.util.Collections.singletonList;
import static net.corda.core.crypto.Crypto.generateKeyPair;
import static net.corda.finance.Currencies.DOLLARS; import static net.corda.finance.Currencies.DOLLARS;
import static net.corda.finance.Currencies.issuedBy; import static net.corda.finance.Currencies.issuedBy;
import static net.corda.finance.contracts.JavaCommercialPaper.JCP_PROGRAM_ID; 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.MockServicesKt.makeTestIdentityService;
import static net.corda.testing.node.NodeTestUtils.ledger; import static net.corda.testing.node.NodeTestUtils.ledger;
import static net.corda.testing.node.NodeTestUtils.transaction; 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 { public class CommercialPaperTest {
private static final TestIdentity ALICE = new TestIdentity(ALICE_NAME, 70L);
private static final PublicKey BIG_CORP_PUBKEY = generateKeyPair().getPublic(); private static final TestIdentity alice = new TestIdentity(ALICE_NAME, 70L);
private static final TestIdentity BOB = new TestIdentity(BOB_NAME, 80L); private static final TestIdentity bigCorp = new TestIdentity(new CordaX500Name("BigCorp", "New York", "GB"));
private static final TestIdentity MEGA_CORP = new TestIdentity(new CordaX500Name("MegaCorp", "London", "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 final byte[] defaultRef = {123};
private final MockServices ledgerServices = new MockServices(MEGA_CORP); private MockServices ledgerServices;
@Before
public void setUp() {
// DOCSTART 11
ledgerServices = new MockServices(
// A list of packages to scan for cordapps
singletonList("net.corda.finance.contracts"),
// The identity represented by this set of mock services. Defaults to a test identity.
// You can also use the alternative parameter initialIdentityName which accepts a
// [CordaX500Name]
megaCorp,
// An implementation of [IdentityService], which contains a list of all identities known
// to the node. Use [makeTestIdentityService] which returns an implementation of
// [InMemoryIdentityService] with the given identities
makeTestIdentityService(megaCorp.getIdentity())
);
// DOCEND 11
}
@SuppressWarnings("unused")
// DOCSTART 12
private final MockServices simpleLedgerServices = new MockServices(
// This is the identity of the node
megaCorp,
// Other identities the test node knows about
bigCorp,
alice
);
// DOCEND 12
// DOCSTART 1 // DOCSTART 1
private ICommercialPaperState getPaper() { private ICommercialPaperState getPaper() {
return new JavaCommercialPaper.State( return new JavaCommercialPaper.State(
MEGA_CORP.ref(defaultRef), megaCorp.ref(defaultRef),
MEGA_CORP.getParty(), megaCorp.getParty(),
issuedBy(DOLLARS(1000), MEGA_CORP.ref(defaultRef)), issuedBy(DOLLARS(1000), megaCorp.ref(defaultRef)),
TEST_TX_TIME.plus(7, ChronoUnit.DAYS) TEST_TX_TIME.plus(7, ChronoUnit.DAYS)
); );
} }
@ -69,7 +96,7 @@ public class CommercialPaperTest {
ledger(ledgerServices, l -> { ledger(ledgerServices, l -> {
l.transaction(tx -> { l.transaction(tx -> {
tx.input(JCP_PROGRAM_ID, inState); tx.input(JCP_PROGRAM_ID, inState);
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move()); tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move());
tx.attachments(JCP_PROGRAM_ID); tx.attachments(JCP_PROGRAM_ID);
return tx.verifies(); return tx.verifies();
}); });
@ -85,7 +112,7 @@ public class CommercialPaperTest {
ledger(ledgerServices, l -> { ledger(ledgerServices, l -> {
l.transaction(tx -> { l.transaction(tx -> {
tx.input(JCP_PROGRAM_ID, inState); tx.input(JCP_PROGRAM_ID, inState);
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move()); tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move());
tx.attachments(JCP_PROGRAM_ID); tx.attachments(JCP_PROGRAM_ID);
return tx.failsWith("the state is propagated"); return tx.failsWith("the state is propagated");
}); });
@ -96,15 +123,15 @@ public class CommercialPaperTest {
// DOCSTART 5 // DOCSTART 5
@Test @Test
public void simpleCPMoveSuccess() { public void simpleCPMoveSuccessAndFailure() {
ICommercialPaperState inState = getPaper(); ICommercialPaperState inState = getPaper();
ledger(ledgerServices, l -> { ledger(ledgerServices, l -> {
l.transaction(tx -> { l.transaction(tx -> {
tx.input(JCP_PROGRAM_ID, inState); tx.input(JCP_PROGRAM_ID, inState);
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move()); tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move());
tx.attachments(JCP_PROGRAM_ID); tx.attachments(JCP_PROGRAM_ID);
tx.failsWith("the state is propagated"); tx.failsWith("the state is propagated");
tx.output(JCP_PROGRAM_ID, "alice's paper", inState.withOwner(ALICE.getParty())); tx.output(JCP_PROGRAM_ID, "alice's paper", inState.withOwner(alice.getParty()));
return tx.verifies(); return tx.verifies();
}); });
return Unit.INSTANCE; return Unit.INSTANCE;
@ -112,6 +139,24 @@ public class CommercialPaperTest {
} }
// DOCEND 5 // DOCEND 5
// DOCSTART 13
@Test
public void simpleCPMoveSuccess() {
ICommercialPaperState inState = getPaper();
ledger(ledgerServices, l -> {
l.transaction(tx -> {
tx.input(JCP_PROGRAM_ID, inState);
tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move());
tx.attachments(JCP_PROGRAM_ID);
tx.timeWindow(TEST_TX_TIME);
tx.output(JCP_PROGRAM_ID, "alice's paper", inState.withOwner(alice.getParty()));
return tx.verifies();
});
return Unit.INSTANCE;
});
}
// DOCEND 13
// DOCSTART 6 // DOCSTART 6
@Test @Test
public void simpleIssuanceWithTweak() { public void simpleIssuanceWithTweak() {
@ -120,11 +165,11 @@ public class CommercialPaperTest {
tx.output(JCP_PROGRAM_ID, "paper", getPaper()); // Some CP is issued onto the ledger by MegaCorp. tx.output(JCP_PROGRAM_ID, "paper", getPaper()); // Some CP is issued onto the ledger by MegaCorp.
tx.attachments(JCP_PROGRAM_ID); tx.attachments(JCP_PROGRAM_ID);
tx.tweak(tw -> { tx.tweak(tw -> {
tw.command(BIG_CORP_PUBKEY, new JavaCommercialPaper.Commands.Issue()); tw.command(bigCorp.getPublicKey(), new JavaCommercialPaper.Commands.Issue());
tw.timeWindow(TEST_TX_TIME); tw.timeWindow(TEST_TX_TIME);
return tw.failsWith("output states are issued by a command signer"); return tw.failsWith("output states are issued by a command signer");
}); });
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Issue()); tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Issue());
tx.timeWindow(TEST_TX_TIME); tx.timeWindow(TEST_TX_TIME);
return tx.verifies(); return tx.verifies();
}); });
@ -140,11 +185,11 @@ public class CommercialPaperTest {
tx.output(JCP_PROGRAM_ID, "paper", getPaper()); // Some CP is issued onto the ledger by MegaCorp. tx.output(JCP_PROGRAM_ID, "paper", getPaper()); // Some CP is issued onto the ledger by MegaCorp.
tx.attachments(JCP_PROGRAM_ID); tx.attachments(JCP_PROGRAM_ID);
tx.tweak(tw -> { tx.tweak(tw -> {
tw.command(BIG_CORP_PUBKEY, new JavaCommercialPaper.Commands.Issue()); tw.command(bigCorp.getPublicKey(), new JavaCommercialPaper.Commands.Issue());
tw.timeWindow(TEST_TX_TIME); tw.timeWindow(TEST_TX_TIME);
return tw.failsWith("output states are issued by a command signer"); return tw.failsWith("output states are issued by a command signer");
}); });
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Issue()); tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Issue());
tx.timeWindow(TEST_TX_TIME); tx.timeWindow(TEST_TX_TIME);
return tx.verifies(); return tx.verifies();
}); });
@ -154,11 +199,11 @@ public class CommercialPaperTest {
// DOCSTART 8 // DOCSTART 8
@Test @Test
public void chainCommercialPaper() { public void chainCommercialPaper() {
PartyAndReference issuer = MEGA_CORP.ref(defaultRef); PartyAndReference issuer = megaCorp.ref(defaultRef);
ledger(ledgerServices, l -> { ledger(ledgerServices, l -> {
l.unverifiedTransaction(tx -> { l.unverifiedTransaction(tx -> {
tx.output(Cash.PROGRAM_ID, "alice's $900", tx.output(Cash.PROGRAM_ID, "alice's $900",
new Cash.State(issuedBy(DOLLARS(900), issuer), ALICE.getParty())); new Cash.State(issuedBy(DOLLARS(900), issuer), alice.getParty()));
tx.attachments(Cash.PROGRAM_ID); tx.attachments(Cash.PROGRAM_ID);
return Unit.INSTANCE; return Unit.INSTANCE;
}); });
@ -166,7 +211,7 @@ public class CommercialPaperTest {
// Some CP is issued onto the ledger by MegaCorp. // Some CP is issued onto the ledger by MegaCorp.
l.transaction("Issuance", tx -> { l.transaction("Issuance", tx -> {
tx.output(JCP_PROGRAM_ID, "paper", getPaper()); tx.output(JCP_PROGRAM_ID, "paper", getPaper());
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Issue()); tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Issue());
tx.attachments(JCP_PROGRAM_ID); tx.attachments(JCP_PROGRAM_ID);
tx.timeWindow(TEST_TX_TIME); tx.timeWindow(TEST_TX_TIME);
return tx.verifies(); return tx.verifies();
@ -175,11 +220,11 @@ public class CommercialPaperTest {
l.transaction("Trade", tx -> { l.transaction("Trade", tx -> {
tx.input("paper"); tx.input("paper");
tx.input("alice's $900"); tx.input("alice's $900");
tx.output(Cash.PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), MEGA_CORP.getParty())); tx.output(Cash.PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), megaCorp.getParty()));
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper"); JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(ALICE.getParty())); tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(alice.getParty()));
tx.command(ALICE.getPublicKey(), new Cash.Commands.Move()); tx.command(alice.getPublicKey(), new Cash.Commands.Move());
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move()); tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move());
return tx.verifies(); return tx.verifies();
}); });
return Unit.INSTANCE; return Unit.INSTANCE;
@ -190,11 +235,11 @@ public class CommercialPaperTest {
// DOCSTART 9 // DOCSTART 9
@Test @Test
public void chainCommercialPaperDoubleSpend() { public void chainCommercialPaperDoubleSpend() {
PartyAndReference issuer = MEGA_CORP.ref(defaultRef); PartyAndReference issuer = megaCorp.ref(defaultRef);
ledger(ledgerServices, l -> { ledger(ledgerServices, l -> {
l.unverifiedTransaction(tx -> { l.unverifiedTransaction(tx -> {
tx.output(Cash.PROGRAM_ID, "alice's $900", tx.output(Cash.PROGRAM_ID, "alice's $900",
new Cash.State(issuedBy(DOLLARS(900), issuer), ALICE.getParty())); new Cash.State(issuedBy(DOLLARS(900), issuer), alice.getParty()));
tx.attachments(Cash.PROGRAM_ID); tx.attachments(Cash.PROGRAM_ID);
return Unit.INSTANCE; return Unit.INSTANCE;
}); });
@ -202,7 +247,7 @@ public class CommercialPaperTest {
// Some CP is issued onto the ledger by MegaCorp. // Some CP is issued onto the ledger by MegaCorp.
l.transaction("Issuance", tx -> { l.transaction("Issuance", tx -> {
tx.output(Cash.PROGRAM_ID, "paper", getPaper()); tx.output(Cash.PROGRAM_ID, "paper", getPaper());
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Issue()); tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Issue());
tx.attachments(JCP_PROGRAM_ID); tx.attachments(JCP_PROGRAM_ID);
tx.timeWindow(TEST_TX_TIME); tx.timeWindow(TEST_TX_TIME);
return tx.verifies(); return tx.verifies();
@ -211,11 +256,11 @@ public class CommercialPaperTest {
l.transaction("Trade", tx -> { l.transaction("Trade", tx -> {
tx.input("paper"); tx.input("paper");
tx.input("alice's $900"); tx.input("alice's $900");
tx.output(Cash.PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), MEGA_CORP.getParty())); tx.output(Cash.PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), megaCorp.getParty()));
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper"); JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(ALICE.getParty())); tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(alice.getParty()));
tx.command(ALICE.getPublicKey(), new Cash.Commands.Move()); tx.command(alice.getPublicKey(), new Cash.Commands.Move());
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move()); tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move());
return tx.verifies(); return tx.verifies();
}); });
@ -223,8 +268,8 @@ public class CommercialPaperTest {
tx.input("paper"); tx.input("paper");
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper"); JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
// We moved a paper to other pubkey. // We moved a paper to other pubkey.
tx.output(JCP_PROGRAM_ID, "bob's paper", inputPaper.withOwner(BOB.getParty())); tx.output(JCP_PROGRAM_ID, "bob's paper", inputPaper.withOwner(bob.getParty()));
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move()); tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move());
return tx.verifies(); return tx.verifies();
}); });
l.fails(); l.fails();
@ -236,11 +281,11 @@ public class CommercialPaperTest {
// DOCSTART 10 // DOCSTART 10
@Test @Test
public void chainCommercialPaperTweak() { public void chainCommercialPaperTweak() {
PartyAndReference issuer = MEGA_CORP.ref(defaultRef); PartyAndReference issuer = megaCorp.ref(defaultRef);
ledger(ledgerServices, l -> { ledger(ledgerServices, l -> {
l.unverifiedTransaction(tx -> { l.unverifiedTransaction(tx -> {
tx.output(Cash.PROGRAM_ID, "alice's $900", tx.output(Cash.PROGRAM_ID, "alice's $900",
new Cash.State(issuedBy(DOLLARS(900), issuer), ALICE.getParty())); new Cash.State(issuedBy(DOLLARS(900), issuer), alice.getParty()));
tx.attachments(Cash.PROGRAM_ID); tx.attachments(Cash.PROGRAM_ID);
return Unit.INSTANCE; return Unit.INSTANCE;
}); });
@ -248,7 +293,7 @@ public class CommercialPaperTest {
// Some CP is issued onto the ledger by MegaCorp. // Some CP is issued onto the ledger by MegaCorp.
l.transaction("Issuance", tx -> { l.transaction("Issuance", tx -> {
tx.output(Cash.PROGRAM_ID, "paper", getPaper()); tx.output(Cash.PROGRAM_ID, "paper", getPaper());
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Issue()); tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Issue());
tx.attachments(JCP_PROGRAM_ID); tx.attachments(JCP_PROGRAM_ID);
tx.timeWindow(TEST_TX_TIME); tx.timeWindow(TEST_TX_TIME);
return tx.verifies(); return tx.verifies();
@ -257,11 +302,11 @@ public class CommercialPaperTest {
l.transaction("Trade", tx -> { l.transaction("Trade", tx -> {
tx.input("paper"); tx.input("paper");
tx.input("alice's $900"); tx.input("alice's $900");
tx.output(Cash.PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), MEGA_CORP.getParty())); tx.output(Cash.PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), megaCorp.getParty()));
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper"); JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(ALICE.getParty())); tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(alice.getParty()));
tx.command(ALICE.getPublicKey(), new Cash.Commands.Move()); tx.command(alice.getPublicKey(), new Cash.Commands.Move());
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move()); tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move());
return tx.verifies(); return tx.verifies();
}); });
@ -270,8 +315,8 @@ public class CommercialPaperTest {
tx.input("paper"); tx.input("paper");
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper"); JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
// We moved a paper to another pubkey. // We moved a paper to another pubkey.
tx.output(JCP_PROGRAM_ID, "bob's paper", inputPaper.withOwner(BOB.getParty())); tx.output(JCP_PROGRAM_ID, "bob's paper", inputPaper.withOwner(bob.getParty()));
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move()); tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move());
return tx.verifies(); return tx.verifies();
}); });
lw.fails(); lw.fails();

View File

@ -13,7 +13,6 @@ import net.corda.testing.node.StartedMockNode
import net.corda.testing.node.startFlow import net.corda.testing.node.startFlow
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Ignore
import org.junit.Test import org.junit.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
@ -37,7 +36,6 @@ class FxTransactionBuildTutorialTest {
mockNet.stopNodes() mockNet.stopNodes()
} }
@Ignore("Pending fix from corda")
@Test @Test
fun `Run ForeignExchangeFlow to completion`() { fun `Run ForeignExchangeFlow to completion`() {
// Use NodeA as issuer and create some dollars // Use NodeA as issuer and create some dollars

View File

@ -18,6 +18,7 @@ import net.corda.testing.core.*
import net.corda.testing.internal.rigorousMock import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices
import net.corda.testing.node.ledger import net.corda.testing.node.ledger
import net.corda.testing.node.makeTestIdentityService
import net.corda.testing.node.transaction import net.corda.testing.node.transaction
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
@ -25,30 +26,46 @@ import org.junit.Test
class CommercialPaperTest { class CommercialPaperTest {
private companion object { private companion object {
val alice = TestIdentity(ALICE_NAME, 70) val alice = TestIdentity(ALICE_NAME, 70)
val BIG_CORP_PUBKEY = generateKeyPair().public val bob = TestIdentity(BOB_NAME, 80)
val BOB = TestIdentity(BOB_NAME, 80).party val bigCorp = TestIdentity((CordaX500Name("BigCorp", "New York", "GB")))
val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party val dummyNotary = TestIdentity(DUMMY_NOTARY_NAME, 20)
val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB")) val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB"))
val ALICE get() = alice.party
val ALICE_PUBKEY get() = alice.publicKey
val MEGA_CORP get() = megaCorp.party
val MEGA_CORP_PUBKEY get() = megaCorp.publicKey
} }
@Rule @Rule
@JvmField @JvmField
val testSerialization = SerializationEnvironmentRule() val testSerialization = SerializationEnvironmentRule()
private val ledgerServices = MockServices(emptyList(), MEGA_CORP.name, rigorousMock<IdentityService>().also { // DOCSTART 11
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY) private val ledgerServices = MockServices(
doReturn(null).whenever(it).partyFromKey(BIG_CORP_PUBKEY) // A list of packages to scan for cordapps
doReturn(null).whenever(it).partyFromKey(ALICE_PUBKEY) listOf("net.corda.finance.contracts"),
// The identity represented by this set of mock services. Defaults to a test identity.
// You can also use the alternative parameter initialIdentityName which accepts a
// [CordaX500Name]
megaCorp,
rigorousMock<IdentityService>().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
// DOCSTART 12
@Suppress("unused")
private val simpleLedgerServices = MockServices(
// This is the identity of the node
megaCorp,
// Other identities the test node knows about
bigCorp,
alice
)
// DOCEND 12
// DOCSTART 1 // DOCSTART 1
fun getPaper(): ICommercialPaperState = CommercialPaper.State( fun getPaper(): ICommercialPaperState = CommercialPaper.State(
issuance = MEGA_CORP.ref(123), issuance = megaCorp.party.ref(123),
owner = MEGA_CORP, owner = megaCorp.party,
faceValue = 1000.DOLLARS `issued by` MEGA_CORP.ref(123), faceValue = 1000.DOLLARS `issued by` megaCorp.party.ref(123),
maturityDate = TEST_TX_TIME + 7.days maturityDate = TEST_TX_TIME + 7.days
) )
// DOCEND 1 // DOCEND 1
@ -58,7 +75,7 @@ class CommercialPaperTest {
@Test(expected = IllegalStateException::class) @Test(expected = IllegalStateException::class)
fun simpleCP() { fun simpleCP() {
val inState = getPaper() val inState = getPaper()
ledgerServices.ledger(DUMMY_NOTARY) { ledgerServices.ledger(dummyNotary.party) {
transaction { transaction {
attachments(CP_PROGRAM_ID) attachments(CP_PROGRAM_ID)
input(CP_PROGRAM_ID, inState) input(CP_PROGRAM_ID, inState)
@ -73,10 +90,10 @@ class CommercialPaperTest {
@Test(expected = TransactionVerificationException.ContractRejection::class) @Test(expected = TransactionVerificationException.ContractRejection::class)
fun simpleCPMove() { fun simpleCPMove() {
val inState = getPaper() val inState = getPaper()
ledgerServices.ledger(DUMMY_NOTARY) { ledgerServices.ledger(dummyNotary.party) {
transaction { transaction {
input(CP_PROGRAM_ID, inState) input(CP_PROGRAM_ID, inState)
command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move()) command(megaCorp.publicKey, CommercialPaper.Commands.Move())
attachments(CP_PROGRAM_ID) attachments(CP_PROGRAM_ID)
verifies() verifies()
} }
@ -88,10 +105,10 @@ class CommercialPaperTest {
@Test @Test
fun simpleCPMoveFails() { fun simpleCPMoveFails() {
val inState = getPaper() val inState = getPaper()
ledgerServices.ledger(DUMMY_NOTARY) { ledgerServices.ledger(dummyNotary.party) {
transaction { transaction {
input(CP_PROGRAM_ID, inState) input(CP_PROGRAM_ID, inState)
command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move()) command(megaCorp.publicKey, CommercialPaper.Commands.Move())
attachments(CP_PROGRAM_ID) attachments(CP_PROGRAM_ID)
`fails with`("the state is propagated") `fails with`("the state is propagated")
} }
@ -101,35 +118,52 @@ class CommercialPaperTest {
// DOCSTART 5 // DOCSTART 5
@Test @Test
fun simpleCPMoveSuccess() { fun simpleCPMoveFailureAndSuccess() {
val inState = getPaper() val inState = getPaper()
ledgerServices.ledger(DUMMY_NOTARY) { ledgerServices.ledger(dummyNotary.party) {
transaction { transaction {
input(CP_PROGRAM_ID, inState) input(CP_PROGRAM_ID, inState)
command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move()) command(megaCorp.publicKey, CommercialPaper.Commands.Move())
attachments(CP_PROGRAM_ID) attachments(CP_PROGRAM_ID)
`fails with`("the state is propagated") `fails with`("the state is propagated")
output(CP_PROGRAM_ID, "alice's paper", inState.withOwner(ALICE)) output(CP_PROGRAM_ID, "alice's paper", inState.withOwner(alice.party))
verifies() verifies()
} }
} }
} }
// DOCEND 5 // DOCEND 5
// DOCSTART 13
@Test
fun simpleCPMoveSuccess() {
val inState = getPaper()
ledgerServices.ledger(dummyNotary.party) {
transaction {
input(CP_PROGRAM_ID, inState)
command(megaCorp.publicKey, CommercialPaper.Commands.Move())
attachments(CP_PROGRAM_ID)
timeWindow(TEST_TX_TIME)
output(CP_PROGRAM_ID, "alice's paper", inState.withOwner(alice.party))
verifies()
}
}
}
// DOCEND 13
// DOCSTART 6 // DOCSTART 6
@Test @Test
fun `simple issuance with tweak`() { fun `simple issuance with tweak`() {
ledgerServices.ledger(DUMMY_NOTARY) { ledgerServices.ledger(dummyNotary.party) {
transaction { transaction {
output(CP_PROGRAM_ID, "paper", getPaper()) // Some CP is issued onto the ledger by MegaCorp. output(CP_PROGRAM_ID, "paper", getPaper()) // Some CP is issued onto the ledger by MegaCorp.
attachments(CP_PROGRAM_ID) attachments(CP_PROGRAM_ID)
tweak { tweak {
// The wrong pubkey. // The wrong pubkey.
command(BIG_CORP_PUBKEY, CommercialPaper.Commands.Issue()) command(bigCorp.publicKey, CommercialPaper.Commands.Issue())
timeWindow(TEST_TX_TIME) timeWindow(TEST_TX_TIME)
`fails with`("output states are issued by a command signer") `fails with`("output states are issued by a command signer")
} }
command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Issue()) command(megaCorp.publicKey, CommercialPaper.Commands.Issue())
timeWindow(TEST_TX_TIME) timeWindow(TEST_TX_TIME)
verifies() verifies()
} }
@ -140,16 +174,16 @@ class CommercialPaperTest {
// DOCSTART 7 // DOCSTART 7
@Test @Test
fun `simple issuance with tweak and top level transaction`() { fun `simple issuance with tweak and top level transaction`() {
ledgerServices.transaction(DUMMY_NOTARY) { ledgerServices.transaction(dummyNotary.party) {
output(CP_PROGRAM_ID, "paper", getPaper()) // Some CP is issued onto the ledger by MegaCorp. output(CP_PROGRAM_ID, "paper", getPaper()) // Some CP is issued onto the ledger by MegaCorp.
attachments(CP_PROGRAM_ID) attachments(CP_PROGRAM_ID)
tweak { tweak {
// The wrong pubkey. // The wrong pubkey.
command(BIG_CORP_PUBKEY, CommercialPaper.Commands.Issue()) command(bigCorp.publicKey, CommercialPaper.Commands.Issue())
timeWindow(TEST_TX_TIME) timeWindow(TEST_TX_TIME)
`fails with`("output states are issued by a command signer") `fails with`("output states are issued by a command signer")
} }
command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Issue()) command(megaCorp.publicKey, CommercialPaper.Commands.Issue())
timeWindow(TEST_TX_TIME) timeWindow(TEST_TX_TIME)
verifies() verifies()
} }
@ -159,17 +193,17 @@ class CommercialPaperTest {
// DOCSTART 8 // DOCSTART 8
@Test @Test
fun `chain commercial paper`() { fun `chain commercial paper`() {
val issuer = MEGA_CORP.ref(123) val issuer = megaCorp.party.ref(123)
ledgerServices.ledger(DUMMY_NOTARY) { ledgerServices.ledger(dummyNotary.party) {
unverifiedTransaction { unverifiedTransaction {
attachments(Cash.PROGRAM_ID) attachments(Cash.PROGRAM_ID)
output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH issuedBy issuer ownedBy ALICE) output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH issuedBy issuer ownedBy alice.party)
} }
// Some CP is issued onto the ledger by MegaCorp. // Some CP is issued onto the ledger by MegaCorp.
transaction("Issuance") { transaction("Issuance") {
output(CP_PROGRAM_ID, "paper", getPaper()) output(CP_PROGRAM_ID, "paper", getPaper())
command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Issue()) command(megaCorp.publicKey, CommercialPaper.Commands.Issue())
attachments(CP_PROGRAM_ID) attachments(CP_PROGRAM_ID)
timeWindow(TEST_TX_TIME) timeWindow(TEST_TX_TIME)
verifies() verifies()
@ -179,10 +213,10 @@ class CommercialPaperTest {
transaction("Trade") { transaction("Trade") {
input("paper") input("paper")
input("alice's $900") input("alice's $900")
output(Cash.PROGRAM_ID, "borrowed $900", 900.DOLLARS.CASH issuedBy issuer ownedBy MEGA_CORP) output(Cash.PROGRAM_ID, "borrowed $900", 900.DOLLARS.CASH issuedBy issuer ownedBy megaCorp.party)
output(CP_PROGRAM_ID, "alice's paper", "paper".output<ICommercialPaperState>().withOwner(ALICE)) output(CP_PROGRAM_ID, "alice's paper", "paper".output<ICommercialPaperState>().withOwner(alice.party))
command(ALICE_PUBKEY, Cash.Commands.Move()) command(alice.publicKey, Cash.Commands.Move())
command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move()) command(megaCorp.publicKey, CommercialPaper.Commands.Move())
verifies() verifies()
} }
} }
@ -192,17 +226,17 @@ class CommercialPaperTest {
// DOCSTART 9 // DOCSTART 9
@Test @Test
fun `chain commercial paper double spend`() { fun `chain commercial paper double spend`() {
val issuer = MEGA_CORP.ref(123) val issuer = megaCorp.party.ref(123)
ledgerServices.ledger(DUMMY_NOTARY) { ledgerServices.ledger(dummyNotary.party) {
unverifiedTransaction { unverifiedTransaction {
attachments(Cash.PROGRAM_ID) attachments(Cash.PROGRAM_ID)
output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH issuedBy issuer ownedBy ALICE) output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH issuedBy issuer ownedBy alice.party)
} }
// Some CP is issued onto the ledger by MegaCorp. // Some CP is issued onto the ledger by MegaCorp.
transaction("Issuance") { transaction("Issuance") {
output(CP_PROGRAM_ID, "paper", getPaper()) output(CP_PROGRAM_ID, "paper", getPaper())
command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Issue()) command(megaCorp.publicKey, CommercialPaper.Commands.Issue())
attachments(CP_PROGRAM_ID) attachments(CP_PROGRAM_ID)
timeWindow(TEST_TX_TIME) timeWindow(TEST_TX_TIME)
verifies() verifies()
@ -211,18 +245,18 @@ class CommercialPaperTest {
transaction("Trade") { transaction("Trade") {
input("paper") input("paper")
input("alice's $900") input("alice's $900")
output(Cash.PROGRAM_ID, "borrowed $900", 900.DOLLARS.CASH issuedBy issuer ownedBy MEGA_CORP) output(Cash.PROGRAM_ID, "borrowed $900", 900.DOLLARS.CASH issuedBy issuer ownedBy megaCorp.party)
output(CP_PROGRAM_ID, "alice's paper", "paper".output<ICommercialPaperState>().withOwner(ALICE)) output(CP_PROGRAM_ID, "alice's paper", "paper".output<ICommercialPaperState>().withOwner(alice.party))
command(ALICE_PUBKEY, Cash.Commands.Move()) command(alice.publicKey, Cash.Commands.Move())
command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move()) command(megaCorp.publicKey, CommercialPaper.Commands.Move())
verifies() verifies()
} }
transaction { transaction {
input("paper") input("paper")
// We moved a paper to another pubkey. // We moved a paper to another pubkey.
output(CP_PROGRAM_ID, "bob's paper", "paper".output<ICommercialPaperState>().withOwner(BOB)) output(CP_PROGRAM_ID, "bob's paper", "paper".output<ICommercialPaperState>().withOwner(bob.party))
command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move()) command(megaCorp.publicKey, CommercialPaper.Commands.Move())
verifies() verifies()
} }
@ -234,17 +268,17 @@ class CommercialPaperTest {
// DOCSTART 10 // DOCSTART 10
@Test @Test
fun `chain commercial tweak`() { fun `chain commercial tweak`() {
val issuer = MEGA_CORP.ref(123) val issuer = megaCorp.party.ref(123)
ledgerServices.ledger(DUMMY_NOTARY) { ledgerServices.ledger(dummyNotary.party) {
unverifiedTransaction { unverifiedTransaction {
attachments(Cash.PROGRAM_ID) attachments(Cash.PROGRAM_ID)
output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH issuedBy issuer ownedBy ALICE) output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH issuedBy issuer ownedBy alice.party)
} }
// Some CP is issued onto the ledger by MegaCorp. // Some CP is issued onto the ledger by MegaCorp.
transaction("Issuance") { transaction("Issuance") {
output(CP_PROGRAM_ID, "paper", getPaper()) output(CP_PROGRAM_ID, "paper", getPaper())
command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Issue()) command(megaCorp.publicKey, CommercialPaper.Commands.Issue())
attachments(CP_PROGRAM_ID) attachments(CP_PROGRAM_ID)
timeWindow(TEST_TX_TIME) timeWindow(TEST_TX_TIME)
verifies() verifies()
@ -253,10 +287,10 @@ class CommercialPaperTest {
transaction("Trade") { transaction("Trade") {
input("paper") input("paper")
input("alice's $900") input("alice's $900")
output(Cash.PROGRAM_ID, "borrowed $900", 900.DOLLARS.CASH issuedBy issuer ownedBy MEGA_CORP) output(Cash.PROGRAM_ID, "borrowed $900", 900.DOLLARS.CASH issuedBy issuer ownedBy megaCorp.party)
output(CP_PROGRAM_ID, "alice's paper", "paper".output<ICommercialPaperState>().withOwner(ALICE)) output(CP_PROGRAM_ID, "alice's paper", "paper".output<ICommercialPaperState>().withOwner(alice.party))
command(ALICE_PUBKEY, Cash.Commands.Move()) command(alice.publicKey, Cash.Commands.Move())
command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move()) command(megaCorp.publicKey, CommercialPaper.Commands.Move())
verifies() verifies()
} }
@ -264,8 +298,8 @@ class CommercialPaperTest {
transaction { transaction {
input("paper") input("paper")
// We moved a paper to another pubkey. // We moved a paper to another pubkey.
output(CP_PROGRAM_ID, "bob's paper", "paper".output<ICommercialPaperState>().withOwner(BOB)) output(CP_PROGRAM_ID, "bob's paper", "paper".output<ICommercialPaperState>().withOwner(bob.party))
command(MEGA_CORP_PUBKEY, CommercialPaper.Commands.Move()) command(megaCorp.publicKey, CommercialPaper.Commands.Move())
verifies() verifies()
} }
fails() fails()

View File

@ -88,7 +88,7 @@ class ObligationTests {
doReturn(MINI_CORP).whenever(it).partyFromKey(MINI_CORP_PUBKEY) 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 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( private fun cashObligationTestRoots(
group: LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter> group: LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>
) = group.apply { ) = group.apply {

View File

@ -68,5 +68,7 @@ internal fun parseNetworkParameters(configuration: NetworkParametersConfiguratio
configuration.maxMessageSize, configuration.maxMessageSize,
configuration.maxTransactionSize, configuration.maxTransactionSize,
Instant.now(), Instant.now(),
epoch) epoch,
// TODO: Tudor, Michal - pass the actual network parameters where we figure out how
emptyMap())
} }

View File

@ -45,6 +45,12 @@ dependencies {
compile "org.apache.curator:curator-recipes:${curator_version}" compile "org.apache.curator:curator-recipes:${curator_version}"
testCompile "org.apache.curator:curator-test:${curator_version}" testCompile "org.apache.curator:curator-test:${curator_version}"
// FastClasspathScanner: classpath scanning - needed for the NetworkBootstraper
compile 'io.github.lukehutch:fast-classpath-scanner:2.0.21'
// Pure-Java Snappy compression
compile 'org.iq80.snappy:snappy:0.4'
// Unit testing helpers. // Unit testing helpers.
testCompile "junit:junit:$junit_version" testCompile "junit:junit:$junit_version"
testCompile "org.assertj:assertj-core:$assertj_version" testCompile "org.assertj:assertj-core:$assertj_version"

View File

@ -1,7 +1,9 @@
package net.corda.nodeapi.internal package net.corda.nodeapi.internal
import net.corda.core.contracts.Attachment import net.corda.core.contracts.Attachment
import net.corda.core.contracts.ContractAttachment
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.internal.DEPLOYED_CORDAPP_UPLOADER
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
@ -31,6 +33,10 @@ class AttachmentsClassLoader(attachments: List<Attachment>, parent: ClassLoader
} }
init { 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) { for (attachment in attachments) {
attachment.openAsJAR().use { jar -> attachment.openAsJAR().use { jar ->
while (true) { while (true) {

View File

@ -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<ContractClassName> {
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 <T> withContractsInJar(jarInputStream: InputStream, withContracts: (List<ContractClassName>, 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()
}
}

View File

@ -15,7 +15,6 @@ import java.security.SignatureException
* A signed [NodeInfo] object containing a signature for each identity. The list of signatures is expected * A signed [NodeInfo] object containing a signature for each identity. The list of signatures is expected
* to be in the same order as the identities. * to be in the same order as the identities.
*/ */
// TODO Move this to net.corda.nodeapi.internal.network
// TODO Add signatures for composite keys. The current thinking is to make sure there is a signature for each leaf key // TODO Add signatures for composite keys. The current thinking is to make sure there is a signature for each leaf key
// that the node owns. This check can only be done by the network map server as it can check with the doorman if a node // that the node owns. This check can only be done by the network map server as it can check with the doorman if a node
// is part of a composite identity. This of course further requires the doorman being able to issue CSRs for composite // is part of a composite identity. This of course further requires the doorman being able to issue CSRs for composite

View File

@ -1,26 +1,33 @@
package net.corda.nodeapi.internal.network package net.corda.nodeapi.internal.network
import com.google.common.hash.Hashing
import com.google.common.hash.HashingInputStream
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import net.corda.cordform.CordformNode 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.identity.Party
import net.corda.core.internal.* import net.corda.core.internal.*
import net.corda.core.internal.concurrent.fork import net.corda.core.internal.concurrent.fork
import net.corda.core.node.NetworkParameters import net.corda.core.node.NetworkParameters
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.node.NotaryInfo import net.corda.core.node.NotaryInfo
import net.corda.core.node.services.AttachmentId
import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.deserialize
import net.corda.nodeapi.internal.serialization.CordaSerializationMagic
import net.corda.core.serialization.internal.SerializationEnvironmentImpl import net.corda.core.serialization.internal.SerializationEnvironmentImpl
import net.corda.core.serialization.internal._contextSerializationEnv import net.corda.core.serialization.internal._contextSerializationEnv
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.seconds import net.corda.core.utilities.seconds
import net.corda.nodeapi.internal.SignedNodeInfo 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.AMQP_P2P_CONTEXT
import net.corda.nodeapi.internal.serialization.CordaSerializationMagic
import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl
import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme
import net.corda.nodeapi.internal.serialization.kryo.AbstractKryoSerializationScheme import net.corda.nodeapi.internal.serialization.kryo.AbstractKryoSerializationScheme
import net.corda.nodeapi.internal.serialization.kryo.kryoMagic 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.Files
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.Paths import java.nio.file.Paths
@ -44,15 +51,17 @@ class NetworkBootstrapper {
) )
private const val LOGS_DIR_NAME = "logs" private const val LOGS_DIR_NAME = "logs"
private const val WHITELIST_FILE_NAME = "whitelist.txt"
@JvmStatic @JvmStatic
fun main(args: Array<String>) { fun main(args: Array<String>) {
val arg = args.singleOrNull() ?: throw IllegalArgumentException("Expecting single argument which is the nodes' parent directory") val baseNodeDirectory = args.firstOrNull() ?: throw IllegalArgumentException("Expecting first argument which is the nodes' parent directory")
NetworkBootstrapper().bootstrap(Paths.get(arg).toAbsolutePath().normalize()) 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<String>?) {
directory.createDirectories() directory.createDirectories()
println("Bootstrapping local network in $directory") println("Bootstrapping local network in $directory")
generateDirectoriesIfNeeded(directory) generateDirectoriesIfNeeded(directory)
@ -69,7 +78,10 @@ class NetworkBootstrapper {
println("Gathering notary identities") println("Gathering notary identities")
val notaryInfos = gatherNotaryInfos(nodeInfoFiles) val notaryInfos = gatherNotaryInfos(nodeInfoFiles)
println("Notary identities to be used in network-parameters file: ${notaryInfos.joinToString("; ") { it.prettyPrint() }}") 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!") println("Bootstrapping complete!")
} finally { } finally {
_contextSerializationEnv.set(null) _contextSerializationEnv.set(null)
@ -85,8 +97,7 @@ class NetworkBootstrapper {
for (confFile in confFiles) { for (confFile in confFiles) {
val nodeName = confFile.fileName.toString().removeSuffix(".conf") val nodeName = confFile.fileName.toString().removeSuffix(".conf")
println("Generating directory for $nodeName") println("Generating directory for $nodeName")
val nodeDir = (directory / nodeName) val nodeDir = (directory / nodeName).createDirectories()
if (!nodeDir.exists()) { nodeDir.createDirectory() }
confFile.moveTo(nodeDir / "node.conf", StandardCopyOption.REPLACE_EXISTING) confFile.moveTo(nodeDir / "node.conf", StandardCopyOption.REPLACE_EXISTING)
Files.copy(cordaJar, (nodeDir / "corda.jar"), StandardCopyOption.REPLACE_EXISTING) Files.copy(cordaJar, (nodeDir / "corda.jar"), StandardCopyOption.REPLACE_EXISTING)
} }
@ -150,7 +161,7 @@ class NetworkBootstrapper {
if (nodeConfig.hasPath("notary")) { if (nodeConfig.hasPath("notary")) {
val validating = nodeConfig.getConfig("notary").getBoolean("validating") val validating = nodeConfig.getConfig("notary").getBoolean("validating")
// And the node-info file contains the notary's identity // And the node-info file contains the notary's identity
val nodeInfo = nodeInfoFile.readAll().deserialize<SignedNodeInfo>().verified() val nodeInfo = nodeInfoFile.readObject<SignedNodeInfo>().verified()
NotaryInfo(nodeInfo.notaryIdentity(), validating) NotaryInfo(nodeInfo.notaryIdentity(), validating)
} else { } else {
null null
@ -158,7 +169,7 @@ class NetworkBootstrapper {
}.distinct() // We need distinct as nodes part of a distributed notary share the same notary identity }.distinct() // We need distinct as nodes part of a distributed notary share the same notary identity
} }
private fun installNetworkParameters(notaryInfos: List<NotaryInfo>, nodeDirs: List<Path>) { private fun installNetworkParameters(notaryInfos: List<NotaryInfo>, nodeDirs: List<Path>, whitelist: Map<String, List<AttachmentId>>) {
// TODO Add config for minimumPlatformVersion, maxMessageSize and maxTransactionSize // TODO Add config for minimumPlatformVersion, maxMessageSize and maxTransactionSize
val copier = NetworkParametersCopier(NetworkParameters( val copier = NetworkParametersCopier(NetworkParameters(
minimumPlatformVersion = 1, minimumPlatformVersion = 1,
@ -166,12 +177,58 @@ class NetworkBootstrapper {
modifiedTime = Instant.now(), modifiedTime = Instant.now(),
maxMessageSize = 10485760, maxMessageSize = 10485760,
maxTransactionSize = Int.MAX_VALUE, maxTransactionSize = Int.MAX_VALUE,
epoch = 1 epoch = 1,
whitelistedContractImplementations = whitelist
), overwriteFile = true) ), overwriteFile = true)
nodeDirs.forEach { copier.install(it) } nodeDirs.forEach { copier.install(it) }
} }
private fun generateWhitelist(whitelistFile: Path, cordapps: List<String>?): Map<String, List<AttachmentId>> {
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<String, List<AttachmentId>>) {
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<String, List<AttachmentId>> = 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 NotaryInfo.prettyPrint(): String = "${identity.name} (${if (validating) "" else "non-"}validating)"
private fun NodeInfo.notaryIdentity(): Party { private fun NodeInfo.notaryIdentity(): Party {

View File

@ -14,6 +14,9 @@ import java.time.Instant
const val NETWORK_PARAMS_FILE_NAME = "network-parameters" const val NETWORK_PARAMS_FILE_NAME = "network-parameters"
const val NETWORK_PARAMS_UPDATE_FILE_NAME = "network-parameters-update" const val NETWORK_PARAMS_UPDATE_FILE_NAME = "network-parameters-update"
typealias SignedNetworkMap = SignedDataWithCert<NetworkMap>
typealias SignedNetworkParameters = SignedDataWithCert<NetworkParameters>
/** /**
* Data structure representing the network map available from the HTTP network map service as a serialised blob. * Data structure representing the network map available from the HTTP network map service as a serialised blob.
* @property nodeInfoHashes list of network participant's [NodeInfo] hashes * @property nodeInfoHashes list of network participant's [NodeInfo] hashes

View File

@ -18,10 +18,12 @@ val KRYO_RPC_CLIENT_CONTEXT = SerializationContextImpl(kryoMagic,
GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()), GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()),
emptyMap(), emptyMap(),
true, true,
SerializationContext.UseCase.RPCClient) SerializationContext.UseCase.RPCClient,
null)
val AMQP_RPC_CLIENT_CONTEXT = SerializationContextImpl(amqpMagic, val AMQP_RPC_CLIENT_CONTEXT = SerializationContextImpl(amqpMagic,
SerializationDefaults.javaClass.classLoader, SerializationDefaults.javaClass.classLoader,
GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()), GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()),
emptyMap(), emptyMap(),
true, true,
SerializationContext.UseCase.RPCClient) SerializationContext.UseCase.RPCClient,
null)

View File

@ -0,0 +1,31 @@
package net.corda.nodeapi.internal.serialization
import java.io.EOFException
import java.io.InputStream
import java.io.OutputStream
import java.nio.ByteBuffer
class OrdinalBits(private val ordinal: Int) {
interface OrdinalWriter {
val bits: OrdinalBits
val encodedSize get() = 1
fun writeTo(stream: OutputStream) = stream.write(bits.ordinal)
fun putTo(buffer: ByteBuffer) = buffer.put(bits.ordinal.toByte())!!
}
init {
require(ordinal >= 0) { "The ordinal must be non-negative." }
require(ordinal < 128) { "Consider implementing a varint encoding." }
}
}
class OrdinalReader<out E : Any>(private val values: Array<E>) {
private val enumName = values[0].javaClass.simpleName
private val range = 0 until values.size
fun readFrom(stream: InputStream): E {
val ordinal = stream.read()
if (ordinal == -1) throw EOFException("Expected a $enumName ordinal.")
if (ordinal !in range) throw NoSuchElementException("No $enumName with ordinal: $ordinal")
return values[ordinal]
}
}

View File

@ -1,8 +1,17 @@
package net.corda.nodeapi.internal.serialization package net.corda.nodeapi.internal.serialization
import net.corda.core.internal.VisibleForTesting
import net.corda.core.serialization.SerializationEncoding
import net.corda.core.utilities.ByteSequence import net.corda.core.utilities.ByteSequence
import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.OpaqueBytes
import net.corda.nodeapi.internal.serialization.OrdinalBits.OrdinalWriter
import org.iq80.snappy.SnappyFramedInputStream
import org.iq80.snappy.SnappyFramedOutputStream
import java.io.OutputStream
import java.io.InputStream
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.util.zip.DeflaterOutputStream
import java.util.zip.InflaterInputStream
class CordaSerializationMagic(bytes: ByteArray) : OpaqueBytes(bytes) { class CordaSerializationMagic(bytes: ByteArray) : OpaqueBytes(bytes) {
private val bufferView = slice() private val bufferView = slice()
@ -10,3 +19,40 @@ class CordaSerializationMagic(bytes: ByteArray) : OpaqueBytes(bytes) {
return if (data.slice(end = size) == bufferView) data.slice(size) else null return if (data.slice(end = size) == bufferView) data.slice(size) else null
} }
} }
enum class SectionId : OrdinalWriter {
/** Serialization data follows, and then discard the rest of the stream (if any) as legacy data may have trailing garbage. */
DATA_AND_STOP,
/** Identical behaviour to [DATA_AND_STOP], historically used for Kryo. Do not use in new code. */
ALT_DATA_AND_STOP,
/** The ordinal of a [CordaSerializationEncoding] follows, which should be used to decode the remainder of the stream. */
ENCODING;
companion object {
val reader = OrdinalReader(values())
}
override val bits = OrdinalBits(ordinal)
}
enum class CordaSerializationEncoding : SerializationEncoding, OrdinalWriter {
DEFLATE {
override fun wrap(stream: OutputStream) = DeflaterOutputStream(stream)
override fun wrap(stream: InputStream) = InflaterInputStream(stream)
},
SNAPPY {
override fun wrap(stream: OutputStream) = SnappyFramedOutputStream(stream)
override fun wrap(stream: InputStream) = SnappyFramedInputStream(stream, false)
};
companion object {
val reader = OrdinalReader(values())
}
override val bits = OrdinalBits(ordinal)
abstract fun wrap(stream: OutputStream): OutputStream
abstract fun wrap(stream: InputStream): InputStream
}
@VisibleForTesting
internal val encodingNotPermittedFormat = "Encoding not permitted: %s"

View File

@ -18,13 +18,18 @@ import java.util.concurrent.ExecutionException
val attachmentsClassLoaderEnabledPropertyName = "attachments.class.loader.enabled" val attachmentsClassLoaderEnabledPropertyName = "attachments.class.loader.enabled"
data class SerializationContextImpl(override val preferredSerializationVersion: SerializationMagic, internal object NullEncodingWhitelist : EncodingWhitelist {
override val deserializationClassLoader: ClassLoader, override fun acceptEncoding(encoding: SerializationEncoding) = false
override val whitelist: ClassWhitelist, }
override val properties: Map<Any, Any>,
override val objectReferencesEnabled: Boolean,
override val useCase: SerializationContext.UseCase) : SerializationContext {
data class SerializationContextImpl @JvmOverloads constructor(override val preferredSerializationVersion: SerializationMagic,
override val deserializationClassLoader: ClassLoader,
override val whitelist: ClassWhitelist,
override val properties: Map<Any, Any>,
override val objectReferencesEnabled: Boolean,
override val useCase: SerializationContext.UseCase,
override val encoding: SerializationEncoding?,
override val encodingWhitelist: EncodingWhitelist = NullEncodingWhitelist) : SerializationContext {
private val cache: Cache<List<SecureHash>, AttachmentsClassLoader> = CacheBuilder.newBuilder().weakValues().maximumSize(1024).build() private val cache: Cache<List<SecureHash>, AttachmentsClassLoader> = CacheBuilder.newBuilder().weakValues().maximumSize(1024).build()
/** /**
@ -70,6 +75,7 @@ data class SerializationContextImpl(override val preferredSerializationVersion:
} }
override fun withPreferredSerializationVersion(magic: SerializationMagic) = copy(preferredSerializationVersion = magic) override fun withPreferredSerializationVersion(magic: SerializationMagic) = copy(preferredSerializationVersion = magic)
override fun withEncoding(encoding: SerializationEncoding?) = copy(encoding = encoding)
} }
open class SerializationFactoryImpl : SerializationFactory() { open class SerializationFactoryImpl : SerializationFactory() {

View File

@ -27,22 +27,26 @@ val KRYO_RPC_SERVER_CONTEXT = SerializationContextImpl(kryoMagic,
GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()), GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()),
emptyMap(), emptyMap(),
true, true,
SerializationContext.UseCase.RPCServer) SerializationContext.UseCase.RPCServer,
null)
val KRYO_STORAGE_CONTEXT = SerializationContextImpl(kryoMagic, val KRYO_STORAGE_CONTEXT = SerializationContextImpl(kryoMagic,
SerializationDefaults.javaClass.classLoader, SerializationDefaults.javaClass.classLoader,
AllButBlacklisted, AllButBlacklisted,
emptyMap(), emptyMap(),
true, true,
SerializationContext.UseCase.Storage) SerializationContext.UseCase.Storage,
null)
val AMQP_STORAGE_CONTEXT = SerializationContextImpl(amqpMagic, val AMQP_STORAGE_CONTEXT = SerializationContextImpl(amqpMagic,
SerializationDefaults.javaClass.classLoader, SerializationDefaults.javaClass.classLoader,
AllButBlacklisted, AllButBlacklisted,
emptyMap(), emptyMap(),
true, true,
SerializationContext.UseCase.Storage) SerializationContext.UseCase.Storage,
null)
val AMQP_RPC_SERVER_CONTEXT = SerializationContextImpl(amqpMagic, val AMQP_RPC_SERVER_CONTEXT = SerializationContextImpl(amqpMagic,
SerializationDefaults.javaClass.classLoader, SerializationDefaults.javaClass.classLoader,
GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()), GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()),
emptyMap(), emptyMap(),
true, true,
SerializationContext.UseCase.RPCServer) SerializationContext.UseCase.RPCServer,
null)

View File

@ -20,18 +20,19 @@ val KRYO_P2P_CONTEXT = SerializationContextImpl(kryoMagic,
GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()), GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()),
emptyMap(), emptyMap(),
true, true,
SerializationContext.UseCase.P2P) SerializationContext.UseCase.P2P,
null)
val KRYO_CHECKPOINT_CONTEXT = SerializationContextImpl(kryoMagic, val KRYO_CHECKPOINT_CONTEXT = SerializationContextImpl(kryoMagic,
SerializationDefaults.javaClass.classLoader, SerializationDefaults.javaClass.classLoader,
QuasarWhitelist, QuasarWhitelist,
emptyMap(), emptyMap(),
true, true,
SerializationContext.UseCase.Checkpoint) SerializationContext.UseCase.Checkpoint,
null)
val AMQP_P2P_CONTEXT = SerializationContextImpl(amqpMagic, val AMQP_P2P_CONTEXT = SerializationContextImpl(amqpMagic,
SerializationDefaults.javaClass.classLoader, SerializationDefaults.javaClass.classLoader,
GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()), GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()),
emptyMap(), emptyMap(),
true, true,
SerializationContext.UseCase.P2P) SerializationContext.UseCase.P2P,
null)

View File

@ -0,0 +1,31 @@
package net.corda.nodeapi.internal.serialization.amqp
import com.esotericsoftware.kryo.io.ByteBufferInputStream
import net.corda.nodeapi.internal.serialization.kryo.ByteBufferOutputStream
import net.corda.nodeapi.internal.serialization.kryo.serializeOutputStreamPool
import java.io.InputStream
import java.io.OutputStream
import java.nio.ByteBuffer
fun InputStream.asByteBuffer(): ByteBuffer {
return if (this is ByteBufferInputStream) {
byteBuffer // BBIS has no other state, so this is perfectly safe.
} else {
ByteBuffer.wrap(serializeOutputStreamPool.run {
copyTo(it)
it.toByteArray()
})
}
}
fun <T> OutputStream.alsoAsByteBuffer(remaining: Int, task: (ByteBuffer) -> T): T {
return if (this is ByteBufferOutputStream) {
alsoAsByteBuffer(remaining, task)
} else {
serializeOutputStreamPool.run {
val result = it.alsoAsByteBuffer(remaining, task)
it.copyTo(this)
result
}
}
}

View File

@ -1,18 +1,27 @@
package net.corda.nodeapi.internal.serialization.amqp package net.corda.nodeapi.internal.serialization.amqp
import com.esotericsoftware.kryo.io.ByteBufferInputStream
import net.corda.core.internal.VisibleForTesting
import net.corda.core.internal.getStackTraceAsString import net.corda.core.internal.getStackTraceAsString
import net.corda.core.serialization.EncodingWhitelist
import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.SerializedBytes
import net.corda.core.utilities.ByteSequence import net.corda.core.utilities.ByteSequence
import net.corda.nodeapi.internal.serialization.CordaSerializationEncoding
import net.corda.nodeapi.internal.serialization.NullEncodingWhitelist
import net.corda.nodeapi.internal.serialization.SectionId
import net.corda.nodeapi.internal.serialization.encodingNotPermittedFormat
import org.apache.qpid.proton.amqp.Binary import org.apache.qpid.proton.amqp.Binary
import org.apache.qpid.proton.amqp.DescribedType import org.apache.qpid.proton.amqp.DescribedType
import org.apache.qpid.proton.amqp.UnsignedByte import org.apache.qpid.proton.amqp.UnsignedByte
import org.apache.qpid.proton.amqp.UnsignedInteger import org.apache.qpid.proton.amqp.UnsignedInteger
import org.apache.qpid.proton.codec.Data import org.apache.qpid.proton.codec.Data
import java.io.InputStream
import java.io.NotSerializableException import java.io.NotSerializableException
import java.lang.reflect.ParameterizedType import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type import java.lang.reflect.Type
import java.lang.reflect.TypeVariable import java.lang.reflect.TypeVariable
import java.lang.reflect.WildcardType import java.lang.reflect.WildcardType
import java.nio.ByteBuffer
data class ObjectAndEnvelope<out T>(val obj: T, val envelope: Envelope) data class ObjectAndEnvelope<out T>(val obj: T, val envelope: Envelope)
@ -22,7 +31,8 @@ data class ObjectAndEnvelope<out T>(val obj: T, val envelope: Envelope)
* @param serializerFactory This is the factory for [AMQPSerializer] instances and can be shared across multiple * @param serializerFactory This is the factory for [AMQPSerializer] instances and can be shared across multiple
* instances and threads. * instances and threads.
*/ */
class DeserializationInput(internal val serializerFactory: SerializerFactory) { class DeserializationInput @JvmOverloads constructor(private val serializerFactory: SerializerFactory,
private val encodingWhitelist: EncodingWhitelist = NullEncodingWhitelist) {
private val objectHistory: MutableList<Any> = mutableListOf() private val objectHistory: MutableList<Any> = mutableListOf()
internal companion object { internal companion object {
@ -47,6 +57,28 @@ class DeserializationInput(internal val serializerFactory: SerializerFactory) {
} }
return size + BYTES_NEEDED_TO_PEEK return size + BYTES_NEEDED_TO_PEEK
} }
@VisibleForTesting
@Throws(NotSerializableException::class)
internal fun <T> withDataBytes(byteSequence: ByteSequence, encodingWhitelist: EncodingWhitelist, task: (ByteBuffer) -> T): T {
// Check that the lead bytes match expected header
val amqpSequence = amqpMagic.consume(byteSequence) ?: throw NotSerializableException("Serialization header does not match.")
var stream: InputStream = ByteBufferInputStream(amqpSequence)
try {
while (true) {
when (SectionId.reader.readFrom(stream)) {
SectionId.ENCODING -> {
val encoding = CordaSerializationEncoding.reader.readFrom(stream)
encodingWhitelist.acceptEncoding(encoding) || throw NotSerializableException(encodingNotPermittedFormat.format(encoding))
stream = encoding.wrap(stream)
}
SectionId.DATA_AND_STOP, SectionId.ALT_DATA_AND_STOP -> return task(stream.asByteBuffer())
}
}
} finally {
stream.close()
}
}
} }
@Throws(NotSerializableException::class) @Throws(NotSerializableException::class)
@ -58,12 +90,12 @@ class DeserializationInput(internal val serializerFactory: SerializerFactory) {
@Throws(NotSerializableException::class) @Throws(NotSerializableException::class)
internal fun getEnvelope(byteSequence: ByteSequence): Envelope { internal fun getEnvelope(byteSequence: ByteSequence): Envelope {
// Check that the lead bytes match expected header return withDataBytes(byteSequence, encodingWhitelist) { dataBytes ->
val dataBytes = amqpMagic.consume(byteSequence) ?: throw NotSerializableException("Serialization header does not match.") val data = Data.Factory.create()
val data = Data.Factory.create() val expectedSize = dataBytes.remaining()
val expectedSize = dataBytes.remaining() if (data.decode(dataBytes) != expectedSize.toLong()) throw NotSerializableException("Unexpected size of data")
if (data.decode(dataBytes) != expectedSize.toLong()) throw NotSerializableException("Unexpected size of data") Envelope.get(data)
return Envelope.get(data) }
} }
@Throws(NotSerializableException::class) @Throws(NotSerializableException::class)

View File

@ -12,7 +12,7 @@ import net.corda.nodeapi.internal.serialization.carpenter.Field as CarpenterFiel
import net.corda.nodeapi.internal.serialization.carpenter.Schema as CarpenterSchema import net.corda.nodeapi.internal.serialization.carpenter.Schema as CarpenterSchema
const val DESCRIPTOR_DOMAIN: String = "net.corda" const val DESCRIPTOR_DOMAIN: String = "net.corda"
val amqpMagic = CordaSerializationMagic("corda".toByteArray() + byteArrayOf(1, 0, 0)) val amqpMagic = CordaSerializationMagic("corda".toByteArray() + byteArrayOf(1, 0))
/** /**
* This and the classes below are OO representations of the AMQP XML schema described in the specification. Their * This and the classes below are OO representations of the AMQP XML schema described in the specification. Their

View File

@ -1,10 +1,14 @@
package net.corda.nodeapi.internal.serialization.amqp package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.serialization.SerializationEncoding
import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.SerializedBytes
import net.corda.nodeapi.internal.serialization.CordaSerializationEncoding
import net.corda.nodeapi.internal.serialization.SectionId
import net.corda.nodeapi.internal.serialization.kryo.byteArrayOutput
import org.apache.qpid.proton.codec.Data import org.apache.qpid.proton.codec.Data
import java.io.NotSerializableException import java.io.NotSerializableException
import java.io.OutputStream
import java.lang.reflect.Type import java.lang.reflect.Type
import java.nio.ByteBuffer
import java.util.* import java.util.*
import kotlin.collections.LinkedHashSet import kotlin.collections.LinkedHashSet
@ -19,8 +23,7 @@ data class BytesAndSchemas<T : Any>(
* @param serializerFactory This is the factory for [AMQPSerializer] instances and can be shared across multiple * @param serializerFactory This is the factory for [AMQPSerializer] instances and can be shared across multiple
* instances and threads. * instances and threads.
*/ */
open class SerializationOutput(internal val serializerFactory: SerializerFactory) { open class SerializationOutput @JvmOverloads constructor(internal val serializerFactory: SerializerFactory, private val encoding: SerializationEncoding? = null) {
private val objectHistory: MutableMap<Any, Int> = IdentityHashMap() private val objectHistory: MutableMap<Any, Int> = IdentityHashMap()
private val serializerHistory: MutableSet<AMQPSerializer<*>> = LinkedHashSet() private val serializerHistory: MutableSet<AMQPSerializer<*>> = LinkedHashSet()
internal val schemaHistory: MutableSet<TypeNotation> = LinkedHashSet() internal val schemaHistory: MutableSet<TypeNotation> = LinkedHashSet()
@ -67,11 +70,21 @@ open class SerializationOutput(internal val serializerFactory: SerializerFactory
writeTransformSchema(TransformsSchema.build(schema, serializerFactory), this) writeTransformSchema(TransformsSchema.build(schema, serializerFactory), this)
} }
} }
val bytes = ByteArray(data.encodedSize().toInt() + 8) return SerializedBytes(byteArrayOutput {
val buf = ByteBuffer.wrap(bytes) var stream: OutputStream = it
amqpMagic.putTo(buf) try {
data.encode(buf) amqpMagic.writeTo(stream)
return SerializedBytes(bytes) if (encoding != null) {
SectionId.ENCODING.writeTo(stream)
(encoding as CordaSerializationEncoding).writeTo(stream)
stream = encoding.wrap(stream)
}
SectionId.DATA_AND_STOP.writeTo(stream)
stream.alsoAsByteBuffer(data.encodedSize().toInt(), data::encode)
} finally {
stream.close()
}
})
} }
internal fun writeObject(obj: Any, data: Data) { internal fun writeObject(obj: Any, data: Data) {

View File

@ -21,12 +21,12 @@ class ContractAttachmentSerializer(factory: SerializerFactory) : CustomSerialize
} catch (e: Exception) { } catch (e: Exception) {
throw MissingAttachmentsException(listOf(obj.id)) 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 { 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<ContractClassName>, val uploader: String?)
} }

View File

@ -12,6 +12,7 @@ import de.javakaffee.kryoserializers.BitSetSerializer
import de.javakaffee.kryoserializers.UnmodifiableCollectionsSerializer import de.javakaffee.kryoserializers.UnmodifiableCollectionsSerializer
import de.javakaffee.kryoserializers.guava.* import de.javakaffee.kryoserializers.guava.*
import net.corda.core.contracts.ContractAttachment import net.corda.core.contracts.ContractAttachment
import net.corda.core.contracts.ContractClassName
import net.corda.core.contracts.PrivacySalt import net.corda.core.contracts.PrivacySalt
import net.corda.core.crypto.CompositeKey import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
@ -206,29 +207,34 @@ object DefaultKryoCustomizer {
output.writeBytesWithLength(buffer.toByteArray()) output.writeBytesWithLength(buffer.toByteArray())
} }
output.writeString(obj.contract) output.writeString(obj.contract)
kryo.writeClassAndObject(output, obj.additionalContracts)
output.writeString(obj.uploader)
} }
override fun read(kryo: Kryo, input: Input, type: Class<ContractAttachment>): ContractAttachment { override fun read(kryo: Kryo, input: Input, type: Class<ContractAttachment>): ContractAttachment {
if (kryo.serializationContext() != null) { if (kryo.serializationContext() != null) {
val attachmentHash = SecureHash.SHA256(input.readBytes(32)) val attachmentHash = SecureHash.SHA256(input.readBytes(32))
val contract = input.readString() val contract = input.readString()
val additionalContracts = kryo.readClassAndObject(input) as Set<ContractClassName>
val uploader = input.readString()
val context = kryo.serializationContext()!! val context = kryo.serializationContext()!!
val attachmentStorage = context.serviceHub.attachments val attachmentStorage = context.serviceHub.attachments
val lazyAttachment = object : AbstractAttachment({ 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() attachment.open().readBytes()
}) { }) {
override val id = attachmentHash override val id = attachmentHash
} }
return ContractAttachment(lazyAttachment, contract) return ContractAttachment(lazyAttachment, contract, additionalContracts, uploader)
} else { } else {
val attachment = GeneratedAttachment(input.readBytesWithLength()) val attachment = GeneratedAttachment(input.readBytesWithLength())
val contract = input.readString() val contract = input.readString()
val additionalContracts = kryo.readClassAndObject(input) as Set<ContractClassName>
return ContractAttachment(attachment, contract) val uploader = input.readString()
return ContractAttachment(attachment, contract, additionalContracts, uploader)
} }
} }
} }

View File

@ -16,10 +16,12 @@ import net.corda.core.utilities.ByteSequence
import net.corda.core.serialization.* import net.corda.core.serialization.*
import net.corda.nodeapi.internal.serialization.CordaSerializationMagic import net.corda.nodeapi.internal.serialization.CordaSerializationMagic
import net.corda.nodeapi.internal.serialization.CordaClassResolver import net.corda.nodeapi.internal.serialization.CordaClassResolver
import net.corda.nodeapi.internal.serialization.SectionId
import net.corda.nodeapi.internal.serialization.SerializationScheme import net.corda.nodeapi.internal.serialization.SerializationScheme
import net.corda.nodeapi.internal.serialization.*
import java.security.PublicKey import java.security.PublicKey
val kryoMagic = CordaSerializationMagic("corda".toByteArray() + byteArrayOf(0, 0, 1)) val kryoMagic = CordaSerializationMagic("corda".toByteArray() + byteArrayOf(0, 0))
private object AutoCloseableSerialisationDetector : Serializer<AutoCloseable>() { private object AutoCloseableSerialisationDetector : Serializer<AutoCloseable>() {
override fun write(kryo: Kryo, output: Output, closeable: AutoCloseable) { override fun write(kryo: Kryo, output: Output, closeable: AutoCloseable) {
@ -87,11 +89,25 @@ abstract class AbstractKryoSerializationScheme : SerializationScheme {
val dataBytes = kryoMagic.consume(byteSequence) ?: throw KryoException("Serialized bytes header does not match expected format.") val dataBytes = kryoMagic.consume(byteSequence) ?: throw KryoException("Serialized bytes header does not match expected format.")
return context.kryo { return context.kryo {
kryoInput(ByteBufferInputStream(dataBytes)) { kryoInput(ByteBufferInputStream(dataBytes)) {
if (context.objectReferencesEnabled) { val result: T
uncheckedCast(readClassAndObject(this)) loop@ while (true) {
} else { when (SectionId.reader.readFrom(this)) {
withoutReferences { uncheckedCast<Any?, T>(readClassAndObject(this)) } SectionId.ENCODING -> {
val encoding = CordaSerializationEncoding.reader.readFrom(this)
context.encodingWhitelist.acceptEncoding(encoding) || throw KryoException(encodingNotPermittedFormat.format(encoding))
substitute(encoding::wrap)
}
SectionId.DATA_AND_STOP, SectionId.ALT_DATA_AND_STOP -> {
result = if (context.objectReferencesEnabled) {
uncheckedCast(readClassAndObject(this))
} else {
withoutReferences { uncheckedCast<Any?, T>(readClassAndObject(this)) }
}
break@loop
}
}
} }
result
} }
} }
} }
@ -100,6 +116,12 @@ abstract class AbstractKryoSerializationScheme : SerializationScheme {
return context.kryo { return context.kryo {
SerializedBytes(kryoOutput { SerializedBytes(kryoOutput {
kryoMagic.writeTo(this) kryoMagic.writeTo(this)
context.encoding?.let { encoding ->
SectionId.ENCODING.writeTo(this)
(encoding as CordaSerializationEncoding).writeTo(this)
substitute(encoding::wrap)
}
SectionId.ALT_DATA_AND_STOP.writeTo(this) // Forward-compatible in null-encoding case.
if (context.objectReferencesEnabled) { if (context.objectReferencesEnabled) {
writeClassAndObject(this, obj) writeClassAndObject(this, obj)
} else { } else {

View File

@ -4,13 +4,34 @@ import com.esotericsoftware.kryo.io.Input
import com.esotericsoftware.kryo.io.Output import com.esotericsoftware.kryo.io.Output
import net.corda.core.internal.LazyPool import net.corda.core.internal.LazyPool
import java.io.* import java.io.*
import java.nio.ByteBuffer
class ByteBufferOutputStream(size: Int) : ByteArrayOutputStream(size) {
companion object {
private val ensureCapacity = ByteArrayOutputStream::class.java.getDeclaredMethod("ensureCapacity", Int::class.java).apply {
isAccessible = true
}
}
fun <T> alsoAsByteBuffer(remaining: Int, task: (ByteBuffer) -> T): T {
ensureCapacity.invoke(this, count + remaining)
val buffer = ByteBuffer.wrap(buf, count, remaining)
val result = task(buffer)
count = buffer.position()
return result
}
fun copyTo(stream: OutputStream) {
stream.write(buf, 0, count)
}
}
private val serializationBufferPool = LazyPool( private val serializationBufferPool = LazyPool(
newInstance = { ByteArray(64 * 1024) }) newInstance = { ByteArray(64 * 1024) })
private val serializeOutputStreamPool = LazyPool( internal val serializeOutputStreamPool = LazyPool(
clear = ByteArrayOutputStream::reset, clear = ByteBufferOutputStream::reset,
shouldReturnToPool = { it.size() < 256 * 1024 }, // Discard if it grew too large shouldReturnToPool = { it.size() < 256 * 1024 }, // Discard if it grew too large
newInstance = { ByteArrayOutputStream(64 * 1024) }) newInstance = { ByteBufferOutputStream(64 * 1024) })
internal fun <T> kryoInput(underlying: InputStream, task: Input.() -> T): T { internal fun <T> kryoInput(underlying: InputStream, task: Input.() -> T): T {
return serializationBufferPool.run { return serializationBufferPool.run {
@ -22,13 +43,19 @@ internal fun <T> kryoInput(underlying: InputStream, task: Input.() -> T): T {
} }
internal fun <T> kryoOutput(task: Output.() -> T): ByteArray { internal fun <T> kryoOutput(task: Output.() -> T): ByteArray {
return serializeOutputStreamPool.run { underlying -> return byteArrayOutput { underlying ->
serializationBufferPool.run { serializationBufferPool.run {
Output(it).use { output -> Output(it).use { output ->
output.outputStream = underlying output.outputStream = underlying
output.task() output.task()
} }
} }
}
}
internal fun <T> byteArrayOutput(task: (ByteBufferOutputStream) -> T): ByteArray {
return serializeOutputStreamPool.run { underlying ->
task(underlying)
underlying.toByteArray() // Must happen after close, to allow ZIP footer to be written for example. underlying.toByteArray() // Must happen after close, to allow ZIP footer to be written for example.
} }
} }

View File

@ -33,7 +33,7 @@ public final class ForbiddenLambdaSerializationTests {
EnumSet<SerializationContext.UseCase> contexts = EnumSet.complementOf(EnumSet.of(SerializationContext.UseCase.Checkpoint)); EnumSet<SerializationContext.UseCase> contexts = EnumSet.complementOf(EnumSet.of(SerializationContext.UseCase.Checkpoint));
contexts.forEach(ctx -> { contexts.forEach(ctx -> {
SerializationContext context = new SerializationContextImpl(KryoSerializationSchemeKt.getKryoMagic(), this.getClass().getClassLoader(), AllWhitelist.INSTANCE, Maps.newHashMap(), true, ctx); SerializationContext context = new SerializationContextImpl(KryoSerializationSchemeKt.getKryoMagic(), this.getClass().getClassLoader(), AllWhitelist.INSTANCE, Maps.newHashMap(), true, ctx, null);
String value = "Hey"; String value = "Hey";
Callable<String> target = (Callable<String> & Serializable) () -> value; Callable<String> target = (Callable<String> & Serializable) () -> value;
@ -55,7 +55,7 @@ public final class ForbiddenLambdaSerializationTests {
EnumSet<SerializationContext.UseCase> contexts = EnumSet.complementOf(EnumSet.of(SerializationContext.UseCase.Checkpoint)); EnumSet<SerializationContext.UseCase> contexts = EnumSet.complementOf(EnumSet.of(SerializationContext.UseCase.Checkpoint));
contexts.forEach(ctx -> { contexts.forEach(ctx -> {
SerializationContext context = new SerializationContextImpl(KryoSerializationSchemeKt.getKryoMagic(), this.getClass().getClassLoader(), AllWhitelist.INSTANCE, Maps.newHashMap(), true, ctx); SerializationContext context = new SerializationContextImpl(KryoSerializationSchemeKt.getKryoMagic(), this.getClass().getClassLoader(), AllWhitelist.INSTANCE, Maps.newHashMap(), true, ctx, null);
String value = "Hey"; String value = "Hey";
Callable<String> target = () -> value; Callable<String> target = () -> value;

View File

@ -26,7 +26,7 @@ public final class LambdaCheckpointSerializationTest {
@Before @Before
public void setup() { public void setup() {
factory = testSerialization.getSerializationFactory(); factory = testSerialization.getSerializationFactory();
context = new SerializationContextImpl(KryoSerializationSchemeKt.getKryoMagic(), this.getClass().getClassLoader(), AllWhitelist.INSTANCE, Maps.newHashMap(), true, SerializationContext.UseCase.Checkpoint); context = new SerializationContextImpl(KryoSerializationSchemeKt.getKryoMagic(), this.getClass().getClassLoader(), AllWhitelist.INSTANCE, Maps.newHashMap(), true, SerializationContext.UseCase.Checkpoint, null);
} }
@Test @Test

View File

@ -13,6 +13,7 @@ import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.node.internal.cordapp.CordappLoader import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.internal.cordapp.CordappProviderImpl 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.DUMMY_NOTARY_NAME
import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.TestIdentity import net.corda.testing.core.TestIdentity
@ -59,7 +60,8 @@ class AttachmentsClassLoaderStaticContractTests {
} }
private val serviceHub = rigorousMock<ServicesForResolution>().also { private val serviceHub = rigorousMock<ServicesForResolution>().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 @Test

View File

@ -18,6 +18,7 @@ import net.corda.nodeapi.DummyContractBackdoor
import net.corda.nodeapi.internal.serialization.SerializeAsTokenContextImpl import net.corda.nodeapi.internal.serialization.SerializeAsTokenContextImpl
import net.corda.nodeapi.internal.serialization.attachmentsClassLoaderEnabledPropertyName import net.corda.nodeapi.internal.serialization.attachmentsClassLoaderEnabledPropertyName
import net.corda.nodeapi.internal.serialization.withTokenContext 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.DUMMY_NOTARY_NAME
import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.TestIdentity import net.corda.testing.core.TestIdentity
@ -58,12 +59,15 @@ class AttachmentsClassLoaderTests {
@JvmField @JvmField
val testSerialization = SerializationEnvironmentRule() val testSerialization = SerializationEnvironmentRule()
private val attachments = MockAttachmentStorage() 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 cordapp get() = cordappProvider.cordapps.first()
private val attachmentId get() = cordappProvider.getCordappAttachmentId(cordapp)!! private val attachmentId get() = cordappProvider.getCordappAttachmentId(cordapp)!!
private val appContext get() = cordappProvider.getAppContext(cordapp) private val appContext get() = cordappProvider.getAppContext(cordapp)
private val serviceHub = rigorousMock<ServiceHub>().also { private val serviceHub = rigorousMock<ServiceHub>().also {
doReturn(attachments).whenever(it).attachments 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 // These ClassLoaders work together to load 'AnotherDummyContract' in a disposable way, such that even though
@ -279,7 +283,7 @@ class AttachmentsClassLoaderTests {
.withClassLoader(child) .withClassLoader(child)
val bytes = run { val bytes = run {
val wireTransaction = tx.toWireTransaction(cordappProvider, context) val wireTransaction = tx.toWireTransaction(serviceHub, context)
wireTransaction.serialize(context = context) wireTransaction.serialize(context = context)
} }
val copiedWireTransaction = bytes.deserialize(context = context) val copiedWireTransaction = bytes.deserialize(context = context)
@ -303,7 +307,7 @@ class AttachmentsClassLoaderTests {
val outboundContext = SerializationFactory.defaultFactory.defaultContext val outboundContext = SerializationFactory.defaultFactory.defaultContext
.withServiceHub(serviceHub) .withServiceHub(serviceHub)
.withClassLoader(child) .withClassLoader(child)
val wireTransaction = tx.toWireTransaction(cordappProvider, outboundContext) val wireTransaction = tx.toWireTransaction(serviceHub, outboundContext)
wireTransaction.serialize(context = outboundContext) wireTransaction.serialize(context = outboundContext)
} }
// use empty attachmentStorage // use empty attachmentStorage

View File

@ -319,7 +319,8 @@ class X509UtilitiesTest {
AllWhitelist, AllWhitelist,
emptyMap(), emptyMap(),
true, true,
SerializationContext.UseCase.P2P) SerializationContext.UseCase.P2P,
null)
val expected = X509Utilities.createSelfSignedCACertificate(ALICE.name.x500Principal, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)) val expected = X509Utilities.createSelfSignedCACertificate(ALICE.name.x500Principal, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME))
val serialized = expected.serialize(factory, context).bytes val serialized = expected.serialize(factory, context).bytes
val actual = serialized.deserialize<X509Certificate>(factory, context) val actual = serialized.deserialize<X509Certificate>(factory, context)
@ -334,7 +335,8 @@ class X509UtilitiesTest {
AllWhitelist, AllWhitelist,
emptyMap(), emptyMap(),
true, true,
SerializationContext.UseCase.P2P) SerializationContext.UseCase.P2P,
null)
val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val rootCACert = X509Utilities.createSelfSignedCACertificate(ALICE_NAME.x500Principal, rootCAKey) val rootCACert = X509Utilities.createSelfSignedCACertificate(ALICE_NAME.x500Principal, rootCAKey)
val certificate = X509Utilities.createCertificate(CertificateType.TLS, rootCACert, rootCAKey, BOB_NAME.x500Principal, BOB.publicKey) val certificate = X509Utilities.createCertificate(CertificateType.TLS, rootCACert, rootCAKey, BOB_NAME.x500Principal, BOB.publicKey)

View File

@ -43,6 +43,7 @@ class ContractAttachmentSerializerTest {
assertEquals(contractAttachment.id, deserialized.attachment.id) assertEquals(contractAttachment.id, deserialized.attachment.id)
assertEquals(contractAttachment.contract, deserialized.contract) assertEquals(contractAttachment.contract, deserialized.contract)
assertEquals(contractAttachment.additionalContracts, deserialized.additionalContracts)
assertArrayEquals(contractAttachment.open().readBytes(), deserialized.open().readBytes()) assertArrayEquals(contractAttachment.open().readBytes(), deserialized.open().readBytes())
} }
@ -58,6 +59,7 @@ class ContractAttachmentSerializerTest {
assertEquals(contractAttachment.id, deserialized.attachment.id) assertEquals(contractAttachment.id, deserialized.attachment.id)
assertEquals(contractAttachment.contract, deserialized.contract) assertEquals(contractAttachment.contract, deserialized.contract)
assertEquals(contractAttachment.additionalContracts, deserialized.additionalContracts)
assertArrayEquals(contractAttachment.open().readBytes(), deserialized.open().readBytes()) assertArrayEquals(contractAttachment.open().readBytes(), deserialized.open().readBytes())
} }

View File

@ -6,6 +6,7 @@ import com.esotericsoftware.kryo.io.Output
import com.esotericsoftware.kryo.util.DefaultClassResolver import com.esotericsoftware.kryo.util.DefaultClassResolver
import com.esotericsoftware.kryo.util.MapReferenceResolver import com.esotericsoftware.kryo.util.MapReferenceResolver
import com.nhaarman.mockito_kotlin.* import com.nhaarman.mockito_kotlin.*
import net.corda.core.internal.DEPLOYED_CORDAPP_UPLOADER
import net.corda.core.node.services.AttachmentStorage import net.corda.core.node.services.AttachmentStorage
import net.corda.core.serialization.* import net.corda.core.serialization.*
import net.corda.nodeapi.internal.AttachmentsClassLoader import net.corda.nodeapi.internal.AttachmentsClassLoader
@ -108,8 +109,8 @@ class CordaClassResolverTests {
val emptyMapClass = mapOf<Any, Any>().javaClass val emptyMapClass = mapOf<Any, Any>().javaClass
} }
private val emptyWhitelistContext: SerializationContext = SerializationContextImpl(kryoMagic, this.javaClass.classLoader, EmptyWhitelist, emptyMap(), true, SerializationContext.UseCase.P2P) private val emptyWhitelistContext: SerializationContext = SerializationContextImpl(kryoMagic, this.javaClass.classLoader, EmptyWhitelist, emptyMap(), true, SerializationContext.UseCase.P2P, null)
private val allButBlacklistedContext: SerializationContext = SerializationContextImpl(kryoMagic, this.javaClass.classLoader, AllButBlacklisted, emptyMap(), true, SerializationContext.UseCase.P2P) private val allButBlacklistedContext: SerializationContext = SerializationContextImpl(kryoMagic, this.javaClass.classLoader, AllButBlacklisted, emptyMap(), true, SerializationContext.UseCase.P2P, null)
@Test @Test
fun `Annotation on enum works for specialised entries`() { fun `Annotation on enum works for specialised entries`() {
CordaClassResolver(emptyWhitelistContext).getRegistration(Foo.Bar::class.java) CordaClassResolver(emptyWhitelistContext).getRegistration(Foo.Bar::class.java)
@ -195,7 +196,7 @@ class CordaClassResolverTests {
CordaClassResolver(emptyWhitelistContext).getRegistration(DefaultSerializable::class.java) 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) @Test(expected = KryoException::class)
fun `Annotation does not work in conjunction with AttachmentClassLoader annotation`() { fun `Annotation does not work in conjunction with AttachmentClassLoader annotation`() {
@ -206,6 +207,15 @@ class CordaClassResolverTests {
CordaClassResolver(emptyWhitelistContext).getRegistration(attachedClass) 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 @Test
fun `Annotation is inherited from interfaces`() { fun `Annotation is inherited from interfaces`() {
CordaClassResolver(emptyWhitelistContext).getRegistration(SerializableViaInterface::class.java) CordaClassResolver(emptyWhitelistContext).getRegistration(SerializableViaInterface::class.java)

View File

@ -1,10 +1,13 @@
package net.corda.nodeapi.internal.serialization package net.corda.nodeapi.internal.serialization
import com.esotericsoftware.kryo.Kryo import com.esotericsoftware.kryo.Kryo
import com.esotericsoftware.kryo.KryoException
import com.esotericsoftware.kryo.KryoSerializable import com.esotericsoftware.kryo.KryoSerializable
import com.esotericsoftware.kryo.io.Input import com.esotericsoftware.kryo.io.Input
import com.esotericsoftware.kryo.io.Output import com.esotericsoftware.kryo.io.Output
import com.google.common.primitives.Ints import com.google.common.primitives.Ints
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.contracts.PrivacySalt import net.corda.core.contracts.PrivacySalt
import net.corda.core.crypto.* import net.corda.core.crypto.*
import net.corda.core.internal.FetchDataFlow import net.corda.core.internal.FetchDataFlow
@ -16,24 +19,29 @@ import net.corda.node.services.persistence.NodeAttachmentService
import net.corda.nodeapi.internal.serialization.kryo.kryoMagic import net.corda.nodeapi.internal.serialization.kryo.kryoMagic
import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.TestIdentity import net.corda.testing.core.TestIdentity
import org.assertj.core.api.Assertions.assertThat import net.corda.testing.internal.rigorousMock
import org.assertj.core.api.Assertions.assertThatThrownBy import org.assertj.core.api.Assertions.*
import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.junit.runners.Parameterized.Parameters
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.InputStream import java.io.InputStream
import java.time.Instant import java.time.Instant
import java.util.* import java.util.*
import kotlin.test.assertEquals import kotlin.test.*
import kotlin.test.assertFalse
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
class KryoTests { @RunWith(Parameterized::class)
class KryoTests(private val compression: CordaSerializationEncoding?) {
companion object { companion object {
private val ALICE_PUBKEY = TestIdentity(ALICE_NAME, 70).publicKey private val ALICE_PUBKEY = TestIdentity(ALICE_NAME, 70).publicKey
@Parameters(name = "{0}")
@JvmStatic
fun compression() = arrayOf<CordaSerializationEncoding?>(null) + CordaSerializationEncoding.values()
} }
private lateinit var factory: SerializationFactory private lateinit var factory: SerializationFactory
@ -47,7 +55,11 @@ class KryoTests {
AllWhitelist, AllWhitelist,
emptyMap(), emptyMap(),
true, true,
SerializationContext.UseCase.Storage) SerializationContext.UseCase.Storage,
compression,
rigorousMock<EncodingWhitelist>().also {
if (compression != null) doReturn(true).whenever(it).acceptEncoding(compression)
})
} }
@Test @Test
@ -259,7 +271,8 @@ class KryoTests {
AllWhitelist, AllWhitelist,
emptyMap(), emptyMap(),
true, true,
SerializationContext.UseCase.P2P) SerializationContext.UseCase.P2P,
null)
pt.serialize(factory, context) pt.serialize(factory, context)
} }
@ -300,4 +313,24 @@ class KryoTests {
val exception2 = exception.serialize(factory, context).deserialize(factory, context) val exception2 = exception.serialize(factory, context).deserialize(factory, context)
assertEquals(randomHash, exception2.requested) assertEquals(randomHash, exception2.requested)
} }
@Test
fun `compression has the desired effect`() {
compression ?: return
val data = ByteArray(12345).also { Random(0).nextBytes(it) }.let { it + it }
val compressed = data.serialize(factory, context)
assertEquals(.5, compressed.size.toDouble() / data.size, .03)
assertArrayEquals(data, compressed.deserialize(factory, context))
}
@Test
fun `a particular encoding can be banned for deserialization`() {
compression ?: return
doReturn(false).whenever(context.encodingWhitelist).acceptEncoding(compression)
val compressed = "whatever".serialize(factory, context)
catchThrowable { compressed.deserialize(factory, context) }.run {
assertSame<Any>(KryoException::class.java, javaClass)
assertEquals(encodingNotPermittedFormat.format(compression), message)
}
}
} }

View File

@ -69,6 +69,7 @@ class ListsSerializationTest {
val serializedForm = emptyList<Int>().serialize() val serializedForm = emptyList<Int>().serialize()
val output = ByteArrayOutputStream().apply { val output = ByteArrayOutputStream().apply {
kryoMagic.writeTo(this) kryoMagic.writeTo(this)
SectionId.ALT_DATA_AND_STOP.writeTo(this)
write(DefaultClassResolver.NAME + 2) write(DefaultClassResolver.NAME + 2)
write(nameID) write(nameID)
write(javaEmptyListClass.name.toAscii()) write(javaEmptyListClass.name.toAscii())

View File

@ -79,6 +79,7 @@ class MapsSerializationTest {
val serializedForm = emptyMap<Int, Int>().serialize() val serializedForm = emptyMap<Int, Int>().serialize()
val output = ByteArrayOutputStream().apply { val output = ByteArrayOutputStream().apply {
kryoMagic.writeTo(this) kryoMagic.writeTo(this)
SectionId.ALT_DATA_AND_STOP.writeTo(this)
write(DefaultClassResolver.NAME + 2) write(DefaultClassResolver.NAME + 2)
write(nameID) write(nameID)
write(javaEmptyMapClass.name.toAscii()) write(javaEmptyMapClass.name.toAscii())

View File

@ -99,6 +99,7 @@ class SerializationTokenTest {
val stream = ByteArrayOutputStream() val stream = ByteArrayOutputStream()
Output(stream).use { Output(stream).use {
kryoMagic.writeTo(it) kryoMagic.writeTo(it)
SectionId.ALT_DATA_AND_STOP.writeTo(it)
kryo.writeClass(it, SingletonSerializeAsToken::class.java) kryo.writeClass(it, SingletonSerializeAsToken::class.java)
kryo.writeObject(it, emptyList<Any>()) kryo.writeObject(it, emptyList<Any>())
} }

View File

@ -56,6 +56,7 @@ class SetsSerializationTest {
val serializedForm = emptySet<Int>().serialize() val serializedForm = emptySet<Int>().serialize()
val output = ByteArrayOutputStream().apply { val output = ByteArrayOutputStream().apply {
kryoMagic.writeTo(this) kryoMagic.writeTo(this)
SectionId.ALT_DATA_AND_STOP.writeTo(this)
write(DefaultClassResolver.NAME + 2) write(DefaultClassResolver.NAME + 2)
write(nameID) write(nameID)
write(javaEmptySetClass.name.toAscii()) write(javaEmptySetClass.name.toAscii())

View File

@ -524,7 +524,7 @@ class EvolvabilityTests {
val resource = "networkParams.<corda version>.<commit sha>" val resource = "networkParams.<corda version>.<commit sha>"
val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party
val networkParameters = NetworkParameters( 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() val sf = testDefaultFactory()
sf.register(net.corda.nodeapi.internal.serialization.amqp.custom.InstantSerializer(sf)) sf.register(net.corda.nodeapi.internal.serialization.amqp.custom.InstantSerializer(sf))

View File

@ -2,6 +2,8 @@
package net.corda.nodeapi.internal.serialization.amqp package net.corda.nodeapi.internal.serialization.amqp
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever
import net.corda.client.rpc.RPCException import net.corda.client.rpc.RPCException
import net.corda.core.CordaRuntimeException import net.corda.core.CordaRuntimeException
import net.corda.core.contracts.* import net.corda.core.contracts.*
@ -11,21 +13,16 @@ import net.corda.core.flows.FlowException
import net.corda.core.identity.AbstractParty import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.AbstractAttachment import net.corda.core.internal.AbstractAttachment
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.*
import net.corda.core.serialization.MissingAttachmentsException
import net.corda.core.serialization.SerializationFactory
import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.LedgerTransaction
import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.OpaqueBytes
import net.corda.nodeapi.internal.serialization.AllWhitelist import net.corda.nodeapi.internal.serialization.*
import net.corda.nodeapi.internal.serialization.EmptyWhitelist
import net.corda.nodeapi.internal.serialization.GeneratedAttachment
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory.Companion.isPrimitive import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory.Companion.isPrimitive
import net.corda.nodeapi.internal.serialization.amqp.custom.BigDecimalSerializer
import net.corda.nodeapi.internal.serialization.amqp.custom.CurrencySerializer
import net.corda.testing.contracts.DummyContract import net.corda.testing.contracts.DummyContract
import net.corda.testing.core.BOB_NAME import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.TestIdentity import net.corda.testing.core.TestIdentity
import net.corda.testing.internal.rigorousMock
import org.apache.activemq.artemis.api.core.SimpleString import org.apache.activemq.artemis.api.core.SimpleString
import org.apache.qpid.proton.amqp.* import org.apache.qpid.proton.amqp.*
import org.apache.qpid.proton.codec.DecoderImpl import org.apache.qpid.proton.codec.DecoderImpl
@ -35,22 +32,23 @@ import org.junit.Assert.*
import org.junit.Ignore import org.junit.Ignore
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.junit.runners.Parameterized.Parameters
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.IOException import java.io.IOException
import java.io.NotSerializableException import java.io.NotSerializableException
import java.lang.reflect.Type
import java.math.BigDecimal import java.math.BigDecimal
import java.nio.ByteBuffer
import java.time.* import java.time.*
import java.time.temporal.ChronoUnit import java.time.temporal.ChronoUnit
import java.util.* import java.util.*
import java.util.concurrent.ConcurrentHashMap
import kotlin.reflect.full.superclasses import kotlin.reflect.full.superclasses
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertNotNull import kotlin.test.assertNotNull
import kotlin.test.assertTrue import kotlin.test.assertTrue
class SerializationOutputTests { @RunWith(Parameterized::class)
class SerializationOutputTests(private val compression: CordaSerializationEncoding?) {
private companion object { private companion object {
val BOB_IDENTITY = TestIdentity(BOB_NAME, 80).identity val BOB_IDENTITY = TestIdentity(BOB_NAME, 80).identity
val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB")) val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB"))
@ -59,6 +57,9 @@ class SerializationOutputTests {
val MEGA_CORP_PUBKEY get() = megaCorp.publicKey val MEGA_CORP_PUBKEY get() = megaCorp.publicKey
val MINI_CORP get() = miniCorp.party val MINI_CORP get() = miniCorp.party
val MINI_CORP_PUBKEY get() = miniCorp.publicKey val MINI_CORP_PUBKEY get() = miniCorp.publicKey
@Parameters(name = "{0}")
@JvmStatic
fun compression() = arrayOf<CordaSerializationEncoding?>(null) + CordaSerializationEncoding.values()
} }
@Rule @Rule
@ -173,16 +174,20 @@ class SerializationOutputTests {
} }
} }
private val encodingWhitelist = rigorousMock<EncodingWhitelist>().also {
if (compression != null) doReturn(true).whenever(it).acceptEncoding(compression)
}
private fun defaultFactory() = SerializerFactory(
AllWhitelist, ClassLoader.getSystemClassLoader(),
EvolutionSerializerGetterTesting())
private inline fun <reified T : Any> serdes(obj: T, private inline fun <reified T : Any> serdes(obj: T,
factory: SerializerFactory = SerializerFactory( factory: SerializerFactory = defaultFactory(),
AllWhitelist, ClassLoader.getSystemClassLoader(), freshDeserializationFactory: SerializerFactory = defaultFactory(),
EvolutionSerializerGetterTesting()),
freshDeserializationFactory: SerializerFactory = SerializerFactory(
AllWhitelist, ClassLoader.getSystemClassLoader(),
EvolutionSerializerGetterTesting()),
expectedEqual: Boolean = true, expectedEqual: Boolean = true,
expectDeserializedEqual: Boolean = true): T { expectDeserializedEqual: Boolean = true): T {
val ser = SerializationOutput(factory) val ser = SerializationOutput(factory, compression)
val bytes = ser.serialize(obj) val bytes = ser.serialize(obj)
val decoder = DecoderImpl().apply { val decoder = DecoderImpl().apply {
@ -198,18 +203,19 @@ class SerializationOutputTests {
this.register(TransformTypes.DESCRIPTOR, TransformTypes.Companion) this.register(TransformTypes.DESCRIPTOR, TransformTypes.Companion)
} }
EncoderImpl(decoder) EncoderImpl(decoder)
decoder.setByteBuffer(ByteBuffer.wrap(bytes.bytes, 8, bytes.size - 8)) DeserializationInput.withDataBytes(bytes, encodingWhitelist) {
// Check that a vanilla AMQP decoder can deserialize without schema. decoder.setByteBuffer(it)
val result = decoder.readObject() as Envelope // Check that a vanilla AMQP decoder can deserialize without schema.
assertNotNull(result) val result = decoder.readObject() as Envelope
assertNotNull(result)
val des = DeserializationInput(freshDeserializationFactory) }
val des = DeserializationInput(freshDeserializationFactory, encodingWhitelist)
val desObj = des.deserialize(bytes) val desObj = des.deserialize(bytes)
assertTrue(Objects.deepEquals(obj, desObj) == expectedEqual) assertTrue(Objects.deepEquals(obj, desObj) == expectedEqual)
// Now repeat with a re-used factory // Now repeat with a re-used factory
val ser2 = SerializationOutput(factory) val ser2 = SerializationOutput(factory, compression)
val des2 = DeserializationInput(factory) val des2 = DeserializationInput(factory, encodingWhitelist)
val desObj2 = des2.deserialize(ser2.serialize(obj)) val desObj2 = des2.deserialize(ser2.serialize(obj))
assertTrue(Objects.deepEquals(obj, desObj2) == expectedEqual) assertTrue(Objects.deepEquals(obj, desObj2) == expectedEqual)
assertTrue(Objects.deepEquals(desObj, desObj2) == expectDeserializedEqual) assertTrue(Objects.deepEquals(desObj, desObj2) == expectDeserializedEqual)
@ -432,9 +438,9 @@ class SerializationOutputTests {
@Test @Test
fun `class constructor is invoked on deserialisation`() { fun `class constructor is invoked on deserialisation`() {
val ser = SerializationOutput(SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())) compression == null || return // Manipulation of serialized bytes is invalid if they're compressed.
val des = DeserializationInput(ser.serializerFactory) val ser = SerializationOutput(SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()), compression)
val des = DeserializationInput(ser.serializerFactory, encodingWhitelist)
val serialisedOne = ser.serialize(NonZeroByte(1)).bytes val serialisedOne = ser.serialize(NonZeroByte(1)).bytes
val serialisedTwo = ser.serialize(NonZeroByte(2)).bytes val serialisedTwo = ser.serialize(NonZeroByte(2)).bytes
@ -1065,6 +1071,7 @@ class SerializationOutputTests {
val obj2 = serdes(obj, factory, factory2, expectedEqual = false, expectDeserializedEqual = false) val obj2 = serdes(obj, factory, factory2, expectedEqual = false, expectDeserializedEqual = false)
assertEquals(obj.id, obj2.attachment.id) assertEquals(obj.id, obj2.attachment.id)
assertEquals(obj.contract, obj2.contract) assertEquals(obj.contract, obj2.contract)
assertEquals(obj.additionalContracts, obj2.additionalContracts)
assertArrayEquals(obj.open().readBytes(), obj2.open().readBytes()) assertArrayEquals(obj.open().readBytes(), obj2.open().readBytes())
} }
@ -1116,6 +1123,29 @@ class SerializationOutputTests {
val c = C(Amount<Currency>(100, BigDecimal("1.5"), Currency.getInstance("USD"))) val c = C(Amount<Currency>(100, BigDecimal("1.5"), Currency.getInstance("USD")))
// were the issue not fixed we'd blow up here // were the issue not fixed we'd blow up here
SerializationOutput(factory).serialize(c) SerializationOutput(factory, compression).serialize(c)
}
@Test
fun `compression has the desired effect`() {
compression ?: return
val factory = defaultFactory()
val data = ByteArray(12345).also { Random(0).nextBytes(it) }.let { it + it }
val compressed = SerializationOutput(factory, compression).serialize(data)
assertEquals(.5, compressed.size.toDouble() / data.size, .03)
assertArrayEquals(data, DeserializationInput(factory, encodingWhitelist).deserialize(compressed))
}
@Test
fun `a particular encoding can be banned for deserialization`() {
compression ?: return
val factory = defaultFactory()
doReturn(false).whenever(encodingWhitelist).acceptEncoding(compression)
val compressed = SerializationOutput(factory, compression).serialize("whatever")
val input = DeserializationInput(factory, encodingWhitelist)
catchThrowable { input.deserialize(compressed) }.run {
assertSame(NotSerializableException::class.java, javaClass)
assertEquals(encodingNotPermittedFormat.format(compression), message)
}
} }
} }

View File

@ -1,12 +1,16 @@
package net.corda.nodeapi.internal.serialization.kryo package net.corda.nodeapi.internal.serialization.kryo
import net.corda.core.internal.declaredField
import org.assertj.core.api.Assertions.catchThrowable
import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertArrayEquals
import org.junit.Test import org.junit.Test
import java.io.* import java.io.*
import java.nio.BufferOverflowException
import java.util.* import java.util.*
import java.util.zip.DeflaterOutputStream import java.util.zip.DeflaterOutputStream
import java.util.zip.InflaterInputStream import java.util.zip.InflaterInputStream
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertSame
class KryoStreamsTest { class KryoStreamsTest {
class NegOutputStream(private val stream: OutputStream) : OutputStream() { class NegOutputStream(private val stream: OutputStream) : OutputStream() {
@ -57,4 +61,37 @@ class KryoStreamsTest {
assertEquals(-1, read()) assertEquals(-1, read())
} }
} }
@Test
fun `ByteBufferOutputStream works`() {
val stream = ByteBufferOutputStream(3)
stream.write("abc".toByteArray())
val getBuf = stream.declaredField<ByteArray>(ByteArrayOutputStream::class, "buf")::value
assertEquals(3, getBuf().size)
repeat(2) {
assertSame<Any>(BufferOverflowException::class.java, catchThrowable {
stream.alsoAsByteBuffer(9) {
it.put("0123456789".toByteArray())
}
}.javaClass)
assertEquals(3 + 9, getBuf().size)
}
// This time make too much space:
stream.alsoAsByteBuffer(11) {
it.put("0123456789".toByteArray())
}
stream.write("def".toByteArray())
assertArrayEquals("abc0123456789def".toByteArray(), stream.toByteArray())
}
@Test
fun `ByteBufferOutputStream discards data after final position`() {
val stream = ByteBufferOutputStream(0)
stream.alsoAsByteBuffer(10) {
it.put("0123456789".toByteArray())
it.position(5)
}
stream.write("def".toByteArray())
assertArrayEquals("01234def".toByteArray(), stream.toByteArray())
}
} }

View File

@ -170,9 +170,6 @@ dependencies {
compile 'commons-codec:commons-codec:1.10' compile 'commons-codec:commons-codec:1.10'
compile 'com.github.bft-smart:library:master-v1.1-beta-g6215ec8-87' 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. // Apache Shiro: authentication, authorization and session management.
compile "org.apache.shiro:shiro-core:${shiro_version}" compile "org.apache.shiro:shiro-core:${shiro_version}"

View File

@ -45,7 +45,7 @@ class AttachmentLoadingTests : IntegrationTest() {
@JvmField @JvmField
val testSerialization = SerializationEnvironmentRule() val testSerialization = SerializationEnvironmentRule()
private val attachments = MockAttachmentStorage() 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 cordapp get() = provider.cordapps.first()
private val attachmentId get() = provider.getCordappAttachmentId(cordapp)!! private val attachmentId get() = provider.getCordappAttachmentId(cordapp)!!
private val appContext get() = provider.getAppContext(cordapp) private val appContext get() = provider.getAppContext(cordapp)

View File

@ -57,7 +57,7 @@ class BFTNotaryServiceTests : IntegrationTest() {
@Before @Before
fun before() { fun before() {
mockNet = InternalMockNetwork(emptyList()) mockNet = InternalMockNetwork(listOf("net.corda.testing.contracts"))
} }
@After @After

View File

@ -2,15 +2,17 @@ package net.corda.node.services.network
import net.corda.cordform.CordformNode import net.corda.cordform.CordformNode
import net.corda.core.crypto.random63BitValue import net.corda.core.crypto.random63BitValue
import net.corda.core.internal.*
import net.corda.core.internal.concurrent.transpose import net.corda.core.internal.concurrent.transpose
import net.corda.core.node.NetworkParameters import net.corda.core.internal.div
import net.corda.core.internal.exists
import net.corda.core.internal.list
import net.corda.core.internal.readObject
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.serialization.deserialize
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.seconds import net.corda.core.utilities.seconds
import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME
import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.common.internal.testNetworkParameters
import net.corda.nodeapi.internal.network.SignedNetworkParameters
import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.DUMMY_NOTARY_NAME import net.corda.testing.core.DUMMY_NOTARY_NAME
@ -70,8 +72,7 @@ class NetworkMapTest : IntegrationTest() {
) { ) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow() val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val networkParameters = (alice.baseDirectory / NETWORK_PARAMS_FILE_NAME) val networkParameters = (alice.baseDirectory / NETWORK_PARAMS_FILE_NAME)
.readAll() .readObject<SignedNetworkParameters>()
.deserialize<SignedDataWithCert<NetworkParameters>>()
.verified() .verified()
// We use a random modified time above to make the network parameters unqiue so that we're sure they came // We use a random modified time above to make the network parameters unqiue so that we're sure they came
// from the server // from the server

View File

@ -553,7 +553,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
checkpointStorage = DBCheckpointStorage() checkpointStorage = DBCheckpointStorage()
val metrics = MetricRegistry() val metrics = MetricRegistry()
attachments = NodeAttachmentService(metrics, configuration.attachmentContentCacheSizeBytes, configuration.attachmentCacheBound) 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) val keyManagementService = makeKeyManagementService(identityService, keyPairs)
_services = ServiceHubInternalImpl( _services = ServiceHubInternalImpl(
identityService, identityService,

View File

@ -14,6 +14,7 @@ import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.FlowStateMachine import net.corda.core.internal.FlowStateMachine
import net.corda.core.internal.RPC_UPLOADER
import net.corda.core.internal.sign import net.corda.core.internal.sign
import net.corda.core.messaging.* import net.corda.core.messaging.*
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
@ -53,7 +54,7 @@ internal class CordaRPCOpsImpl(
} }
override fun networkParametersFeed(): DataFeed<ParametersUpdateInfo?, ParametersUpdateInfo> { override fun networkParametersFeed(): DataFeed<ParametersUpdateInfo?, ParametersUpdateInfo> {
return services.networkMapUpdater.track() return services.networkMapUpdater.trackParametersUpdate()
} }
override fun acceptNewNetworkParameters(parametersHash: SecureHash) { override fun acceptNewNetworkParameters(parametersHash: SecureHash) {
@ -192,7 +193,7 @@ internal class CordaRPCOpsImpl(
override fun uploadAttachment(jar: InputStream): SecureHash { override fun uploadAttachment(jar: InputStream): SecureHash {
// TODO: this operation should not require an explicit transaction // TODO: this operation should not require an explicit transaction
return database.transaction { return database.transaction {
services.attachments.importAttachment(jar) services.attachments.importAttachment(jar, RPC_UPLOADER, null)
} }
} }

View File

@ -3,12 +3,12 @@ package net.corda.node.internal
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.internal.* import net.corda.core.internal.*
import net.corda.core.node.NetworkParameters import net.corda.core.node.NetworkParameters
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.node.services.network.NetworkMapClient import net.corda.node.services.network.NetworkMapClient
import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME
import net.corda.nodeapi.internal.network.NETWORK_PARAMS_UPDATE_FILE_NAME import net.corda.nodeapi.internal.network.NETWORK_PARAMS_UPDATE_FILE_NAME
import net.corda.nodeapi.internal.network.SignedNetworkParameters
import net.corda.nodeapi.internal.network.verifiedNetworkMapCert import net.corda.nodeapi.internal.network.verifiedNetworkMapCert
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.StandardCopyOption import java.nio.file.StandardCopyOption
@ -26,9 +26,9 @@ class NetworkParametersReader(private val trustRoot: X509Certificate,
val networkParameters by lazy { retrieveNetworkParameters() } val networkParameters by lazy { retrieveNetworkParameters() }
private fun retrieveNetworkParameters(): NetworkParameters { private fun retrieveNetworkParameters(): NetworkParameters {
val advertisedParametersHash = networkMapClient?.getNetworkMap()?.networkMap?.networkParameterHash val advertisedParametersHash = networkMapClient?.getNetworkMap()?.payload?.networkParameterHash
val signedParametersFromFile = if (networkParamsFile.exists()) { val signedParametersFromFile = if (networkParamsFile.exists()) {
networkParamsFile.readAll().deserialize<SignedDataWithCert<NetworkParameters>>() networkParamsFile.readObject<SignedNetworkParameters>()
} else { } else {
null null
} }
@ -51,13 +51,13 @@ class NetworkParametersReader(private val trustRoot: X509Certificate,
return parameters return parameters
} }
private fun readParametersUpdate(advertisedParametersHash: SecureHash, previousParametersHash: SecureHash): SignedDataWithCert<NetworkParameters> { private fun readParametersUpdate(advertisedParametersHash: SecureHash, previousParametersHash: SecureHash): SignedNetworkParameters {
if (!parametersUpdateFile.exists()) { if (!parametersUpdateFile.exists()) {
throw IllegalArgumentException("Node uses parameters with hash: $previousParametersHash " + throw IllegalArgumentException("Node uses parameters with hash: $previousParametersHash " +
"but network map is advertising: ${advertisedParametersHash}.\n" + "but network map is advertising: $advertisedParametersHash.\n" +
"Please update node to use correct network parameters file.") "Please update node to use correct network parameters file.")
} }
val signedUpdatedParameters = parametersUpdateFile.readAll().deserialize<SignedDataWithCert<NetworkParameters>>() val signedUpdatedParameters = parametersUpdateFile.readObject<SignedNetworkParameters>()
if (signedUpdatedParameters.raw.hash != advertisedParametersHash) { if (signedUpdatedParameters.raw.hash != advertisedParametersHash) {
throw IllegalArgumentException("Both network parameters and network parameters update files don't match" + throw IllegalArgumentException("Both network parameters and network parameters update files don't match" +
"parameters advertised by network map.\n" + "parameters advertised by network map.\n" +

View File

@ -1,10 +1,12 @@
package net.corda.node.internal.cordapp package net.corda.node.internal.cordapp
import com.google.common.collect.HashBiMap import com.google.common.collect.HashBiMap
import net.corda.core.contracts.ContractAttachment
import net.corda.core.contracts.ContractClassName import net.corda.core.contracts.ContractClassName
import net.corda.core.cordapp.Cordapp import net.corda.core.cordapp.Cordapp
import net.corda.core.cordapp.CordappContext import net.corda.core.cordapp.CordappContext
import net.corda.core.crypto.SecureHash 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.cordapp.CordappConfigProvider
import net.corda.core.internal.createCordappContext import net.corda.core.internal.createCordappContext
import net.corda.core.node.services.AttachmentId 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. * 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<String, List<AttachmentId>>) : SingletonSerializeAsToken(), CordappProviderInternal {
companion object { companion object {
private val log = loggerFor<CordappProviderImpl>() private val log = loggerFor<CordappProviderImpl>()
@ -25,6 +30,34 @@ open class CordappProviderImpl(private val cordappLoader: CordappLoader, private
private val contextCache = ConcurrentHashMap<Cordapp, CordappContext>() private val contextCache = ConcurrentHashMap<Cordapp, CordappContext>()
/**
* 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 { override fun getAppContext(): CordappContext {
// TODO: Use better supported APIs in Java 9 // 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) 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 * 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) fun getCordappAttachmentId(cordapp: Cordapp): SecureHash? = cordappAttachments.inverse().get(cordapp.jarPath)
private fun loadContractsIntoAttachmentStore(attachmentStorage: AttachmentStorage): Map<SecureHash, URL> { private fun loadContractsIntoAttachmentStore(attachmentStorage: AttachmentStorage): Map<SecureHash, URL> =
val cordappsWithAttachments = cordapps.filter { !it.contractClassNames.isEmpty() }.map { it.jarPath } cordapps.filter { !it.contractClassNames.isEmpty() }.map {
val attachmentIds = cordappsWithAttachments.map { it.openStream().use { attachmentStorage.importOrGetAttachment(it) } } it.jarPath.openStream().use { stream ->
return attachmentIds.zip(cordappsWithAttachments).toMap() 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 * Get the current cordapp context for the given CorDapp

View File

@ -196,15 +196,15 @@ data class NodeConfigurationImpl(
override fun validate(): List<String> { override fun validate(): List<String> {
val errors = mutableListOf<String>() val errors = mutableListOf<String>()
errors + validateRpcOptions(rpcOptions) errors += validateRpcOptions(rpcOptions)
return errors return errors
} }
private fun validateRpcOptions(options: NodeRpcOptions): List<String> { private fun validateRpcOptions(options: NodeRpcOptions): List<String> {
val errors = mutableListOf<String>() val errors = mutableListOf<String>()
if (!options.useSsl) { if (options.address != null) {
if (options.adminAddress == null) { if (!options.useSsl && options.adminAddress == null) {
errors + "'rpcSettings.adminAddress': missing. Property is mandatory when 'rpcSettings.useSsl' is false (default)." errors += "'rpcSettings.adminAddress': missing. Property is mandatory when 'rpcSettings.useSsl' is false (default)."
} }
} }
return errors return errors

View File

@ -1,43 +1,27 @@
package net.corda.node.services.network package net.corda.node.services.network
import com.google.common.util.concurrent.MoreExecutors
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.SignedData import net.corda.core.crypto.SignedData
import net.corda.core.internal.* import net.corda.core.internal.*
import net.corda.core.messaging.DataFeed
import net.corda.core.messaging.ParametersUpdateInfo
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.minutes
import net.corda.core.utilities.seconds import net.corda.core.utilities.seconds
import net.corda.core.utilities.trace import net.corda.core.utilities.trace
import net.corda.node.services.api.NetworkMapCacheInternal
import net.corda.node.utilities.NamedThreadFactory
import net.corda.node.utilities.registration.cacheControl import net.corda.node.utilities.registration.cacheControl
import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.network.NETWORK_PARAMS_UPDATE_FILE_NAME import net.corda.nodeapi.internal.network.*
import net.corda.nodeapi.internal.network.NetworkMap
import net.corda.nodeapi.internal.network.ParametersUpdate
import net.corda.nodeapi.internal.network.verifiedNetworkMapCert
import rx.Subscription
import rx.subjects.PublishSubject
import java.io.BufferedReader import java.io.BufferedReader
import java.io.Closeable
import java.net.URL import java.net.URL
import java.nio.file.Path
import java.nio.file.StandardCopyOption
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
import java.time.Duration import java.time.Duration
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
class NetworkMapClient(compatibilityZoneURL: URL, val trustedRoot: X509Certificate) { class NetworkMapClient(compatibilityZoneURL: URL, val trustedRoot: X509Certificate) {
companion object { companion object {
private val logger = contextLogger() private val logger = contextLogger()
} }
private val networkMapUrl = URL("$compatibilityZoneURL/network-map") private val networkMapUrl = URL("$compatibilityZoneURL/network-map")
fun publish(signedNodeInfo: SignedNodeInfo) { fun publish(signedNodeInfo: SignedNodeInfo) {
@ -57,10 +41,13 @@ class NetworkMapClient(compatibilityZoneURL: URL, val trustedRoot: X509Certifica
fun getNetworkMap(): NetworkMapResponse { fun getNetworkMap(): NetworkMapResponse {
logger.trace { "Fetching network map update from $networkMapUrl." } logger.trace { "Fetching network map update from $networkMapUrl." }
val connection = networkMapUrl.openHttpConnection() val connection = networkMapUrl.openHttpConnection()
val signedNetworkMap = connection.responseAs<SignedDataWithCert<NetworkMap>>() val signedNetworkMap = connection.responseAs<SignedNetworkMap>()
val networkMap = signedNetworkMap.verifiedNetworkMapCert(trustedRoot) val networkMap = signedNetworkMap.verifiedNetworkMapCert(trustedRoot)
val timeout = connection.cacheControl().maxAgeSeconds().seconds val timeout = connection.cacheControl().maxAgeSeconds().seconds
logger.trace { "Fetched network map update from $networkMapUrl successfully, retrieved ${networkMap.nodeInfoHashes.size} node info hashes. Node Info hashes: ${networkMap.nodeInfoHashes.joinToString("\n")}" } logger.trace {
"Fetched network map update from $networkMapUrl successfully, retrieved ${networkMap.nodeInfoHashes.size} " +
"node info hashes. Node Info hashes:\n${networkMap.nodeInfoHashes.joinToString("\n")}"
}
return NetworkMapResponse(networkMap, timeout) return NetworkMapResponse(networkMap, timeout)
} }
@ -72,10 +59,10 @@ class NetworkMapClient(compatibilityZoneURL: URL, val trustedRoot: X509Certifica
return verifiedNodeInfo return verifiedNodeInfo
} }
fun getNetworkParameters(networkParameterHash: SecureHash): SignedDataWithCert<NetworkParameters> { fun getNetworkParameters(networkParameterHash: SecureHash): SignedNetworkParameters {
val url = URL("$networkMapUrl/network-parameters/$networkParameterHash") val url = URL("$networkMapUrl/network-parameters/$networkParameterHash")
logger.trace { "Fetching network parameters: '$networkParameterHash' from $url." } logger.trace { "Fetching network parameters: '$networkParameterHash' from $url." }
val networkParameter = url.openHttpConnection().responseAs<SignedDataWithCert<NetworkParameters>>() val networkParameter = url.openHttpConnection().responseAs<SignedNetworkParameters>()
logger.trace { "Fetched network parameters: '$networkParameterHash' successfully. Network Parameters: $networkParameter" } logger.trace { "Fetched network parameters: '$networkParameterHash' successfully. Network Parameters: $networkParameter" }
return networkParameter return networkParameter
} }
@ -89,143 +76,4 @@ class NetworkMapClient(compatibilityZoneURL: URL, val trustedRoot: X509Certifica
} }
} }
data class NetworkMapResponse(val networkMap: NetworkMap, val cacheMaxAge: Duration) data class NetworkMapResponse(val payload: NetworkMap, val cacheMaxAge: Duration)
class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
private val fileWatcher: NodeInfoWatcher,
private val networkMapClient: NetworkMapClient?,
private val currentParametersHash: SecureHash,
private val baseDirectory: Path) : Closeable {
companion object {
private val logger = contextLogger()
private val retryInterval = 1.minutes
}
private var newNetworkParameters: Pair<ParametersUpdate, SignedDataWithCert<NetworkParameters>>? = null
fun track(): DataFeed<ParametersUpdateInfo?, ParametersUpdateInfo> {
val currentUpdateInfo = newNetworkParameters?.let {
ParametersUpdateInfo(it.first.newParametersHash, it.second.verified(), it.first.description, it.first.updateDeadline)
}
return DataFeed(
currentUpdateInfo,
parametersUpdatesTrack
)
}
private val parametersUpdatesTrack: PublishSubject<ParametersUpdateInfo> = PublishSubject.create<ParametersUpdateInfo>()
private val executor = Executors.newSingleThreadScheduledExecutor(NamedThreadFactory("Network Map Updater Thread", Executors.defaultThreadFactory()))
private var fileWatcherSubscription: Subscription? = null
override fun close() {
fileWatcherSubscription?.unsubscribe()
MoreExecutors.shutdownAndAwaitTermination(executor, 50, TimeUnit.SECONDS)
}
fun updateNodeInfo(newInfo: NodeInfo, signNodeInfo: (NodeInfo) -> SignedNodeInfo) {
val oldInfo = networkMapCache.getNodeByLegalIdentity(newInfo.legalIdentities.first())
// Compare node info without timestamp.
if (newInfo.copy(serial = 0L) == oldInfo?.copy(serial = 0L)) return
// Only publish and write to disk if there are changes to the node info.
val signedNodeInfo = signNodeInfo(newInfo)
networkMapCache.addNode(newInfo)
fileWatcher.saveToFile(signedNodeInfo)
if (networkMapClient != null) {
tryPublishNodeInfoAsync(signedNodeInfo, networkMapClient)
}
}
fun subscribeToNetworkMap() {
require(fileWatcherSubscription == null) { "Should not call this method twice." }
// Subscribe to file based networkMap
fileWatcherSubscription = fileWatcher.nodeInfoUpdates().subscribe(networkMapCache::addNode)
if (networkMapClient == null) return
// Subscribe to remote network map if configured.
val task = object : Runnable {
override fun run() {
val nextScheduleDelay = try {
val (networkMap, cacheTimeout) = networkMapClient.getNetworkMap()
networkMap.parametersUpdate?.let { handleUpdateNetworkParameters(it) }
if (currentParametersHash != networkMap.networkParameterHash) {
// TODO This needs special handling (node omitted update process/didn't accept new parameters or didn't restart on updateDeadline)
logger.error("Node is using parameters with hash: $currentParametersHash but network map is advertising: ${networkMap.networkParameterHash}.\n" +
"Please update node to use correct network parameters file.\"")
System.exit(1)
}
val currentNodeHashes = networkMapCache.allNodeHashes
val hashesFromNetworkMap = networkMap.nodeInfoHashes
(hashesFromNetworkMap - currentNodeHashes).mapNotNull {
// Download new node info from network map
try {
networkMapClient.getNodeInfo(it)
} catch (e: Exception) {
// Failure to retrieve one node info shouldn't stop the whole update, log and return null instead.
logger.warn("Error encountered when downloading node info '$it', skipping...", e)
null
}
}.forEach {
// Add new node info to the network map cache, these could be new node info or modification of node info for existing nodes.
networkMapCache.addNode(it)
}
// Remove node info from network map.
(currentNodeHashes - hashesFromNetworkMap - fileWatcher.processedNodeInfoHashes)
.mapNotNull(networkMapCache::getNodeByHash)
.forEach(networkMapCache::removeNode)
cacheTimeout
} catch (t: Throwable) {
logger.warn("Error encountered while updating network map, will retry in ${retryInterval.seconds} seconds", t)
retryInterval
}
// Schedule the next update.
executor.schedule(this, nextScheduleDelay.toMillis(), TimeUnit.MILLISECONDS)
}
}
executor.submit(task) // The check may be expensive, so always run it in the background even the first time.
}
private fun tryPublishNodeInfoAsync(signedNodeInfo: SignedNodeInfo, networkMapClient: NetworkMapClient) {
val task = object : Runnable {
override fun run() {
try {
networkMapClient.publish(signedNodeInfo)
} catch (t: Throwable) {
logger.warn("Error encountered while publishing node info, will retry in ${retryInterval.seconds} seconds.", t)
// TODO: Exponential backoff?
executor.schedule(this, retryInterval.toMillis(), TimeUnit.MILLISECONDS)
}
}
}
executor.submit(task)
}
private fun handleUpdateNetworkParameters(update: ParametersUpdate) {
if (update.newParametersHash == newNetworkParameters?.first?.newParametersHash) { // This update was handled already.
return
}
val newParameters = networkMapClient?.getNetworkParameters(update.newParametersHash)
if (newParameters != null) {
logger.info("Downloaded new network parameters: $newParameters from the update: $update")
newNetworkParameters = Pair(update, newParameters)
parametersUpdatesTrack.onNext(ParametersUpdateInfo(update.newParametersHash, newParameters.verifiedNetworkMapCert(networkMapClient!!.trustedRoot), update.description, update.updateDeadline))
}
}
fun acceptNewNetworkParameters(parametersHash: SecureHash, sign: (SecureHash) -> SignedData<SecureHash>) {
networkMapClient ?: throw IllegalStateException("Network parameters updates are not support without compatibility zone configured")
// TODO This scenario will happen if node was restarted and didn't download parameters yet, but we accepted them. Add persisting of newest parameters from update.
val (_, newParams) = newNetworkParameters ?: throw IllegalArgumentException("Couldn't find parameters update for the hash: $parametersHash")
val newParametersHash = newParams.verifiedNetworkMapCert(networkMapClient.trustedRoot).serialize().hash // We should check that we sign the right data structure hash.
if (parametersHash == newParametersHash) {
// The latest parameters have priority.
newParams.serialize()
.open()
.copyTo(baseDirectory / NETWORK_PARAMS_UPDATE_FILE_NAME, StandardCopyOption.REPLACE_EXISTING)
networkMapClient.ackNetworkParametersUpdate(sign(parametersHash))
} else {
throw IllegalArgumentException("Refused to accept parameters with hash $parametersHash because network map advertises update with hash $newParametersHash. Please check newest version")
}
}
}

View File

@ -0,0 +1,178 @@
package net.corda.node.services.network
import com.google.common.util.concurrent.MoreExecutors
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.SignedData
import net.corda.core.internal.copyTo
import net.corda.core.internal.div
import net.corda.core.messaging.DataFeed
import net.corda.core.messaging.ParametersUpdateInfo
import net.corda.core.node.NodeInfo
import net.corda.core.serialization.serialize
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.minutes
import net.corda.node.services.api.NetworkMapCacheInternal
import net.corda.node.utilities.NamedThreadFactory
import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.network.NETWORK_PARAMS_UPDATE_FILE_NAME
import net.corda.nodeapi.internal.network.ParametersUpdate
import net.corda.nodeapi.internal.network.SignedNetworkParameters
import net.corda.nodeapi.internal.network.verifiedNetworkMapCert
import rx.Subscription
import rx.subjects.PublishSubject
import java.nio.file.Path
import java.nio.file.StandardCopyOption
import java.time.Duration
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
private val fileWatcher: NodeInfoWatcher,
private val networkMapClient: NetworkMapClient?,
private val currentParametersHash: SecureHash,
private val baseDirectory: Path
) : AutoCloseable {
companion object {
private val logger = contextLogger()
private val defaultRetryInterval = 1.minutes
}
private val parametersUpdatesTrack: PublishSubject<ParametersUpdateInfo> = PublishSubject.create<ParametersUpdateInfo>()
private val executor = Executors.newSingleThreadScheduledExecutor(NamedThreadFactory("Network Map Updater Thread", Executors.defaultThreadFactory()))
private var newNetworkParameters: Pair<ParametersUpdate, SignedNetworkParameters>? = null
private var fileWatcherSubscription: Subscription? = null
override fun close() {
fileWatcherSubscription?.unsubscribe()
MoreExecutors.shutdownAndAwaitTermination(executor, 50, TimeUnit.SECONDS)
}
fun trackParametersUpdate(): DataFeed<ParametersUpdateInfo?, ParametersUpdateInfo> {
val currentUpdateInfo = newNetworkParameters?.let {
ParametersUpdateInfo(it.first.newParametersHash, it.second.verified(), it.first.description, it.first.updateDeadline)
}
return DataFeed(currentUpdateInfo, parametersUpdatesTrack)
}
fun updateNodeInfo(newInfo: NodeInfo, signer: (NodeInfo) -> SignedNodeInfo) {
val oldInfo = networkMapCache.getNodeByLegalIdentity(newInfo.legalIdentities.first())
// Compare node info without timestamp.
if (newInfo.copy(serial = 0L) == oldInfo?.copy(serial = 0L)) return
// Only publish and write to disk if there are changes to the node info.
val signedNodeInfo = signer(newInfo)
networkMapCache.addNode(newInfo)
fileWatcher.saveToFile(signedNodeInfo)
if (networkMapClient != null) {
tryPublishNodeInfoAsync(signedNodeInfo, networkMapClient)
}
}
private fun tryPublishNodeInfoAsync(signedNodeInfo: SignedNodeInfo, networkMapClient: NetworkMapClient) {
executor.submit(object : Runnable {
override fun run() {
try {
networkMapClient.publish(signedNodeInfo)
} catch (t: Throwable) {
logger.warn("Error encountered while publishing node info, will retry in $defaultRetryInterval", t)
// TODO: Exponential backoff?
executor.schedule(this, defaultRetryInterval.toMillis(), TimeUnit.MILLISECONDS)
}
}
})
}
fun subscribeToNetworkMap() {
require(fileWatcherSubscription == null) { "Should not call this method twice." }
// Subscribe to file based networkMap
fileWatcherSubscription = fileWatcher.nodeInfoUpdates().subscribe(networkMapCache::addNode)
if (networkMapClient == null) return
// Subscribe to remote network map if configured.
executor.submit(object : Runnable {
override fun run() {
val nextScheduleDelay = try {
updateNetworkMapCache(networkMapClient)
} catch (t: Throwable) {
logger.warn("Error encountered while updating network map, will retry in $defaultRetryInterval", t)
defaultRetryInterval
}
// Schedule the next update.
executor.schedule(this, nextScheduleDelay.toMillis(), TimeUnit.MILLISECONDS)
}
}) // The check may be expensive, so always run it in the background even the first time.
}
private fun updateNetworkMapCache(networkMapClient: NetworkMapClient): Duration {
val (networkMap, cacheTimeout) = networkMapClient.getNetworkMap()
networkMap.parametersUpdate?.let { handleUpdateNetworkParameters(networkMapClient, it) }
if (currentParametersHash != networkMap.networkParameterHash) {
// TODO This needs special handling (node omitted update process/didn't accept new parameters or didn't restart on updateDeadline)
logger.error("Node is using parameters with hash: $currentParametersHash but network map is " +
"advertising: ${networkMap.networkParameterHash}.\n" +
"Please update node to use correct network parameters file.\"")
System.exit(1)
}
val currentNodeHashes = networkMapCache.allNodeHashes
val hashesFromNetworkMap = networkMap.nodeInfoHashes
(hashesFromNetworkMap - currentNodeHashes).mapNotNull {
// Download new node info from network map
try {
networkMapClient.getNodeInfo(it)
} catch (e: Exception) {
// Failure to retrieve one node info shouldn't stop the whole update, log and return null instead.
logger.warn("Error encountered when downloading node info '$it', skipping...", e)
null
}
}.forEach {
// Add new node info to the network map cache, these could be new node info or modification of node info for existing nodes.
networkMapCache.addNode(it)
}
// Remove node info from network map.
(currentNodeHashes - hashesFromNetworkMap - fileWatcher.processedNodeInfoHashes)
.mapNotNull(networkMapCache::getNodeByHash)
.forEach(networkMapCache::removeNode)
return cacheTimeout
}
private fun handleUpdateNetworkParameters(networkMapClient: NetworkMapClient, update: ParametersUpdate) {
if (update.newParametersHash == newNetworkParameters?.first?.newParametersHash) {
// This update was handled already.
return
}
val newParameters = networkMapClient.getNetworkParameters(update.newParametersHash)
logger.info("Downloaded new network parameters: $newParameters from the update: $update")
newNetworkParameters = Pair(update, newParameters)
val updateInfo = ParametersUpdateInfo(
update.newParametersHash,
newParameters.verifiedNetworkMapCert(networkMapClient.trustedRoot),
update.description,
update.updateDeadline)
parametersUpdatesTrack.onNext(updateInfo)
}
fun acceptNewNetworkParameters(parametersHash: SecureHash, sign: (SecureHash) -> SignedData<SecureHash>) {
networkMapClient ?: throw IllegalStateException("Network parameters updates are not support without compatibility zone configured")
// TODO This scenario will happen if node was restarted and didn't download parameters yet, but we accepted them.
// Add persisting of newest parameters from update.
val (_, newParams) = requireNotNull(newNetworkParameters) { "Couldn't find parameters update for the hash: $parametersHash" }
// We should check that we sign the right data structure hash.
val newParametersHash = newParams.verifiedNetworkMapCert(networkMapClient.trustedRoot).serialize().hash
if (parametersHash == newParametersHash) {
// The latest parameters have priority.
newParams.serialize()
.open()
.copyTo(baseDirectory / NETWORK_PARAMS_UPDATE_FILE_NAME, StandardCopyOption.REPLACE_EXISTING)
networkMapClient.ackNetworkParametersUpdate(sign(parametersHash))
} else {
throw IllegalArgumentException("Refused to accept parameters with hash $parametersHash because network map " +
"advertises update with hash $newParametersHash. Please check newest version")
}
}
}

View File

@ -4,12 +4,11 @@ import net.corda.cordform.CordformNode
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.internal.* import net.corda.core.internal.*
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.seconds import net.corda.core.utilities.seconds
import net.corda.nodeapi.internal.network.NodeInfoFilesCopier
import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.network.NodeInfoFilesCopier
import rx.Observable import rx.Observable
import rx.Scheduler import rx.Scheduler
import java.io.IOException import java.io.IOException
@ -118,7 +117,7 @@ class NodeInfoWatcher(private val nodePath: Path,
private fun processFile(file: Path): NodeInfo? { private fun processFile(file: Path): NodeInfo? {
return try { return try {
logger.info("Reading NodeInfo from file: $file") logger.info("Reading NodeInfo from file: $file")
val signedData = file.readAll().deserialize<SignedNodeInfo>() val signedData = file.readObject<SignedNodeInfo>()
signedData.verified() signedData.verified()
} catch (e: Exception) { } catch (e: Exception) {
logger.warn("Exception parsing NodeInfo from file. $file", e) logger.warn("Exception parsing NodeInfo from file. $file", e)

View File

@ -8,8 +8,11 @@ import com.google.common.hash.HashingInputStream
import com.google.common.io.CountingInputStream import com.google.common.io.CountingInputStream
import net.corda.core.CordaRuntimeException import net.corda.core.CordaRuntimeException
import net.corda.core.contracts.Attachment 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.SecureHash
import net.corda.core.internal.AbstractAttachment import net.corda.core.internal.AbstractAttachment
import net.corda.core.internal.UNKNOWN_UPLOADER
import net.corda.core.internal.VisibleForTesting import net.corda.core.internal.VisibleForTesting
import net.corda.core.node.services.AttachmentId import net.corda.core.node.services.AttachmentId
import net.corda.core.node.services.AttachmentStorage 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.node.utilities.defaultCordaCacheConcurrencyLevel
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
import net.corda.nodeapi.internal.persistence.currentDBSession import net.corda.nodeapi.internal.persistence.currentDBSession
import net.corda.nodeapi.internal.withContractsInJar
import java.io.* import java.io.*
import java.nio.file.Paths import java.nio.file.Paths
import java.time.Instant import java.time.Instant
@ -85,7 +89,14 @@ class NodeAttachmentService(
var uploader: String? = null, var uploader: String? = null,
@Column(name = "filename", updatable = false) @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<ContractClassName>? = null
) : Serializable ) : Serializable
@VisibleForTesting @VisibleForTesting
@ -196,23 +207,31 @@ class NodeAttachmentService(
// If repeatedly looking for non-existing attachments becomes a performance issue, this is either indicating a // 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. // a problem somewhere else or this needs to be revisited.
private val attachmentContentCache = NonInvalidatingWeightBasedCache<SecureHash, Optional<ByteArray>>( private val attachmentContentCache = NonInvalidatingWeightBasedCache<SecureHash, Optional<Pair<Attachment, ByteArray>>>(
maxWeight = attachmentContentCacheSize, maxWeight = attachmentContentCacheSize,
concurrencyLevel = defaultCordaCacheConcurrencyLevel, concurrencyLevel = defaultCordaCacheConcurrencyLevel,
weigher = object : Weigher<SecureHash, Optional<ByteArray>> { weigher = object : Weigher<SecureHash, Optional<Pair<Attachment, ByteArray>>> {
override fun weigh(key: SecureHash, value: Optional<ByteArray>): Int { override fun weigh(key: SecureHash, value: Optional<Pair<Attachment, ByteArray>>): Int {
return key.size + if (value.isPresent) value.get().size else 0 return key.size + if (value.isPresent) value.get().second.size else 0
} }
}, },
loadFunction = { Optional.ofNullable(loadAttachmentContent(it)) } loadFunction = { Optional.ofNullable(loadAttachmentContent(it)) }
) )
private fun loadAttachmentContent(id: SecureHash): ByteArray? { private fun loadAttachmentContent(id: SecureHash): Pair<Attachment, ByteArray>? {
val attachment = currentDBSession().get(NodeAttachmentService.DBAttachment::class.java, id.toString()) 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<SecureHash, Optional<Attachment>>( private val attachmentCache = NonInvalidatingCache<SecureHash, Optional<Attachment>>(
attachmentCacheBound, attachmentCacheBound,
defaultCordaCacheConcurrencyLevel, defaultCordaCacheConcurrencyLevel,
@ -222,16 +241,7 @@ class NodeAttachmentService(
private fun createAttachment(key: SecureHash): Attachment? { private fun createAttachment(key: SecureHash): Attachment? {
val content = attachmentContentCache.get(key) val content = attachmentContentCache.get(key)
if (content.isPresent) { if (content.isPresent) {
return AttachmentImpl( return content.get().first
key,
{
attachmentContentCache
.get(key)
.orElseThrow {
IllegalArgumentException("No attachement impl should have been created for non existent content")
}
},
checkAttachmentsOnLoad)
} }
// if no attachement has been found, we don't want to cache that - it might arrive later // if no attachement has been found, we don't want to cache that - it might arrive later
attachmentContentCache.invalidate(key) attachmentContentCache.invalidate(key)
@ -248,10 +258,10 @@ class NodeAttachmentService(
} }
override fun importAttachment(jar: InputStream): AttachmentId { 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) return import(jar, uploader, filename)
} }
@ -263,47 +273,39 @@ class NodeAttachmentService(
return Pair(id, bytes) return Pair(id, bytes)
} }
override fun hasAttachment(attachmentId: AttachmentId): Boolean { override fun hasAttachment(attachmentId: AttachmentId): Boolean =
val session = currentDBSession() currentDBSession().find(NodeAttachmentService.DBAttachment::class.java, attachmentId.toString()) != null
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<String>(DBAttachment::attId.name), attachmentId.toString()))
return (session.createQuery(criteriaQuery).singleResult > 0)
}
// TODO: PLT-147: The attachment should be randomised to prevent brute force guessing and thus privacy leaks. // 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 { 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. // 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. // 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 // 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 // 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. // set the hash field of the new attachment record.
val (id, bytes) = getAttachmentIdAndBytes(jar) val (id, bytes) = getAttachmentIdAndBytes(inputStream)
if (!hasAttachment(id)) { if (!hasAttachment(id)) {
checkIsAValidJAR(ByteArrayInputStream(bytes)) checkIsAValidJAR(ByteArrayInputStream(bytes))
val session = currentDBSession() val session = currentDBSession()
val attachment = NodeAttachmentService.DBAttachment(attId = id.toString(), content = bytes, uploader = uploader, filename = filename) val attachment = NodeAttachmentService.DBAttachment(attId = id.toString(), content = bytes, uploader = uploader, filename = filename, contractClassNames = contractClassNames)
session.save(attachment) session.save(attachment)
attachmentCount.inc() attachmentCount.inc()
log.info("Stored new attachment $id") log.info("Stored new attachment $id")
return id id
} else { } else {
throw java.nio.file.FileAlreadyExistsException(id.toString()) throw java.nio.file.FileAlreadyExistsException(id.toString())
}
} }
} }
override fun importOrGetAttachment(jar: InputStream): AttachmentId { override fun importOrGetAttachment(jar: InputStream): AttachmentId = try {
try { importAttachment(jar)
return importAttachment(jar) } catch (faee: java.nio.file.FileAlreadyExistsException) {
} AttachmentId.parse(faee.message!!)
catch (faee: java.nio.file.FileAlreadyExistsException) {
return AttachmentId.parse(faee.message!!)
}
} }
override fun queryAttachments(criteria: AttachmentQueryCriteria, sorting: AttachmentSort?): List<AttachmentId> { override fun queryAttachments(criteria: AttachmentQueryCriteria, sorting: AttachmentSort?): List<AttachmentId> {
@ -328,5 +330,4 @@ class NodeAttachmentService(
return results.map { AttachmentId.parse(it.attId) } return results.map { AttachmentId.parse(it.attId) }
} }
} }

View File

@ -1,6 +1,5 @@
myLegalName = "Vast Global MegaCorp, Ltd" myLegalName = "Vast Global MegaCorp, Ltd"
emailAddress = "admin@company.com" emailAddress = "admin@company.com"
exportJMXto = "http"
keyStorePassword = "cordacadevpass" keyStorePassword = "cordacadevpass"
trustStorePassword = "trustpass" trustStorePassword = "trustpass"
dataSourceProperties = { dataSourceProperties = {

View File

@ -2,14 +2,18 @@ package net.corda.node.internal.cordapp
import com.typesafe.config.Config import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import junit.framework.Assert.assertNull
import net.corda.core.internal.cordapp.CordappConfigProvider import net.corda.core.internal.cordapp.CordappConfigProvider
import net.corda.core.node.services.AttachmentStorage import net.corda.core.node.services.AttachmentStorage
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.internal.MockCordappConfigProvider import net.corda.testing.internal.MockCordappConfigProvider
import net.corda.testing.services.MockAttachmentStorage import net.corda.testing.services.MockAttachmentStorage
import org.assertj.core.api.Assertions.assertThat 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.Before
import org.junit.Test import org.junit.Test
import java.net.URL
class CordappProviderImplTests { class CordappProviderImplTests {
private companion object { private companion object {
@ -25,6 +29,7 @@ class CordappProviderImplTests {
} }
private lateinit var attachmentStore: AttachmentStorage private lateinit var attachmentStore: AttachmentStorage
private val whitelistedContractImplementations = testNetworkParameters().whitelistedContractImplementations
@Before @Before
fun setup() { fun setup() {
@ -33,44 +38,40 @@ class CordappProviderImplTests {
@Test @Test
fun `isolated jar is loaded into the attachment store`() { fun `isolated jar is loaded into the attachment store`() {
val loader = CordappLoader.createDevMode(listOf(isolatedJAR)) val provider = newCordappProvider(isolatedJAR)
val provider = CordappProviderImpl(loader, stubConfigProvider, attachmentStore)
val maybeAttachmentId = provider.getCordappAttachmentId(provider.cordapps.first()) val maybeAttachmentId = provider.getCordappAttachmentId(provider.cordapps.first())
Assert.assertNotNull(maybeAttachmentId) assertNotNull(maybeAttachmentId)
Assert.assertNotNull(attachmentStore.openAttachment(maybeAttachmentId!!)) assertNotNull(attachmentStore.openAttachment(maybeAttachmentId!!))
} }
@Test @Test
fun `empty jar is not loaded into the attachment store`() { fun `empty jar is not loaded into the attachment store`() {
val loader = CordappLoader.createDevMode(listOf(emptyJAR)) val provider = newCordappProvider(emptyJAR)
val provider = CordappProviderImpl(loader, stubConfigProvider, attachmentStore) assertNull(provider.getCordappAttachmentId(provider.cordapps.first()))
Assert.assertNull(provider.getCordappAttachmentId(provider.cordapps.first()))
} }
@Test @Test
fun `test that we find a cordapp class that is loaded into the store`() { fun `test that we find a cordapp class that is loaded into the store`() {
val loader = CordappLoader.createDevMode(listOf(isolatedJAR)) val provider = newCordappProvider(isolatedJAR)
val provider = CordappProviderImpl(loader, stubConfigProvider, attachmentStore)
val className = "net.corda.finance.contracts.isolated.AnotherDummyContract" val className = "net.corda.finance.contracts.isolated.AnotherDummyContract"
val expected = provider.cordapps.first() val expected = provider.cordapps.first()
val actual = provider.getCordappForClass(className) val actual = provider.getCordappForClass(className)
Assert.assertNotNull(actual) assertNotNull(actual)
Assert.assertEquals(expected, actual) assertEquals(expected, actual)
} }
@Test @Test
fun `test that we find an attachment for a cordapp contract class`() { fun `test that we find an attachment for a cordapp contrat class`() {
val loader = CordappLoader.createDevMode(listOf(isolatedJAR)) val provider = newCordappProvider(isolatedJAR)
val provider = CordappProviderImpl(loader, stubConfigProvider, attachmentStore)
val className = "net.corda.finance.contracts.isolated.AnotherDummyContract" val className = "net.corda.finance.contracts.isolated.AnotherDummyContract"
val expected = provider.getAppContext(provider.cordapps.first()).attachmentId val expected = provider.getAppContext(provider.cordapps.first()).attachmentId
val actual = provider.getContractAttachmentID(className) val actual = provider.getContractAttachmentID(className)
Assert.assertNotNull(actual) assertNotNull(actual)
Assert.assertEquals(actual!!, expected) assertEquals(actual!!, expected)
} }
@Test @Test
@ -78,10 +79,15 @@ class CordappProviderImplTests {
val configProvider = MockCordappConfigProvider() val configProvider = MockCordappConfigProvider()
configProvider.cordappConfigs.put(isolatedCordappName, validConfig) configProvider.cordappConfigs.put(isolatedCordappName, validConfig)
val loader = CordappLoader.createDevMode(listOf(isolatedJAR)) 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 val expected = provider.getAppContext(provider.cordapps.first()).config
assertThat(expected.getString("key")).isEqualTo("value") 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)
}
} }

View File

@ -57,7 +57,7 @@ class NetworkMapClientTest {
val nodeInfoHash = nodeInfo.serialize().sha256() val nodeInfoHash = nodeInfo.serialize().sha256()
assertThat(networkMapClient.getNetworkMap().networkMap.nodeInfoHashes).containsExactly(nodeInfoHash) assertThat(networkMapClient.getNetworkMap().payload.nodeInfoHashes).containsExactly(nodeInfoHash)
assertEquals(nodeInfo, networkMapClient.getNodeInfo(nodeInfoHash)) assertEquals(nodeInfo, networkMapClient.getNodeInfo(nodeInfoHash))
val (nodeInfo2, signedNodeInfo2) = createNodeInfoAndSigned(BOB_NAME) val (nodeInfo2, signedNodeInfo2) = createNodeInfoAndSigned(BOB_NAME)
@ -65,7 +65,7 @@ class NetworkMapClientTest {
networkMapClient.publish(signedNodeInfo2) networkMapClient.publish(signedNodeInfo2)
val nodeInfoHash2 = nodeInfo2.serialize().sha256() val nodeInfoHash2 = nodeInfo2.serialize().sha256()
assertThat(networkMapClient.getNetworkMap().networkMap.nodeInfoHashes).containsExactly(nodeInfoHash, nodeInfoHash2) assertThat(networkMapClient.getNetworkMap().payload.nodeInfoHashes).containsExactly(nodeInfoHash, nodeInfoHash2)
assertEquals(cacheTimeout, networkMapClient.getNetworkMap().cacheMaxAge) assertEquals(cacheTimeout, networkMapClient.getNetworkMap().cacheMaxAge)
assertEquals(nodeInfo2, networkMapClient.getNodeInfo(nodeInfoHash2)) assertEquals(nodeInfo2, networkMapClient.getNodeInfo(nodeInfoHash2))
} }

View File

@ -15,17 +15,13 @@ import net.corda.core.internal.*
import net.corda.core.messaging.ParametersUpdateInfo import net.corda.core.messaging.ParametersUpdateInfo
import net.corda.core.node.NetworkParameters import net.corda.core.node.NetworkParameters
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.core.utilities.millis import net.corda.core.utilities.millis
import net.corda.node.services.api.NetworkMapCacheInternal import net.corda.node.services.api.NetworkMapCacheInternal
import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.createDevNetworkMapCa import net.corda.nodeapi.internal.createDevNetworkMapCa
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
import net.corda.nodeapi.internal.network.NETWORK_PARAMS_UPDATE_FILE_NAME import net.corda.nodeapi.internal.network.*
import net.corda.nodeapi.internal.network.NetworkMap
import net.corda.nodeapi.internal.network.ParametersUpdate
import net.corda.nodeapi.internal.network.verifiedNetworkMapCert
import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.* import net.corda.testing.core.*
import net.corda.testing.internal.DEV_ROOT_CA import net.corda.testing.internal.DEV_ROOT_CA
@ -197,7 +193,7 @@ class NetworkMapUpdaterTest {
@Test @Test
fun `emit new parameters update info on parameters update from network map`() { fun `emit new parameters update info on parameters update from network map`() {
val paramsFeed = updater.track() val paramsFeed = updater.trackParametersUpdate()
val snapshot = paramsFeed.snapshot val snapshot = paramsFeed.snapshot
val updates = paramsFeed.updates.bufferUntilSubscribed() val updates = paramsFeed.updates.bufferUntilSubscribed()
assertEquals(null, snapshot) assertEquals(null, snapshot)
@ -229,7 +225,7 @@ class NetworkMapUpdaterTest {
updater.acceptNewNetworkParameters(newHash, { hash -> hash.serialize().sign(keyPair)}) updater.acceptNewNetworkParameters(newHash, { hash -> hash.serialize().sign(keyPair)})
verify(networkMapClient).ackNetworkParametersUpdate(any()) verify(networkMapClient).ackNetworkParametersUpdate(any())
val updateFile = baseDir / NETWORK_PARAMS_UPDATE_FILE_NAME val updateFile = baseDir / NETWORK_PARAMS_UPDATE_FILE_NAME
val signedNetworkParams = updateFile.readAll().deserialize<SignedDataWithCert<NetworkParameters>>() val signedNetworkParams = updateFile.readObject<SignedNetworkParameters>()
val paramsFromFile = signedNetworkParams.verifiedNetworkMapCert(DEV_ROOT_CA.certificate) val paramsFromFile = signedNetworkParams.verifiedNetworkMapCert(DEV_ROOT_CA.certificate)
assertEquals(newParameters, paramsFromFile) assertEquals(newParameters, paramsFromFile)
} }

View File

@ -2,15 +2,13 @@ package net.corda.node.services.network
import com.google.common.jimfs.Configuration import com.google.common.jimfs.Configuration
import com.google.common.jimfs.Jimfs import com.google.common.jimfs.Jimfs
import net.corda.core.internal.* import net.corda.core.internal.createDirectories
import net.corda.core.node.NetworkParameters import net.corda.core.internal.div
import net.corda.core.serialization.deserialize import net.corda.core.internal.exists
import net.corda.core.internal.readObject
import net.corda.core.utilities.seconds import net.corda.core.utilities.seconds
import net.corda.node.internal.NetworkParametersReader import net.corda.node.internal.NetworkParametersReader
import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME import net.corda.nodeapi.internal.network.*
import net.corda.nodeapi.internal.network.NETWORK_PARAMS_UPDATE_FILE_NAME
import net.corda.nodeapi.internal.network.NetworkParametersCopier
import net.corda.nodeapi.internal.network.verifiedNetworkMapCert
import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.driver.PortAllocation import net.corda.testing.driver.PortAllocation
@ -57,7 +55,9 @@ class NetworkParametersReaderTest {
assertFalse((baseDirectory / NETWORK_PARAMS_UPDATE_FILE_NAME).exists()) assertFalse((baseDirectory / NETWORK_PARAMS_UPDATE_FILE_NAME).exists())
assertEquals(server.networkParameters, parameters) assertEquals(server.networkParameters, parameters)
// Parameters from update should be moved to `network-parameters` file. // Parameters from update should be moved to `network-parameters` file.
val parametersFromFile = (baseDirectory / NETWORK_PARAMS_FILE_NAME).readAll().deserialize<SignedDataWithCert<NetworkParameters>>().verifiedNetworkMapCert(DEV_ROOT_CA.certificate) val parametersFromFile = (baseDirectory / NETWORK_PARAMS_FILE_NAME)
.readObject<SignedNetworkParameters>()
.verifiedNetworkMapCert(DEV_ROOT_CA.certificate)
assertEquals(server.networkParameters, parametersFromFile) assertEquals(server.networkParameters, parametersFromFile)
} }
} }

View File

@ -21,6 +21,7 @@ import net.corda.testing.node.startFlow
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Ignore
import org.junit.Test import org.junit.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith

View File

@ -78,7 +78,7 @@ open class MockServices private constructor(
cordappLoader: CordappLoader, cordappLoader: CordappLoader,
override val validatedTransactions: WritableTransactionStorage, override val validatedTransactions: WritableTransactionStorage,
override val identityService: IdentityService, override val identityService: IdentityService,
override val networkParameters: NetworkParameters, final override val networkParameters: NetworkParameters,
private val initialIdentity: TestIdentity, private val initialIdentity: TestIdentity,
private val moreKeys: Array<out KeyPair> private val moreKeys: Array<out KeyPair>
) : ServiceHub, StateLoader by validatedTransactions { ) : ServiceHub, StateLoader by validatedTransactions {
@ -261,7 +261,7 @@ open class MockServices private constructor(
return NodeInfo(listOf(NetworkHostAndPort("mock.node.services", 10000)), listOf(initialIdentity.identity), 1, serial = 1L) return NodeInfo(listOf(NetworkHostAndPort("mock.node.services", 10000)), listOf(initialIdentity.identity), 1, serial = 1L)
} }
override val transactionVerifierService: TransactionVerifierService get() = InMemoryTransactionVerifierService(2) 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 override val cordappProvider: CordappProvider get() = mockCordappProvider
internal fun makeVaultService(hibernateConfig: HibernateConfiguration, schemaService: SchemaService): VaultServiceInternal { internal fun makeVaultService(hibernateConfig: HibernateConfiguration, schemaService: SchemaService): VaultServiceInternal {

View File

@ -18,7 +18,6 @@ import net.corda.core.messaging.CordaRPCOps
import net.corda.core.node.NetworkParameters import net.corda.core.node.NetworkParameters
import net.corda.core.node.NotaryInfo import net.corda.core.node.NotaryInfo
import net.corda.core.node.services.NetworkMapCache import net.corda.core.node.services.NetworkMapCache
import net.corda.core.serialization.deserialize
import net.corda.core.toFuture import net.corda.core.toFuture
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
@ -485,7 +484,7 @@ class DriverDSLImpl(
val nodeInfoFile = config.corda.baseDirectory.list { paths -> val nodeInfoFile = config.corda.baseDirectory.list { paths ->
paths.filter { it.fileName.toString().startsWith(NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX) }.findFirst().get() paths.filter { it.fileName.toString().startsWith(NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX) }.findFirst().get()
} }
val nodeInfo = nodeInfoFile.readAll().deserialize<SignedNodeInfo>().verified() val nodeInfo = nodeInfoFile.readObject<SignedNodeInfo>().verified()
NotaryInfo(nodeInfo.legalIdentities[0], spec.validating) NotaryInfo(nodeInfo.legalIdentities[0], spec.validating)
} }
} }

View File

@ -39,7 +39,7 @@ class NetworkMapServer(private val cacheTimeout: Duration,
private val myHostNameValue: String = "test.host.name", private val myHostNameValue: String = "test.host.name",
vararg additionalServices: Any) : Closeable { vararg additionalServices: Any) : Closeable {
companion object { 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 private val server: Server

View File

@ -19,6 +19,7 @@ fun testNetworkParameters(
modifiedTime = modifiedTime, modifiedTime = modifiedTime,
maxMessageSize = maxMessageSize, maxMessageSize = maxMessageSize,
maxTransactionSize = maxTransactionSize, maxTransactionSize = maxTransactionSize,
epoch = epoch epoch = epoch,
whitelistedContractImplementations = emptyMap()
) )
} }

View File

@ -2,6 +2,7 @@ package net.corda.testing.internal
import net.corda.core.contracts.ContractClassName import net.corda.core.contracts.ContractClassName
import net.corda.core.cordapp.Cordapp import net.corda.core.cordapp.Cordapp
import net.corda.core.internal.TEST_UPLOADER
import net.corda.core.internal.cordapp.CordappImpl import net.corda.core.internal.cordapp.CordappImpl
import net.corda.core.node.services.AttachmentId import net.corda.core.node.services.AttachmentId
import net.corda.core.node.services.AttachmentStorage import net.corda.core.node.services.AttachmentStorage
@ -14,9 +15,10 @@ import java.util.*
class MockCordappProvider( class MockCordappProvider(
cordappLoader: CordappLoader, cordappLoader: CordappLoader,
attachmentStorage: AttachmentStorage, attachmentStorage: AttachmentStorage,
val cordappConfigProvider: MockCordappConfigProvider = MockCordappConfigProvider() whitelistedContractImplementations: Map<String, List<AttachmentId>>,
) : CordappProviderImpl(cordappLoader, cordappConfigProvider, attachmentStorage) { cordappConfigProvider: MockCordappConfigProvider = MockCordappConfigProvider()
constructor(cordappLoader: CordappLoader, attachmentStorage: AttachmentStorage) : this(cordappLoader, attachmentStorage, MockCordappConfigProvider()) ) : CordappProviderImpl(cordappLoader, cordappConfigProvider, attachmentStorage, whitelistedContractImplementations) {
constructor(cordappLoader: CordappLoader, attachmentStorage: AttachmentStorage, whitelistedContractImplementations: Map<String, List<AttachmentId>>) : this(cordappLoader, attachmentStorage, whitelistedContractImplementations, MockCordappConfigProvider())
val cordappRegistry = mutableListOf<Pair<Cordapp, AttachmentId>>() val cordappRegistry = mutableListOf<Pair<Cordapp, AttachmentId>>()
@ -33,20 +35,21 @@ class MockCordappProvider(
customSchemas = emptySet(), customSchemas = emptySet(),
jarPath = Paths.get("").toUri().toURL()) jarPath = Paths.get("").toUri().toURL())
if (cordappRegistry.none { it.first.contractClassNames.contains(contractClassName) }) { 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<ContractClassName>, data: ByteArray, attachments: MockAttachmentStorage): AttachmentId {
val existingAttachment = attachments.files.filter { val existingAttachment = attachments.files.filter {
Arrays.equals(it.value, data) Arrays.equals(it.value.second, data)
} }
return if (!existingAttachment.isEmpty()) { return if (!existingAttachment.isEmpty()) {
existingAttachment.keys.first() existingAttachment.keys.first()
} else { } else {
attachments.importAttachment(data.inputStream()) attachments.importContractAttachment(contractClassNames, TEST_UPLOADER, data.inputStream())
} }
} }
} }

View File

@ -1,14 +1,18 @@
package net.corda.testing.services package net.corda.testing.services
import net.corda.core.contracts.Attachment 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.SecureHash
import net.corda.core.crypto.sha256 import net.corda.core.crypto.sha256
import net.corda.core.internal.AbstractAttachment 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.AttachmentId
import net.corda.core.node.services.AttachmentStorage import net.corda.core.node.services.AttachmentStorage
import net.corda.core.node.services.vault.AttachmentQueryCriteria import net.corda.core.node.services.vault.AttachmentQueryCriteria
import net.corda.core.node.services.vault.AttachmentSort import net.corda.core.node.services.vault.AttachmentSort
import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.nodeapi.internal.withContractsInJar
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.InputStream import java.io.InputStream
import java.util.* import java.util.*
@ -24,31 +28,17 @@ class MockAttachmentStorage : AttachmentStorage, SingletonSerializeAsToken() {
} }
} }
override fun importAttachment(jar: InputStream): AttachmentId { val files = HashMap<SecureHash, Pair<Attachment, ByteArray>>()
// 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) override fun importAttachment(jar: InputStream): AttachmentId = importAttachment(jar, UNKNOWN_UPLOADER, null)
val sha256 = bytes.sha256() override fun importAttachment(jar: InputStream, uploader: String, filename: String?): AttachmentId {
if (!files.containsKey(sha256)) { return withContractsInJar(jar) { contractClassNames, inputStream ->
files[sha256] = bytes importAttachmentInternal(inputStream, uploader, filename, contractClassNames)
} }
return sha256
} }
override fun importAttachment(jar: InputStream, uploader: String, filename: String): AttachmentId { override fun openAttachment(id: SecureHash): Attachment? = files[id]?.first
return importAttachment(jar)
}
val files = HashMap<SecureHash, ByteArray>()
private class MockAttachment(dataLoader: () -> ByteArray, override val id: SecureHash) : AbstractAttachment(dataLoader)
override fun openAttachment(id: SecureHash): Attachment? {
val f = files[id] ?: return null
return MockAttachment({ f }, id)
}
override fun queryAttachments(criteria: AttachmentQueryCriteria, sorting: AttachmentSort?): List<AttachmentId> { override fun queryAttachments(criteria: AttachmentQueryCriteria, sorting: AttachmentSort?): List<AttachmentId> {
throw NotImplementedError("Querying for attachments not implemented") throw NotImplementedError("Querying for attachments not implemented")
@ -56,11 +46,6 @@ class MockAttachmentStorage : AttachmentStorage, SingletonSerializeAsToken() {
override fun hasAttachment(attachmentId: AttachmentId) = files.containsKey(attachmentId) override fun hasAttachment(attachmentId: AttachmentId) = files.containsKey(attachmentId)
fun getAttachmentIdAndBytes(jar: InputStream): Pair<AttachmentId, ByteArray> {
val bytes = getBytes(jar)
return Pair(bytes.sha256(), bytes)
}
override fun importOrGetAttachment(jar: InputStream): AttachmentId { override fun importOrGetAttachment(jar: InputStream): AttachmentId {
try { try {
return importAttachment(jar) return importAttachment(jar)
@ -68,4 +53,25 @@ class MockAttachmentStorage : AttachmentStorage, SingletonSerializeAsToken() {
return AttachmentId.parse(faee.message!!) return AttachmentId.parse(faee.message!!)
} }
} }
fun importContractAttachment(contractClassNames: List<ContractClassName>, uploader: String, jar: InputStream): AttachmentId = importAttachmentInternal(jar, uploader, null, contractClassNames)
fun getAttachmentIdAndBytes(jar: InputStream): Pair<AttachmentId, ByteArray> = 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<ContractClassName>? = 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
}
} }

View File

@ -145,7 +145,8 @@ class NodeController(check: atRuntime = ::checkExists) : Controller() {
modifiedTime = Instant.now(), modifiedTime = Instant.now(),
maxMessageSize = 10485760, maxMessageSize = 10485760,
maxTransactionSize = Int.MAX_VALUE, maxTransactionSize = Int.MAX_VALUE,
epoch = 1 epoch = 1,
whitelistedContractImplementations = emptyMap()
)) ))
notaryIdentity = identity notaryIdentity = identity
networkParametersCopier = parametersCopier networkParametersCopier = parametersCopier

View File

@ -31,20 +31,32 @@ class HealthCheckCordform : CordformDefinition() {
} }
notaryNode(0, 10008) { notaryNode(0, 10008) {
p2pPort(10009) p2pPort(10009)
rpcPort(10010) rpcSettings {
port(10010)
adminPort(10110)
}
} }
notaryNode(1, 10012, 10008) { notaryNode(1, 10012, 10008) {
p2pPort(10013) p2pPort(10013)
rpcPort(10014) rpcSettings {
port(10014)
adminPort(10114)
}
} }
notaryNode(2, 10016, 10008) { notaryNode(2, 10016, 10008) {
p2pPort(10017) p2pPort(10017)
rpcPort(10018) rpcSettings {
port(10018)
adminPort(10118)
}
} }
node { node {
name(CordaX500Name("R3 Notary Health Check", "London", "GB")) name(CordaX500Name("R3 Notary Health Check", "London", "GB"))
p2pPort(10002) p2pPort(10002)
rpcPort(10003) rpcSettings {
port(10003)
adminPort(10103)
}
rpcUsers(notaryDemoUser) rpcUsers(notaryDemoUser)
} }
} }

View File

@ -43,7 +43,7 @@ data class GeneratedLedger(
private val attachmentMap: Map<SecureHash, Attachment> by lazy { attachments.associateBy(Attachment::id) } private val attachmentMap: Map<SecureHash, Attachment> by lazy { attachments.associateBy(Attachment::id) }
private val identityMap: Map<PublicKey, Party> by lazy { identities.associateBy(Party::owningKey) } private val identityMap: Map<PublicKey, Party> by lazy { identities.associateBy(Party::owningKey) }
private val contractAttachmentMap: Map<String, ContractAttachment> by lazy { private val contractAttachmentMap: Map<String, ContractAttachment> 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 { private val services = object : ServicesForResolution {

View File

@ -22,6 +22,7 @@ import net.corda.testing.internal.IntegrationTestSchemas
import net.corda.testing.internal.toDatabaseSchemaName import net.corda.testing.internal.toDatabaseSchemaName
import net.corda.testing.node.NotarySpec import net.corda.testing.node.NotarySpec
import org.junit.ClassRule import org.junit.ClassRule
import org.junit.Ignore
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import java.util.* import java.util.*
@ -142,6 +143,7 @@ class VerifierTests : IntegrationTest() {
} }
} }
@Ignore("CORDA-1022")
@Test @Test
fun `single verifier works with a node`() { fun `single verifier works with a node`() {
verifierDriver(DriverParameters( verifierDriver(DriverParameters(