mirror of
https://github.com/corda/corda.git
synced 2025-03-14 00:06:45 +00:00
Merge branch 'master' of github.com:corda/corda
This commit is contained in:
commit
6796691611
@ -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()
|
||||
##
|
||||
|
@ -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
9
.idea/compiler.xml
generated
@ -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>
|
35
build.gradle
35
build.gradle
@ -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')
|
||||
|
@ -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)
|
||||
|
@ -15,6 +15,14 @@ configurations {
|
||||
smokeTestRuntime.extendsFrom runtime
|
||||
}
|
||||
|
||||
compileKotlin {
|
||||
kotlinOptions.jvmTarget = "1.8"
|
||||
}
|
||||
|
||||
compileTestKotlin {
|
||||
kotlinOptions.jvmTarget = "1.8"
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
integrationTest {
|
||||
kotlin {
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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}')"
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
||||
|
@ -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)
|
70
core/src/main/kotlin/net/corda/core/cordapp/CordappConfig.kt
Normal file
70
core/src/main/kotlin/net/corda/core/cordapp/CordappConfig.kt
Normal 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
|
||||
}
|
@ -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
|
||||
)
|
||||
|
@ -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)))
|
||||
}
|
||||
|
||||
/**
|
||||
|
101
core/src/main/kotlin/net/corda/core/flows/NotarisationRequest.kt
Normal file
101
core/src/main/kotlin/net/corda/core/flows/NotarisationRequest.kt
Normal 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
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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" }
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
/**
|
||||
|
@ -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?
|
||||
|
||||
|
@ -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")))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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))
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
@ -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"))
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
})
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
******
|
||||
|
||||
|
@ -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
467
docs/source/api-testing.rst
Normal 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>`_
|
@ -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.
|
||||
|
||||
|
@ -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:
|
||||
|
||||
|
@ -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
|
||||
|
@ -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>`.
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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>`_.
|
||||
|
||||
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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 = [
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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();
|
||||
|
@ -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)
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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.
|
||||
|
@ -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:
|
||||
|
@ -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`.
|
||||
|
||||
|
||||
|
||||
|
@ -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
|
||||
~~~~~~~~~~~~~~
|
||||
|
@ -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:
|
||||
|
@ -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.
|
@ -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 {
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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.
|
@ -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
|
||||
}
|
@ -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"));
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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()]);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
implementation-class=net.corda.plugins.ApiScanner
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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.
|
@ -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
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1 +0,0 @@
|
||||
implementation-class=net.corda.plugins.CordappPlugin
|
@ -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.
|
@ -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
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package net.corda.cordform;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
public interface CordformContext {
|
||||
Path baseDirectory(String nodeName);
|
||||
}
|
@ -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);
|
||||
}
|
@ -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
Loading…
x
Reference in New Issue
Block a user