Merge branch 'master' of github.com:corda/corda

This commit is contained in:
Maksymilian Pawlak 2018-02-26 11:11:45 +00:00
commit 6796691611
307 changed files with 4926 additions and 4713 deletions

View File

@ -14,8 +14,6 @@
public void setMessage(String)
public void setOriginalExceptionClassName(String)
##
public @interface net.corda.core.CordaInternal
##
public final class net.corda.core.CordaOID extends java.lang.Object
@org.jetbrains.annotations.NotNull public static final String CORDA_PLATFORM = "1.3.6.1.4.1.50530.1"
public static final net.corda.core.CordaOID INSTANCE
@ -209,7 +207,7 @@ public static final class net.corda.core.context.Trace$InvocationId$Companion ex
public static final class net.corda.core.context.Trace$SessionId$Companion extends java.lang.Object
@kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.context.Trace$SessionId newInstance(String, java.time.Instant)
##
@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.AlwaysAcceptAttachmentConstraint extends java.lang.Object implements net.corda.core.contracts.AttachmentConstraint
@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.contracts.AlwaysAcceptAttachmentConstraint extends java.lang.Object implements net.corda.core.contracts.AttachmentConstraint
public boolean isSatisfiedBy(net.corda.core.contracts.Attachment)
public static final net.corda.core.contracts.AlwaysAcceptAttachmentConstraint INSTANCE
##
@ -284,14 +282,14 @@ public static final class net.corda.core.contracts.AmountTransfer$Companion exte
@org.jetbrains.annotations.NotNull public abstract java.io.InputStream open()
@org.jetbrains.annotations.NotNull public abstract jar.JarInputStream openAsJAR()
##
@net.corda.core.serialization.CordaSerializable public interface net.corda.core.contracts.AttachmentConstraint
@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public interface net.corda.core.contracts.AttachmentConstraint
public abstract boolean isSatisfiedBy(net.corda.core.contracts.Attachment)
##
@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.AttachmentResolutionException extends net.corda.core.flows.FlowException
public <init>(net.corda.core.crypto.SecureHash)
@org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getHash()
##
@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.AutomaticHashConstraint extends java.lang.Object implements net.corda.core.contracts.AttachmentConstraint
@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.contracts.AutomaticHashConstraint extends java.lang.Object implements net.corda.core.contracts.AttachmentConstraint
public boolean isSatisfiedBy(net.corda.core.contracts.Attachment)
public static final net.corda.core.contracts.AutomaticHashConstraint INSTANCE
##
@ -343,14 +341,20 @@ public final class net.corda.core.contracts.ComponentGroupEnum extends java.lang
##
@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.ContractAttachment extends java.lang.Object implements net.corda.core.contracts.Attachment
public <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)
@org.jetbrains.annotations.NotNull public final Set getAdditionalContracts()
@org.jetbrains.annotations.NotNull public final Set getAllContracts()
@org.jetbrains.annotations.NotNull public final net.corda.core.contracts.Attachment getAttachment()
@org.jetbrains.annotations.NotNull public final String getContract()
@org.jetbrains.annotations.NotNull public net.corda.core.crypto.SecureHash getId()
@org.jetbrains.annotations.NotNull public List getSigners()
public int getSize()
@org.jetbrains.annotations.Nullable public final String getUploader()
@org.jetbrains.annotations.NotNull public java.io.InputStream open()
@org.jetbrains.annotations.NotNull public jar.JarInputStream openAsJAR()
@org.jetbrains.annotations.NotNull public String toString()
##
@net.corda.core.serialization.CordaSerializable public interface net.corda.core.contracts.ContractState
@org.jetbrains.annotations.NotNull public abstract List getParticipants()
@ -366,7 +370,7 @@ public final class net.corda.core.contracts.ContractsDSL extends java.lang.Objec
@org.jetbrains.annotations.NotNull public abstract Collection getExitKeys()
@org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.FungibleAsset withNewOwnerAndAmount(net.corda.core.contracts.Amount, net.corda.core.identity.AbstractParty)
##
@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.HashAttachmentConstraint extends java.lang.Object implements net.corda.core.contracts.AttachmentConstraint
@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.contracts.HashAttachmentConstraint extends java.lang.Object implements net.corda.core.contracts.AttachmentConstraint
public <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.contracts.HashAttachmentConstraint copy(net.corda.core.crypto.SecureHash)
@ -557,6 +561,9 @@ public final class net.corda.core.contracts.TransactionStateKt extends java.lang
@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.contracts.TransactionVerificationException extends net.corda.core.flows.FlowException
@org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getTxId()
##
@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.contracts.TransactionVerificationException$ConflictingAttachmentsRejection extends net.corda.core.contracts.TransactionVerificationException
public <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
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 net.corda.core.contracts.ContractState upgrade(net.corda.core.contracts.ContractState)
##
@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.contracts.WhitelistedByZoneAttachmentConstraint extends java.lang.Object implements net.corda.core.contracts.AttachmentConstraint
public boolean isSatisfiedBy(net.corda.core.contracts.Attachment)
public static final net.corda.core.contracts.WhitelistedByZoneAttachmentConstraint INSTANCE
##
@net.corda.core.DoNotImplement public interface net.corda.core.cordapp.Cordapp
@org.jetbrains.annotations.NotNull public abstract List getContractClassNames()
@org.jetbrains.annotations.NotNull public abstract List getCordappClasses()
@ -634,10 +645,25 @@ public static final class net.corda.core.contracts.UniqueIdentifier$Companion ex
@org.jetbrains.annotations.NotNull public abstract List getServiceFlows()
@org.jetbrains.annotations.NotNull public abstract List getServices()
##
@net.corda.core.DoNotImplement public interface net.corda.core.cordapp.CordappConfig
public abstract boolean exists(String)
@org.jetbrains.annotations.NotNull public abstract Object get(String)
public abstract boolean getBoolean(String)
public abstract double getDouble(String)
public abstract float getFloat(String)
public abstract int getInt(String)
public abstract long getLong(String)
@org.jetbrains.annotations.NotNull public abstract Number getNumber(String)
@org.jetbrains.annotations.NotNull public abstract String getString(String)
##
public final class net.corda.core.cordapp.CordappConfigException extends java.lang.Exception
public <init>(String, Throwable)
##
public final class net.corda.core.cordapp.CordappContext extends java.lang.Object
public <init>(net.corda.core.cordapp.Cordapp, net.corda.core.crypto.SecureHash, ClassLoader)
public <init>(net.corda.core.cordapp.Cordapp, net.corda.core.crypto.SecureHash, ClassLoader, net.corda.core.cordapp.CordappConfig)
@org.jetbrains.annotations.Nullable public final net.corda.core.crypto.SecureHash getAttachmentId()
@org.jetbrains.annotations.NotNull public final ClassLoader getClassLoader()
@org.jetbrains.annotations.NotNull public final net.corda.core.cordapp.CordappConfig getConfig()
@org.jetbrains.annotations.NotNull public final net.corda.core.cordapp.Cordapp getCordapp()
##
@net.corda.core.DoNotImplement public interface net.corda.core.cordapp.CordappProvider
@ -1128,7 +1154,8 @@ public final class net.corda.core.flows.ContractUpgradeFlow extends java.lang.Ob
public <init>(net.corda.core.contracts.StateAndRef, Class)
@co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull protected net.corda.core.flows.AbstractStateReplacementFlow$UpgradeTx assembleTx()
##
public abstract class net.corda.core.flows.DataVendingFlow extends net.corda.core.flows.FlowLogic
public class net.corda.core.flows.DataVendingFlow extends net.corda.core.flows.FlowLogic
public <init>(net.corda.core.flows.FlowSession, Object)
@co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.Nullable public Void call()
@org.jetbrains.annotations.NotNull public final net.corda.core.flows.FlowSession getOtherSideSession()
@org.jetbrains.annotations.NotNull public final Object getPayload()
@ -1233,16 +1260,14 @@ public abstract class net.corda.core.flows.FlowLogic extends java.lang.Object
@org.jetbrains.annotations.Nullable public net.corda.core.utilities.ProgressTracker getProgressTracker()
@org.jetbrains.annotations.NotNull public final net.corda.core.flows.StateMachineRunId getRunId()
@org.jetbrains.annotations.NotNull public final net.corda.core.node.ServiceHub getServiceHub()
@net.corda.core.CordaInternal @org.jetbrains.annotations.NotNull public final net.corda.core.internal.FlowStateMachine getStateMachine()
@co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public final net.corda.core.flows.FlowSession initiateFlow(net.corda.core.identity.Party)
@co.paralleluniverse.fibers.Suspendable public final void persistFlowStackSnapshot()
@kotlin.Deprecated @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public net.corda.core.utilities.UntrustworthyData receive(Class, net.corda.core.identity.Party)
@co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public List receiveAll(Class, List)
@co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public Map receiveAll(Map)
@co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public Map receiveAllMap(Map)
public final void recordAuditEvent(String, String, Map)
@kotlin.Deprecated @co.paralleluniverse.fibers.Suspendable public void send(net.corda.core.identity.Party, Object)
@kotlin.Deprecated @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public net.corda.core.utilities.UntrustworthyData sendAndReceive(Class, net.corda.core.identity.Party, Object)
@net.corda.core.CordaInternal public final void setStateMachine(net.corda.core.internal.FlowStateMachine)
@co.paralleluniverse.fibers.Suspendable @kotlin.jvm.JvmStatic public static final void sleep(java.time.Duration)
@co.paralleluniverse.fibers.Suspendable public Object subFlow(net.corda.core.flows.FlowLogic)
@org.jetbrains.annotations.Nullable public final net.corda.core.messaging.DataFeed track()
@ -1306,6 +1331,39 @@ public @interface net.corda.core.flows.InitiatedBy
public @interface net.corda.core.flows.InitiatingFlow
public abstract int version()
##
@net.corda.core.serialization.CordaSerializable public final class net.corda.core.flows.NotarisationPayload extends java.lang.Object
public <init>(Object, net.corda.core.flows.NotarisationRequestSignature)
@org.jetbrains.annotations.NotNull public final Object component1()
@org.jetbrains.annotations.NotNull public final net.corda.core.flows.NotarisationRequestSignature component2()
@org.jetbrains.annotations.NotNull public final net.corda.core.flows.NotarisationPayload copy(Object, net.corda.core.flows.NotarisationRequestSignature)
public boolean equals(Object)
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.CoreTransaction getCoreTransaction()
@org.jetbrains.annotations.NotNull public final net.corda.core.flows.NotarisationRequestSignature getRequestSignature()
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.SignedTransaction getSignedTransaction()
@org.jetbrains.annotations.NotNull public final Object getTransaction()
public int hashCode()
public String toString()
##
@net.corda.core.serialization.CordaSerializable public final class net.corda.core.flows.NotarisationRequest extends java.lang.Object
public <init>(List, net.corda.core.crypto.SecureHash)
@org.jetbrains.annotations.NotNull public final List getStatesToConsume()
@org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getTransactionId()
public final void verifySignature(net.corda.core.flows.NotarisationRequestSignature, net.corda.core.identity.Party)
public static final net.corda.core.flows.NotarisationRequest$Companion Companion
##
public static final class net.corda.core.flows.NotarisationRequest$Companion extends java.lang.Object
##
@net.corda.core.serialization.CordaSerializable public final class net.corda.core.flows.NotarisationRequestSignature extends java.lang.Object
public <init>(net.corda.core.crypto.DigitalSignature$WithKey, int)
@org.jetbrains.annotations.NotNull public final net.corda.core.crypto.DigitalSignature$WithKey component1()
public final int component2()
@org.jetbrains.annotations.NotNull public final net.corda.core.flows.NotarisationRequestSignature copy(net.corda.core.crypto.DigitalSignature$WithKey, int)
public boolean equals(Object)
@org.jetbrains.annotations.NotNull public final net.corda.core.crypto.DigitalSignature$WithKey getDigitalSignature()
public final int getPlatformVersion()
public int hashCode()
public String toString()
##
@net.corda.core.flows.InitiatingFlow public final class net.corda.core.flows.NotaryChangeFlow extends net.corda.core.flows.AbstractStateReplacementFlow$Instigator
public <init>(net.corda.core.contracts.StateAndRef, net.corda.core.identity.Party, net.corda.core.utilities.ProgressTracker)
@org.jetbrains.annotations.NotNull protected net.corda.core.flows.AbstractStateReplacementFlow$UpgradeTx assembleTx()
@ -1324,11 +1382,20 @@ public @interface net.corda.core.flows.InitiatingFlow
@org.jetbrains.annotations.NotNull public String toString()
##
@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.NotaryError$General extends net.corda.core.flows.NotaryError
public <init>(String)
@org.jetbrains.annotations.NotNull public final String component1()
@org.jetbrains.annotations.NotNull public final net.corda.core.flows.NotaryError$General copy(String)
public <init>(Throwable)
@org.jetbrains.annotations.NotNull public final Throwable component1()
@org.jetbrains.annotations.NotNull public final net.corda.core.flows.NotaryError$General copy(Throwable)
public boolean equals(Object)
@org.jetbrains.annotations.NotNull public final String getCause()
@org.jetbrains.annotations.NotNull public final Throwable getCause()
public int hashCode()
@org.jetbrains.annotations.NotNull public String toString()
##
@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.NotaryError$RequestSignatureInvalid extends net.corda.core.flows.NotaryError
public <init>(Throwable)
@org.jetbrains.annotations.NotNull public final Throwable component1()
@org.jetbrains.annotations.NotNull public final net.corda.core.flows.NotaryError$RequestSignatureInvalid copy(Throwable)
public boolean equals(Object)
@org.jetbrains.annotations.NotNull public final Throwable getCause()
public int hashCode()
@org.jetbrains.annotations.NotNull public String toString()
##
@ -1370,7 +1437,10 @@ public final class net.corda.core.flows.NotaryFlow extends java.lang.Object
public <init>(net.corda.core.transactions.SignedTransaction)
public <init>(net.corda.core.transactions.SignedTransaction, net.corda.core.utilities.ProgressTracker)
@co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public List call()
@org.jetbrains.annotations.NotNull protected final net.corda.core.identity.Party checkTransaction()
@org.jetbrains.annotations.NotNull public net.corda.core.utilities.ProgressTracker getProgressTracker()
@co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull protected final net.corda.core.utilities.UntrustworthyData notarise(net.corda.core.identity.Party)
@org.jetbrains.annotations.NotNull protected final List validateResponse(net.corda.core.utilities.UntrustworthyData, net.corda.core.identity.Party)
public static final net.corda.core.flows.NotaryFlow$Client$Companion Companion
##
public static final class net.corda.core.flows.NotaryFlow$Client$Companion extends java.lang.Object
@ -1747,14 +1817,15 @@ public @interface net.corda.core.messaging.RPCReturnsObservables
@org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.FlowProgressHandle startTrackedFlow(net.corda.core.flows.FlowLogic)
##
@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.NetworkParameters extends java.lang.Object
public <init>(int, List, int, int, java.time.Instant, int)
public <init>(int, List, int, int, java.time.Instant, int, Map)
public final int component1()
@org.jetbrains.annotations.NotNull public final List component2()
public final int component3()
public final int component4()
@org.jetbrains.annotations.NotNull public final java.time.Instant component5()
public final int component6()
@org.jetbrains.annotations.NotNull public final net.corda.core.node.NetworkParameters copy(int, List, int, int, java.time.Instant, int)
@org.jetbrains.annotations.NotNull public final Map component7()
@org.jetbrains.annotations.NotNull public final net.corda.core.node.NetworkParameters copy(int, List, int, int, java.time.Instant, int, Map)
public boolean equals(Object)
public final int getEpoch()
public final int getMaxMessageSize()
@ -1762,6 +1833,7 @@ public @interface net.corda.core.messaging.RPCReturnsObservables
public final int getMinimumPlatformVersion()
@org.jetbrains.annotations.NotNull public final java.time.Instant getModifiedTime()
@org.jetbrains.annotations.NotNull public final List getNotaries()
@org.jetbrains.annotations.NotNull public final Map getWhitelistedContractImplementations()
public int hashCode()
public String toString()
##
@ -1803,6 +1875,7 @@ public @interface net.corda.core.messaging.RPCReturnsObservables
@org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.FilteredTransaction, java.security.PublicKey)
@org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.SignedTransaction)
@org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.SignedTransaction, java.security.PublicKey)
@org.jetbrains.annotations.NotNull public abstract net.corda.core.cordapp.CordappContext getAppContext()
@org.jetbrains.annotations.NotNull public abstract java.time.Clock getClock()
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.ContractUpgradeService getContractUpgradeService()
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.KeyManagementService getKeyManagementService()
@ -1825,6 +1898,7 @@ public @interface net.corda.core.messaging.RPCReturnsObservables
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.AttachmentStorage getAttachments()
@org.jetbrains.annotations.NotNull public abstract net.corda.core.cordapp.CordappProvider getCordappProvider()
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.IdentityService getIdentityService()
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.NetworkParameters getNetworkParameters()
##
@net.corda.core.DoNotImplement public interface net.corda.core.node.StateLoader
@org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.TransactionState loadState(net.corda.core.contracts.StateRef)
@ -1837,9 +1911,9 @@ public final class net.corda.core.node.StatesToRecord extends java.lang.Enum
##
@net.corda.core.DoNotImplement public interface net.corda.core.node.services.AttachmentStorage
public abstract boolean hasAttachment(net.corda.core.crypto.SecureHash)
@org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.SecureHash importAttachment(java.io.InputStream)
@kotlin.Deprecated @org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.SecureHash importAttachment(java.io.InputStream)
@org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.SecureHash importAttachment(java.io.InputStream, String, String)
@org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.SecureHash importOrGetAttachment(java.io.InputStream)
@kotlin.Deprecated @org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.SecureHash importOrGetAttachment(java.io.InputStream)
@org.jetbrains.annotations.Nullable public abstract net.corda.core.contracts.Attachment openAttachment(net.corda.core.crypto.SecureHash)
@org.jetbrains.annotations.NotNull public abstract List queryAttachments(net.corda.core.node.services.vault.AttachmentQueryCriteria, net.corda.core.node.services.vault.AttachmentSort)
##
@ -2816,7 +2890,7 @@ public final class net.corda.core.serialization.ObjectWithCompatibleContext exte
public final class net.corda.core.serialization.SerializationAPIKt extends java.lang.Object
@org.jetbrains.annotations.NotNull public static final net.corda.core.serialization.SerializedBytes serialize(Object, net.corda.core.serialization.SerializationFactory, net.corda.core.serialization.SerializationContext)
##
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()
public abstract boolean getObjectReferencesEnabled()
@org.jetbrains.annotations.NotNull public abstract net.corda.core.utilities.ByteSequence getPreferredSerializationVersion()
@ -2921,7 +2995,7 @@ public static final class net.corda.core.serialization.SingletonSerializationTok
@org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getId()
@org.jetbrains.annotations.NotNull public final String getReason()
##
@net.corda.core.DoNotImplement public abstract class net.corda.core.transactions.CoreTransaction extends net.corda.core.transactions.BaseTransaction
@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public abstract class net.corda.core.transactions.CoreTransaction extends net.corda.core.transactions.BaseTransaction
public <init>()
@org.jetbrains.annotations.NotNull public abstract List getInputs()
##
@ -2967,6 +3041,7 @@ public static final class net.corda.core.transactions.FilteredTransaction$Compan
##
@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.transactions.LedgerTransaction extends net.corda.core.transactions.FullTransaction
public <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 component1()
@org.jetbrains.annotations.NotNull public final List component2()
@ -2977,6 +3052,7 @@ public static final class net.corda.core.transactions.FilteredTransaction$Compan
@org.jetbrains.annotations.Nullable public final net.corda.core.contracts.TimeWindow component7()
@org.jetbrains.annotations.NotNull public final net.corda.core.contracts.PrivacySalt component8()
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.LedgerTransaction copy(List, List, List, List, net.corda.core.crypto.SecureHash, net.corda.core.identity.Party, net.corda.core.contracts.TimeWindow, net.corda.core.contracts.PrivacySalt)
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.LedgerTransaction copy(List, List, List, List, net.corda.core.crypto.SecureHash, net.corda.core.identity.Party, net.corda.core.contracts.TimeWindow, net.corda.core.contracts.PrivacySalt, net.corda.core.node.NetworkParameters)
public boolean equals(Object)
@org.jetbrains.annotations.NotNull public final List filterCommands(Class, function.Predicate)
@org.jetbrains.annotations.NotNull public final List filterInRefs(Class, function.Predicate)
@ -3163,7 +3239,7 @@ public class net.corda.core.transactions.TransactionBuilder extends java.lang.Ob
public abstract void verifyRequiredSignatures()
public abstract void verifySignaturesExcept(Collection)
##
@net.corda.core.DoNotImplement public abstract class net.corda.core.transactions.TraversableTransaction extends net.corda.core.transactions.CoreTransaction
@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public abstract class net.corda.core.transactions.TraversableTransaction extends net.corda.core.transactions.CoreTransaction
public <init>(List)
@org.jetbrains.annotations.NotNull public final List getAttachments()
@org.jetbrains.annotations.NotNull public final List getAvailableComponentGroups()
@ -3185,7 +3261,7 @@ public class net.corda.core.transactions.TransactionBuilder extends java.lang.Ob
@org.jetbrains.annotations.NotNull public final net.corda.core.contracts.PrivacySalt getPrivacySalt()
@org.jetbrains.annotations.NotNull public final Set getRequiredSigningKeys()
public int hashCode()
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.LedgerTransaction toLedgerTransaction(kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function1)
@kotlin.Deprecated @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.LedgerTransaction toLedgerTransaction(kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function1)
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.LedgerTransaction toLedgerTransaction(net.corda.core.node.ServicesForResolution)
@org.jetbrains.annotations.NotNull public String toString()
public static final net.corda.core.transactions.WireTransaction$Companion Companion
@ -3655,12 +3731,11 @@ public final class net.corda.testing.driver.Driver extends java.lang.Object
##
public final class net.corda.testing.driver.DriverParameters extends java.lang.Object
public <init>()
public <init>(boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, Map, boolean, boolean, boolean, boolean, List, List, net.corda.testing.driver.JmxPolicy, int)
public <init>(boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, Map, boolean, boolean, boolean, List, List, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters)
public final boolean component1()
@org.jetbrains.annotations.NotNull public final List component10()
@org.jetbrains.annotations.NotNull public final List component11()
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.JmxPolicy component12()
public final int component13()
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.JmxPolicy component11()
@org.jetbrains.annotations.NotNull public final net.corda.core.node.NetworkParameters component12()
@org.jetbrains.annotations.NotNull public final java.nio.file.Path component2()
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.PortAllocation component3()
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.PortAllocation component4()
@ -3668,15 +3743,14 @@ public final class net.corda.testing.driver.DriverParameters extends java.lang.O
public final boolean component6()
public final boolean component7()
public final boolean component8()
public final boolean component9()
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters copy(boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, Map, boolean, boolean, boolean, boolean, List, List, net.corda.testing.driver.JmxPolicy, int)
@org.jetbrains.annotations.NotNull public final List component9()
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters copy(boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, Map, boolean, boolean, boolean, List, List, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters)
public boolean equals(Object)
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.PortAllocation getDebugPortAllocation()
@org.jetbrains.annotations.NotNull public final java.nio.file.Path getDriverDirectory()
@org.jetbrains.annotations.NotNull public final List getExtraCordappPackagesToScan()
public final boolean getInitialiseSerialization()
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.JmxPolicy getJmxPolicy()
public final int getMaxTransactionSize()
@org.jetbrains.annotations.NotNull public final net.corda.core.node.NetworkParameters getNetworkParameters()
@org.jetbrains.annotations.NotNull public final List getNotarySpecs()
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.PortAllocation getPortAllocation()
public final boolean getStartNodesInProcess()
@ -3685,22 +3759,21 @@ public final class net.corda.testing.driver.DriverParameters extends java.lang.O
public final boolean getWaitForAllNodesToFinish()
public int hashCode()
public final boolean isDebug()
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters setDebugPortAllocation(net.corda.testing.driver.PortAllocation)
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters setDriverDirectory(java.nio.file.Path)
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters setExtraCordappPackagesToScan(List)
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters setInitialiseSerialization(boolean)
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters setIsDebug(boolean)
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters setJmxPolicy(net.corda.testing.driver.JmxPolicy)
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters setNotarySpecs(List)
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters setPortAllocation(net.corda.testing.driver.PortAllocation)
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters setStartNodesInProcess(boolean)
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters setSystemProperties(Map)
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters setUseTestClock(boolean)
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters setWaitForAllNodesToFinish(boolean)
public String toString()
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters withDebugPortAllocation(net.corda.testing.driver.PortAllocation)
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters withDriverDirectory(java.nio.file.Path)
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters withExtraCordappPackagesToScan(List)
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters withIsDebug(boolean)
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters withJmxPolicy(net.corda.testing.driver.JmxPolicy)
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters withNetworkParameters(net.corda.core.node.NetworkParameters)
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters withNotarySpecs(List)
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters withPortAllocation(net.corda.testing.driver.PortAllocation)
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters withStartNodesInProcess(boolean)
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters withSystemProperties(Map)
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters withUseTestClock(boolean)
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters withWaitForAllNodesToFinish(boolean)
##
@net.corda.core.DoNotImplement public interface net.corda.testing.driver.InProcess extends net.corda.testing.driver.NodeHandle
@org.jetbrains.annotations.NotNull public abstract net.corda.nodeapi.internal.persistence.CordaPersistence getDatabase()
@org.jetbrains.annotations.NotNull public abstract net.corda.node.services.api.StartedNodeServices getServices()
@org.jetbrains.annotations.NotNull public abstract rx.Observable registerInitiatedFlow(Class)
##
@ -3743,13 +3816,13 @@ public final class net.corda.testing.driver.NodeParameters extends java.lang.Obj
@org.jetbrains.annotations.Nullable public final Boolean getStartInSameProcess()
@org.jetbrains.annotations.NotNull public final net.corda.node.services.config.VerifierType getVerifierType()
public int hashCode()
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.NodeParameters setCustomerOverrides(Map)
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.NodeParameters setMaximumHeapSize(String)
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.NodeParameters setProvidedName(net.corda.core.identity.CordaX500Name)
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.NodeParameters setRpcUsers(List)
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.NodeParameters setStartInSameProcess(Boolean)
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.NodeParameters setVerifierType(net.corda.node.services.config.VerifierType)
public String toString()
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.NodeParameters withCustomerOverrides(Map)
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.NodeParameters withMaximumHeapSize(String)
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.NodeParameters withProvidedName(net.corda.core.identity.CordaX500Name)
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.NodeParameters withRpcUsers(List)
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.NodeParameters withStartInSameProcess(Boolean)
@org.jetbrains.annotations.NotNull public final net.corda.testing.driver.NodeParameters withVerifierType(net.corda.node.services.config.VerifierType)
##
public final class net.corda.testing.driver.NotaryHandle extends java.lang.Object
public <init>(net.corda.core.identity.Party, boolean, net.corda.core.concurrent.CordaFuture)
@ -3805,7 +3878,6 @@ public static final class net.corda.testing.node.ClusterSpec$Raft extends net.co
public String toString()
##
@javax.annotation.concurrent.ThreadSafe public final class net.corda.testing.node.InMemoryMessagingNetwork extends net.corda.core.serialization.SingletonSerializeAsToken
public <init>(boolean, net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy, org.apache.activemq.artemis.utils.ReusableLatch)
@org.jetbrains.annotations.NotNull public synchronized final List getEndpoints()
@org.jetbrains.annotations.NotNull public final rx.Observable getReceivedMessages()
@org.jetbrains.annotations.NotNull public final rx.Observable getSentMessages()
@ -3888,10 +3960,6 @@ public static final class net.corda.testing.node.InMemoryMessagingNetwork$Servic
@org.jetbrains.annotations.Nullable public abstract net.corda.testing.node.InMemoryMessagingNetwork$MessageTransfer pumpReceive(boolean)
public abstract void stop()
##
public static final class net.corda.testing.node.InMemoryMessagingNetwork$pumpSend$$inlined$schedule$1 extends java.util.TimerTask
public <init>(net.corda.testing.node.InMemoryMessagingNetwork, net.corda.testing.node.InMemoryMessagingNetwork$MessageTransfer, net.corda.core.internal.concurrent.OpenFuture)
public void run()
##
public class net.corda.testing.node.MessagingServiceSpy extends java.lang.Object implements net.corda.node.services.messaging.MessagingService
public <init>(net.corda.node.services.messaging.MessagingService)
@org.jetbrains.annotations.NotNull public net.corda.node.services.messaging.MessageHandlerRegistration addMessageHandler(String, kotlin.jvm.functions.Function2)
@ -3916,7 +3984,7 @@ public final class net.corda.testing.node.MockKeyManagementService extends net.c
public class net.corda.testing.node.MockNetwork extends java.lang.Object
public <init>(List)
public <init>(List, net.corda.testing.node.MockNetworkParameters)
public <init>(List, net.corda.testing.node.MockNetworkParameters, boolean, boolean, net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy, boolean, List, int)
public <init>(List, net.corda.testing.node.MockNetworkParameters, boolean, boolean, net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy, List, net.corda.core.node.NetworkParameters)
@org.jetbrains.annotations.NotNull public final java.nio.file.Path baseDirectory(int)
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.StartedMockNode createNode()
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.StartedMockNode createNode(net.corda.core.identity.CordaX500Name)
@ -3937,8 +4005,7 @@ public class net.corda.testing.node.MockNetwork extends java.lang.Object
@org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party getDefaultNotaryIdentity()
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.StartedMockNode getDefaultNotaryNode()
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetworkParameters getDefaultParameters()
public final boolean getInitialiseSerialization()
public final int getMaxTransactionSize()
@org.jetbrains.annotations.NotNull public final net.corda.core.node.NetworkParameters getNetworkParameters()
public final boolean getNetworkSendManuallyPumped()
public final int getNextNodeId()
@org.jetbrains.annotations.NotNull public final List getNotaryNodes()
@ -3965,29 +4032,26 @@ public final class net.corda.testing.node.MockNetworkNotarySpec extends java.lan
##
public final class net.corda.testing.node.MockNetworkParameters extends java.lang.Object
public <init>()
public <init>(boolean, boolean, net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy, boolean, List, int)
public <init>(boolean, boolean, net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy, List, net.corda.core.node.NetworkParameters)
public final boolean component1()
public final boolean component2()
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy component3()
public final boolean component4()
@org.jetbrains.annotations.NotNull public final List component5()
public final int component6()
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetworkParameters copy(boolean, boolean, net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy, boolean, List, int)
@org.jetbrains.annotations.NotNull public final List component4()
@org.jetbrains.annotations.NotNull public final net.corda.core.node.NetworkParameters component5()
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetworkParameters copy(boolean, boolean, net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy, List, net.corda.core.node.NetworkParameters)
public boolean equals(Object)
public final boolean getInitialiseSerialization()
public final int getMaxTransactionSize()
@org.jetbrains.annotations.NotNull public final net.corda.core.node.NetworkParameters getNetworkParameters()
public final boolean getNetworkSendManuallyPumped()
@org.jetbrains.annotations.NotNull public final List getNotarySpecs()
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy getServicePeerAllocationStrategy()
public final boolean getThreadPerNode()
public int hashCode()
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetworkParameters setInitialiseSerialization(boolean)
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetworkParameters setMaxTransactionSize(int)
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetworkParameters setNetworkSendManuallyPumped(boolean)
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetworkParameters setNotarySpecs(List)
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetworkParameters setServicePeerAllocationStrategy(net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy)
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetworkParameters setThreadPerNode(boolean)
public String toString()
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetworkParameters withNetworkParameters(net.corda.core.node.NetworkParameters)
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetworkParameters withNetworkSendManuallyPumped(boolean)
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetworkParameters withNotarySpecs(List)
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetworkParameters withServicePeerAllocationStrategy(net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy)
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetworkParameters withThreadPerNode(boolean)
##
public final class net.corda.testing.node.MockNodeParameters extends java.lang.Object
public <init>()
@ -4005,11 +4069,11 @@ public final class net.corda.testing.node.MockNodeParameters extends java.lang.O
@org.jetbrains.annotations.Nullable public final net.corda.core.identity.CordaX500Name getLegalName()
@org.jetbrains.annotations.NotNull public final net.corda.node.VersionInfo getVersion()
public int hashCode()
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNodeParameters setConfigOverrides(kotlin.jvm.functions.Function1)
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNodeParameters setEntropyRoot(java.math.BigInteger)
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNodeParameters setForcedID(Integer)
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNodeParameters setLegalName(net.corda.core.identity.CordaX500Name)
public String toString()
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNodeParameters withConfigOverrides(kotlin.jvm.functions.Function1)
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNodeParameters withEntropyRoot(java.math.BigInteger)
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNodeParameters withForcedID(Integer)
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNodeParameters withLegalName(net.corda.core.identity.CordaX500Name)
##
public class net.corda.testing.node.MockServices extends java.lang.Object implements net.corda.core.node.StateLoader, net.corda.core.node.ServiceHub
public <init>()
@ -4018,6 +4082,7 @@ public class net.corda.testing.node.MockServices extends java.lang.Object implem
public <init>(List, net.corda.core.identity.CordaX500Name, net.corda.core.node.services.IdentityService)
public <init>(net.corda.core.identity.CordaX500Name)
public <init>(net.corda.core.identity.CordaX500Name, net.corda.core.node.services.IdentityService)
public final void addMockCordapp(String)
@org.jetbrains.annotations.NotNull public net.corda.core.transactions.SignedTransaction addSignature(net.corda.core.transactions.SignedTransaction)
@org.jetbrains.annotations.NotNull public net.corda.core.transactions.SignedTransaction addSignature(net.corda.core.transactions.SignedTransaction, java.security.PublicKey)
@org.jetbrains.annotations.NotNull public net.corda.core.serialization.SerializeAsToken cordaService(Class)
@ -4025,6 +4090,7 @@ public class net.corda.testing.node.MockServices extends java.lang.Object implem
@org.jetbrains.annotations.NotNull public net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.FilteredTransaction, java.security.PublicKey)
@org.jetbrains.annotations.NotNull public net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.SignedTransaction)
@org.jetbrains.annotations.NotNull public net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.SignedTransaction, java.security.PublicKey)
@org.jetbrains.annotations.NotNull public net.corda.core.cordapp.CordappContext getAppContext()
@org.jetbrains.annotations.NotNull public final net.corda.testing.services.MockAttachmentStorage getAttachments()
@org.jetbrains.annotations.NotNull public java.time.Clock getClock()
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.ContractUpgradeService getContractUpgradeService()
@ -4033,9 +4099,9 @@ public class net.corda.testing.node.MockServices extends java.lang.Object implem
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.IdentityService getIdentityService()
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.KeyManagementService getKeyManagementService()
@org.jetbrains.annotations.NotNull public static final net.corda.node.VersionInfo getMOCK_VERSION_INFO()
@org.jetbrains.annotations.NotNull public final net.corda.testing.services.MockCordappProvider getMockCordappProvider()
@org.jetbrains.annotations.NotNull public net.corda.core.node.NodeInfo getMyInfo()
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.NetworkMapCache getNetworkMapCache()
@org.jetbrains.annotations.NotNull public final net.corda.core.node.NetworkParameters getNetworkParameters()
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.TransactionVerifierService getTransactionVerifierService()
@org.jetbrains.annotations.NotNull public net.corda.node.services.api.WritableTransactionStorage getValidatedTransactions()
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.VaultService getVaultService()
@ -4074,6 +4140,7 @@ public static final class net.corda.testing.node.MockServicesKt$createMockCordaS
@org.jetbrains.annotations.NotNull public net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.FilteredTransaction, java.security.PublicKey)
@org.jetbrains.annotations.NotNull public net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.SignedTransaction)
@org.jetbrains.annotations.NotNull public net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.SignedTransaction, java.security.PublicKey)
@org.jetbrains.annotations.NotNull public net.corda.core.cordapp.CordappContext getAppContext()
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.AttachmentStorage getAttachments()
@org.jetbrains.annotations.NotNull public java.time.Clock getClock()
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.ContractUpgradeService getContractUpgradeService()
@ -4082,6 +4149,7 @@ public static final class net.corda.testing.node.MockServicesKt$createMockCordaS
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.KeyManagementService getKeyManagementService()
@org.jetbrains.annotations.NotNull public net.corda.core.node.NodeInfo getMyInfo()
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.NetworkMapCache getNetworkMapCache()
@org.jetbrains.annotations.NotNull public net.corda.core.node.NetworkParameters getNetworkParameters()
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockServices getServiceHub()
@org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializeAsToken getServiceInstance()
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.TransactionVerifierService getTransactionVerifierService()
@ -4139,7 +4207,6 @@ public final class net.corda.testing.node.NotarySpec extends java.lang.Object
##
public final class net.corda.testing.node.StartedMockNode extends java.lang.Object
@org.jetbrains.annotations.NotNull public final List findStateMachines(Class)
@org.jetbrains.annotations.NotNull public final net.corda.nodeapi.internal.persistence.CordaPersistence getDatabase()
public final int getId()
@org.jetbrains.annotations.NotNull public final net.corda.core.node.NodeInfo getInfo()
@org.jetbrains.annotations.NotNull public final net.corda.node.services.messaging.MessagingService getNetwork()
@ -4148,11 +4215,12 @@ public final class net.corda.testing.node.StartedMockNode extends java.lang.Obje
@org.jetbrains.annotations.NotNull public final rx.Observable registerInitiatedFlow(Class)
public final void setMessagingServiceSpy(net.corda.testing.node.MessagingServiceSpy)
public final void stop()
public final Object transaction(kotlin.jvm.functions.Function0)
public static final net.corda.testing.node.StartedMockNode$Companion Companion
##
public static final class net.corda.testing.node.StartedMockNode$Companion extends java.lang.Object
##
@javax.annotation.concurrent.ThreadSafe public final class net.corda.testing.node.TestClock extends net.corda.node.internal.MutableClock
@javax.annotation.concurrent.ThreadSafe public final class net.corda.testing.node.TestClock extends net.corda.node.MutableClock
public <init>(java.time.Clock)
public synchronized final void advanceBy(java.time.Duration)
public synchronized final void setTo(java.time.Instant)
@ -4411,24 +4479,7 @@ public static final class net.corda.testing.core.SerializationEnvironmentRule$Co
public static final class net.corda.testing.core.SerializationEnvironmentRule$apply$1 extends org.junit.runners.model.Statement
public void evaluate()
##
public final class net.corda.testing.core.SerializationTestHelpersKt extends java.lang.Object
@org.jetbrains.annotations.NotNull public static final net.corda.testing.core.GlobalSerializationEnvironment setGlobalSerialization(boolean)
##
public static final class net.corda.testing.core.SerializationTestHelpersKt$createTestSerializationEnv$1 extends net.corda.core.serialization.internal.SerializationEnvironmentImpl
@org.jetbrains.annotations.NotNull public String toString()
##
public static final class net.corda.testing.core.SerializationTestHelpersKt$setGlobalSerialization$1 extends java.lang.Object implements net.corda.testing.core.GlobalSerializationEnvironment, net.corda.core.serialization.internal.SerializationEnvironment
@org.jetbrains.annotations.NotNull public net.corda.core.serialization.SerializationContext getCheckpointContext()
@org.jetbrains.annotations.NotNull public net.corda.core.serialization.SerializationContext getP2pContext()
@org.jetbrains.annotations.NotNull public net.corda.core.serialization.SerializationContext getRpcClientContext()
@org.jetbrains.annotations.NotNull public net.corda.core.serialization.SerializationContext getRpcServerContext()
@org.jetbrains.annotations.NotNull public net.corda.core.serialization.SerializationFactory getSerializationFactory()
@org.jetbrains.annotations.NotNull public net.corda.core.serialization.SerializationContext getStorageContext()
public void unset()
##
public final class net.corda.testing.core.TestConstants extends java.lang.Object
@org.jetbrains.annotations.NotNull public static final net.corda.nodeapi.internal.crypto.CertificateAndKeyPair getDEV_INTERMEDIATE_CA()
@org.jetbrains.annotations.NotNull public static final net.corda.nodeapi.internal.crypto.CertificateAndKeyPair getDEV_ROOT_CA()
@kotlin.jvm.JvmField @org.jetbrains.annotations.NotNull public static final net.corda.core.identity.CordaX500Name ALICE_NAME
@kotlin.jvm.JvmField @org.jetbrains.annotations.NotNull public static final net.corda.core.identity.CordaX500Name BOB_NAME
@kotlin.jvm.JvmField @org.jetbrains.annotations.NotNull public static final net.corda.core.identity.CordaX500Name BOC_NAME
@ -4583,6 +4634,7 @@ public static final class net.corda.testing.dsl.TestTransactionDSLInterpreter$se
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.AttachmentStorage getAttachments()
@org.jetbrains.annotations.NotNull public net.corda.core.cordapp.CordappProvider getCordappProvider()
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.IdentityService getIdentityService()
@org.jetbrains.annotations.NotNull public net.corda.core.node.NetworkParameters getNetworkParameters()
@org.jetbrains.annotations.NotNull public net.corda.core.contracts.TransactionState loadState(net.corda.core.contracts.StateRef)
@org.jetbrains.annotations.NotNull public Set loadStates(Set)
##
@ -4670,6 +4722,7 @@ public final class net.corda.testing.services.MockAttachmentStorage extends net.
public boolean hasAttachment(net.corda.core.crypto.SecureHash)
@org.jetbrains.annotations.NotNull public net.corda.core.crypto.SecureHash importAttachment(java.io.InputStream)
@org.jetbrains.annotations.NotNull public net.corda.core.crypto.SecureHash importAttachment(java.io.InputStream, String, String)
@org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash importContractAttachment(List, String, java.io.InputStream)
@org.jetbrains.annotations.NotNull public net.corda.core.crypto.SecureHash importOrGetAttachment(java.io.InputStream)
@org.jetbrains.annotations.Nullable public net.corda.core.contracts.Attachment openAttachment(net.corda.core.crypto.SecureHash)
@org.jetbrains.annotations.NotNull public List queryAttachments(net.corda.core.node.services.vault.AttachmentQueryCriteria, net.corda.core.node.services.vault.AttachmentSort)
@ -4678,12 +4731,3 @@ public final class net.corda.testing.services.MockAttachmentStorage extends net.
public static final class net.corda.testing.services.MockAttachmentStorage$Companion extends java.lang.Object
public final byte[] getBytes(java.io.InputStream)
##
public static final class net.corda.testing.services.MockAttachmentStorage$openAttachment$1 extends net.corda.core.internal.AbstractAttachment
@org.jetbrains.annotations.NotNull public net.corda.core.crypto.SecureHash getId()
##
public final class net.corda.testing.services.MockCordappProvider extends net.corda.node.internal.cordapp.CordappProviderImpl
public <init>(net.corda.node.internal.cordapp.CordappLoader, net.corda.core.node.services.AttachmentStorage)
public final void addMockCordapp(String, net.corda.testing.services.MockAttachmentStorage)
@org.jetbrains.annotations.Nullable public net.corda.core.crypto.SecureHash getContractAttachmentID(String)
@org.jetbrains.annotations.NotNull public final List getCordappRegistry()
##

View File

@ -31,8 +31,7 @@ if [ $removalCount -gt 0 ]; then
fi
# Adding new abstract methods could also break the API.
# However, first exclude anything with the @DoNotImplement annotation.
# However, first exclude classes marked with the @DoNotImplement annotation
function forUserImpl() {
awk '/DoNotImplement/,/^##/{ next }{ print }' $1
}
@ -45,13 +44,28 @@ $newAbstracts
EOF
`
#Get a list of any methods that expose classes in .internal. namespaces, and any classes which extend/implement
#an internal class
newInternalExposures=$(echo "$userDiffContents" | grep "^+" | grep "\.internal\." )
internalCount=`grep -v "^$" <<EOF | wc -l
$newInternalExposures
EOF
`
echo "Number of new internal class exposures: "$internalCount
if [ $internalCount -gt 0 ]; then
echo "$newInternalExposures"
echo
fi
echo "Number of new abstract APIs: "$abstractCount
if [ $abstractCount -gt 0 ]; then
echo "$newAbstracts"
echo
fi
badChanges=$(($removalCount + $abstractCount))
badChanges=$(($removalCount + $abstractCount + $internalCount))
if [ $badChanges -gt 255 ]; then
echo "OVERFLOW! Number of bad API changes: $badChanges"
badChanges=255

9
.idea/compiler.xml generated
View File

@ -29,6 +29,8 @@
<module name="corda-webserver_integrationTest" target="1.8" />
<module name="corda-webserver_main" target="1.8" />
<module name="corda-webserver_test" target="1.8" />
<module name="cordapp-configuration_main" target="1.8" />
<module name="cordapp-configuration_test" target="1.8" />
<module name="cordapp_integrationTest" target="1.8" />
<module name="cordapp_main" target="1.8" />
<module name="cordapp_test" target="1.8" />
@ -37,6 +39,7 @@
<module name="cordformation_main" target="1.8" />
<module name="cordformation_runnodes" target="1.8" />
<module name="cordformation_test" target="1.8" />
<module name="core_extraResource" target="1.8" />
<module name="core_integrationTest" target="1.8" />
<module name="core_main" target="1.8" />
<module name="core_smokeTest" target="1.8" />
@ -153,8 +156,8 @@
<module name="web_test" target="1.8" />
<module name="webcapsule_main" target="1.6" />
<module name="webcapsule_test" target="1.6" />
<module name="webserver-webcapsule_main" target="1.6" />
<module name="webserver-webcapsule_test" target="1.6" />
<module name="webserver-webcapsule_main" target="1.8" />
<module name="webserver-webcapsule_test" target="1.8" />
<module name="webserver_integrationTest" target="1.8" />
<module name="webserver_main" target="1.8" />
<module name="webserver_test" target="1.8" />
@ -163,4 +166,4 @@
<component name="JavacSettings">
<option name="ADDITIONAL_OPTIONS_STRING" value="-parameters" />
</component>
</project>
</project>

View File

@ -13,7 +13,9 @@ buildscript {
//
// TODO: Sort this alphabetically.
ext.kotlin_version = constants.getProperty("kotlinVersion")
ext.quasar_version = '0.7.9'
// use our fork of quasar
ext.quasar_group = 'com.github.corda.quasar'
ext.quasar_version = '7629695563deae6cc95adcfbebcbc8322fd0241a'
// gradle-capsule-plugin:1.0.2 contains capsule:1.0.1
// TODO: Upgrade gradle-capsule-plugin to a version with capsule:1.0.3
@ -38,7 +40,6 @@ buildscript {
ext.jackson_version = '2.9.3'
ext.jetty_version = '9.4.7.v20170914'
ext.jersey_version = '2.25'
ext.jolokia_version = '1.3.7'
ext.assertj_version = '3.8.0'
ext.slf4j_version = '1.7.25'
ext.log4j_version = '2.9.1'
@ -69,6 +70,7 @@ buildscript {
ext.docker_compose_rule_version = '0.33.0'
ext.selenium_version = '3.8.1'
ext.ghostdriver_version = '2.1.0'
ext.eaagentloader_version = '1.0.3'
// Update 121 is required for ObjectInputFilter and at time of writing 131 was latest:
ext.java8_minUpdateVersion = '131'
@ -114,7 +116,6 @@ ext {
apply plugin: 'project-report'
apply plugin: 'com.github.ben-manes.versions'
apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'net.corda.plugins.cordformation'
apply plugin: 'maven-publish'
apply plugin: 'com.jfrog.artifactory'
@ -172,6 +173,10 @@ allprojects {
if (System.getProperty("test.maxParallelForks") != null) {
maxParallelForks = Integer.valueOf(System.getProperty("test.maxParallelForks"))
}
if (project.path.startsWith(':experimental') && System.getProperty("experimental.test.enable") == null) {
enabled = false
}
}
group 'net.corda'
@ -260,30 +265,6 @@ tasks.withType(Test) {
reports.html.destination = file("${reporting.baseDir}/${name}")
}
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
directory "./build/nodes"
node {
name "O=Controller,OU=corda,L=London,C=GB"
notary = [validating : true]
p2pPort 10002
cordapps = []
}
node {
name "O=Bank A,OU=corda,L=London,C=GB"
p2pPort 10012
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 = []
}
}
bintrayConfig {
user = System.getenv('CORDA_BINTRAY_USER')
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.transactions.SignedTransaction
import net.corda.finance.USD
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.contracts.DummyContract
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.DUMMY_NOTARY_NAME
@ -101,6 +102,7 @@ class JacksonSupportTest {
fun writeTransaction() {
val attachmentRef = SecureHash.randomSHA256()
doReturn(attachmentRef).whenever(cordappProvider).getContractAttachmentID(DummyContract.PROGRAM_ID)
doReturn(testNetworkParameters()).whenever(services).networkParameters
fun makeDummyTx(): SignedTransaction {
val wtx = DummyContract.generateInitial(1, DUMMY_NOTARY, MINI_CORP.ref(1))
.toWireTransaction(services)

View File

@ -15,6 +15,14 @@ configurations {
smokeTestRuntime.extendsFrom runtime
}
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
sourceSets {
integrationTest {
kotlin {

View File

@ -13,7 +13,7 @@ import net.corda.core.utilities.*
import net.corda.node.services.messaging.RPCServerConfiguration
import net.corda.nodeapi.RPCApi
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.internal.*
import net.corda.testing.internal.testThreadFactory
import net.corda.testing.node.internal.*
import org.apache.activemq.artemis.api.core.SimpleString
import org.junit.After
@ -30,6 +30,7 @@ import java.util.concurrent.Executors
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicLong
class RPCStabilityTests {
@Rule
@ -127,7 +128,7 @@ class RPCStabilityTests {
rpcDriver {
fun startAndCloseServer(broker: RpcBrokerHandle) {
startRpcServerWithBrokerRunning(
configuration = RPCServerConfiguration.default.copy(consumerPoolSize = 1, producerPoolBound = 1),
configuration = RPCServerConfiguration.default,
ops = DummyOps,
brokerHandle = broker
).rpcServer.close()
@ -148,7 +149,7 @@ class RPCStabilityTests {
@Test
fun `rpc client close doesnt leak broker resources`() {
rpcDriver {
val server = startRpcServer(configuration = RPCServerConfiguration.default.copy(consumerPoolSize = 1, producerPoolBound = 1), ops = DummyOps).get()
val server = startRpcServer(configuration = RPCServerConfiguration.default, ops = DummyOps).get()
RPCClient<RPCOps>(server.broker.hostAndPort!!).start(RPCOps::class.java, rpcTestUser.username, rpcTestUser.password).close()
val initial = server.broker.getStats()
repeat(100) {
@ -337,11 +338,12 @@ class RPCStabilityTests {
val request = RPCApi.ClientToServer.RpcRequest(
clientAddress = SimpleString(myQueue),
methodName = SlowConsumerRPCOps::streamAtInterval.name,
serialisedArguments = listOf(10.millis, 123456).serialize(context = SerializationDefaults.RPC_SERVER_CONTEXT).bytes,
serialisedArguments = listOf(10.millis, 123456).serialize(context = SerializationDefaults.RPC_SERVER_CONTEXT),
replyId = Trace.InvocationId.newInstance(),
sessionId = Trace.SessionId.newInstance()
)
request.writeToClientMessage(message)
message.putLongProperty(RPCApi.DEDUPLICATION_SEQUENCE_NUMBER_FIELD_NAME, 0)
producer.send(message)
session.commit()
@ -350,6 +352,79 @@ class RPCStabilityTests {
}
}
@Test
fun `deduplication in the server`() {
rpcDriver {
val server = startRpcServer(ops = SlowConsumerRPCOpsImpl()).getOrThrow()
// Construct an RPC client session manually
val myQueue = "${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.test.${random63BitValue()}"
val session = startArtemisSession(server.broker.hostAndPort!!)
session.createTemporaryQueue(myQueue, myQueue)
val consumer = session.createConsumer(myQueue, null, -1, -1, false)
val replies = ArrayList<Any>()
consumer.setMessageHandler {
replies.add(it)
it.acknowledge()
}
val producer = session.createProducer(RPCApi.RPC_SERVER_QUEUE_NAME)
session.start()
pollUntilClientNumber(server, 1)
val message = session.createMessage(false)
val request = RPCApi.ClientToServer.RpcRequest(
clientAddress = SimpleString(myQueue),
methodName = DummyOps::protocolVersion.name,
serialisedArguments = emptyList<Any>().serialize(context = SerializationDefaults.RPC_SERVER_CONTEXT),
replyId = Trace.InvocationId.newInstance(),
sessionId = Trace.SessionId.newInstance()
)
request.writeToClientMessage(message)
message.putLongProperty(RPCApi.DEDUPLICATION_SEQUENCE_NUMBER_FIELD_NAME, 0)
producer.send(message)
// duplicate the message
producer.send(message)
pollUntilTrue("Number of replies is 1") {
replies.size == 1
}.getOrThrow()
}
}
@Test
fun `deduplication in the client`() {
rpcDriver {
val broker = startRpcBroker().getOrThrow()
// Construct an RPC server session manually
val session = startArtemisSession(broker.hostAndPort!!)
val consumer = session.createConsumer(RPCApi.RPC_SERVER_QUEUE_NAME)
val producer = session.createProducer()
val dedupeId = AtomicLong(0)
consumer.setMessageHandler {
it.acknowledge()
val request = RPCApi.ClientToServer.fromClientMessage(it)
when (request) {
is RPCApi.ClientToServer.RpcRequest -> {
val reply = RPCApi.ServerToClient.RpcReply(request.replyId, Try.Success(0), "server")
val message = session.createMessage(false)
reply.writeToClientMessage(SerializationDefaults.RPC_SERVER_CONTEXT, message)
message.putLongProperty(RPCApi.DEDUPLICATION_SEQUENCE_NUMBER_FIELD_NAME, dedupeId.getAndIncrement())
producer.send(request.clientAddress, message)
// duplicate the reply
producer.send(request.clientAddress, message)
}
is RPCApi.ClientToServer.ObservablesClosed -> {
}
}
}
session.start()
startRpcClient<RPCOps>(broker.hostAndPort!!).getOrThrow()
}
}
}
fun RPCDriverDSL.pollUntilClientNumber(server: RpcServerHandle, expected: Int) {

View File

@ -44,8 +44,6 @@ data class RPCClientConfiguration(
val reapInterval: Duration,
/** The number of threads to use for observations (for executing [Observable.onNext]) */
val observationExecutorPoolSize: Int,
/** The maximum number of producers to create to handle outgoing messages */
val producerPoolBound: Int,
/**
* Determines the concurrency level of the Observable Cache. This is exposed because it implicitly determines
* the limit on the number of leaked observables reaped because of garbage collection per reaping.
@ -58,9 +56,12 @@ data class RPCClientConfiguration(
val connectionRetryIntervalMultiplier: Double,
/** Maximum retry interval */
val connectionMaxRetryInterval: Duration,
/** Maximum reconnect attempts on failover */
val maxReconnectAttempts: Int,
/** Maximum file size */
val maxFileSize: Int
val maxFileSize: Int,
/** The cache expiry of a deduplication watermark per client. */
val deduplicationCacheExpiry: Duration
) {
companion object {
val unlimitedReconnectAttempts = -1
@ -70,14 +71,14 @@ data class RPCClientConfiguration(
trackRpcCallSites = false,
reapInterval = 1.seconds,
observationExecutorPoolSize = 4,
producerPoolBound = 1,
cacheConcurrencyLevel = 8,
connectionRetryInterval = 5.seconds,
connectionRetryIntervalMultiplier = 1.5,
connectionMaxRetryInterval = 3.minutes,
maxReconnectAttempts = unlimitedReconnectAttempts,
/** 10 MiB maximum allowed file size for attachments, including message headers. TODO: acquire this value from Network Map when supported. */
maxFileSize = 10485760
maxFileSize = 10485760,
deduplicationCacheExpiry = 1.days
)
}
}

View File

@ -15,7 +15,6 @@ import net.corda.client.rpc.RPCSinceVersion
import net.corda.core.context.Actor
import net.corda.core.context.Trace
import net.corda.core.context.Trace.InvocationId
import net.corda.core.internal.LazyPool
import net.corda.core.internal.LazyStickyPool
import net.corda.core.internal.LifeCycle
import net.corda.core.internal.ThreadBox
@ -26,14 +25,12 @@ import net.corda.core.utilities.Try
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import net.corda.core.utilities.getOrThrow
import net.corda.nodeapi.ArtemisConsumer
import net.corda.nodeapi.ArtemisProducer
import net.corda.nodeapi.RPCApi
import net.corda.nodeapi.internal.DeduplicationChecker
import org.apache.activemq.artemis.api.core.RoutingType
import org.apache.activemq.artemis.api.core.SimpleString
import org.apache.activemq.artemis.api.core.client.*
import org.apache.activemq.artemis.api.core.client.ActiveMQClient.DEFAULT_ACK_BATCH_SIZE
import org.apache.activemq.artemis.api.core.client.ClientMessage
import org.apache.activemq.artemis.api.core.client.ServerLocator
import rx.Notification
import rx.Observable
import rx.subjects.UnicastSubject
@ -43,6 +40,7 @@ import java.time.Instant
import java.util.*
import java.util.concurrent.*
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicLong
import kotlin.reflect.jvm.javaMethod
/**
@ -111,6 +109,8 @@ class RPCClientProxyHandler(
// Used for reaping
private var reaperExecutor: ScheduledExecutorService? = null
// Used for sending
private var sendExecutor: ExecutorService? = null
// A sticky pool for running Observable.onNext()s. We need the stickiness to preserve the observation ordering.
private val observationExecutorThreadFactory = ThreadFactoryBuilder().setNameFormat("rpc-client-observation-pool-%d").setDaemon(true).build()
@ -161,22 +161,14 @@ class RPCClientProxyHandler(
build()
}
// We cannot pool consumers as we need to preserve the original muxed message order.
// TODO We may need to pool these somehow anyway, otherwise if the server sends many big messages in parallel a
// single consumer may be starved for flow control credits. Recheck this once Artemis's large message streaming is
// integrated properly.
private var sessionAndConsumer: ArtemisConsumer? = null
// Pool producers to reduce contention on the client side.
private val sessionAndProducerPool = LazyPool(bound = rpcConfiguration.producerPoolBound) {
// Note how we create new sessions *and* session factories per producer.
// We cannot simply pool producers on one session because sessions are single threaded.
// We cannot simply pool sessions on one session factory because flow control credits are tied to factories, so
// sessions tend to starve each other when used concurrently.
val sessionFactory = serverLocator.createSessionFactory()
val session = sessionFactory.createSession(rpcUsername, rpcPassword, false, true, true, false, DEFAULT_ACK_BATCH_SIZE)
session.start()
ArtemisProducer(sessionFactory, session, session.createProducer(RPCApi.RPC_SERVER_QUEUE_NAME))
}
private var sessionFactory: ClientSessionFactory? = null
private var producerSession: ClientSession? = null
private var consumerSession: ClientSession? = null
private var rpcProducer: ClientProducer? = null
private var rpcConsumer: ClientConsumer? = null
private val deduplicationChecker = DeduplicationChecker(rpcConfiguration.deduplicationCacheExpiry)
private val deduplicationSequenceNumber = AtomicLong(0)
/**
* Start the client. This creates the per-client queue, starts the consumer session and the reaper.
@ -187,22 +179,25 @@ class RPCClientProxyHandler(
1,
ThreadFactoryBuilder().setNameFormat("rpc-client-reaper-%d").setDaemon(true).build()
)
sendExecutor = Executors.newSingleThreadExecutor(
ThreadFactoryBuilder().setNameFormat("rpc-client-sender-%d").build()
)
reaperScheduledFuture = reaperExecutor!!.scheduleAtFixedRate(
this::reapObservablesAndNotify,
rpcConfiguration.reapInterval.toMillis(),
rpcConfiguration.reapInterval.toMillis(),
TimeUnit.MILLISECONDS
)
sessionAndProducerPool.run {
it.session.createTemporaryQueue(clientAddress, RoutingType.ANYCAST, clientAddress)
}
val sessionFactory = serverLocator.createSessionFactory()
val session = sessionFactory.createSession(rpcUsername, rpcPassword, false, true, true, false, DEFAULT_ACK_BATCH_SIZE)
val consumer = session.createConsumer(clientAddress)
consumer.setMessageHandler(this@RPCClientProxyHandler::artemisMessageHandler)
sessionAndConsumer = ArtemisConsumer(sessionFactory, session, consumer)
sessionFactory = serverLocator.createSessionFactory()
producerSession = sessionFactory!!.createSession(rpcUsername, rpcPassword, false, true, true, false, DEFAULT_ACK_BATCH_SIZE)
rpcProducer = producerSession!!.createProducer(RPCApi.RPC_SERVER_QUEUE_NAME)
consumerSession = sessionFactory!!.createSession(rpcUsername, rpcPassword, false, true, true, false, DEFAULT_ACK_BATCH_SIZE)
consumerSession!!.createTemporaryQueue(clientAddress, RoutingType.ANYCAST, clientAddress)
rpcConsumer = consumerSession!!.createConsumer(clientAddress)
rpcConsumer!!.setMessageHandler(this::artemisMessageHandler)
lifeCycle.transition(State.UNSTARTED, State.SERVER_VERSION_NOT_SET)
session.start()
consumerSession!!.start()
producerSession!!.start()
}
// This is the general function that transforms a client side RPC to internal Artemis messages.
@ -212,7 +207,7 @@ class RPCClientProxyHandler(
if (method == toStringMethod) {
return "Client RPC proxy for $rpcOpsClass"
}
if (sessionAndConsumer!!.session.isClosed) {
if (consumerSession!!.isClosed) {
throw RPCException("RPC Proxy is closed")
}
@ -220,23 +215,20 @@ class RPCClientProxyHandler(
callSiteMap?.set(replyId, Throwable("<Call site of root RPC '${method.name}'>"))
try {
val serialisedArguments = (arguments?.toList() ?: emptyList()).serialize(context = serializationContextWithObservableContext)
val request = RPCApi.ClientToServer.RpcRequest(clientAddress, method.name, serialisedArguments.bytes, replyId, sessionId, externalTrace, impersonatedActor)
val request = RPCApi.ClientToServer.RpcRequest(
clientAddress,
method.name,
serialisedArguments,
replyId,
sessionId,
externalTrace,
impersonatedActor
)
val replyFuture = SettableFuture.create<Any>()
sessionAndProducerPool.run {
val message = it.session.createMessage(false)
request.writeToClientMessage(message)
log.debug {
val argumentsString = arguments?.joinToString() ?: ""
"-> RPC(${replyId.value}) -> ${method.name}($argumentsString): ${method.returnType}"
}
require(rpcReplyMap.put(replyId, replyFuture) == null) {
"Generated several RPC requests with same ID $replyId"
}
it.producer.send(message)
it.session.commit()
require(rpcReplyMap.put(replyId, replyFuture) == null) {
"Generated several RPC requests with same ID $replyId"
}
sendMessage(request)
return replyFuture.getOrThrow()
} catch (e: RuntimeException) {
// Already an unchecked exception, so just rethrow it
@ -249,9 +241,24 @@ class RPCClientProxyHandler(
}
}
private fun sendMessage(message: RPCApi.ClientToServer) {
val artemisMessage = producerSession!!.createMessage(false)
message.writeToClientMessage(artemisMessage)
sendExecutor!!.submit {
artemisMessage.putLongProperty(RPCApi.DEDUPLICATION_SEQUENCE_NUMBER_FIELD_NAME, deduplicationSequenceNumber.getAndIncrement())
log.debug { "-> RPC -> $message" }
rpcProducer!!.send(artemisMessage)
}
}
// The handler for Artemis messages.
private fun artemisMessageHandler(message: ClientMessage) {
val serverToClient = RPCApi.ServerToClient.fromClientMessage(serializationContextWithObservableContext, message)
val deduplicationSequenceNumber = message.getLongProperty(RPCApi.DEDUPLICATION_SEQUENCE_NUMBER_FIELD_NAME)
if (deduplicationChecker.checkDuplicateMessageId(serverToClient.deduplicationIdentity, deduplicationSequenceNumber)) {
log.info("Message duplication detected, discarding message")
return
}
log.debug { "Got message from RPC server $serverToClient" }
when (serverToClient) {
is RPCApi.ServerToClient.RpcReply -> {
@ -325,14 +332,12 @@ class RPCClientProxyHandler(
* @param notify whether to notify observables or not.
*/
private fun close(notify: Boolean = true) {
sessionAndConsumer?.sessionFactory?.close()
sessionFactory?.close()
reaperScheduledFuture?.cancel(false)
observableContext.observableMap.invalidateAll()
reapObservables(notify)
reaperExecutor?.shutdownNow()
sessionAndProducerPool.close().forEach {
it.sessionFactory.close()
}
sendExecutor?.shutdownNow()
// Note the ordering is important, we shut down the consumer *before* the observation executor, otherwise we may
// leak borrowed executors.
val observationExecutors = observationExecutorPool.close()
@ -385,11 +390,7 @@ class RPCClientProxyHandler(
}
if (observableIds != null) {
log.debug { "Reaping ${observableIds.size} observables" }
sessionAndProducerPool.run {
val message = it.session.createMessage(false)
RPCApi.ClientToServer.ObservablesClosed(observableIds).writeToClientMessage(message)
it.producer.send(message)
}
sendMessage(RPCApi.ClientToServer.ObservablesClosed(observableIds))
}
}
}

View File

@ -3,6 +3,7 @@ package net.corda.client.rpc
import com.google.common.base.Stopwatch
import net.corda.client.rpc.internal.RPCClientConfiguration
import net.corda.core.messaging.RPCOps
import net.corda.core.utilities.days
import net.corda.core.utilities.minutes
import net.corda.core.utilities.seconds
import net.corda.node.services.messaging.RPCServerConfiguration
@ -87,13 +88,10 @@ class RPCPerformanceTests : AbstractRPCTest() {
val proxy = testProxy(
RPCClientConfiguration.default.copy(
cacheConcurrencyLevel = 16,
observationExecutorPoolSize = 2,
producerPoolBound = 2
observationExecutorPoolSize = 2
),
RPCServerConfiguration.default.copy(
rpcThreadPoolSize = 8,
consumerPoolSize = 2,
producerPoolBound = 8
rpcThreadPoolSize = 8
)
)
@ -130,13 +128,10 @@ class RPCPerformanceTests : AbstractRPCTest() {
val proxy = testProxy(
RPCClientConfiguration.default.copy(
reapInterval = 1.seconds,
cacheConcurrencyLevel = 16,
producerPoolBound = 8
cacheConcurrencyLevel = 16
),
RPCServerConfiguration.default.copy(
rpcThreadPoolSize = 8,
consumerPoolSize = 1,
producerPoolBound = 8
rpcThreadPoolSize = 8
)
)
startPublishingFixedRateInjector(
@ -165,9 +160,7 @@ class RPCPerformanceTests : AbstractRPCTest() {
rpcDriver {
val proxy = testProxy(
RPCClientConfiguration.default,
RPCServerConfiguration.default.copy(
consumerPoolSize = 1
)
RPCServerConfiguration.default
)
val numberOfMessages = 1000
val bigSize = 10_000_000

View File

@ -14,7 +14,7 @@ dependencies {
compile project(':core')
// Quasar, for suspendable fibres.
compileOnly "co.paralleluniverse:quasar-core:$quasar_version:jdk8"
compileOnly "$quasar_group:quasar-core:$quasar_version:jdk8"
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
testCompile "junit:junit:$junit_version"

View File

@ -53,7 +53,7 @@ object IdentitySyncFlow {
}
private fun extractOurConfidentialIdentities(): Map<AbstractParty, PartyAndCertificate?> {
val states: List<ContractState> = (tx.inputs.map { serviceHub.loadState(it) }.requireNoNulls().map { it.data } + tx.outputs.map { it.data })
val states: List<ContractState> = (serviceHub.loadStates(tx.inputs.toSet()).map { it.state.data } + tx.outputs.map { it.data })
val identities: Set<AbstractParty> = states.flatMap(ContractState::participants).toSet()
// Filter participants down to the set of those not in the network map (are not well known)
val confidentialIdentities = identities

View File

@ -18,7 +18,7 @@ import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.CHARLIE_NAME
import net.corda.testing.core.singleIdentity
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.startFlow
import org.junit.After
import org.junit.Before
@ -28,15 +28,15 @@ import kotlin.test.assertNotNull
import kotlin.test.assertNull
class IdentitySyncFlowTests {
private lateinit var mockNet: MockNetwork
private lateinit var mockNet: InternalMockNetwork
@Before
fun before() {
// We run this in parallel threads to help catch any race conditions that may exist.
mockNet = MockNetwork(
mockNet = InternalMockNetwork(
cordappPackages = listOf("net.corda.finance.contracts.asset"),
networkSendManuallyPumped = false,
threadPerNode = true,
cordappPackages = listOf("net.corda.finance.contracts.asset")
threadPerNode = true
)
}

View File

@ -3,19 +3,19 @@ package net.corda.confidential
import net.corda.core.identity.*
import net.corda.core.utilities.getOrThrow
import net.corda.testing.core.*
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.internal.InternalMockNetwork
import org.junit.Before
import net.corda.testing.node.startFlow
import org.junit.Test
import kotlin.test.*
class SwapIdentitiesFlowTests {
private lateinit var mockNet: MockNetwork
private lateinit var mockNet: InternalMockNetwork
@Before
fun setup() {
// We run this in parallel threads to help catch any race conditions that may exist.
mockNet = MockNetwork(emptyList(), networkSendManuallyPumped = false, threadPerNode = true)
mockNet = InternalMockNetwork(emptyList(), networkSendManuallyPumped = false, threadPerNode = true)
}
@Test

View File

@ -1,4 +1,4 @@
gradlePluginsVersion=3.0.5
gradlePluginsVersion=4.0.2
kotlinVersion=1.2.20
platformVersion=2
guavaVersion=21.0
@ -6,4 +6,4 @@ bouncycastleVersion=1.57
typesafeConfigVersion=1.3.1
jsr305Version=3.0.2
artifactoryPluginVersion=4.4.18
snakeYamlVersion=1.19
snakeYamlVersion=1.19

View File

@ -75,7 +75,7 @@ dependencies {
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
// Quasar, for suspendable fibres.
compileOnly "co.paralleluniverse:quasar-core:$quasar_version:jdk8"
compileOnly "$quasar_group:quasar-core:$quasar_version:jdk8"
// Thread safety annotations
compile "com.google.code.findbugs:jsr305:$jsr305_version"

View File

@ -1,10 +1,14 @@
package net.corda.core.contracts
import net.corda.core.DoNotImplement
import net.corda.core.contracts.AlwaysAcceptAttachmentConstraint.isSatisfiedBy
import net.corda.core.crypto.SecureHash
import net.corda.core.internal.AttachmentWithContext
import net.corda.core.serialization.CordaSerializable
/** Constrain which contract-code-containing attachment can be used with a [ContractState]. */
@CordaSerializable
@DoNotImplement
interface AttachmentConstraint {
/** Returns whether the given contract attachment can be used with the [ContractState] associated with this constraint object. */
fun isSatisfiedBy(attachment: Attachment): Boolean
@ -20,6 +24,20 @@ data class HashAttachmentConstraint(val attachmentId: SecureHash) : AttachmentCo
override fun isSatisfiedBy(attachment: Attachment) = attachment.id == attachmentId
}
/**
* An [AttachmentConstraint] that verifies that the hash of the attachment is in the network parameters whitelist.
* See: [net.corda.core.node.NetworkParameters.whitelistedContractImplementations]
* It allows for centralized control over the cordapps that can be used.
*/
object WhitelistedByZoneAttachmentConstraint : AttachmentConstraint {
override fun isSatisfiedBy(attachment: Attachment): Boolean {
return if (attachment is AttachmentWithContext) {
val whitelist = attachment.whitelistedContractImplementations ?: throw IllegalStateException("Unable to verify WhitelistedByZoneAttachmentConstraint - whitelist not specified")
attachment.id in (whitelist[attachment.stateContract] ?: emptyList())
} else false
}
}
/**
* This [AttachmentConstraint] is a convenience class that will be automatically resolved to a [HashAttachmentConstraint].
* The resolution occurs in [TransactionBuilder.toWireTransaction] and uses the [TransactionState.contract] value

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
*
* @property attachment The attachment representing the contract JAR
* @property contract The contract name contained within the JAR
* @property contract The contract name contained within the JAR. A Contract attachment has to contain at least 1 contract.
* @property additionalContracts Additional contract names contained within the JAR.
*/
@CordaSerializable
class ContractAttachment(val attachment: Attachment, val contract: ContractClassName) : Attachment by attachment
class ContractAttachment @JvmOverloads constructor (val attachment: Attachment, val contract: ContractClassName, val additionalContracts: Set<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)
: TransactionVerificationException(txId, "Contract constraints failed, could not find attachment for: $contractClass", null)
class ConflictingAttachmentsRejection(txId: SecureHash, contractClass: String)
: TransactionVerificationException(txId, "Contract constraints failed for: $contractClass, because multiple attachments providing this contract were attached.", null)
class ContractCreationError(txId: SecureHash, contractClass: String, cause: Throwable)
: TransactionVerificationException(txId, "Contract verification failed: ${cause.message}, could not create contract class: $contractClass", cause)

View File

@ -0,0 +1,6 @@
package net.corda.core.cordapp
/**
* Thrown if an exception occurs in accessing or parsing cordapp configuration
*/
class CordappConfigException(msg: String, e: Throwable) : Exception(msg, e)

View File

@ -0,0 +1,70 @@
package net.corda.core.cordapp
import net.corda.core.DoNotImplement
/**
* Provides access to cordapp configuration independent of the configuration provider.
*/
@DoNotImplement
interface CordappConfig {
/**
* Check if a config exists at path
*/
fun exists(path: String): Boolean
/**
* Get the value of the configuration at "path".
*
* @throws CordappConfigException If the configuration fails to load, parse, or find a value.
*/
fun get(path: String): Any
/**
* Get the int value of the configuration at "path".
*
* @throws CordappConfigException If the configuration fails to load, parse, or find a value.
*/
fun getInt(path: String): Int
/**
* Get the long value of the configuration at "path".
*
* @throws CordappConfigException If the configuration fails to load, parse, or find a value.
*/
fun getLong(path: String): Long
/**
* Get the float value of the configuration at "path".
*
* @throws CordappConfigException If the configuration fails to load, parse, or find a value.
*/
fun getFloat(path: String): Float
/**
* Get the double value of the configuration at "path".
*
* @throws CordappConfigException If the configuration fails to load, parse, or find a value.
*/
fun getDouble(path: String): Double
/**
* Get the number value of the configuration at "path".
*
* @throws CordappConfigException If the configuration fails to load, parse, or find a value.
*/
fun getNumber(path: String): Number
/**
* Get the string value of the configuration at "path".
*
* @throws CordappConfigException If the configuration fails to load, parse, or find a value.
*/
fun getString(path: String): String
/**
* Get the boolean value of the configuration at "path".
*
* @throws CordappConfigException If the configuration fails to load, parse, or find a value.
*/
fun getBoolean(path: String): Boolean
}

View File

@ -2,8 +2,6 @@ package net.corda.core.cordapp
import net.corda.core.crypto.SecureHash
// TODO: Add per app config
/**
* An app context provides information about where an app was loaded from, access to its classloader,
* and (in the included [Cordapp] object) lists of annotated classes discovered via scanning the JAR.
@ -15,5 +13,11 @@ import net.corda.core.crypto.SecureHash
* @property attachmentId For CorDapps containing [Contract] or [UpgradedContract] implementations this will be populated
* with the attachment containing those class files
* @property classLoader the classloader used to load this cordapp's classes
* @property config Configuration for this CorDapp
*/
class CordappContext(val cordapp: Cordapp, val attachmentId: SecureHash?, val classLoader: ClassLoader)
class CordappContext internal constructor(
val cordapp: Cordapp,
val attachmentId: SecureHash?,
val classLoader: ClassLoader,
val config: CordappConfig
)

View File

@ -232,7 +232,7 @@ abstract class FlowLogic<out T> {
* @returns a [Map] containing the objects received, wrapped in an [UntrustworthyData], by the [FlowSession]s who sent them.
*/
@Suspendable
open fun receiveAll(sessions: Map<FlowSession, Class<out Any>>): Map<FlowSession, UntrustworthyData<Any>> {
open fun receiveAllMap(sessions: Map<FlowSession, Class<out Any>>): Map<FlowSession, UntrustworthyData<Any>> {
return stateMachine.receiveAll(sessions, this)
}
@ -250,7 +250,7 @@ abstract class FlowLogic<out T> {
@Suspendable
open fun <R : Any> receiveAll(receiveType: Class<R>, sessions: List<FlowSession>): List<UntrustworthyData<R>> {
enforceNoDuplicates(sessions)
return castMapValuesToKnownType(receiveAll(associateSessionsToReceiveType(receiveType, sessions)))
return castMapValuesToKnownType(receiveAllMap(associateSessionsToReceiveType(receiveType, sessions)))
}
/**

View File

@ -0,0 +1,101 @@
package net.corda.core.flows
import net.corda.core.contracts.StateRef
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.Party
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.serialize
import net.corda.core.transactions.CoreTransaction
import net.corda.core.transactions.SignedTransaction
import java.security.InvalidKeyException
import java.security.SignatureException
/**
* A notarisation request specifies a list of states to consume and the id of the consuming transaction. Its primary
* purpose is for notarisation traceability a signature over the notarisation request, [NotarisationRequestSignature],
* allows a notary to prove that a certain party requested the consumption of a particular state.
*
* While the signature must be retained, the notarisation request does not need to be transferred or stored anywhere - it
* can be built from a [SignedTransaction] or a [CoreTransaction]. The notary can recompute it from the committed states index.
*
* In case there is a need to prove that a party spent a particular state, the notary will:
* 1) Locate the consuming transaction id in the index, along with all other states consumed in the same transaction.
* 2) Build a [NotarisationRequest].
* 3) Locate the [NotarisationRequestSignature] for the transaction id. The signature will contain the signing public key.
* 4) Demonstrate the signature verifies against the serialized request. The provided states are always sorted internally,
* to ensure the serialization does not get affected by the order.
*/
@CordaSerializable
class NotarisationRequest(statesToConsume: List<StateRef>, val transactionId: SecureHash) {
companion object {
/** Sorts in ascending order first by transaction hash, then by output index. */
private val stateRefComparator = compareBy<StateRef>({ it.txhash }, { it.index })
}
private val _statesToConsumeSorted = statesToConsume.sortedWith(stateRefComparator)
/** States this request specifies to be consumed. Sorted to ensure the serialized form does not get affected by the state order. */
val statesToConsume: List<StateRef> get() = _statesToConsumeSorted // Getter required for AMQP serialization
/** Verifies the signature against this notarisation request. Checks that the signature is issued by the right party. */
fun verifySignature(requestSignature: NotarisationRequestSignature, intendedSigner: Party) {
val signature = requestSignature.digitalSignature
if (intendedSigner.owningKey != signature.by) {
val errorMessage = "Expected a signature by ${intendedSigner.owningKey}, but received by ${signature.by}}"
throw NotaryException(NotaryError.RequestSignatureInvalid(IllegalArgumentException(errorMessage)))
}
// TODO: if requestSignature was generated over an old version of NotarisationRequest, we need to be able to
// reserialize it in that version to get the exact same bytes. Modify the serialization logic once that's
// available.
val expectedSignedBytes = this.serialize().bytes
verifyCorrectBytesSigned(signature, expectedSignedBytes)
}
private fun verifyCorrectBytesSigned(signature: DigitalSignature.WithKey, bytes: ByteArray) {
try {
signature.verify(bytes)
} catch (e: Exception) {
when (e) {
is InvalidKeyException, is SignatureException -> {
val error = NotaryError.RequestSignatureInvalid(e)
throw NotaryException(error)
}
else -> throw e
}
}
}
}
/**
* A wrapper around a digital signature used for notarisation requests.
*
* The [platformVersion] is required so the notary can verify the signature against the right version of serialized
* bytes of the [NotarisationRequest]. Otherwise, the request may be rejected.
*/
@CordaSerializable
data class NotarisationRequestSignature(val digitalSignature: DigitalSignature.WithKey, val platformVersion: Int)
/**
* Container for the transaction and notarisation request signature.
* This is the payload that gets sent by a client to a notary service for committing the input states of the [transaction].
*/
@CordaSerializable
data class NotarisationPayload(val transaction: Any, val requestSignature: NotarisationRequestSignature) {
init {
require(transaction is SignedTransaction || transaction is CoreTransaction) {
"Unsupported transaction type in the notarisation payload: ${transaction.javaClass.simpleName}"
}
}
/**
* A helper for automatically casting the underlying [transaction] payload to a [SignedTransaction].
* Should only be used by validating notaries.
*/
val signedTransaction get() = transaction as SignedTransaction
/**
* A helper for automatically casting the underlying [transaction] payload to a [CoreTransaction].
* Should only be used by non-validating notaries.
*/
val coreTransaction get() = transaction as CoreTransaction
}

View File

@ -9,10 +9,12 @@ import net.corda.core.crypto.TransactionSignature
import net.corda.core.crypto.keys
import net.corda.core.identity.Party
import net.corda.core.internal.FetchDataFlow
import net.corda.core.internal.generateSignature
import net.corda.core.node.services.NotaryService
import net.corda.core.node.services.TrustedAuthorityNotaryService
import net.corda.core.node.services.UniquenessProvider
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.CoreTransaction
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.UntrustworthyData
@ -61,7 +63,7 @@ class NotaryFlow {
protected fun checkTransaction(): Party {
val notaryParty = stx.notary ?: throw IllegalStateException("Transaction does not specify a Notary")
check(serviceHub.networkMapCache.isNotary(notaryParty)) { "$notaryParty is not a notary on the network" }
check(stx.inputs.all { stateRef -> serviceHub.loadState(stateRef).notary == notaryParty }) {
check(serviceHub.loadStates(stx.inputs.toSet()).all { it.state.notary == notaryParty }) {
"Input states must have the same Notary"
}
@ -73,15 +75,17 @@ class NotaryFlow {
return notaryParty
}
/** Notarises the transaction with the [notaryParty], obtains the notary's signature(s). */
@Throws(NotaryException::class)
@Suspendable
protected fun notarise(notaryParty: Party): UntrustworthyData<List<TransactionSignature>> {
return try {
val session = initiateFlow(notaryParty)
val requestSignature = NotarisationRequest(stx.inputs, stx.id).generateSignature(serviceHub)
if (serviceHub.networkMapCache.isValidatingNotary(notaryParty)) {
sendAndReceiveValidating(session)
sendAndReceiveValidating(session, requestSignature)
} else {
sendAndReceiveNonValidating(notaryParty, session)
sendAndReceiveNonValidating(notaryParty, session, requestSignature)
}
} catch (e: NotaryException) {
if (e.error is NotaryError.Conflict) {
@ -92,21 +96,23 @@ class NotaryFlow {
}
@Suspendable
protected open fun sendAndReceiveValidating(session: FlowSession): UntrustworthyData<List<TransactionSignature>> {
subFlow(SendTransactionWithRetry(session, stx))
private fun sendAndReceiveValidating(session: FlowSession, signature: NotarisationRequestSignature): UntrustworthyData<List<TransactionSignature>> {
val payload = NotarisationPayload(stx, signature)
subFlow(NotarySendTransactionFlow(session, payload))
return session.receive()
}
@Suspendable
protected open fun sendAndReceiveNonValidating(notaryParty: Party, session: FlowSession): UntrustworthyData<List<TransactionSignature>> {
val tx: Any = if (stx.isNotaryChangeTransaction()) {
private fun sendAndReceiveNonValidating(notaryParty: Party, session: FlowSession, signature: NotarisationRequestSignature): UntrustworthyData<List<TransactionSignature>> {
val tx: CoreTransaction = if (stx.isNotaryChangeTransaction()) {
stx.notaryChangeTx // Notary change transactions do not support filtering
} else {
stx.buildFilteredTransaction(Predicate { it is StateRef || it is TimeWindow || it == notaryParty })
}
return session.sendAndReceiveWithRetry(tx)
return session.sendAndReceiveWithRetry(NotarisationPayload(tx, signature))
}
/** Checks that the notary's signature(s) is/are valid. */
protected fun validateResponse(response: UntrustworthyData<List<TransactionSignature>>, notaryParty: Party): List<TransactionSignature> {
return response.unwrap { signatures ->
signatures.forEach { validateSignature(it, stx.id, notaryParty) }
@ -118,16 +124,16 @@ class NotaryFlow {
check(sig.by in notaryParty.owningKey.keys) { "Invalid signer for the notary result" }
sig.verify(txId)
}
}
/**
* The [SendTransactionWithRetry] flow is equivalent to [SendTransactionFlow] but using [sendAndReceiveWithRetry]
* instead of [sendAndReceive], [SendTransactionWithRetry] is intended to be use by the notary client only.
*/
private class SendTransactionWithRetry(otherSideSession: FlowSession, stx: SignedTransaction) : SendTransactionFlow(otherSideSession, stx) {
@Suspendable
override fun sendPayloadAndReceiveDataRequest(otherSideSession: FlowSession, payload: Any): UntrustworthyData<FetchDataFlow.Request> {
return otherSideSession.sendAndReceiveWithRetry(payload)
/**
* The [NotarySendTransactionFlow] flow is similar to [SendTransactionFlow], but uses [NotarisationPayload] as the
* initial message, and retries message delivery.
*/
private class NotarySendTransactionFlow(otherSide: FlowSession, payload: NotarisationPayload) : DataVendingFlow(otherSide, payload) {
@Suspendable
override fun sendPayloadAndReceiveDataRequest(otherSideSession: FlowSession, payload: Any): UntrustworthyData<FetchDataFlow.Request> {
return otherSideSession.sendAndReceiveWithRetry(payload)
}
}
}
@ -186,10 +192,16 @@ class NotaryFlow {
*/
data class TransactionParts(val id: SecureHash, val inputs: List<StateRef>, val timestamp: TimeWindow?, val notary: Party?)
/**
* Exception thrown by the notary service if any issues are encountered while trying to commit a transaction. The
* underlying [error] specifies the cause of failure.
*/
class NotaryException(val error: NotaryError) : FlowException("Unable to notarise: $error")
/** Specifies the cause for notarisation request failure. */
@CordaSerializable
sealed class NotaryError {
/** Occurs when one or more input states of transaction with [txId] have already been consumed by another transaction. */
data class Conflict(val txId: SecureHash, val conflict: SignedData<UniquenessProvider.Conflict>) : NotaryError() {
override fun toString() = "One or more input states for transaction $txId have been used in another transaction"
}
@ -199,18 +211,27 @@ sealed class NotaryError {
override fun toString() = "Current time $currentTime is outside the time bounds specified by the transaction: $txTimeWindow"
companion object {
@JvmField @Deprecated("Here only for binary compatibility purposes, do not use.")
@JvmField
@Deprecated("Here only for binary compatibility purposes, do not use.")
val INSTANCE = TimeWindowInvalid(Instant.EPOCH, TimeWindow.fromOnly(Instant.EPOCH))
}
}
/** Occurs when the provided transaction fails to verify. */
data class TransactionInvalid(val cause: Throwable) : NotaryError() {
override fun toString() = cause.toString()
}
/** Occurs when the transaction sent for notarisation is assigned to a different notary identity. */
object WrongNotary : NotaryError()
data class General(val cause: String): NotaryError() {
override fun toString() = cause
/** Occurs when the notarisation request signature does not verify for the provided transaction. */
data class RequestSignatureInvalid(val cause: Throwable) : NotaryError() {
override fun toString() = "Request signature invalid: $cause"
}
/** Occurs when the notary service encounters an unexpected issue or becomes temporarily unavailable. */
data class General(val cause: Throwable) : NotaryError() {
override fun toString() = cause.toString()
}
}

View File

@ -28,7 +28,7 @@ open class SendTransactionFlow(otherSide: FlowSession, stx: SignedTransaction) :
*/
open class SendStateAndRefFlow(otherSideSession: FlowSession, stateAndRefs: List<StateAndRef<*>>) : DataVendingFlow(otherSideSession, stateAndRefs)
sealed class DataVendingFlow(val otherSideSession: FlowSession, val payload: Any) : FlowLogic<Void?>() {
open class DataVendingFlow(val otherSideSession: FlowSession, val payload: Any) : FlowLogic<Void?>() {
@Suspendable
protected open fun sendPayloadAndReceiveDataRequest(otherSideSession: FlowSession, payload: Any) = otherSideSession.sendAndReceive<FetchDataFlow.Request>(payload)

View File

@ -13,6 +13,13 @@ import java.security.CodeSigner
import java.security.cert.X509Certificate
import java.util.jar.JarInputStream
// Possible attachment uploaders
const val DEPLOYED_CORDAPP_UPLOADER = "app"
const val RPC_UPLOADER = "rpc"
const val TEST_UPLOADER = "test"
const val P2P_UPLOADER = "p2p"
const val UNKNOWN_UPLOADER = "unknown"
abstract class AbstractAttachment(dataLoader: () -> ByteArray) : Attachment {
companion object {
fun SerializeAsTokenContext.attachmentDataLoader(id: SecureHash): () -> ByteArray {

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

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

View File

@ -1,14 +0,0 @@
package net.corda.core.internal
import net.corda.core.node.NetworkParameters
// TODO: This will cause problems when we run tests in parallel, make each node have its own properties.
object GlobalProperties {
private var _networkParameters: NetworkParameters? = null
var networkParameters: NetworkParameters
get() = checkNotNull(_networkParameters) { "Property 'networkParameters' has not been initialised." }
set(value) {
_networkParameters = value
}
}

View File

@ -2,9 +2,16 @@
package net.corda.core.internal
import net.corda.core.cordapp.Cordapp
import net.corda.core.cordapp.CordappConfig
import net.corda.core.cordapp.CordappContext
import net.corda.core.cordapp.CordappProvider
import net.corda.core.crypto.*
import net.corda.core.flows.NotarisationRequest
import net.corda.core.flows.NotarisationRequestSignature
import net.corda.core.flows.NotaryFlow
import net.corda.core.identity.CordaX500Name
import net.corda.core.node.ServiceHub
import net.corda.core.node.ServicesForResolution
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializedBytes
@ -140,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.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 String.abbreviate(maxWidth: Int): String = if (length <= maxWidth) this else take(maxWidth - 1) + ""
@ -297,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 }
/** Provide access to internal method for AttachmentClassLoaderTests */
fun TransactionBuilder.toWireTransaction(cordappProvider: CordappProvider, serializationContext: SerializationContext): WireTransaction {
return toWireTransactionWithContext(cordappProvider, serializationContext)
fun TransactionBuilder.toWireTransaction(services: ServicesForResolution, serializationContext: SerializationContext): WireTransaction {
return toWireTransactionWithContext(services, serializationContext)
}
/** Provide access to internal method for AttachmentClassLoaderTests */
@ -370,8 +379,27 @@ inline fun <T : Any> SerializedBytes<T>.sign(signer: (SerializedBytes<T>) -> Dig
return SignedData(this, signer(this))
}
inline fun <T : Any> SerializedBytes<T>.sign(keyPair: KeyPair): SignedData<T> {
return SignedData(this, keyPair.sign(this.bytes))
fun <T : Any> SerializedBytes<T>.sign(keyPair: KeyPair): SignedData<T> = SignedData(this, keyPair.sign(this.bytes))
fun ByteBuffer.copyBytes(): ByteArray = ByteArray(remaining()).also { get(it) }
fun createCordappContext(cordapp: Cordapp, attachmentId: SecureHash?, classLoader: ClassLoader, config: CordappConfig): CordappContext {
return CordappContext(cordapp, attachmentId, classLoader, config)
}
fun ByteBuffer.copyBytes() = ByteArray(remaining()).also { get(it) }
/** Verifies that the correct notarisation request was signed by the counterparty. */
fun NotaryFlow.Service.validateRequest(request: NotarisationRequest, signature: NotarisationRequestSignature) {
val requestingParty = otherSideSession.counterparty
request.verifySignature(signature, requestingParty)
// TODO: persist the signature for traceability. Do we need to persist the request as well?
}
/** Creates a signature over the notarisation request using the legal identity key. */
fun NotarisationRequest.generateSignature(serviceHub: ServiceHub): NotarisationRequestSignature {
val serializedRequest = this.serialize().bytes
val signature = with(serviceHub) {
val myLegalIdentity = myInfo.legalIdentitiesAndCerts.first().owningKey
keyManagementService.sign(serializedRequest, myLegalIdentity)
}
return NotarisationRequestSignature(signature, serviceHub.myInfo.platformVersion)
}

View File

@ -1,6 +1,7 @@
package net.corda.core.node
import net.corda.core.identity.Party
import net.corda.core.node.services.AttachmentId
import net.corda.core.serialization.CordaSerializable
import java.time.Instant
@ -9,11 +10,13 @@ import java.time.Instant
* correctly interoperate with each other.
* @property minimumPlatformVersion Minimum version of Corda platform that is required for nodes in the network.
* @property notaries List of well known and trusted notary identities with information on validation type.
* @property maxMessageSize Maximum P2P message sent over the wire in bytes.
* @property maxMessageSize This is currently ignored. However, it will be wired up in a future release.
* @property maxTransactionSize Maximum permitted transaction size in bytes.
* @property modifiedTime Last modification time of network parameters set.
* @property epoch Version number of the network parameters. Starting from 1, this will always increment on each new set
* of parameters.
* @property whitelistedContractImplementations List of whitelisted jars containing contract code for each contract class.
* This will be used by [net.corda.core.contracts.WhitelistedByZoneAttachmentConstraint]. Read more about contract constraints here: <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.
// It needs separate design.
@ -24,7 +27,8 @@ data class NetworkParameters(
val maxMessageSize: Int,
val maxTransactionSize: Int,
val modifiedTime: Instant,
val epoch: Int
val epoch: Int,
val whitelistedContractImplementations: Map<String, List<AttachmentId>>
) {
init {
require(minimumPlatformVersion > 0) { "minimumPlatformVersion must be at least 1" }

View File

@ -26,7 +26,9 @@ data class NodeInfo(val addresses: List<NetworkHostAndPort>,
) {
// TODO We currently don't support multi-IP/multi-identity nodes, we only left slots in the data structures.
init {
require(legalIdentitiesAndCerts.isNotEmpty()) { "Node should have at least one legal identity" }
require(addresses.isNotEmpty()) { "Node must have at least one address" }
require(legalIdentitiesAndCerts.isNotEmpty()) { "Node must have at least one legal identity" }
require(platformVersion > 0) { "Platform version must be at least 1" }
}
@Transient private var _legalIdentities: List<Party>? = null

View File

@ -2,6 +2,7 @@ package net.corda.core.node
import net.corda.core.DoNotImplement
import net.corda.core.contracts.*
import net.corda.core.cordapp.CordappContext
import net.corda.core.cordapp.CordappProvider
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SignableData
@ -25,6 +26,10 @@ interface StateLoader {
/**
* Given a [StateRef] loads the referenced transaction and looks up the specified output [ContractState].
*
* *WARNING* Do not use this method unless you really only want a single state - any batch loading should
* go through [loadStates] as repeatedly calling [loadState] can lead to repeat deserialsiation work and
* severe performance degradation.
*
* @throws TransactionResolutionException if [stateRef] points to a non-existent transaction.
*/
@Throws(TransactionResolutionException::class)
@ -38,9 +43,7 @@ interface StateLoader {
// TODO: future implementation to use a Vault state ref -> contract state BLOB table and perform single query bulk load
// as the existing transaction store will become encrypted at some point
@Throws(TransactionResolutionException::class)
fun loadStates(stateRefs: Set<StateRef>): Set<StateAndRef<ContractState>> {
return stateRefs.map { StateAndRef(loadState(it), it) }.toSet()
}
fun loadStates(stateRefs: Set<StateRef>): Set<StateAndRef<ContractState>>
}
/**
@ -60,6 +63,9 @@ interface ServicesForResolution : StateLoader {
/** Provides access to anything relating to cordapps including contract attachment resolution and app context */
val cordappProvider: CordappProvider
/** Returns the network parameters the node is operating under. */
val networkParameters: NetworkParameters
}
/**
@ -369,4 +375,9 @@ interface ServiceHub : ServicesForResolution {
* node starts.
*/
fun registerUnloadHandler(runOnStop: () -> Unit)
/**
* See [CordappProvider.getAppContext]
*/
fun getAppContext(): CordappContext = cordappProvider.getAppContext()
}

View File

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

View File

@ -67,12 +67,22 @@ interface NetworkMapCacheBase {
fun track(): DataFeed<List<NodeInfo>, NetworkMapCache.MapChange>
/**
* Look up the node info for a legal name.
* Notice that when there are more than one node for a given name (in case of distributed services) first service node
* found will be returned.
* Return a [NodeInfo] which has the given legal name for one of its identities, or null if no such node is found.
*
* @throws IllegalArgumentException If more than one matching node is found, in the case of a distributed service identity
* (such as with a notary cluster). For such a scenerio use [getNodesByLegalName] instead.
*/
fun getNodeByLegalName(name: CordaX500Name): NodeInfo?
/**
* Return a list of [NodeInfo]s which have the given legal name for one of their identities, or an empty list if no
* such nodes are found.
*
* Normally there is at most one node for a legal name, but for distributed service identities (such as with a notary
* cluster) there can be multiple nodes sharing the same identity.
*/
fun getNodesByLegalName(name: CordaX500Name): List<NodeInfo>
/** Look up the node info for a host and port. */
fun getNodeByAddress(address: NetworkHostAndPort): NodeInfo?
@ -100,13 +110,6 @@ interface NetworkMapCacheBase {
*/
fun getNodesByLegalIdentityKey(identityKey: PublicKey): List<NodeInfo>
/**
* Look up the node information entries for a legal name.
* Note that normally there will be only one node for a legal name, but for clusters of nodes or distributed services there
* can be multiple nodes.
*/
fun getNodesByLegalName(name: CordaX500Name): List<NodeInfo>
/** Returns information about the party, which may be a specific node or a service */
fun getPartyInfo(party: Party): PartyInfo?

View File

@ -94,7 +94,7 @@ abstract class TrustedAuthorityNotaryService : NotaryService() {
}
} catch (e: Exception) {
log.error("Internal error", e)
throw NotaryException(NotaryError.General("Service unavailable, please try again later"))
throw NotaryException(NotaryError.General(Exception("Service unavailable, please try again later")))
}
}

View File

@ -1,9 +1,7 @@
package net.corda.core.node.services
import net.corda.core.DoNotImplement
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TransactionResolutionException
import net.corda.core.contracts.TransactionState
import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash
import net.corda.core.messaging.DataFeed
import net.corda.core.node.StateLoader
@ -26,6 +24,15 @@ interface TransactionStorage : StateLoader {
return stx.resolveBaseTransaction(this).outputs[stateRef.index]
}
@Throws(TransactionResolutionException::class)
override fun loadStates(stateRefs: Set<StateRef>): Set<StateAndRef<ContractState>> {
return stateRefs.groupBy { it.txhash }.flatMap {
val stx = getTransaction(it.key) ?: throw TransactionResolutionException(it.key)
val baseTx = stx.resolveBaseTransaction(this)
it.value.map { StateAndRef(baseTx.outputs[it.index], it) }
}.toSet()
}
/**
* Get a synchronous Observable of updates. When observations are pushed to the Observer, the vault will already
* incorporate the update.

View File

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

View File

@ -3,12 +3,14 @@ package net.corda.core.transactions
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.StateRef
import net.corda.core.serialization.CordaSerializable
/**
* A transaction with the minimal amount of information required to compute the unique transaction [id], and
* resolve a [FullTransaction]. This type of transaction, wrapped in [SignedTransaction], gets transferred across the
* wire and recorded to storage.
*/
@CordaSerializable
abstract class CoreTransaction : BaseTransaction() {
/** The inputs of this transaction, containing state references only **/
abstract override val inputs: List<StateRef>

View File

@ -3,9 +3,11 @@ package net.corda.core.transactions
import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.Party
import net.corda.core.internal.AttachmentWithContext
import net.corda.core.internal.UpgradeCommand
import net.corda.core.internal.castIfPossible
import net.corda.core.internal.uncheckedCast
import net.corda.core.node.NetworkParameters
import net.corda.core.serialization.CordaSerializable
import net.corda.core.utilities.Try
import java.security.PublicKey
@ -27,7 +29,7 @@ import java.util.function.Predicate
// currently sends this across to out-of-process verifiers. We'll need to change that first.
// DOCSTART 1
@CordaSerializable
data class LedgerTransaction(
data class LedgerTransaction @JvmOverloads constructor(
/** The resolved input states which will be consumed/invalidated by the execution of this transaction. */
override val inputs: List<StateAndRef<ContractState>>,
override val outputs: List<TransactionState<ContractState>>,
@ -39,7 +41,8 @@ data class LedgerTransaction(
override val id: SecureHash,
override val notary: Party?,
val timeWindow: TimeWindow?,
val privacySalt: PrivacySalt
val privacySalt: PrivacySalt,
private val networkParameters: NetworkParameters? = null
) : FullTransaction() {
//DOCEND 1
init {
@ -87,17 +90,29 @@ data class LedgerTransaction(
/**
* Verify that all contract constraints are valid for each state before running any contract code
*
* In case the transaction was created on this node then the attachments will contain the hash of the current cordapp jars.
* In case this verifies an older transaction or one originated on a different node, then this verifies that the attachments
* are valid.
*
* @throws TransactionVerificationException if the constraints fail to verify
*/
private fun verifyConstraints() {
val contractAttachments = attachments.filterIsInstance<ContractAttachment>()
(inputs.map { it.state } + outputs).forEach { state ->
// Ordering of attachments matters - if two attachments contain the same named contract then the second
// will be shadowed by the first.
val contractAttachment = contractAttachments.find { it.contract == state.contract }
?: throw TransactionVerificationException.MissingAttachmentRejection(id, state.contract)
val stateAttachments = contractAttachments.filter { state.contract in it.allContracts }
if (stateAttachments.isEmpty()) throw TransactionVerificationException.MissingAttachmentRejection(id, state.contract)
if (!state.constraint.isSatisfiedBy(contractAttachment)) {
val uniqueAttachmentsForStateContract = stateAttachments.distinctBy { it.id }
// In case multiple attachments have been added for the same contract, fail because this transaction will not be able to be verified
// because it will break the no-overlap rule that we have implemented in our Classloaders
if (uniqueAttachmentsForStateContract.size > 1) {
throw TransactionVerificationException.ConflictingAttachmentsRejection(id, state.contract)
}
val contractAttachment = uniqueAttachmentsForStateContract.first()
val constraintAttachment = AttachmentWithContext(contractAttachment, state.contract, networkParameters?.whitelistedContractImplementations)
if (!state.constraint.isSatisfiedBy(constraintAttachment)) {
throw TransactionVerificationException.ContractConstraintRejection(id, state.contract)
}
}
@ -403,5 +418,14 @@ data class LedgerTransaction(
* @throws IllegalArgumentException if no item matches the id.
*/
fun getAttachment(id: SecureHash): Attachment = attachments.first { it.id == id }
}
fun copy(inputs: List<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

@ -42,9 +42,7 @@ data class NotaryChangeWireTransaction(
fun resolve(services: ServiceHub, sigs: List<TransactionSignature>) = resolve(services as StateLoader, sigs)
fun resolve(stateLoader: StateLoader, sigs: List<TransactionSignature>): NotaryChangeLedgerTransaction {
val resolvedInputs = inputs.map { ref ->
stateLoader.loadState(ref).let { StateAndRef(it, ref) }
}
val resolvedInputs = stateLoader.loadStates(inputs.toSet()).toList()
return NotaryChangeLedgerTransaction(resolvedInputs, notary, newNotary, id, sigs)
}
}

View File

@ -8,8 +8,10 @@ import net.corda.core.crypto.SignableData
import net.corda.core.crypto.SignatureMetadata
import net.corda.core.identity.Party
import net.corda.core.internal.FlowStateMachine
import net.corda.core.node.NetworkParameters
import net.corda.core.node.ServiceHub
import net.corda.core.node.ServicesForResolution
import net.corda.core.node.services.AttachmentId
import net.corda.core.node.services.KeyManagementService
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationFactory
@ -17,6 +19,7 @@ import java.security.PublicKey
import java.time.Duration
import java.time.Instant
import java.util.*
import kotlin.collections.ArrayList
/**
* A TransactionBuilder is a transaction class that's mutable (unlike the others which are all immutable). It is
@ -42,18 +45,24 @@ open class TransactionBuilder(
) {
constructor(notary: Party) : this(notary, (Strand.currentStrand() as? FlowStateMachine<*>)?.id?.uuid ?: UUID.randomUUID())
private val inputsWithTransactionState = arrayListOf<TransactionState<ContractState>>()
/**
* Creates a copy of the builder.
*/
fun copy() = TransactionBuilder(
notary = notary,
inputs = ArrayList(inputs),
attachments = ArrayList(attachments),
outputs = ArrayList(outputs),
commands = ArrayList(commands),
window = window,
privacySalt = privacySalt
)
fun copy(): TransactionBuilder {
val t = TransactionBuilder(
notary = notary,
inputs = ArrayList(inputs),
attachments = ArrayList(attachments),
outputs = ArrayList(outputs),
commands = ArrayList(commands),
window = window,
privacySalt = privacySalt
)
t.inputsWithTransactionState.addAll(this.inputsWithTransactionState)
return t
}
// DOCSTART 1
/** A more convenient way to add items to this transaction that calls the add* methods for you based on type */
@ -83,31 +92,52 @@ open class TransactionBuilder(
* @returns A new [WireTransaction] that will be unaffected by further changes to this [TransactionBuilder].
*/
@Throws(MissingContractAttachments::class)
fun toWireTransaction(services: ServicesForResolution): WireTransaction = toWireTransactionWithContext(services.cordappProvider)
fun toWireTransaction(services: ServicesForResolution): WireTransaction = toWireTransactionWithContext(services)
internal fun toWireTransactionWithContext(cordappProvider: CordappProvider, serializationContext: SerializationContext? = null): WireTransaction {
// Resolves the AutomaticHashConstraints to HashAttachmentConstraints for convenience. The AutomaticHashConstraint
// allows for less boiler plate when constructing transactions since for the typical case the named contract
internal fun toWireTransactionWithContext(services: ServicesForResolution, serializationContext: SerializationContext? = null): WireTransaction {
// Resolves the AutomaticHashConstraints to HashAttachmentConstraints or WhitelistedByZoneAttachmentConstraint based on a global parameter.
// The AutomaticHashConstraint allows for less boiler plate when constructing transactions since for the typical case the named contract
// will be available when building the transaction. In exceptional cases the TransactionStates must be created
// with an explicit [AttachmentConstraint]
val resolvedOutputs = outputs.map { state ->
if (state.constraint is AutomaticHashConstraint) {
cordappProvider.getContractAttachmentID(state.contract)?.let {
when {
state.constraint !is AutomaticHashConstraint -> state
useWhitelistedByZoneAttachmentConstraint(state.contract, services.networkParameters) -> state.copy(constraint = WhitelistedByZoneAttachmentConstraint)
else -> services.cordappProvider.getContractAttachmentID(state.contract)?.let {
state.copy(constraint = HashAttachmentConstraint(it))
} ?: throw MissingContractAttachments(listOf(state))
} else {
state
}
}
return SerializationFactory.defaultFactory.withCurrentContext(serializationContext) {
WireTransaction(WireTransaction.createComponentGroups(inputs, resolvedOutputs, commands, attachments, notary, window), privacySalt)
WireTransaction(WireTransaction.createComponentGroups(inputStates(), resolvedOutputs, commands, attachments + makeContractAttachments(services.cordappProvider), notary, window), privacySalt)
}
}
private fun useWhitelistedByZoneAttachmentConstraint(contractClassName: ContractClassName, networkParameters: NetworkParameters): Boolean {
return contractClassName in networkParameters.whitelistedContractImplementations.keys
}
/**
* The attachments added to the current transaction contain only the hashes of the current cordapps.
* NOT the hashes of the cordapps that were used when the input states were created ( in case they changed in the meantime)
* TODO - review this logic
*/
private fun makeContractAttachments(cordappProvider: CordappProvider): List<AttachmentId> {
return (inputsWithTransactionState + outputs).map { state ->
cordappProvider.getContractAttachmentID(state.contract)
?: throw MissingContractAttachments(listOf(state))
}.distinct()
}
@Throws(AttachmentResolutionException::class, TransactionResolutionException::class)
fun toLedgerTransaction(services: ServiceHub) = toWireTransaction(services).toLedgerTransaction(services)
internal fun toLedgerTransactionWithContext(services: ServicesForResolution, serializationContext: SerializationContext) = toWireTransactionWithContext(services.cordappProvider, serializationContext).toLedgerTransaction(services)
internal fun toLedgerTransactionWithContext(services: ServicesForResolution, serializationContext: SerializationContext): LedgerTransaction {
return toWireTransactionWithContext(services, serializationContext).toLedgerTransaction(services)
}
@Throws(AttachmentResolutionException::class, TransactionResolutionException::class, TransactionVerificationException::class)
fun verify(services: ServiceHub) {
toLedgerTransaction(services).verify()
@ -117,6 +147,7 @@ open class TransactionBuilder(
val notary = stateAndRef.state.notary
require(notary == this.notary) { "Input state requires notary \"$notary\" which does not match the transaction notary \"${this.notary}\"." }
inputs.add(stateAndRef.ref)
inputsWithTransactionState.add(stateAndRef.state)
return this
}

View File

@ -5,7 +5,7 @@ import net.corda.core.contracts.ComponentGroupEnum.*
import net.corda.core.crypto.*
import net.corda.core.identity.Party
import net.corda.core.internal.Emoji
import net.corda.core.internal.GlobalProperties
import net.corda.core.node.NetworkParameters
import net.corda.core.node.ServicesForResolution
import net.corda.core.node.services.AttachmentId
import net.corda.core.serialization.CordaSerializable
@ -85,11 +85,11 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
*/
@Throws(AttachmentResolutionException::class, TransactionResolutionException::class)
fun toLedgerTransaction(services: ServicesForResolution): LedgerTransaction {
return toLedgerTransaction(
return toLedgerTransactionInternal(
resolveIdentity = { services.identityService.partyFromKey(it) },
resolveAttachment = { services.attachments.openAttachment(it) },
resolveStateRef = { services.loadState(it) },
resolveContractAttachment = { services.cordappProvider.getContractAttachmentID(it.contract) }
networkParameters = services.networkParameters
)
}
@ -100,6 +100,7 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
* @throws AttachmentResolutionException if a required attachment was not found using [resolveAttachment].
* @throws TransactionResolutionException if an input was not found not using [resolveStateRef].
*/
@Deprecated("Use toLedgerTransaction(ServicesForTransaction) instead")
@Throws(AttachmentResolutionException::class, TransactionResolutionException::class)
fun toLedgerTransaction(
resolveIdentity: (PublicKey) -> Party?,
@ -107,7 +108,16 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
resolveStateRef: (StateRef) -> TransactionState<*>?,
resolveContractAttachment: (TransactionState<ContractState>) -> AttachmentId?
): LedgerTransaction {
// Look up public keys to authenticated identities. This is just a stub placeholder and will all change in future.
return toLedgerTransactionInternal(resolveIdentity, resolveAttachment, resolveStateRef, null)
}
private fun toLedgerTransactionInternal(
resolveIdentity: (PublicKey) -> Party?,
resolveAttachment: (SecureHash) -> Attachment?,
resolveStateRef: (StateRef) -> TransactionState<*>?,
networkParameters: NetworkParameters?
): LedgerTransaction {
// Look up public keys to authenticated identities.
val authenticatedArgs = commands.map {
val parties = it.signers.mapNotNull { pk -> resolveIdentity(pk) }
CommandWithParties(it.signers, parties, it.value)
@ -115,25 +125,23 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
val resolvedInputs = inputs.map { ref ->
resolveStateRef(ref)?.let { StateAndRef(it, ref) } ?: throw TransactionResolutionException(ref.txhash)
}
// Open attachments specified in this transaction. If we haven't downloaded them, we fail.
val contractAttachments = findAttachmentContracts(resolvedInputs, resolveContractAttachment, resolveAttachment)
// Order of attachments is important since contracts may refer to indexes so only append automatic attachments
val attachments = (attachments.map { resolveAttachment(it) ?: throw AttachmentResolutionException(it) } + contractAttachments).distinct()
val ltx = LedgerTransaction(resolvedInputs, outputs, authenticatedArgs, attachments, id, notary, timeWindow, privacySalt)
checkTransactionSize(ltx)
val attachments = attachments.map { resolveAttachment(it) ?: throw AttachmentResolutionException(it) }
val ltx = LedgerTransaction(resolvedInputs, outputs, authenticatedArgs, attachments, id, notary, timeWindow, privacySalt, networkParameters)
checkTransactionSize(ltx, networkParameters?.maxTransactionSize ?: 10485760)
return ltx
}
private fun checkTransactionSize(ltx: LedgerTransaction) {
var remainingTransactionSize = GlobalProperties.networkParameters.maxTransactionSize
private fun checkTransactionSize(ltx: LedgerTransaction, maxTransactionSize: Int) {
var remainingTransactionSize = maxTransactionSize
fun minus(size: Int) {
require(remainingTransactionSize > size) { "Transaction exceeded network's maximum transaction size limit : ${GlobalProperties.networkParameters.maxTransactionSize} bytes." }
require(remainingTransactionSize > size) { "Transaction exceeded network's maximum transaction size limit : $maxTransactionSize bytes." }
remainingTransactionSize -= size
}
// Check attachment size first as they are most likely to go over the limit.
ltx.attachments.forEach { minus(it.size) }
// Check attachments size first as they are most likely to go over the limit. With ContractAttachment instances
// it's likely that the same underlying Attachment CorDapp will occur more than once so we dedup on the attachment id.
ltx.attachments.distinctBy { it.id }.forEach { minus(it.size) }
minus(ltx.inputs.serialize().size)
minus(ltx.commands.serialize().size)
minus(ltx.outputs.serialize().size)
@ -254,19 +262,6 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
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 {
if (other is WireTransaction) {
return (this.id == other.id)

View File

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

View File

@ -13,7 +13,6 @@ import net.corda.node.services.persistence.NodeAttachmentService
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.singleIdentity
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.MockNodeParameters
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.startFlow

View File

@ -11,12 +11,13 @@ import net.corda.core.identity.groupAbstractPartyByWellKnownParty
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.getOrThrow
import net.corda.node.internal.StartedNode
import net.corda.testing.contracts.DummyContract
import net.corda.testing.core.*
import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.StartedMockNode
import net.corda.testing.node.MockServices
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.InternalMockNetwork.MockNode
import net.corda.testing.node.startFlow
import org.junit.After
import org.junit.Before
@ -29,10 +30,10 @@ class CollectSignaturesFlowTests {
private val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB"))
}
private lateinit var mockNet: MockNetwork
private lateinit var aliceNode: StartedMockNode
private lateinit var bobNode: StartedMockNode
private lateinit var charlieNode: StartedMockNode
private lateinit var mockNet: InternalMockNetwork
private lateinit var aliceNode: StartedNode<MockNode>
private lateinit var bobNode: StartedNode<MockNode>
private lateinit var charlieNode: StartedNode<MockNode>
private lateinit var alice: Party
private lateinit var bob: Party
private lateinit var charlie: Party
@ -40,7 +41,7 @@ class CollectSignaturesFlowTests {
@Before
fun setup() {
mockNet = MockNetwork(cordappPackages = listOf("net.corda.testing.contracts"))
mockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.testing.contracts"))
aliceNode = mockNet.createPartyNode(ALICE_NAME)
bobNode = mockNet.createPartyNode(BOB_NAME)
charlieNode = mockNet.createPartyNode(CHARLIE_NAME)

View File

@ -5,7 +5,6 @@ import net.corda.core.utilities.UntrustworthyData
import net.corda.core.utilities.unwrap
import net.corda.node.internal.InitiatedFlowFactory
import net.corda.node.internal.StartedNode
import net.corda.testing.node.StartedMockNode
import net.corda.testing.node.internal.InternalMockNetwork
import kotlin.reflect.KClass
@ -81,7 +80,7 @@ infix fun <T : Any> KClass<T>.from(session: FlowSession): Pair<FlowSession, Clas
fun FlowLogic<*>.receiveAll(session: Pair<FlowSession, Class<out Any>>, vararg sessions: Pair<FlowSession, Class<out Any>>): Map<FlowSession, UntrustworthyData<Any>> {
val allSessions = arrayOf(session, *sessions)
allSessions.enforceNoDuplicates()
return receiveAll(mapOf(*allSessions))
return receiveAllMap(mapOf(*allSessions))
}
/**

View File

@ -7,9 +7,9 @@ import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.nodeapi.internal.crypto.X509KeyStore
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.testing.core.DEV_ROOT_CA
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.getTestPartyAndCertificate
import net.corda.testing.internal.DEV_ROOT_CA
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test

View File

@ -8,10 +8,11 @@ import net.corda.core.identity.Party
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.sequence
import net.corda.node.internal.StartedNode
import net.corda.testing.contracts.DummyContract
import net.corda.testing.node.MockNetwork
import net.corda.testing.core.singleIdentity
import net.corda.testing.node.StartedMockNode
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.InternalMockNetwork.MockNode
import net.corda.testing.node.startFlow
import org.junit.After
import org.junit.Before
@ -27,17 +28,17 @@ import kotlin.test.assertNull
// DOCSTART 3
class ResolveTransactionsFlowTest {
private lateinit var mockNet: MockNetwork
private lateinit var notaryNode: StartedMockNode
private lateinit var megaCorpNode: StartedMockNode
private lateinit var miniCorpNode: StartedMockNode
private lateinit var mockNet: InternalMockNetwork
private lateinit var notaryNode: StartedNode<MockNode>
private lateinit var megaCorpNode: StartedNode<MockNode>
private lateinit var miniCorpNode: StartedNode<MockNode>
private lateinit var megaCorp: Party
private lateinit var miniCorp: Party
private lateinit var notary: Party
@Before
fun setup() {
mockNet = MockNetwork(cordappPackages = listOf("net.corda.testing.contracts"))
mockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.testing.contracts"))
notaryNode = mockNet.defaultNotaryNode
megaCorpNode = mockNet.createPartyNode(CordaX500Name("MegaCorp", "London", "GB"))
miniCorpNode = mockNet.createPartyNode(CordaX500Name("MiniCorp", "London", "GB"))

View File

@ -18,7 +18,6 @@ import net.corda.node.services.persistence.NodeAttachmentService
import net.corda.nodeapi.internal.persistence.currentDBSession
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.MockNodeParameters
import net.corda.testing.core.singleIdentity
import net.corda.testing.node.internal.InternalMockNetwork

View File

@ -29,7 +29,7 @@ class LedgerTransactionQueryTests {
@JvmField
val testSerialization = SerializationEnvironmentRule()
private val keyPair = generateKeyPair()
private val services = MockServices(emptyList(), CordaX500Name("MegaCorp", "London", "GB"),
private val services = MockServices(listOf("net.corda.testing.contracts"), CordaX500Name("MegaCorp", "London", "GB"),
rigorousMock<IdentityServiceInternal>().also {
doReturn(null).whenever(it).partyFromKey(keyPair.public)
}, keyPair)
@ -37,7 +37,7 @@ class LedgerTransactionQueryTests {
@Before
fun setup() {
services.mockCordappProvider.addMockCordapp(DummyContract.PROGRAM_ID, services.attachments)
services.addMockCordapp(DummyContract.PROGRAM_ID)
}
interface Commands {

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

View File

@ -1,5 +1,7 @@
package net.corda.core.transactions
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.contracts.*
import net.corda.core.crypto.*
import net.corda.core.crypto.CompositeKey
@ -112,7 +114,9 @@ class TransactionTests {
val inputs = emptyList<StateAndRef<*>>()
val outputs = listOf(baseOutState, baseOutState.copy(notary = ALICE), baseOutState.copy(notary = BOB))
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 timeWindow: TimeWindow? = null
val privacySalt: PrivacySalt = PrivacySalt()

View File

@ -7,55 +7,135 @@ API: Contract Constraints
Contract constraints
--------------------
Transaction states specify a constraint over the contract that will be used to verify it. For a transaction to be
valid, the ``verify`` function associated with each state must run successfully. However, for this to be secure, it is
not sufficient to specify the ``verify`` function by name as there may exist multiple different implementations with
the same method signature and enclosing class. Contract constraints solve this problem by allowing a contract developer
to constrain which ``verify`` functions out of the universe of implementations can be used (i.e. the universe is
everything that matches the signature and contract constraints restrict this universe to a subset).
A typical constraint is the hash of the CorDapp JAR that contains the contract and states but will in future releases
include constraints that require specific signers of the JAR, or both the signer and the hash. Constraints can be
specified when constructing a transaction; if unspecified, an automatic constraint is used.
Corda separates verification of states from their definition. Whilst you might have expected the ``ContractState``
interface to define a verify method, or perhaps to do verification logic in the constructor, instead it is primarily
done by a method on a ``Contract`` class. This is because what we're actually checking is the
validity of a *transaction*, which is more than just whether the individual states are internally consistent.
The transition between two valid states may be invalid, if the rules of the application are not being respected.
For instance, two cash states of $100 and $200 may both be internally valid, but replacing the first with the second
isn't allowed unless you're a cash issuer - otherwise you could print money for free.
For a transaction to be valid, the ``verify`` function associated with each state must run successfully. However,
for this to be secure, it is not sufficient to specify the ``verify`` function by name as there may exist multiple
different implementations with the same method signature and enclosing class. This normally will happen as applications
evolve, but could also happen maliciously.
Contract constraints solve this problem by allowing a contract developer to constrain which ``verify`` functions out of
the universe of implementations can be used (i.e. the universe is everything that matches the signature and contract
constraints restrict this universe to a subset). Constraints are satisfied by attachments (JARs). You are not allowed to
attach two JARs that both define the same application due to the *no overlap rule*. This rule specifies that two
attachment JARs may not provide the same file path. If they do, the transaction is considered invalid. Because each
state specifies both a constraint over attachments *and* a Contract class name to use, the specified class must appear
in only one attachment.
So who picks the attachment to use? It is chosen by the creator of the transaction that has to satisfy input constraints.
The transaction creator also gets to pick the constraints used by any output states, but the contract logic itself may
have opinions about what those constraints are - a typical contract would require that the constraints are propagated,
that is, the contract will not just enforce the validity of the next transaction that uses a state, but *all successive
transactions as well*. The constraints mechanism creates a balance of power between the creator of data on
the ledger and the user who is trying to edit it, which can be very useful when managing upgrades to Corda applications.
There are two ways of handling upgrades to a smart contract in Corda:
1. *Implicit:* By allowing multiple implementations of the contract ahead of time, using constraints.
2. *Explicit:* By creating a special *contract upgrade transaction* and getting all participants of a state to sign it using the
contract upgrade flows.
This article focuses on the first approach. To learn about the second please see :doc:`upgrading-cordapps`.
The advantage of pre-authorising upgrades using constraints is that you don't need the heavyweight process of creating
upgrade transactions for every state on the ledger. The disadvantage is that you place more faith in third parties,
who could potentially change the app in ways you did not expect or agree with. The advantage of using the explicit
upgrade approach is that you can upgrade states regardless of their constraint, including in cases where you didn't
anticipate a need to do so. But it requires everyone to sign, requires everyone to manually authorise the upgrade,
consumes notary and ledger resources, and is just in general more complex.
How constraints work
--------------------
Starting from Corda 3 there are two types of constraint that can be used: hash and zone whitelist. In future
releases a third type will be added, the signature constraint.
**Hash constraints.** The behaviour provided by public blockchain systems like Bitcoin and Ethereum is that once data is placed on the ledger,
the program that controls it is fixed and cannot be changed. There is no support for upgrades at all. This implements a
form of "code is law", assuming you trust the community of that blockchain to not release a new version of the platform
that invalidates or changes the meaning of your program.
This is supported by Corda using a hash constraint. This specifies exactly one hash of a CorDapp JAR that contains the
contract and states any consuming transaction is allowed to use. Once such a state is created, other nodes will only
accept a transaction if it uses that exact JAR file as an attachment. By implication, any bugs in the contract code
or state definitions cannot be fixed except by using an explicit upgrade process via ``ContractUpgradeFlow``.
.. note:: Corda does not support any way to create states that can never be upgraded at all, but the same effect can be
obtained by using a hash constraint and then simply refusing to agree to any explicit upgrades. Hash
constraints put you in control by requiring an explicit agreement to any upgrade.
**Zone constraints.** Often a hash constraint will be too restrictive. You do want the ability to upgrade an app,
and you don't mind the upgrade taking effect "just in time" when a transaction happens to be required for other business
reasons. In this case you can use a zone constraint. This specifies that the network parameters of a compatibility zone
(see :doc:`network-map`) is expected to contain a map of class name to hashes of JARs that are allowed to provide that
class. The process for upgrading an app then involves asking the zone operator to add the hash of your new JAR to the
parameters file, and trigger the network parameters upgrade process. This involves each node operator running a shell
command to accept the new parameters file and then restarting the node. Node owners who do not restart their node in
time effectively stop being a part of the network.
**Signature constraints.** These are not yet supported, but once implemented they will allow a state to require a JAR
signed by a specified identity, via the regular Java jarsigner tool. This will be the most flexible type
and the smoothest to deploy: no restarts or contract upgrade transactions are needed.
**Defaults.** The default constraint type is either a zone constraint, if the network parameters in effect when the
transaction is built contain an entry for that contract class, or a hash constraint if not.
A ``TransactionState`` has a ``constraint`` field that represents that state's attachment constraint. When a party
constructs a ``TransactionState`` without specifying the constraint parameter a default value
(``AutomaticHashConstraint``) is used. This default will be automatically resolved to a specific
``HashAttachmentConstraint`` that contains the hash of the attachment which contains the contract of that
``TransactionState``. This automatic resolution occurs when a ``TransactionBuilder`` is converted to a
``WireTransaction``. This reduces the boilerplate involved in finding a specific hash constraint when building a
transaction.
constructs a ``TransactionState``, or adds a state using ``TransactionBuilder.addOutput(ContractState)`` without
specifying the constraint parameter, a default value (``AutomaticHashConstraint``) is used. This default will be
automatically resolved to a specific ``HashAttachmentConstraint`` or a ``WhitelistedByZoneAttachmentConstraint``.
This automatic resolution occurs when a ``TransactionBuilder`` is converted to a ``WireTransaction``. This reduces
the boilerplate that would otherwise be involved.
It is possible to specify the constraint explicitly with any other class that implements the ``AttachmentConstraint``
interface. To specify a hash manually the ``HashAttachmentConstraint`` can be used and to not provide any constraint
the ``AlwaysAcceptAttachmentConstraint`` can be used - though this is intended for testing only. An example below
shows how to construct a ``TransactionState`` with an explicitly specified hash constraint from within a flow:
Finally, an ``AlwaysAcceptAttachmentConstraint`` can be used which accepts anything, though this is intended for
testing only.
Please note that the ``AttachmentConstraint`` interface is marked as ``@DoNotImplement``. You are not allowed to write
new constraint types. Only the platform may implement this interface. If you tried, other nodes would not understand
your constraint type and your transaction would not verify.
.. warning:: An AlwaysAccept constraint is effectively the same as disabling security for those states entirely.
Nothing stops you using this constraint in production, but that degrades Corda to being effectively a form
of distributed messaging with optional contract logic being useful only to catch mistakes, rather than potentially
malicious action. If you are deploying an app for which malicious actors aren't in your threat model, using an
AlwaysAccept constraint might simplify things operationally.
An example below shows how to construct a ``TransactionState`` with an explicitly specified hash constraint from within
a flow:
.. sourcecode:: java
// Constructing a transaction with a custom hash constraint on a state
TransactionBuilder tx = new TransactionBuilder()
// Constructing a transaction with a custom hash constraint on a state
TransactionBuilder tx = new TransactionBuilder();
Party notaryParty = ... // a notary party
DummyState contractState = new DummyState()
SecureHash myAttachmentsHash = serviceHub.cordappProvider.getContractAttachmentID(DummyContract.PROGRAM_ID)
TransactionState transactionState = new TransactionState(contractState, DummyContract.Companion.getPROGRAMID(), notaryParty, new AttachmentHashConstraint(myAttachmentsHash))
Party notaryParty = ... // a notary party
DummyState contractState = new DummyState();
tx.addOutputState(transactionState)
WireTransaction wtx = tx.toWireTransaction(serviceHub) // This is where an automatic constraint would be resolved
LedgerTransaction ltx = wtx.toLedgerTransaction(serviceHub)
ltx.verify() // Verifies both the attachment constraints and contracts
SecureHash myAttachmentHash = SecureHash.parse("2b4042aed7e0e39d312c4c477dca1d96ec5a878ddcfd5583251a8367edbd4a5f");
TransactionState transactionState = new TransactionState(contractState, DummyContract.Companion.getPROGRAMID(), notaryParty, new AttachmentHashConstraint(myAttachmentHash));
This mechanism exists both for integrity and security reasons. It is important not to verify against the wrong contract,
which could happen if the wrong version of the contract is attached. More importantly when resolving transaction chains
there will, in a future release, be attachments loaded from the network into the attachment sandbox that are used
to verify the transaction chain. Ensuring the attachment used is the correct one ensures that the verification is
tamper-proof by providing a fake contract.
tx.addOutputState(transactionState);
WireTransaction wtx = tx.toWireTransaction(serviceHub); // This is where an automatic constraint would be resolved.
LedgerTransaction ltx = wtx.toLedgerTransaction(serviceHub);
ltx.verify(); // Verifies both the attachment constraints and contracts
Hard-coding the hash of your app in the code itself can be pretty awkward, so the API also offers the ``AutomaticHashConstraint``.
This isn't a real constraint that will appear in a transaction: it acts as a marker to the ``TransactionBuilder`` that
you require the hash of the node's installed app which supplies the specified contract to be used. In practice, when using
hash constraints, you almost always want "whatever the current code is" and not a hard-coded hash. So this automatic
constraint placeholder is useful.
CorDapps as attachments
-----------------------
CorDapp JARs (:doc:`cordapp-overview`) that are installed to the node and contain classes implementing the ``Contract``
CorDapp JARs (see :doc:`cordapp-overview`) that are installed to the node and contain classes implementing the ``Contract``
interface are automatically loaded into the ``AttachmentStorage`` of a node at startup.
After CorDapps are loaded into the attachment store the node creates a link between contract classes and the attachment
@ -63,28 +143,12 @@ that they were loaded from. This makes it possible to find the attachment for an
automatic resolution of attachments is done by the ``TransactionBuilder`` and how, when verifying the constraints and
contracts, attachments are associated with their respective contracts.
Implementations of AttachmentConstraint
---------------------------------------
There are three implementations of ``AttachmentConstraint`` with more planned in the future.
``AlwaysAcceptAttachmentConstraint``: Any attachment (except a missing one) will satisfy this constraint.
``AutomaticHashConstraint``: This will be resolved to a ``HashAttachmentConstraint`` when a ``TransactionBuilder`` is
converted to a ``WireTransaction``. The ``HashAttachmentConstraint`` will include the attachment hash of the CorDapp
that contains the ``ContractState`` on the ``TransactionState.contract`` field.
``HashAttachmentConstraint``: Will require that the hash of the attachment containing the contract matches the hash
stored in the constraint.
We plan to add a future ``AttachmentConstraint`` that will only be satisfied by the presence of signatures on the
attachment JAR. This allows for trusting of attachments from trusted entities.
Limitations
-----------
An ``AttachmentConstraint`` is verified by running the ``AttachmentConstraint.isSatisfiedBy`` method. When this is called
it is provided only the relevant attachment by the transaction that is verifying it.
.. note:: The obvious way to write a CorDapp is to put all you states, contracts, flows and support code into a single
Java module. This will work but it will effectively publish your entire app onto the ledger. That has two problems:
(1) it is inefficient, and (2) it means changes to your flows or other parts of the app will be seen by the ledger
as a "new app", which may end up requiring essentially unnecessary upgrade procedures. It's better to split your
app into multiple modules: one which contains just states, contracts and core data types. And another which contains
the rest of the app. See :ref:`cordapp-structure`.
Testing
-------
@ -93,7 +157,8 @@ Since all tests involving transactions now require attachments it is also requir
for tests. Unit test environments in JVM ecosystems tend to use class directories rather than JARs, and so CorDapp JARs
typically aren't built for testing. Requiring this would add significant complexity to the build systems of Corda
and CorDapps, so the test suite has a set of convenient functions to generate CorDapps from package names or
to specify JAR URLs in the case that the CorDapp(s) involved in testing already exist.
to specify JAR URLs in the case that the CorDapp(s) involved in testing already exist. You can also just use
``AlwaysAcceptAttachmentConstraint`` in your tests to disable the constraints mechanism.
MockNetwork/MockNode
********************
@ -102,12 +167,14 @@ The simplest way to ensure that a vanilla instance of a MockNode generates the c
``cordappPackages`` constructor parameter (Kotlin) or the ``setCordappPackages`` method on ``MockNetworkParameters`` (Java)
when creating the MockNetwork. This will cause the ``AbstractNode`` to use the named packages as sources for CorDapps. All files
within those packages will be zipped into a JAR and added to the attachment store and loaded as CorDapps by the
``CordappLoader``. An example of this usage would be:
``CordappLoader``.
An example of this usage would be:
.. sourcecode:: java
class SomeTestClass {
MockNetwork network = null
MockNetwork network = null;
@Before
void setup() {
@ -117,6 +184,7 @@ within those packages will be zipped into a JAR and added to the attachment stor
... // Your tests go here
}
MockServices
************
@ -127,6 +195,10 @@ to use as CorDapps using the ``cordappPackages`` parameter.
MockServices mockServices = new MockServices(Arrays.asList("com.domain.cordapp"))
However - there is an easier way! If your unit tests are in the same package as the contract code itself, then you
can use the no-args constructor of ``MockServices``. The package to be scanned for CorDapps will be the same as the
the package of the class that constructed the object. This is a convenient default.
Driver
******

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``
* 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
~~~~~~~~~~~~

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

@ -615,6 +615,24 @@ which the signatures are allowed to be missing:
:end-before: DOCEND 36
:dedent: 16
There is also an overload of ``SignedTransaction.verifySignaturesExcept``, which takes a ``Collection`` of the
public keys for which the signatures are allowed to be missing:
.. container:: codeset
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
:language: kotlin
:start-after: DOCSTART 54
:end-before: DOCEND 54
:dedent: 8
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
:language: java
:start-after: DOCSTART 54
:end-before: DOCEND 54
:dedent: 16
If the transaction is missing any signatures without the corresponding public keys being passed in, a
``SignaturesMissingException`` is thrown.

View File

@ -7,6 +7,13 @@ from the previous milestone release.
UNRELEASED
----------
* Added ``NetworkMapCache.getNodesByLegalName`` for querying nodes belonging to a distributed service such as a notary cluster
where they all share a common identity. ``NetworkMapCache.getNodeByLegalName`` has been tightened to throw if more than
one node with the legal name is found.
* Per CorDapp configuration is now exposed. ``CordappContext`` now exposes a ``CordappConfig`` object that is populated
at CorDapp context creation time from a file source during runtime.
* Introduced Flow Draining mode, in which a node continues executing existing flows, but does not start new. This is to support graceful node shutdown/restarts.
In particular, when this mode is on, new flows through RPC will be rejected, scheduled flows will be ignored, and initial session messages will not be consumed.
This will ensure that the number of checkpoints will strictly diminish with time, allowing for a clean shutdown.
@ -69,7 +76,10 @@ UNRELEASED
:doc:`corda-configuration-file` for more details.
* Introducing the concept of network parameters which are a set of constants which all nodes on a network must agree on
to correctly interop.
to correctly interop. These can be retrieved from ``ServiceHub.networkParameters``.
* One of these parameters, ``maxTransactionSize``, limits the size of a transaction, including its attachments, so that
all nodes have sufficient memory to validate transactions.
* The set of valid notaries has been moved to the network parameters. Notaries are no longer identified by the CN in
their X500 name.
@ -82,9 +92,6 @@ UNRELEASED
* Moved ``NodeInfoSchema`` to internal package as the node info's database schema is not part of the public API. This
was needed to allow changes to the schema.
* Introduced max transaction size limit on transactions. The max transaction size parameter is set by the compatibility zone
operator. The parameter is distributed to Corda nodes by network map service as part of the ``NetworkParameters``.
* Support for external user credentials data source and password encryption [CORDA-827].
* Exporting additional JMX metrics (artemis, hibernate statistics) and loading Jolokia agent at JVM startup when using
@ -196,9 +203,9 @@ UNRELEASED
* The test utils in ``Expect.kt``, ``SerializationTestHelpers.kt``, ``TestConstants.kt`` and ``TestUtils.kt`` have moved
from the ``net.corda.testing`` package to the ``net.corda.testing.core`` package, and ``FlowStackSnapshot.kt`` has moved to the
``net.corda.testing.services`` package. Moving items out of the ``net.corda.testing.*`` package will help make it clearer which
parts of the api are stable. The bash script ``tools\scripts\update-test-packages.sh`` can be used to smooth the upgrade
process for existing projects.
``net.corda.testing.services`` package. Moving existing classes out of the ``net.corda.testing.*`` package
will help make it clearer which parts of the api are stable. Scripts have been provided to smooth the upgrade
process for existing projects in the ``tools\scripts`` directory of the Corda repo.
.. _changelog_v1:

View File

@ -46,7 +46,7 @@ master_doc = 'index'
# General information about the project.
project = u'R3 Corda'
copyright = u'2017, R3 Limited'
copyright = u'2018, R3 Limited'
author = u'R3 DLG'
# 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-rpc
api-core-types
api-testing
Before reading this page, you should be familiar with the :doc:`key concepts of Corda <key-concepts>`.

View File

@ -185,8 +185,8 @@ path to the node's base directory.
:port: The port to start SSH server on
:exportJMXTo: If set to ``http``, will enable JMX metrics reporting via the Jolokia HTTP/JSON agent.
Default Jolokia access url is http://127.0.0.1:7005/jolokia/
:jmxMonitoringHttpPort: If set, will enable JMX metrics reporting via the Jolokia HTTP/JSON agent on the corresponding port.
Default Jolokia access url is http://127.0.0.1:port/jolokia/
:transactionCacheSizeMegaBytes: Optionally specify how much memory should be used for caching of ledger transactions in memory.
Otherwise defaults to 8MB plus 5% of all heap memory above 300MB.

View File

@ -159,3 +159,20 @@ Installing the CorDapp JAR
At runtime, nodes will load any CorDapps present in their ``cordapps`` folder. Therefore in order to install a CorDapp on
a node, the CorDapp JAR must be added to the ``<node_dir>/cordapps/`` folder, where ``node_dir`` is the folder in which
the node's JAR and configuration files are stored.
CorDapp configuration files
---------------------------
CorDapp configuration files should be placed in ``<node_dir>/cordapps/config``. The name of the file should match the
name of the JAR of the CorDapp (eg; if your CorDapp is called ``hello-0.1.jar`` the config should be ``config/hello-0.1.conf``).
Config files are currently only available in the `Typesafe/Lightbend <https://github.com/lightbend/config>`_ config format.
These files are loaded when a CorDapp context is created and so can change during runtime.
CorDapp configuration can be accessed from ``CordappContext::config`` whenever a ``CordappContext`` is available.
There is an example project that demonstrates in ``samples` called ``cordapp-configuration`` and API documentation in
<api/kotlin/corda/net.corda.core.cordapp/index.html>`_.

View File

@ -30,8 +30,8 @@ handling, and ensures the Corda service is run at boot.
4. (Optional) Download the `Corda webserver jar <http://r3.bintray.com/corda/net/corda/corda-webserver/>`_
(under ``/VERSION_NUMBER/corda-VERSION_NUMBER.jar``) and place it in ``/opt/corda``
5. Create a directory called ``plugins`` in ``/opt/corda`` and save your CorDapp jar file to it. Alternatively, download one of
our `sample CorDapps <https://www.corda.net/samples/>`_ to the ``plugins`` directory
5. Create a directory called ``cordapps`` in ``/opt/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
6. Save the below as ``/opt/corda/node.conf``. See :doc:`corda-configuration-file` for a description of these options
@ -199,8 +199,8 @@ at boot, and means the Corda service stays running with no users connected to th
mkdir C:\Corda
wget http://jcenter.bintray.com/net/corda/corda/VERSION_NUMBER/corda-VERSION_NUMBER.jar -OutFile C:\Corda\corda.jar
2. Create a directory called ``plugins`` in ``/opt/corda`` and save your CorDapp jar file to it. Alternatively,
download one of our `sample CorDapps <https://www.corda.net/samples/>`_ to the ``plugins`` directory
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
3. Save the below as ``C:\Corda\node.conf``. See :doc:`corda-configuration-file` for a description of these options
@ -267,13 +267,16 @@ at boot, and means the Corda service stays running with no users connected to th
* Set the amount of Java heap memory available to this node by modifying the -Xmx argument
* Set an informative description
10. Run the batch file by clicking on it or from a command prompt
10. Provision the required certificates to your node. Contact the network permissioning service or see
:doc:`permissioning`
11. Run ``services.msc`` and verify that a service called ``cordanode1`` is present and running
11. Run the batch file by clicking on it or from a command prompt
12. Run ``netstat -ano`` and check for the ports you configured in ``node.conf``
12. Run ``services.msc`` and verify that a service called ``cordanode1`` is present and running
13. You may need to open the ports on the Windows firewall
13. Run ``netstat -ano`` and check for the ports you configured in ``node.conf``
* You may need to open the ports on the Windows firewall
Testing your installation
-------------------------
@ -282,4 +285,4 @@ You can verify Corda is running by connecting to your RPC port from another host
``telnet your-hostname.example.com 10002``
If you receive the message "Escape character is ^]", Corda is running and accessible. Press Ctrl-] and Ctrl-D to exit
telnet.
telnet.

View File

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

View File

@ -540,12 +540,21 @@ public class FlowCookbookJava {
// DOCEND 35
// If the transaction is only partially signed, we have to pass in
// a list of the public keys corresponding to the missing
// a vararg of the public keys corresponding to the missing
// signatures, explicitly telling the system not to check them.
// DOCSTART 36
onceSignedTx.verifySignaturesExcept(counterpartyPubKey);
// DOCEND 36
// There is also an overload of ``verifySignaturesExcept`` which accepts
// a ``Collection`` of the public keys corresponding to the missing
// signatures. In the example below, we could also use
// ``Arrays.asList(counterpartyPubKey)`` instead of
// ``Collections.singletonList(counterpartyPubKey)``.
// DOCSTART 54
onceSignedTx.verifySignaturesExcept(Collections.singletonList(counterpartyPubKey));
// DOCEND 54
// We can also choose to only check the signatures that are
// present. BE VERY CAREFUL - this function provides no guarantees
// that the signatures are correct, or that none are missing.

View File

@ -27,6 +27,7 @@ import net.corda.finance.contracts.asset.Cash
import net.corda.testing.contracts.DummyContract
import net.corda.testing.contracts.DummyState
import java.security.PublicKey
import java.security.Signature
import java.time.Instant
// ``InitiatorFlow`` is our first flow, and will communicate with
@ -205,6 +206,19 @@ class InitiatorFlow(val arg1: Boolean, val arg2: Int, private val counterparty:
val packet3: UntrustworthyData<Any> = regulatorSession.receive<Any>()
// 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 *
-----------------------------------**/
@ -522,12 +536,19 @@ class InitiatorFlow(val arg1: Boolean, val arg2: Int, private val counterparty:
// DOCEND 35
// If the transaction is only partially signed, we have to pass in
// a list of the public keys corresponding to the missing
// a vararg of the public keys corresponding to the missing
// signatures, explicitly telling the system not to check them.
// DOCSTART 36
onceSignedTx.verifySignaturesExcept(counterpartyPubKey)
// DOCEND 36
// There is also an overload of ``verifySignaturesExcept`` which accepts
// a ``Collection`` of the public keys corresponding to the missing
// signatures.
// DOCSTART 54
onceSignedTx.verifySignaturesExcept(listOf(counterpartyPubKey))
// DOCEND 54
// We can also choose to only check the signatures that are
// present. BE VERY CAREFUL - this function provides no guarantees
// that the signatures are correct, or that none are missing.

View File

@ -7,39 +7,66 @@ import net.corda.core.identity.CordaX500Name;
import net.corda.finance.contracts.ICommercialPaperState;
import net.corda.finance.contracts.JavaCommercialPaper;
import net.corda.finance.contracts.asset.Cash;
import net.corda.testing.node.MockServices;
import net.corda.testing.core.TestIdentity;
import net.corda.testing.node.MockServices;
import org.junit.Before;
import org.junit.Test;
import java.security.PublicKey;
import java.time.temporal.ChronoUnit;
import static java.util.Collections.emptyList;
import static net.corda.core.crypto.Crypto.generateKeyPair;
import static java.util.Collections.singletonList;
import static net.corda.finance.Currencies.DOLLARS;
import static net.corda.finance.Currencies.issuedBy;
import static net.corda.finance.contracts.JavaCommercialPaper.JCP_PROGRAM_ID;
import static net.corda.testing.core.TestConstants.*;
import static net.corda.testing.node.MockServicesKt.makeTestIdentityService;
import static net.corda.testing.node.NodeTestUtils.ledger;
import static net.corda.testing.node.NodeTestUtils.transaction;
import static net.corda.testing.core.TestConstants.ALICE_NAME;
import static net.corda.testing.core.TestConstants.BOB_NAME;
import static net.corda.testing.core.TestConstants.TEST_TX_TIME;
public class CommercialPaperTest {
private static final TestIdentity ALICE = new TestIdentity(ALICE_NAME, 70L);
private static final PublicKey BIG_CORP_PUBKEY = generateKeyPair().getPublic();
private static final TestIdentity BOB = new TestIdentity(BOB_NAME, 80L);
private static final TestIdentity MEGA_CORP = new TestIdentity(new CordaX500Name("MegaCorp", "London", "GB"));
private static final TestIdentity alice = new TestIdentity(ALICE_NAME, 70L);
private static final TestIdentity bigCorp = new TestIdentity(new CordaX500Name("BigCorp", "New York", "GB"));
private static final TestIdentity bob = new TestIdentity(BOB_NAME, 80L);
private static final TestIdentity megaCorp = new TestIdentity(new CordaX500Name("MegaCorp", "London", "GB"));
private final byte[] defaultRef = {123};
private 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
private ICommercialPaperState getPaper() {
return new JavaCommercialPaper.State(
MEGA_CORP.ref(defaultRef),
MEGA_CORP.getParty(),
issuedBy(DOLLARS(1000), MEGA_CORP.ref(defaultRef)),
megaCorp.ref(defaultRef),
megaCorp.getParty(),
issuedBy(DOLLARS(1000), megaCorp.ref(defaultRef)),
TEST_TX_TIME.plus(7, ChronoUnit.DAYS)
);
}
@ -69,7 +96,7 @@ public class CommercialPaperTest {
ledger(ledgerServices, l -> {
l.transaction(tx -> {
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);
return tx.verifies();
});
@ -85,7 +112,7 @@ public class CommercialPaperTest {
ledger(ledgerServices, l -> {
l.transaction(tx -> {
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);
return tx.failsWith("the state is propagated");
});
@ -96,15 +123,15 @@ public class CommercialPaperTest {
// DOCSTART 5
@Test
public void simpleCPMoveSuccess() {
public void simpleCPMoveSuccessAndFailure() {
ICommercialPaperState inState = getPaper();
ledger(ledgerServices, l -> {
l.transaction(tx -> {
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.failsWith("the state is propagated");
tx.output(JCP_PROGRAM_ID, "alice's paper", inState.withOwner(ALICE.getParty()));
tx.output(JCP_PROGRAM_ID, "alice's paper", inState.withOwner(alice.getParty()));
return tx.verifies();
});
return Unit.INSTANCE;
@ -112,6 +139,24 @@ public class CommercialPaperTest {
}
// DOCEND 5
// DOCSTART 13
@Test
public void simpleCPMoveSuccess() {
ICommercialPaperState inState = getPaper();
ledger(ledgerServices, l -> {
l.transaction(tx -> {
tx.input(JCP_PROGRAM_ID, inState);
tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move());
tx.attachments(JCP_PROGRAM_ID);
tx.timeWindow(TEST_TX_TIME);
tx.output(JCP_PROGRAM_ID, "alice's paper", inState.withOwner(alice.getParty()));
return tx.verifies();
});
return Unit.INSTANCE;
});
}
// DOCEND 13
// DOCSTART 6
@Test
public void simpleIssuanceWithTweak() {
@ -120,11 +165,11 @@ public class CommercialPaperTest {
tx.output(JCP_PROGRAM_ID, "paper", getPaper()); // Some CP is issued onto the ledger by MegaCorp.
tx.attachments(JCP_PROGRAM_ID);
tx.tweak(tw -> {
tw.command(BIG_CORP_PUBKEY, new JavaCommercialPaper.Commands.Issue());
tw.command(bigCorp.getPublicKey(), new JavaCommercialPaper.Commands.Issue());
tw.timeWindow(TEST_TX_TIME);
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);
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.attachments(JCP_PROGRAM_ID);
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);
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);
return tx.verifies();
});
@ -154,11 +199,11 @@ public class CommercialPaperTest {
// DOCSTART 8
@Test
public void chainCommercialPaper() {
PartyAndReference issuer = MEGA_CORP.ref(defaultRef);
PartyAndReference issuer = megaCorp.ref(defaultRef);
ledger(ledgerServices, l -> {
l.unverifiedTransaction(tx -> {
tx.output(Cash.PROGRAM_ID, "alice's $900",
new Cash.State(issuedBy(DOLLARS(900), issuer), ALICE.getParty()));
new Cash.State(issuedBy(DOLLARS(900), issuer), alice.getParty()));
tx.attachments(Cash.PROGRAM_ID);
return Unit.INSTANCE;
});
@ -166,7 +211,7 @@ public class CommercialPaperTest {
// Some CP is issued onto the ledger by MegaCorp.
l.transaction("Issuance", tx -> {
tx.output(JCP_PROGRAM_ID, "paper", getPaper());
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Issue());
tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Issue());
tx.attachments(JCP_PROGRAM_ID);
tx.timeWindow(TEST_TX_TIME);
return tx.verifies();
@ -175,11 +220,11 @@ public class CommercialPaperTest {
l.transaction("Trade", tx -> {
tx.input("paper");
tx.input("alice's $900");
tx.output(Cash.PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), 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");
tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(ALICE.getParty()));
tx.command(ALICE.getPublicKey(), new Cash.Commands.Move());
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move());
tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(alice.getParty()));
tx.command(alice.getPublicKey(), new Cash.Commands.Move());
tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move());
return tx.verifies();
});
return Unit.INSTANCE;
@ -190,11 +235,11 @@ public class CommercialPaperTest {
// DOCSTART 9
@Test
public void chainCommercialPaperDoubleSpend() {
PartyAndReference issuer = MEGA_CORP.ref(defaultRef);
PartyAndReference issuer = megaCorp.ref(defaultRef);
ledger(ledgerServices, l -> {
l.unverifiedTransaction(tx -> {
tx.output(Cash.PROGRAM_ID, "alice's $900",
new Cash.State(issuedBy(DOLLARS(900), issuer), ALICE.getParty()));
new Cash.State(issuedBy(DOLLARS(900), issuer), alice.getParty()));
tx.attachments(Cash.PROGRAM_ID);
return Unit.INSTANCE;
});
@ -202,7 +247,7 @@ public class CommercialPaperTest {
// Some CP is issued onto the ledger by MegaCorp.
l.transaction("Issuance", tx -> {
tx.output(Cash.PROGRAM_ID, "paper", getPaper());
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Issue());
tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Issue());
tx.attachments(JCP_PROGRAM_ID);
tx.timeWindow(TEST_TX_TIME);
return tx.verifies();
@ -211,11 +256,11 @@ public class CommercialPaperTest {
l.transaction("Trade", tx -> {
tx.input("paper");
tx.input("alice's $900");
tx.output(Cash.PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), 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");
tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(ALICE.getParty()));
tx.command(ALICE.getPublicKey(), new Cash.Commands.Move());
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move());
tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(alice.getParty()));
tx.command(alice.getPublicKey(), new Cash.Commands.Move());
tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move());
return tx.verifies();
});
@ -223,8 +268,8 @@ public class CommercialPaperTest {
tx.input("paper");
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
// We moved a paper to other pubkey.
tx.output(JCP_PROGRAM_ID, "bob's paper", inputPaper.withOwner(BOB.getParty()));
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move());
tx.output(JCP_PROGRAM_ID, "bob's paper", inputPaper.withOwner(bob.getParty()));
tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move());
return tx.verifies();
});
l.fails();
@ -236,11 +281,11 @@ public class CommercialPaperTest {
// DOCSTART 10
@Test
public void chainCommercialPaperTweak() {
PartyAndReference issuer = MEGA_CORP.ref(defaultRef);
PartyAndReference issuer = megaCorp.ref(defaultRef);
ledger(ledgerServices, l -> {
l.unverifiedTransaction(tx -> {
tx.output(Cash.PROGRAM_ID, "alice's $900",
new Cash.State(issuedBy(DOLLARS(900), issuer), ALICE.getParty()));
new Cash.State(issuedBy(DOLLARS(900), issuer), alice.getParty()));
tx.attachments(Cash.PROGRAM_ID);
return Unit.INSTANCE;
});
@ -248,7 +293,7 @@ public class CommercialPaperTest {
// Some CP is issued onto the ledger by MegaCorp.
l.transaction("Issuance", tx -> {
tx.output(Cash.PROGRAM_ID, "paper", getPaper());
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Issue());
tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Issue());
tx.attachments(JCP_PROGRAM_ID);
tx.timeWindow(TEST_TX_TIME);
return tx.verifies();
@ -257,11 +302,11 @@ public class CommercialPaperTest {
l.transaction("Trade", tx -> {
tx.input("paper");
tx.input("alice's $900");
tx.output(Cash.PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), 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");
tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(ALICE.getParty()));
tx.command(ALICE.getPublicKey(), new Cash.Commands.Move());
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move());
tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(alice.getParty()));
tx.command(alice.getPublicKey(), new Cash.Commands.Move());
tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move());
return tx.verifies();
});
@ -270,8 +315,8 @@ public class CommercialPaperTest {
tx.input("paper");
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
// We moved a paper to another pubkey.
tx.output(JCP_PROGRAM_ID, "bob's paper", inputPaper.withOwner(BOB.getParty()));
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move());
tx.output(JCP_PROGRAM_ID, "bob's paper", inputPaper.withOwner(bob.getParty()));
tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move());
return tx.verifies();
});
lw.fails();

View File

@ -74,16 +74,14 @@ class CustomVaultQueryTest {
private fun getBalances(): Pair<Map<Currency, Amount<Currency>>, Map<Currency, Amount<Currency>>> {
// Print out the balances
val balancesNodesA =
nodeA.database.transaction {
nodeA.services.getCashBalances()
}
val balancesNodesA = nodeA.transaction {
nodeA.services.getCashBalances()
}
println("BalanceA\n" + balancesNodesA)
val balancesNodesB =
nodeB.database.transaction {
nodeB.services.getCashBalances()
}
val balancesNodesB = nodeB.transaction {
nodeB.services.getCashBalances()
}
println("BalanceB\n" + balancesNodesB)
return Pair(balancesNodesA, balancesNodesB)

View File

@ -13,6 +13,7 @@ import net.corda.testing.node.StartedMockNode
import net.corda.testing.node.startFlow
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import kotlin.test.assertEquals
@ -68,13 +69,14 @@ class FxTransactionBuildTutorialTest {
doIt.getOrThrow()
// Get the balances when the vault updates
nodeAVaultUpdate.get()
val balancesA = nodeA.database.transaction {
val balancesA = nodeA.transaction {
nodeA.services.getCashBalances()
}
nodeBVaultUpdate.get()
val balancesB = nodeB.database.transaction {
val balancesB = nodeB.transaction {
nodeB.services.getCashBalances()
}
println("BalanceA\n" + balancesA)
println("BalanceB\n" + balancesB)
// Verify the transfers occurred as expected
@ -86,10 +88,10 @@ class FxTransactionBuildTutorialTest {
private fun printBalances() {
// Print out the balances
nodeA.database.transaction {
nodeA.transaction {
println("BalanceA\n" + nodeA.services.getCashBalances())
}
nodeB.database.transaction {
nodeB.transaction {
println("BalanceB\n" + nodeB.services.getCashBalances())
}
}

View File

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

View File

@ -1,14 +1,25 @@
Network Map
===========
The network map is a collection of signed ``NodeInfo`` objects (signed by the node it represents and thus tamper-proof)
forming the set of reachable nodes in a compatibility zone. A node can receive these objects from two sources:
The network map is a collection of signed ``NodeInfo`` objects. Each NodeInfo is signed by the node it represents and
thus cannot be tampered with. It forms the set of reachable nodes in a compatibility zone. A node can receive these
objects from two sources:
1. The HTTP network map service if the ``compatibilityZoneURL`` config key is specified.
1. A network map server that speaks a simple HTTP based protocol.
2. The ``additional-node-infos`` directory within the node's directory.
HTTP network map service
------------------------
The network map server also distributes the parameters file that define values for various settings that all nodes need
to agree on to remain in sync.
.. note:: In Corda 3 no implementation of the HTTP network map server is provided. This is because the details of how
a compatibility zone manages its membership (the databases, ticketing workflows, HSM hardware etc) is expected to vary
between operators, so we provide a simple REST based protocol for uploading/downloading NodeInfos and managing
network parameters. A future version of Corda may provide a simple "stub" implementation for running test zones.
In Corda 3 the right way to run a test network is through distribution of the relevant files via your own mechanisms.
We provide a tool to automate the bulk of this task (see below).
HTTP network map protocol
-------------------------
If the node is configured with the ``compatibilityZoneURL`` config then it first uploads its own signed ``NodeInfo``
to the server (and each time it changes on startup) and then proceeds to download the entire network map. The network map
@ -31,6 +42,12 @@ The set of REST end-points for the network map service are as follows.
| GET | /network-map/network-parameters/{hash} | Retrieve the signed network parameters (see below). The entire object is signed with the network map certificate which is also attached. |
+----------------+-----------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------+
HTTP is used for the network map service instead of Corda's own AMQP based peer to peer messaging protocol to
enable the server to be placed behind caching content delivery networks like Cloudflare, Akamai, Amazon Cloudfront and so on.
By using industrial HTTP cache networks the map server can be shielded from DoS attacks more effectively. Additionally,
for the case of distributing small files that rarely change, HTTP is a well understood and optimised protocol. Corda's
own protocol is designed for complex multi-way conversations between authenticated identities using signed binary
messages separated into parallel and nested flows, which isn't necessary for network map distribution.
The ``additional-node-infos`` directory
---------------------------------------
@ -43,24 +60,39 @@ latest one is taken.
On startup the node generates its own signed node info file, filename of the format ``nodeInfo-${hash}``. To create a simple
network without the HTTP network map service then simply place this file in the ``additional-node-infos`` directory
of every node that's part of this network.
of every node that's part of this network. For example, a simple way to do this is to use rsync.
Usually, test networks have a structure that is known ahead of time. For the creation of such networks we provide a
``network-bootstrapper`` tool. This tool pre-generates node configuration directories if given the IP addresses/domain
names of each machine in the network. The generated node directories contain the NodeInfos for every other node on
the network, along with the network parameters file and identity certificates. Generated nodes do not need to all be
online at once - an offline node that isn't being interacted with doesn't impact the network in any way. So a test
cluster generated like this can be sized for the maximum size you may need, and then scaled up and down as necessary.
More information can be found in :doc:`setting-up-a-corda-network`.
Network parameters
------------------
Network parameters are a set of values that every node participating in the zone needs to agree on and use to
correctly interoperate with each other. If the node is using the HTTP network map service then on first startup it will
download the signed network parameters, cache it in a ``network-parameters`` file and apply them on the node.
correctly interoperate with each other. They can be thought of as an encapsulation of all aspects of a Corda deployment
on which reasonable people may disagree. Whilst other blockchain/DLT systems typically require a source code fork to
alter various constants (like the total number of coins in a cryptocurrency, port numbers to use etc), in Corda we
have refactored these sorts of decisions out into a separate file and allow "zone operators" to make decisions about
them. The operator signs a data structure that contains the values and they are distributed along with the network map.
Tools are provided to gain user opt-in consent to a new version of the parameters and ensure everyone switches to them
at the same time.
If the node is using the HTTP network map service then on first startup it will download the signed network parameters,
cache it in a ``network-parameters`` file and apply them on the node.
.. warning:: If the ``network-parameters`` file is changed and no longer matches what the network map service is advertising
then the node will automatically shutdown. Resolution to this is to delete the incorrect file and restart the node so
that the parameters can be downloaded again.
.. note:: A future release will support the notion of phased rollout of network parameter changes.
If the node isn't using a HTTP network map service then it's expected the signed file is provided by some other means.
For such a scenario there is the network bootstrapper tool which in addition to generating the network parameters file
also distributes the node info files to the node directories. More information can be found in :doc:`setting-up-a-corda-network`.
also distributes the node info files to the node directories.
The current set of network parameters:
@ -85,16 +117,23 @@ Network parameters update process
---------------------------------
In case of the need to change network parameters Corda zone operator will start the update process. There are many reasons
that may lead to this decision: we discovered that some new fields have to be added to enable smooth network interoperability or change
of the existing compatibility constants is required due to upgrade or security reasons.
that may lead to this decision: adding a notary, setting new fields that were added to enable smooth network interoperability,
or a change of the existing compatibility constants is required, for example.
To synchronize all nodes in the compatibility zone to use the new set of the network parameters two RPC methods exist. The process
requires human interaction and approval of the change.
.. note:: A future release may support the notion of phased rollout of network parameter changes.
To synchronize all nodes in the compatibility zone to use the new set of the network parameters two RPC methods are
provided. The process requires human interaction and approval of the change, so node operators can review the
differences before agreeing to them.
When the update is about to happen the network map service starts to advertise the additional information with the usual network map
data. It includes new network parameters hash, description of the change and the update deadline. Node queries network map server
for the new set of parameters and emits ``ParametersUpdateInfo`` via ``CordaRPCOps::networkParametersFeed`` method to inform
node operator about the event.
data. It includes new network parameters hash, description of the change and the update deadline. Nodes query the network map server
for the new set of parameters.
The fact a new set of parameters is being advertised shows up in the node logs with the message
"Downloaded new network parameters", and programs connected via RPC can receive ``ParametersUpdateInfo`` by using
the ``CordaRPCOps.networkParametersFeed`` method. Typically a zone operator would also email node operators to let them
know about the details of the impending change, along with the justification, how to object, deadlines and so on.
.. container:: codeset
@ -103,12 +142,22 @@ node operator about the event.
:start-after: DOCSTART 1
:end-before: DOCEND 1
Node administrator can review the change and decide if is going to accept it. The approval should be done before ``updateDeadline``.
Nodes that don't approve before the deadline will be removed from the network map.
If the network operator starts advertising a different set of new parameters then that new set overrides the previous set. Only the latest update can be accepted.
To send back parameters approval to the zone operator RPC method ``fun acceptNewNetworkParameters(parametersHash: SecureHash)``
has to be called with ``parametersHash`` from update. Notice that the process cannot be undone.
The node administrator can review the change and decide if they are going to accept it. The approval should be do
before the update Deadline. Nodes that don't approve before the deadline will likely be removed from the network map by
the zone operator, but that is a decision that is left to the operator's discretion. For example the operator might also
choose to change the deadline instead.
If the network operator starts advertising a different set of new parameters then that new set overrides the previous set.
Only the latest update can be accepted.
To send back parameters approval to the zone operator, the RPC method ``fun acceptNewNetworkParameters(parametersHash: SecureHash)``
has to be called with ``parametersHash`` from the update. Note that approval cannot be undone. You can do this via the Corda
shell (see :doc:`shell`):
``run acceptNewNetworkParameters parametersHash: "ba19fc1b9e9c1c7cbea712efda5f78b53ae4e5d123c89d02c9da44ec50e9c17d"``
If the administrator does not accept the update then next time the node polls network map after the deadline, the
advertised network parameters will be the updated ones. The previous set of parameters will no longer be valid.
At this point the node will automatically shutdown and will require the node operator to bring it back again.
Next time the node polls network map after the deadline the advertised network parameters will be the updated ones. Previous set
of parameters will no longer be valid. At this point the node will automatically shutdown and will require the node operator
to bring it back again.

View File

@ -106,7 +106,7 @@ Here are a few ways to build dashboards and extract monitoring data for a node:
It can bridge any data input to any output using their plugin system, for example, Telegraf can
be configured to collect data from Jolokia and write to DataDog web api.
The Node configuration parameter `exportJMXTo` should be set to ``http`` to ensure a Jolokia agent is instrumented with
The Node configuration parameter `jmxMonitoringHttpPort` has to be present in order to ensure a Jolokia agent is instrumented with
the JVM run-time.
The following JMX statistics are exported:

View File

@ -396,6 +396,51 @@ Providing a public getter, as per the following example, is acceptable:
}
}
Mismatched Class Properties / Constructor Parameters
````````````````````````````````````````````````````
Consider an example where you wish to ensure that a property of class whose type is some form of container is always sorted using some specific criteria yet you wish to maintain the immutability of the class.
This could be codified as follows:
.. container:: codeset
.. sourcecode:: kotlin
@CordaSerializable
class ConfirmRequest(statesToConsume: List<StateRef>, val transactionId: SecureHash) {
companion object {
private val stateRefComparator = compareBy<StateRef>({ it.txhash }, { it.index })
}
private val states = statesToConsume.sortedWith(stateRefComparator)
}
The intention in the example is to always ensure that the states are stored in a specific order regardless of the ordering
of the list used to initialise instances of the class. This is achieved by using the first constructor parameter as the
basis for a private member. However, because that member is not mentioned in the constructor (whose parameters determine
what is serializable as discussed above) it would not be serialized. In addition, as there is no provided mechanism to retrieve
a value for ``statesToConsume`` we would fail to build a serializer for this Class.
In this case a secondary constructor annotated with ``@ConstructorForDeserialization`` would not be a valid solution as the
two signatures would be the same. Best practice is thus to provide a getter for the constructor parameter which explicitly
associates it with the actual member variable.
.. container:: codeset
.. sourcecode:: kotlin
@CordaSerializable
class ConfirmRequest(statesToConsume: List<StateRef>, val transactionId: SecureHash) {
companion object {
private val stateRefComparator = compareBy<StateRef>({ it.txhash }, { it.index })
}
private val states = statesToConsume.sortedWith(stateRefComparator)
//Explicit "getter" for a property identified from the constructor parameters
fun getStatesToConsume() = states
}
Enums
`````
@ -451,5 +496,3 @@ and a version of the current state of the class instantiated.
More detail can be found in :doc:`serialization-default-evolution`.

View File

@ -48,9 +48,6 @@ The ``version`` property, which defaults to 1, specifies the flow's version. Thi
whenever there is a release of a flow which has changes that are not backwards-compatible. A non-backwards compatible
change is one that changes the interface of the flow.
Currently, CorDapp developers have to explicitly write logic to handle these flow version numbers. In the future,
however, the platform will use prescribed rules for handling versions.
What defines the interface of a flow?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The flow interface is defined by the sequence of ``send`` and ``receive`` calls between an ``InitiatingFlow`` and an
@ -103,28 +100,19 @@ following behaviour:
How do I upgrade my flows?
~~~~~~~~~~~~~~~~~~~~~~~~~~
For flag-day upgrades, the process is simple.
Assumptions
^^^^^^^^^^^
* All nodes in the business network can be shut down for a period of time
* All nodes retire the old flows and adopt the new flows at the same time
Process
^^^^^^^
1. Update the flow and test the changes. Increment the flow version number in the ``InitiatingFlow`` annotation
1. Update the flow and test the changes. Increment the flow version number in the ``InitiatingFlow`` annotation.
2. Ensure that all versions of the existing flow have finished running and there are no pending ``SchedulableFlows`` on
any of the nodes on the business network
3. Shut down all the nodes
4. Replace the existing CorDapp JAR with the CorDapp JAR containing the new flow
5. Start the nodes
any of the nodes on the business network. This can be done by *draining the node* (see below).
3. Shut down the node.
4. Replace the existing CorDapp JAR with the CorDapp JAR containing the new flow.
5. Start the node.
From this point onwards, all the nodes will be using the updated flows.
If you shut down all nodes and upgrade them all at the same time, any incompatible change can be made.
In situations where some nodes may still be using previous versions of a flow, the updated flows need to be
backwards-compatible.
In situations where some nodes may still be using previous versions of a flow and thus new versions of your flow may
talk to old versions, the updated flows need to be backwards-compatible. This will be the case for almost any real
deployment in which you cannot easily coordinate the rollout of new code across the network.
How do I ensure flow backwards-compatibility?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -262,37 +250,57 @@ In code, inlined subflows appear as regular ``FlowLogic`` instances without eith
Inlined flows are not versioned, as they inherit the version of their parent ``InitiatingFlow`` or ``InitiatedBy``
flow.
Are there any other considerations?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Suspended flows
^^^^^^^^^^^^^^^
Currently, serialised flow state machines persisted in the node's database cannot be updated. All flows must finish
before the updated flow classes are added to the node's plugins folder.
Flows that don't create sessions
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Flows which are not an ``InitiatingFlow`` or ``InitiatedBy`` flow, or inlined subflows that are not called from an
``InitiatingFlow`` or ``InitiatedBy`` flow, can be updated without consideration of backwards-compatibility. Flows of
this type include utility flows for querying the vault and flows for reaching out to external systems.
Flow drains
~~~~~~~~~~~
A flow *checkpoint* is a serialised snapshot of the flow's stack frames and any objects reachable from the stack.
Checkpoints are saved to the database automatically when a flow suspends or resumes, which typically happens when
sending or receiving messages. A flow may be replayed from the last checkpoint if the node restarts. Automatic
checkpointing is an unusual feature of Corda and significantly helps developers write reliable code that can survive
node restarts and crashes. It also assists with scaling up, as flows that are waiting for a response can be flushed
from memory.
However, this means that restoring an old checkpoint to a new version of a flow may cause resume failures. For example
if you remove a local variable from a method that previously had one, then the flow engine won't be able to figure out
where to put the stored value of the variable.
For this reason, in currently released versions of Corda you must *drain the node* before doing an app upgrade that
changes ``@Suspendable`` code. A drain blocks new flows from starting but allows existing flows to finish. Thus once
a drain is complete there should be no outstanding checkpoints or running flows. Upgrading the app will then succeed.
A node can be drained or undrained via RPC using the ``setFlowsDrainingModeEnabled`` method, and via the shell using
the standard ``run`` command to invoke the RPC. See :doc:`shell` to learn more.
Contract and state versioning
-----------------------------
Contracts and states can be upgraded if and only if all of the state's participants agree to the proposed upgrade. The
following combinations of upgrades are possible:
* A contract is upgraded while the state definition remains the same
* A state is upgraded while the contract stays the same
* The state and the contract are updated simultaneously
There are two types of contract/state upgrade:
1. *Implicit:* By allowing multiple implementations of the contract ahead of time, using constraints. See :doc:`api-contract-constraints` to learn more.
2. *Explicit:* By creating a special *contract upgrade transaction* and getting all participants of a state to sign it using the
contract upgrade flows.
This section of the documentation focuses only on the *explicit* type of upgrade.
In an explicit upgrade contracts and states can be changed in arbitrary ways, if and only if all of the state'
s participants agree to the proposed upgrade. The following combinations of upgrades are possible:
* A contract is upgraded while the state definition remains the same.
* A state is upgraded while the contract stays the same.
* The state and the contract are updated simultaneously.
The procedure for updating a state or a contract using a flag-day approach is quite simple:
* Update and test the state or contract
* Stop all the nodes on the business network
* Produce a new CorDapp JAR file and distribute it to all the relevant parties
* Start all nodes on the network
* Run the contract upgrade authorisation flow for each state that requires updating on every node
* For each state, one node should run the contract upgrade initiation flow
* Update and test the state or contract.
* Produce a new CorDapp JAR file and distribute it to all the relevant parties.
* Each node operator stops their node, replaces the existing JAR with the new one, and restarts. They may wish to do
a node drain first to avoid the definition of states or contracts changing whilst a flow is in progress.
* Run the contract upgrade authorisation flow for each state that requires updating on every node.
* For each state, one node should run the contract upgrade initiation flow, which will contact the rest.
Update Process
~~~~~~~~~~~~~~

View File

@ -26,6 +26,8 @@ For testing purposes, CorDapps may also include:
In production, a production-ready webserver should be used, and these files should be moved into a different module or
project so that they do not bloat the CorDapp at build time.
.. _cordapp-structure:
Structure
---------
You should base the structure of your project on the Java or Kotlin templates:

View File

@ -7,3 +7,6 @@ either be moved into the main modules and go through code review, or be deleted.
Code placed here can be committed to directly onto master at any time as long as it doesn't break the build
(no compile failures or unit test failures). Any commits here that break the build will simply be rolled back.
To help reduce the build times, unit tests for experimental projects have been disabled and will NOT run alongside
the whole project tests run via Gradle. Add parameter ```experimental.test.enable``` (example command is ```gradlew test -Dexperimental.test.enable```
to enable those tests.

View File

@ -34,7 +34,7 @@ dependencies {
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
compile "javassist:javassist:$javaassist_version"
compile "com.esotericsoftware:kryo:4.0.0"
compile "co.paralleluniverse:quasar-core:$quasar_version:jdk8"
compile "$quasar_group:quasar-core:$quasar_version:jdk8"
}
jar {

View File

@ -97,7 +97,7 @@ object TwoPartyTradeFlow {
val signTransactionFlow = object : SignTransactionFlow(otherSideSession, VERIFYING_AND_SIGNING.childProgressTracker()) {
override fun checkTransaction(stx: SignedTransaction) {
// Verify that we know who all the participants in the transaction are
val states: Iterable<ContractState> = stx.tx.inputs.map { serviceHub.loadState(it).data } + stx.tx.outputs.map { it.data }
val states: Iterable<ContractState> = serviceHub.loadStates(stx.tx.inputs.toSet()).map { it.state.data } + stx.tx.outputs.map { it.data }
states.forEach { state ->
state.participants.forEach { anon ->
require(serviceHub.identityService.wellKnownPartyFromAnonymous(anon) != null) {

View File

@ -88,7 +88,7 @@ class ObligationTests {
doReturn(MINI_CORP).whenever(it).partyFromKey(MINI_CORP_PUBKEY)
}
private val mockService = MockServices(listOf("net.corda.finance.contracts.asset"), MEGA_CORP.name, identityService)
private val ledgerServices get() = MockServices(emptyList(), MEGA_CORP.name, identityService)
private val ledgerServices get() = MockServices(listOf("net.corda.finance.contracts.asset", "net.corda.testing.contracts"), MEGA_CORP.name, identityService)
private fun cashObligationTestRoots(
group: LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>
) = group.apply {

View File

@ -9,10 +9,11 @@ import net.corda.core.utilities.getOrThrow
import net.corda.finance.DOLLARS
import net.corda.finance.`issued by`
import net.corda.finance.contracts.asset.Cash
import net.corda.node.internal.StartedNode
import net.corda.testing.core.*
import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.StartedMockNode
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.InternalMockNetwork.MockNode
import net.corda.testing.node.startFlow
import org.junit.After
import org.junit.Before
@ -21,16 +22,16 @@ import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class CashPaymentFlowTests {
private lateinit var mockNet: MockNetwork
private lateinit var mockNet: InternalMockNetwork
private val initialBalance = 2000.DOLLARS
private val ref = OpaqueBytes.of(0x01)
private lateinit var bankOfCordaNode: StartedMockNode
private lateinit var bankOfCordaNode: StartedNode<MockNode>
private lateinit var bankOfCorda: Party
private lateinit var aliceNode: StartedMockNode
private lateinit var aliceNode: StartedNode<MockNode>
@Before
fun start() {
mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin(), cordappPackages = listOf("net.corda.finance.contracts.asset"))
mockNet = InternalMockNetwork(servicePeerAllocationStrategy = RoundRobin(), cordappPackages = listOf("net.corda.finance.contracts.asset"))
bankOfCordaNode = mockNet.createPartyNode(BOC_NAME)
bankOfCorda = bankOfCordaNode.info.identityFromX500Name(BOC_NAME)
aliceNode = mockNet.createPartyNode(ALICE_NAME)

View File

@ -1,29 +0,0 @@
Gradle Plugins for Cordapps
===========================
The projects at this level of the project are gradle plugins for cordapps and are published to Maven Local with
the rest of the Corda libraries.
.. note::
Some of the plugins here are duplicated with the ones in buildSrc. While the duplication is unwanted any
currently known solution (such as publishing from buildSrc or setting up a separate project/repo) would
introduce a two step build which is less convenient.
Version number
--------------
To modify the version number edit constants.properties in root dir
Installing
----------
If you need to bootstrap the corda repository you can install these plugins with
.. code-block:: text
cd publish-utils
../../gradlew -u install
cd ../
../gradlew install

View File

@ -1,79 +0,0 @@
# API Scanner
Generates a text summary of Corda's public API that we can check for API-breaking changes.
```bash
$ gradlew generateApi
```
See [here](../../docs/source/corda-api.rst) for Corda's public API strategy. We will need to
apply this plugin to other modules in future Corda releases as those modules' APIs stabilise.
Basically, this plugin will document a module's `public` and `protected` classes/methods/fields,
excluding those from our `*.internal.*` packages, any synthetic methods, bridge methods, or methods
identified as having Kotlin's `internal` scope. (Kotlin doesn't seem to have implemented `internal`
scope for classes or fields yet as these are currently `public` inside the `.class` file.)
## Usage
Include this line in the `build.gradle` file of every Corda module that exports public API:
```gradle
apply plugin: 'net.corda.plugins.api-scanner'
```
This will create a Gradle task called `scanApi` which will analyse that module's Jar artifacts. More precisely,
it will analyse all of the Jar artifacts that have not been assigned a Maven classifier, on the basis
that these should be the module's main artifacts.
The `scanApi` task supports the following configuration options:
```gradle
scanApi {
// Make the classpath-scanning phase more verbose.
verbose = {true|false}
// Enable / disable the task within this module.
enabled = {true|false}
// Names of classes that should be excluded from the output.
excludeClasses = [
...
]
}
```
All of the `ScanApi` tasks write their output files to their own `$buildDir/api` directory, where they
are collated into a single output file by the `GenerateApi` task. The `GenerateApi` task is declared
in the root project's `build.gradle` file:
```gradle
task generateApi(type: net.corda.plugins.GenerateApi){
baseName = "api-corda"
}
```
The final API file is written to `$buildDir/api/$baseName-$project.version.txt`
### Sample Output
```
public interface net.corda.core.contracts.Attachment extends net.corda.core.contracts.NamedByHash
public abstract void extractFile(String, java.io.OutputStream)
@org.jetbrains.annotations.NotNull public abstract List getSigners()
@org.jetbrains.annotations.NotNull public abstract java.io.InputStream open()
@org.jetbrains.annotations.NotNull public abstract jar.JarInputStream openAsJAR()
##
public interface net.corda.core.contracts.AttachmentConstraint
public abstract boolean isSatisfiedBy(net.corda.core.contracts.Attachment)
##
public final class net.corda.core.contracts.AttachmentResolutionException extends net.corda.core.flows.FlowException
public <init>(net.corda.core.crypto.SecureHash)
@org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getHash()
##
```
#### Notes
The `GenerateApi` task will collate the output of every `ScanApi` task found either in the same project,
or in any of that project's subprojects. So it is _theoretically_ possible also to collate the API output
from subtrees of modules simply by defining a new `GenerateApi` task at the root of that subtree.
## Plugin Installation
See [here](../README.rst) for full installation instructions.

View File

@ -1,19 +0,0 @@
apply plugin: 'java'
apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'com.jfrog.artifactory'
description "Generates a summary of the artifact's public API"
repositories {
mavenCentral()
}
dependencies {
compile gradleApi()
compile "io.github.lukehutch:fast-classpath-scanner:2.7.0"
testCompile "junit:junit:4.12"
}
publish {
name project.name
}

View File

@ -1,70 +0,0 @@
package net.corda.plugins;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.file.FileCollection;
import org.gradle.api.tasks.TaskCollection;
import org.gradle.jvm.tasks.Jar;
public class ApiScanner implements Plugin<Project> {
/**
* Identify the Gradle Jar tasks creating jars
* without Maven classifiers, and generate API
* documentation for them.
* @param p Current project.
*/
@Override
public void apply(Project p) {
p.getLogger().info("Applying API scanner to {}", p.getName());
ScannerExtension extension = p.getExtensions().create("scanApi", ScannerExtension.class);
p.afterEvaluate(project -> {
TaskCollection<Jar> jarTasks = project.getTasks()
.withType(Jar.class)
.matching(jarTask -> jarTask.getClassifier().isEmpty() && jarTask.isEnabled());
if (jarTasks.isEmpty()) {
return;
}
project.getLogger().info("Adding scanApi task to {}", project.getName());
project.getTasks().create("scanApi", ScanApi.class, scanTask -> {
scanTask.setClasspath(compilationClasspath(project.getConfigurations()));
// Automatically creates a dependency on jar tasks.
scanTask.setSources(project.files(jarTasks));
scanTask.setExcludeClasses(extension.getExcludeClasses());
scanTask.setVerbose(extension.isVerbose());
scanTask.setEnabled(extension.isEnabled());
// Declare this ScanApi task to be a dependency of any
// GenerateApi tasks belonging to any of our ancestors.
project.getRootProject().getTasks()
.withType(GenerateApi.class)
.matching(generateTask -> isAncestorOf(generateTask.getProject(), project))
.forEach(generateTask -> generateTask.dependsOn(scanTask));
});
});
}
/*
* Recurse through a child project's parents until we reach the root,
* and return true iff we find our target project along the way.
*/
private static boolean isAncestorOf(Project target, Project child) {
Project p = child;
while (p != null) {
if (p == target) {
return true;
}
p = p.getParent();
}
return false;
}
private static FileCollection compilationClasspath(ConfigurationContainer configurations) {
return configurations.getByName("compile")
.plus(configurations.getByName("compileOnly"));
}
}

View File

@ -1,61 +0,0 @@
package net.corda.plugins;
import org.gradle.api.DefaultTask;
import org.gradle.api.file.FileCollection;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;
import java.io.*;
import java.nio.file.Files;
import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.toList;
@SuppressWarnings("unused")
public class GenerateApi extends DefaultTask {
private final File outputDir;
private String baseName;
public GenerateApi() {
outputDir = new File(getProject().getBuildDir(), "api");
baseName = "api-" + getProject().getName();
}
public void setBaseName(String baseName) {
this.baseName = baseName;
}
@InputFiles
public FileCollection getSources() {
return getProject().files(getProject().getAllprojects().stream()
.flatMap(project -> project.getTasks()
.withType(ScanApi.class)
.matching(ScanApi::isEnabled)
.stream())
.flatMap(scanTask -> scanTask.getTargets().getFiles().stream())
.sorted(comparing(File::getName))
.collect(toList())
);
}
@OutputFile
public File getTarget() {
return new File(outputDir, String.format("%s-%s.txt", baseName, getProject().getVersion()));
}
@TaskAction
public void generate() {
FileCollection apiFiles = getSources();
if (!apiFiles.isEmpty()) {
try (OutputStream output = new BufferedOutputStream(new FileOutputStream(getTarget()))) {
for (File apiFile : apiFiles) {
Files.copy(apiFile.toPath(), output);
}
} catch (IOException e) {
getLogger().error("Failed to generate API file", e);
}
}
}
}

View File

@ -1,389 +0,0 @@
package net.corda.plugins;
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner;
import io.github.lukehutch.fastclasspathscanner.scanner.ClassInfo;
import io.github.lukehutch.fastclasspathscanner.scanner.FieldInfo;
import io.github.lukehutch.fastclasspathscanner.scanner.MethodInfo;
import io.github.lukehutch.fastclasspathscanner.scanner.ScanResult;
import org.gradle.api.DefaultTask;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.FileCollection;
import org.gradle.api.tasks.CompileClasspath;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.OutputFiles;
import org.gradle.api.tasks.TaskAction;
import java.io.*;
import java.lang.annotation.Annotation;
import java.lang.annotation.Inherited;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.stream.StreamSupport;
import static java.util.Collections.*;
import static java.util.stream.Collectors.*;
@SuppressWarnings("unused")
public class ScanApi extends DefaultTask {
private static final int CLASS_MASK = Modifier.classModifiers();
private static final int INTERFACE_MASK = Modifier.interfaceModifiers() & ~Modifier.ABSTRACT;
private static final int METHOD_MASK = Modifier.methodModifiers();
private static final int FIELD_MASK = Modifier.fieldModifiers();
private static final int VISIBILITY_MASK = Modifier.PUBLIC | Modifier.PROTECTED;
private static final Set<String> ANNOTATION_BLACKLIST;
static {
Set<String> blacklist = new LinkedHashSet<>();
blacklist.add("kotlin.jvm.JvmOverloads");
ANNOTATION_BLACKLIST = unmodifiableSet(blacklist);
}
/**
* This information has been lifted from:
* @link <a href="https://github.com/JetBrains/kotlin/blob/master/core/runtime.jvm/src/kotlin/Metadata.kt">Metadata.kt</a>
*/
private static final String KOTLIN_METADATA = "kotlin.Metadata";
private static final String KOTLIN_CLASSTYPE_METHOD = "k";
private static final int KOTLIN_SYNTHETIC = 3;
private final ConfigurableFileCollection sources;
private final ConfigurableFileCollection classpath;
private final Set<String> excludeClasses;
private final File outputDir;
private boolean verbose;
public ScanApi() {
sources = getProject().files();
classpath = getProject().files();
excludeClasses = new LinkedHashSet<>();
outputDir = new File(getProject().getBuildDir(), "api");
}
@InputFiles
public FileCollection getSources() {
return sources;
}
void setSources(FileCollection sources) {
this.sources.setFrom(sources);
}
@CompileClasspath
@InputFiles
public FileCollection getClasspath() {
return classpath;
}
void setClasspath(FileCollection classpath) {
this.classpath.setFrom(classpath);
}
@Input
public Collection<String> getExcludeClasses() {
return unmodifiableSet(excludeClasses);
}
void setExcludeClasses(Collection<String> excludeClasses) {
this.excludeClasses.clear();
this.excludeClasses.addAll(excludeClasses);
}
@OutputFiles
public FileCollection getTargets() {
return getProject().files(
StreamSupport.stream(sources.spliterator(), false)
.map(this::toTarget)
.collect(toList())
);
}
public boolean isVerbose() {
return verbose;
}
void setVerbose(boolean verbose) {
this.verbose = verbose;
}
private File toTarget(File source) {
return new File(outputDir, source.getName().replaceAll(".jar$", ".txt"));
}
@TaskAction
public void scan() {
try (Scanner scanner = new Scanner(classpath)) {
for (File source : sources) {
scanner.scan(source);
}
} catch (IOException e) {
getLogger().error("Failed to write API file", e);
}
}
class Scanner implements Closeable {
private final URLClassLoader classpathLoader;
private final Class<? extends Annotation> metadataClass;
private final Method classTypeMethod;
@SuppressWarnings("unchecked")
Scanner(URLClassLoader classpathLoader) {
this.classpathLoader = classpathLoader;
Class<? extends Annotation> kClass;
Method kMethod;
try {
kClass = (Class<Annotation>) Class.forName(KOTLIN_METADATA, true, classpathLoader);
kMethod = kClass.getDeclaredMethod(KOTLIN_CLASSTYPE_METHOD);
} catch (ClassNotFoundException | NoSuchMethodException e) {
kClass = null;
kMethod = null;
}
metadataClass = kClass;
classTypeMethod = kMethod;
}
Scanner(FileCollection classpath) throws MalformedURLException {
this(new URLClassLoader(toURLs(classpath)));
}
@Override
public void close() throws IOException {
classpathLoader.close();
}
void scan(File source) {
File target = toTarget(source);
try (
URLClassLoader appLoader = new URLClassLoader(new URL[]{ toURL(source) }, classpathLoader);
PrintWriter writer = new PrintWriter(target, "UTF-8")
) {
scan(writer, appLoader);
} catch (IOException e) {
getLogger().error("API scan has failed", e);
}
}
void scan(PrintWriter writer, ClassLoader appLoader) {
ScanResult result = new FastClasspathScanner(getScanSpecification())
.overrideClassLoaders(appLoader)
.ignoreParentClassLoaders()
.ignoreMethodVisibility()
.ignoreFieldVisibility()
.enableMethodInfo()
.enableFieldInfo()
.verbose(verbose)
.scan();
writeApis(writer, result);
}
private String[] getScanSpecification() {
String[] spec = new String[2 + excludeClasses.size()];
spec[0] = "!"; // Don't blacklist system classes from the output.
spec[1] = "-dir:"; // Ignore classes on the filesystem.
int i = 2;
for (String excludeClass : excludeClasses) {
spec[i++] = '-' + excludeClass;
}
return spec;
}
private void writeApis(PrintWriter writer, ScanResult result) {
Map<String, ClassInfo> allInfo = result.getClassNameToClassInfo();
result.getNamesOfAllClasses().forEach(className -> {
if (className.contains(".internal.")) {
// These classes belong to internal Corda packages.
return;
}
ClassInfo classInfo = allInfo.get(className);
if (classInfo.getClassLoaders() == null) {
// Ignore classes that belong to one of our target ClassLoader's parents.
return;
}
Class<?> javaClass = result.classNameToClassRef(className);
if (!isVisible(javaClass.getModifiers())) {
// Excludes private and package-protected classes
return;
}
int kotlinClassType = getKotlinClassType(javaClass);
if (kotlinClassType == KOTLIN_SYNTHETIC) {
// Exclude classes synthesised by the Kotlin compiler.
return;
}
writeClass(writer, classInfo, javaClass.getModifiers());
writeMethods(writer, classInfo.getMethodAndConstructorInfo());
writeFields(writer, classInfo.getFieldInfo());
writer.println("##");
});
}
private void writeClass(PrintWriter writer, ClassInfo classInfo, int modifiers) {
if (classInfo.isAnnotation()) {
/*
* Annotation declaration.
*/
writer.append(Modifier.toString(modifiers & INTERFACE_MASK));
writer.append(" @interface ").print(classInfo);
} else if (classInfo.isStandardClass()) {
/*
* Class declaration.
*/
List<String> annotationNames = toNames(readClassAnnotationsFor(classInfo));
if (!annotationNames.isEmpty()) {
writer.append(asAnnotations(annotationNames));
}
writer.append(Modifier.toString(modifiers & CLASS_MASK));
writer.append(" class ").print(classInfo);
Set<ClassInfo> superclasses = classInfo.getDirectSuperclasses();
if (!superclasses.isEmpty()) {
writer.append(" extends ").print(stringOf(superclasses));
}
Set<ClassInfo> interfaces = classInfo.getDirectlyImplementedInterfaces();
if (!interfaces.isEmpty()) {
writer.append(" implements ").print(stringOf(interfaces));
}
} else {
/*
* Interface declaration.
*/
List<String> annotationNames = toNames(readInterfaceAnnotationsFor(classInfo));
if (!annotationNames.isEmpty()) {
writer.append(asAnnotations(annotationNames));
}
writer.append(Modifier.toString(modifiers & INTERFACE_MASK));
writer.append(" interface ").print(classInfo);
Set<ClassInfo> superinterfaces = classInfo.getDirectSuperinterfaces();
if (!superinterfaces.isEmpty()) {
writer.append(" extends ").print(stringOf(superinterfaces));
}
}
writer.println();
}
private void writeMethods(PrintWriter writer, List<MethodInfo> methods) {
sort(methods);
for (MethodInfo method : methods) {
if (isVisible(method.getAccessFlags()) // Only public and protected methods
&& isValid(method.getAccessFlags(), METHOD_MASK) // Excludes bridge and synthetic methods
&& !isKotlinInternalScope(method)) {
writer.append(" ").println(filterAnnotationsFor(method));
}
}
}
private void writeFields(PrintWriter output, List<FieldInfo> fields) {
sort(fields);
for (FieldInfo field : fields) {
if (isVisible(field.getAccessFlags()) && isValid(field.getAccessFlags(), FIELD_MASK)) {
output.append(" ").println(field);
}
}
}
private int getKotlinClassType(Class<?> javaClass) {
if (metadataClass != null) {
Annotation metadata = javaClass.getAnnotation(metadataClass);
if (metadata != null) {
try {
return (int) classTypeMethod.invoke(metadata);
} catch (IllegalAccessException | InvocationTargetException e) {
getLogger().error("Failed to read Kotlin annotation", e);
}
}
}
return 0;
}
private List<String> toNames(Collection<ClassInfo> classes) {
return classes.stream()
.map(ClassInfo::toString)
.filter(ScanApi::isApplicationClass)
.collect(toList());
}
private Set<ClassInfo> readClassAnnotationsFor(ClassInfo classInfo) {
Set<ClassInfo> annotations = new HashSet<>(classInfo.getAnnotations());
annotations.addAll(selectInheritedAnnotations(classInfo.getSuperclasses()));
annotations.addAll(selectInheritedAnnotations(classInfo.getImplementedInterfaces()));
return annotations;
}
private Set<ClassInfo> readInterfaceAnnotationsFor(ClassInfo classInfo) {
Set<ClassInfo> annotations = new HashSet<>(classInfo.getAnnotations());
annotations.addAll(selectInheritedAnnotations(classInfo.getSuperinterfaces()));
return annotations;
}
/**
* Returns those annotations which have themselves been annotated as "Inherited".
*/
private List<ClassInfo> selectInheritedAnnotations(Collection<ClassInfo> classes) {
return classes.stream()
.flatMap(cls -> cls.getAnnotations().stream())
.filter(ann -> ann.hasMetaAnnotation(Inherited.class.getName()))
.collect(toList());
}
private MethodInfo filterAnnotationsFor(MethodInfo method) {
return new MethodInfo(
method.getClassName(),
method.getMethodName(),
method.getAccessFlags(),
method.getTypeDescriptor(),
method.getAnnotationNames().stream()
.filter(ScanApi::isVisibleAnnotation)
.collect(toList())
);
}
}
private static boolean isVisibleAnnotation(String annotationName) {
return !ANNOTATION_BLACKLIST.contains(annotationName);
}
private static boolean isKotlinInternalScope(MethodInfo method) {
return method.getMethodName().indexOf('$') >= 0;
}
private static boolean isValid(int modifiers, int mask) {
return (modifiers & mask) == modifiers;
}
private static boolean isVisible(int accessFlags) {
return (accessFlags & VISIBILITY_MASK) != 0;
}
private static String stringOf(Collection<ClassInfo> items) {
return items.stream().map(ClassInfo::toString).collect(joining(", "));
}
private static String asAnnotations(Collection<String> items) {
return items.stream().collect(joining(" @", "@", " "));
}
private static boolean isApplicationClass(String typeName) {
return !typeName.startsWith("java.") && !typeName.startsWith("kotlin.");
}
private static URL toURL(File file) throws MalformedURLException {
return file.toURI().toURL();
}
private static URL[] toURLs(Iterable<File> files) throws MalformedURLException {
List<URL> urls = new LinkedList<>();
for (File file : files) {
urls.add(toURL(file));
}
return urls.toArray(new URL[urls.size()]);
}
}

View File

@ -1,37 +0,0 @@
package net.corda.plugins;
import java.util.List;
import static java.util.Collections.emptyList;
@SuppressWarnings("unused")
public class ScannerExtension {
private boolean verbose;
private boolean enabled = true;
private List<String> excludeClasses = emptyList();
public boolean isVerbose() {
return verbose;
}
public void setVerbose(boolean verbose) {
this.verbose = verbose;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public List<String> getExcludeClasses() {
return excludeClasses;
}
public void setExcludeClasses(List<String> excludeClasses) {
this.excludeClasses = excludeClasses;
}
}

View File

@ -1 +0,0 @@
implementation-class=net.corda.plugins.ApiScanner

View File

@ -1,84 +0,0 @@
// This script exists just to allow bootstrapping the gradle plugins if maven central or jcenter are unavailable
// or if you are developing these plugins. See the readme for more information.
buildscript {
// For sharing constants between builds
Properties constants = new Properties()
file("$projectDir/../constants.properties").withInputStream { constants.load(it) }
// If you bump this version you must re-bootstrap the codebase. See the README for more information.
ext {
gradle_plugins_version = constants.getProperty("gradlePluginsVersion")
bouncycastle_version = constants.getProperty("bouncycastleVersion")
typesafe_config_version = constants.getProperty("typesafeConfigVersion")
jsr305_version = constants.getProperty("jsr305Version")
kotlin_version = constants.getProperty("kotlinVersion")
artifactory_plugin_version = constants.getProperty('artifactoryPluginVersion')
snake_yaml_version = constants.getProperty('snakeYamlVersion')
}
repositories {
mavenLocal()
jcenter()
}
dependencies {
classpath "net.corda.plugins:publish-utils:$gradle_plugins_version"
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jfrog.buildinfo:build-info-extractor-gradle:$artifactory_plugin_version"
}
}
apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'com.jfrog.artifactory'
allprojects {
version gradle_plugins_version
group 'net.corda.plugins'
}
bintrayConfig {
user = System.getenv('CORDA_BINTRAY_USER')
key = System.getenv('CORDA_BINTRAY_KEY')
repo = 'corda'
org = 'r3'
licenses = ['Apache-2.0']
vcsUrl = 'https://github.com/corda/corda'
projectUrl = 'https://github.com/corda/corda'
gpgSign = true
gpgPassphrase = System.getenv('CORDA_BINTRAY_GPG_PASSPHRASE')
publications = ['cordformation', 'quasar-utils', 'cordform-common', 'api-scanner', 'cordapp']
license {
name = 'Apache-2.0'
url = 'https://www.apache.org/licenses/LICENSE-2.0'
distribution = 'repo'
}
developer {
id = 'R3'
name = 'R3'
email = 'dev@corda.net'
}
}
artifactory {
publish {
contextUrl = 'https://ci-artifactory.corda.r3cev.com/artifactory'
repository {
repoKey = 'corda-dev'
username = 'teamcity'
password = System.getenv('CORDA_ARTIFACTORY_PASSWORD')
}
defaults {
// Publish utils does not have a publish block because it would be circular for it to apply it's own
// extensions to itself
if(project.name == 'publish-utils') {
publications('publishUtils')
// Root project applies the plugin (for this block) but does not need to be published
} else if(project != rootProject) {
publications(project.extensions.publish.name())
}
}
}
}

View File

@ -1,10 +0,0 @@
# Cordapp Gradle Plugin
## Purpose
To transform any project this plugin is applied to into a cordapp project that generates a cordapp JAR.
## Effects
Will modify the default JAR task to create a CorDapp format JAR instead [see here](https://docs.corda.net/cordapp-build-systems.html)
for more information.

View File

@ -1,19 +0,0 @@
apply plugin: 'kotlin'
apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'com.jfrog.artifactory'
description 'Turns a project into a cordapp project that produces cordapp fat JARs'
repositories {
mavenCentral()
jcenter()
}
dependencies {
compile gradleApi()
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
}
publish {
name project.name
}

View File

@ -1,75 +0,0 @@
package net.corda.plugins
import org.gradle.api.*
import org.gradle.api.artifacts.*
import org.gradle.jvm.tasks.Jar
import java.io.File
/**
* The Cordapp plugin will turn a project into a cordapp project which builds cordapp JARs with the correct format
* and with the information needed to run on Corda.
*/
class CordappPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.logger.info("Configuring ${project.name} as a cordapp")
Utils.createCompileConfiguration("cordapp", project)
Utils.createCompileConfiguration("cordaCompile", project)
val configuration: Configuration = project.configurations.create("cordaRuntime")
configuration.isTransitive = false
project.configurations.single { it.name == "runtime" }.extendsFrom(configuration)
configureCordappJar(project)
}
/**
* Configures this project's JAR as a Cordapp JAR
*/
private fun configureCordappJar(project: Project) {
// Note: project.afterEvaluate did not have full dependency resolution completed, hence a task is used instead
val task = project.task("configureCordappFatJar")
val jarTask = project.tasks.getByName("jar") as Jar
task.doLast {
jarTask.from(getDirectNonCordaDependencies(project).map { project.zipTree(it)}).apply {
exclude("META-INF/*.SF")
exclude("META-INF/*.DSA")
exclude("META-INF/*.RSA")
}
}
jarTask.dependsOn(task)
}
private fun getDirectNonCordaDependencies(project: Project): Set<File> {
project.logger.info("Finding direct non-corda dependencies for inclusion in CorDapp JAR")
val excludes = listOf(
mapOf("group" to "org.jetbrains.kotlin", "name" to "kotlin-stdlib"),
mapOf("group" to "org.jetbrains.kotlin", "name" to "kotlin-stdlib-jre8"),
mapOf("group" to "org.jetbrains.kotlin", "name" to "kotlin-reflect"),
mapOf("group" to "co.paralleluniverse", "name" to "quasar-core")
)
val runtimeConfiguration = project.configuration("runtime")
// The direct dependencies of this project
val excludeDeps = project.configuration("cordapp").allDependencies +
project.configuration("cordaCompile").allDependencies +
project.configuration("cordaRuntime").allDependencies
val directDeps = runtimeConfiguration.allDependencies - excludeDeps
// We want to filter out anything Corda related or provided by Corda, like kotlin-stdlib and quasar
val filteredDeps = directDeps.filter { dep ->
excludes.none { exclude -> (exclude["group"] == dep.group) && (exclude["name"] == dep.name) }
}
filteredDeps.forEach {
// net.corda or com.r3.corda.enterprise may be a core dependency which shouldn't be included in this cordapp so give a warning
val group = it.group?.toString() ?: ""
if (group.startsWith("net.corda.") || group.startsWith("com.r3.corda.enterprise.")) {
project.logger.warn("You appear to have included a Corda platform component ($it) using a 'compile' or 'runtime' dependency." +
"This can cause node stability problems. Please use 'corda' instead." +
"See http://docs.corda.net/cordapp-build-systems.html")
} else {
project.logger.info("Including dependency in CorDapp JAR: $it")
}
}
return filteredDeps.map { runtimeConfiguration.files(it) }.flatten().toSet()
}
}

View File

@ -1,35 +0,0 @@
package net.corda.plugins
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.Configuration
import org.gradle.api.plugins.ExtraPropertiesExtension
/**
* Mimics the "project.ext" functionality in groovy which provides a direct
* accessor to the "ext" extention (See: ExtraPropertiesExtension)
*/
@Suppress("UNCHECKED_CAST")
fun <T : Any> Project.ext(name: String): T = (extensions.findByName("ext") as ExtraPropertiesExtension).get(name) as T
fun Project.configuration(name: String): Configuration = configurations.single { it.name == name }
class Utils {
companion object {
@JvmStatic
fun createCompileConfiguration(name: String, project: Project) {
if(!project.configurations.any { it.name == name }) {
val configuration = project.configurations.create(name)
configuration.isTransitive = false
project.configurations.single { it.name == "compile" }.extendsFrom(configuration)
}
}
fun createRuntimeConfiguration(name: String, project: Project) {
if(!project.configurations.any { it.name == name }) {
val configuration = project.configurations.create(name)
configuration.isTransitive = false
project.configurations.single { it.name == "runtime" }.extendsFrom(configuration)
}
}
}
}

View File

@ -1 +0,0 @@
implementation-class=net.corda.plugins.CordappPlugin

View File

@ -1,4 +0,0 @@
# Cordform Common
This project contains common node types that both the Corda gradle plugin suite and Corda project
require in order to build Corda nodes.

View File

@ -1,24 +0,0 @@
apply plugin: 'java'
apply plugin: 'maven-publish'
apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'com.jfrog.artifactory'
repositories {
mavenCentral()
}
// This tracks the gradle plugins version and not Corda
version gradle_plugins_version
group 'net.corda.plugins'
dependencies {
// JSR 305: Nullability annotations
compile "com.google.code.findbugs:jsr305:$jsr305_version"
// TypeSafe Config: for simple and human friendly config files.
compile "com.typesafe:config:$typesafe_config_version"
}
publish {
name project.name
}

View File

@ -1,7 +0,0 @@
package net.corda.cordform;
import java.nio.file.Path;
public interface CordformContext {
Path baseDirectory(String nodeName);
}

View File

@ -1,40 +0,0 @@
package net.corda.cordform;
import javax.annotation.Nonnull;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
public abstract class CordformDefinition {
private Path nodesDirectory = Paths.get("build", "nodes");
private final List<Consumer<CordformNode>> nodeConfigurers = new ArrayList<>();
private final List<String> cordappPackages = new ArrayList<>();
public Path getNodesDirectory() {
return nodesDirectory;
}
public void setNodesDirectory(Path nodesDirectory) {
this.nodesDirectory = nodesDirectory;
}
public List<Consumer<CordformNode>> getNodeConfigurers() {
return nodeConfigurers;
}
public void addNode(Consumer<CordformNode> configurer) {
nodeConfigurers.add(configurer);
}
public List<String> getCordappPackages() {
return cordappPackages;
}
/**
* Make arbitrary changes to the node directories before they are started.
* @param context Lookup of node directory by node name.
*/
public abstract void setup(@Nonnull CordformContext context);
}

View File

@ -1,190 +0,0 @@
package net.corda.cordform;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import com.typesafe.config.ConfigValueFactory;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.List;
import java.util.Map;
import static java.util.Collections.emptyList;
public class CordformNode implements NodeDefinition {
/**
* Path relative to the running node where the serialized NodeInfos are stored.
*/
public static final String NODE_INFO_DIRECTORY = "additional-node-infos";
protected static final String DEFAULT_HOST = "localhost";
/**
* Name of the node. Node will be placed in directory based on this name - all lowercase with whitespaces removed.
* Actual node name inside node.conf will be as set here.
*/
private String name;
public String getName() {
return name;
}
/**
* p2p Port.
*/
private int p2pPort = 10002;
public int getP2pPort() { return p2pPort; }
/**
* RPC Port.
*/
private int rpcPort = 10003;
public int getRpcPort() { return rpcPort; }
/**
* Set the RPC users for this node. This configuration block allows arbitrary configuration.
* The recommended current structure is:
* [[['username': "username_here", 'password': "password_here", 'permissions': ["permissions_here"]]]
* The above is a list to a map of keys to values using Groovy map and list shorthands.
*
* Incorrect configurations will not cause a DSL error.
*/
public List<Map<String, Object>> rpcUsers = emptyList();
/**
* Apply the notary configuration if this node is a notary. The map is the config structure of
* net.corda.node.services.config.NotaryConfig
*/
public Map<String, Object> notary = null;
public Map<String, Object> extraConfig = null;
protected Config config = ConfigFactory.empty();
public Config getConfig() {
return config;
}
/**
* Set the name of the node.
*
* @param name The node name.
*/
public void name(String name) {
this.name = name;
setValue("myLegalName", name);
}
/**
* Get the artemis address for this node.
*
* @return This node's P2P address.
*/
@Nonnull
public String getP2pAddress() {
return config.getString("p2pAddress");
}
/**
* Set the Artemis P2P port for this node on localhost.
*
* @param p2pPort The Artemis messaging queue port.
*/
public void p2pPort(int p2pPort) {
p2pAddress(DEFAULT_HOST + ':' + p2pPort);
this.p2pPort = p2pPort;
}
/**
* Set the Artemis P2P address for this node.
*
* @param p2pAddress The Artemis messaging queue host and port.
*/
public void p2pAddress(String p2pAddress) {
setValue("p2pAddress", p2pAddress);
}
/**
* Returns the RPC address for this node, or null if one hasn't been specified.
*/
@Nullable
public String getRpcAddress() {
if (config.hasPath("rpcSettings.address")) {
return config.getConfig("rpcSettings").getString("address");
}
return getOptionalString("rpcAddress");
}
/**
* Set the Artemis RPC port for this node on localhost.
*
* @param rpcPort The Artemis RPC queue port.
* @deprecated Use {@link CordformNode#rpcSettings(RpcSettings)} instead.
*/
@Deprecated
public void rpcPort(int rpcPort) {
rpcAddress(DEFAULT_HOST + ':' + rpcPort);
this.rpcPort = rpcPort;
}
/**
* Set the Artemis RPC address for this node.
*
* @param rpcAddress The Artemis RPC queue host and port.
* @deprecated Use {@link CordformNode#rpcSettings(RpcSettings)} instead.
*/
@Deprecated
public void rpcAddress(String rpcAddress) {
setValue("rpcAddress", rpcAddress);
}
/**
* Returns the address of the web server that will connect to the node, or null if one hasn't been specified.
*/
@Nullable
public String getWebAddress() {
return getOptionalString("webAddress");
}
/**
* Configure a webserver to connect to the node via RPC. This port will specify the port it will listen on. The node
* must have an RPC address configured.
*/
public void webPort(int webPort) {
webAddress(DEFAULT_HOST + ':' + webPort);
}
/**
* Configure a webserver to connect to the node via RPC. This address will specify the port it will listen on. The node
* must have an RPC address configured.
*/
public void webAddress(String webAddress) {
setValue("webAddress", webAddress);
}
/**
* Specifies RPC settings for the node.
*/
public void rpcSettings(RpcSettings settings) {
config = settings.addTo("rpcSettings", config);
}
/**
* Set the path to a file with optional properties, which are appended to the generated node.conf file.
*
* @param configFile The file path.
*/
public void configFile(String configFile) {
setValue("configFile", configFile);
}
private String getOptionalString(String path) {
return config.hasPath(path) ? config.getString(path) : null;
}
private void setValue(String path, Object value) {
config = config.withValue(path, ConfigValueFactory.fromAnyRef(value));
}
}

Some files were not shown because too many files have changed in this diff Show More