diff --git a/.ci/api-current.txt b/.ci/api-current.txt index 6e8e8df371..e66520b6f2 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -1,4 +1,4 @@ -public class net.corda.core.CordaException extends java.lang.Exception implements net.corda.core.CordaThrowable +@net.corda.core.serialization.CordaSerializable public class net.corda.core.CordaException extends java.lang.Exception implements net.corda.core.CordaThrowable public () public (String) public (String, String, Throwable) @@ -14,7 +14,7 @@ public class net.corda.core.CordaException extends java.lang.Exception implement public void setMessage(String) public void setOriginalExceptionClassName(String) ## -public class net.corda.core.CordaRuntimeException extends java.lang.RuntimeException implements net.corda.core.CordaThrowable +@net.corda.core.serialization.CordaSerializable public class net.corda.core.CordaRuntimeException extends java.lang.RuntimeException implements net.corda.core.CordaThrowable public (String) public (String, String, Throwable) public (String, Throwable) @@ -29,7 +29,7 @@ public class net.corda.core.CordaRuntimeException extends java.lang.RuntimeExcep public void setMessage(String) public void setOriginalExceptionClassName(String) ## -public interface net.corda.core.CordaThrowable +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.CordaThrowable public abstract void addSuppressed(Throwable[]) @org.jetbrains.annotations.Nullable public abstract String getOriginalExceptionClassName() @org.jetbrains.annotations.Nullable public abstract String getOriginalMessage() @@ -37,6 +37,8 @@ public interface net.corda.core.CordaThrowable public abstract void setMessage(String) public abstract void setOriginalExceptionClassName(String) ## +public @interface net.corda.core.DoNotImplement +## public final class net.corda.core.Utils extends java.lang.Object @org.jetbrains.annotations.NotNull public static final net.corda.core.concurrent.CordaFuture toFuture(rx.Observable) @org.jetbrains.annotations.NotNull public static final rx.Observable toObservable(net.corda.core.concurrent.CordaFuture) @@ -51,11 +53,11 @@ public interface net.corda.core.concurrent.CordaFuture extends java.util.concurr public abstract void then(kotlin.jvm.functions.Function1) @org.jetbrains.annotations.NotNull public abstract concurrent.CompletableFuture toCompletableFuture() ## -public final class net.corda.core.contracts.AlwaysAcceptAttachmentConstraint extends java.lang.Object implements net.corda.core.contracts.AttachmentConstraint +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.AlwaysAcceptAttachmentConstraint extends java.lang.Object implements net.corda.core.contracts.AttachmentConstraint public boolean isSatisfiedBy(net.corda.core.contracts.Attachment) public static final net.corda.core.contracts.AlwaysAcceptAttachmentConstraint INSTANCE ## -public final class net.corda.core.contracts.Amount extends java.lang.Object implements java.lang.Comparable +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.Amount extends java.lang.Object implements java.lang.Comparable public (long, Object) public (long, java.math.BigDecimal, Object) public int compareTo(net.corda.core.contracts.Amount) @@ -95,7 +97,7 @@ public static final class net.corda.core.contracts.Amount$Companion extends java @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.Amount sumOrZero(Iterable, Object) @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.Amount zero(Object) ## -public final class net.corda.core.contracts.AmountTransfer extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.AmountTransfer extends java.lang.Object public (long, Object, Object, Object) @org.jetbrains.annotations.NotNull public final List apply(List, Object) @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.AmountTransfer copy(long, Object, Object, Object) @@ -119,24 +121,24 @@ public static final class net.corda.core.contracts.AmountTransfer$Companion exte @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.AmountTransfer fromDecimal(java.math.BigDecimal, Object, Object, Object, java.math.RoundingMode) @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.AmountTransfer zero(Object, Object, Object) ## -public interface net.corda.core.contracts.Attachment extends net.corda.core.contracts.NamedByHash +@net.corda.core.serialization.CordaSerializable 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 +@net.corda.core.serialization.CordaSerializable 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 +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.AttachmentResolutionException extends net.corda.core.flows.FlowException public (net.corda.core.crypto.SecureHash) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getHash() ## -public final class net.corda.core.contracts.AutomaticHashConstraint extends java.lang.Object implements net.corda.core.contracts.AttachmentConstraint +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.AutomaticHashConstraint extends java.lang.Object implements net.corda.core.contracts.AttachmentConstraint public boolean isSatisfiedBy(net.corda.core.contracts.Attachment) public static final net.corda.core.contracts.AutomaticHashConstraint INSTANCE ## -public final class net.corda.core.contracts.Command extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.Command extends java.lang.Object public (net.corda.core.contracts.CommandData, java.security.PublicKey) public (net.corda.core.contracts.CommandData, List) @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.CommandData component1() @@ -159,9 +161,9 @@ public final class net.corda.core.contracts.CommandAndState extends java.lang.Ob public int hashCode() public String toString() ## -public interface net.corda.core.contracts.CommandData +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.contracts.CommandData ## -public final class net.corda.core.contracts.CommandWithParties extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.CommandWithParties extends java.lang.Object public (List, List, net.corda.core.contracts.CommandData) @org.jetbrains.annotations.NotNull public final List component1() @org.jetbrains.annotations.NotNull public final List component2() @@ -179,10 +181,10 @@ public final class net.corda.core.contracts.ComponentGroupEnum extends java.lang public static net.corda.core.contracts.ComponentGroupEnum valueOf(String) public static net.corda.core.contracts.ComponentGroupEnum[] values() ## -public interface net.corda.core.contracts.Contract +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.contracts.Contract public abstract void verify(net.corda.core.transactions.LedgerTransaction) ## -public final class net.corda.core.contracts.ContractAttachment extends java.lang.Object implements net.corda.core.contracts.Attachment +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.ContractAttachment extends java.lang.Object implements net.corda.core.contracts.Attachment public (net.corda.core.contracts.Attachment, String) public void extractFile(String, java.io.OutputStream) @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.Attachment getAttachment() @@ -192,19 +194,19 @@ public final class net.corda.core.contracts.ContractAttachment extends java.lang @org.jetbrains.annotations.NotNull public java.io.InputStream open() @org.jetbrains.annotations.NotNull public jar.JarInputStream openAsJAR() ## -public interface net.corda.core.contracts.ContractState +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.contracts.ContractState @org.jetbrains.annotations.NotNull public abstract List getParticipants() ## public final class net.corda.core.contracts.ContractsDSL extends java.lang.Object @org.jetbrains.annotations.NotNull public static final net.corda.core.contracts.CommandWithParties requireSingleCommand(Collection, Class) public static final Object requireThat(kotlin.jvm.functions.Function1) ## -public interface net.corda.core.contracts.FungibleAsset extends net.corda.core.contracts.OwnableState +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.contracts.FungibleAsset extends net.corda.core.contracts.OwnableState @org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.Amount getAmount() @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) ## -public final class net.corda.core.contracts.HashAttachmentConstraint extends java.lang.Object implements net.corda.core.contracts.AttachmentConstraint +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.HashAttachmentConstraint extends java.lang.Object implements net.corda.core.contracts.AttachmentConstraint public (net.corda.core.crypto.SecureHash) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component1() @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.HashAttachmentConstraint copy(net.corda.core.crypto.SecureHash) @@ -214,11 +216,11 @@ public final class net.corda.core.contracts.HashAttachmentConstraint extends jav public boolean isSatisfiedBy(net.corda.core.contracts.Attachment) public String toString() ## -public final class net.corda.core.contracts.InsufficientBalanceException extends net.corda.core.flows.FlowException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.InsufficientBalanceException extends net.corda.core.flows.FlowException public (net.corda.core.contracts.Amount) @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.Amount getAmountMissing() ## -public final class net.corda.core.contracts.Issued extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.Issued extends java.lang.Object public (net.corda.core.contracts.PartyAndReference, Object) @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.PartyAndReference component1() @org.jetbrains.annotations.NotNull public final Object component2() @@ -232,20 +234,20 @@ public final class net.corda.core.contracts.Issued extends java.lang.Object public @interface net.corda.core.contracts.LegalProseReference public abstract String uri() ## -public interface net.corda.core.contracts.LinearState extends net.corda.core.contracts.ContractState +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.contracts.LinearState extends net.corda.core.contracts.ContractState @org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.UniqueIdentifier getLinearId() ## -public interface net.corda.core.contracts.MoveCommand extends net.corda.core.contracts.CommandData +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.contracts.MoveCommand extends net.corda.core.contracts.CommandData @org.jetbrains.annotations.Nullable public abstract Class getContract() ## public interface net.corda.core.contracts.NamedByHash @org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.SecureHash getId() ## -public interface net.corda.core.contracts.OwnableState extends net.corda.core.contracts.ContractState +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.contracts.OwnableState extends net.corda.core.contracts.ContractState @org.jetbrains.annotations.NotNull public abstract net.corda.core.identity.AbstractParty getOwner() @org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.CommandAndState withNewOwner(net.corda.core.identity.AbstractParty) ## -public final class net.corda.core.contracts.PartyAndReference extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.PartyAndReference extends java.lang.Object public (net.corda.core.identity.AbstractParty, net.corda.core.utilities.OpaqueBytes) @org.jetbrains.annotations.NotNull public final net.corda.core.identity.AbstractParty component1() @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.OpaqueBytes component2() @@ -256,7 +258,7 @@ public final class net.corda.core.contracts.PartyAndReference extends java.lang. public int hashCode() @org.jetbrains.annotations.NotNull public String toString() ## -public final class net.corda.core.contracts.PrivacySalt extends net.corda.core.utilities.OpaqueBytes +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.PrivacySalt extends net.corda.core.utilities.OpaqueBytes public () public (byte[]) ## @@ -264,7 +266,7 @@ public final class net.corda.core.contracts.Requirements extends java.lang.Objec public final void using(String, boolean) public static final net.corda.core.contracts.Requirements INSTANCE ## -public interface net.corda.core.contracts.SchedulableState extends net.corda.core.contracts.ContractState +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.contracts.SchedulableState extends net.corda.core.contracts.ContractState @org.jetbrains.annotations.Nullable public abstract net.corda.core.contracts.ScheduledActivity nextScheduledActivity(net.corda.core.contracts.StateRef, net.corda.core.flows.FlowLogicRefFactory) ## public interface net.corda.core.contracts.Scheduled @@ -316,7 +318,7 @@ public final class net.corda.core.contracts.StateAndContract extends java.lang.O public int hashCode() public String toString() ## -public final class net.corda.core.contracts.StateAndRef extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.StateAndRef extends java.lang.Object public (net.corda.core.contracts.TransactionState, net.corda.core.contracts.StateRef) @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.TransactionState component1() @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.StateRef component2() @@ -327,7 +329,7 @@ public final class net.corda.core.contracts.StateAndRef extends java.lang.Object public int hashCode() public String toString() ## -public final class net.corda.core.contracts.StateRef extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.StateRef extends java.lang.Object public (net.corda.core.crypto.SecureHash, int) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component1() public final int component2() @@ -342,7 +344,7 @@ public final class net.corda.core.contracts.Structures extends java.lang.Object @org.jetbrains.annotations.NotNull public static final net.corda.core.crypto.SecureHash hash(net.corda.core.contracts.ContractState) @org.jetbrains.annotations.NotNull public static final net.corda.core.contracts.Amount withoutIssuer(net.corda.core.contracts.Amount) ## -public abstract class net.corda.core.contracts.TimeWindow extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.contracts.TimeWindow extends java.lang.Object public () @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.contracts.TimeWindow between(java.time.Instant, java.time.Instant) public abstract boolean contains(java.time.Instant) @@ -365,11 +367,11 @@ public static final class net.corda.core.contracts.TimeWindow$Companion extends public interface net.corda.core.contracts.TokenizableAssetInfo @org.jetbrains.annotations.NotNull public abstract java.math.BigDecimal getDisplayTokenSize() ## -public final class net.corda.core.contracts.TransactionResolutionException extends net.corda.core.flows.FlowException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.TransactionResolutionException extends net.corda.core.flows.FlowException public (net.corda.core.crypto.SecureHash) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getHash() ## -public final class net.corda.core.contracts.TransactionState extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.TransactionState extends java.lang.Object public (net.corda.core.contracts.ContractState, String, net.corda.core.identity.Party) public (net.corda.core.contracts.ContractState, String, net.corda.core.identity.Party, Integer) public (net.corda.core.contracts.ContractState, String, net.corda.core.identity.Party, Integer, net.corda.core.contracts.AttachmentConstraint) @@ -390,51 +392,51 @@ public final class net.corda.core.contracts.TransactionState extends java.lang.O ## public final class net.corda.core.contracts.TransactionStateKt extends java.lang.Object ## -public abstract class net.corda.core.contracts.TransactionVerificationException extends net.corda.core.flows.FlowException +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.contracts.TransactionVerificationException extends net.corda.core.flows.FlowException @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getTxId() ## -public static final class net.corda.core.contracts.TransactionVerificationException$ContractConstraintRejection extends net.corda.core.contracts.TransactionVerificationException +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.contracts.TransactionVerificationException$ContractConstraintRejection extends net.corda.core.contracts.TransactionVerificationException public (net.corda.core.crypto.SecureHash, String) ## -public static final class net.corda.core.contracts.TransactionVerificationException$ContractCreationError extends net.corda.core.contracts.TransactionVerificationException +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.contracts.TransactionVerificationException$ContractCreationError extends net.corda.core.contracts.TransactionVerificationException public (net.corda.core.crypto.SecureHash, String, Throwable) ## -public static final class net.corda.core.contracts.TransactionVerificationException$ContractRejection extends net.corda.core.contracts.TransactionVerificationException +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.contracts.TransactionVerificationException$ContractRejection extends net.corda.core.contracts.TransactionVerificationException public (net.corda.core.crypto.SecureHash, net.corda.core.contracts.Contract, Throwable) ## -public static final class net.corda.core.contracts.TransactionVerificationException$Direction extends java.lang.Enum +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.contracts.TransactionVerificationException$Direction extends java.lang.Enum protected (String, int) public static net.corda.core.contracts.TransactionVerificationException$Direction valueOf(String) public static net.corda.core.contracts.TransactionVerificationException$Direction[] values() ## -public static final class net.corda.core.contracts.TransactionVerificationException$DuplicateInputStates extends net.corda.core.contracts.TransactionVerificationException +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.contracts.TransactionVerificationException$DuplicateInputStates extends net.corda.core.contracts.TransactionVerificationException public (net.corda.core.crypto.SecureHash, net.corda.core.utilities.NonEmptySet) @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.NonEmptySet getDuplicates() ## -public static final class net.corda.core.contracts.TransactionVerificationException$InvalidNotaryChange extends net.corda.core.contracts.TransactionVerificationException +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.contracts.TransactionVerificationException$InvalidNotaryChange extends net.corda.core.contracts.TransactionVerificationException public (net.corda.core.crypto.SecureHash) ## -public static final class net.corda.core.contracts.TransactionVerificationException$MissingAttachmentRejection extends net.corda.core.contracts.TransactionVerificationException +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.contracts.TransactionVerificationException$MissingAttachmentRejection extends net.corda.core.contracts.TransactionVerificationException public (net.corda.core.crypto.SecureHash, String) ## -public static final class net.corda.core.contracts.TransactionVerificationException$MoreThanOneNotary extends net.corda.core.contracts.TransactionVerificationException +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.contracts.TransactionVerificationException$MoreThanOneNotary extends net.corda.core.contracts.TransactionVerificationException public (net.corda.core.crypto.SecureHash) ## -public static final class net.corda.core.contracts.TransactionVerificationException$NotaryChangeInWrongTransactionType extends net.corda.core.contracts.TransactionVerificationException +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.contracts.TransactionVerificationException$NotaryChangeInWrongTransactionType extends net.corda.core.contracts.TransactionVerificationException public (net.corda.core.crypto.SecureHash, net.corda.core.identity.Party, net.corda.core.identity.Party) ## -public static final class net.corda.core.contracts.TransactionVerificationException$SignersMissing extends net.corda.core.contracts.TransactionVerificationException +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.contracts.TransactionVerificationException$SignersMissing extends net.corda.core.contracts.TransactionVerificationException public (net.corda.core.crypto.SecureHash, List) ## -public static final class net.corda.core.contracts.TransactionVerificationException$TransactionMissingEncumbranceException extends net.corda.core.contracts.TransactionVerificationException +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.contracts.TransactionVerificationException$TransactionMissingEncumbranceException extends net.corda.core.contracts.TransactionVerificationException public (net.corda.core.crypto.SecureHash, int, net.corda.core.contracts.TransactionVerificationException$Direction) ## -public abstract class net.corda.core.contracts.TypeOnlyCommandData extends java.lang.Object implements net.corda.core.contracts.CommandData +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.contracts.TypeOnlyCommandData extends java.lang.Object implements net.corda.core.contracts.CommandData public () public boolean equals(Object) public int hashCode() ## -public final class net.corda.core.contracts.UniqueIdentifier extends java.lang.Object implements java.lang.Comparable +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.UniqueIdentifier extends java.lang.Object implements java.lang.Comparable public () public (String, UUID) public int compareTo(net.corda.core.contracts.UniqueIdentifier) @@ -451,11 +453,11 @@ public final class net.corda.core.contracts.UniqueIdentifier extends java.lang.O public static final class net.corda.core.contracts.UniqueIdentifier$Companion extends java.lang.Object @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.UniqueIdentifier fromString(String) ## -public interface net.corda.core.contracts.UpgradedContract extends net.corda.core.contracts.Contract +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.contracts.UpgradedContract extends net.corda.core.contracts.Contract @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) ## -public interface net.corda.core.cordapp.Cordapp +@net.corda.core.DoNotImplement public interface net.corda.core.cordapp.Cordapp @org.jetbrains.annotations.NotNull public abstract List getContractClassNames() @org.jetbrains.annotations.NotNull public abstract List getCordappClasses() @org.jetbrains.annotations.NotNull public abstract Set getCustomSchemas() @@ -474,7 +476,7 @@ public final class net.corda.core.cordapp.CordappContext extends java.lang.Objec @org.jetbrains.annotations.NotNull public final ClassLoader getClassLoader() @org.jetbrains.annotations.NotNull public final net.corda.core.cordapp.Cordapp getCordapp() ## -public interface net.corda.core.cordapp.CordappProvider +@net.corda.core.DoNotImplement public interface net.corda.core.cordapp.CordappProvider @org.jetbrains.annotations.NotNull public abstract net.corda.core.cordapp.CordappContext getAppContext() @org.jetbrains.annotations.Nullable public abstract net.corda.core.crypto.SecureHash getContractAttachmentID(String) ## @@ -489,7 +491,7 @@ public class net.corda.core.crypto.Base58 extends java.lang.Object public static java.math.BigInteger decodeToBigInteger(String) public static String encode(byte[]) ## -public final class net.corda.core.crypto.CompositeKey extends java.lang.Object implements java.security.PublicKey +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.crypto.CompositeKey extends java.lang.Object implements java.security.PublicKey public final void checkValidity() public boolean equals(Object) @org.jetbrains.annotations.NotNull public String getAlgorithm() @@ -515,7 +517,7 @@ public static final class net.corda.core.crypto.CompositeKey$Companion extends j @org.jetbrains.annotations.NotNull public final java.security.PublicKey getInstance(org.bouncycastle.asn1.ASN1Primitive) @org.jetbrains.annotations.NotNull public final java.security.PublicKey getInstance(byte[]) ## -public static final class net.corda.core.crypto.CompositeKey$NodeAndWeight extends org.bouncycastle.asn1.ASN1Object implements java.lang.Comparable +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.crypto.CompositeKey$NodeAndWeight extends org.bouncycastle.asn1.ASN1Object implements java.lang.Comparable public (java.security.PublicKey, int) public int compareTo(net.corda.core.crypto.CompositeKey$NodeAndWeight) @org.jetbrains.annotations.NotNull public final java.security.PublicKey component1() @@ -565,7 +567,7 @@ public static final class net.corda.core.crypto.CompositeSignature$State extends public int hashCode() public String toString() ## -public final class net.corda.core.crypto.CompositeSignaturesWithKeys extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.crypto.CompositeSignaturesWithKeys extends java.lang.Object public (List) @org.jetbrains.annotations.NotNull public final List component1() @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.CompositeSignaturesWithKeys copy(List) @@ -664,10 +666,10 @@ public final class net.corda.core.crypto.CryptoUtils extends java.lang.Object public static final boolean verify(java.security.PublicKey, byte[], net.corda.core.crypto.DigitalSignature) public static final boolean verify(java.security.PublicKey, byte[], byte[]) ## -public class net.corda.core.crypto.DigitalSignature extends net.corda.core.utilities.OpaqueBytes +@net.corda.core.serialization.CordaSerializable public class net.corda.core.crypto.DigitalSignature extends net.corda.core.utilities.OpaqueBytes public (byte[]) ## -public static class net.corda.core.crypto.DigitalSignature$WithKey extends net.corda.core.crypto.DigitalSignature +@net.corda.core.serialization.CordaSerializable public static class net.corda.core.crypto.DigitalSignature$WithKey extends net.corda.core.crypto.DigitalSignature public (java.security.PublicKey, byte[]) @org.jetbrains.annotations.NotNull public final java.security.PublicKey getBy() public final boolean isValid(byte[]) @@ -703,7 +705,7 @@ public static final class net.corda.core.crypto.MerkleTree$Node extends net.cord public int hashCode() public String toString() ## -public final class net.corda.core.crypto.MerkleTreeException extends net.corda.core.CordaException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.crypto.MerkleTreeException extends net.corda.core.CordaException public (String) @org.jetbrains.annotations.NotNull public final String getReason() ## @@ -712,7 +714,7 @@ public final class net.corda.core.crypto.NullKeys extends java.lang.Object @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.TransactionSignature getNULL_SIGNATURE() public static final net.corda.core.crypto.NullKeys INSTANCE ## -public static final class net.corda.core.crypto.NullKeys$NullPublicKey extends java.lang.Object implements java.security.PublicKey, java.lang.Comparable +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.crypto.NullKeys$NullPublicKey extends java.lang.Object implements java.security.PublicKey, java.lang.Comparable public int compareTo(java.security.PublicKey) @org.jetbrains.annotations.NotNull public String getAlgorithm() @org.jetbrains.annotations.NotNull public byte[] getEncoded() @@ -720,7 +722,7 @@ public static final class net.corda.core.crypto.NullKeys$NullPublicKey extends j @org.jetbrains.annotations.NotNull public String toString() public static final net.corda.core.crypto.NullKeys$NullPublicKey INSTANCE ## -public final class net.corda.core.crypto.PartialMerkleTree extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.crypto.PartialMerkleTree extends java.lang.Object public (net.corda.core.crypto.PartialMerkleTree$PartialTree) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.PartialMerkleTree$PartialTree getRoot() public final boolean verify(net.corda.core.crypto.SecureHash, List) @@ -730,9 +732,9 @@ public static final class net.corda.core.crypto.PartialMerkleTree$Companion exte @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.PartialMerkleTree build(net.corda.core.crypto.MerkleTree, List) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash rootAndUsedHashes(net.corda.core.crypto.PartialMerkleTree$PartialTree, List) ## -public abstract static class net.corda.core.crypto.PartialMerkleTree$PartialTree extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public abstract static class net.corda.core.crypto.PartialMerkleTree$PartialTree extends java.lang.Object ## -public static final class net.corda.core.crypto.PartialMerkleTree$PartialTree$IncludedLeaf extends net.corda.core.crypto.PartialMerkleTree$PartialTree +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.crypto.PartialMerkleTree$PartialTree$IncludedLeaf extends net.corda.core.crypto.PartialMerkleTree$PartialTree public (net.corda.core.crypto.SecureHash) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component1() @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.PartialMerkleTree$PartialTree$IncludedLeaf copy(net.corda.core.crypto.SecureHash) @@ -741,7 +743,7 @@ public static final class net.corda.core.crypto.PartialMerkleTree$PartialTree$In public int hashCode() public String toString() ## -public static final class net.corda.core.crypto.PartialMerkleTree$PartialTree$Leaf extends net.corda.core.crypto.PartialMerkleTree$PartialTree +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.crypto.PartialMerkleTree$PartialTree$Leaf extends net.corda.core.crypto.PartialMerkleTree$PartialTree public (net.corda.core.crypto.SecureHash) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component1() @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.PartialMerkleTree$PartialTree$Leaf copy(net.corda.core.crypto.SecureHash) @@ -750,7 +752,7 @@ public static final class net.corda.core.crypto.PartialMerkleTree$PartialTree$Le public int hashCode() public String toString() ## -public static final class net.corda.core.crypto.PartialMerkleTree$PartialTree$Node extends net.corda.core.crypto.PartialMerkleTree$PartialTree +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.crypto.PartialMerkleTree$PartialTree$Node extends net.corda.core.crypto.PartialMerkleTree$PartialTree public (net.corda.core.crypto.PartialMerkleTree$PartialTree, net.corda.core.crypto.PartialMerkleTree$PartialTree) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.PartialMerkleTree$PartialTree component1() @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.PartialMerkleTree$PartialTree component2() @@ -761,7 +763,7 @@ public static final class net.corda.core.crypto.PartialMerkleTree$PartialTree$No public int hashCode() public String toString() ## -public abstract class net.corda.core.crypto.SecureHash extends net.corda.core.utilities.OpaqueBytes +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.crypto.SecureHash extends net.corda.core.utilities.OpaqueBytes @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash$SHA256 hashConcat(net.corda.core.crypto.SecureHash) @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.crypto.SecureHash$SHA256 parse(String) @org.jetbrains.annotations.NotNull public final String prefixChars(int) @@ -781,14 +783,14 @@ public static final class net.corda.core.crypto.SecureHash$Companion extends jav @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash$SHA256 sha256(byte[]) @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash$SHA256 sha256Twice(byte[]) ## -public static final class net.corda.core.crypto.SecureHash$SHA256 extends net.corda.core.crypto.SecureHash +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.crypto.SecureHash$SHA256 extends net.corda.core.crypto.SecureHash public (byte[]) ## public final class net.corda.core.crypto.SecureHashKt extends java.lang.Object @org.jetbrains.annotations.NotNull public static final net.corda.core.crypto.SecureHash$SHA256 sha256(net.corda.core.utilities.OpaqueBytes) @org.jetbrains.annotations.NotNull public static final net.corda.core.crypto.SecureHash$SHA256 sha256(byte[]) ## -public final class net.corda.core.crypto.SignableData extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.crypto.SignableData extends java.lang.Object public (net.corda.core.crypto.SecureHash, net.corda.core.crypto.SignatureMetadata) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component1() @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SignatureMetadata component2() @@ -799,7 +801,7 @@ public final class net.corda.core.crypto.SignableData extends java.lang.Object public int hashCode() public String toString() ## -public final class net.corda.core.crypto.SignatureMetadata extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.crypto.SignatureMetadata extends java.lang.Object public (int, int) public final int component1() public final int component2() @@ -837,14 +839,14 @@ public final class net.corda.core.crypto.SignatureScheme extends java.lang.Objec public int hashCode() public String toString() ## -public class net.corda.core.crypto.SignedData extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public class net.corda.core.crypto.SignedData extends java.lang.Object public (net.corda.core.serialization.SerializedBytes, net.corda.core.crypto.DigitalSignature$WithKey) @org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializedBytes getRaw() @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.DigitalSignature$WithKey getSig() @org.jetbrains.annotations.NotNull public final Object verified() protected void verifyData(Object) ## -public final class net.corda.core.crypto.TransactionSignature extends net.corda.core.crypto.DigitalSignature +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.crypto.TransactionSignature extends net.corda.core.crypto.DigitalSignature public (byte[], java.security.PublicKey, net.corda.core.crypto.SignatureMetadata) public boolean equals(Object) @org.jetbrains.annotations.NotNull public final java.security.PublicKey getBy() @@ -868,10 +870,10 @@ public abstract static class net.corda.core.flows.AbstractStateReplacementFlow$A public static final class net.corda.core.flows.AbstractStateReplacementFlow$Acceptor$Companion extends java.lang.Object @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ProgressTracker tracker() ## -public static final class net.corda.core.flows.AbstractStateReplacementFlow$Acceptor$Companion$APPROVING extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.AbstractStateReplacementFlow$Acceptor$Companion$APPROVING extends net.corda.core.utilities.ProgressTracker$Step public static final net.corda.core.flows.AbstractStateReplacementFlow$Acceptor$Companion$APPROVING INSTANCE ## -public static final class net.corda.core.flows.AbstractStateReplacementFlow$Acceptor$Companion$VERIFYING extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.AbstractStateReplacementFlow$Acceptor$Companion$VERIFYING extends net.corda.core.utilities.ProgressTracker$Step public static final net.corda.core.flows.AbstractStateReplacementFlow$Acceptor$Companion$VERIFYING INSTANCE ## public abstract static class net.corda.core.flows.AbstractStateReplacementFlow$Instigator extends net.corda.core.flows.FlowLogic @@ -887,13 +889,13 @@ public abstract static class net.corda.core.flows.AbstractStateReplacementFlow$I public static final class net.corda.core.flows.AbstractStateReplacementFlow$Instigator$Companion extends java.lang.Object @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ProgressTracker tracker() ## -public static final class net.corda.core.flows.AbstractStateReplacementFlow$Instigator$Companion$NOTARY extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.AbstractStateReplacementFlow$Instigator$Companion$NOTARY extends net.corda.core.utilities.ProgressTracker$Step public static final net.corda.core.flows.AbstractStateReplacementFlow$Instigator$Companion$NOTARY INSTANCE ## -public static final class net.corda.core.flows.AbstractStateReplacementFlow$Instigator$Companion$SIGNING extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.AbstractStateReplacementFlow$Instigator$Companion$SIGNING extends net.corda.core.utilities.ProgressTracker$Step public static final net.corda.core.flows.AbstractStateReplacementFlow$Instigator$Companion$SIGNING INSTANCE ## -public static final class net.corda.core.flows.AbstractStateReplacementFlow$Proposal extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.AbstractStateReplacementFlow$Proposal extends java.lang.Object public (net.corda.core.contracts.StateRef, Object) @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.StateRef component1() public final Object component2() @@ -913,7 +915,7 @@ public static final class net.corda.core.flows.AbstractStateReplacementFlow$Upgr public int hashCode() public String toString() ## -public final class net.corda.core.flows.CollectSignatureFlow extends net.corda.core.flows.FlowLogic +@co.paralleluniverse.fibers.Suspendable public final class net.corda.core.flows.CollectSignatureFlow extends net.corda.core.flows.FlowLogic public (net.corda.core.transactions.SignedTransaction, net.corda.core.flows.FlowSession, List) @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public List call() @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.SignedTransaction getPartiallySignedTx() @@ -936,26 +938,26 @@ public final class net.corda.core.flows.CollectSignaturesFlow extends net.corda. public static final class net.corda.core.flows.CollectSignaturesFlow$Companion extends java.lang.Object @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ProgressTracker tracker() ## -public static final class net.corda.core.flows.CollectSignaturesFlow$Companion$COLLECTING extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.CollectSignaturesFlow$Companion$COLLECTING extends net.corda.core.utilities.ProgressTracker$Step public static final net.corda.core.flows.CollectSignaturesFlow$Companion$COLLECTING INSTANCE ## -public static final class net.corda.core.flows.CollectSignaturesFlow$Companion$VERIFYING extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.CollectSignaturesFlow$Companion$VERIFYING extends net.corda.core.utilities.ProgressTracker$Step public static final net.corda.core.flows.CollectSignaturesFlow$Companion$VERIFYING INSTANCE ## public final class net.corda.core.flows.ContractUpgradeFlow extends java.lang.Object public static final net.corda.core.flows.ContractUpgradeFlow INSTANCE ## -public static final class net.corda.core.flows.ContractUpgradeFlow$Authorise extends net.corda.core.flows.FlowLogic +@net.corda.core.flows.StartableByRPC public static final class net.corda.core.flows.ContractUpgradeFlow$Authorise extends net.corda.core.flows.FlowLogic public (net.corda.core.contracts.StateAndRef, Class) @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.Nullable public Void call() @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.StateAndRef getStateAndRef() ## -public static final class net.corda.core.flows.ContractUpgradeFlow$Deauthorise extends net.corda.core.flows.FlowLogic +@net.corda.core.flows.StartableByRPC public static final class net.corda.core.flows.ContractUpgradeFlow$Deauthorise extends net.corda.core.flows.FlowLogic public (net.corda.core.contracts.StateRef) @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.Nullable public Void call() @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.StateRef getStateRef() ## -public static final class net.corda.core.flows.ContractUpgradeFlow$Initiate extends net.corda.core.flows.AbstractStateReplacementFlow$Instigator +@net.corda.core.flows.InitiatingFlow @net.corda.core.flows.StartableByRPC public static final class net.corda.core.flows.ContractUpgradeFlow$Initiate extends net.corda.core.flows.AbstractStateReplacementFlow$Instigator public (net.corda.core.contracts.StateAndRef, Class) @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull protected net.corda.core.flows.AbstractStateReplacementFlow$UpgradeTx assembleTx() ## @@ -966,7 +968,7 @@ public abstract class net.corda.core.flows.DataVendingFlow extends net.corda.cor @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull protected net.corda.core.utilities.UntrustworthyData sendPayloadAndReceiveDataRequest(net.corda.core.flows.FlowSession, Object) @co.paralleluniverse.fibers.Suspendable protected void verifyDataRequest(net.corda.core.internal.FetchDataFlow$Request$Data) ## -public final class net.corda.core.flows.FinalityFlow extends net.corda.core.flows.FlowLogic +@net.corda.core.flows.InitiatingFlow public final class net.corda.core.flows.FinalityFlow extends net.corda.core.flows.FlowLogic public (net.corda.core.transactions.SignedTransaction) public (net.corda.core.transactions.SignedTransaction, Set) public (net.corda.core.transactions.SignedTransaction, Set, net.corda.core.utilities.ProgressTracker) @@ -980,20 +982,20 @@ public final class net.corda.core.flows.FinalityFlow extends net.corda.core.flow public static final class net.corda.core.flows.FinalityFlow$Companion extends java.lang.Object @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ProgressTracker tracker() ## -public static final class net.corda.core.flows.FinalityFlow$Companion$BROADCASTING extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.FinalityFlow$Companion$BROADCASTING extends net.corda.core.utilities.ProgressTracker$Step public static final net.corda.core.flows.FinalityFlow$Companion$BROADCASTING INSTANCE ## -public static final class net.corda.core.flows.FinalityFlow$Companion$NOTARISING extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.FinalityFlow$Companion$NOTARISING extends net.corda.core.utilities.ProgressTracker$Step @org.jetbrains.annotations.NotNull public net.corda.core.utilities.ProgressTracker childProgressTracker() public static final net.corda.core.flows.FinalityFlow$Companion$NOTARISING INSTANCE ## -public class net.corda.core.flows.FlowException extends net.corda.core.CordaException +@net.corda.core.serialization.CordaSerializable public class net.corda.core.flows.FlowException extends net.corda.core.CordaException public () public (String) public (String, Throwable) public (Throwable) ## -public final class net.corda.core.flows.FlowInfo extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.flows.FlowInfo extends java.lang.Object public (int, String) public final int component1() @org.jetbrains.annotations.NotNull public final String component2() @@ -1004,9 +1006,9 @@ public final class net.corda.core.flows.FlowInfo extends java.lang.Object public int hashCode() public String toString() ## -public abstract class net.corda.core.flows.FlowInitiator extends java.lang.Object implements java.security.Principal +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.flows.FlowInitiator extends java.lang.Object implements java.security.Principal ## -public static final class net.corda.core.flows.FlowInitiator$Peer extends net.corda.core.flows.FlowInitiator +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.FlowInitiator$Peer extends net.corda.core.flows.FlowInitiator public (net.corda.core.identity.Party) @org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party component1() @org.jetbrains.annotations.NotNull public final net.corda.core.flows.FlowInitiator$Peer copy(net.corda.core.identity.Party) @@ -1016,7 +1018,7 @@ public static final class net.corda.core.flows.FlowInitiator$Peer extends net.co public int hashCode() public String toString() ## -public static final class net.corda.core.flows.FlowInitiator$RPC extends net.corda.core.flows.FlowInitiator +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.FlowInitiator$RPC extends net.corda.core.flows.FlowInitiator public (String) @org.jetbrains.annotations.NotNull public final String component1() @org.jetbrains.annotations.NotNull public final net.corda.core.flows.FlowInitiator$RPC copy(String) @@ -1026,7 +1028,7 @@ public static final class net.corda.core.flows.FlowInitiator$RPC extends net.cor public int hashCode() public String toString() ## -public static final class net.corda.core.flows.FlowInitiator$Scheduled extends net.corda.core.flows.FlowInitiator +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.FlowInitiator$Scheduled extends net.corda.core.flows.FlowInitiator public (net.corda.core.contracts.ScheduledStateRef) @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.ScheduledStateRef component1() @org.jetbrains.annotations.NotNull public final net.corda.core.flows.FlowInitiator$Scheduled copy(net.corda.core.contracts.ScheduledStateRef) @@ -1036,7 +1038,7 @@ public static final class net.corda.core.flows.FlowInitiator$Scheduled extends n public int hashCode() public String toString() ## -public static final class net.corda.core.flows.FlowInitiator$Shell extends net.corda.core.flows.FlowInitiator +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.FlowInitiator$Shell extends net.corda.core.flows.FlowInitiator @org.jetbrains.annotations.NotNull public String getName() public static final net.corda.core.flows.FlowInitiator$Shell INSTANCE ## @@ -1064,17 +1066,21 @@ public abstract class net.corda.core.flows.FlowLogic extends java.lang.Object @org.jetbrains.annotations.Nullable public final net.corda.core.messaging.DataFeed track() @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.SignedTransaction waitForLedgerCommit(net.corda.core.crypto.SecureHash) ## -public interface net.corda.core.flows.FlowLogicRef +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public interface net.corda.core.flows.FlowLogicRef ## -public interface net.corda.core.flows.FlowLogicRefFactory +@net.corda.core.DoNotImplement public interface net.corda.core.flows.FlowLogicRefFactory ## -public abstract class net.corda.core.flows.FlowSession extends java.lang.Object +@net.corda.core.DoNotImplement public abstract class net.corda.core.flows.FlowSession extends java.lang.Object public () @org.jetbrains.annotations.NotNull public abstract net.corda.core.identity.Party getCounterparty() @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public abstract net.corda.core.flows.FlowInfo getCounterpartyFlowInfo() + @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public abstract net.corda.core.flows.FlowInfo getCounterpartyFlowInfo(boolean) @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public abstract net.corda.core.utilities.UntrustworthyData receive(Class) + @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public abstract net.corda.core.utilities.UntrustworthyData receive(Class, boolean) @co.paralleluniverse.fibers.Suspendable public abstract void send(Object) + @co.paralleluniverse.fibers.Suspendable public abstract void send(Object, boolean) @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public abstract net.corda.core.utilities.UntrustworthyData sendAndReceive(Class, Object) + @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public abstract net.corda.core.utilities.UntrustworthyData sendAndReceive(Class, Object, boolean) ## public final class net.corda.core.flows.FlowStackSnapshot extends java.lang.Object public (java.time.Instant, String, List) @@ -1100,7 +1106,7 @@ public static final class net.corda.core.flows.FlowStackSnapshot$Frame extends j public int hashCode() @org.jetbrains.annotations.NotNull public String toString() ## -public final class net.corda.core.flows.IllegalFlowLogicException extends java.lang.IllegalArgumentException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.flows.IllegalFlowLogicException extends java.lang.IllegalArgumentException public (Class, String) ## public @interface net.corda.core.flows.InitiatedBy @@ -1109,13 +1115,13 @@ public @interface net.corda.core.flows.InitiatedBy public @interface net.corda.core.flows.InitiatingFlow public abstract int version() ## -public final class net.corda.core.flows.NotaryChangeFlow extends net.corda.core.flows.AbstractStateReplacementFlow$Instigator +@net.corda.core.flows.InitiatingFlow public final class net.corda.core.flows.NotaryChangeFlow extends net.corda.core.flows.AbstractStateReplacementFlow$Instigator public (net.corda.core.contracts.StateAndRef, net.corda.core.identity.Party, net.corda.core.utilities.ProgressTracker) @org.jetbrains.annotations.NotNull protected net.corda.core.flows.AbstractStateReplacementFlow$UpgradeTx assembleTx() ## -public abstract class net.corda.core.flows.NotaryError extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.flows.NotaryError extends java.lang.Object ## -public static final class net.corda.core.flows.NotaryError$Conflict extends net.corda.core.flows.NotaryError +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.NotaryError$Conflict extends net.corda.core.flows.NotaryError public (net.corda.core.crypto.SecureHash, net.corda.core.crypto.SignedData) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component1() @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SignedData component2() @@ -1126,10 +1132,10 @@ public static final class net.corda.core.flows.NotaryError$Conflict extends net. public int hashCode() @org.jetbrains.annotations.NotNull public String toString() ## -public static final class net.corda.core.flows.NotaryError$TimeWindowInvalid extends net.corda.core.flows.NotaryError +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.NotaryError$TimeWindowInvalid extends net.corda.core.flows.NotaryError public static final net.corda.core.flows.NotaryError$TimeWindowInvalid INSTANCE ## -public static final class net.corda.core.flows.NotaryError$TransactionInvalid extends net.corda.core.flows.NotaryError +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.NotaryError$TransactionInvalid extends net.corda.core.flows.NotaryError public (Throwable) @org.jetbrains.annotations.NotNull public final Throwable component1() @org.jetbrains.annotations.NotNull public final net.corda.core.flows.NotaryError$TransactionInvalid copy(Throwable) @@ -1138,17 +1144,17 @@ public static final class net.corda.core.flows.NotaryError$TransactionInvalid ex public int hashCode() @org.jetbrains.annotations.NotNull public String toString() ## -public static final class net.corda.core.flows.NotaryError$WrongNotary extends net.corda.core.flows.NotaryError +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.NotaryError$WrongNotary extends net.corda.core.flows.NotaryError public static final net.corda.core.flows.NotaryError$WrongNotary INSTANCE ## -public final class net.corda.core.flows.NotaryException extends net.corda.core.flows.FlowException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.flows.NotaryException extends net.corda.core.flows.FlowException public (net.corda.core.flows.NotaryError) @org.jetbrains.annotations.NotNull public final net.corda.core.flows.NotaryError getError() ## public final class net.corda.core.flows.NotaryFlow extends java.lang.Object public () ## -public static class net.corda.core.flows.NotaryFlow$Client extends net.corda.core.flows.FlowLogic +@net.corda.core.flows.InitiatingFlow public static class net.corda.core.flows.NotaryFlow$Client extends net.corda.core.flows.FlowLogic public (net.corda.core.transactions.SignedTransaction) public (net.corda.core.transactions.SignedTransaction, net.corda.core.utilities.ProgressTracker) @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public List call() @@ -1158,10 +1164,10 @@ public static class net.corda.core.flows.NotaryFlow$Client extends net.corda.cor public static final class net.corda.core.flows.NotaryFlow$Client$Companion extends java.lang.Object @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ProgressTracker tracker() ## -public static final class net.corda.core.flows.NotaryFlow$Client$Companion$REQUESTING extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.NotaryFlow$Client$Companion$REQUESTING extends net.corda.core.utilities.ProgressTracker$Step public static final net.corda.core.flows.NotaryFlow$Client$Companion$REQUESTING INSTANCE ## -public static final class net.corda.core.flows.NotaryFlow$Client$Companion$VALIDATING extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.NotaryFlow$Client$Companion$VALIDATING extends net.corda.core.utilities.ProgressTracker$Step public static final net.corda.core.flows.NotaryFlow$Client$Companion$VALIDATING INSTANCE ## public abstract static class net.corda.core.flows.NotaryFlow$Service extends net.corda.core.flows.FlowLogic @@ -1201,13 +1207,13 @@ public abstract class net.corda.core.flows.SignTransactionFlow extends net.corda public static final class net.corda.core.flows.SignTransactionFlow$Companion extends java.lang.Object @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ProgressTracker tracker() ## -public static final class net.corda.core.flows.SignTransactionFlow$Companion$RECEIVING extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.SignTransactionFlow$Companion$RECEIVING extends net.corda.core.utilities.ProgressTracker$Step public static final net.corda.core.flows.SignTransactionFlow$Companion$RECEIVING INSTANCE ## -public static final class net.corda.core.flows.SignTransactionFlow$Companion$SIGNING extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.SignTransactionFlow$Companion$SIGNING extends net.corda.core.utilities.ProgressTracker$Step public static final net.corda.core.flows.SignTransactionFlow$Companion$SIGNING INSTANCE ## -public static final class net.corda.core.flows.SignTransactionFlow$Companion$VERIFYING extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.SignTransactionFlow$Companion$VERIFYING extends net.corda.core.utilities.ProgressTracker$Step public static final net.corda.core.flows.SignTransactionFlow$Companion$VERIFYING INSTANCE ## public final class net.corda.core.flows.StackFrameDataToken extends java.lang.Object @@ -1221,7 +1227,7 @@ public final class net.corda.core.flows.StackFrameDataToken extends java.lang.Ob ## public @interface net.corda.core.flows.StartableByRPC ## -public final class net.corda.core.flows.StateMachineRunId extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.flows.StateMachineRunId extends java.lang.Object public (UUID) @org.jetbrains.annotations.NotNull public final UUID component1() @org.jetbrains.annotations.NotNull public final net.corda.core.flows.StateMachineRunId copy(UUID) @@ -1234,7 +1240,7 @@ public final class net.corda.core.flows.StateMachineRunId extends java.lang.Obje public static final class net.corda.core.flows.StateMachineRunId$Companion extends java.lang.Object @org.jetbrains.annotations.NotNull public final net.corda.core.flows.StateMachineRunId createRandom() ## -public class net.corda.core.flows.StateReplacementException extends net.corda.core.flows.FlowException +@net.corda.core.serialization.CordaSerializable public class net.corda.core.flows.StateReplacementException extends net.corda.core.flows.FlowException public () public (String) public (String, Throwable) @@ -1254,11 +1260,11 @@ public final class net.corda.core.flows.TransactionParts extends java.lang.Objec public int hashCode() public String toString() ## -public final class net.corda.core.flows.UnexpectedFlowEndException extends net.corda.core.CordaRuntimeException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.flows.UnexpectedFlowEndException extends net.corda.core.CordaRuntimeException public (String) public (String, Throwable) ## -public abstract class net.corda.core.identity.AbstractParty extends java.lang.Object +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public abstract class net.corda.core.identity.AbstractParty extends java.lang.Object public (java.security.PublicKey) public boolean equals(Object) @org.jetbrains.annotations.NotNull public final java.security.PublicKey getOwningKey() @@ -1266,13 +1272,13 @@ public abstract class net.corda.core.identity.AbstractParty extends java.lang.Ob @org.jetbrains.annotations.Nullable public abstract net.corda.core.identity.CordaX500Name nameOrNull() @org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.PartyAndReference ref(net.corda.core.utilities.OpaqueBytes) ## -public final class net.corda.core.identity.AnonymousParty extends net.corda.core.identity.AbstractParty +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.identity.AnonymousParty extends net.corda.core.identity.AbstractParty public (java.security.PublicKey) @org.jetbrains.annotations.Nullable public net.corda.core.identity.CordaX500Name nameOrNull() @org.jetbrains.annotations.NotNull public net.corda.core.contracts.PartyAndReference ref(net.corda.core.utilities.OpaqueBytes) @org.jetbrains.annotations.NotNull public String toString() ## -public final class net.corda.core.identity.CordaX500Name extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.identity.CordaX500Name extends java.lang.Object public (String, String, String) public (String, String, String, String) public (String, String, String, String, String, String) @@ -1315,7 +1321,7 @@ public final class net.corda.core.identity.IdentityUtils extends java.lang.Objec @org.jetbrains.annotations.NotNull public static final Map groupPublicKeysByWellKnownParty(net.corda.core.node.ServiceHub, Collection) @org.jetbrains.annotations.NotNull public static final Map groupPublicKeysByWellKnownParty(net.corda.core.node.ServiceHub, Collection, boolean) ## -public final class net.corda.core.identity.Party extends net.corda.core.identity.AbstractParty +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.identity.Party extends net.corda.core.identity.AbstractParty public (java.security.cert.X509Certificate) public (net.corda.core.identity.CordaX500Name, java.security.PublicKey) @org.jetbrains.annotations.NotNull public final net.corda.core.identity.AnonymousParty anonymise() @@ -1324,7 +1330,7 @@ public final class net.corda.core.identity.Party extends net.corda.core.identity @org.jetbrains.annotations.NotNull public net.corda.core.contracts.PartyAndReference ref(net.corda.core.utilities.OpaqueBytes) @org.jetbrains.annotations.NotNull public String toString() ## -public final class net.corda.core.identity.PartyAndCertificate extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.identity.PartyAndCertificate extends java.lang.Object public (java.security.cert.CertPath) @org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party component1() @org.jetbrains.annotations.NotNull public final java.security.cert.X509Certificate component2() @@ -1338,9 +1344,9 @@ public final class net.corda.core.identity.PartyAndCertificate extends java.lang @org.jetbrains.annotations.NotNull public String toString() @org.jetbrains.annotations.NotNull public final java.security.cert.PKIXCertPathValidatorResult verify(java.security.cert.TrustAnchor) ## -public interface net.corda.core.messaging.AllPossibleRecipients extends net.corda.core.messaging.MessageRecipients +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.messaging.AllPossibleRecipients extends net.corda.core.messaging.MessageRecipients ## -public interface net.corda.core.messaging.CordaRPCOps extends net.corda.core.messaging.RPCOps +@net.corda.core.DoNotImplement public interface net.corda.core.messaging.CordaRPCOps extends net.corda.core.messaging.RPCOps public abstract void addVaultTransactionNote(net.corda.core.crypto.SecureHash, String) public abstract boolean attachmentExists(net.corda.core.crypto.SecureHash) public abstract void clearNetworkMapCache() @@ -1380,7 +1386,7 @@ public interface net.corda.core.messaging.CordaRPCOps extends net.corda.core.mes ## public final class net.corda.core.messaging.CordaRPCOpsKt extends java.lang.Object ## -public final class net.corda.core.messaging.DataFeed extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.messaging.DataFeed extends java.lang.Object public (Object, rx.Observable) public final Object component1() @org.jetbrains.annotations.NotNull public final rx.Observable component2() @@ -1391,12 +1397,12 @@ public final class net.corda.core.messaging.DataFeed extends java.lang.Object public int hashCode() public String toString() ## -public interface net.corda.core.messaging.FlowHandle extends java.lang.AutoCloseable +@net.corda.core.DoNotImplement public interface net.corda.core.messaging.FlowHandle extends java.lang.AutoCloseable public abstract void close() @org.jetbrains.annotations.NotNull public abstract net.corda.core.flows.StateMachineRunId getId() @org.jetbrains.annotations.NotNull public abstract net.corda.core.concurrent.CordaFuture getReturnValue() ## -public final class net.corda.core.messaging.FlowHandleImpl extends java.lang.Object implements net.corda.core.messaging.FlowHandle +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.messaging.FlowHandleImpl extends java.lang.Object implements net.corda.core.messaging.FlowHandle public (net.corda.core.flows.StateMachineRunId, net.corda.core.concurrent.CordaFuture) public void close() @org.jetbrains.annotations.NotNull public final net.corda.core.flows.StateMachineRunId component1() @@ -1408,11 +1414,11 @@ public final class net.corda.core.messaging.FlowHandleImpl extends java.lang.Obj public int hashCode() public String toString() ## -public interface net.corda.core.messaging.FlowProgressHandle extends net.corda.core.messaging.FlowHandle +@net.corda.core.DoNotImplement public interface net.corda.core.messaging.FlowProgressHandle extends net.corda.core.messaging.FlowHandle public abstract void close() @org.jetbrains.annotations.NotNull public abstract rx.Observable getProgress() ## -public final class net.corda.core.messaging.FlowProgressHandleImpl extends java.lang.Object implements net.corda.core.messaging.FlowProgressHandle +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.messaging.FlowProgressHandleImpl extends java.lang.Object implements net.corda.core.messaging.FlowProgressHandle public (net.corda.core.flows.StateMachineRunId, net.corda.core.concurrent.CordaFuture, rx.Observable) public void close() @org.jetbrains.annotations.NotNull public final net.corda.core.flows.StateMachineRunId component1() @@ -1426,18 +1432,18 @@ public final class net.corda.core.messaging.FlowProgressHandleImpl extends java. public int hashCode() public String toString() ## -public interface net.corda.core.messaging.MessageRecipientGroup extends net.corda.core.messaging.MessageRecipients +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.messaging.MessageRecipientGroup extends net.corda.core.messaging.MessageRecipients ## -public interface net.corda.core.messaging.MessageRecipients +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.messaging.MessageRecipients ## -public interface net.corda.core.messaging.RPCOps +@net.corda.core.DoNotImplement public interface net.corda.core.messaging.RPCOps public abstract int getProtocolVersion() ## public @interface net.corda.core.messaging.RPCReturnsObservables ## -public interface net.corda.core.messaging.SingleMessageRecipient extends net.corda.core.messaging.MessageRecipients +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.messaging.SingleMessageRecipient extends net.corda.core.messaging.MessageRecipients ## -public final class net.corda.core.messaging.StateMachineInfo extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.messaging.StateMachineInfo extends java.lang.Object public (net.corda.core.flows.StateMachineRunId, String, net.corda.core.flows.FlowInitiator, net.corda.core.messaging.DataFeed) @org.jetbrains.annotations.NotNull public final net.corda.core.flows.StateMachineRunId component1() @org.jetbrains.annotations.NotNull public final String component2() @@ -1452,7 +1458,7 @@ public final class net.corda.core.messaging.StateMachineInfo extends java.lang.O public int hashCode() @org.jetbrains.annotations.NotNull public String toString() ## -public final class net.corda.core.messaging.StateMachineTransactionMapping extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.messaging.StateMachineTransactionMapping extends java.lang.Object public (net.corda.core.flows.StateMachineRunId, net.corda.core.crypto.SecureHash) @org.jetbrains.annotations.NotNull public final net.corda.core.flows.StateMachineRunId component1() @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component2() @@ -1463,10 +1469,10 @@ public final class net.corda.core.messaging.StateMachineTransactionMapping exten public int hashCode() public String toString() ## -public abstract class net.corda.core.messaging.StateMachineUpdate extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.messaging.StateMachineUpdate extends java.lang.Object @org.jetbrains.annotations.NotNull public abstract net.corda.core.flows.StateMachineRunId getId() ## -public static final class net.corda.core.messaging.StateMachineUpdate$Added extends net.corda.core.messaging.StateMachineUpdate +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.messaging.StateMachineUpdate$Added extends net.corda.core.messaging.StateMachineUpdate public (net.corda.core.messaging.StateMachineInfo) @org.jetbrains.annotations.NotNull public final net.corda.core.messaging.StateMachineInfo component1() @org.jetbrains.annotations.NotNull public final net.corda.core.messaging.StateMachineUpdate$Added copy(net.corda.core.messaging.StateMachineInfo) @@ -1476,7 +1482,7 @@ public static final class net.corda.core.messaging.StateMachineUpdate$Added exte public int hashCode() public String toString() ## -public static final class net.corda.core.messaging.StateMachineUpdate$Removed extends net.corda.core.messaging.StateMachineUpdate +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.messaging.StateMachineUpdate$Removed extends net.corda.core.messaging.StateMachineUpdate public (net.corda.core.flows.StateMachineRunId, net.corda.core.utilities.Try) @org.jetbrains.annotations.NotNull public final net.corda.core.flows.StateMachineRunId component1() @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.Try component2() @@ -1487,11 +1493,11 @@ public static final class net.corda.core.messaging.StateMachineUpdate$Removed ex public int hashCode() public String toString() ## -public interface net.corda.core.node.AppServiceHub extends net.corda.core.node.ServiceHub +@net.corda.core.DoNotImplement public interface net.corda.core.node.AppServiceHub extends net.corda.core.node.ServiceHub @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.FlowHandle startFlow(net.corda.core.flows.FlowLogic) @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.FlowProgressHandle startTrackedFlow(net.corda.core.flows.FlowLogic) ## -public final class net.corda.core.node.NodeInfo extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.NodeInfo extends java.lang.Object public (List, List, int, long) @org.jetbrains.annotations.NotNull public final List component1() @org.jetbrains.annotations.NotNull public final List component2() @@ -1508,7 +1514,7 @@ public final class net.corda.core.node.NodeInfo extends java.lang.Object public final boolean isLegalIdentity(net.corda.core.identity.Party) public String toString() ## -public interface net.corda.core.node.ServiceHub extends net.corda.core.node.ServicesForResolution +@net.corda.core.DoNotImplement public interface net.corda.core.node.ServiceHub extends net.corda.core.node.ServicesForResolution @org.jetbrains.annotations.NotNull public abstract net.corda.core.transactions.SignedTransaction addSignature(net.corda.core.transactions.SignedTransaction) @org.jetbrains.annotations.NotNull public abstract net.corda.core.transactions.SignedTransaction addSignature(net.corda.core.transactions.SignedTransaction, java.security.PublicKey) @org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.SerializeAsToken cordaService(Class) @@ -1532,28 +1538,28 @@ public interface net.corda.core.node.ServiceHub extends net.corda.core.node.Serv @org.jetbrains.annotations.NotNull public abstract net.corda.core.transactions.SignedTransaction signInitialTransaction(net.corda.core.transactions.TransactionBuilder, java.security.PublicKey) @org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.StateAndRef toStateAndRef(net.corda.core.contracts.StateRef) ## -public interface net.corda.core.node.ServicesForResolution extends net.corda.core.node.StateLoader +@net.corda.core.DoNotImplement public interface net.corda.core.node.ServicesForResolution extends net.corda.core.node.StateLoader @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() ## -public interface net.corda.core.node.StateLoader +@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) ## -public interface net.corda.core.node.services.AttachmentStorage +@net.corda.core.DoNotImplement public interface net.corda.core.node.services.AttachmentStorage @org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.SecureHash importAttachment(java.io.InputStream) @org.jetbrains.annotations.Nullable public abstract net.corda.core.contracts.Attachment openAttachment(net.corda.core.crypto.SecureHash) ## public final class net.corda.core.node.services.AttachmentStorageKt extends java.lang.Object ## -public interface net.corda.core.node.services.ContractUpgradeService +@net.corda.core.DoNotImplement public interface net.corda.core.node.services.ContractUpgradeService @org.jetbrains.annotations.Nullable public abstract String getAuthorisedContractUpgrade(net.corda.core.contracts.StateRef) public abstract void removeAuthorisedContractUpgrade(net.corda.core.contracts.StateRef) public abstract void storeAuthorisedContractUpgrade(net.corda.core.contracts.StateRef, Class) ## public @interface net.corda.core.node.services.CordaService ## -public interface net.corda.core.node.services.IdentityService +@net.corda.core.DoNotImplement public interface net.corda.core.node.services.IdentityService public abstract void assertOwnership(net.corda.core.identity.Party, net.corda.core.identity.AnonymousParty) @org.jetbrains.annotations.Nullable public abstract net.corda.core.identity.PartyAndCertificate certificateFromKey(java.security.PublicKey) @org.jetbrains.annotations.NotNull public abstract Iterable getAllIdentities() @@ -1568,7 +1574,7 @@ public interface net.corda.core.node.services.IdentityService @org.jetbrains.annotations.Nullable public abstract net.corda.core.identity.Party wellKnownPartyFromAnonymous(net.corda.core.identity.AbstractParty) @org.jetbrains.annotations.Nullable public abstract net.corda.core.identity.Party wellKnownPartyFromX500Name(net.corda.core.identity.CordaX500Name) ## -public interface net.corda.core.node.services.KeyManagementService +@net.corda.core.DoNotImplement public interface net.corda.core.node.services.KeyManagementService @org.jetbrains.annotations.NotNull public abstract Iterable filterMyKeys(Iterable) @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public abstract java.security.PublicKey freshKey() @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public abstract net.corda.core.identity.PartyAndCertificate freshKeyAndCert(net.corda.core.identity.PartyAndCertificate, boolean) @@ -1576,12 +1582,46 @@ public interface net.corda.core.node.services.KeyManagementService @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.TransactionSignature sign(net.corda.core.crypto.SignableData, java.security.PublicKey) @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.DigitalSignature$WithKey sign(byte[], java.security.PublicKey) ## -public interface net.corda.core.node.services.NetworkMapCache +@net.corda.core.DoNotImplement public interface net.corda.core.node.services.NetworkMapCache extends net.corda.core.node.services.NetworkMapCacheBase + @org.jetbrains.annotations.Nullable public abstract net.corda.core.node.NodeInfo getNodeByLegalIdentity(net.corda.core.identity.AbstractParty) +## +@net.corda.core.serialization.CordaSerializable public abstract static class net.corda.core.node.services.NetworkMapCache$MapChange extends java.lang.Object + @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.NodeInfo getNode() +## +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.NetworkMapCache$MapChange$Added extends net.corda.core.node.services.NetworkMapCache$MapChange + public (net.corda.core.node.NodeInfo) + @org.jetbrains.annotations.NotNull public final net.corda.core.node.NodeInfo component1() + @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.NetworkMapCache$MapChange$Added copy(net.corda.core.node.NodeInfo) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public net.corda.core.node.NodeInfo getNode() + public int hashCode() + public String toString() +## +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.NetworkMapCache$MapChange$Modified extends net.corda.core.node.services.NetworkMapCache$MapChange + public (net.corda.core.node.NodeInfo, net.corda.core.node.NodeInfo) + @org.jetbrains.annotations.NotNull public final net.corda.core.node.NodeInfo component1() + @org.jetbrains.annotations.NotNull public final net.corda.core.node.NodeInfo component2() + @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.NetworkMapCache$MapChange$Modified copy(net.corda.core.node.NodeInfo, net.corda.core.node.NodeInfo) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public net.corda.core.node.NodeInfo getNode() + @org.jetbrains.annotations.NotNull public final net.corda.core.node.NodeInfo getPreviousNode() + public int hashCode() + public String toString() +## +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.NetworkMapCache$MapChange$Removed extends net.corda.core.node.services.NetworkMapCache$MapChange + public (net.corda.core.node.NodeInfo) + @org.jetbrains.annotations.NotNull public final net.corda.core.node.NodeInfo component1() + @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.NetworkMapCache$MapChange$Removed copy(net.corda.core.node.NodeInfo) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public net.corda.core.node.NodeInfo getNode() + public int hashCode() + public String toString() +## +@net.corda.core.DoNotImplement public interface net.corda.core.node.services.NetworkMapCacheBase public abstract void clearNetworkMapCache() @org.jetbrains.annotations.NotNull public abstract List getAllNodes() @org.jetbrains.annotations.NotNull public abstract rx.Observable getChanged() @org.jetbrains.annotations.Nullable public abstract net.corda.core.node.NodeInfo getNodeByAddress(net.corda.core.utilities.NetworkHostAndPort) - @org.jetbrains.annotations.Nullable public abstract net.corda.core.node.NodeInfo getNodeByLegalIdentity(net.corda.core.identity.AbstractParty) @org.jetbrains.annotations.Nullable public abstract net.corda.core.node.NodeInfo getNodeByLegalName(net.corda.core.identity.CordaX500Name) @org.jetbrains.annotations.NotNull public abstract net.corda.core.concurrent.CordaFuture getNodeReady() @org.jetbrains.annotations.NotNull public abstract List getNodesByLegalIdentityKey(java.security.PublicKey) @@ -1595,39 +1635,7 @@ public interface net.corda.core.node.services.NetworkMapCache public abstract boolean isValidatingNotary(net.corda.core.identity.Party) @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.DataFeed track() ## -public abstract static class net.corda.core.node.services.NetworkMapCache$MapChange extends java.lang.Object - @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.NodeInfo getNode() -## -public static final class net.corda.core.node.services.NetworkMapCache$MapChange$Added extends net.corda.core.node.services.NetworkMapCache$MapChange - public (net.corda.core.node.NodeInfo) - @org.jetbrains.annotations.NotNull public final net.corda.core.node.NodeInfo component1() - @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.NetworkMapCache$MapChange$Added copy(net.corda.core.node.NodeInfo) - public boolean equals(Object) - @org.jetbrains.annotations.NotNull public net.corda.core.node.NodeInfo getNode() - public int hashCode() - public String toString() -## -public static final class net.corda.core.node.services.NetworkMapCache$MapChange$Modified extends net.corda.core.node.services.NetworkMapCache$MapChange - public (net.corda.core.node.NodeInfo, net.corda.core.node.NodeInfo) - @org.jetbrains.annotations.NotNull public final net.corda.core.node.NodeInfo component1() - @org.jetbrains.annotations.NotNull public final net.corda.core.node.NodeInfo component2() - @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.NetworkMapCache$MapChange$Modified copy(net.corda.core.node.NodeInfo, net.corda.core.node.NodeInfo) - public boolean equals(Object) - @org.jetbrains.annotations.NotNull public net.corda.core.node.NodeInfo getNode() - @org.jetbrains.annotations.NotNull public final net.corda.core.node.NodeInfo getPreviousNode() - public int hashCode() - public String toString() -## -public static final class net.corda.core.node.services.NetworkMapCache$MapChange$Removed extends net.corda.core.node.services.NetworkMapCache$MapChange - public (net.corda.core.node.NodeInfo) - @org.jetbrains.annotations.NotNull public final net.corda.core.node.NodeInfo component1() - @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.NetworkMapCache$MapChange$Removed copy(net.corda.core.node.NodeInfo) - public boolean equals(Object) - @org.jetbrains.annotations.NotNull public net.corda.core.node.NodeInfo getNode() - public int hashCode() - public String toString() -## -public abstract class net.corda.core.node.services.NotaryService extends net.corda.core.serialization.SingletonSerializeAsToken +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.node.services.NotaryService extends net.corda.core.serialization.SingletonSerializeAsToken public () @org.jetbrains.annotations.NotNull public abstract net.corda.core.flows.FlowLogic createServiceFlow(net.corda.core.flows.FlowSession) @org.jetbrains.annotations.NotNull public abstract java.security.PublicKey getNotaryIdentityKey() @@ -1658,7 +1666,7 @@ public static final class net.corda.core.node.services.PartyInfo$SingleNode exte public int hashCode() public String toString() ## -public final class net.corda.core.node.services.StatesNotAvailableException extends net.corda.core.flows.FlowException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.services.StatesNotAvailableException extends net.corda.core.flows.FlowException public (String, Throwable) @org.jetbrains.annotations.Nullable public Throwable getCause() @org.jetbrains.annotations.Nullable public String getMessage() @@ -1670,15 +1678,15 @@ public final class net.corda.core.node.services.TimeWindowChecker extends java.l @org.jetbrains.annotations.NotNull public final java.time.Clock getClock() public final boolean isValid(net.corda.core.contracts.TimeWindow) ## -public interface net.corda.core.node.services.TransactionStorage +@net.corda.core.DoNotImplement public interface net.corda.core.node.services.TransactionStorage @org.jetbrains.annotations.Nullable public abstract net.corda.core.transactions.SignedTransaction getTransaction(net.corda.core.crypto.SecureHash) @org.jetbrains.annotations.NotNull public abstract rx.Observable getUpdates() @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.DataFeed track() ## -public interface net.corda.core.node.services.TransactionVerifierService +@net.corda.core.DoNotImplement public interface net.corda.core.node.services.TransactionVerifierService @org.jetbrains.annotations.NotNull public abstract net.corda.core.concurrent.CordaFuture verify(net.corda.core.transactions.LedgerTransaction) ## -public abstract class net.corda.core.node.services.TrustedAuthorityNotaryService extends net.corda.core.node.services.NotaryService +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.node.services.TrustedAuthorityNotaryService extends net.corda.core.node.services.NotaryService public () public final void commitInputStates(List, net.corda.core.crypto.SecureHash, net.corda.core.identity.Party) @org.jetbrains.annotations.NotNull protected org.slf4j.Logger getLog() @@ -1688,14 +1696,14 @@ public abstract class net.corda.core.node.services.TrustedAuthorityNotaryService @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.DigitalSignature$WithKey sign(byte[]) public final void validateTimeWindow(net.corda.core.contracts.TimeWindow) ## -public final class net.corda.core.node.services.UniquenessException extends net.corda.core.CordaException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.services.UniquenessException extends net.corda.core.CordaException public (net.corda.core.node.services.UniquenessProvider$Conflict) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.UniquenessProvider$Conflict getError() ## public interface net.corda.core.node.services.UniquenessProvider public abstract void commit(List, net.corda.core.crypto.SecureHash, net.corda.core.identity.Party) ## -public static final class net.corda.core.node.services.UniquenessProvider$Conflict extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.UniquenessProvider$Conflict extends java.lang.Object public (Map) @org.jetbrains.annotations.NotNull public final Map component1() @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.UniquenessProvider$Conflict copy(Map) @@ -1704,7 +1712,7 @@ public static final class net.corda.core.node.services.UniquenessProvider$Confli public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.UniquenessProvider$ConsumingTx extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.UniquenessProvider$ConsumingTx extends java.lang.Object public (net.corda.core.crypto.SecureHash, int, net.corda.core.identity.Party) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component1() public final int component2() @@ -1717,10 +1725,10 @@ public static final class net.corda.core.node.services.UniquenessProvider$Consum public int hashCode() public String toString() ## -public final class net.corda.core.node.services.UnknownAnonymousPartyException extends net.corda.core.CordaException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.services.UnknownAnonymousPartyException extends net.corda.core.CordaException public (String) ## -public final class net.corda.core.node.services.Vault extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.services.Vault extends java.lang.Object public (Iterable) @org.jetbrains.annotations.NotNull public final Iterable getStates() public static final net.corda.core.node.services.Vault$Companion Companion @@ -1729,7 +1737,7 @@ public static final class net.corda.core.node.services.Vault$Companion extends j @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.Vault$Update getNoNotaryUpdate() @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.Vault$Update getNoUpdate() ## -public static final class net.corda.core.node.services.Vault$Page extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.Vault$Page extends java.lang.Object public (List, List, long, net.corda.core.node.services.Vault$StateStatus, List) @org.jetbrains.annotations.NotNull public final List component1() @org.jetbrains.annotations.NotNull public final List component2() @@ -1746,7 +1754,7 @@ public static final class net.corda.core.node.services.Vault$Page extends java.l public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.Vault$StateMetadata extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.Vault$StateMetadata extends java.lang.Object public (net.corda.core.contracts.StateRef, String, java.time.Instant, java.time.Instant, net.corda.core.node.services.Vault$StateStatus, net.corda.core.identity.AbstractParty, String, java.time.Instant) @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.StateRef component1() @org.jetbrains.annotations.NotNull public final String component2() @@ -1769,12 +1777,12 @@ public static final class net.corda.core.node.services.Vault$StateMetadata exten public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.Vault$StateStatus extends java.lang.Enum +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.Vault$StateStatus extends java.lang.Enum protected (String, int) public static net.corda.core.node.services.Vault$StateStatus valueOf(String) public static net.corda.core.node.services.Vault$StateStatus[] values() ## -public static final class net.corda.core.node.services.Vault$Update extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.Vault$Update extends java.lang.Object public (Set, Set, UUID, net.corda.core.node.services.Vault$UpdateType) @org.jetbrains.annotations.NotNull public final Set component1() @org.jetbrains.annotations.NotNull public final Set component2() @@ -1792,15 +1800,15 @@ public static final class net.corda.core.node.services.Vault$Update extends java @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.Vault$Update plus(net.corda.core.node.services.Vault$Update) @org.jetbrains.annotations.NotNull public String toString() ## -public static final class net.corda.core.node.services.Vault$UpdateType extends java.lang.Enum +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.Vault$UpdateType extends java.lang.Enum protected (String, int) public static net.corda.core.node.services.Vault$UpdateType valueOf(String) public static net.corda.core.node.services.Vault$UpdateType[] values() ## -public final class net.corda.core.node.services.VaultQueryException extends net.corda.core.flows.FlowException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.services.VaultQueryException extends net.corda.core.flows.FlowException public (String) ## -public interface net.corda.core.node.services.VaultService +@net.corda.core.DoNotImplement public interface net.corda.core.node.services.VaultService @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.Vault$Page _queryBy(net.corda.core.node.services.vault.QueryCriteria, net.corda.core.node.services.vault.PageSpecification, net.corda.core.node.services.vault.Sort, Class) @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.DataFeed _trackBy(net.corda.core.node.services.vault.QueryCriteria, net.corda.core.node.services.vault.PageSpecification, net.corda.core.node.services.vault.Sort, Class) public abstract void addNoteToTransaction(net.corda.core.crypto.SecureHash, String) @@ -1824,22 +1832,22 @@ public interface net.corda.core.node.services.VaultService ## public final class net.corda.core.node.services.VaultServiceKt extends java.lang.Object ## -public final class net.corda.core.node.services.vault.AggregateFunctionType extends java.lang.Enum +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.services.vault.AggregateFunctionType extends java.lang.Enum protected (String, int) public static net.corda.core.node.services.vault.AggregateFunctionType valueOf(String) public static net.corda.core.node.services.vault.AggregateFunctionType[] values() ## -public final class net.corda.core.node.services.vault.BinaryComparisonOperator extends java.lang.Enum implements net.corda.core.node.services.vault.Operator +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.node.services.vault.BinaryComparisonOperator extends java.lang.Enum implements net.corda.core.node.services.vault.Operator protected (String, int) public static net.corda.core.node.services.vault.BinaryComparisonOperator valueOf(String) public static net.corda.core.node.services.vault.BinaryComparisonOperator[] values() ## -public final class net.corda.core.node.services.vault.BinaryLogicalOperator extends java.lang.Enum implements net.corda.core.node.services.vault.Operator +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.node.services.vault.BinaryLogicalOperator extends java.lang.Enum implements net.corda.core.node.services.vault.Operator protected (String, int) public static net.corda.core.node.services.vault.BinaryLogicalOperator valueOf(String) public static net.corda.core.node.services.vault.BinaryLogicalOperator[] values() ## -public final class net.corda.core.node.services.vault.Builder extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.services.vault.Builder extends java.lang.Object @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression avg(reflect.Field) @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression avg(reflect.Field, List) @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression avg(reflect.Field, List, net.corda.core.node.services.vault.Sort$Direction) @@ -1902,21 +1910,21 @@ public final class net.corda.core.node.services.vault.Builder extends java.lang. @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression sum(kotlin.reflect.KProperty1, List, net.corda.core.node.services.vault.Sort$Direction) public static final net.corda.core.node.services.vault.Builder INSTANCE ## -public final class net.corda.core.node.services.vault.CollectionOperator extends java.lang.Enum implements net.corda.core.node.services.vault.Operator +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.node.services.vault.CollectionOperator extends java.lang.Enum implements net.corda.core.node.services.vault.Operator protected (String, int) public static net.corda.core.node.services.vault.CollectionOperator valueOf(String) public static net.corda.core.node.services.vault.CollectionOperator[] values() ## -public final class net.corda.core.node.services.vault.Column extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.services.vault.Column extends java.lang.Object public (String, Class) public (reflect.Field) public (kotlin.reflect.KProperty1) @org.jetbrains.annotations.NotNull public final Class getDeclaringClass() @org.jetbrains.annotations.NotNull public final String getName() ## -public abstract class net.corda.core.node.services.vault.ColumnPredicate extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.node.services.vault.ColumnPredicate extends java.lang.Object ## -public static final class net.corda.core.node.services.vault.ColumnPredicate$AggregateFunction extends net.corda.core.node.services.vault.ColumnPredicate +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.ColumnPredicate$AggregateFunction extends net.corda.core.node.services.vault.ColumnPredicate public (net.corda.core.node.services.vault.AggregateFunctionType) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.AggregateFunctionType component1() @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.ColumnPredicate$AggregateFunction copy(net.corda.core.node.services.vault.AggregateFunctionType) @@ -1925,7 +1933,7 @@ public static final class net.corda.core.node.services.vault.ColumnPredicate$Agg public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.vault.ColumnPredicate$Between extends net.corda.core.node.services.vault.ColumnPredicate +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.ColumnPredicate$Between extends net.corda.core.node.services.vault.ColumnPredicate public (Comparable, Comparable) @org.jetbrains.annotations.NotNull public final Comparable component1() @org.jetbrains.annotations.NotNull public final Comparable component2() @@ -1936,7 +1944,7 @@ public static final class net.corda.core.node.services.vault.ColumnPredicate$Bet public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.vault.ColumnPredicate$BinaryComparison extends net.corda.core.node.services.vault.ColumnPredicate +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.ColumnPredicate$BinaryComparison extends net.corda.core.node.services.vault.ColumnPredicate public (net.corda.core.node.services.vault.BinaryComparisonOperator, Comparable) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.BinaryComparisonOperator component1() @org.jetbrains.annotations.NotNull public final Comparable component2() @@ -1947,7 +1955,7 @@ public static final class net.corda.core.node.services.vault.ColumnPredicate$Bin public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.vault.ColumnPredicate$CollectionExpression extends net.corda.core.node.services.vault.ColumnPredicate +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.ColumnPredicate$CollectionExpression extends net.corda.core.node.services.vault.ColumnPredicate public (net.corda.core.node.services.vault.CollectionOperator, Collection) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.CollectionOperator component1() @org.jetbrains.annotations.NotNull public final Collection component2() @@ -1958,7 +1966,7 @@ public static final class net.corda.core.node.services.vault.ColumnPredicate$Col public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.vault.ColumnPredicate$EqualityComparison extends net.corda.core.node.services.vault.ColumnPredicate +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.ColumnPredicate$EqualityComparison extends net.corda.core.node.services.vault.ColumnPredicate public (net.corda.core.node.services.vault.EqualityComparisonOperator, Object) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.EqualityComparisonOperator component1() public final Object component2() @@ -1969,7 +1977,7 @@ public static final class net.corda.core.node.services.vault.ColumnPredicate$Equ public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.vault.ColumnPredicate$Likeness extends net.corda.core.node.services.vault.ColumnPredicate +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.ColumnPredicate$Likeness extends net.corda.core.node.services.vault.ColumnPredicate public (net.corda.core.node.services.vault.LikenessOperator, String) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.LikenessOperator component1() @org.jetbrains.annotations.NotNull public final String component2() @@ -1980,7 +1988,7 @@ public static final class net.corda.core.node.services.vault.ColumnPredicate$Lik public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.vault.ColumnPredicate$NullExpression extends net.corda.core.node.services.vault.ColumnPredicate +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.ColumnPredicate$NullExpression extends net.corda.core.node.services.vault.ColumnPredicate public (net.corda.core.node.services.vault.NullOperator) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.NullOperator component1() @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.ColumnPredicate$NullExpression copy(net.corda.core.node.services.vault.NullOperator) @@ -1989,9 +1997,9 @@ public static final class net.corda.core.node.services.vault.ColumnPredicate$Nul public int hashCode() public String toString() ## -public abstract class net.corda.core.node.services.vault.CriteriaExpression extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.node.services.vault.CriteriaExpression extends java.lang.Object ## -public static final class net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression extends net.corda.core.node.services.vault.CriteriaExpression +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression extends net.corda.core.node.services.vault.CriteriaExpression public (net.corda.core.node.services.vault.Column, net.corda.core.node.services.vault.ColumnPredicate, List, net.corda.core.node.services.vault.Sort$Direction) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.Column component1() @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.ColumnPredicate component2() @@ -2006,7 +2014,7 @@ public static final class net.corda.core.node.services.vault.CriteriaExpression$ public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.vault.CriteriaExpression$BinaryLogical extends net.corda.core.node.services.vault.CriteriaExpression +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.CriteriaExpression$BinaryLogical extends net.corda.core.node.services.vault.CriteriaExpression public (net.corda.core.node.services.vault.CriteriaExpression, net.corda.core.node.services.vault.CriteriaExpression, net.corda.core.node.services.vault.BinaryLogicalOperator) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.CriteriaExpression component1() @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.CriteriaExpression component2() @@ -2019,7 +2027,7 @@ public static final class net.corda.core.node.services.vault.CriteriaExpression$ public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression extends net.corda.core.node.services.vault.CriteriaExpression +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression extends net.corda.core.node.services.vault.CriteriaExpression public (net.corda.core.node.services.vault.Column, net.corda.core.node.services.vault.ColumnPredicate) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.Column component1() @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.ColumnPredicate component2() @@ -2030,7 +2038,7 @@ public static final class net.corda.core.node.services.vault.CriteriaExpression$ public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.vault.CriteriaExpression$Not extends net.corda.core.node.services.vault.CriteriaExpression +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.CriteriaExpression$Not extends net.corda.core.node.services.vault.CriteriaExpression public (net.corda.core.node.services.vault.CriteriaExpression) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.CriteriaExpression component1() @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.CriteriaExpression$Not copy(net.corda.core.node.services.vault.CriteriaExpression) @@ -2039,12 +2047,12 @@ public static final class net.corda.core.node.services.vault.CriteriaExpression$ public int hashCode() public String toString() ## -public final class net.corda.core.node.services.vault.EqualityComparisonOperator extends java.lang.Enum implements net.corda.core.node.services.vault.Operator +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.node.services.vault.EqualityComparisonOperator extends java.lang.Enum implements net.corda.core.node.services.vault.Operator protected (String, int) public static net.corda.core.node.services.vault.EqualityComparisonOperator valueOf(String) public static net.corda.core.node.services.vault.EqualityComparisonOperator[] values() ## -public interface net.corda.core.node.services.vault.IQueryCriteriaParser +@net.corda.core.DoNotImplement public interface net.corda.core.node.services.vault.IQueryCriteriaParser @org.jetbrains.annotations.NotNull public abstract Collection parse(net.corda.core.node.services.vault.QueryCriteria, net.corda.core.node.services.vault.Sort) @org.jetbrains.annotations.NotNull public abstract Collection parseAnd(net.corda.core.node.services.vault.QueryCriteria, net.corda.core.node.services.vault.QueryCriteria) @org.jetbrains.annotations.NotNull public abstract Collection parseCriteria(net.corda.core.node.services.vault.QueryCriteria$CommonQueryCriteria) @@ -2054,19 +2062,19 @@ public interface net.corda.core.node.services.vault.IQueryCriteriaParser @org.jetbrains.annotations.NotNull public abstract Collection parseCriteria(net.corda.core.node.services.vault.QueryCriteria$VaultQueryCriteria) @org.jetbrains.annotations.NotNull public abstract Collection parseOr(net.corda.core.node.services.vault.QueryCriteria, net.corda.core.node.services.vault.QueryCriteria) ## -public final class net.corda.core.node.services.vault.LikenessOperator extends java.lang.Enum implements net.corda.core.node.services.vault.Operator +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.node.services.vault.LikenessOperator extends java.lang.Enum implements net.corda.core.node.services.vault.Operator protected (String, int) public static net.corda.core.node.services.vault.LikenessOperator valueOf(String) public static net.corda.core.node.services.vault.LikenessOperator[] values() ## -public final class net.corda.core.node.services.vault.NullOperator extends java.lang.Enum implements net.corda.core.node.services.vault.Operator +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.node.services.vault.NullOperator extends java.lang.Enum implements net.corda.core.node.services.vault.Operator protected (String, int) public static net.corda.core.node.services.vault.NullOperator valueOf(String) public static net.corda.core.node.services.vault.NullOperator[] values() ## -public interface net.corda.core.node.services.vault.Operator +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public interface net.corda.core.node.services.vault.Operator ## -public final class net.corda.core.node.services.vault.PageSpecification extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.services.vault.PageSpecification extends java.lang.Object public () public (int, int) public final int component1() @@ -2079,18 +2087,18 @@ public final class net.corda.core.node.services.vault.PageSpecification extends public final boolean isDefault() public String toString() ## -public abstract class net.corda.core.node.services.vault.QueryCriteria extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.node.services.vault.QueryCriteria extends java.lang.Object @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.QueryCriteria and(net.corda.core.node.services.vault.QueryCriteria) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.QueryCriteria or(net.corda.core.node.services.vault.QueryCriteria) @org.jetbrains.annotations.NotNull public abstract Collection visit(net.corda.core.node.services.vault.IQueryCriteriaParser) ## -public abstract static class net.corda.core.node.services.vault.QueryCriteria$CommonQueryCriteria extends net.corda.core.node.services.vault.QueryCriteria +@net.corda.core.serialization.CordaSerializable public abstract static class net.corda.core.node.services.vault.QueryCriteria$CommonQueryCriteria extends net.corda.core.node.services.vault.QueryCriteria public () @org.jetbrains.annotations.Nullable public abstract Set getContractStateTypes() @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.Vault$StateStatus getStatus() @org.jetbrains.annotations.NotNull public Collection visit(net.corda.core.node.services.vault.IQueryCriteriaParser) ## -public static final class net.corda.core.node.services.vault.QueryCriteria$FungibleAssetQueryCriteria extends net.corda.core.node.services.vault.QueryCriteria$CommonQueryCriteria +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.QueryCriteria$FungibleAssetQueryCriteria extends net.corda.core.node.services.vault.QueryCriteria$CommonQueryCriteria public () public (List) public (List, List) @@ -2119,7 +2127,7 @@ public static final class net.corda.core.node.services.vault.QueryCriteria$Fungi public String toString() @org.jetbrains.annotations.NotNull public Collection visit(net.corda.core.node.services.vault.IQueryCriteriaParser) ## -public static final class net.corda.core.node.services.vault.QueryCriteria$LinearStateQueryCriteria extends net.corda.core.node.services.vault.QueryCriteria$CommonQueryCriteria +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.QueryCriteria$LinearStateQueryCriteria extends net.corda.core.node.services.vault.QueryCriteria$CommonQueryCriteria public () public (List) public (List, List) @@ -2143,7 +2151,7 @@ public static final class net.corda.core.node.services.vault.QueryCriteria$Linea public String toString() @org.jetbrains.annotations.NotNull public Collection visit(net.corda.core.node.services.vault.IQueryCriteriaParser) ## -public static final class net.corda.core.node.services.vault.QueryCriteria$SoftLockingCondition extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.QueryCriteria$SoftLockingCondition extends java.lang.Object public (net.corda.core.node.services.vault.QueryCriteria$SoftLockingType, List) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.QueryCriteria$SoftLockingType component1() @org.jetbrains.annotations.NotNull public final List component2() @@ -2154,12 +2162,12 @@ public static final class net.corda.core.node.services.vault.QueryCriteria$SoftL public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.vault.QueryCriteria$SoftLockingType extends java.lang.Enum +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.QueryCriteria$SoftLockingType extends java.lang.Enum protected (String, int) public static net.corda.core.node.services.vault.QueryCriteria$SoftLockingType valueOf(String) public static net.corda.core.node.services.vault.QueryCriteria$SoftLockingType[] values() ## -public static final class net.corda.core.node.services.vault.QueryCriteria$TimeCondition extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.QueryCriteria$TimeCondition extends java.lang.Object public (net.corda.core.node.services.vault.QueryCriteria$TimeInstantType, net.corda.core.node.services.vault.ColumnPredicate) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.QueryCriteria$TimeInstantType component1() @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.ColumnPredicate component2() @@ -2170,12 +2178,12 @@ public static final class net.corda.core.node.services.vault.QueryCriteria$TimeC public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.vault.QueryCriteria$TimeInstantType extends java.lang.Enum +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.QueryCriteria$TimeInstantType extends java.lang.Enum protected (String, int) public static net.corda.core.node.services.vault.QueryCriteria$TimeInstantType valueOf(String) public static net.corda.core.node.services.vault.QueryCriteria$TimeInstantType[] values() ## -public static final class net.corda.core.node.services.vault.QueryCriteria$VaultCustomQueryCriteria extends net.corda.core.node.services.vault.QueryCriteria$CommonQueryCriteria +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.QueryCriteria$VaultCustomQueryCriteria extends net.corda.core.node.services.vault.QueryCriteria$CommonQueryCriteria public (net.corda.core.node.services.vault.CriteriaExpression) public (net.corda.core.node.services.vault.CriteriaExpression, net.corda.core.node.services.Vault$StateStatus) public (net.corda.core.node.services.vault.CriteriaExpression, net.corda.core.node.services.Vault$StateStatus, Set) @@ -2191,7 +2199,7 @@ public static final class net.corda.core.node.services.vault.QueryCriteria$Vault public String toString() @org.jetbrains.annotations.NotNull public Collection visit(net.corda.core.node.services.vault.IQueryCriteriaParser) ## -public static final class net.corda.core.node.services.vault.QueryCriteria$VaultQueryCriteria extends net.corda.core.node.services.vault.QueryCriteria$CommonQueryCriteria +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.QueryCriteria$VaultQueryCriteria extends net.corda.core.node.services.vault.QueryCriteria$CommonQueryCriteria public () public (net.corda.core.node.services.Vault$StateStatus) public (net.corda.core.node.services.Vault$StateStatus, Set) @@ -2226,7 +2234,7 @@ public final class net.corda.core.node.services.vault.QueryCriteriaUtils extends public static final int DEFAULT_PAGE_SIZE = 200 public static final int MAX_PAGE_SIZE = 2147483647 ## -public final class net.corda.core.node.services.vault.Sort extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.services.vault.Sort extends java.lang.Object public (Collection) @org.jetbrains.annotations.NotNull public final Collection component1() @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.Sort copy(Collection) @@ -2235,33 +2243,33 @@ public final class net.corda.core.node.services.vault.Sort extends java.lang.Obj public int hashCode() public String toString() ## -public static interface net.corda.core.node.services.vault.Sort$Attribute +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public static interface net.corda.core.node.services.vault.Sort$Attribute ## -public static final class net.corda.core.node.services.vault.Sort$CommonStateAttribute extends java.lang.Enum implements net.corda.core.node.services.vault.Sort$Attribute +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public static final class net.corda.core.node.services.vault.Sort$CommonStateAttribute extends java.lang.Enum implements net.corda.core.node.services.vault.Sort$Attribute protected (String, int, String, String) @org.jetbrains.annotations.Nullable public final String getAttributeChild() @org.jetbrains.annotations.NotNull public final String getAttributeParent() public static net.corda.core.node.services.vault.Sort$CommonStateAttribute valueOf(String) public static net.corda.core.node.services.vault.Sort$CommonStateAttribute[] values() ## -public static final class net.corda.core.node.services.vault.Sort$Direction extends java.lang.Enum +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.Sort$Direction extends java.lang.Enum protected (String, int) public static net.corda.core.node.services.vault.Sort$Direction valueOf(String) public static net.corda.core.node.services.vault.Sort$Direction[] values() ## -public static final class net.corda.core.node.services.vault.Sort$FungibleStateAttribute extends java.lang.Enum implements net.corda.core.node.services.vault.Sort$Attribute +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public static final class net.corda.core.node.services.vault.Sort$FungibleStateAttribute extends java.lang.Enum implements net.corda.core.node.services.vault.Sort$Attribute protected (String, int, String) @org.jetbrains.annotations.NotNull public final String getAttributeName() public static net.corda.core.node.services.vault.Sort$FungibleStateAttribute valueOf(String) public static net.corda.core.node.services.vault.Sort$FungibleStateAttribute[] values() ## -public static final class net.corda.core.node.services.vault.Sort$LinearStateAttribute extends java.lang.Enum implements net.corda.core.node.services.vault.Sort$Attribute +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public static final class net.corda.core.node.services.vault.Sort$LinearStateAttribute extends java.lang.Enum implements net.corda.core.node.services.vault.Sort$Attribute protected (String, int, String) @org.jetbrains.annotations.NotNull public final String getAttributeName() public static net.corda.core.node.services.vault.Sort$LinearStateAttribute valueOf(String) public static net.corda.core.node.services.vault.Sort$LinearStateAttribute[] values() ## -public static final class net.corda.core.node.services.vault.Sort$SortColumn extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.Sort$SortColumn extends java.lang.Object public (net.corda.core.node.services.vault.SortAttribute, net.corda.core.node.services.vault.Sort$Direction) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.SortAttribute component1() @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.Sort$Direction component2() @@ -2272,15 +2280,15 @@ public static final class net.corda.core.node.services.vault.Sort$SortColumn ext public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.vault.Sort$VaultStateAttribute extends java.lang.Enum implements net.corda.core.node.services.vault.Sort$Attribute +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public static final class net.corda.core.node.services.vault.Sort$VaultStateAttribute extends java.lang.Enum implements net.corda.core.node.services.vault.Sort$Attribute protected (String, int, String) @org.jetbrains.annotations.NotNull public final String getAttributeName() public static net.corda.core.node.services.vault.Sort$VaultStateAttribute valueOf(String) public static net.corda.core.node.services.vault.Sort$VaultStateAttribute[] values() ## -public abstract class net.corda.core.node.services.vault.SortAttribute extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.node.services.vault.SortAttribute extends java.lang.Object ## -public static final class net.corda.core.node.services.vault.SortAttribute$Custom extends net.corda.core.node.services.vault.SortAttribute +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.SortAttribute$Custom extends net.corda.core.node.services.vault.SortAttribute public (Class, String) @org.jetbrains.annotations.NotNull public final Class component1() @org.jetbrains.annotations.NotNull public final String component2() @@ -2291,7 +2299,7 @@ public static final class net.corda.core.node.services.vault.SortAttribute$Custo public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.vault.SortAttribute$Standard extends net.corda.core.node.services.vault.SortAttribute +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.SortAttribute$Standard extends net.corda.core.node.services.vault.SortAttribute public (net.corda.core.node.services.vault.Sort$Attribute) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.Sort$Attribute component1() @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.SortAttribute$Standard copy(net.corda.core.node.services.vault.Sort$Attribute) @@ -2306,7 +2314,7 @@ public final class net.corda.core.schemas.CommonSchema extends java.lang.Object public final class net.corda.core.schemas.CommonSchemaV1 extends net.corda.core.schemas.MappedSchema public static final net.corda.core.schemas.CommonSchemaV1 INSTANCE ## -public static class net.corda.core.schemas.CommonSchemaV1$FungibleState extends net.corda.core.schemas.PersistentState +@javax.persistence.MappedSuperclass @net.corda.core.serialization.CordaSerializable public static class net.corda.core.schemas.CommonSchemaV1$FungibleState extends net.corda.core.schemas.PersistentState public (Set, net.corda.core.identity.AbstractParty, long, net.corda.core.identity.AbstractParty, byte[]) @org.jetbrains.annotations.NotNull public final net.corda.core.identity.AbstractParty getIssuer() @org.jetbrains.annotations.NotNull public final byte[] getIssuerRef() @@ -2319,7 +2327,7 @@ public static class net.corda.core.schemas.CommonSchemaV1$FungibleState extends public final void setParticipants(Set) public final void setQuantity(long) ## -public static class net.corda.core.schemas.CommonSchemaV1$LinearState extends net.corda.core.schemas.PersistentState +@javax.persistence.MappedSuperclass @net.corda.core.serialization.CordaSerializable public static class net.corda.core.schemas.CommonSchemaV1$LinearState extends net.corda.core.schemas.PersistentState public (Set, String, UUID) public (net.corda.core.contracts.UniqueIdentifier, Set) @org.jetbrains.annotations.Nullable public final String getExternalId() @@ -2342,7 +2350,7 @@ public final class net.corda.core.schemas.NodeInfoSchema extends java.lang.Objec public final class net.corda.core.schemas.NodeInfoSchemaV1 extends net.corda.core.schemas.MappedSchema public static final net.corda.core.schemas.NodeInfoSchemaV1 INSTANCE ## -public static final class net.corda.core.schemas.NodeInfoSchemaV1$DBHostAndPort extends java.lang.Object +@javax.persistence.Entity public static final class net.corda.core.schemas.NodeInfoSchemaV1$DBHostAndPort extends java.lang.Object public () public (net.corda.core.schemas.NodeInfoSchemaV1$PKHostAndPort) @org.jetbrains.annotations.NotNull public final net.corda.core.schemas.NodeInfoSchemaV1$DBHostAndPort copy(net.corda.core.schemas.NodeInfoSchemaV1$PKHostAndPort) @@ -2355,7 +2363,7 @@ public static final class net.corda.core.schemas.NodeInfoSchemaV1$DBHostAndPort public static final class net.corda.core.schemas.NodeInfoSchemaV1$DBHostAndPort$Companion extends java.lang.Object @org.jetbrains.annotations.NotNull public final net.corda.core.schemas.NodeInfoSchemaV1$DBHostAndPort fromHostAndPort(net.corda.core.utilities.NetworkHostAndPort) ## -public static final class net.corda.core.schemas.NodeInfoSchemaV1$DBPartyAndCertificate extends java.lang.Object +@javax.persistence.Entity @javax.persistence.Table public static final class net.corda.core.schemas.NodeInfoSchemaV1$DBPartyAndCertificate extends java.lang.Object public () public (String, String, byte[], boolean, Set) public (net.corda.core.identity.PartyAndCertificate, boolean) @@ -2366,14 +2374,14 @@ public static final class net.corda.core.schemas.NodeInfoSchemaV1$DBPartyAndCert @org.jetbrains.annotations.NotNull public final net.corda.core.schemas.NodeInfoSchemaV1$DBPartyAndCertificate copy(String, String, byte[], boolean, Set) public boolean equals(Object) @org.jetbrains.annotations.NotNull public final String getName() - @org.jetbrains.annotations.NotNull public final String getOwningKey() + @org.jetbrains.annotations.NotNull public final String getOwningKeyHash() @org.jetbrains.annotations.NotNull public final byte[] getPartyCertBinary() public int hashCode() public final boolean isMain() @org.jetbrains.annotations.NotNull public final net.corda.core.identity.PartyAndCertificate toLegalIdentityAndCert() public String toString() ## -public static final class net.corda.core.schemas.NodeInfoSchemaV1$PKHostAndPort extends java.lang.Object implements java.io.Serializable +@javax.persistence.Embeddable public static final class net.corda.core.schemas.NodeInfoSchemaV1$PKHostAndPort extends java.lang.Object implements java.io.Serializable public () public (String, Integer) @org.jetbrains.annotations.Nullable public final String component1() @@ -2385,7 +2393,7 @@ public static final class net.corda.core.schemas.NodeInfoSchemaV1$PKHostAndPort public int hashCode() public String toString() ## -public static final class net.corda.core.schemas.NodeInfoSchemaV1$PersistentNodeInfo extends java.lang.Object +@javax.persistence.Entity @javax.persistence.Table public static final class net.corda.core.schemas.NodeInfoSchemaV1$PersistentNodeInfo extends java.lang.Object public () public (int, List, List, int, long) @org.jetbrains.annotations.NotNull public final List getAddresses() @@ -2396,13 +2404,13 @@ public static final class net.corda.core.schemas.NodeInfoSchemaV1$PersistentNode public final void setId(int) @org.jetbrains.annotations.NotNull public final net.corda.core.node.NodeInfo toNodeInfo() ## -public class net.corda.core.schemas.PersistentState extends java.lang.Object implements net.corda.core.schemas.StatePersistable +@javax.persistence.MappedSuperclass @net.corda.core.serialization.CordaSerializable public class net.corda.core.schemas.PersistentState extends java.lang.Object implements net.corda.core.schemas.StatePersistable public () public (net.corda.core.schemas.PersistentStateRef) @org.jetbrains.annotations.Nullable public final net.corda.core.schemas.PersistentStateRef getStateRef() public final void setStateRef(net.corda.core.schemas.PersistentStateRef) ## -public final class net.corda.core.schemas.PersistentStateRef extends java.lang.Object implements java.io.Serializable +@javax.persistence.Embeddable public final class net.corda.core.schemas.PersistentStateRef extends java.lang.Object implements java.io.Serializable public () public (String, Integer) public (net.corda.core.contracts.StateRef) @@ -2417,7 +2425,7 @@ public final class net.corda.core.schemas.PersistentStateRef extends java.lang.O public final void setTxId(String) public String toString() ## -public interface net.corda.core.schemas.QueryableState extends net.corda.core.contracts.ContractState +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.schemas.QueryableState extends net.corda.core.contracts.ContractState @org.jetbrains.annotations.NotNull public abstract net.corda.core.schemas.PersistentState generateMappedObject(net.corda.core.schemas.MappedSchema) @org.jetbrains.annotations.NotNull public abstract Iterable supportedSchemas() ## @@ -2431,10 +2439,21 @@ public @interface net.corda.core.serialization.CordaSerializable public @interface net.corda.core.serialization.DeprecatedConstructorForDeserialization public abstract int version() ## -public final class net.corda.core.serialization.MissingAttachmentsException extends net.corda.core.CordaException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.serialization.MissingAttachmentsException extends net.corda.core.CordaException public (List) @org.jetbrains.annotations.NotNull public final List getIds() ## +public final class net.corda.core.serialization.ObjectWithCompatibleContext extends java.lang.Object + public (Object, net.corda.core.serialization.SerializationContext) + @org.jetbrains.annotations.NotNull public final Object component1() + @org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializationContext component2() + @org.jetbrains.annotations.NotNull public final net.corda.core.serialization.ObjectWithCompatibleContext copy(Object, net.corda.core.serialization.SerializationContext) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializationContext getContext() + @org.jetbrains.annotations.NotNull public final Object getObj() + public int hashCode() + public String toString() +## 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) ## @@ -2476,6 +2495,7 @@ public abstract class net.corda.core.serialization.SerializationFactory extends public () public final Object asCurrent(kotlin.jvm.functions.Function1) @org.jetbrains.annotations.NotNull public abstract Object deserialize(net.corda.core.utilities.ByteSequence, Class, net.corda.core.serialization.SerializationContext) + @org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.ObjectWithCompatibleContext deserializeWithCompatibleContext(net.corda.core.utilities.ByteSequence, Class, net.corda.core.serialization.SerializationContext) @org.jetbrains.annotations.Nullable public final net.corda.core.serialization.SerializationContext getCurrentContext() @org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializationContext getDefaultContext() @org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.SerializedBytes serialize(Object, net.corda.core.serialization.SerializationContext) @@ -2492,7 +2512,7 @@ public interface net.corda.core.serialization.SerializationToken public interface net.corda.core.serialization.SerializationWhitelist @org.jetbrains.annotations.NotNull public abstract List getWhitelist() ## -public interface net.corda.core.serialization.SerializeAsToken +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.serialization.SerializeAsToken @org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.SerializationToken toToken(net.corda.core.serialization.SerializeAsTokenContext) ## public interface net.corda.core.serialization.SerializeAsTokenContext @@ -2500,7 +2520,7 @@ public interface net.corda.core.serialization.SerializeAsTokenContext @org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.SerializeAsToken getSingleton(String) public abstract void putSingleton(net.corda.core.serialization.SerializeAsToken) ## -public final class net.corda.core.serialization.SerializedBytes extends net.corda.core.utilities.OpaqueBytes +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.serialization.SerializedBytes extends net.corda.core.utilities.OpaqueBytes public (byte[]) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getHash() ## @@ -2512,11 +2532,11 @@ public final class net.corda.core.serialization.SingletonSerializationToken exte public static final class net.corda.core.serialization.SingletonSerializationToken$Companion extends java.lang.Object @org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SingletonSerializationToken singletonSerializationToken(Class) ## -public abstract class net.corda.core.serialization.SingletonSerializeAsToken extends java.lang.Object implements net.corda.core.serialization.SerializeAsToken +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.serialization.SingletonSerializeAsToken extends java.lang.Object implements net.corda.core.serialization.SerializeAsToken public () @org.jetbrains.annotations.NotNull public net.corda.core.serialization.SingletonSerializationToken toToken(net.corda.core.serialization.SerializeAsTokenContext) ## -public abstract class net.corda.core.transactions.BaseTransaction extends java.lang.Object implements net.corda.core.contracts.NamedByHash +@net.corda.core.DoNotImplement public abstract class net.corda.core.transactions.BaseTransaction extends java.lang.Object implements net.corda.core.contracts.NamedByHash public () protected void checkBaseInvariants() @org.jetbrains.annotations.NotNull public final List filterOutRefs(Class, function.Predicate) @@ -2534,21 +2554,21 @@ public abstract class net.corda.core.transactions.BaseTransaction extends java.l @org.jetbrains.annotations.NotNull public final List outputsOfType(Class) @org.jetbrains.annotations.NotNull public String toString() ## -public class net.corda.core.transactions.ComponentGroup extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public class net.corda.core.transactions.ComponentGroup extends java.lang.Object public (int, List) @org.jetbrains.annotations.NotNull public List getComponents() public int getGroupIndex() ## -public final class net.corda.core.transactions.ComponentVisibilityException extends net.corda.core.CordaException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.transactions.ComponentVisibilityException extends net.corda.core.CordaException public (net.corda.core.crypto.SecureHash, String) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getId() @org.jetbrains.annotations.NotNull public final String getReason() ## -public abstract class net.corda.core.transactions.CoreTransaction extends net.corda.core.transactions.BaseTransaction +@net.corda.core.DoNotImplement public abstract class net.corda.core.transactions.CoreTransaction extends net.corda.core.transactions.BaseTransaction public () @org.jetbrains.annotations.NotNull public abstract List getInputs() ## -public final class net.corda.core.transactions.FilteredComponentGroup extends net.corda.core.transactions.ComponentGroup +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.transactions.FilteredComponentGroup extends net.corda.core.transactions.ComponentGroup public (int, List, List, net.corda.core.crypto.PartialMerkleTree) public final int component1() @org.jetbrains.annotations.NotNull public final List component2() @@ -2563,7 +2583,7 @@ public final class net.corda.core.transactions.FilteredComponentGroup extends ne public int hashCode() public String toString() ## -public final class net.corda.core.transactions.FilteredTransaction extends net.corda.core.transactions.TraversableTransaction +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.transactions.FilteredTransaction extends net.corda.core.transactions.TraversableTransaction @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.transactions.FilteredTransaction buildFilteredTransaction(net.corda.core.transactions.WireTransaction, function.Predicate) public final void checkAllComponentsVisible(net.corda.core.contracts.ComponentGroupEnum) public final boolean checkWithFun(kotlin.jvm.functions.Function1) @@ -2576,17 +2596,17 @@ public final class net.corda.core.transactions.FilteredTransaction extends net.c public static final class net.corda.core.transactions.FilteredTransaction$Companion extends java.lang.Object @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.FilteredTransaction buildFilteredTransaction(net.corda.core.transactions.WireTransaction, function.Predicate) ## -public final class net.corda.core.transactions.FilteredTransactionVerificationException extends net.corda.core.CordaException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.transactions.FilteredTransactionVerificationException extends net.corda.core.CordaException public (net.corda.core.crypto.SecureHash, String) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getId() @org.jetbrains.annotations.NotNull public final String getReason() ## -public abstract class net.corda.core.transactions.FullTransaction extends net.corda.core.transactions.BaseTransaction +@net.corda.core.DoNotImplement public abstract class net.corda.core.transactions.FullTransaction extends net.corda.core.transactions.BaseTransaction public () protected void checkBaseInvariants() @org.jetbrains.annotations.NotNull public abstract List getInputs() ## -public final class net.corda.core.transactions.LedgerTransaction extends net.corda.core.transactions.FullTransaction +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.transactions.LedgerTransaction extends net.corda.core.transactions.FullTransaction public (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 List commandsOfType(Class) @org.jetbrains.annotations.NotNull public final List component1() @@ -2639,11 +2659,11 @@ public static final class net.corda.core.transactions.LedgerTransaction$InOutGro public int hashCode() public String toString() ## -public final class net.corda.core.transactions.MissingContractAttachments extends net.corda.core.flows.FlowException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.transactions.MissingContractAttachments extends net.corda.core.flows.FlowException public (List) @org.jetbrains.annotations.NotNull public final List getStates() ## -public final class net.corda.core.transactions.NotaryChangeLedgerTransaction extends net.corda.core.transactions.FullTransaction implements net.corda.core.transactions.TransactionWithSignatures +@net.corda.core.DoNotImplement public final class net.corda.core.transactions.NotaryChangeLedgerTransaction extends net.corda.core.transactions.FullTransaction implements net.corda.core.transactions.TransactionWithSignatures public (List, net.corda.core.identity.Party, net.corda.core.identity.Party, net.corda.core.crypto.SecureHash, List) public void checkSignaturesAreValid() @org.jetbrains.annotations.NotNull public final List component1() @@ -2666,7 +2686,7 @@ public final class net.corda.core.transactions.NotaryChangeLedgerTransaction ext public String toString() public void verifyRequiredSignatures() ## -public final class net.corda.core.transactions.NotaryChangeWireTransaction extends net.corda.core.transactions.CoreTransaction +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.transactions.NotaryChangeWireTransaction extends net.corda.core.transactions.CoreTransaction public (List, net.corda.core.identity.Party, net.corda.core.identity.Party) @org.jetbrains.annotations.NotNull public final List component1() @org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party component2() @@ -2682,7 +2702,7 @@ public final class net.corda.core.transactions.NotaryChangeWireTransaction exten @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.NotaryChangeLedgerTransaction resolve(net.corda.core.node.ServiceHub, List) public String toString() ## -public final class net.corda.core.transactions.SignedTransaction extends java.lang.Object implements net.corda.core.transactions.TransactionWithSignatures +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.transactions.SignedTransaction extends java.lang.Object implements net.corda.core.transactions.TransactionWithSignatures public (net.corda.core.serialization.SerializedBytes, List) public (net.corda.core.transactions.CoreTransaction, List) @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.FilteredTransaction buildFilteredTransaction(function.Predicate) @@ -2717,7 +2737,7 @@ public final class net.corda.core.transactions.SignedTransaction extends java.la @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.SignedTransaction withAdditionalSignatures(Iterable) public static final net.corda.core.transactions.SignedTransaction$Companion Companion ## -public static final class net.corda.core.transactions.SignedTransaction$SignaturesMissingException extends java.security.SignatureException implements net.corda.core.CordaThrowable, net.corda.core.contracts.NamedByHash +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.transactions.SignedTransaction$SignaturesMissingException extends java.security.SignatureException implements net.corda.core.CordaThrowable, net.corda.core.contracts.NamedByHash public (Set, List, net.corda.core.crypto.SecureHash) public void addSuppressed(Throwable[]) @org.jetbrains.annotations.NotNull public final List getDescriptions() @@ -2768,7 +2788,7 @@ public class net.corda.core.transactions.TransactionBuilder extends java.lang.Ob @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.WireTransaction toWireTransaction(net.corda.core.node.ServicesForResolution) public final void verify(net.corda.core.node.ServiceHub) ## -public interface net.corda.core.transactions.TransactionWithSignatures extends net.corda.core.contracts.NamedByHash +@net.corda.core.DoNotImplement public interface net.corda.core.transactions.TransactionWithSignatures extends net.corda.core.contracts.NamedByHash public abstract void checkSignaturesAreValid() @org.jetbrains.annotations.NotNull public abstract List getKeyDescriptions(Set) @org.jetbrains.annotations.NotNull public abstract Set getMissingSigners() @@ -2776,7 +2796,7 @@ public interface net.corda.core.transactions.TransactionWithSignatures extends n @org.jetbrains.annotations.NotNull public abstract List getSigs() public abstract void verifyRequiredSignatures() ## -public abstract class net.corda.core.transactions.TraversableTransaction extends net.corda.core.transactions.CoreTransaction +@net.corda.core.DoNotImplement public abstract class net.corda.core.transactions.TraversableTransaction extends net.corda.core.transactions.CoreTransaction public (List) @org.jetbrains.annotations.NotNull public final List getAttachments() @org.jetbrains.annotations.NotNull public final List getAvailableComponentGroups() @@ -2787,7 +2807,7 @@ public abstract class net.corda.core.transactions.TraversableTransaction extends @org.jetbrains.annotations.NotNull public List getOutputs() @org.jetbrains.annotations.Nullable public final net.corda.core.contracts.TimeWindow getTimeWindow() ## -public final class net.corda.core.transactions.WireTransaction extends net.corda.core.transactions.TraversableTransaction +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.transactions.WireTransaction extends net.corda.core.transactions.TraversableTransaction @kotlin.Deprecated public (List, List, List, List, net.corda.core.identity.Party, net.corda.core.contracts.TimeWindow, net.corda.core.contracts.PrivacySalt) public (List, net.corda.core.contracts.PrivacySalt) @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.FilteredTransaction buildFilteredTransaction(function.Predicate) @@ -2811,7 +2831,7 @@ public final class net.corda.core.utilities.ByteArrays extends java.lang.Object @org.jetbrains.annotations.NotNull public static final net.corda.core.utilities.ByteSequence sequence(byte[], int, int) @org.jetbrains.annotations.NotNull public static final String toHexString(byte[]) ## -public abstract class net.corda.core.utilities.ByteSequence extends java.lang.Object implements java.lang.Comparable +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.utilities.ByteSequence extends java.lang.Object implements java.lang.Comparable public int compareTo(net.corda.core.utilities.ByteSequence) @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ByteSequence copy() public boolean equals(Object) @@ -2867,7 +2887,7 @@ public final class net.corda.core.utilities.KotlinUtilsKt extends java.lang.Obje public static final void trace(org.slf4j.Logger, kotlin.jvm.functions.Function0) @org.jetbrains.annotations.NotNull public static final net.corda.core.utilities.PropertyDelegate transient(kotlin.jvm.functions.Function0) ## -public final class net.corda.core.utilities.NetworkHostAndPort extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.utilities.NetworkHostAndPort extends java.lang.Object public (String, int) @org.jetbrains.annotations.NotNull public final String component1() public final int component2() @@ -2921,7 +2941,7 @@ public static final class net.corda.core.utilities.NonEmptySet$iterator$1 extend public Object next() public void remove() ## -public class net.corda.core.utilities.OpaqueBytes extends net.corda.core.utilities.ByteSequence +@net.corda.core.serialization.CordaSerializable public class net.corda.core.utilities.OpaqueBytes extends net.corda.core.utilities.ByteSequence public (byte[]) @org.jetbrains.annotations.NotNull public final byte[] getBytes() public int getOffset() @@ -2930,13 +2950,13 @@ public class net.corda.core.utilities.OpaqueBytes extends net.corda.core.utiliti ## public static final class net.corda.core.utilities.OpaqueBytes$Companion extends java.lang.Object ## -public final class net.corda.core.utilities.OpaqueBytesSubSequence extends net.corda.core.utilities.ByteSequence +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.utilities.OpaqueBytesSubSequence extends net.corda.core.utilities.ByteSequence public (byte[], int, int) @org.jetbrains.annotations.NotNull public byte[] getBytes() public int getOffset() public int getSize() ## -public final class net.corda.core.utilities.ProgressTracker extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.utilities.ProgressTracker extends java.lang.Object public final void endWithError(Throwable) @org.jetbrains.annotations.NotNull public final List getAllSteps() @org.jetbrains.annotations.NotNull public final rx.Observable getChanges() @@ -2952,9 +2972,9 @@ public final class net.corda.core.utilities.ProgressTracker extends java.lang.Ob public final void setChildProgressTracker(net.corda.core.utilities.ProgressTracker$Step, net.corda.core.utilities.ProgressTracker) public final void setCurrentStep(net.corda.core.utilities.ProgressTracker$Step) ## -public abstract static class net.corda.core.utilities.ProgressTracker$Change extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public abstract static class net.corda.core.utilities.ProgressTracker$Change extends java.lang.Object ## -public static final class net.corda.core.utilities.ProgressTracker$Change$Position extends net.corda.core.utilities.ProgressTracker$Change +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.utilities.ProgressTracker$Change$Position extends net.corda.core.utilities.ProgressTracker$Change public (net.corda.core.utilities.ProgressTracker, net.corda.core.utilities.ProgressTracker$Step) @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ProgressTracker component1() @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ProgressTracker$Step component2() @@ -2965,7 +2985,7 @@ public static final class net.corda.core.utilities.ProgressTracker$Change$Positi public int hashCode() @org.jetbrains.annotations.NotNull public String toString() ## -public static final class net.corda.core.utilities.ProgressTracker$Change$Rendering extends net.corda.core.utilities.ProgressTracker$Change +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.utilities.ProgressTracker$Change$Rendering extends net.corda.core.utilities.ProgressTracker$Change public (net.corda.core.utilities.ProgressTracker, net.corda.core.utilities.ProgressTracker$Step) @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ProgressTracker component1() @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ProgressTracker$Step component2() @@ -2976,7 +2996,7 @@ public static final class net.corda.core.utilities.ProgressTracker$Change$Render public int hashCode() @org.jetbrains.annotations.NotNull public String toString() ## -public static final class net.corda.core.utilities.ProgressTracker$Change$Structural extends net.corda.core.utilities.ProgressTracker$Change +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.utilities.ProgressTracker$Change$Structural extends net.corda.core.utilities.ProgressTracker$Change public (net.corda.core.utilities.ProgressTracker, net.corda.core.utilities.ProgressTracker$Step) @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ProgressTracker component1() @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ProgressTracker$Step component2() @@ -2987,25 +3007,25 @@ public static final class net.corda.core.utilities.ProgressTracker$Change$Struct public int hashCode() @org.jetbrains.annotations.NotNull public String toString() ## -public static final class net.corda.core.utilities.ProgressTracker$DONE extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.utilities.ProgressTracker$DONE extends net.corda.core.utilities.ProgressTracker$Step public boolean equals(Object) public static final net.corda.core.utilities.ProgressTracker$DONE INSTANCE ## -public static class net.corda.core.utilities.ProgressTracker$Step extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public static class net.corda.core.utilities.ProgressTracker$Step extends java.lang.Object public (String) @org.jetbrains.annotations.Nullable public net.corda.core.utilities.ProgressTracker childProgressTracker() @org.jetbrains.annotations.NotNull public rx.Observable getChanges() @org.jetbrains.annotations.NotNull public Map getExtraAuditData() @org.jetbrains.annotations.NotNull public String getLabel() ## -public static final class net.corda.core.utilities.ProgressTracker$UNSTARTED extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.utilities.ProgressTracker$UNSTARTED extends net.corda.core.utilities.ProgressTracker$Step public boolean equals(Object) public static final net.corda.core.utilities.ProgressTracker$UNSTARTED INSTANCE ## public interface net.corda.core.utilities.PropertyDelegate public abstract Object getValue(Object, kotlin.reflect.KProperty) ## -public abstract class net.corda.core.utilities.Try extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.utilities.Try extends java.lang.Object @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.Try combine(net.corda.core.utilities.Try, kotlin.jvm.functions.Function2) @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.Try flatMap(kotlin.jvm.functions.Function1) public abstract Object getOrThrow() @@ -3018,7 +3038,7 @@ public abstract class net.corda.core.utilities.Try extends java.lang.Object public static final class net.corda.core.utilities.Try$Companion extends java.lang.Object @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.Try on(kotlin.jvm.functions.Function0) ## -public static final class net.corda.core.utilities.Try$Failure extends net.corda.core.utilities.Try +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.utilities.Try$Failure extends net.corda.core.utilities.Try public (Throwable) @org.jetbrains.annotations.NotNull public final Throwable component1() @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.Try$Failure copy(Throwable) @@ -3030,7 +3050,7 @@ public static final class net.corda.core.utilities.Try$Failure extends net.corda public boolean isSuccess() @org.jetbrains.annotations.NotNull public String toString() ## -public static final class net.corda.core.utilities.Try$Success extends net.corda.core.utilities.Try +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.utilities.Try$Success extends net.corda.core.utilities.Try public (Object) public final Object component1() @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.Try$Success copy(Object) @@ -3182,7 +3202,7 @@ public abstract static class net.corda.client.jackson.JacksonSupport$WireTransac @com.fasterxml.jackson.annotation.JsonIgnore @org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.MerkleTree getMerkleTree() @com.fasterxml.jackson.annotation.JsonIgnore @org.jetbrains.annotations.NotNull public abstract List getOutputStates() ## -public class net.corda.client.jackson.StringToMethodCallParser extends java.lang.Object +@javax.annotation.concurrent.ThreadSafe public class net.corda.client.jackson.StringToMethodCallParser extends java.lang.Object public (Class) public (Class, com.fasterxml.jackson.databind.ObjectMapper) public (kotlin.reflect.KClass) @@ -3254,7 +3274,7 @@ public final class net.corda.client.rpc.CordaRPCConnection extends java.lang.Obj public final class net.corda.client.rpc.PermissionException extends net.corda.core.CordaRuntimeException public (String) ## -public interface net.corda.client.rpc.RPCConnection extends java.io.Closeable +@net.corda.core.DoNotImplement public interface net.corda.client.rpc.RPCConnection extends java.io.Closeable public abstract void forceClose() @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.RPCOps getProxy() public abstract int getServerProtocolVersion() diff --git a/.ci/check-api-changes.sh b/.ci/check-api-changes.sh index e9ac25cf6d..72ae5f9d80 100755 --- a/.ci/check-api-changes.sh +++ b/.ci/check-api-changes.sh @@ -1,4 +1,5 @@ #!/bin/bash +set +o posix echo "Starting API Diff" @@ -11,7 +12,7 @@ if [ ! -f $apiCurrent ]; then fi # Remove the two header lines from the diff output. -diffContents=`diff -u $apiCurrent $APIHOME/../build/api/api-corda-*.txt | tail --lines=+3` +diffContents=`diff -u $apiCurrent $APIHOME/../build/api/api-corda-*.txt | tail -n +3` echo "Diff contents:" echo "$diffContents" echo @@ -30,7 +31,15 @@ if [ $removalCount -gt 0 ]; then fi # Adding new abstract methods could also break the API. -newAbstracts=$(echo "$diffContents" | grep "^+" | grep "\(public\|protected\) abstract") +# However, first exclude anything with the @DoNotImplement annotation. + +function forUserImpl() { + awk '/DoNotImplement/,/^##/{ next }{ print }' $1 +} + +userDiffContents=`diff -u <(forUserImpl $apiCurrent) <(forUserImpl $APIHOME/../build/api/api-corda-*.txt) | tail -n +3` + +newAbstracts=$(echo "$userDiffContents" | grep "^+" | grep "\(public\|protected\) abstract") abstractCount=`grep -v "^$" < + + + + + diff --git a/.idea/runConfigurations/BankOfCordaDriverKt___Issue_Web.xml b/.idea/runConfigurations/BankOfCordaDriverKt___Issue_Web.xml new file mode 100644 index 0000000000..321d3d2d06 --- /dev/null +++ b/.idea/runConfigurations/BankOfCordaDriverKt___Issue_Web.xml @@ -0,0 +1,15 @@ + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/BankOfCordaDriverKt___Run_Stack.xml b/.idea/runConfigurations/BankOfCordaDriverKt___Run_Stack.xml new file mode 100644 index 0000000000..ea61b6ef8d --- /dev/null +++ b/.idea/runConfigurations/BankOfCordaDriverKt___Run_Stack.xml @@ -0,0 +1,15 @@ + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index ca333dcec6..8b11a16368 100644 --- a/build.gradle +++ b/build.gradle @@ -41,9 +41,11 @@ buildscript { ext.jansi_version = '1.14' ext.hibernate_version = '5.2.6.Final' ext.h2_version = '1.4.194' // Update docs if renamed or removed. + ext.postgresql_version = '42.1.4' ext.rxjava_version = '1.2.4' ext.dokka_version = '0.9.14' ext.eddsa_version = '0.2.0' + ext.dependency_checker_version = '3.0.1' // Update 121 is required for ObjectInputFilter and at time of writing 131 was latest: ext.java8_minUpdateVersion = '131' @@ -66,6 +68,7 @@ buildscript { classpath "org.jetbrains.dokka:dokka-gradle-plugin:${dokka_version}" classpath "org.ajoberstar:grgit:1.1.0" classpath "net.i2p.crypto:eddsa:$eddsa_version" // Needed for ServiceIdentityGenerator in the build environment. + classpath "org.owasp:dependency-check-gradle:${dependency_checker_version}" } } @@ -100,7 +103,13 @@ allprojects { apply plugin: 'kotlin' apply plugin: 'java' apply plugin: 'jacoco' + apply plugin: 'org.owasp.dependencycheck' + dependencyCheck { + suppressionFile = '.ci/dependency-checker/suppressedLibraries.xml' + cveValidForHours = 1 + format = 'ALL' + } sourceCompatibility = 1.8 targetCompatibility = 1.8 diff --git a/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt b/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt index d8595e6bd9..c9ee7af180 100644 --- a/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt +++ b/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt @@ -1,7 +1,7 @@ package net.corda.client.jackson import com.fasterxml.jackson.databind.SerializationFeature -import com.nhaarman.mockito_kotlin.mock +import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever import net.corda.core.contracts.Amount import net.corda.core.cordapp.CordappProvider @@ -9,10 +9,7 @@ import net.corda.core.crypto.* import net.corda.core.node.ServiceHub import net.corda.core.transactions.SignedTransaction import net.corda.finance.USD -import net.corda.testing.ALICE_PUBKEY -import net.corda.testing.DUMMY_NOTARY -import net.corda.testing.MINI_CORP -import net.corda.testing.TestDependencyInjectionBase +import net.corda.testing.* import net.corda.testing.contracts.DummyContract import org.junit.Before import org.junit.Test @@ -32,9 +29,9 @@ class JacksonSupportTest : TestDependencyInjectionBase() { @Before fun setup() { - services = mock() - cordappProvider = mock() - whenever(services.cordappProvider).thenReturn(cordappProvider) + services = rigorousMock() + cordappProvider = rigorousMock() + doReturn(cordappProvider).whenever(services).cordappProvider } @Test @@ -91,8 +88,7 @@ class JacksonSupportTest : TestDependencyInjectionBase() { @Test fun writeTransaction() { val attachmentRef = SecureHash.randomSHA256() - whenever(cordappProvider.getContractAttachmentID(DummyContract.PROGRAM_ID)) - .thenReturn(attachmentRef) + doReturn(attachmentRef).whenever(cordappProvider).getContractAttachmentID(DummyContract.PROGRAM_ID) fun makeDummyTx(): SignedTransaction { val wtx = DummyContract.generateInitial(1, DUMMY_NOTARY, MINI_CORP.ref(1)) .toWireTransaction(services) diff --git a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt index 277769354c..9479ffe36a 100644 --- a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt +++ b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt @@ -2,6 +2,7 @@ package net.corda.client.rpc import net.corda.core.crypto.random63BitValue import net.corda.core.flows.FlowInitiator +import net.corda.core.internal.concurrent.flatMap import net.corda.core.internal.packageName import net.corda.core.messaging.FlowProgressHandle import net.corda.core.messaging.StateMachineUpdate @@ -143,7 +144,7 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C } } val nodeIdentity = node.info.chooseIdentity() - node.services.startFlow(CashIssueFlow(2000.DOLLARS, OpaqueBytes.of(0), nodeIdentity), FlowInitiator.Shell).resultFuture.getOrThrow() + node.services.startFlow(CashIssueFlow(2000.DOLLARS, OpaqueBytes.of(0), nodeIdentity), FlowInitiator.Shell).flatMap { it.resultFuture }.getOrThrow() proxy.startFlow(::CashIssueFlow, 123.DOLLARS, OpaqueBytes.of(0), diff --git a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt index 7ccf1913d5..a25881f4ec 100644 --- a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt +++ b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt @@ -92,6 +92,7 @@ class RPCStabilityTests { startAndStop() } val numberOfThreadsAfter = waitUntilNumberOfThreadsStable(executor) + assertTrue(numberOfThreadsBefore >= numberOfThreadsAfter) executor.shutdownNow() } diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/RPCConnection.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/RPCConnection.kt index ae53adee53..bb6a7b165a 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/RPCConnection.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/RPCConnection.kt @@ -1,5 +1,6 @@ package net.corda.client.rpc +import net.corda.core.DoNotImplement import net.corda.core.messaging.RPCOps import java.io.Closeable @@ -10,6 +11,7 @@ import java.io.Closeable * [Closeable.close] may be used to shut down the connection and release associated resources. It is an * alias for [notifyServerAndClose]. */ +@DoNotImplement interface RPCConnection : Closeable { /** * Holds a synthetic class that automatically forwards method calls to the server, and returns the response. diff --git a/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt b/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt index 8030469fe5..27fb5b6791 100644 --- a/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt +++ b/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt @@ -13,10 +13,10 @@ class SwapIdentitiesFlowTests { @Test fun `issue key`() { // We run this in parallel threads to help catch any race conditions that may exist. - val mockNet = MockNetwork(false, true) + val mockNet = MockNetwork(threadPerNode = true) // Set up values we'll need - val notaryNode = mockNet.createNotaryNode() + mockNet.createNotaryNode() val aliceNode = mockNet.createPartyNode(ALICE.name) val bobNode = mockNet.createPartyNode(BOB.name) val alice = aliceNode.info.singleIdentity() @@ -53,7 +53,7 @@ class SwapIdentitiesFlowTests { @Test fun `verifies identity name`() { // We run this in parallel threads to help catch any race conditions that may exist. - val mockNet = MockNetwork(false, true) + val mockNet = MockNetwork(threadPerNode = true) // Set up values we'll need val notaryNode = mockNet.createNotaryNode(DUMMY_NOTARY.name) @@ -78,7 +78,7 @@ class SwapIdentitiesFlowTests { @Test fun `verifies signature`() { // We run this in parallel threads to help catch any race conditions that may exist. - val mockNet = MockNetwork(false, true) + val mockNet = MockNetwork(threadPerNode = true) // Set up values we'll need val notaryNode = mockNet.createNotaryNode(DUMMY_NOTARY.name) diff --git a/constants.properties b/constants.properties index 48f942803c..c85d51a756 100644 --- a/constants.properties +++ b/constants.properties @@ -1,4 +1,4 @@ -gradlePluginsVersion=2.0.4 +gradlePluginsVersion=2.0.6 kotlinVersion=1.1.50 guavaVersion=21.0 bouncycastleVersion=1.57 diff --git a/core/src/main/kotlin/net/corda/core/DoNotImplement.kt b/core/src/main/kotlin/net/corda/core/DoNotImplement.kt new file mode 100644 index 0000000000..6800a92f0c --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/DoNotImplement.kt @@ -0,0 +1,18 @@ +package net.corda.core + +import java.lang.annotation.Inherited + +/** + * This annotation is for interfaces and abstract classes that provide Corda functionality + * to user applications. Future versions of Corda may add new methods to such interfaces and + * classes, but will not remove or modify existing methods. + * + * Adding new methods does not break Corda's API compatibility guarantee because applications + * should not implement or extend anything annotated with [DoNotImplement]. These classes are + * only meant to be implemented by Corda itself. + */ +@Retention(AnnotationRetention.BINARY) +@Target(AnnotationTarget.CLASS) +@MustBeDocumented +@Inherited +annotation class DoNotImplement \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/contracts/Attachment.kt b/core/src/main/kotlin/net/corda/core/contracts/Attachment.kt index b55055e529..e55a91d546 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/Attachment.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/Attachment.kt @@ -2,6 +2,7 @@ package net.corda.core.contracts import net.corda.core.identity.Party import net.corda.core.internal.extractFile +import net.corda.core.serialization.CordaSerializable import java.io.FileNotFoundException import java.io.InputStream import java.io.OutputStream @@ -17,6 +18,7 @@ import java.util.jar.JarInputStream * - Legal documents * - Facts generated by oracles which might be reused a lot */ +@CordaSerializable interface Attachment : NamedByHash { fun open(): InputStream fun openAsJAR(): JarInputStream { diff --git a/core/src/main/kotlin/net/corda/core/contracts/ComponentGroupEnum.kt b/core/src/main/kotlin/net/corda/core/contracts/ComponentGroupEnum.kt index c32792a998..514abb986a 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/ComponentGroupEnum.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/ComponentGroupEnum.kt @@ -10,5 +10,6 @@ enum class ComponentGroupEnum { COMMANDS_GROUP, // ordinal = 2. ATTACHMENTS_GROUP, // ordinal = 3. NOTARY_GROUP, // ordinal = 4. - TIMEWINDOW_GROUP // ordinal = 5. + TIMEWINDOW_GROUP, // ordinal = 5. + SIGNERS_GROUP // ordinal = 6. } diff --git a/core/src/main/kotlin/net/corda/core/contracts/TransactionVerificationException.kt b/core/src/main/kotlin/net/corda/core/contracts/TransactionVerificationException.kt index eb935965b3..26145b38a1 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/TransactionVerificationException.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/TransactionVerificationException.kt @@ -19,7 +19,7 @@ sealed class TransactionVerificationException(val txId: SecureHash, message: Str class ContractConstraintRejection(txId: SecureHash, contractClass: String) : TransactionVerificationException(txId, "Contract constraints failed for $contractClass", null) - class MissingAttachmentRejection(txId: SecureHash, contractClass: String) + class MissingAttachmentRejection(txId: SecureHash, val contractClass: String) : TransactionVerificationException(txId, "Contract constraints failed, could not find attachment for: $contractClass", null) class ContractCreationError(txId: SecureHash, contractClass: String, cause: Throwable) diff --git a/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt b/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt index 8f7f5f8d56..f4fe71ead0 100644 --- a/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt +++ b/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt @@ -1,5 +1,6 @@ package net.corda.core.cordapp +import net.corda.core.DoNotImplement import net.corda.core.flows.FlowLogic import net.corda.core.schemas.MappedSchema import net.corda.core.serialization.SerializationWhitelist @@ -17,13 +18,14 @@ import java.net.URL * @property contractClassNames List of contracts * @property initiatedFlows List of initiatable flow classes * @property rpcFlows List of RPC initiable flows classes - * @property serviceFlows List of [CordaService] initiable flows classes + * @property serviceFlows List of [net.corda.core.node.services.CordaService] initiable flows classes * @property schedulableFlows List of flows startable by the scheduler - * @property servies List of RPC services + * @property services List of RPC services * @property serializationWhitelists List of Corda plugin registries * @property customSchemas List of custom schemas * @property jarPath The path to the JAR for this CorDapp */ +@DoNotImplement interface Cordapp { val name: String val contractClassNames: List diff --git a/core/src/main/kotlin/net/corda/core/cordapp/CordappProvider.kt b/core/src/main/kotlin/net/corda/core/cordapp/CordappProvider.kt index 289e606ac4..bf7864ee95 100644 --- a/core/src/main/kotlin/net/corda/core/cordapp/CordappProvider.kt +++ b/core/src/main/kotlin/net/corda/core/cordapp/CordappProvider.kt @@ -1,11 +1,13 @@ package net.corda.core.cordapp +import net.corda.core.DoNotImplement import net.corda.core.contracts.ContractClassName import net.corda.core.node.services.AttachmentId /** * Provides access to what the node knows about loaded applications. */ +@DoNotImplement interface CordappProvider { /** * Exposes the current CorDapp context which will contain information and configuration of the CorDapp that diff --git a/core/src/main/kotlin/net/corda/core/crypto/PartialMerkleTree.kt b/core/src/main/kotlin/net/corda/core/crypto/PartialMerkleTree.kt index 77e06bf1c2..b46eb7f631 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/PartialMerkleTree.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/PartialMerkleTree.kt @@ -158,4 +158,41 @@ class PartialMerkleTree(val root: PartialTree) { return false return (verifyRoot == merkleRootHash) } + + /** + * Method to return the index of the input leaf in the partial Merkle tree structure. + * @param leaf the component hash to check. + * @return leaf-index of this component (starting from zero). + * @throws MerkleTreeException if the provided hash is not in the tree. + */ + @Throws(MerkleTreeException::class) + internal fun leafIndex(leaf: SecureHash): Int { + // Special handling if the tree consists of one node only. + if (root is PartialTree.IncludedLeaf && root.hash == leaf) return 0 + val flagPath = mutableListOf() + if (!leafIndexHelper(leaf, this.root, flagPath)) throw MerkleTreeException("The provided hash $leaf is not in the tree.") + return indexFromFlagPath(flagPath) + } + + // Helper function to compute the path. False means go to the left and True to the right. + // Because the path is updated recursively, the path is returned in reverse order. + private fun leafIndexHelper(leaf: SecureHash, node: PartialTree, path: MutableList): Boolean { + if (node is PartialTree.IncludedLeaf) { + return node.hash == leaf + } else if (node is PartialTree.Node) { + if (leafIndexHelper(leaf, node.left, path)) { + path.add(false) + return true + } + if (leafIndexHelper(leaf, node.right, path)) { + path.add(true) + return true + } + } + return false + } + + // Return the leaf index from the path boolean list. + private fun indexFromFlagPath(pathList: List) = + pathList.mapIndexed { index, value -> if (value) (1 shl index) else 0 }.sum() } diff --git a/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt b/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt index 0239c81f20..322a9f9925 100644 --- a/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt +++ b/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt @@ -41,6 +41,16 @@ import java.time.Instant * and request they start their counterpart flow, then make sure it's annotated with [InitiatingFlow]. This annotation * also has a version property to allow you to version your flow and enables a node to restrict support for the flow to * that particular version. + * + * Functions that suspend the flow (including all functions on [FlowSession]) accept a [maySkipCheckpoint] parameter + * defaulting to false, false meaning a checkpoint should always be created on suspend. This parameter may be set to + * true which allows the implementation to potentially optimise away the checkpoint, saving a roundtrip to the database. + * + * This option however comes with a big warning sign: Setting the parameter to true requires the flow's code to be + * replayable from the previous checkpoint (or start of flow) up until the next checkpoint (or end of flow) in order to + * prepare for hard failures. As suspending functions always commit the flow's database transaction regardless of this + * parameter the flow must be prepared for scenarios where a previous running of the flow *already committed its + * relevant database transactions*. Only set this option to true if you know what you're doing. */ abstract class FlowLogic { /** This is where you should log things to. */ @@ -123,7 +133,7 @@ abstract class FlowLogic { */ @Deprecated("Use FlowSession.getFlowInfo()", level = DeprecationLevel.WARNING) @Suspendable - fun getFlowInfo(otherParty: Party): FlowInfo = stateMachine.getFlowInfo(otherParty, flowUsedForSessions) + fun getFlowInfo(otherParty: Party): FlowInfo = stateMachine.getFlowInfo(otherParty, flowUsedForSessions, maySkipCheckpoint = false) /** * Serializes and queues the given [payload] object for sending to the [otherParty]. Suspends until a response @@ -157,7 +167,7 @@ abstract class FlowLogic { @Deprecated("Use FlowSession.sendAndReceive()", level = DeprecationLevel.WARNING) @Suspendable open fun sendAndReceive(receiveType: Class, otherParty: Party, payload: Any): UntrustworthyData { - return stateMachine.sendAndReceive(receiveType, otherParty, payload, flowUsedForSessions) + return stateMachine.sendAndReceive(receiveType, otherParty, payload, flowUsedForSessions, retrySend = false, maySkipCheckpoint = false) } /** @@ -171,17 +181,17 @@ abstract class FlowLogic { */ @Deprecated("Use FlowSession.sendAndReceiveWithRetry()", level = DeprecationLevel.WARNING) internal inline fun sendAndReceiveWithRetry(otherParty: Party, payload: Any): UntrustworthyData { - return stateMachine.sendAndReceive(R::class.java, otherParty, payload, flowUsedForSessions, retrySend = true) + return stateMachine.sendAndReceive(R::class.java, otherParty, payload, flowUsedForSessions, retrySend = true, maySkipCheckpoint = false) } @Suspendable internal fun FlowSession.sendAndReceiveWithRetry(receiveType: Class, payload: Any): UntrustworthyData { - return stateMachine.sendAndReceive(receiveType, counterparty, payload, flowUsedForSessions, retrySend = true) + return stateMachine.sendAndReceive(receiveType, counterparty, payload, flowUsedForSessions, retrySend = true, maySkipCheckpoint = false) } @Suspendable internal inline fun FlowSession.sendAndReceiveWithRetry(payload: Any): UntrustworthyData { - return stateMachine.sendAndReceive(R::class.java, counterparty, payload, flowUsedForSessions, retrySend = true) + return stateMachine.sendAndReceive(R::class.java, counterparty, payload, flowUsedForSessions, retrySend = true, maySkipCheckpoint = false) } /** @@ -206,7 +216,7 @@ abstract class FlowLogic { @Deprecated("Use FlowSession.receive()", level = DeprecationLevel.WARNING) @Suspendable open fun receive(receiveType: Class, otherParty: Party): UntrustworthyData { - return stateMachine.receive(receiveType, otherParty, flowUsedForSessions) + return stateMachine.receive(receiveType, otherParty, flowUsedForSessions, maySkipCheckpoint = false) } /** Suspends until a message has been received for each session in the specified [sessions]. @@ -250,7 +260,9 @@ abstract class FlowLogic { */ @Deprecated("Use FlowSession.send()", level = DeprecationLevel.WARNING) @Suspendable - open fun send(otherParty: Party, payload: Any) = stateMachine.send(otherParty, payload, flowUsedForSessions) + open fun send(otherParty: Party, payload: Any) { + stateMachine.send(otherParty, payload, flowUsedForSessions, maySkipCheckpoint = false) + } /** * Invokes the given subflow. This function returns once the subflow completes successfully with the result @@ -342,7 +354,10 @@ abstract class FlowLogic { * valid by the local node, but that doesn't imply the vault will consider it relevant. */ @Suspendable - fun waitForLedgerCommit(hash: SecureHash): SignedTransaction = stateMachine.waitForLedgerCommit(hash, this) + @JvmOverloads + fun waitForLedgerCommit(hash: SecureHash, maySkipCheckpoint: Boolean = false): SignedTransaction { + return stateMachine.waitForLedgerCommit(hash, this, maySkipCheckpoint = maySkipCheckpoint) + } /** * Returns a shallow copy of the Quasar stack frames at the time of call to [flowStackSnapshot]. Use this to inspect diff --git a/core/src/main/kotlin/net/corda/core/flows/FlowLogicRef.kt b/core/src/main/kotlin/net/corda/core/flows/FlowLogicRef.kt index 0b90909180..a3043709d0 100644 --- a/core/src/main/kotlin/net/corda/core/flows/FlowLogicRef.kt +++ b/core/src/main/kotlin/net/corda/core/flows/FlowLogicRef.kt @@ -1,6 +1,6 @@ package net.corda.core.flows -import net.corda.core.crypto.SecureHash +import net.corda.core.DoNotImplement import net.corda.core.serialization.CordaSerializable /** @@ -8,6 +8,7 @@ import net.corda.core.serialization.CordaSerializable * Typically this would be used from within the nextScheduledActivity method of a QueryableState to specify * the flow to run at the scheduled time. */ +@DoNotImplement interface FlowLogicRefFactory { fun create(flowClass: Class>, vararg args: Any?): FlowLogicRef } @@ -24,4 +25,5 @@ class IllegalFlowLogicException(type: Class<*>, msg: String) : IllegalArgumentEx */ // TODO: align this with the existing [FlowRef] in the bank-side API (probably replace some of the API classes) @CordaSerializable +@DoNotImplement interface FlowLogicRef \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/flows/FlowSession.kt b/core/src/main/kotlin/net/corda/core/flows/FlowSession.kt index f5589f96c4..b1782f5424 100644 --- a/core/src/main/kotlin/net/corda/core/flows/FlowSession.kt +++ b/core/src/main/kotlin/net/corda/core/flows/FlowSession.kt @@ -1,6 +1,7 @@ package net.corda.core.flows import co.paralleluniverse.fibers.Suspendable +import net.corda.core.DoNotImplement import net.corda.core.identity.Party import net.corda.core.utilities.UntrustworthyData @@ -41,6 +42,7 @@ import net.corda.core.utilities.UntrustworthyData * will become * otherSideSession.send(something) */ +@DoNotImplement abstract class FlowSession { /** * The [Party] on the other side of this session. In the case of a session created by [FlowLogic.initiateFlow] @@ -52,8 +54,20 @@ abstract class FlowSession { * Returns a [FlowInfo] object describing the flow [counterparty] is using. With [FlowInfo.flowVersion] it * provides the necessary information needed for the evolution of flows and enabling backwards compatibility. * - * This method can be called before any send or receive has been done with [counterparty]. In such a case this will force - * them to start their flow. + * This method can be called before any send or receive has been done with [counterparty]. In such a case this will + * force them to start their flow. + * + * @param maySkipCheckpoint setting it to true indicates to the platform that it may optimise away the checkpoint. + */ + @Suspendable + abstract fun getCounterpartyFlowInfo(maySkipCheckpoint: Boolean): FlowInfo + + /** + * Returns a [FlowInfo] object describing the flow [counterparty] is using. With [FlowInfo.flowVersion] it + * provides the necessary information needed for the evolution of flows and enabling backwards compatibility. + * + * This method can be called before any send or receive has been done with [counterparty]. In such a case this will + * force them to start their flow. */ @Suspendable abstract fun getCounterpartyFlowInfo(): FlowInfo @@ -78,8 +92,26 @@ abstract class FlowSession { /** * Serializes and queues the given [payload] object for sending to the [counterparty]. Suspends until a response - * is received, which must be of the given [receiveType]. Remember that when receiving data from other parties the data - * should not be trusted until it's been thoroughly verified for consistency and that all expectations are + * is received, which must be of the given [receiveType]. Remember that when receiving data from other parties the + * data should not be trusted until it's been thoroughly verified for consistency and that all expectations are + * satisfied, as a malicious peer may send you subtly corrupted data in order to exploit your code. + * + * Note that this function is not just a simple send+receive pair: it is more efficient and more correct to + * use this when you expect to do a message swap than do use [send] and then [receive] in turn. + * + * @param maySkipCheckpoint setting it to true indicates to the platform that it may optimise away the checkpoint. + * @return an [UntrustworthyData] wrapper around the received object. + */ + @Suspendable + abstract fun sendAndReceive( + receiveType: Class, + payload: Any, maySkipCheckpoint: Boolean + ): UntrustworthyData + + /** + * Serializes and queues the given [payload] object for sending to the [counterparty]. Suspends until a response + * is received, which must be of the given [receiveType]. Remember that when receiving data from other parties the + * data should not be trusted until it's been thoroughly verified for consistency and that all expectations are * satisfied, as a malicious peer may send you subtly corrupted data in order to exploit your code. * * Note that this function is not just a simple send+receive pair: it is more efficient and more correct to @@ -102,6 +134,19 @@ abstract class FlowSession { return receive(R::class.java) } + /** + * Suspends until [counterparty] sends us a message of type [receiveType]. + * + * Remember that when receiving data from other parties the data should not be trusted until it's been thoroughly + * verified for consistency and that all expectations are satisfied, as a malicious peer may send you subtly + * corrupted data in order to exploit your code. + * + * @param maySkipCheckpoint setting it to true indicates to the platform that it may optimise away the checkpoint. + * @return an [UntrustworthyData] wrapper around the received object. + */ + @Suspendable + abstract fun receive(receiveType: Class, maySkipCheckpoint: Boolean): UntrustworthyData + /** * Suspends until [counterparty] sends us a message of type [receiveType]. * @@ -114,6 +159,18 @@ abstract class FlowSession { @Suspendable abstract fun receive(receiveType: Class): UntrustworthyData + /** + * Queues the given [payload] for sending to the [counterparty] and continues without suspending. + * + * Note that the other party may receive the message at some arbitrary later point or not at all: if [counterparty] + * is offline then message delivery will be retried until it comes back or until the message is older than the + * network's event horizon time. + * + * @param maySkipCheckpoint setting it to true indicates to the platform that it may optimise away the checkpoint. + */ + @Suspendable + abstract fun send(payload: Any, maySkipCheckpoint: Boolean) + /** * Queues the given [payload] for sending to the [counterparty] and continues without suspending. * diff --git a/core/src/main/kotlin/net/corda/core/identity/AbstractParty.kt b/core/src/main/kotlin/net/corda/core/identity/AbstractParty.kt index 8f03ed640b..a24096f973 100644 --- a/core/src/main/kotlin/net/corda/core/identity/AbstractParty.kt +++ b/core/src/main/kotlin/net/corda/core/identity/AbstractParty.kt @@ -1,5 +1,6 @@ package net.corda.core.identity +import net.corda.core.DoNotImplement import net.corda.core.contracts.PartyAndReference import net.corda.core.serialization.CordaSerializable import net.corda.core.utilities.OpaqueBytes @@ -10,6 +11,7 @@ import java.security.PublicKey * the party. In most cases [Party] or [AnonymousParty] should be used, depending on use-case. */ @CordaSerializable +@DoNotImplement abstract class AbstractParty(val owningKey: PublicKey) { /** Anonymised parties do not include any detail apart from owning key, so equality is dependent solely on the key */ override fun equals(other: Any?): Boolean = other === this || other is AbstractParty && other.owningKey == owningKey diff --git a/core/src/main/kotlin/net/corda/core/internal/FlowStateMachine.kt b/core/src/main/kotlin/net/corda/core/internal/FlowStateMachine.kt index a2b0e2fd15..5e4f3e490a 100644 --- a/core/src/main/kotlin/net/corda/core/internal/FlowStateMachine.kt +++ b/core/src/main/kotlin/net/corda/core/internal/FlowStateMachine.kt @@ -15,7 +15,7 @@ import java.time.Instant /** This is an internal interface that is implemented by code in the node module. You should look at [FlowLogic]. */ interface FlowStateMachine { @Suspendable - fun getFlowInfo(otherParty: Party, sessionFlow: FlowLogic<*>): FlowInfo + fun getFlowInfo(otherParty: Party, sessionFlow: FlowLogic<*>, maySkipCheckpoint: Boolean): FlowInfo @Suspendable fun initiateFlow(otherParty: Party, sessionFlow: FlowLogic<*>): FlowSession @@ -25,16 +25,17 @@ interface FlowStateMachine { otherParty: Party, payload: Any, sessionFlow: FlowLogic<*>, - retrySend: Boolean = false): UntrustworthyData + retrySend: Boolean, + maySkipCheckpoint: Boolean): UntrustworthyData @Suspendable - fun receive(receiveType: Class, otherParty: Party, sessionFlow: FlowLogic<*>): UntrustworthyData + fun receive(receiveType: Class, otherParty: Party, sessionFlow: FlowLogic<*>, maySkipCheckpoint: Boolean): UntrustworthyData @Suspendable - fun send(otherParty: Party, payload: Any, sessionFlow: FlowLogic<*>) + fun send(otherParty: Party, payload: Any, sessionFlow: FlowLogic<*>, maySkipCheckpoint: Boolean) @Suspendable - fun waitForLedgerCommit(hash: SecureHash, sessionFlow: FlowLogic<*>): SignedTransaction + fun waitForLedgerCommit(hash: SecureHash, sessionFlow: FlowLogic<*>, maySkipCheckpoint: Boolean): SignedTransaction @Suspendable fun sleepUntil(until: Instant) diff --git a/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt b/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt index d29cc7a193..3e87d4ba39 100644 --- a/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt +++ b/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt @@ -24,7 +24,6 @@ import rx.Observable import java.io.InputStream import java.security.PublicKey import java.time.Instant -import java.util.* @CordaSerializable data class StateMachineInfo( diff --git a/core/src/main/kotlin/net/corda/core/messaging/FlowHandle.kt b/core/src/main/kotlin/net/corda/core/messaging/FlowHandle.kt index 166825de0c..ee241d1924 100644 --- a/core/src/main/kotlin/net/corda/core/messaging/FlowHandle.kt +++ b/core/src/main/kotlin/net/corda/core/messaging/FlowHandle.kt @@ -1,5 +1,6 @@ package net.corda.core.messaging +import net.corda.core.DoNotImplement import net.corda.core.concurrent.CordaFuture import net.corda.core.flows.StateMachineRunId import net.corda.core.serialization.CordaSerializable @@ -11,6 +12,7 @@ import rx.Observable * @property id The started state machine's ID. * @property returnValue A [CordaFuture] of the flow's return value. */ +@DoNotImplement interface FlowHandle : AutoCloseable { val id: StateMachineRunId val returnValue: CordaFuture diff --git a/core/src/main/kotlin/net/corda/core/messaging/RPCOps.kt b/core/src/main/kotlin/net/corda/core/messaging/RPCOps.kt index 6a3eaa4c1d..e6aa0b79a4 100644 --- a/core/src/main/kotlin/net/corda/core/messaging/RPCOps.kt +++ b/core/src/main/kotlin/net/corda/core/messaging/RPCOps.kt @@ -1,9 +1,12 @@ package net.corda.core.messaging +import net.corda.core.DoNotImplement + /** * Base interface that all RPC servers must implement. Note: in Corda there's only one RPC interface. This base * interface is here in case we split the RPC system out into a separate library one day. */ +@DoNotImplement interface RPCOps { /** Returns the RPC protocol version. Exists since version 0 so guaranteed to be present. */ val protocolVersion: Int diff --git a/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt b/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt index 7114e6c843..ce08e56881 100644 --- a/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt +++ b/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt @@ -1,5 +1,6 @@ package net.corda.core.node +import net.corda.core.DoNotImplement import net.corda.core.contracts.* import net.corda.core.cordapp.CordappProvider import net.corda.core.crypto.Crypto @@ -19,6 +20,7 @@ import java.time.Clock /** * Part of [ServiceHub]. */ +@DoNotImplement interface StateLoader { /** * Given a [StateRef] loads the referenced transaction and looks up the specified output [ContractState]. @@ -164,7 +166,7 @@ interface ServiceHub : ServicesForResolution { @Throws(TransactionResolutionException::class) fun toStateAndRef(stateRef: StateRef): StateAndRef { val stx = validatedTransactions.getTransaction(stateRef.txhash) ?: throw TransactionResolutionException(stateRef.txhash) - return stx.resolveBaseTransaction(this).outRef(stateRef.index) + return stx.resolveBaseTransaction(this).outRef(stateRef.index) } private val legalIdentityKey: PublicKey get() = this.myInfo.legalIdentitiesAndCerts.first().owningKey diff --git a/core/src/main/kotlin/net/corda/core/node/services/AttachmentStorage.kt b/core/src/main/kotlin/net/corda/core/node/services/AttachmentStorage.kt index 4e038925c1..ed13d5edd5 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/AttachmentStorage.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/AttachmentStorage.kt @@ -1,5 +1,6 @@ package net.corda.core.node.services +import net.corda.core.DoNotImplement import net.corda.core.contracts.Attachment import net.corda.core.crypto.SecureHash import java.io.IOException @@ -11,6 +12,7 @@ typealias AttachmentId = SecureHash /** * An attachment store records potentially large binary objects, identified by their hash. */ +@DoNotImplement interface AttachmentStorage { /** * Returns a handle to a locally stored attachment, or null if it's not known. The handle can be used to open diff --git a/core/src/main/kotlin/net/corda/core/node/services/ContractUpgradeService.kt b/core/src/main/kotlin/net/corda/core/node/services/ContractUpgradeService.kt index 42d1689796..b489027fc6 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/ContractUpgradeService.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/ContractUpgradeService.kt @@ -1,5 +1,6 @@ package net.corda.core.node.services +import net.corda.core.DoNotImplement import net.corda.core.contracts.StateRef import net.corda.core.contracts.UpgradedContract import net.corda.core.flows.ContractUpgradeFlow @@ -9,6 +10,7 @@ import net.corda.core.flows.ContractUpgradeFlow * a specified and mutually agreed (amongst participants) contract version. * See also [ContractUpgradeFlow] to understand the workflow associated with contract upgrades. */ +@DoNotImplement interface ContractUpgradeService { /** Get contracts we would be willing to upgrade the suggested contract to. */ diff --git a/core/src/main/kotlin/net/corda/core/node/services/IdentityService.kt b/core/src/main/kotlin/net/corda/core/node/services/IdentityService.kt index 489e316e01..1aa8e35f24 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/IdentityService.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/IdentityService.kt @@ -1,6 +1,7 @@ package net.corda.core.node.services import net.corda.core.CordaException +import net.corda.core.DoNotImplement import net.corda.core.contracts.PartyAndReference import net.corda.core.identity.* import java.security.InvalidAlgorithmParameterException @@ -16,6 +17,7 @@ import java.security.cert.* * whereas confidential identities are distributed only on a need to know basis (typically between parties in * a transaction being built). See [NetworkMapCache] for retrieving well known identities from the network map. */ +@DoNotImplement interface IdentityService { val trustRoot: X509Certificate val trustAnchor: TrustAnchor diff --git a/core/src/main/kotlin/net/corda/core/node/services/KeyManagementService.kt b/core/src/main/kotlin/net/corda/core/node/services/KeyManagementService.kt index ed2d106bae..7b4801b112 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/KeyManagementService.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/KeyManagementService.kt @@ -1,6 +1,7 @@ package net.corda.core.node.services import co.paralleluniverse.fibers.Suspendable +import net.corda.core.DoNotImplement import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.SignableData import net.corda.core.crypto.TransactionSignature @@ -11,6 +12,7 @@ import java.security.PublicKey * The KMS is responsible for storing and using private keys to sign things. An implementation of this may, for example, * call out to a hardware security module that enforces various auditing and frequency-of-use requirements. */ +@DoNotImplement interface KeyManagementService { /** * Returns a snapshot of the current signing [PublicKey]s. diff --git a/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt b/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt index 792baadb39..b5c415c578 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt @@ -1,5 +1,6 @@ package net.corda.core.node.services +import net.corda.core.DoNotImplement import net.corda.core.concurrent.CordaFuture import net.corda.core.identity.AbstractParty import net.corda.core.identity.CordaX500Name @@ -18,8 +19,7 @@ import java.security.PublicKey * from an authoritative service, and adds easy lookup of the data stored within it. Generally it would be initialised * with a specified network map service, which it fetches data from and then subscribes to updates of. */ -interface NetworkMapCache { - +interface NetworkMapCache : NetworkMapCacheBase { @CordaSerializable sealed class MapChange { abstract val node: NodeInfo @@ -29,6 +29,23 @@ interface NetworkMapCache { data class Modified(override val node: NodeInfo, val previousNode: NodeInfo) : MapChange() } + /** + * Look up the node info for a specific party. Will attempt to de-anonymise the party if applicable; if the party + * is anonymised and the well known party cannot be resolved, it is impossible ot identify the node and therefore this + * returns null. + * Notice that when there are more than one node for a given party (in case of distributed services) first service node + * found will be returned. See also: [NetworkMapCache.getNodesByLegalIdentityKey]. + * + * @param party party to retrieve node information for. + * @return the node for the identity, or null if the node could not be found. This does not necessarily mean there is + * no node for the party, only that this cache is unaware of it. + */ + fun getNodeByLegalIdentity(party: AbstractParty): NodeInfo? +} + +/** Subset of [NetworkMapCache] that doesn't depend on an [IdentityService]. */ +@DoNotImplement +interface NetworkMapCacheBase { // DOCSTART 1 /** * A list of notary services available on the network. @@ -40,7 +57,7 @@ interface NetworkMapCache { // DOCEND 1 /** Tracks changes to the network map cache. */ - val changed: Observable + val changed: Observable /** Future to track completion of the NetworkMapService registration. */ val nodeReady: CordaFuture @@ -48,20 +65,7 @@ interface NetworkMapCache { * Atomically get the current party nodes and a stream of updates. Note that the Observable buffers updates until the * first subscriber is registered so as to avoid racing with early updates. */ - fun track(): DataFeed, MapChange> - - /** - * Look up the node info for a specific party. Will attempt to de-anonymise the party if applicable; if the party - * is anonymised and the well known party cannot be resolved, it is impossible ot identify the node and therefore this - * returns null. - * Notice that when there are more than one node for a given party (in case of distributed services) first service node - * found will be returned. See also: [getNodesByLegalIdentityKey]. - * - * @param party party to retrieve node information for. - * @return the node for the identity, or null if the node could not be found. This does not necessarily mean there is - * no node for the party, only that this cache is unaware of it. - */ - fun getNodeByLegalIdentity(party: AbstractParty): NodeInfo? + fun track(): DataFeed, NetworkMapCache.MapChange> /** * Look up the node info for a legal name. diff --git a/core/src/main/kotlin/net/corda/core/node/services/TransactionStorage.kt b/core/src/main/kotlin/net/corda/core/node/services/TransactionStorage.kt index 8173616eda..9b6b713ed2 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/TransactionStorage.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/TransactionStorage.kt @@ -1,5 +1,6 @@ package net.corda.core.node.services +import net.corda.core.DoNotImplement import net.corda.core.crypto.SecureHash import net.corda.core.messaging.DataFeed import net.corda.core.transactions.SignedTransaction @@ -8,6 +9,7 @@ import rx.Observable /** * Thread-safe storage of transactions. */ +@DoNotImplement interface TransactionStorage { /** * Return the transaction with the given [id], or null if no such transaction exists. diff --git a/core/src/main/kotlin/net/corda/core/node/services/TransactionVerifierService.kt b/core/src/main/kotlin/net/corda/core/node/services/TransactionVerifierService.kt index 8cc5c7af65..d72eec72f2 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/TransactionVerifierService.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/TransactionVerifierService.kt @@ -1,5 +1,6 @@ package net.corda.core.node.services +import net.corda.core.DoNotImplement import net.corda.core.concurrent.CordaFuture import net.corda.core.transactions.LedgerTransaction @@ -7,6 +8,7 @@ import net.corda.core.transactions.LedgerTransaction * Provides verification service. The implementation may be a simple in-memory verify() call or perhaps an IPC/RPC. * @suppress */ +@DoNotImplement interface TransactionVerifierService { /** * @param transaction The transaction to be verified. diff --git a/core/src/main/kotlin/net/corda/core/node/services/VaultService.kt b/core/src/main/kotlin/net/corda/core/node/services/VaultService.kt index 38545ac754..2440d80f77 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/VaultService.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/VaultService.kt @@ -1,6 +1,7 @@ package net.corda.core.node.services import co.paralleluniverse.fibers.Suspendable +import net.corda.core.DoNotImplement import net.corda.core.concurrent.CordaFuture import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash @@ -14,7 +15,6 @@ import net.corda.core.serialization.CordaSerializable import net.corda.core.toFuture import net.corda.core.utilities.NonEmptySet import rx.Observable -import rx.subjects.PublishSubject import java.time.Instant import java.util.* @@ -151,6 +151,7 @@ class Vault(val states: Iterable>) { * * Note that transactions we've seen are held by the storage service, not the vault. */ +@DoNotImplement interface VaultService { /** * Prefer the use of [updates] unless you know why you want to use this instead. diff --git a/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteria.kt b/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteria.kt index 1e5eeb858f..208109f3b4 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteria.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteria.kt @@ -2,6 +2,7 @@ package net.corda.core.node.services.vault +import net.corda.core.DoNotImplement import net.corda.core.contracts.ContractState import net.corda.core.contracts.StateRef import net.corda.core.contracts.UniqueIdentifier @@ -144,6 +145,7 @@ sealed class QueryCriteria { infix fun or(criteria: QueryCriteria): QueryCriteria = OrComposition(this, criteria) } +@DoNotImplement interface IQueryCriteriaParser { fun parseCriteria(criteria: QueryCriteria.CommonQueryCriteria): Collection fun parseCriteria(criteria: QueryCriteria.FungibleAssetQueryCriteria): Collection diff --git a/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteriaUtils.kt b/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteriaUtils.kt index 85d7292061..098a5a07e2 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteriaUtils.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteriaUtils.kt @@ -2,6 +2,7 @@ package net.corda.core.node.services.vault +import net.corda.core.DoNotImplement import net.corda.core.internal.uncheckedCast import net.corda.core.schemas.PersistentState import net.corda.core.serialization.CordaSerializable @@ -10,6 +11,7 @@ import kotlin.reflect.KProperty1 import kotlin.reflect.jvm.javaGetter @CordaSerializable +@DoNotImplement interface Operator enum class BinaryLogicalOperator : Operator { @@ -138,6 +140,7 @@ data class Sort(val columns: Collection) { } @CordaSerializable + @DoNotImplement interface Attribute enum class CommonStateAttribute(val attributeParent: String, val attributeChild: String?) : Attribute { diff --git a/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt b/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt index a91636ddc7..2b5705a187 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt @@ -8,6 +8,8 @@ import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.sequence import java.sql.Blob +data class ObjectWithCompatibleContext(val obj: T, val context: SerializationContext) + /** * An abstraction for serializing and deserializing objects, with support for versioning of the wire format via * a header / prefix in the bytes. @@ -22,6 +24,16 @@ abstract class SerializationFactory { */ abstract fun deserialize(byteSequence: ByteSequence, clazz: Class, context: SerializationContext): T + /** + * Deserialize the bytes in to an object, using the prefixed bytes to determine the format. + * + * @param byteSequence The bytes to deserialize, including a format header prefix. + * @param clazz The class or superclass or the object to be deserialized, or [Any] or [Object] if unknown. + * @param context A context that configures various parameters to deserialization. + * @return deserialized object along with [SerializationContext] to identify encoding used. + */ + abstract fun deserializeWithCompatibleContext(byteSequence: ByteSequence, clazz: Class, context: SerializationContext): ObjectWithCompatibleContext + /** * Serialize an object to bytes using the preferred serialization format version from the context. * @@ -87,6 +99,8 @@ abstract class SerializationFactory { } } +typealias VersionHeader = ByteSequence + /** * Parameters to serialization and deserialization. */ @@ -94,7 +108,7 @@ interface SerializationContext { /** * When serializing, use the format this header sequence represents. */ - val preferredSerializationVersion: ByteSequence + val preferredSerializationVersion: VersionHeader /** * The class loader to use for deserialization. */ @@ -147,7 +161,7 @@ interface SerializationContext { /** * Helper method to return a new context based on this context but with serialization using the format this header sequence represents. */ - fun withPreferredSerializationVersion(versionHeader: ByteSequence): SerializationContext + fun withPreferredSerializationVersion(versionHeader: VersionHeader): SerializationContext /** * The use case that we are serializing for, since it influences the implementations chosen. @@ -174,6 +188,15 @@ inline fun ByteSequence.deserialize(serializationFactory: Seri return serializationFactory.deserialize(this, T::class.java, context) } +/** + * Additionally returns [SerializationContext] which was used for encoding. + * It might be helpful to know [SerializationContext] to use the same encoding in the reply. + */ +inline fun ByteSequence.deserializeWithCompatibleContext(serializationFactory: SerializationFactory = SerializationFactory.defaultFactory, + context: SerializationContext = serializationFactory.defaultContext): ObjectWithCompatibleContext { + return serializationFactory.deserializeWithCompatibleContext(this, T::class.java, context) +} + /** * Convenience extension method for deserializing SerializedBytes with type matching, utilising the defaults. */ diff --git a/core/src/main/kotlin/net/corda/core/transactions/BaseTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/BaseTransaction.kt index 0d5a798d9f..14483543b2 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/BaseTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/BaseTransaction.kt @@ -1,5 +1,6 @@ package net.corda.core.transactions +import net.corda.core.DoNotImplement import net.corda.core.contracts.* import net.corda.core.identity.Party import net.corda.core.internal.castIfPossible @@ -10,6 +11,7 @@ import java.util.function.Predicate /** * An abstract class defining fields shared by all transaction types in the system. */ +@DoNotImplement abstract class BaseTransaction : NamedByHash { /** The inputs of this transaction. Note that in BaseTransaction subclasses the type of this list may change! */ abstract val inputs: List<*> diff --git a/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt index 8f2c476004..ba7cb8de1f 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt @@ -25,7 +25,7 @@ abstract class TraversableTransaction(open val componentGroups: List> = deserialiseComponentGroup(ComponentGroupEnum.OUTPUTS_GROUP, { SerializedBytes>(it).deserialize(context = SerializationFactory.defaultFactory.defaultContext.withAttachmentsClassLoader(attachments)) }) /** Ordered list of ([CommandData], [PublicKey]) pairs that instruct the contracts what to do. */ - val commands: List> = deserialiseComponentGroup(ComponentGroupEnum.COMMANDS_GROUP, { SerializedBytes>(it).deserialize(context = SerializationFactory.defaultFactory.defaultContext.withAttachmentsClassLoader(attachments)) }) + val commands: List> = deserialiseCommands() override val notary: Party? = let { val notaries: List = deserialiseComponentGroup(ComponentGroupEnum.NOTARY_GROUP, { SerializedBytes(it).deserialize() }) @@ -74,6 +74,31 @@ abstract class TraversableTransaction(open val componentGroups: List> { + // TODO: we could avoid deserialising unrelated signers. + // However, current approach ensures the transaction is not malformed + // and it will throw if any of the signers objects is not List of public keys). + val signersList = deserialiseComponentGroup(ComponentGroupEnum.SIGNERS_GROUP, { SerializedBytes>(it).deserialize() }) + val commandDataList = deserialiseComponentGroup(ComponentGroupEnum.COMMANDS_GROUP, { SerializedBytes(it).deserialize(context = SerializationFactory.defaultFactory.defaultContext.withAttachmentsClassLoader(attachments)) }) + val group = componentGroups.firstOrNull { it.groupIndex == ComponentGroupEnum.COMMANDS_GROUP.ordinal } + if (group is FilteredComponentGroup) { + check(commandDataList.size <= signersList.size) { "Invalid Transaction. Less Signers (${signersList.size}) than CommandData (${commandDataList.size}) objects" } + val componentHashes = group.components.mapIndexed { index, component -> componentHash(group.nonces[index], component) } + val leafIndices = componentHashes.map { group.partialMerkleTree.leafIndex(it) } + if (leafIndices.isNotEmpty()) + check(leafIndices.max()!! < signersList.size) { "Invalid Transaction. A command with no corresponding signer detected" } + return commandDataList.mapIndexed { index, commandData -> Command(commandData, signersList[leafIndices[index]]) } + } else { + // It is a WireTransaction + // or a FilteredTransaction with no Commands (in which case group is null). + check(commandDataList.size == signersList.size) { "Invalid Transaction. Sizes of CommandData (${commandDataList.size}) and Signers (${signersList.size}) do not match" } + return commandDataList.mapIndexed { index, commandData -> Command(commandData, signersList[index]) } + } + } } /** @@ -111,11 +136,12 @@ class FilteredTransaction private constructor( val filteredSerialisedComponents: MutableMap> = hashMapOf() val filteredComponentNonces: MutableMap> = hashMapOf() val filteredComponentHashes: MutableMap> = hashMapOf() // Required for partial Merkle tree generation. + var signersIncluded = false fun filter(t: T, componentGroupIndex: Int, internalIndex: Int) { if (filtering.test(t)) { val group = filteredSerialisedComponents[componentGroupIndex] - // Because the filter passed, we know there is a match. We also use first vs single as the init function + // Because the filter passed, we know there is a match. We also use first Vs single as the init function // of WireTransaction ensures there are no duplicated groups. val serialisedComponent = wtx.componentGroups.first { it.groupIndex == componentGroupIndex }.components[internalIndex] if (group == null) { @@ -132,6 +158,17 @@ class FilteredTransaction private constructor( filteredComponentNonces[componentGroupIndex]!!.add(wtx.availableComponentNonces[componentGroupIndex]!![internalIndex]) filteredComponentHashes[componentGroupIndex]!!.add(wtx.availableComponentHashes[componentGroupIndex]!![internalIndex]) } + // If at least one command is visible, then all command-signers should be visible as well. + // This is required for visibility purposes, see FilteredTransaction.checkAllCommandsVisible() for more details. + if (componentGroupIndex == ComponentGroupEnum.COMMANDS_GROUP.ordinal && !signersIncluded) { + signersIncluded = true + val signersGroupIndex = ComponentGroupEnum.SIGNERS_GROUP.ordinal + // There exist commands, thus the signers group is not empty. + val signersGroupComponents = wtx.componentGroups.first { it.groupIndex == signersGroupIndex } + filteredSerialisedComponents.put(signersGroupIndex, signersGroupComponents.components.toMutableList()) + filteredComponentNonces.put(signersGroupIndex, wtx.availableComponentNonces[signersGroupIndex]!!.toMutableList()) + filteredComponentHashes.put(signersGroupIndex, wtx.availableComponentHashes[signersGroupIndex]!!.toMutableList()) + } } } @@ -142,6 +179,10 @@ class FilteredTransaction private constructor( wtx.attachments.forEachIndexed { internalIndex, it -> filter(it, ComponentGroupEnum.ATTACHMENTS_GROUP.ordinal, internalIndex) } if (wtx.notary != null) filter(wtx.notary, ComponentGroupEnum.NOTARY_GROUP.ordinal, 0) if (wtx.timeWindow != null) filter(wtx.timeWindow, ComponentGroupEnum.TIMEWINDOW_GROUP.ordinal, 0) + // It is highlighted that because there is no a signers property in TraversableTransaction, + // one cannot specifically filter them in or out. + // The above is very important to ensure someone won't filter out the signers component group if at least one + // command is included in a FilteredTransaction. // It's sometimes possible that when we receive a WireTransaction for which there is a new or more unknown component groups, // we decide to filter and attach this field to a FilteredTransaction. @@ -207,7 +248,9 @@ class FilteredTransaction private constructor( /** * Function that checks if all of the components in a particular group are visible. * This functionality is required on non-Validating Notaries to check that all inputs are visible. - * It might also be applied in Oracles, where an Oracle should know it can see all commands. + * It might also be applied in Oracles or any other entity requiring [Command] visibility, but because this method + * cannot distinguish between related and unrelated to the signer [Command]s, one should use the + * [checkCommandVisibility] method, which is specifically designed for [Command] visibility purposes. * The logic behind this algorithm is that we check that the root of the provided group partialMerkleTree matches with the * root of a fullMerkleTree if computed using all visible components. * Note that this method is usually called after or before [verify], to also ensure that the provided partial Merkle @@ -229,18 +272,54 @@ class FilteredTransaction private constructor( visibilityCheck(group.groupIndex < groupHashes.size) { "There is no matching component group hash for group ${group.groupIndex}" } val groupPartialRoot = groupHashes[group.groupIndex] val groupFullRoot = MerkleTree.getMerkleTree(group.components.mapIndexed { index, component -> componentHash(group.nonces[index], component) }).hash - visibilityCheck(groupPartialRoot == groupFullRoot) { "The partial Merkle tree root does not match with the received root for group ${group.groupIndex}" } + visibilityCheck(groupPartialRoot == groupFullRoot) { "Some components for group ${group.groupIndex} are not visible" } + // Verify the top level Merkle tree from groupHashes. + visibilityCheck(MerkleTree.getMerkleTree(groupHashes).hash == id) { "Transaction is malformed. Top level Merkle tree cannot be verified against transaction's id" } } } - inline private fun verificationCheck(value: Boolean, lazyMessage: () -> Any): Unit { + /** + * Function that checks if all of the commands that should be signed by the input public key are visible. + * This functionality is required from Oracles to check that all of the commands they should sign are visible. + * This algorithm uses the [ComponentGroupEnum.SIGNERS_GROUP] to count how many commands should be signed by the + * input [PublicKey] and it then matches it with the size of received [commands]. + * Note that this method does not throw if there are no commands for this key to sign in the original [WireTransaction]. + * @param publicKey signer's [PublicKey] + * @throws ComponentVisibilityException if not all of the related commands are visible. + */ + @Throws(ComponentVisibilityException::class) + fun checkCommandVisibility(publicKey: PublicKey) { + val commandSigners = componentGroups.firstOrNull { it.groupIndex == ComponentGroupEnum.SIGNERS_GROUP.ordinal } + val expectedNumOfCommands = expectedNumOfCommands(publicKey, commandSigners) + val receivedForThisKeyNumOfCommands = commands.filter { publicKey in it.signers }.size + visibilityCheck(expectedNumOfCommands == receivedForThisKeyNumOfCommands) { "$expectedNumOfCommands commands were expected, but received $receivedForThisKeyNumOfCommands" } + } + + // Function to return number of expected commands to sign. + private fun expectedNumOfCommands(publicKey: PublicKey, commandSigners: ComponentGroup?): Int { + checkAllComponentsVisible(ComponentGroupEnum.SIGNERS_GROUP) + if (commandSigners == null) return 0 + fun signersKeys (internalIndex: Int, opaqueBytes: OpaqueBytes): List { + try { + return SerializedBytes>(opaqueBytes.bytes).deserialize() + } catch (e: Exception) { + throw Exception("Malformed transaction, signers at index $internalIndex cannot be deserialised", e) + } + } + + return commandSigners.components + .mapIndexed { internalIndex, opaqueBytes -> signersKeys(internalIndex, opaqueBytes) } + .filter { signers -> publicKey in signers }.size + } + + inline private fun verificationCheck(value: Boolean, lazyMessage: () -> Any) { if (!value) { val message = lazyMessage() throw FilteredTransactionVerificationException(id, message.toString()) } } - inline private fun visibilityCheck(value: Boolean, lazyMessage: () -> Any): Unit { + inline private fun visibilityCheck(value: Boolean, lazyMessage: () -> Any) { if (!value) { val message = lazyMessage() throw ComponentVisibilityException(id, message.toString()) diff --git a/core/src/main/kotlin/net/corda/core/transactions/TransactionWithSignatures.kt b/core/src/main/kotlin/net/corda/core/transactions/TransactionWithSignatures.kt index ab25d68064..6835e39686 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/TransactionWithSignatures.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/TransactionWithSignatures.kt @@ -1,5 +1,6 @@ package net.corda.core.transactions +import net.corda.core.DoNotImplement import net.corda.core.contracts.NamedByHash import net.corda.core.crypto.TransactionSignature import net.corda.core.crypto.isFulfilledBy @@ -10,6 +11,7 @@ import java.security.PublicKey import java.security.SignatureException /** An interface for transactions containing signatures, with logic for signature verification */ +@DoNotImplement interface TransactionWithSignatures : NamedByHash { val sigs: List diff --git a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt index 2325344286..7112369667 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt @@ -6,9 +6,10 @@ import net.corda.core.crypto.* import net.corda.core.identity.Party import net.corda.core.internal.Emoji import net.corda.core.node.ServicesForResolution -import net.corda.core.serialization.* -import net.corda.core.utilities.OpaqueBytes import net.corda.core.node.services.AttachmentId +import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.serialize +import net.corda.core.utilities.OpaqueBytes import java.security.PublicKey import java.security.SignatureException import java.util.function.Predicate @@ -213,10 +214,14 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr val componentGroupMap: MutableList = mutableListOf() if (inputs.isNotEmpty()) componentGroupMap.add(ComponentGroup(INPUTS_GROUP.ordinal, inputs.map { it.serialize() })) if (outputs.isNotEmpty()) componentGroupMap.add(ComponentGroup(OUTPUTS_GROUP.ordinal, outputs.map { it.serialize() })) - if (commands.isNotEmpty()) componentGroupMap.add(ComponentGroup(COMMANDS_GROUP.ordinal, commands.map { it.serialize() })) + // Adding commandData only to the commands group. Signers are added in their own group. + if (commands.isNotEmpty()) componentGroupMap.add(ComponentGroup(COMMANDS_GROUP.ordinal, commands.map { it.value.serialize() })) if (attachments.isNotEmpty()) componentGroupMap.add(ComponentGroup(ATTACHMENTS_GROUP.ordinal, attachments.map { it.serialize() })) if (notary != null) componentGroupMap.add(ComponentGroup(NOTARY_GROUP.ordinal, listOf(notary.serialize()))) if (timeWindow != null) componentGroupMap.add(ComponentGroup(TIMEWINDOW_GROUP.ordinal, listOf(timeWindow.serialize()))) + // Adding signers to their own group. This is required for command visibility purposes: a party receiving + // a FilteredTransaction can now verify it sees all the commands it should sign. + if (commands.isNotEmpty()) componentGroupMap.add(ComponentGroup(SIGNERS_GROUP.ordinal, commands.map { it.signers.serialize() })) return componentGroupMap } } diff --git a/core/src/test/kotlin/net/corda/core/concurrent/ConcurrencyUtilsTest.kt b/core/src/test/kotlin/net/corda/core/concurrent/ConcurrencyUtilsTest.kt index a0b4f62453..830398964c 100644 --- a/core/src/test/kotlin/net/corda/core/concurrent/ConcurrencyUtilsTest.kt +++ b/core/src/test/kotlin/net/corda/core/concurrent/ConcurrencyUtilsTest.kt @@ -3,6 +3,7 @@ package net.corda.core.concurrent import com.nhaarman.mockito_kotlin.* import net.corda.core.internal.concurrent.openFuture import net.corda.core.utilities.getOrThrow +import net.corda.testing.rigorousMock import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Test import org.slf4j.Logger @@ -16,7 +17,10 @@ class ConcurrencyUtilsTest { private val f1 = openFuture() private val f2 = openFuture() private var invocations = 0 - private val log = mock() + private val log = rigorousMock().also { + doNothing().whenever(it).error(any(), any()) + } + @Test fun `firstOf short circuit`() { // Order not significant in this case: diff --git a/core/src/test/kotlin/net/corda/core/contracts/CompatibleTransactionTests.kt b/core/src/test/kotlin/net/corda/core/contracts/CompatibleTransactionTests.kt index 7ed31d3d38..b50aa66b99 100644 --- a/core/src/test/kotlin/net/corda/core/contracts/CompatibleTransactionTests.kt +++ b/core/src/test/kotlin/net/corda/core/contracts/CompatibleTransactionTests.kt @@ -1,13 +1,9 @@ package net.corda.core.contracts import net.corda.core.contracts.ComponentGroupEnum.* -import net.corda.core.crypto.MerkleTree -import net.corda.core.crypto.SecureHash -import net.corda.core.crypto.secureRandomBytes +import net.corda.core.crypto.* import net.corda.core.serialization.serialize -import net.corda.core.transactions.ComponentGroup -import net.corda.core.transactions.ComponentVisibilityException -import net.corda.core.transactions.WireTransaction +import net.corda.core.transactions.* import net.corda.core.utilities.OpaqueBytes import net.corda.testing.* import net.corda.testing.contracts.DummyContract @@ -34,22 +30,24 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() { private val inputGroup by lazy { ComponentGroup(INPUTS_GROUP.ordinal, inputs.map { it.serialize() }) } private val outputGroup by lazy { ComponentGroup(OUTPUTS_GROUP.ordinal, outputs.map { it.serialize() }) } - private val commandGroup by lazy { ComponentGroup(COMMANDS_GROUP.ordinal, commands.map { it.serialize() }) } + private val commandGroup by lazy { ComponentGroup(COMMANDS_GROUP.ordinal, commands.map { it.value.serialize() }) } private val attachmentGroup by lazy { ComponentGroup(ATTACHMENTS_GROUP.ordinal, attachments.map { it.serialize() }) } // The list is empty. private val notaryGroup by lazy { ComponentGroup(NOTARY_GROUP.ordinal, listOf(notary.serialize())) } private val timeWindowGroup by lazy { ComponentGroup(TIMEWINDOW_GROUP.ordinal, listOf(timeWindow.serialize())) } + private val signersGroup by lazy { ComponentGroup(SIGNERS_GROUP.ordinal, commands.map { it.signers.serialize() }) } - private val newUnknownComponentGroup = ComponentGroup(20, listOf(OpaqueBytes(secureRandomBytes(4)), OpaqueBytes(secureRandomBytes(8)))) - private val newUnknownComponentEmptyGroup = ComponentGroup(21, emptyList()) + private val newUnknownComponentGroup = ComponentGroup(100, listOf(OpaqueBytes(secureRandomBytes(4)), OpaqueBytes(secureRandomBytes(8)))) + private val newUnknownComponentEmptyGroup = ComponentGroup(101, emptyList()) // Do not add attachments (empty list). private val componentGroupsA by lazy { listOf( - inputGroup, - outputGroup, - commandGroup, - notaryGroup, - timeWindowGroup + inputGroup, + outputGroup, + commandGroup, + notaryGroup, + timeWindowGroup, + signersGroup ) } private val wireTransactionA by lazy { WireTransaction(componentGroups = componentGroupsA, privacySalt = privacySalt) } @@ -74,7 +72,8 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() { commandGroup, attachmentGroup, notaryGroup, - timeWindowGroup + timeWindowGroup, + signersGroup ) assertFails { WireTransaction(componentGroups = componentGroupsEmptyAttachment, privacySalt = privacySalt) } @@ -86,7 +85,8 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() { outputGroup, commandGroup, notaryGroup, - timeWindowGroup + timeWindowGroup, + signersGroup ) val wireTransaction1ShuffledInputs = WireTransaction(componentGroups = componentGroupsB, privacySalt = privacySalt) // The ID has changed due to change of the internal ordering in inputs. @@ -106,7 +106,8 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() { inputGroup, commandGroup, notaryGroup, - timeWindowGroup + timeWindowGroup, + signersGroup ) assertEquals(wireTransactionA, WireTransaction(componentGroups = shuffledComponentGroupsA, privacySalt = privacySalt)) } @@ -123,7 +124,8 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() { commandGroup, ComponentGroup(ATTACHMENTS_GROUP.ordinal, inputGroup.components), notaryGroup, - timeWindowGroup + timeWindowGroup, + signersGroup ) assertFails { WireTransaction(componentGroupsB, privacySalt) } @@ -134,7 +136,8 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() { commandGroup, // First commandsGroup. commandGroup, // Second commandsGroup. notaryGroup, - timeWindowGroup + timeWindowGroup, + signersGroup ) assertFails { WireTransaction(componentGroupsDuplicatedCommands, privacySalt) } @@ -144,7 +147,8 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() { outputGroup, commandGroup, notaryGroup, - timeWindowGroup + timeWindowGroup, + signersGroup ) assertFails { WireTransaction(componentGroupsC, privacySalt) } @@ -154,23 +158,24 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() { commandGroup, notaryGroup, timeWindowGroup, - newUnknownComponentGroup // A new unknown component with ordinal 20 that we cannot process. + signersGroup, + newUnknownComponentGroup // A new unknown component with ordinal 100 that we cannot process. ) // The old client (receiving more component types than expected) is still compatible. val wireTransactionCompatibleA = WireTransaction(componentGroupsCompatibleA, privacySalt) assertEquals(wireTransactionCompatibleA.availableComponentGroups, wireTransactionA.availableComponentGroups) // The known components are the same. assertNotEquals(wireTransactionCompatibleA, wireTransactionA) // But obviously, its Merkle root has changed Vs wireTransactionA (which doesn't include this extra component). - assertEquals(6, wireTransactionCompatibleA.componentGroups.size) - // The old client will trhow if receiving an empty component (even if this unknown). + // The old client will throw if receiving an empty component (even if this is unknown). val componentGroupsCompatibleEmptyNew = listOf( inputGroup, outputGroup, commandGroup, notaryGroup, timeWindowGroup, - newUnknownComponentEmptyGroup // A new unknown component with ordinal 21 that we cannot process. + signersGroup, + newUnknownComponentEmptyGroup // A new unknown component with ordinal 101 that we cannot process. ) assertFails { WireTransaction(componentGroupsCompatibleEmptyNew, privacySalt) } } @@ -179,7 +184,9 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() { fun `FilteredTransaction constructors and compatibility`() { // Filter out all of the components. val ftxNothing = wireTransactionA.buildFilteredTransaction(Predicate { false }) // Nothing filtered. - assertEquals(6, ftxNothing.groupHashes.size) // Although nothing filtered, we still receive the group hashes for the top level Merkle tree. + // Although nothing filtered, we still receive the group hashes for the top level Merkle tree. + // Note that attachments are not sent, but group hashes include the allOnesHash flag for the attachment group hash; that's why we expect +1 group hashes. + assertEquals(wireTransactionA.componentGroups.size + 1, ftxNothing.groupHashes.size) ftxNothing.verify() // Include all of the components. @@ -191,6 +198,7 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() { ftxAll.checkAllComponentsVisible(ATTACHMENTS_GROUP) ftxAll.checkAllComponentsVisible(NOTARY_GROUP) ftxAll.checkAllComponentsVisible(TIMEWINDOW_GROUP) + ftxAll.checkAllComponentsVisible(SIGNERS_GROUP) // Filter inputs only. fun filtering(elem: Any): Boolean { @@ -222,12 +230,14 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() { assertNotNull(ftxOneInput.filteredComponentGroups.firstOrNull { it.groupIndex == INPUTS_GROUP.ordinal }!!.partialMerkleTree) // And the Merkle tree. // The old client (receiving more component types than expected) is still compatible. - val componentGroupsCompatibleA = listOf(inputGroup, + val componentGroupsCompatibleA = listOf( + inputGroup, outputGroup, commandGroup, notaryGroup, timeWindowGroup, - newUnknownComponentGroup // A new unknown component with ordinal 10,000 that we cannot process. + signersGroup, + newUnknownComponentGroup // A new unknown component with ordinal 100 that we cannot process. ) val wireTransactionCompatibleA = WireTransaction(componentGroupsCompatibleA, privacySalt) val ftxCompatible = wireTransactionCompatibleA.buildFilteredTransaction(Predicate(::filtering)) @@ -245,9 +255,288 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() { ftxCompatibleAll.verify() assertEquals(wireTransactionCompatibleA.id, ftxCompatibleAll.id) - // Check we received the last (6th) element that we cannot process (backwards compatibility). - assertEquals(6, ftxCompatibleAll.filteredComponentGroups.size) + // Check we received the last element that we cannot process (backwards compatibility). + assertEquals(wireTransactionCompatibleA.componentGroups.size, ftxCompatibleAll.filteredComponentGroups.size) + // Hide one component group only. + // Filter inputs only. + fun filterOutInputs(elem: Any): Boolean { + return when (elem) { + is StateRef -> false + else -> true + } + } + val ftxCompatibleNoInputs = wireTransactionCompatibleA.buildFilteredTransaction(Predicate(::filterOutInputs)) + ftxCompatibleNoInputs.verify() + assertFailsWith { ftxCompatibleNoInputs.checkAllComponentsVisible(INPUTS_GROUP) } + assertEquals(wireTransactionCompatibleA.componentGroups.size - 1, ftxCompatibleNoInputs.filteredComponentGroups.size) + assertEquals(wireTransactionCompatibleA.componentGroups.map { it.groupIndex }.max()!!, ftxCompatibleNoInputs.groupHashes.size - 1) + } + + @Test + fun `Command visibility tests`() { + // 1st and 3rd commands require a signature from KEY_1. + val twoCommandsforKey1 = listOf(dummyCommand(DUMMY_KEY_1.public, DUMMY_KEY_2.public), dummyCommand(DUMMY_KEY_2.public), dummyCommand(DUMMY_KEY_1.public)) + val componentGroups = listOf( + inputGroup, + outputGroup, + ComponentGroup(COMMANDS_GROUP.ordinal, twoCommandsforKey1.map { it.value.serialize() }), + notaryGroup, + timeWindowGroup, + ComponentGroup(SIGNERS_GROUP.ordinal, twoCommandsforKey1.map { it.signers.serialize() }), + newUnknownComponentGroup // A new unknown component with ordinal 100 that we cannot process. + ) + val wtx = WireTransaction(componentGroups = componentGroups, privacySalt = PrivacySalt()) + + // Filter all commands. + fun filterCommandsOnly(elem: Any): Boolean { + return when (elem) { + is Command<*> -> true // Even if one Command is filtered, all signers are automatically filtered as well + else -> false + } + } + + // Filter out commands only. + fun filterOutCommands(elem: Any): Boolean { + return when (elem) { + is Command<*> -> false + else -> true + } + } + + // Filter KEY_1 commands. + fun filterKEY1Commands(elem: Any): Boolean { + return when (elem) { + is Command<*> -> DUMMY_KEY_1.public in elem.signers + else -> false + } + } + + // Filter only one KEY_1 command. + fun filterTwoSignersCommands(elem: Any): Boolean { + return when (elem) { + is Command<*> -> elem.signers.size == 2 // dummyCommand(DUMMY_KEY_1.public) is filtered out. + else -> false + } + } + + // Again filter only one KEY_1 command. + fun filterSingleSignersCommands(elem: Any): Boolean { + return when (elem) { + is Command<*> -> elem.signers.size == 1 // dummyCommand(DUMMY_KEY_1.public, DUMMY_KEY_2.public) is filtered out. + else -> false + } + } + + val allCommandsFtx = wtx.buildFilteredTransaction(Predicate(::filterCommandsOnly)) + val noCommandsFtx = wtx.buildFilteredTransaction(Predicate(::filterOutCommands)) + val key1CommandsFtx = wtx.buildFilteredTransaction(Predicate(::filterKEY1Commands)) + val oneKey1CommandFtxA = wtx.buildFilteredTransaction(Predicate(::filterTwoSignersCommands)) + val oneKey1CommandFtxB = wtx.buildFilteredTransaction(Predicate(::filterSingleSignersCommands)) + + allCommandsFtx.checkCommandVisibility(DUMMY_KEY_1.public) + assertFailsWith { noCommandsFtx.checkCommandVisibility(DUMMY_KEY_1.public) } + key1CommandsFtx.checkCommandVisibility(DUMMY_KEY_1.public) + assertFailsWith { oneKey1CommandFtxA.checkCommandVisibility(DUMMY_KEY_1.public) } + assertFailsWith { oneKey1CommandFtxB.checkCommandVisibility(DUMMY_KEY_1.public) } + + allCommandsFtx.checkAllComponentsVisible(SIGNERS_GROUP) + assertFailsWith { noCommandsFtx.checkAllComponentsVisible(SIGNERS_GROUP) } // If we filter out all commands, signers are not sent as well. + key1CommandsFtx.checkAllComponentsVisible(SIGNERS_GROUP) // If at least one Command is visible, then all Signers are visible. + oneKey1CommandFtxA.checkAllComponentsVisible(SIGNERS_GROUP) // If at least one Command is visible, then all Signers are visible. + oneKey1CommandFtxB.checkAllComponentsVisible(SIGNERS_GROUP) // If at least one Command is visible, then all Signers are visible. + + // We don't send a list of signers. + val componentGroupsCompatible = listOf( + inputGroup, + outputGroup, + ComponentGroup(COMMANDS_GROUP.ordinal, twoCommandsforKey1.map { it.value.serialize() }), + notaryGroup, + timeWindowGroup, + // ComponentGroup(SIGNERS_GROUP.ordinal, twoCommandsforKey1.map { it.signers.serialize() }), + newUnknownComponentGroup // A new unknown component with ordinal 100 that we cannot process. + ) + + // Invalid Transaction. Sizes of CommandData and Signers (empty) do not match. + assertFailsWith { WireTransaction(componentGroups = componentGroupsCompatible, privacySalt = PrivacySalt()) } + + // We send smaller list of signers. + val componentGroupsLessSigners = listOf( + inputGroup, + outputGroup, + ComponentGroup(COMMANDS_GROUP.ordinal, twoCommandsforKey1.map { it.value.serialize() }), + notaryGroup, + timeWindowGroup, + ComponentGroup(SIGNERS_GROUP.ordinal, twoCommandsforKey1.map { it.signers.serialize() }.subList(0, 1)), // Send first signer only. + newUnknownComponentGroup // A new unknown component with ordinal 100 that we cannot process. + ) + + // Invalid Transaction. Sizes of CommandData and Signers (empty) do not match. + assertFailsWith { WireTransaction(componentGroups = componentGroupsLessSigners, privacySalt = PrivacySalt()) } + + // Test if there is no command to sign. + val commandsNoKey1= listOf(dummyCommand(DUMMY_KEY_2.public)) + + val componentGroupsNoKey1ToSign = listOf( + inputGroup, + outputGroup, + ComponentGroup(COMMANDS_GROUP.ordinal, commandsNoKey1.map { it.value.serialize() }), + notaryGroup, + timeWindowGroup, + ComponentGroup(SIGNERS_GROUP.ordinal, commandsNoKey1.map { it.signers.serialize() }), + newUnknownComponentGroup // A new unknown component with ordinal 100 that we cannot process. + ) + + val wtxNoKey1 = WireTransaction(componentGroups = componentGroupsNoKey1ToSign, privacySalt = PrivacySalt()) + val allCommandsNoKey1Ftx= wtxNoKey1.buildFilteredTransaction(Predicate(::filterCommandsOnly)) + allCommandsNoKey1Ftx.checkCommandVisibility(DUMMY_KEY_1.public) // This will pass, because there are indeed no commands to sign in the original transaction. + } + + @Test + fun `FilteredTransaction signer manipulation tests`() { + // Required to call the private constructor. + val ftxConstructor = FilteredTransaction::class.java.declaredConstructors[1] + ftxConstructor.isAccessible = true + + // 1st and 3rd commands require a signature from KEY_1. + val twoCommandsforKey1 = listOf(dummyCommand(DUMMY_KEY_1.public, DUMMY_KEY_2.public), dummyCommand(DUMMY_KEY_2.public), dummyCommand(DUMMY_KEY_1.public)) + val componentGroups = listOf( + inputGroup, + outputGroup, + ComponentGroup(COMMANDS_GROUP.ordinal, twoCommandsforKey1.map { it.value.serialize() }), + notaryGroup, + timeWindowGroup, + ComponentGroup(SIGNERS_GROUP.ordinal, twoCommandsforKey1.map { it.signers.serialize() }) + ) + val wtx = WireTransaction(componentGroups = componentGroups, privacySalt = PrivacySalt()) + + // Filter KEY_1 commands (commands 1 and 3). + fun filterKEY1Commands(elem: Any): Boolean { + return when (elem) { + is Command<*> -> DUMMY_KEY_1.public in elem.signers + else -> false + } + } + + // Filter KEY_2 commands (commands 1 and 2). + fun filterKEY2Commands(elem: Any): Boolean { + return when (elem) { + is Command<*> -> DUMMY_KEY_2.public in elem.signers + else -> false + } + } + + val key1CommandsFtx = wtx.buildFilteredTransaction(Predicate(::filterKEY1Commands)) + val key2CommandsFtx = wtx.buildFilteredTransaction(Predicate(::filterKEY2Commands)) + + // val commandDataComponents = key1CommandsFtx.filteredComponentGroups[0].components + val commandDataHashes = wtx.availableComponentHashes[ComponentGroupEnum.COMMANDS_GROUP.ordinal]!! + val noLastCommandDataPMT = PartialMerkleTree.build( + MerkleTree.getMerkleTree(commandDataHashes), + commandDataHashes.subList(0, 1) + ) + val noLastCommandDataComponents = key1CommandsFtx.filteredComponentGroups[0].components.subList(0, 1) + val noLastCommandDataNonces = key1CommandsFtx.filteredComponentGroups[0].nonces.subList(0, 1) + val noLastCommandDataGroup = FilteredComponentGroup( + ComponentGroupEnum.COMMANDS_GROUP.ordinal, + noLastCommandDataComponents, + noLastCommandDataNonces, + noLastCommandDataPMT + ) + + val signerComponents = key1CommandsFtx.filteredComponentGroups[1].components + val signerHashes = wtx.availableComponentHashes[ComponentGroupEnum.SIGNERS_GROUP.ordinal]!! + val noLastSignerPMT = PartialMerkleTree.build( + MerkleTree.getMerkleTree(signerHashes), + signerHashes.subList(0, 2) + ) + val noLastSignerComponents = key1CommandsFtx.filteredComponentGroups[1].components.subList(0, 2) + val noLastSignerNonces = key1CommandsFtx.filteredComponentGroups[1].nonces.subList(0, 2) + val noLastSignerGroup = FilteredComponentGroup( + ComponentGroupEnum.SIGNERS_GROUP.ordinal, + noLastSignerComponents, + noLastSignerNonces, + noLastSignerPMT + ) + val noLastSignerGroupSamePartialTree = FilteredComponentGroup( + ComponentGroupEnum.SIGNERS_GROUP.ordinal, + noLastSignerComponents, + noLastSignerNonces, + key1CommandsFtx.filteredComponentGroups[1].partialMerkleTree) // We don't update that, so we can catch the index mismatch. + + val updatedFilteredComponentsNoSignersKey2 = listOf(key2CommandsFtx.filteredComponentGroups[0], noLastSignerGroup) + val updatedFilteredComponentsNoSignersKey2SamePMT = listOf(key2CommandsFtx.filteredComponentGroups[0], noLastSignerGroupSamePartialTree) + + // There are only two components in key1CommandsFtx (commandData and signers). + assertEquals(2, key1CommandsFtx.componentGroups.size) + + // Remove last signer for which there is a pointer from a visible commandData. This is the case of Key1. + // This will result to an invalid transaction. + // A command with no corresponding signer detected + // because the pointer of CommandData (3rd leaf) cannot find a corresponding (3rd) signer. + val updatedFilteredComponentsNoSignersKey1SamePMT = listOf(key1CommandsFtx.filteredComponentGroups[0], noLastSignerGroupSamePartialTree) + assertFails { ftxConstructor.newInstance(key1CommandsFtx.id, updatedFilteredComponentsNoSignersKey1SamePMT, key1CommandsFtx.groupHashes) } + + // Remove both last signer (KEY1) and related command. + // Update partial Merkle tree for signers. + val updatedFilteredComponentsNoLastCommandAndSigners = listOf(noLastCommandDataGroup, noLastSignerGroup) + val ftxNoLastCommandAndSigners = ftxConstructor.newInstance(key1CommandsFtx.id, updatedFilteredComponentsNoLastCommandAndSigners, key1CommandsFtx.groupHashes) as FilteredTransaction + // verify() will pass as the transaction is well-formed. + ftxNoLastCommandAndSigners.verify() + // checkCommandVisibility() will not pass, because checkAllComponentsVisible(ComponentGroupEnum.SIGNERS_GROUP) will fail. + assertFailsWith { ftxNoLastCommandAndSigners.checkCommandVisibility(DUMMY_KEY_1.public) } + + // Remove last signer for which there is no pointer from a visible commandData. This is the case of Key2. + // Do not change partial Merkle tree for signers. + // This time the object can be constructed as there is no pointer mismatch. + val ftxNoLastSigner = ftxConstructor.newInstance(key2CommandsFtx.id, updatedFilteredComponentsNoSignersKey2SamePMT, key2CommandsFtx.groupHashes) as FilteredTransaction + // verify() will fail as we didn't change the partial Merkle tree. + assertFailsWith { ftxNoLastSigner.verify() } + // checkCommandVisibility() will not pass. + assertFailsWith { ftxNoLastSigner.checkCommandVisibility(DUMMY_KEY_2.public) } + + // Remove last signer for which there is no pointer from a visible commandData. This is the case of Key2. + // Update partial Merkle tree for signers. + val ftxNoLastSignerB = ftxConstructor.newInstance(key2CommandsFtx.id, updatedFilteredComponentsNoSignersKey2, key2CommandsFtx.groupHashes) as FilteredTransaction + // verify() will pass, the transaction is well-formed. + ftxNoLastSignerB.verify() + // But, checkAllComponentsVisible() will not pass. + assertFailsWith { ftxNoLastSignerB.checkCommandVisibility(DUMMY_KEY_2.public) } + + // Modify last signer (we have a pointer from commandData). + // Update partial Merkle tree for signers. + val alterSignerComponents = signerComponents.subList(0, 2) + signerComponents[1] // Third one is removed and the 2nd command is added twice. + val alterSignersHashes = wtx.availableComponentHashes[ComponentGroupEnum.SIGNERS_GROUP.ordinal]!!.subList(0, 2) + componentHash(key1CommandsFtx.filteredComponentGroups[1].nonces[2], alterSignerComponents[2]) + val alterMTree = MerkleTree.getMerkleTree(alterSignersHashes) + val alterSignerPMTK = PartialMerkleTree.build( + alterMTree, + alterSignersHashes + ) + + val alterSignerGroup = FilteredComponentGroup( + ComponentGroupEnum.SIGNERS_GROUP.ordinal, + alterSignerComponents, + key1CommandsFtx.filteredComponentGroups[1].nonces, + alterSignerPMTK + ) + val alterFilteredComponents = listOf(key1CommandsFtx.filteredComponentGroups[0], alterSignerGroup) + + // Do not update groupHashes. + val ftxAlterSigner = ftxConstructor.newInstance(key1CommandsFtx.id, alterFilteredComponents, key1CommandsFtx.groupHashes) as FilteredTransaction + // Visible components in signers group cannot be verified against their partial Merkle tree. + assertFailsWith { ftxAlterSigner.verify() } + // Also, checkAllComponentsVisible() will not pass (groupHash matching will fail). + assertFailsWith { ftxAlterSigner.checkCommandVisibility(DUMMY_KEY_1.public) } + + // Update groupHashes. + val ftxAlterSignerB = ftxConstructor.newInstance(key1CommandsFtx.id, alterFilteredComponents, key1CommandsFtx.groupHashes.subList(0, 6) + alterMTree.hash) as FilteredTransaction + // Visible components in signers group cannot be verified against their partial Merkle tree. + assertFailsWith { ftxAlterSignerB.verify() } + // Also, checkAllComponentsVisible() will not pass (top level Merkle tree cannot be verified against transaction's id). + assertFailsWith { ftxAlterSignerB.checkCommandVisibility(DUMMY_KEY_1.public) } + + ftxConstructor.isAccessible = false } } + diff --git a/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt b/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt index a82e698a97..e476f64cb5 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt @@ -1,6 +1,5 @@ package net.corda.core.crypto - import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash.Companion.zeroHash import net.corda.core.identity.Party @@ -14,10 +13,12 @@ import net.corda.testing.* import org.junit.Test import java.security.PublicKey import java.util.function.Predicate +import java.util.stream.IntStream +import kotlin.streams.toList import kotlin.test.* class PartialMerkleTreeTest : TestDependencyInjectionBase() { - val nodes = "abcdef" + private val nodes = "abcdef" private val hashed = nodes.map { initialiseTestSerialization() try { @@ -115,16 +116,18 @@ class PartialMerkleTreeTest : TestDependencyInjectionBase() { val d = testTx.serialize().deserialize() assertEquals(testTx.id, d.id) - val mt = testTx.buildFilteredTransaction(Predicate(::filtering)) + val ftx = testTx.buildFilteredTransaction(Predicate(::filtering)) - assertEquals(4, mt.filteredComponentGroups.size) - assertEquals(1, mt.inputs.size) - assertEquals(0, mt.attachments.size) - assertEquals(1, mt.outputs.size) - assertEquals(1, mt.commands.size) - assertNull(mt.notary) - assertNotNull(mt.timeWindow) - mt.verify() + // We expect 5 and not 4 component groups, because there is at least one command in the ftx and thus, + // the signers component is also sent (required for visibility purposes). + assertEquals(5, ftx.filteredComponentGroups.size) + assertEquals(1, ftx.inputs.size) + assertEquals(0, ftx.attachments.size) + assertEquals(1, ftx.outputs.size) + assertEquals(1, ftx.commands.size) + assertNull(ftx.notary) + assertNotNull(ftx.timeWindow) + ftx.verify() } @Test @@ -246,4 +249,50 @@ class PartialMerkleTreeTest : TestDependencyInjectionBase() { privacySalt = privacySalt ) } + + @Test + fun `Find leaf index`() { + // A Merkle tree with 20 leaves. + val sampleLeaves = IntStream.rangeClosed(0, 19).toList().map { SecureHash.sha256(it.toString()) } + val merkleTree = MerkleTree.getMerkleTree(sampleLeaves) + + // Provided hashes are not in the tree. + assertFailsWith { PartialMerkleTree.build(merkleTree, listOf(SecureHash.sha256("20"))) } + // One of the provided hashes is not in the tree. + assertFailsWith { PartialMerkleTree.build(merkleTree, listOf(SecureHash.sha256("20"), SecureHash.sha256("1"), SecureHash.sha256("5"))) } + + val pmt = PartialMerkleTree.build(merkleTree, listOf(SecureHash.sha256("1"), SecureHash.sha256("5"), SecureHash.sha256("0"), SecureHash.sha256("19"))) + // First leaf. + assertEquals(0, pmt.leafIndex(SecureHash.sha256("0"))) + // Second leaf. + assertEquals(1, pmt.leafIndex(SecureHash.sha256("1"))) + // A random leaf. + assertEquals(5, pmt.leafIndex(SecureHash.sha256("5"))) + // The last leaf. + assertEquals(19, pmt.leafIndex(SecureHash.sha256("19"))) + // The provided hash is not in the tree. + assertFailsWith { pmt.leafIndex(SecureHash.sha256("10")) } + // The provided hash is not in the tree (using a leaf that didn't exist in the original Merkle tree). + assertFailsWith { pmt.leafIndex(SecureHash.sha256("30")) } + + val pmtFirstElementOnly = PartialMerkleTree.build(merkleTree, listOf(SecureHash.sha256("0"))) + assertEquals(0, pmtFirstElementOnly.leafIndex(SecureHash.sha256("0"))) + // The provided hash is not in the tree. + assertFailsWith { pmtFirstElementOnly.leafIndex(SecureHash.sha256("10")) } + + val pmtLastElementOnly = PartialMerkleTree.build(merkleTree, listOf(SecureHash.sha256("19"))) + assertEquals(19, pmtLastElementOnly.leafIndex(SecureHash.sha256("19"))) + // The provided hash is not in the tree. + assertFailsWith { pmtLastElementOnly.leafIndex(SecureHash.sha256("10")) } + + val pmtOneElement = PartialMerkleTree.build(merkleTree, listOf(SecureHash.sha256("5"))) + assertEquals(5, pmtOneElement.leafIndex(SecureHash.sha256("5"))) + // The provided hash is not in the tree. + assertFailsWith { pmtOneElement.leafIndex(SecureHash.sha256("10")) } + + val pmtAllIncluded = PartialMerkleTree.build(merkleTree, sampleLeaves) + for (i in 0..19) assertEquals(i, pmtAllIncluded.leafIndex(SecureHash.sha256(i.toString()))) + // The provided hash is not in the tree (using a leaf that didn't exist in the original Merkle tree). + assertFailsWith { pmtAllIncluded.leafIndex(SecureHash.sha256("30")) } + } } diff --git a/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt b/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt index aae7f6fc0d..81e67b3b6e 100644 --- a/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt @@ -7,24 +7,21 @@ import net.corda.core.crypto.sha256 import net.corda.core.identity.Party import net.corda.core.internal.FetchAttachmentsFlow import net.corda.core.internal.FetchDataFlow -import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.utilities.getOrThrow import net.corda.node.internal.StartedNode -import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.persistence.NodeAttachmentService -import net.corda.nodeapi.internal.ServiceInfo import net.corda.testing.ALICE import net.corda.testing.ALICE_NAME import net.corda.testing.BOB import net.corda.testing.node.MockNetwork +import net.corda.testing.node.MockNodeArgs +import net.corda.testing.node.MockNodeParameters import net.corda.testing.singleIdentity import org.junit.After import org.junit.Before import org.junit.Test import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream -import java.math.BigInteger -import java.security.KeyPair import java.util.jar.JarOutputStream import java.util.zip.ZipEntry import kotlin.test.assertEquals @@ -60,7 +57,6 @@ class AttachmentTests { // Ensure that registration was successful before progressing any further mockNet.runNetwork() - aliceNode.internals.ensureRegistered() val alice = aliceNode.info.singleIdentity() aliceNode.internals.registerInitiatedFlow(FetchAttachmentsResponse::class.java) @@ -98,7 +94,6 @@ class AttachmentTests { // Ensure that registration was successful before progressing any further mockNet.runNetwork() - aliceNode.internals.ensureRegistered() aliceNode.internals.registerInitiatedFlow(FetchAttachmentsResponse::class.java) bobNode.internals.registerInitiatedFlow(FetchAttachmentsResponse::class.java) @@ -116,20 +111,15 @@ class AttachmentTests { @Test fun `malicious response`() { // Make a node that doesn't do sanity checking at load time. - val aliceNode = mockNet.createNotaryNode(legalName = ALICE.name, nodeFactory = object : MockNetwork.Factory { - override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, - id: Int, notaryIdentity: Pair?, - entropyRoot: BigInteger): MockNetwork.MockNode { - return object : MockNetwork.MockNode(config, network, networkMapAddr, id, notaryIdentity, entropyRoot) { + val aliceNode = mockNet.createNotaryNode(MockNodeParameters(legalName = ALICE.name), nodeFactory = object : MockNetwork.Factory { + override fun create(args: MockNodeArgs): MockNetwork.MockNode { + return object : MockNetwork.MockNode(args) { override fun start() = super.start().apply { attachments.checkAttachmentsOnLoad = false } } } }, validating = false) - val bobNode = mockNet.createNode(legalName = BOB.name) - - // Ensure that registration was successful before progressing any further + val bobNode = mockNet.createNode(MockNodeParameters(legalName = BOB.name)) mockNet.runNetwork() - aliceNode.internals.ensureRegistered() val alice = aliceNode.services.myInfo.identityFromX500Name(ALICE_NAME) aliceNode.internals.registerInitiatedFlow(FetchAttachmentsResponse::class.java) diff --git a/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt b/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt index a0b2c0020c..9ecea5940b 100644 --- a/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt @@ -44,7 +44,6 @@ class CollectSignaturesFlowTests { bobNode = mockNet.createPartyNode(BOB.name) charlieNode = mockNet.createPartyNode(CHARLIE.name) mockNet.runNetwork() - aliceNode.internals.ensureRegistered() alice = aliceNode.info.singleIdentity() bob = bobNode.info.singleIdentity() charlie = charlieNode.info.singleIdentity() diff --git a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt index 782d3ee35f..19b93ba663 100644 --- a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt +++ b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt @@ -47,7 +47,6 @@ class ContractUpgradeFlowTest { // Process registration mockNet.runNetwork() - aliceNode.internals.ensureRegistered() notary = notaryNode.services.getDefaultNotary() } @@ -119,7 +118,7 @@ class ContractUpgradeFlowTest { return startRpcClient( rpcAddress = startRpcServer( rpcUser = user, - ops = CordaRPCOpsImpl(node.services, node.smm, node.database) + ops = CordaRPCOpsImpl(node.services, node.smm, node.database, node.services) ).get().broker.hostAndPort!!, username = user.username, password = user.password diff --git a/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt b/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt index bdc76e1d83..1bc496db6c 100644 --- a/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt @@ -6,7 +6,7 @@ import net.corda.core.utilities.getOrThrow import net.corda.finance.POUNDS import net.corda.finance.contracts.asset.Cash import net.corda.finance.issuedBy -import net.corda.node.services.api.ServiceHubInternal +import net.corda.node.services.api.StartedNodeServices import net.corda.testing.* import net.corda.testing.node.MockNetwork import org.junit.After @@ -17,8 +17,8 @@ import kotlin.test.assertFailsWith class FinalityFlowTests { private lateinit var mockNet: MockNetwork - private lateinit var aliceServices: ServiceHubInternal - private lateinit var bobServices: ServiceHubInternal + private lateinit var aliceServices: StartedNodeServices + private lateinit var bobServices: StartedNodeServices private lateinit var alice: Party private lateinit var bob: Party private lateinit var notary: Party @@ -30,7 +30,6 @@ class FinalityFlowTests { val aliceNode = mockNet.createPartyNode(ALICE_NAME) val bobNode = mockNet.createPartyNode(BOB_NAME) mockNet.runNetwork() - aliceNode.internals.ensureRegistered() aliceServices = aliceNode.services bobServices = bobNode.services alice = aliceNode.info.singleIdentity() diff --git a/core/src/test/kotlin/net/corda/core/internal/concurrent/CordaFutureImplTest.kt b/core/src/test/kotlin/net/corda/core/internal/concurrent/CordaFutureImplTest.kt index 8b9efe991a..6ca2319239 100644 --- a/core/src/test/kotlin/net/corda/core/internal/concurrent/CordaFutureImplTest.kt +++ b/core/src/test/kotlin/net/corda/core/internal/concurrent/CordaFutureImplTest.kt @@ -3,6 +3,7 @@ package net.corda.core.internal.concurrent import com.nhaarman.mockito_kotlin.* import net.corda.core.concurrent.CordaFuture import net.corda.core.utilities.getOrThrow +import net.corda.testing.rigorousMock import org.assertj.core.api.Assertions import org.junit.Test import org.slf4j.Logger @@ -31,7 +32,7 @@ class CordaFutureTest { fun `if a listener fails its throwable is logged`() { val f = CordaFutureImpl() val x = Exception() - val log = mock() + val log = rigorousMock() val flag = AtomicBoolean() f.thenImpl(log) { throw x } f.thenImpl(log) { flag.set(true) } // Must not be affected by failure of previous listener. @@ -57,7 +58,7 @@ class CordaFutureTest { Assertions.assertThatThrownBy { g.getOrThrow() }.isSameAs(x) } run { - val block = mock<(Any?) -> Any?>() + val block = rigorousMock<(Any?) -> Any?>() val f = CordaFutureImpl() val g = f.map(block) val x = Exception() @@ -90,7 +91,7 @@ class CordaFutureTest { Assertions.assertThatThrownBy { g.getOrThrow() }.isSameAs(x) } run { - val block = mock<(Any?) -> CordaFuture<*>>() + val block = rigorousMock<(Any?) -> CordaFuture<*>>() val f = CordaFutureImpl() val g = f.flatMap(block) val x = Exception() @@ -102,7 +103,8 @@ class CordaFutureTest { @Test fun `andForget works`() { - val log = mock() + val log = rigorousMock() + doNothing().whenever(log).error(any(), any()) val throwable = Exception("Boom") val executor = Executors.newSingleThreadExecutor() executor.fork { throw throwable }.andForget(log) diff --git a/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt b/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt index f6d1eec6b2..8057164bcf 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt @@ -9,24 +9,21 @@ import net.corda.core.flows.InitiatingFlow import net.corda.core.flows.TestDataVendingFlow import net.corda.core.internal.FetchAttachmentsFlow import net.corda.core.internal.FetchDataFlow -import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.unwrap import net.corda.node.internal.InitiatedFlowFactory import net.corda.node.internal.StartedNode -import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.persistence.NodeAttachmentService import net.corda.node.utilities.currentDBSession -import net.corda.nodeapi.internal.ServiceInfo import net.corda.testing.chooseIdentity import net.corda.testing.node.MockNetwork +import net.corda.testing.node.MockNodeArgs +import net.corda.testing.node.MockNodeParameters import org.junit.After import org.junit.Before import org.junit.Test import java.io.ByteArrayOutputStream -import java.math.BigInteger import java.nio.charset.StandardCharsets.UTF_8 -import java.security.KeyPair import java.util.zip.ZipEntry import java.util.zip.ZipOutputStream import kotlin.test.assertEquals @@ -74,7 +71,6 @@ class AttachmentSerializationTest { client = mockNet.createNode() client.internals.disableDBCloseOnStop() // Otherwise the in-memory database may disappear (taking the checkpoint with it) while we reboot the client. mockNet.runNetwork() - server.internals.ensureRegistered() } @After @@ -160,10 +156,9 @@ class AttachmentSerializationTest { private fun rebootClientAndGetAttachmentContent(checkAttachmentsOnLoad: Boolean = true): String { client.dispose() - client = mockNet.createNode(client.internals.id, object : MockNetwork.Factory { - override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, - id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): MockNetwork.MockNode { - return object : MockNetwork.MockNode(config, network, networkMapAddr, id, notaryIdentity, entropyRoot) { + client = mockNet.createNode(MockNodeParameters(client.internals.id), object : MockNetwork.Factory { + override fun create(args: MockNodeArgs): MockNetwork.MockNode { + return object : MockNetwork.MockNode(args) { override fun start() = super.start().apply { attachments.checkAttachmentsOnLoad = checkAttachmentsOnLoad } } } diff --git a/core/src/test/kotlin/net/corda/core/utilities/KotlinUtilsTest.kt b/core/src/test/kotlin/net/corda/core/utilities/KotlinUtilsTest.kt index 22ef438a6b..b4ef01dbbc 100644 --- a/core/src/test/kotlin/net/corda/core/utilities/KotlinUtilsTest.kt +++ b/core/src/test/kotlin/net/corda/core/utilities/KotlinUtilsTest.kt @@ -6,6 +6,7 @@ import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.nodeapi.internal.serialization.KRYO_CHECKPOINT_CONTEXT +import net.corda.nodeapi.internal.serialization.KRYO_P2P_CONTEXT import net.corda.testing.TestDependencyInjectionBase import org.assertj.core.api.Assertions.assertThat import org.junit.Rule @@ -26,7 +27,7 @@ class KotlinUtilsTest : TestDependencyInjectionBase() { } @Test - fun `checkpointing a transient property with non-capturing lamba`() { + fun `checkpointing a transient property with non-capturing lambda`() { val original = NonCapturingTransientProperty() val originalVal = original.transientVal val copy = original.serialize(context = KRYO_CHECKPOINT_CONTEXT).deserialize(context = KRYO_CHECKPOINT_CONTEXT) @@ -36,15 +37,15 @@ class KotlinUtilsTest : TestDependencyInjectionBase() { } @Test - fun `serialise transient property with non-capturing lamba`() { + fun `serialise transient property with non-capturing lambda`() { expectedEx.expect(KryoException::class.java) expectedEx.expectMessage("is not annotated or on the whitelist, so cannot be used in serialization") val original = NonCapturingTransientProperty() - original.serialize() + original.serialize(context = KRYO_P2P_CONTEXT) } @Test - fun `deserialise transient property with non-capturing lamba`() { + fun `deserialise transient property with non-capturing lambda`() { expectedEx.expect(KryoException::class.java) expectedEx.expectMessage("is not annotated or on the whitelist, so cannot be used in serialization") val original = NonCapturingTransientProperty() @@ -52,7 +53,7 @@ class KotlinUtilsTest : TestDependencyInjectionBase() { } @Test - fun `checkpointing a transient property with capturing lamba`() { + fun `checkpointing a transient property with capturing lambda`() { val original = CapturingTransientProperty("Hello") val originalVal = original.transientVal val copy = original.serialize(context = KRYO_CHECKPOINT_CONTEXT).deserialize(context = KRYO_CHECKPOINT_CONTEXT) @@ -63,15 +64,15 @@ class KotlinUtilsTest : TestDependencyInjectionBase() { } @Test - fun `serialise transient property with capturing lamba`() { + fun `serialise transient property with capturing lambda`() { expectedEx.expect(KryoException::class.java) expectedEx.expectMessage("is not annotated or on the whitelist, so cannot be used in serialization") val original = CapturingTransientProperty("Hello") - original.serialize() + original.serialize(context = KRYO_P2P_CONTEXT) } @Test - fun `deserialise transient property with capturing lamba`() { + fun `deserialise transient property with capturing lambda`() { expectedEx.expect(KryoException::class.java) expectedEx.expectMessage("is not annotated or on the whitelist, so cannot be used in serialization") val original = CapturingTransientProperty("Hello") diff --git a/docs/build.gradle b/docs/build.gradle index 542cc7380f..a0d60a371e 100644 --- a/docs/build.gradle +++ b/docs/build.gradle @@ -5,13 +5,17 @@ dependencies { compile rootProject } +ext { + // TODO: Add '../client/jfx/src/main/kotlin' and '../client/mock/src/main/kotlin' if we decide to make them into public API + dokkaSourceDirs = files('../core/src/main/kotlin', '../client/rpc/src/main/kotlin', '../finance/src/main/kotlin', '../client/jackson/src/main/kotlin', + '../testing/test-utils/src/main/kotlin', '../testing/node-driver/src/main/kotlin') +} + dokka { moduleName = 'corda' outputDirectory = file("${rootProject.rootDir}/docs/build/html/api/kotlin") processConfigurations = ['compile'] - // TODO: Re-add '../testing/node-driver/src/main/kotlin', '../testing/test-utils/src/main/kotlin' when they're API stable - // TODO: Add '../client/jfx/src/main/kotlin' and '../client/mock/src/main/kotlin' if we decide to make them into public API - sourceDirs = files('../core/src/main/kotlin', '../client/rpc/src/main/kotlin', '../finance/src/main/kotlin', '../client/jackson/src/main/kotlin') + sourceDirs = dokkaSourceDirs includes = ['packages.md'] jdkVersion = 8 @@ -31,8 +35,7 @@ task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaTask) { outputFormat = "javadoc" outputDirectory = file("${rootProject.rootDir}/docs/build/html/api/javadoc") processConfigurations = ['compile'] - // TODO: Make this a copy of the list above programmatically. - sourceDirs = files('../core/src/main/kotlin', '../client/rpc/src/main/kotlin', '../finance/src/main/kotlin', '../client/jackson/src/main/kotlin') + sourceDirs = dokkaSourceDirs includes = ['packages.md'] jdkVersion = 8 diff --git a/docs/source/api-persistence.rst b/docs/source/api-persistence.rst index add8f1b785..c2c6e88e46 100644 --- a/docs/source/api-persistence.rst +++ b/docs/source/api-persistence.rst @@ -81,10 +81,10 @@ Custom schema registration Custom contract schemas are automatically registered at startup time for CorDapps. The node bootstrap process will scan for schemas (any class that extends the ``MappedSchema`` interface) in the `plugins` configuration directory in your CorDapp jar. -For testing purposes it is necessary to manually register custom schemas as follows: +For testing purposes it is necessary to manually register the packages containing custom schemas as follows: -- Tests using ``MockNetwork`` and ``MockNode`` must explicitly register custom schemas using the `registerCustomSchemas()` method of ``MockNode`` -- Tests using ``MockServices`` must explicitly register schemas using `customSchemas` attribute of the ``MockServices`` `makeTestDatabaseAndMockServices()` helper method. +- Tests using ``MockNetwork`` and ``MockNode`` must explicitly register packages using the `cordappPackages` parameter of ``MockNetwork`` +- Tests using ``MockServices`` must explicitly register packages using the `cordappPackages` parameter of the ``MockServices`` `makeTestDatabaseAndMockServices()` helper method. .. note:: Tests using the `DriverDSL` will automatically register your custom schemas if they are in the same project structure as the driver call. diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 480f187ce6..aa7af701b7 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -11,6 +11,8 @@ UNRELEASED * ``OpaqueBytes.bytes`` now returns a clone of its underlying ``ByteArray``, and has been redeclared as ``final``. This is a minor change to the public API, but is required to ensure that classes like ``SecureHash`` are immutable. +* Experimental support for PostgreSQL: CashSelection done using window functions + * ``FlowLogic`` now exposes a series of function called ``receiveAll(...)`` allowing to join ``receive(...)`` instructions. * Renamed "plugins" directory on nodes to "cordapps" @@ -28,7 +30,7 @@ UNRELEASED * ``Cordapp`` now has a name field for identifying CorDapps and all CorDapp names are printed to console at startup. -* Enums now respsect the whitelist applied to the Serializer factory serializing / deserializing them. If the enum isn't +* Enums now respect the whitelist applied to the Serializer factory serializing / deserializing them. If the enum isn't either annotated with the @CordaSerializable annotation or explicitly whitelisted then a NotSerializableException is thrown. @@ -56,6 +58,17 @@ UNRELEASED * ``TimeWindow`` now has a ``length`` property that returns the length of the time-window, or ``null`` if the time-window is open-ended. +* A new ``SIGNERS_GROUP`` with ordinal 6 has been added to ``ComponentGroupEnum`` that corresponds to the ``Command`` + signers. + +* ``PartialMerkleTree`` is equipped with a ``leafIndex`` function that returns the index of a hash (leaf) in the + partial Merkle tree structure. + +* A new function ``checkCommandVisibility(publicKey: PublicKey)`` has been added to ``FilteredTransaction`` to check + if every command that a signer should receive (e.g. an Oracle) is indeed visible. + +* Change the AMQP serialiser to use the oficially assigned R3 identifier rather than a placeholder. + .. _changelog_v1: Release 1.0 diff --git a/docs/source/corda-api.rst b/docs/source/corda-api.rst index 8b5dd94053..bb973b0f05 100644 --- a/docs/source/corda-api.rst +++ b/docs/source/corda-api.rst @@ -91,3 +91,14 @@ The following modules are available but we do not commit to their stability or c Future releases will reject any CorDapps that use types from these packages. .. warning:: The web server module will be removed in future. You should call Corda nodes through RPC from your web server of choice e.g., Spring Boot, Vertx, Undertow. + +The ``@DoNotImplement`` annotation +---------------------------------- + +Certain interfaces and abstract classes within the Corda API have been annotated +as ``@DoNotImplement``. While we undertake not to remove or modify any of these classes' existing +functionality, the annotation is a warning that we may need to extend them in future versions of Corda. +Cordapp developers should therefore just use these classes "as is", and *not* attempt to extend or implement any of them themselves. + +This annotation is inherited by subclasses and subinterfaces. + diff --git a/docs/source/corda-configuration-file.rst b/docs/source/corda-configuration-file.rst index 721e6ff74b..6b964d60a6 100644 --- a/docs/source/corda-configuration-file.rst +++ b/docs/source/corda-configuration-file.rst @@ -155,6 +155,16 @@ path to the node's base directory. :certificateSigningService: Certificate Signing Server address. It is used by the certificate signing request utility to obtain SSL certificate. (See :doc:`permissioning` for more information.) +:jvmArgs: An optional list of JVM args, as strings, which replace those inherited from the command line when launching via ``corda.jar`` + only. e.g. ``jvmArgs = [ "-Xmx220m", "-Xms220m", "-XX:+UseG1GC" ]`` + +:systemProperties: An optional map of additional system properties to be set when launching via ``corda.jar`` only. Keys and values + of the map should be strings. e.g. ``systemProperties = { visualvm.display.name = FooBar }`` + +:jarDirs: An optional list of file system directories containing JARs to include in the classpath when launching via ``corda.jar`` only. + Each should be a string. Only the JARs in the directories are added, not the directories themselves. This is useful + for including JDBC drivers and the like. e.g. ``jarDirs = [ 'lib' ]`` + :relay: If provided, the node will attempt to tunnel inbound connections via an external relay. The relay's address will be advertised to the network map service instead of the provided ``p2pAddress``. diff --git a/docs/source/example-code/build.gradle b/docs/source/example-code/build.gradle index 0568552add..6cad970f10 100644 --- a/docs/source/example-code/build.gradle +++ b/docs/source/example-code/build.gradle @@ -1,7 +1,6 @@ apply plugin: 'kotlin' apply plugin: 'application' apply plugin: 'net.corda.plugins.cordformation' -apply plugin: 'net.corda.plugins.cordapp' apply plugin: 'net.corda.plugins.quasar-utils' repositories { diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/mocknetwork/TutorialMockNetwork.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/mocknetwork/TutorialMockNetwork.kt new file mode 100644 index 0000000000..9e86b57aab --- /dev/null +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/mocknetwork/TutorialMockNetwork.kt @@ -0,0 +1,111 @@ +package net.corda.docs.tutorial.mocknetwork + +import co.paralleluniverse.fibers.Suspendable +import net.corda.core.contracts.requireThat +import net.corda.core.flows.FlowLogic +import net.corda.core.flows.FlowSession +import net.corda.core.flows.InitiatedBy +import net.corda.core.flows.InitiatingFlow +import net.corda.core.identity.Party +import net.corda.core.messaging.MessageRecipients +import net.corda.core.serialization.deserialize +import net.corda.core.serialization.serialize +import net.corda.core.utilities.getOrThrow +import net.corda.core.utilities.unwrap +import net.corda.node.internal.StartedNode +import net.corda.node.services.messaging.Message +import net.corda.node.services.statemachine.SessionData +import net.corda.testing.node.InMemoryMessagingNetwork +import net.corda.testing.node.MessagingServiceSpy +import net.corda.testing.node.MockNetwork +import net.corda.testing.node.setMessagingServiceSpy +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.ExpectedException + +class TutorialMockNetwork { + + @InitiatingFlow + class FlowA(private val otherParty: Party) : FlowLogic() { + + @Suspendable + override fun call() { + val session = initiateFlow(otherParty) + + session.receive().unwrap { + requireThat { "Expected to receive 1" using (it == 1) } + } + + session.receive().unwrap { + requireThat { "Expected to receive 2" using (it == 2) } + } + } + } + + @InitiatedBy(FlowA::class) + class FlowB(private val session: FlowSession) : FlowLogic() { + + @Suspendable + override fun call() { + session.send(1) + session.send(2) + } + } + + lateinit private var mockNet: MockNetwork + lateinit private var notary: StartedNode + lateinit private var nodeA: StartedNode + lateinit private var nodeB: StartedNode + + @Rule + @JvmField + val expectedEx: ExpectedException = ExpectedException.none() + + @Before + fun setUp() { + mockNet = MockNetwork() + notary = mockNet.createNotaryNode() + nodeA = mockNet.createPartyNode() + nodeB = mockNet.createPartyNode() + + nodeB.registerInitiatedFlow(FlowB::class.java) + + mockNet.runNetwork() + } + + @After + fun tearDown() { + mockNet.stopNodes() + } + + @Test + fun `fail if initiated doesn't send back 1 on first result`() { + + // DOCSTART 1 + // modify message if it's 1 + nodeB.setMessagingServiceSpy(object : MessagingServiceSpy(nodeB.network) { + + override fun send(message: Message, target: MessageRecipients, retryId: Long?, sequenceKey: Any, acknowledgementHandler: (() -> Unit)?) { + val messageData = message.data.deserialize() + + if (messageData is SessionData && messageData.payload.deserialize() == 1) { + val alteredMessageData = SessionData(messageData.recipientSessionId, 99.serialize()).serialize().bytes + messagingService.send(InMemoryMessagingNetwork.InMemoryMessage(message.topicSession, alteredMessageData, message.uniqueMessageId), target, retryId) + } else { + messagingService.send(message, target, retryId) + } + } + }) + // DOCEND 1 + + val initiatingReceiveFlow = nodeA.services.startFlow(FlowA(nodeB.info.legalIdentities.first())) + + mockNet.runNetwork() + + expectedEx.expect(IllegalArgumentException::class.java) + expectedEx.expectMessage("Expected to receive 1") + initiatingReceiveFlow.resultFuture.getOrThrow() + } +} \ No newline at end of file diff --git a/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt b/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt index 1ec43e7271..9690a5ab41 100644 --- a/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt +++ b/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt @@ -27,12 +27,18 @@ class CustomVaultQueryTest { @Before fun setup() { - mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.finance.contracts.asset", CashSchemaV1::class.packageName)) + mockNet = MockNetwork( + threadPerNode = true, + cordappPackages = listOf( + "net.corda.finance.contracts.asset", + CashSchemaV1::class.packageName, + "net.corda.docs" + ) + ) mockNet.createNotaryNode(legalName = DUMMY_NOTARY.name) nodeA = mockNet.createPartyNode() nodeB = mockNet.createPartyNode() nodeA.internals.registerInitiatedFlow(TopupIssuerFlow.TopupIssuer::class.java) - nodeA.internals.installCordaService(CustomVaultQuery.Service::class.java) notary = nodeA.services.getDefaultNotary() } diff --git a/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt b/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt index f7b99ead79..833f4c140d 100644 --- a/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt +++ b/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt @@ -9,7 +9,7 @@ import net.corda.core.node.services.queryBy import net.corda.core.node.services.vault.QueryCriteria import net.corda.core.toFuture import net.corda.core.utilities.getOrThrow -import net.corda.node.services.api.ServiceHubInternal +import net.corda.node.services.api.StartedNodeServices import net.corda.testing.* import net.corda.testing.node.MockNetwork import org.junit.After @@ -19,8 +19,8 @@ import kotlin.test.assertEquals class WorkflowTransactionBuildTutorialTest { lateinit var mockNet: MockNetwork - lateinit var aliceServices: ServiceHubInternal - lateinit var bobServices: ServiceHubInternal + lateinit var aliceServices: StartedNodeServices + lateinit var bobServices: StartedNodeServices lateinit var alice: Party lateinit var bob: Party diff --git a/docs/source/flow-testing.rst b/docs/source/flow-testing.rst index 967dbe9877..8e131b94ff 100644 --- a/docs/source/flow-testing.rst +++ b/docs/source/flow-testing.rst @@ -63,4 +63,17 @@ transaction as shown here. With regards to initiated flows (see :doc:`flow-state-machines` for information on initiated and initiating flows), the full node automatically registers them by scanning the CorDapp jars. In a unit test environment this is not possible so -``MockNode`` has the ``registerInitiatedFlow`` method to manually register an initiated flow. \ No newline at end of file +``MockNode`` has the ``registerInitiatedFlow`` method to manually register an initiated flow. + +MockNetwork message manipulation +-------------------------------- +The MockNetwork has the ability to manipulate message streams. You can use this to test your flows behaviour on corrupted, +or malicious data received. + +Message modification example in ``TutorialMockNetwork.kt``: + +.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/mocknetwork/TutorialMockNetwork.kt + :language: kotlin + :start-after: DOCSTART 1 + :end-before: DOCEND 1 + :dedent: 8 diff --git a/docs/source/key-concepts-contract-constraints.rst b/docs/source/key-concepts-contract-constraints.rst index edd4c6694d..35db806193 100644 --- a/docs/source/key-concepts-contract-constraints.rst +++ b/docs/source/key-concepts-contract-constraints.rst @@ -95,9 +95,9 @@ to specify JAR URLs in the case that the CorDapp(s) involved in testing already MockNetwork/MockNode ******************** -The most simple way to ensure that a vanilla instance of a MockNode generates the correct CorDapps is to make a call -to ``setCordappPackages`` before the MockNetwork/Node are created and then ``unsetCordappPackages`` after the test -has finished. These calls will cause the ``AbstractNode`` to use the named packages as sources for CorDapps. All files +The most simple way to ensure that a vanilla instance of a MockNode generates the correct CorDapps is to use the +``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: @@ -108,17 +108,7 @@ within those packages will be zipped into a JAR and added to the attachment stor @Before void setup() { - // The ordering of the two below lines is important - if the MockNetwork is created before the nodes and network - // are created the CorDapps will not be loaded into the MockNodes correctly. - setCordappPackages(Arrays.asList("com.domain.cordapp")) - network = new MockNetwork() - } - - @After - void teardown() { - // This must be called at the end otherwise the global state set by setCordappPackages may leak into future - // tests in the same test runner environment. - unsetCordappPackages() + network = new MockNetwork(new MockNetworkParameters().setCordappPackages(Arrays.asList("com.domain.cordapp"))) } ... // Your tests go here diff --git a/docs/source/network-map.rst b/docs/source/network-map.rst new file mode 100644 index 0000000000..d8ee82a2cf --- /dev/null +++ b/docs/source/network-map.rst @@ -0,0 +1,38 @@ +Network Map +=========== + +Protocol Design +--------------- +The node info publishing protocol: + +* Create a ``NodeInfo`` object, and sign it to create a ``SignedData`` object. TODO: We will need list of signatures in ``SignedData`` to support multiple node identities in the future. + +* Serialise the signed data and POST the data to the network map server. + +* The network map server validates the signature and acknowledges the registration with a HTTP 200 response, it will return HTTP 400 "Bad Request" if the data failed validation or if the public key wasn't registered with the network. + +* The network map server will sign and distribute the new network map periodically. + +Node side network map update protocol: + +* The Corda node will query the network map service periodically according to the ``Expires`` attribute in the HTTP header. + +* The network map service returns a signed ``NetworkMap`` object, containing list of node info hashes and the network parameters hashes. + +* The node updates its local copy of ``NodeInfos`` if it is different from the newly downloaded ``NetworkMap``. + +Network Map service REST API: + ++----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Request method | Path | Description | ++================+===================================+========================================================================================================================================================+ +| POST | /api/network-map/publish | Publish new ``NodeInfo`` to the network map service, the legal identity in ``NodeInfo`` must match with the identity registered with the doorman. | ++----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ +| GET | /api/network-map | Retrieve ``NetworkMap`` from the server, the ``NetworkMap`` object contains list of node info hashes and NetworkParameters hash. | ++----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ +| GET | /api/network-map/node-info/{hash} | Retrieve ``NodeInfo`` object with the same hash. | ++----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ +| GET | /api/network-map/parameters/{hash}| Retrieve ``NetworkParameters`` object with the same hash. | ++----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ + +TODO: Access control of the network map will be added in the future. \ No newline at end of file diff --git a/docs/source/upgrade-notes.rst b/docs/source/upgrade-notes.rst index 0040c48e05..1dcb0d72b4 100644 --- a/docs/source/upgrade-notes.rst +++ b/docs/source/upgrade-notes.rst @@ -25,6 +25,19 @@ versions you are currently using are still in force. We also strongly recommend cross referencing with the :doc:`changelog` to confirm changes. +UNRELEASED +---------- + +Testing +^^^^^^^ + +* The registration mechanism for CorDapps in ``MockNetwork`` unit tests has changed. + + It is now done via the ``cordappPackages`` constructor parameter of MockNetwork. + This takes a list of `String` values which should be the + package names of the CorDapps containing the contract verification code you wish to load. + The ``unsetCordappPackages`` method is now redundant and has been removed. + :ref:`Milestone 14 ` ------------ diff --git a/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionPostgreSQLImpl.kt b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionPostgreSQLImpl.kt new file mode 100644 index 0000000000..d8154336e3 --- /dev/null +++ b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionPostgreSQLImpl.kt @@ -0,0 +1,83 @@ +package net.corda.finance.contracts.asset.cash.selection + +import net.corda.core.contracts.Amount +import net.corda.core.identity.AbstractParty +import net.corda.core.identity.Party +import net.corda.core.utilities.OpaqueBytes +import net.corda.core.utilities.debug +import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.toBase58String +import java.sql.Connection +import java.sql.DatabaseMetaData +import java.sql.ResultSet +import java.util.* + +class CashSelectionPostgreSQLImpl : AbstractCashSelection() { + + companion object { + val JDBC_DRIVER_NAME = "PostgreSQL JDBC Driver" + val log = loggerFor() + } + + override fun isCompatible(metadata: DatabaseMetaData): Boolean { + return metadata.driverName == JDBC_DRIVER_NAME + } + + override fun toString() = "${this::class.java} for $JDBC_DRIVER_NAME" + + // This is using PostgreSQL window functions for selecting a minimum set of rows that match a request amount of coins: + // 1) This may also be possible with user-defined functions (e.g. using PL/pgSQL) + // 2) The window function accumulated column (`total`) does not include the current row (starts from 0) and cannot + // appear in the WHERE clause, hence restricting row selection and adjusting the returned total in the outer query. + // 3) Currently (version 9.6), FOR UPDATE cannot be specified with window functions + override fun executeQuery(connection: Connection, amount: Amount, lockId: UUID, notary: Party?, + onlyFromIssuerParties: Set, withIssuerRefs: Set) : ResultSet { + val selectJoin = """SELECT nested.transaction_id, nested.output_index, nested.contract_state, nested.pennies, + nested.total+nested.pennies as total_pennies, nested.lock_id + FROM + (SELECT vs.transaction_id, vs.output_index, vs.contract_state, ccs.pennies, + coalesce((SUM(ccs.pennies) OVER (PARTITION BY 1 ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING)), 0) + AS total, vs.lock_id + FROM vault_states AS vs, contract_cash_states AS ccs + WHERE vs.transaction_id = ccs.transaction_id AND vs.output_index = ccs.output_index + AND vs.state_status = 0 + AND ccs.ccy_code = ? + AND (vs.lock_id = ? OR vs.lock_id is null) + """ + + (if (notary != null) + " AND vs.notary_name = ?" else "") + + (if (onlyFromIssuerParties.isNotEmpty()) + " AND ccs.issuer_key = ANY (?)" else "") + + (if (withIssuerRefs.isNotEmpty()) + " AND ccs.issuer_ref = ANY (?)" else "") + + """) + nested WHERE nested.total < ? + """ + + val statement = connection.prepareStatement(selectJoin) + statement.setString(1, amount.token.toString()) + statement.setString(2, lockId.toString()) + var paramOffset = 0 + if (notary != null) { + statement.setString(3, notary.name.toString()) + paramOffset += 1 + } + if (onlyFromIssuerParties.isNotEmpty()) { + val issuerKeys = connection.createArrayOf("VARCHAR", onlyFromIssuerParties.map + { it.owningKey.toBase58String() }.toTypedArray()) + statement.setArray(3 + paramOffset, issuerKeys) + paramOffset += 1 + } + if (withIssuerRefs.isNotEmpty()) { + val issuerRefs = connection.createArrayOf("BYTEA", withIssuerRefs.map + { it.bytes }.toTypedArray()) + statement.setArray(3 + paramOffset, issuerRefs) + paramOffset += 1 + } + statement.setLong(3 + paramOffset, amount.quantity) + log.debug { statement.toString() } + + return statement.executeQuery() + } + +} diff --git a/finance/src/main/resources/META-INF/services/net.corda.finance.contracts.asset.cash.selection.AbstractCashSelection b/finance/src/main/resources/META-INF/services/net.corda.finance.contracts.asset.cash.selection.AbstractCashSelection index 9ac2461b3c..a4feda21cf 100644 --- a/finance/src/main/resources/META-INF/services/net.corda.finance.contracts.asset.cash.selection.AbstractCashSelection +++ b/finance/src/main/resources/META-INF/services/net.corda.finance.contracts.asset.cash.selection.AbstractCashSelection @@ -1,2 +1,3 @@ net.corda.finance.contracts.asset.cash.selection.CashSelectionH2Impl net.corda.finance.contracts.asset.cash.selection.CashSelectionMySQLImpl +net.corda.finance.contracts.asset.cash.selection.CashSelectionPostgreSQLImpl \ No newline at end of file diff --git a/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashSelectionH2Test.kt b/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashSelectionH2Test.kt index ef85dead0a..2a69ac0631 100644 --- a/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashSelectionH2Test.kt +++ b/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashSelectionH2Test.kt @@ -1,11 +1,14 @@ package net.corda.finance.contracts.asset +import net.corda.core.internal.packageName import net.corda.core.utilities.getOrThrow import net.corda.finance.DOLLARS import net.corda.finance.flows.CashException import net.corda.finance.flows.CashPaymentFlow +import net.corda.finance.schemas.CashSchemaV1 import net.corda.testing.chooseIdentity import net.corda.testing.node.MockNetwork +import net.corda.testing.node.MockNodeParameters import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Test @@ -14,15 +17,13 @@ class CashSelectionH2Test { @Test fun `check does not hold connection over retries`() { - val mockNet = MockNetwork(threadPerNode = true) + val mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.finance.contracts.asset", CashSchemaV1::class.packageName)) try { val notaryNode = mockNet.createNotaryNode() - val bankA = mockNet.createNode(configOverrides = { existingConfig -> + val bankA = mockNet.createNode(MockNodeParameters(configOverrides = { existingConfig -> // Tweak connections to be minimal to make this easier (1 results in a hung node during start up, so use 2 connections). existingConfig.dataSourceProperties.setProperty("maximumPoolSize", "2") - existingConfig - }) - + })) mockNet.startNodes() // Start more cash spends than we have connections. If spend leaks a connection on retry, we will run out of connections. diff --git a/gradle-plugins/api-scanner/src/main/java/net/corda/plugins/ScanApi.java b/gradle-plugins/api-scanner/src/main/java/net/corda/plugins/ScanApi.java index f0220add42..eb2b7e5599 100644 --- a/gradle-plugins/api-scanner/src/main/java/net/corda/plugins/ScanApi.java +++ b/gradle-plugins/api-scanner/src/main/java/net/corda/plugins/ScanApi.java @@ -16,6 +16,7 @@ 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; @@ -25,7 +26,7 @@ import java.net.URLClassLoader; import java.util.*; import java.util.stream.StreamSupport; -import static java.util.Collections.unmodifiableSet; +import static java.util.Collections.*; import static java.util.stream.Collectors.*; @SuppressWarnings("unused") @@ -228,9 +229,19 @@ public class ScanApi extends DefaultTask { 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 annotationNames = toNames(readClassAnnotationsFor(classInfo)); + if (!annotationNames.isEmpty()) { + writer.append(asAnnotations(annotationNames)); + } writer.append(Modifier.toString(modifiers & CLASS_MASK)); writer.append(" class ").print(classInfo); Set superclasses = classInfo.getDirectSuperclasses(); @@ -242,6 +253,13 @@ public class ScanApi extends DefaultTask { writer.append(" implements ").print(stringOf(interfaces)); } } else { + /* + * Interface declaration. + */ + List annotationNames = toNames(readInterfaceAnnotationsFor(classInfo)); + if (!annotationNames.isEmpty()) { + writer.append(asAnnotations(annotationNames)); + } writer.append(Modifier.toString(modifiers & INTERFACE_MASK)); writer.append(" interface ").print(classInfo); Set superinterfaces = classInfo.getDirectSuperinterfaces(); @@ -253,7 +271,7 @@ public class ScanApi extends DefaultTask { } private void writeMethods(PrintWriter writer, List methods) { - Collections.sort(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 @@ -264,7 +282,7 @@ public class ScanApi extends DefaultTask { } private void writeFields(PrintWriter output, List fields) { - Collections.sort(fields); + sort(fields); for (FieldInfo field : fields) { if (isVisible(field.getAccessFlags()) && isValid(field.getAccessFlags(), FIELD_MASK)) { output.append(" ").println(field); @@ -286,6 +304,36 @@ public class ScanApi extends DefaultTask { return 0; } + private List toNames(Collection classes) { + return classes.stream() + .map(ClassInfo::toString) + .filter(ScanApi::isApplicationClass) + .collect(toList()); + } + + private Set readClassAnnotationsFor(ClassInfo classInfo) { + Set annotations = new HashSet<>(classInfo.getAnnotations()); + annotations.addAll(selectInheritedAnnotations(classInfo.getSuperclasses())); + annotations.addAll(selectInheritedAnnotations(classInfo.getImplementedInterfaces())); + return annotations; + } + + private Set readInterfaceAnnotationsFor(ClassInfo classInfo) { + Set annotations = new HashSet<>(classInfo.getAnnotations()); + annotations.addAll(selectInheritedAnnotations(classInfo.getSuperinterfaces())); + return annotations; + } + + /** + * Returns those annotations which have themselves been annotated as "Inherited". + */ + private List selectInheritedAnnotations(Collection 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(), @@ -319,6 +367,14 @@ public class ScanApi extends DefaultTask { return items.stream().map(ClassInfo::toString).collect(joining(", ")); } + private static String asAnnotations(Collection 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(); } diff --git a/gradle-plugins/cordapp/src/main/kotlin/net/corda/plugins/CordappPlugin.kt b/gradle-plugins/cordapp/src/main/kotlin/net/corda/plugins/CordappPlugin.kt index ce65e18e5f..e307a7b162 100644 --- a/gradle-plugins/cordapp/src/main/kotlin/net/corda/plugins/CordappPlugin.kt +++ b/gradle-plugins/cordapp/src/main/kotlin/net/corda/plugins/CordappPlugin.kt @@ -29,7 +29,7 @@ class CordappPlugin : Plugin { 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.single { it.name == "jar" } as Jar + val jarTask = project.tasks.getByName("jar") as Jar task.doLast { jarTask.from(getDirectNonCordaDependencies(project).map { project.zipTree(it)}).apply { exclude("META-INF/*.SF") @@ -71,6 +71,4 @@ class CordappPlugin : Plugin { } return filteredDeps.map { runtimeConfiguration.files(it) }.flatten().toSet() } - - private fun Project.configuration(name: String): Configuration = configurations.single { it.name == name } } diff --git a/gradle-plugins/cordapp/src/main/kotlin/net/corda/plugins/Utils.kt b/gradle-plugins/cordapp/src/main/kotlin/net/corda/plugins/Utils.kt index 33e552ca7d..d7200ba9f8 100644 --- a/gradle-plugins/cordapp/src/main/kotlin/net/corda/plugins/Utils.kt +++ b/gradle-plugins/cordapp/src/main/kotlin/net/corda/plugins/Utils.kt @@ -1,7 +1,17 @@ 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 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 { @@ -14,4 +24,5 @@ class Utils { } } } + } \ No newline at end of file diff --git a/gradle-plugins/cordformation/build.gradle b/gradle-plugins/cordformation/build.gradle index 828647d434..94eb8f3b7b 100644 --- a/gradle-plugins/cordformation/build.gradle +++ b/gradle-plugins/cordformation/build.gradle @@ -8,7 +8,6 @@ buildscript { } } -apply plugin: 'groovy' apply plugin: 'kotlin' apply plugin: 'net.corda.plugins.publish-utils' @@ -34,8 +33,8 @@ sourceSets { dependencies { compile gradleApi() - compile localGroovy() compile project(":cordapp") + compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" noderunner "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" diff --git a/gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Cordform.groovy b/gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Cordform.groovy deleted file mode 100644 index 7baf20a45a..0000000000 --- a/gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Cordform.groovy +++ /dev/null @@ -1,153 +0,0 @@ -package net.corda.plugins - -import static org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME -import net.corda.cordform.CordformContext -import net.corda.cordform.CordformDefinition -import org.apache.tools.ant.filters.FixCrLfFilter -import org.gradle.api.DefaultTask -import org.gradle.api.plugins.JavaPluginConvention -import org.gradle.api.tasks.TaskAction -import java.nio.file.Path -import java.nio.file.Paths - -/** - * Creates nodes based on the configuration of this task in the gradle configuration DSL. - * - * See documentation for examples. - */ -class Cordform extends DefaultTask { - /** - * Optionally the name of a CordformDefinition subclass to which all configuration will be delegated. - */ - String definitionClass - protected def directory = Paths.get("build", "nodes") - private def nodes = new ArrayList() - - /** - * Set the directory to install nodes into. - * - * @param directory The directory the nodes will be installed into. - * @return - */ - void directory(String directory) { - this.directory = Paths.get(directory) - } - - /** - * Add a node configuration. - * - * @param configureClosure A node configuration that will be deployed. - */ - void node(Closure configureClosure) { - nodes << (Node) project.configure(new Node(project), configureClosure) - } - - /** - * Returns a node by name. - * - * @param name The name of the node as specified in the node configuration DSL. - * @return A node instance. - */ - private Node getNodeByName(String name) { - for (Node node : nodes) { - if (node.name == name) { - return node - } - } - - return null - } - - /** - * Installs the run script into the nodes directory. - */ - private void installRunScript() { - project.copy { - from Cordformation.getPluginFile(project, "net/corda/plugins/runnodes.jar") - fileMode 0755 - into "${directory}/" - } - - project.copy { - from Cordformation.getPluginFile(project, "net/corda/plugins/runnodes") - // Replaces end of line with lf to avoid issues with the bash interpreter and Windows style line endings. - filter(FixCrLfFilter.class, eol: FixCrLfFilter.CrLf.newInstance("lf")) - fileMode 0755 - into "${directory}/" - } - - project.copy { - from Cordformation.getPluginFile(project, "net/corda/plugins/runnodes.bat") - into "${directory}/" - } - } - - /** - * The definitionClass needn't be compiled until just before our build method, so we load it manually via sourceSets.main.runtimeClasspath. - */ - private CordformDefinition loadCordformDefinition() { - def plugin = project.convention.getPlugin(JavaPluginConvention.class) - def classpath = plugin.sourceSets.getByName(MAIN_SOURCE_SET_NAME).runtimeClasspath - URL[] urls = classpath.files.collect { it.toURI().toURL() } - (CordformDefinition) new URLClassLoader(urls, CordformDefinition.classLoader).loadClass(definitionClass).newInstance() - } - - /** - * This task action will create and install the nodes based on the node configurations added. - */ - @TaskAction - void build() { - initializeConfiguration() - installRunScript() - nodes.each { - it.build() - } - generateNodeInfos() - } - - private initializeConfiguration() { - if (null != definitionClass) { - def cd = loadCordformDefinition() - cd.nodeConfigurers.each { nc -> - node { Node it -> - nc.accept it - it.rootDir directory - } - } - cd.setup new CordformContext() { - Path baseDirectory(String nodeName) { - project.projectDir.toPath().resolve(getNodeByName(nodeName).nodeDir.toPath()) - } - } - } else { - nodes.each { - it.rootDir directory - } - } - } - - Path fullNodePath(Node node) { - return project.projectDir.toPath().resolve(node.nodeDir.toPath()) - } - - private generateNodeInfos() { - nodes.each { Node node -> - def process = new ProcessBuilder("java", "-jar", Node.NODEJAR_NAME, "--just-generate-node-info") - .directory(fullNodePath(node).toFile()) - .redirectErrorStream(true) - .start() - .waitFor() - } - for (source in nodes) { - for (destination in nodes) { - if (source.nodeDir != destination.nodeDir) { - project.copy { - from fullNodePath(source).toString() - include 'nodeInfo-*' - into fullNodePath(destination).resolve(Node.NODE_INFO_DIRECTORY).toString() - } - } - } - } - } -} diff --git a/gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Cordformation.groovy b/gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Cordformation.groovy deleted file mode 100644 index eeb4443801..0000000000 --- a/gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Cordformation.groovy +++ /dev/null @@ -1,28 +0,0 @@ -package net.corda.plugins - -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.api.artifacts.Configuration - -/** - * The Cordformation plugin deploys nodes to a directory in a state ready to be used by a developer for experimentation, - * testing, and debugging. It will prepopulate several fields in the configuration and create a simple node runner. - */ -class Cordformation implements Plugin { - /** - * Gets a resource file from this plugin's JAR file. - * - * @param project The project environment this plugin executes in. - * @param filePathInJar The file in the JAR, relative to root, you wish to access. - * @return A file handle to the file in the JAR. - */ - protected static File getPluginFile(Project project, String filePathInJar) { - return project.rootProject.resources.text.fromArchiveEntry(project.rootProject.buildscript.configurations.classpath.find { - it.name.contains('cordformation') - }, filePathInJar).asFile() - } - - void apply(Project project) { - Utils.createCompileConfiguration("cordapp", project) - } -} diff --git a/gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Node.groovy b/gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Node.groovy deleted file mode 100644 index 8f6dcea295..0000000000 --- a/gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Node.groovy +++ /dev/null @@ -1,268 +0,0 @@ -package net.corda.plugins - -import com.typesafe.config.* -import net.corda.cordform.CordformNode -import org.bouncycastle.asn1.x500.X500Name -import org.bouncycastle.asn1.x500.style.BCStyle -import org.gradle.api.Project -import java.nio.charset.StandardCharsets -import java.nio.file.Files -import java.nio.file.Path - -/** - * Represents a node that will be installed. - */ -class Node extends CordformNode { - static final String NODEJAR_NAME = 'corda.jar' - static final String WEBJAR_NAME = 'corda-webserver.jar' - - /** - * Set the list of CorDapps to install to the cordapps directory. Each cordapp is a fully qualified Maven - * dependency name, eg: com.example:product-name:0.1 - * - * @note Your app will be installed by default and does not need to be included here. - */ - protected List cordapps = [] - - protected File nodeDir - private Project project - - /** - * Sets whether this node will use HTTPS communication. - * - * @param isHttps True if this node uses HTTPS communication. - */ - void https(Boolean isHttps) { - config = config.withValue("useHTTPS", ConfigValueFactory.fromAnyRef(isHttps)) - } - - /** - * Sets the H2 port for this node - */ - void h2Port(Integer h2Port) { - config = config.withValue("h2port", ConfigValueFactory.fromAnyRef(h2Port)) - } - - void useTestClock(Boolean useTestClock) { - config = config.withValue("useTestClock", ConfigValueFactory.fromAnyRef(useTestClock)) - } - - /** - * Set the HTTP web server port for this node. - * - * @param webPort The web port number for this node. - */ - void webPort(Integer webPort) { - config = config.withValue("webAddress", - ConfigValueFactory.fromAnyRef("$DEFAULT_HOST:$webPort".toString())) - } - - /** - * Set the network map address for this node. - * - * @warning This should not be directly set unless you know what you are doing. Use the networkMapName in the - * Cordform task instead. - * @param networkMapAddress Network map node address. - * @param networkMapLegalName Network map node legal name. - */ - void networkMapAddress(String networkMapAddress, String networkMapLegalName) { - def networkMapService = new HashMap() - networkMapService.put("address", networkMapAddress) - networkMapService.put("legalName", networkMapLegalName) - config = config.withValue("networkMapService", ConfigValueFactory.fromMap(networkMapService)) - } - - /** - * Set the SSHD port for this node. - * - * @param sshdPort The SSHD port. - */ - void sshdPort(Integer sshdPort) { - config = config.withValue("sshdAddress", - ConfigValueFactory.fromAnyRef("$DEFAULT_HOST:$sshdPort".toString())) - } - - Node(Project project) { - this.project = project - } - - protected void rootDir(Path rootDir) { - def dirName - try { - X500Name x500Name = new X500Name(name) - dirName = x500Name.getRDNs(BCStyle.O).getAt(0).getFirst().getValue().toString() - } catch(IllegalArgumentException ignore) { - // Can't parse as an X500 name, use the full string - dirName = name - } - nodeDir = new File(rootDir.toFile(), dirName.replaceAll("\\s","")) - } - - protected void build() { - configureProperties() - installCordaJar() - if (config.hasPath("webAddress")) { - installWebserverJar() - } - installBuiltCordapp() - installCordapps() - installConfig() - appendOptionalConfig() - } - - /** - * Get the artemis address for this node. - * - * @return This node's P2P address. - */ - String getP2PAddress() { - return config.getString("p2pAddress") - } - - private void configureProperties() { - config = config.withValue("rpcUsers", ConfigValueFactory.fromIterable(rpcUsers)) - if (notary) { - config = config.withValue("notary", ConfigValueFactory.fromMap(notary)) - } - if (extraConfig) { - config = config.withFallback(ConfigFactory.parseMap(extraConfig)) - } - } - - /** - * Installs the corda fat JAR to the node directory. - */ - private void installCordaJar() { - def cordaJar = verifyAndGetCordaJar() - project.copy { - from cordaJar - into nodeDir - rename cordaJar.name, NODEJAR_NAME - fileMode 0755 - } - } - - /** - * Installs the corda webserver JAR to the node directory - */ - private void installWebserverJar() { - def webJar = verifyAndGetWebserverJar() - project.copy { - from webJar - into nodeDir - rename webJar.name, WEBJAR_NAME - } - } - - /** - * Installs this project's cordapp to this directory. - */ - private void installBuiltCordapp() { - def cordappsDir = new File(nodeDir, "cordapps") - project.copy { - from project.jar - into cordappsDir - } - } - - /** - * Installs other cordapps to this node's cordapps directory. - */ - private void installCordapps() { - def cordappsDir = new File(nodeDir, "cordapps") - def cordapps = getCordappList() - project.copy { - from cordapps - into cordappsDir - } - } - - /** - * Installs the configuration file to this node's directory and detokenises it. - */ - private void installConfig() { - def configFileText = config.root().render(new ConfigRenderOptions(false, false, true, false)).split("\n").toList() - - // Need to write a temporary file first to use the project.copy, which resolves directories correctly. - def tmpDir = new File(project.buildDir, "tmp") - def tmpConfFile = new File(tmpDir, 'node.conf') - Files.write(tmpConfFile.toPath(), configFileText, StandardCharsets.UTF_8) - - project.copy { - from tmpConfFile - into nodeDir - } - } - - /** - * Appends installed config file with properties from an optional file. - */ - private void appendOptionalConfig() { - final configFileProperty = "configFile" - File optionalConfig - if (project.findProperty(configFileProperty)) { //provided by -PconfigFile command line property when running Gradle task - optionalConfig = new File(project.findProperty(configFileProperty)) - } else if (config.hasPath(configFileProperty)) { - optionalConfig = new File(config.getString(configFileProperty)) - } - if (optionalConfig) { - if (!optionalConfig.exists()) { - println "$configFileProperty '$optionalConfig' not found" - } else { - def confFile = new File(project.buildDir.getPath() + "/../" + nodeDir, 'node.conf') - optionalConfig.withInputStream { - input -> confFile << input - } - } - } - } - - /** - * Find the corda JAR amongst the dependencies. - * - * @return A file representing the Corda JAR. - */ - private File verifyAndGetCordaJar() { - def maybeCordaJAR = project.configurations.runtime.filter { - it.toString().contains("corda-${project.corda_release_version}.jar") || it.toString().contains("corda-enterprise-${project.corda_release_version}.jar") - } - if (maybeCordaJAR.size() == 0) { - throw new RuntimeException("No Corda Capsule JAR found. Have you deployed the Corda project to Maven? Looked for \"corda-${project.corda_release_version}.jar\"") - } else { - def cordaJar = maybeCordaJAR.getSingleFile() - assert(cordaJar.isFile()) - return cordaJar - } - } - - /** - * Find the corda JAR amongst the dependencies - * - * @return A file representing the Corda webserver JAR - */ - private File verifyAndGetWebserverJar() { - def maybeJar = project.configurations.runtime.filter { - it.toString().contains("corda-webserver-${project.corda_release_version}.jar") - } - if (maybeJar.size() == 0) { - throw new RuntimeException("No Corda Webserver JAR found. Have you deployed the Corda project to Maven? Looked for \"corda-webserver-${project.corda_release_version}.jar\"") - } else { - def jar = maybeJar.getSingleFile() - assert(jar.isFile()) - return jar - } - } - - /** - * Gets a list of cordapps based on what dependent cordapps were specified. - * - * @return List of this node's cordapps. - */ - private Collection getCordappList() { - // Cordapps can sometimes contain a GString instance which fails the equality test with the Java string - List cordapps = this.cordapps.collect { it.toString() } - return project.configurations.cordapp.files { - cordapps.contains(it.group + ":" + it.name + ":" + it.version) - } - } -} diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt new file mode 100644 index 0000000000..5193703c59 --- /dev/null +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt @@ -0,0 +1,198 @@ +package net.corda.plugins + +import groovy.lang.Closure +import net.corda.cordform.CordformDefinition +import net.corda.cordform.CordformNode +import org.apache.tools.ant.filters.FixCrLfFilter +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.plugins.JavaPluginConvention +import org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME +import org.gradle.api.tasks.TaskAction +import java.io.File +import java.net.URLClassLoader +import java.nio.file.Path +import java.nio.file.Paths +import java.util.concurrent.TimeUnit + +/** + * Creates nodes based on the configuration of this task in the gradle configuration DSL. + * + * See documentation for examples. + */ +@Suppress("unused") +open class Cordform : DefaultTask() { + /** + * Optionally the name of a CordformDefinition subclass to which all configuration will be delegated. + */ + @Suppress("MemberVisibilityCanPrivate") + var definitionClass: String? = null + private var directory = Paths.get("build", "nodes") + private val nodes = mutableListOf() + + /** + * Set the directory to install nodes into. + * + * @param directory The directory the nodes will be installed into. + */ + fun directory(directory: String) { + this.directory = Paths.get(directory) + } + + /** + * Add a node configuration. + * + * @param configureClosure A node configuration that will be deployed. + */ + @Suppress("MemberVisibilityCanPrivate") + fun node(configureClosure: Closure) { + nodes += project.configure(Node(project), configureClosure) as Node + } + + /** + * Add a node configuration + * + * @param configureFunc A node configuration that will be deployed + */ + @Suppress("MemberVisibilityCanPrivate") + fun node(configureFunc: Node.() -> Any?): Node { + val node = Node(project).apply { configureFunc() } + nodes += node + return node + } + + /** + * Returns a node by name. + * + * @param name The name of the node as specified in the node configuration DSL. + * @return A node instance. + */ + private fun getNodeByName(name: String): Node? = nodes.firstOrNull { it.name == name } + + /** + * Installs the run script into the nodes directory. + */ + private fun installRunScript() { + project.copy { + it.apply { + from(Cordformation.getPluginFile(project, "net/corda/plugins/runnodes.jar")) + fileMode = Cordformation.executableFileMode + into("$directory/") + } + } + + project.copy { + it.apply { + from(Cordformation.getPluginFile(project, "net/corda/plugins/runnodes")) + // Replaces end of line with lf to avoid issues with the bash interpreter and Windows style line endings. + filter(mapOf("eol" to FixCrLfFilter.CrLf.newInstance("lf")), FixCrLfFilter::class.java) + fileMode = Cordformation.executableFileMode + into("$directory/") + } + } + + project.copy { + it.apply { + from(Cordformation.getPluginFile(project, "net/corda/plugins/runnodes.bat")) + into("$directory/") + } + } + } + + /** + * The definitionClass needn't be compiled until just before our build method, so we load it manually via sourceSets.main.runtimeClasspath. + */ + private fun loadCordformDefinition(): CordformDefinition { + val plugin = project.convention.getPlugin(JavaPluginConvention::class.java) + val classpath = plugin.sourceSets.getByName(MAIN_SOURCE_SET_NAME).runtimeClasspath + val urls = classpath.files.map { it.toURI().toURL() }.toTypedArray() + return URLClassLoader(urls, CordformDefinition::class.java.classLoader) + .loadClass(definitionClass) + .asSubclass(CordformDefinition::class.java) + .newInstance() + } + + /** + * This task action will create and install the nodes based on the node configurations added. + */ + @Suppress("unused") + @TaskAction + fun build() { + project.logger.info("Running Cordform task") + initializeConfiguration() + installRunScript() + nodes.forEach(Node::build) + generateAndInstallNodeInfos() + } + + private fun initializeConfiguration() { + if (definitionClass != null) { + val cd = loadCordformDefinition() + cd.nodeConfigurers.forEach { + val node = node { } + it.accept(node) + node.rootDir(directory) + } + cd.setup { nodeName -> project.projectDir.toPath().resolve(getNodeByName(nodeName)!!.nodeDir.toPath()) } + } else { + nodes.forEach { + it.rootDir(directory) + } + } + } + + private fun fullNodePath(node: Node): Path = project.projectDir.toPath().resolve(node.nodeDir.toPath()) + + private fun generateAndInstallNodeInfos() { + generateNodeInfos() + installNodeInfos() + } + + private fun generateNodeInfos() { + project.logger.info("Generating node infos") + val generateTimeoutSeconds = 60L + val processes = nodes.map { node -> + project.logger.info("Generating node info for ${fullNodePath(node)}") + val logDir = File(fullNodePath(node).toFile(), "logs") + logDir.mkdirs() // Directory may not exist at this point + Pair(node, ProcessBuilder("java", "-jar", Node.nodeJarName, "--just-generate-node-info") + .directory(fullNodePath(node).toFile()) + .redirectErrorStream(true) + // InheritIO causes hangs on windows due the gradle buffer also not being flushed. + // Must redirect to output or logger (node log is still written, this is just startup banner) + .redirectOutput(File(logDir, "generate-info-log.txt")) + .start()) + } + try { + processes.parallelStream().forEach { (node, process) -> + if (!process.waitFor(generateTimeoutSeconds, TimeUnit.SECONDS)) { + throw GradleException("Node took longer $generateTimeoutSeconds seconds than too to generate node info - see node log at ${fullNodePath(node)}/logs") + } else if (process.exitValue() != 0) { + throw GradleException("Node exited with ${process.exitValue()} when generating node infos - see node log at ${fullNodePath(node)}/logs") + } + } + } finally { + // This will be a no-op on success - abort remaining on failure + processes.forEach { + it.second.destroyForcibly() + } + } + } + + private fun installNodeInfos() { + project.logger.info("Installing node infos") + for (source in nodes) { + for (destination in nodes) { + if (source.nodeDir != destination.nodeDir) { + project.copy { + it.apply { + from(fullNodePath(source).toString()) + include("nodeInfo-*") + into(fullNodePath(destination).resolve(CordformNode.NODE_INFO_DIRECTORY).toString()) + } + } + } + } + } + } +} diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordformation.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordformation.kt new file mode 100644 index 0000000000..b0e09ad2da --- /dev/null +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordformation.kt @@ -0,0 +1,33 @@ +package net.corda.plugins + +import org.gradle.api.Plugin +import org.gradle.api.Project +import java.io.File + +/** + * The Cordformation plugin deploys nodes to a directory in a state ready to be used by a developer for experimentation, + * testing, and debugging. It will prepopulate several fields in the configuration and create a simple node runner. + */ +class Cordformation : Plugin { + internal companion object { + /** + * Gets a resource file from this plugin's JAR file. + * + * @param project The project environment this plugin executes in. + * @param filePathInJar The file in the JAR, relative to root, you wish to access. + * @return A file handle to the file in the JAR. + */ + fun getPluginFile(project: Project, filePathInJar: String): File { + val archive: File? = project.rootProject.buildscript.configurations.single { it.name == "classpath" }.find { + it.name.contains("cordformation") + } + return project.rootProject.resources.text.fromArchiveEntry(archive, filePathInJar).asFile() + } + + val executableFileMode = "0755".toInt(8) + } + + override fun apply(project: Project) { + Utils.createCompileConfiguration("cordapp", project) + } +} diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt new file mode 100644 index 0000000000..b0660a9245 --- /dev/null +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt @@ -0,0 +1,290 @@ +package net.corda.plugins + +import com.typesafe.config.* +import net.corda.cordform.CordformNode +import org.bouncycastle.asn1.x500.X500Name +import org.bouncycastle.asn1.x500.RDN +import org.bouncycastle.asn1.x500.style.BCStyle +import org.gradle.api.Project +import java.io.File +import java.nio.charset.StandardCharsets +import java.nio.file.Files +import java.nio.file.Path + +/** + * Represents a node that will be installed. + */ +class Node(private val project: Project) : CordformNode() { + companion object { + @JvmStatic + val nodeJarName = "corda.jar" + @JvmStatic + val webJarName = "corda-webserver.jar" + private val configFileProperty = "configFile" + } + + /** + * Set the list of CorDapps to install to the plugins directory. Each cordapp is a fully qualified Maven + * dependency name, eg: com.example:product-name:0.1 + * + * @note Your app will be installed by default and does not need to be included here. + * @note Type is any due to gradle's use of "GStrings" - each value will have "toString" called on it + */ + var cordapps = mutableListOf() + + private val releaseVersion = project.rootProject.ext("corda_release_version") + internal lateinit var nodeDir: File + + /** + * Sets whether this node will use HTTPS communication. + * + * @param isHttps True if this node uses HTTPS communication. + */ + fun https(isHttps: Boolean) { + config = config.withValue("useHTTPS", ConfigValueFactory.fromAnyRef(isHttps)) + } + + /** + * Sets the H2 port for this node + */ + fun h2Port(h2Port: Int) { + config = config.withValue("h2port", ConfigValueFactory.fromAnyRef(h2Port)) + } + + fun useTestClock(useTestClock: Boolean) { + config = config.withValue("useTestClock", ConfigValueFactory.fromAnyRef(useTestClock)) + } + + /** + * Set the HTTP web server port for this node. + * + * @param webPort The web port number for this node. + */ + fun webPort(webPort: Int) { + config = config.withValue("webAddress", + ConfigValueFactory.fromAnyRef("$DEFAULT_HOST:$webPort")) + } + + /** + * Set the network map address for this node. + * + * @warning This should not be directly set unless you know what you are doing. Use the networkMapName in the + * Cordform task instead. + * @param networkMapAddress Network map node address. + * @param networkMapLegalName Network map node legal name. + */ + fun networkMapAddress(networkMapAddress: String, networkMapLegalName: String) { + val networkMapService = mutableMapOf() + networkMapService.put("address", networkMapAddress) + networkMapService.put("legalName", networkMapLegalName) + config = config.withValue("networkMapService", ConfigValueFactory.fromMap(networkMapService)) + } + + /** + * Set the SSHD port for this node. + * + * @param sshdPort The SSHD port. + */ + fun sshdPort(sshdPort: Int) { + config = config.withValue("sshdAddress", + ConfigValueFactory.fromAnyRef("$DEFAULT_HOST:$sshdPort")) + } + + internal fun build() { + configureProperties() + installCordaJar() + if (config.hasPath("webAddress")) { + installWebserverJar() + } + installBuiltCordapp() + installCordapps() + installConfig() + appendOptionalConfig() + } + + /** + * Get the artemis address for this node. + * + * @return This node's P2P address. + */ + fun getP2PAddress(): String { + return config.getString("p2pAddress") + } + + internal fun rootDir(rootDir: Path) { + if(name == null) { + project.logger.error("Node has a null name - cannot create node") + throw IllegalStateException("Node has a null name - cannot create node") + } + + val dirName = try { + val o = X500Name(name).getRDNs(BCStyle.O) + if (o.size > 0) { + o.first().first.value.toString() + } else { + name + } + } catch(_ : IllegalArgumentException) { + // Can't parse as an X500 name, use the full string + name + } + nodeDir = File(rootDir.toFile(), dirName.replace("\\s", "")) + } + + private fun configureProperties() { + config = config.withValue("rpcUsers", ConfigValueFactory.fromIterable(rpcUsers)) + if (notary != null) { + config = config.withValue("notary", ConfigValueFactory.fromMap(notary)) + } + if (extraConfig != null) { + config = config.withFallback(ConfigFactory.parseMap(extraConfig)) + } + } + + /** + * Installs the corda fat JAR to the node directory. + */ + private fun installCordaJar() { + val cordaJar = verifyAndGetCordaJar() + project.copy { + it.apply { + from(cordaJar) + into(nodeDir) + rename(cordaJar.name, nodeJarName) + fileMode = Cordformation.executableFileMode + } + } + } + + /** + * Installs the corda webserver JAR to the node directory + */ + private fun installWebserverJar() { + val webJar = verifyAndGetWebserverJar() + project.copy { + it.apply { + from(webJar) + into(nodeDir) + rename(webJar.name, webJarName) + } + } + } + + /** + * Installs this project's cordapp to this directory. + */ + private fun installBuiltCordapp() { + val cordappsDir = File(nodeDir, "cordapps") + project.copy { + it.apply { + from(project.tasks.getByName("jar")) + into(cordappsDir) + } + } + } + + /** + * Installs other cordapps to this node's cordapps directory. + */ + private fun installCordapps() { + val cordappsDir = File(nodeDir, "cordapps") + val cordapps = getCordappList() + project.copy { + it.apply { + from(cordapps) + into(cordappsDir) + } + } + } + + /** + * Installs the configuration file to this node's directory and detokenises it. + */ + private fun installConfig() { + val options = ConfigRenderOptions.defaults().setOriginComments(false).setComments(false).setFormatted(false).setJson(false) + val configFileText = config.root().render(options).split("\n").toList() + + // Need to write a temporary file first to use the project.copy, which resolves directories correctly. + val tmpDir = File(project.buildDir, "tmp") + val tmpConfFile = File(tmpDir, "node.conf") + Files.write(tmpConfFile.toPath(), configFileText, StandardCharsets.UTF_8) + + project.copy { + it.apply { + from(tmpConfFile) + into(nodeDir) + } + } + } + + /** + * Appends installed config file with properties from an optional file. + */ + private fun appendOptionalConfig() { + val optionalConfig: File? = when { + project.findProperty(configFileProperty) != null -> //provided by -PconfigFile command line property when running Gradle task + File(project.findProperty(configFileProperty) as String) + config.hasPath(configFileProperty) -> File(config.getString(configFileProperty)) + else -> null + } + + if (optionalConfig != null) { + if (!optionalConfig.exists()) { + project.logger.error("$configFileProperty '$optionalConfig' not found") + } else { + val confFile = File(project.buildDir.path + "/../" + nodeDir, "node.conf") + confFile.appendBytes(optionalConfig.readBytes()) + } + } + } + + /** + * Find the corda JAR amongst the dependencies. + * + * @return A file representing the Corda JAR. + */ + private fun verifyAndGetCordaJar(): File { + val maybeCordaJAR = project.configuration("runtime").filter { + it.toString().contains("corda-$releaseVersion.jar") || it.toString().contains("corda-enterprise-$releaseVersion.jar") + } + if (maybeCordaJAR.isEmpty) { + throw RuntimeException("No Corda Capsule JAR found. Have you deployed the Corda project to Maven? Looked for \"corda-$releaseVersion.jar\"") + } else { + val cordaJar = maybeCordaJAR.singleFile + assert(cordaJar.isFile) + return cordaJar + } + } + + /** + * Find the corda JAR amongst the dependencies + * + * @return A file representing the Corda webserver JAR + */ + private fun verifyAndGetWebserverJar(): File { + val maybeJar = project.configuration("runtime").filter { + it.toString().contains("corda-webserver-$releaseVersion.jar") + } + if (maybeJar.isEmpty) { + throw RuntimeException("No Corda Webserver JAR found. Have you deployed the Corda project to Maven? Looked for \"corda-webserver-$releaseVersion.jar\"") + } else { + val jar = maybeJar.singleFile + assert(jar.isFile) + return jar + } + } + + /** + * Gets a list of cordapps based on what dependent cordapps were specified. + * + * @return List of this node's cordapps. + */ + private fun getCordappList(): Collection { + // Cordapps can sometimes contain a GString instance which fails the equality test with the Java string + @Suppress("RemoveRedundantCallsOfConversionMethods") + val cordapps: List = cordapps.map { it.toString() } + return project.configuration("cordapp").files { + cordapps.contains(it.group + ":" + it.name + ":" + it.version) + } + } +} diff --git a/node-api/build.gradle b/node-api/build.gradle index be014cdc80..a9384bd4ab 100644 --- a/node-api/build.gradle +++ b/node-api/build.gradle @@ -18,6 +18,8 @@ dependencies { // TODO: Remove this dependency and the code that requires it compile "commons-fileupload:commons-fileupload:$fileupload_version" + compile "net.corda.plugins:cordform-common:$gradle_plugins_version" + // TypeSafe Config: for simple and human friendly config files. compile "com.typesafe:config:$typesafe_config_version" diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/NodeInfoFilesCopier.kt b/node-api/src/main/kotlin/net/corda/nodeapi/NodeInfoFilesCopier.kt new file mode 100644 index 0000000000..aadc49f4c3 --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/NodeInfoFilesCopier.kt @@ -0,0 +1,165 @@ +package net.corda.nodeapi + +import net.corda.cordform.CordformNode +import net.corda.core.internal.ThreadBox +import net.corda.core.internal.createDirectories +import net.corda.core.internal.isRegularFile +import net.corda.core.internal.list +import net.corda.core.utilities.loggerFor +import rx.Observable +import rx.Scheduler +import rx.Subscription +import rx.schedulers.Schedulers +import java.io.IOException +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.StandardCopyOption.COPY_ATTRIBUTES +import java.nio.file.StandardCopyOption.REPLACE_EXISTING +import java.nio.file.attribute.BasicFileAttributes +import java.nio.file.attribute.FileTime +import java.util.concurrent.TimeUnit + +/** + * Utility class which copies nodeInfo files across a set of running nodes. + * + * This class will create paths that it needs to poll and to where it needs to copy files in case those + * don't exist yet. + */ +class NodeInfoFilesCopier(scheduler: Scheduler = Schedulers.io()) : AutoCloseable { + + companion object { + private val log = loggerFor() + const val NODE_INFO_FILE_NAME_PREFIX = "nodeInfo-" + } + + private val nodeDataMapBox = ThreadBox(mutableMapOf()) + /** + * Whether the NodeInfoFilesCopier is closed. When the NodeInfoFilesCopier is closed it will stop polling the + * filesystem and all the public methods except [#close] will throw. + */ + private var closed = false + private val subscription: Subscription + + init { + this.subscription = Observable.interval(5, TimeUnit.SECONDS, scheduler) + .subscribe { poll() } + } + + /** + * @param nodeDir a path to be watched for NodeInfos + * Add a path of a node which is about to be started. + * Its nodeInfo file will be copied to other nodes' additional-node-infos directory, and conversely, + * other nodes' nodeInfo files will be copied to this node additional-node-infos directory. + */ + fun addConfig(nodeDir: Path) { + require(!closed) { "NodeInfoFilesCopier is already closed" } + nodeDataMapBox.locked { + val newNodeFile = NodeData(nodeDir) + put(nodeDir, newNodeFile) + + for (previouslySeenFile in allPreviouslySeenFiles()) { + atomicCopy(previouslySeenFile, newNodeFile.additionalNodeInfoDirectory.resolve(previouslySeenFile.fileName)) + } + log.info("Now watching: $nodeDir") + } + } + + /** + * @param nodeConfig the configuration to be removed. + * Remove the configuration of a node which is about to be stopped or already stopped. + * No files written by that node will be copied to other nodes, nor files from other nodes will be copied to this + * one. + */ + fun removeConfig(nodeDir: Path) { + require(!closed) { "NodeInfoFilesCopier is already closed" } + nodeDataMapBox.locked { + remove(nodeDir) ?: return + log.info("Stopped watching: $nodeDir") + } + } + + fun reset() { + require(!closed) { "NodeInfoFilesCopier is already closed" } + nodeDataMapBox.locked { + clear() + } + } + + /** + * Stops polling the filesystem. + * This function can be called as many times as one wants. + */ + override fun close() { + if (!closed) { + closed = true + subscription.unsubscribe() + } + } + + private fun allPreviouslySeenFiles() = nodeDataMapBox.alreadyLocked { values.flatMap { it.previouslySeenFiles.keys } } + + private fun poll() { + nodeDataMapBox.locked { + for (nodeData in values) { + nodeData.nodeDir.list { paths -> + paths.filter { it.isRegularFile() } + .filter { it.fileName.toString().startsWith(NODE_INFO_FILE_NAME_PREFIX) } + .forEach { path -> processPath(nodeData, path) } + } + } + } + } + + // Takes a path under nodeData config dir and decides whether the file represented by that path needs to + // be copied. + private fun processPath(nodeData: NodeData, path: Path) { + nodeDataMapBox.alreadyLocked { + val newTimestamp = Files.readAttributes(path, BasicFileAttributes::class.java).lastModifiedTime() + val previousTimestamp = nodeData.previouslySeenFiles.put(path, newTimestamp) ?: FileTime.fromMillis(-1) + if (newTimestamp > previousTimestamp) { + for (destination in this.values.filter { it.nodeDir != nodeData.nodeDir }.map { it.additionalNodeInfoDirectory }) { + val fullDestinationPath = destination.resolve(path.fileName) + atomicCopy(path, fullDestinationPath) + } + } + } + } + + private fun atomicCopy(source: Path, destination: Path) { + val tempDestination = try { + Files.createTempFile(destination.parent, "", null) + } catch (exception: IOException) { + log.warn("Couldn't create a temporary file to copy $source", exception) + throw exception + } + try { + // First copy the file to a temporary file within the appropriate directory. + Files.copy(source, tempDestination, COPY_ATTRIBUTES, REPLACE_EXISTING) + } catch (exception: IOException) { + log.warn("Couldn't copy $source to $tempDestination.", exception) + Files.delete(tempDestination) + throw exception + } + try { + // Then rename it to the desired name. This way the file 'appears' on the filesystem as an atomic operation. + Files.move(tempDestination, destination, REPLACE_EXISTING) + } catch (exception: IOException) { + log.warn("Couldn't move $tempDestination to $destination.", exception) + Files.delete(tempDestination) + throw exception + } + } + + /** + * Convenience holder for all the paths and files relative to a single node. + */ + private class NodeData(val nodeDir: Path) { + val additionalNodeInfoDirectory: Path = nodeDir.resolve(CordformNode.NODE_INFO_DIRECTORY) + // Map from Path to its lastModifiedTime. + val previouslySeenFiles = mutableMapOf() + + init { + additionalNodeInfoDirectory.createDirectories() + } + } +} diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/VerifierApi.kt b/node-api/src/main/kotlin/net/corda/nodeapi/VerifierApi.kt index eee653b30f..ca9f03cc07 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/VerifierApi.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/VerifierApi.kt @@ -1,8 +1,8 @@ package net.corda.nodeapi -import net.corda.core.serialization.deserialize -import net.corda.core.serialization.serialize +import net.corda.core.serialization.* import net.corda.core.transactions.LedgerTransaction +import net.corda.core.utilities.sequence import org.apache.activemq.artemis.api.core.SimpleString import org.apache.activemq.artemis.api.core.client.ClientMessage import org.apache.activemq.artemis.reader.MessageUtil @@ -20,12 +20,15 @@ object VerifierApi { val responseAddress: SimpleString ) { companion object { - fun fromClientMessage(message: ClientMessage): VerificationRequest { - return VerificationRequest( + fun fromClientMessage(message: ClientMessage): ObjectWithCompatibleContext { + val bytes = ByteArray(message.bodySize).apply { message.bodyBuffer.readBytes(this) } + val bytesSequence = bytes.sequence() + val (transaction, context) = bytesSequence.deserializeWithCompatibleContext() + val request = VerificationRequest( message.getLongProperty(VERIFICATION_ID_FIELD_NAME), - ByteArray(message.bodySize).apply { message.bodyBuffer.readBytes(this) }.deserialize(), - MessageUtil.getJMSReplyTo(message) - ) + transaction, + MessageUtil.getJMSReplyTo(message)) + return ObjectWithCompatibleContext(request, context) } } @@ -49,11 +52,11 @@ object VerifierApi { } } - fun writeToClientMessage(message: ClientMessage) { + fun writeToClientMessage(message: ClientMessage, context: SerializationContext) { message.putLongProperty(VERIFICATION_ID_FIELD_NAME, verificationId) if (exception != null) { - message.putBytesProperty(RESULT_EXCEPTION_FIELD_NAME, exception.serialize().bytes) + message.putBytesProperty(RESULT_EXCEPTION_FIELD_NAME, exception.serialize(context = context).bytes) } } } -} +} \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/GeneratedAttachment.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/GeneratedAttachment.kt index e42f18ef4b..82601202e6 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/GeneratedAttachment.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/GeneratedAttachment.kt @@ -3,6 +3,6 @@ package net.corda.nodeapi.internal.serialization import net.corda.core.crypto.sha256 import net.corda.core.internal.AbstractAttachment -class GeneratedAttachment(bytes: ByteArray) : AbstractAttachment({ bytes }) { +class GeneratedAttachment(val bytes: ByteArray) : AbstractAttachment({ bytes }) { override val id = bytes.sha256() } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationScheme.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationScheme.kt index 1e7c257a1d..a6be235f88 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationScheme.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationScheme.kt @@ -19,6 +19,7 @@ import net.corda.core.serialization.* import net.corda.core.utilities.ByteSequence import net.corda.core.utilities.OpaqueBytes import net.corda.nodeapi.internal.AttachmentsClassLoader +import org.slf4j.LoggerFactory import java.io.ByteArrayOutputStream import java.io.NotSerializableException import java.util.* @@ -37,7 +38,7 @@ object NotSupportedSerializationScheme : SerializationScheme { override fun serialize(obj: T, context: SerializationContext): SerializedBytes = doThrow() } -data class SerializationContextImpl(override val preferredSerializationVersion: ByteSequence, +data class SerializationContextImpl(override val preferredSerializationVersion: VersionHeader, override val deserializationClassLoader: ClassLoader, override val whitelist: ClassWhitelist, override val properties: Map, @@ -88,36 +89,54 @@ data class SerializationContextImpl(override val preferredSerializationVersion: }) } - override fun withPreferredSerializationVersion(versionHeader: ByteSequence) = copy(preferredSerializationVersion = versionHeader) + override fun withPreferredSerializationVersion(versionHeader: VersionHeader) = copy(preferredSerializationVersion = versionHeader) } private const val HEADER_SIZE: Int = 8 +fun ByteSequence.obtainHeaderSignature(): VersionHeader = take(HEADER_SIZE).copy() + open class SerializationFactoryImpl : SerializationFactory() { private val creator: List = Exception().stackTrace.asList() private val registeredSchemes: MutableCollection = Collections.synchronizedCollection(mutableListOf()) + private val logger = LoggerFactory.getLogger(javaClass) + // TODO: This is read-mostly. Probably a faster implementation to be found. private val schemes: ConcurrentHashMap, SerializationScheme> = ConcurrentHashMap() - private fun schemeFor(byteSequence: ByteSequence, target: SerializationContext.UseCase): SerializationScheme { + private fun schemeFor(byteSequence: ByteSequence, target: SerializationContext.UseCase): Pair { // truncate sequence to 8 bytes, and make sure it's a copy to avoid holding onto large ByteArrays - return schemes.computeIfAbsent(byteSequence.take(HEADER_SIZE).copy() to target) { + val lookupKey = byteSequence.obtainHeaderSignature() to target + val scheme = schemes.computeIfAbsent(lookupKey) { registeredSchemes .filter { scheme -> scheme.canDeserializeVersion(it.first, it.second) } .forEach { return@computeIfAbsent it } + logger.warn("Cannot find serialization scheme for: $lookupKey, registeredSchemes are: $registeredSchemes") NotSupportedSerializationScheme } + return scheme to lookupKey.first } @Throws(NotSerializableException::class) override fun deserialize(byteSequence: ByteSequence, clazz: Class, context: SerializationContext): T { - return asCurrent { withCurrentContext(context) { schemeFor(byteSequence, context.useCase).deserialize(byteSequence, clazz, context) } } + return asCurrent { withCurrentContext(context) { schemeFor(byteSequence, context.useCase).first.deserialize(byteSequence, clazz, context) } } + } + + @Throws(NotSerializableException::class) + override fun deserializeWithCompatibleContext(byteSequence: ByteSequence, clazz: Class, context: SerializationContext): ObjectWithCompatibleContext { + return asCurrent { + withCurrentContext(context) { + val (scheme, versionHeader) = schemeFor(byteSequence, context.useCase) + val deserializedObject = scheme.deserialize(byteSequence, clazz, context) + ObjectWithCompatibleContext(deserializedObject, context.withPreferredSerializationVersion(versionHeader)) + } + } } override fun serialize(obj: T, context: SerializationContext): SerializedBytes { - return asCurrent { withCurrentContext(context) { schemeFor(context.preferredSerializationVersion, context.useCase).serialize(obj, context) } } + return asCurrent { withCurrentContext(context) { schemeFor(context.preferredSerializationVersion, context.useCase).first.serialize(obj, context) } } } fun registerScheme(scheme: SerializationScheme) { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt index adb0c44f0c..cfb505ab6c 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt @@ -18,8 +18,15 @@ import java.util.* import net.corda.nodeapi.internal.serialization.carpenter.Field as CarpenterField import net.corda.nodeapi.internal.serialization.carpenter.Schema as CarpenterSchema -// TODO: get an assigned number as per AMQP spec -const val DESCRIPTOR_TOP_32BITS: Long = 0xc0da0000 +/** + * R3 AMQP assigned enterprise number + * + * see [here](https://www.iana.org/assignments/enterprise-numbers/enterprise-numbers) + * + * Repeated here for brevity: + * 50530 - R3 - Mike Hearn - mike&r3.com + */ +const val DESCRIPTOR_TOP_32BITS: Long = 0xc5620000 const val DESCRIPTOR_DOMAIN: String = "net.corda" diff --git a/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeInfoFilesCopierTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/NodeInfoFilesCopierTest.kt similarity index 73% rename from tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeInfoFilesCopierTest.kt rename to node-api/src/test/kotlin/net/corda/nodeapi/NodeInfoFilesCopierTest.kt index 557bd7095b..562717e641 100644 --- a/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeInfoFilesCopierTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/NodeInfoFilesCopierTest.kt @@ -1,8 +1,6 @@ -package net.corda.demobench.model +package net.corda.nodeapi import net.corda.cordform.CordformNode -import net.corda.core.identity.CordaX500Name -import net.corda.core.utilities.NetworkHostAndPort import net.corda.testing.eventually import org.junit.Before import org.junit.Rule @@ -30,17 +28,13 @@ class NodeInfoFilesCopierTest { private const val NODE_2_PATH = "node2" private val content = "blah".toByteArray(Charsets.UTF_8) - private val GOOD_NODE_INFO_NAME = "nodeInfo-test" - private val GOOD_NODE_INFO_NAME_2 = "nodeInfo-anotherNode" + private val GOOD_NODE_INFO_NAME = "${NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX}test" + private val GOOD_NODE_INFO_NAME_2 = "${NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX}anotherNode" private val BAD_NODE_INFO_NAME = "something" - private val legalName = CordaX500Name(organisation = ORGANIZATION, locality = "Nowhere", country = "GB") - private val hostAndPort = NetworkHostAndPort("localhost", 1) } private fun nodeDir(nodeBaseDir : String) = rootPath.resolve(nodeBaseDir).resolve(ORGANIZATION.toLowerCase()) - private val node1Config by lazy { createConfig(NODE_1_PATH) } - private val node2Config by lazy { createConfig(NODE_2_PATH) } private val node1RootPath by lazy { nodeDir(NODE_1_PATH) } private val node2RootPath by lazy { nodeDir(NODE_2_PATH) } private val node1AdditionalNodeInfoPath by lazy { node1RootPath.resolve(CordformNode.NODE_INFO_DIRECTORY) } @@ -56,7 +50,7 @@ class NodeInfoFilesCopierTest { @Test fun `files created before a node is started are copied to that node`() { // Configure the first node. - nodeInfoFilesCopier.addConfig(node1Config) + nodeInfoFilesCopier.addConfig(node1RootPath) // Ensure directories are created. advanceTime() @@ -65,7 +59,7 @@ class NodeInfoFilesCopierTest { Files.write(node1RootPath.resolve(BAD_NODE_INFO_NAME), content) // Configure the second node. - nodeInfoFilesCopier.addConfig(node2Config) + nodeInfoFilesCopier.addConfig(node2RootPath) advanceTime() eventually(Duration.ofMinutes(1)) { @@ -77,8 +71,8 @@ class NodeInfoFilesCopierTest { @Test fun `polling of running nodes`() { // Configure 2 nodes. - nodeInfoFilesCopier.addConfig(node1Config) - nodeInfoFilesCopier.addConfig(node2Config) + nodeInfoFilesCopier.addConfig(node1RootPath) + nodeInfoFilesCopier.addConfig(node2RootPath) advanceTime() // Create 2 files, one of which to be copied, in a node root path. @@ -95,8 +89,8 @@ class NodeInfoFilesCopierTest { @Test fun `remove nodes`() { // Configure 2 nodes. - nodeInfoFilesCopier.addConfig(node1Config) - nodeInfoFilesCopier.addConfig(node2Config) + nodeInfoFilesCopier.addConfig(node1RootPath) + nodeInfoFilesCopier.addConfig(node2RootPath) advanceTime() // Create a file, in node 2 root path. @@ -104,7 +98,7 @@ class NodeInfoFilesCopierTest { advanceTime() // Remove node 2 - nodeInfoFilesCopier.removeConfig(node2Config) + nodeInfoFilesCopier.removeConfig(node2RootPath) // Create another file in node 2 directory. Files.write(node2RootPath.resolve(GOOD_NODE_INFO_NAME_2), content) @@ -119,8 +113,8 @@ class NodeInfoFilesCopierTest { @Test fun `clear`() { // Configure 2 nodes. - nodeInfoFilesCopier.addConfig(node1Config) - nodeInfoFilesCopier.addConfig(node2Config) + nodeInfoFilesCopier.addConfig(node1RootPath) + nodeInfoFilesCopier.addConfig(node2RootPath) advanceTime() nodeInfoFilesCopier.reset() @@ -142,15 +136,4 @@ class NodeInfoFilesCopierTest { val onlyFileName = Files.list(path).toList().first().fileName.toString() assertEquals(filename, onlyFileName) } - - private fun createConfig(relativePath: String) = - NodeConfigWrapper(rootPath.resolve(relativePath), - NodeConfig(myLegalName = legalName, - p2pAddress = hostAndPort, - rpcAddress = hostAndPort, - webAddress = hostAndPort, - h2port = -1, - notary = null, - networkMapService = null, - rpcUsers = listOf())) } \ No newline at end of file diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderTests.kt index 5fd6b46f21..b2cd36ff1f 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderTests.kt @@ -1,6 +1,6 @@ package net.corda.nodeapi.internal -import com.nhaarman.mockito_kotlin.mock +import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash @@ -17,10 +17,7 @@ import net.corda.nodeapi.DummyContractBackdoor import net.corda.nodeapi.internal.serialization.SerializeAsTokenContextImpl import net.corda.nodeapi.internal.serialization.attachmentsClassLoaderEnabledPropertyName import net.corda.nodeapi.internal.serialization.withTokenContext -import net.corda.testing.DUMMY_NOTARY -import net.corda.testing.MEGA_CORP -import net.corda.testing.TestDependencyInjectionBase -import net.corda.testing.kryoSpecific +import net.corda.testing.* import net.corda.testing.node.MockAttachmentStorage import net.corda.testing.node.MockServices import org.apache.commons.io.IOUtils @@ -41,8 +38,8 @@ class AttachmentsClassLoaderTests : TestDependencyInjectionBase() { private const val ISOLATED_CONTRACT_CLASS_NAME = "net.corda.finance.contracts.isolated.AnotherDummyContract" private fun SerializationContext.withAttachmentStorage(attachmentStorage: AttachmentStorage): SerializationContext { - val serviceHub = mock() - whenever(serviceHub.attachments).thenReturn(attachmentStorage) + val serviceHub = rigorousMock() + doReturn(attachmentStorage).whenever(serviceHub).attachments return this.withServiceHub(serviceHub) } diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolverTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolverTests.kt index e684ef1f29..04f4c69122 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolverTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolverTests.kt @@ -5,15 +5,13 @@ import com.esotericsoftware.kryo.io.Input import com.esotericsoftware.kryo.io.Output import com.esotericsoftware.kryo.util.DefaultClassResolver import com.esotericsoftware.kryo.util.MapReferenceResolver -import com.nhaarman.mockito_kotlin.mock -import com.nhaarman.mockito_kotlin.verify -import com.nhaarman.mockito_kotlin.whenever +import com.nhaarman.mockito_kotlin.* import net.corda.core.node.services.AttachmentStorage import net.corda.core.serialization.* -import net.corda.core.utilities.ByteSequence import net.corda.nodeapi.internal.AttachmentsClassLoader import net.corda.nodeapi.internal.AttachmentsClassLoaderTests import net.corda.testing.node.MockAttachmentStorage +import net.corda.testing.rigorousMock import org.junit.Rule import org.junit.Test import org.junit.rules.ExpectedException @@ -108,16 +106,6 @@ class CordaClassResolverTests { val emptyMapClass = mapOf().javaClass } - val factory: SerializationFactory = object : SerializationFactory() { - override fun deserialize(byteSequence: ByteSequence, clazz: Class, context: SerializationContext): T { - TODO("not implemented") - } - - override fun serialize(obj: T, context: SerializationContext): SerializedBytes { - TODO("not implemented") - } - } - private val emptyWhitelistContext: SerializationContext = SerializationContextImpl(KryoHeaderV0_1, this.javaClass.classLoader, EmptyWhitelist, emptyMap(), true, SerializationContext.UseCase.P2P) private val allButBlacklistedContext: SerializationContext = SerializationContextImpl(KryoHeaderV0_1, this.javaClass.classLoader, AllButBlacklisted, emptyMap(), true, SerializationContext.UseCase.P2P) @@ -252,10 +240,11 @@ class CordaClassResolverTests { @Test fun `Kotlin EmptyList registers as Java emptyList`() { val javaEmptyListClass = Collections.emptyList().javaClass - val kryo = mock() + val kryo = rigorousMock() val resolver = CordaClassResolver(allButBlacklistedContext).apply { setKryo(kryo) } - whenever(kryo.getDefaultSerializer(javaEmptyListClass)).thenReturn(DefaultSerializableSerializer()) - + doReturn(DefaultSerializableSerializer()).whenever(kryo).getDefaultSerializer(javaEmptyListClass) + doReturn(false).whenever(kryo).references + doReturn(false).whenever(kryo).references = any() val registration = resolver.registerImplicit(emptyListClass) assertNotNull(registration) assertEquals(javaEmptyListClass, registration.type) @@ -273,10 +262,11 @@ class CordaClassResolverTests { @Test fun `Kotlin EmptySet registers as Java emptySet`() { val javaEmptySetClass = Collections.emptySet().javaClass - val kryo = mock() + val kryo = rigorousMock() val resolver = CordaClassResolver(allButBlacklistedContext).apply { setKryo(kryo) } - whenever(kryo.getDefaultSerializer(javaEmptySetClass)).thenReturn(DefaultSerializableSerializer()) - + doReturn(DefaultSerializableSerializer()).whenever(kryo).getDefaultSerializer(javaEmptySetClass) + doReturn(false).whenever(kryo).references + doReturn(false).whenever(kryo).references = any() val registration = resolver.registerImplicit(emptySetClass) assertNotNull(registration) assertEquals(javaEmptySetClass, registration.type) @@ -294,10 +284,11 @@ class CordaClassResolverTests { @Test fun `Kotlin EmptyMap registers as Java emptyMap`() { val javaEmptyMapClass = Collections.emptyMap().javaClass - val kryo = mock() + val kryo = rigorousMock() val resolver = CordaClassResolver(allButBlacklistedContext).apply { setKryo(kryo) } - whenever(kryo.getDefaultSerializer(javaEmptyMapClass)).thenReturn(DefaultSerializableSerializer()) - + doReturn(DefaultSerializableSerializer()).whenever(kryo).getDefaultSerializer(javaEmptyMapClass) + doReturn(false).whenever(kryo).references + doReturn(false).whenever(kryo).references = any() val registration = resolver.registerImplicit(emptyMapClass) assertNotNull(registration) assertEquals(javaEmptyMapClass, registration.type) diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SerializationTokenTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SerializationTokenTest.kt index 909f672fde..64b2040642 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SerializationTokenTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SerializationTokenTest.kt @@ -3,10 +3,10 @@ package net.corda.nodeapi.internal.serialization import com.esotericsoftware.kryo.Kryo import com.esotericsoftware.kryo.KryoException import com.esotericsoftware.kryo.io.Output -import com.nhaarman.mockito_kotlin.mock import net.corda.core.serialization.* import net.corda.core.utilities.OpaqueBytes import net.corda.testing.TestDependencyInjectionBase +import net.corda.testing.rigorousMock import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Test @@ -35,8 +35,7 @@ class SerializationTokenTest : TestDependencyInjectionBase() { override fun equals(other: Any?) = other is LargeTokenizable && other.bytes.size == this.bytes.size } - private fun serializeAsTokenContext(toBeTokenized: Any) = SerializeAsTokenContextImpl(toBeTokenized, factory, context, mock()) - + private fun serializeAsTokenContext(toBeTokenized: Any) = SerializeAsTokenContextImpl(toBeTokenized, factory, context, rigorousMock()) @Test fun `write token and read tokenizable`() { val tokenizableBefore = LargeTokenizable() diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt index cdf690029c..9c4a949384 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt @@ -30,6 +30,7 @@ class EvolvabilityTests { // data class C (val a: Int, val b: Int) // val sc = SerializationOutput(sf).serialize(C(A, B)) // f.writeBytes(sc.bytes) + // println (path) // new version of the class, in this case the order of the parameters has been swapped data class C(val b: Int, val a: Int) @@ -54,6 +55,7 @@ class EvolvabilityTests { // data class C (val a: Int, val b: String) // val sc = SerializationOutput(sf).serialize(C(A, B)) // f.writeBytes(sc.bytes) + // println (path) // new version of the class, in this case the order of the parameters has been swapped data class C(val b: String, val a: Int) @@ -78,7 +80,6 @@ class EvolvabilityTests { // val sc = SerializationOutput(sf).serialize(C(A)) // f.writeBytes(sc.bytes) // println ("Path = $path") - data class C(val a: Int, val b: Int?) val sc2 = f.readBytes() @@ -300,9 +301,6 @@ class EvolvabilityTests { val path2 = EvolvabilityTests::class.java.getResource("EvolvabilityTests.multiVersion.2") val path3 = EvolvabilityTests::class.java.getResource("EvolvabilityTests.multiVersion.3") - @Suppress("UNUSED_VARIABLE") - val f = File(path1.toURI()) - val a = 100 val b = 200 val c = 300 @@ -312,14 +310,24 @@ class EvolvabilityTests { // // Version 1: // data class C (val a: Int, val b: Int) + // + // val scc = SerializationOutput(sf).serialize(C(a, b)) + // File(path1.toURI()).writeBytes(scc.bytes) + // println ("Path = $path1") + // // Version 2 - add param c // data class C (val c: Int, val b: Int, val a: Int) + // + // val scc = SerializationOutput(sf).serialize(C(c, b, a)) + // File(path2.toURI()).writeBytes(scc.bytes) + // println ("Path = $path2") + // // Version 3 - add param d // data class C (val b: Int, val c: Int, val d: Int, val a: Int) // // val scc = SerializationOutput(sf).serialize(C(b, c, d, a)) - // f.writeBytes(scc.bytes) - // println ("Path = $path1") + // File(path3.toURI()).writeBytes(scc.bytes) + // println ("Path = $path3") @Suppress("UNUSED") data class C(val e: Int, val c: Int, val b: Int, val a: Int, val d: Int) { @@ -409,14 +417,24 @@ class EvolvabilityTests { // // Version 1: // data class C (val a: Int, val b: Int, val c: Int) + // + // val scc = SerializationOutput(sf).serialize(C(a, b, c)) + // File(path1.toURI()).writeBytes(scc.bytes) + // println ("Path = $path1") + // // Version 2 - add param c // data class C (val b: Int, val c: Int, val d: Int, val e: Int) + // + // val scc = SerializationOutput(sf).serialize(C(b, c, d, e)) + // File(path2.toURI()).writeBytes(scc.bytes) + // println ("Path = $path2") + // // Version 3 - add param d // data class C (val b: Int, val c: Int, val d: Int, val e: Int, val f: Int) // // val scc = SerializationOutput(sf).serialize(C(b, c, d, e, f)) - // File(path1.toURI()).writeBytes(scc.bytes) - // println ("Path = $path1") + // File(path3.toURI()).writeBytes(scc.bytes) + // println ("Path = $path3") @Suppress("UNUSED") data class C(val b: Int, val c: Int, val d: Int, val e: Int, val f: Int, val g: Int) { diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addAdditionalParam b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addAdditionalParam index 06c32d9eff..e9accefe39 100644 Binary files a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addAdditionalParam and b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addAdditionalParam differ diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addAdditionalParamNotMandatory b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addAdditionalParamNotMandatory index 4b5eb1dda9..8e37059133 100644 Binary files a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addAdditionalParamNotMandatory and b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addAdditionalParamNotMandatory differ diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addAndRemoveParameters b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addAndRemoveParameters index d31d1b26fd..3943c84020 100644 Binary files a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addAndRemoveParameters and b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addAndRemoveParameters differ diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltConstructor b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltConstructor index 6daa3c7714..a7e207ac75 100644 Binary files a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltConstructor and b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltConstructor differ diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltConstructorUnAnnotated b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltConstructorUnAnnotated index e47d5270f8..e712d58560 100644 Binary files a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltConstructorUnAnnotated and b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltConstructorUnAnnotated differ diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructor b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructor index 290fad125a..87506ae203 100644 Binary files a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructor and b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructor differ diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructorAndRemoval b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructorAndRemoval index 769ea67e2d..922eecc335 100644 Binary files a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructorAndRemoval and b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructorAndRemoval differ diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.changeSubType b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.changeSubType index 1158e284fe..96e11c7f09 100644 Binary files a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.changeSubType and b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.changeSubType differ diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersion.1 b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersion.1 index 46714457ec..2cca4802ac 100644 Binary files a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersion.1 and b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersion.1 differ diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersion.2 b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersion.2 index 3f467b2390..76818dc697 100644 Binary files a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersion.2 and b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersion.2 differ diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersion.3 b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersion.3 index 6b3e9a3a37..1e36577ca9 100644 Binary files a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersion.3 and b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersion.3 differ diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersionWithRemoval.1 b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersionWithRemoval.1 index fa424bf678..9496b5632a 100644 Binary files a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersionWithRemoval.1 and b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersionWithRemoval.1 differ diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersionWithRemoval.2 b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersionWithRemoval.2 index bc41d9c9d0..44cd5fb07f 100644 Binary files a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersionWithRemoval.2 and b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersionWithRemoval.2 differ diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersionWithRemoval.3 b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersionWithRemoval.3 index f8a7705470..c1dcb85ab8 100644 Binary files a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersionWithRemoval.3 and b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersionWithRemoval.3 differ diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.removeParameters b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.removeParameters index c0d7333de5..65d11e24e3 100644 Binary files a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.removeParameters and b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.removeParameters differ diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.simpleOrderSwapDifferentType b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.simpleOrderSwapDifferentType index 2e6a7098f6..6d81269d3e 100644 Binary files a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.simpleOrderSwapDifferentType and b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.simpleOrderSwapDifferentType differ diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.simpleOrderSwapSameType b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.simpleOrderSwapSameType index 6e04f4bdf3..b96b0ce60e 100644 Binary files a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.simpleOrderSwapSameType and b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.simpleOrderSwapSameType differ diff --git a/node/build.gradle b/node/build.gradle index 3d6c5d4e0f..2a7f24aa52 100644 --- a/node/build.gradle +++ b/node/build.gradle @@ -137,6 +137,9 @@ dependencies { // For H2 database support in persistence compile "com.h2database:h2:$h2_version" + // For Postgres database support in persistence + compile "org.postgresql:postgresql:$postgresql_version" + // SQL connection pooling library compile "com.zaxxer:HikariCP:2.5.1" @@ -182,6 +185,17 @@ dependencies { smokeTestCompile project(':smoke-test-utils') smokeTestCompile "org.assertj:assertj-core:${assertj_version}" smokeTestCompile "junit:junit:$junit_version" + + // Jetty dependencies for NetworkMapClient test. + // Web stuff: for HTTP[S] servlets + testCompile "org.eclipse.jetty:jetty-servlet:${jetty_version}" + testCompile "org.eclipse.jetty:jetty-webapp:${jetty_version}" + testCompile "javax.servlet:javax.servlet-api:3.1.0" + + // Jersey for JAX-RS implementation for use in Jetty + testCompile "org.glassfish.jersey.core:jersey-server:${jersey_version}" + testCompile "org.glassfish.jersey.containers:jersey-container-servlet-core:${jersey_version}" + testCompile "org.glassfish.jersey.containers:jersey-container-jetty-http:${jersey_version}" } task integrationTest(type: Test) { diff --git a/node/capsule/build.gradle b/node/capsule/build.gradle index e81eb844c0..f307d809db 100644 --- a/node/capsule/build.gradle +++ b/node/capsule/build.gradle @@ -31,7 +31,10 @@ task buildCordaJAR(type: FatCapsule, dependsOn: project(':node').compileJava) { "$rootDir/config/dev/log4j2.xml" ) from 'NOTICE' // Copy CDDL notice - + from { project(':node').configurations.runtime.allDependencies.matching { // Include config library JAR. + it.group.equals("com.typesafe") && it.name.equals("config") + }.collect { zipTree(project(':node').configurations.runtime.files(it).first()) } } + from { "$rootDir/node/build/resources/main/reference.conf" } capsuleManifest { applicationVersion = corda_release_version @@ -47,6 +50,7 @@ task buildCordaJAR(type: FatCapsule, dependsOn: project(':node').compileJava) { // JVM configuration: // - Constrain to small heap sizes to ease development on low end devices. // - Switch to the G1 GC which is going to be the default in Java 9 and gives low pause times/string dedup. + // NOTE: these can be overridden in node.conf. // // If you change these flags, please also update Driver.kt jvmArgs = ['-Xmx200m', '-XX:+UseG1GC'] diff --git a/node/src/integration-test/kotlin/net/corda/node/BootTests.kt b/node/src/integration-test/kotlin/net/corda/node/BootTests.kt index be45027072..e402d00849 100644 --- a/node/src/integration-test/kotlin/net/corda/node/BootTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/BootTests.kt @@ -14,7 +14,6 @@ import net.corda.nodeapi.internal.ServiceType import net.corda.testing.ALICE import net.corda.testing.ProjectStructure.projectRootDir import net.corda.testing.driver.ListenProcessDeathException -import net.corda.testing.driver.NetworkMapStartStrategy import net.corda.testing.driver.driver import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy @@ -59,7 +58,7 @@ class BootTests { @Test fun `node quits on failure to register with network map`() { val tooManyAdvertisedServices = (1..100).map { ServiceInfo(ServiceType.notary.getSubType("$it")) }.toSet() - driver(networkMapStartStrategy = NetworkMapStartStrategy.Nominated(ALICE.name)) { + driver { val future = startNode(providedName = ALICE.name) assertFailsWith(ListenProcessDeathException::class) { future.getOrThrow() } } diff --git a/node/src/integration-test/kotlin/net/corda/node/NodeStartupPerformanceTests.kt b/node/src/integration-test/kotlin/net/corda/node/NodeStartupPerformanceTests.kt index 2f29d8df6d..a9f2586013 100644 --- a/node/src/integration-test/kotlin/net/corda/node/NodeStartupPerformanceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/NodeStartupPerformanceTests.kt @@ -1,7 +1,6 @@ package net.corda.node import com.google.common.base.Stopwatch -import net.corda.testing.driver.NetworkMapStartStrategy import net.corda.testing.driver.driver import org.junit.Ignore import org.junit.Test @@ -14,8 +13,7 @@ class NodeStartupPerformanceTests { // Measure the startup time of nodes. Note that this includes an RPC roundtrip, which causes e.g. Kryo initialisation. @Test fun `single node startup time`() { - driver(networkMapStartStrategy = NetworkMapStartStrategy.Dedicated(startAutomatically = false)) { - startDedicatedNetworkMapService().get() + driver { val times = ArrayList() for (i in 1..10) { val time = Stopwatch.createStarted().apply { diff --git a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt index 9a6a5c3112..3605ab14a4 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt @@ -1,5 +1,6 @@ package net.corda.node.services +import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever import net.corda.core.contracts.AlwaysAcceptAttachmentConstraint import net.corda.core.contracts.ContractState @@ -29,6 +30,7 @@ import net.corda.testing.contracts.DummyContract import net.corda.testing.dummyCommand import net.corda.testing.getDefaultNotary import net.corda.testing.node.MockNetwork +import net.corda.testing.node.MockNodeParameters import org.junit.After import org.junit.Test import java.nio.file.Paths @@ -56,10 +58,10 @@ class BFTNotaryServiceTests { clusterName) val clusterAddresses = replicaIds.map { NetworkHostAndPort("localhost", 11000 + it * 10) } replicaIds.forEach { replicaId -> - mockNet.createNode(configOverrides = { + mockNet.createNode(MockNodeParameters(configOverrides = { val notary = NotaryConfig(validating = false, bftSMaRt = BFTSMaRtConfiguration(replicaId, clusterAddresses, exposeRaces = exposeRaces)) - whenever(it.notary).thenReturn(notary) - }) + doReturn(notary).whenever(it).notary + })) } mockNet.runNetwork() // Exchange initial network map registration messages. } diff --git a/node/src/integration-test/kotlin/net/corda/node/services/network/NodeInfoWatcherTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/network/NodeInfoWatcherTest.kt index 97e2deb364..d307a02705 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/network/NodeInfoWatcherTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/network/NodeInfoWatcherTest.kt @@ -8,6 +8,7 @@ import net.corda.core.internal.div import net.corda.core.node.NodeInfo import net.corda.core.node.services.KeyManagementService import net.corda.node.services.identity.InMemoryIdentityService +import net.corda.nodeapi.NodeInfoFilesCopier import net.corda.testing.ALICE import net.corda.testing.ALICE_KEY import net.corda.testing.DEV_TRUST_ROOT @@ -42,7 +43,6 @@ class NodeInfoWatcherTest : NodeBasedTest() { lateinit var nodeInfoWatcher: NodeInfoWatcher companion object { - val nodeInfoFileRegex = Regex("nodeInfo\\-.*") val nodeInfo = NodeInfo(listOf(), listOf(getTestPartyAndCertificate(ALICE)), 0, 0) } @@ -56,13 +56,14 @@ class NodeInfoWatcherTest : NodeBasedTest() { @Test fun `save a NodeInfo`() { - assertEquals(0, folder.root.list().filter { it.matches(nodeInfoFileRegex) }.size) + assertEquals(0, + folder.root.list().filter { it.startsWith(NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX) }.size) NodeInfoWatcher.saveToFile(folder.root.toPath(), nodeInfo, keyManagementService) - val nodeInfoFiles = folder.root.list().filter { it.matches(nodeInfoFileRegex) } + val nodeInfoFiles = folder.root.list().filter { it.startsWith(NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX) } assertEquals(1, nodeInfoFiles.size) val fileName = nodeInfoFiles.first() - assertTrue(fileName.matches(nodeInfoFileRegex)) + assertTrue(fileName.startsWith(NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX)) val file = (folder.root.path / fileName).toFile() // Just check that something is written, another tests verifies that the written value can be read back. assertThat(contentOf(file)).isNotEmpty() diff --git a/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt index fae4c25c51..00671b35a9 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt @@ -46,7 +46,7 @@ class PersistentNetworkMapCacheTest : NodeBasedTest() { @Test fun `get nodes by owning key and by name, no network map service`() { val alice = startNodesWithPort(listOf(ALICE), noNetworkMap = true)[0] - val netCache = alice.services.networkMapCache as PersistentNetworkMapCache + val netCache = alice.services.networkMapCache alice.database.transaction { val res = netCache.getNodeByLegalIdentity(alice.info.chooseIdentity()) assertEquals(alice.info, res) @@ -58,7 +58,7 @@ class PersistentNetworkMapCacheTest : NodeBasedTest() { @Test fun `get nodes by address no network map service`() { val alice = startNodesWithPort(listOf(ALICE), noNetworkMap = true)[0] - val netCache = alice.services.networkMapCache as PersistentNetworkMapCache + val netCache = alice.services.networkMapCache alice.database.transaction { val res = netCache.getNodeByAddress(alice.info.addresses[0]) assertEquals(alice.info, res) diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/P2PSecurityTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/P2PSecurityTest.kt index 317252e531..07e396de2b 100644 --- a/node/src/integration-test/kotlin/net/corda/services/messaging/P2PSecurityTest.kt +++ b/node/src/integration-test/kotlin/net/corda/services/messaging/P2PSecurityTest.kt @@ -1,5 +1,6 @@ package net.corda.services.messaging +import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever import net.corda.core.concurrent.CordaFuture import net.corda.core.crypto.random63BitValue @@ -61,8 +62,8 @@ class P2PSecurityTest : NodeBasedTest() { val config = testNodeConfiguration( baseDirectory = baseDirectory(legalName), myLegalName = legalName).also { - whenever(it.networkMapService).thenReturn(NetworkMapInfo(networkMapNode.internals.configuration.p2pAddress, networkMapNode.info.chooseIdentity().name)) - whenever(it.activeMQServer).thenReturn(ActiveMqServerConfiguration(BridgeConfiguration(1001, 2, 3.4))) + doReturn(NetworkMapInfo(networkMapNode.internals.configuration.p2pAddress, networkMapNode.info.chooseIdentity().name)).whenever(it).networkMapService + doReturn(ActiveMqServerConfiguration(BridgeConfiguration(1001, 2, 3.4))).whenever(it).activeMQServer } config.configureWithDevSSLCertificate() // This creates the node's TLS cert with the CN as the legal name return SimpleNode(config, trustRoot = trustRoot).apply { start() } @@ -73,6 +74,6 @@ class P2PSecurityTest : NodeBasedTest() { val nodeInfo = NodeInfo(listOf(MOCK_HOST_AND_PORT), listOf(legalIdentity), 1, serial = 1) val registration = NodeRegistration(nodeInfo, System.currentTimeMillis(), AddOrRemove.ADD, Instant.MAX) val request = RegistrationRequest(registration.toWire(keyService, identity.public), network.myAddress) - return network.sendRequest(NetworkMapService.REGISTER_TOPIC, request, networkMapNode.network.myAddress) + return network.sendRequest(NetworkMapService.REGISTER_TOPIC, request, networkMapNode.network.myAddress) } } diff --git a/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt b/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt index 0e8f062c9c..9a71e5c60f 100644 --- a/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt @@ -7,6 +7,7 @@ import net.corda.core.flows.FlowLogic import net.corda.core.flows.StartableByRPC import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party +import net.corda.core.internal.concurrent.transpose import net.corda.core.messaging.startFlow import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentState @@ -21,37 +22,56 @@ import net.corda.node.services.FlowPermissions import net.corda.nodeapi.User import net.corda.testing.DUMMY_NOTARY import net.corda.testing.chooseIdentity +import net.corda.testing.driver.DriverDSLExposedInterface +import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.driver +import org.junit.Assume import org.junit.Test import java.lang.management.ManagementFactory import javax.persistence.Column import javax.persistence.Entity import javax.persistence.Table import kotlin.test.assertEquals +import kotlin.test.assertNotNull class NodeStatePersistenceTests { @Test fun `persistent state survives node restart`() { + // Temporary disable this test when executed on Windows. It is known to be sporadically failing. + // More investigation is needed to establish why. + Assume.assumeFalse(System.getProperty("os.name").toLowerCase().startsWith("win")) + val user = User("mark", "dadada", setOf(FlowPermissions.startFlowPermission())) val message = Message("Hello world!") driver(isDebug = true, startNodesInProcess = isQuasarAgentSpecified()) { - startNotaryNode(DUMMY_NOTARY.name, validating = false).getOrThrow() - var nodeHandle = startNode(rpcUsers = listOf(user)).getOrThrow() - val nodeName = nodeHandle.nodeInfo.chooseIdentity().name - nodeHandle.rpcClientToNode().start(user.username, user.password).use { - it.proxy.startFlow(::SendMessageFlow, message).returnValue.getOrThrow() - } - nodeHandle.stop().getOrThrow() + val (nodeName, notaryNodeHandle) = { + val notaryNodeHandle = startNotaryNode(DUMMY_NOTARY.name, validating = false).getOrThrow() + val nodeHandle = startNode(rpcUsers = listOf(user)).getOrThrow() + ensureAcquainted(notaryNodeHandle, nodeHandle) + val nodeName = nodeHandle.nodeInfo.chooseIdentity().name + nodeHandle.rpcClientToNode().start(user.username, user.password).use { + it.proxy.startFlow(::SendMessageFlow, message).returnValue.getOrThrow() + } + nodeHandle.stop().getOrThrow() + nodeName to notaryNodeHandle + }() - nodeHandle = startNode(providedName = nodeName, rpcUsers = listOf(user)).getOrThrow() + val nodeHandle = startNode(providedName = nodeName, rpcUsers = listOf(user)).getOrThrow() + ensureAcquainted(notaryNodeHandle, nodeHandle) nodeHandle.rpcClientToNode().start(user.username, user.password).use { val page = it.proxy.vaultQuery(MessageState::class.java) - val retrievedMessage = page.states.singleOrNull()?.state?.data?.message + val stateAndRef = page.states.singleOrNull() + assertNotNull(stateAndRef) + val retrievedMessage = stateAndRef!!.state.data.message assertEquals(message, retrievedMessage) } } } + + private fun DriverDSLExposedInterface.ensureAcquainted(one: NodeHandle, another: NodeHandle) { + listOf(one.pollUntilKnowsAbout(another), another.pollUntilKnowsAbout(one)).transpose().getOrThrow() + } } fun isQuasarAgentSpecified(): Boolean { @@ -95,7 +115,7 @@ object MessageSchemaV1 : MappedSchema( ) : PersistentState() } -val MESSAGE_CONTRACT_PROGRAM_ID = "net.corda.test.node.MessageContract" +const val MESSAGE_CONTRACT_PROGRAM_ID = "net.corda.test.node.MessageContract" open class MessageContract : Contract { override fun verify(tx: LedgerTransaction) { diff --git a/node/src/main/java/CordaCaplet.java b/node/src/main/java/CordaCaplet.java index fa39580fa7..e42e5acd80 100644 --- a/node/src/main/java/CordaCaplet.java +++ b/node/src/main/java/CordaCaplet.java @@ -2,18 +2,73 @@ // must also be in the default package. When using Kotlin there are a whole host of exceptions // trying to construct this from Capsule, so it is written in Java. -import sun.misc.*; +import com.typesafe.config.*; +import sun.misc.Signal; +import sun.misc.SignalHandler; -import java.io.*; -import java.nio.file.*; +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.*; public class CordaCaplet extends Capsule { + private Config nodeConfig = null; + private String baseDir = null; + protected CordaCaplet(Capsule pred) { super(pred); } + private Config parseConfigFile(List args) { + String baseDirOption = getOption(args, "--base-directory"); + this.baseDir = Paths.get((baseDirOption == null) ? "." : baseDirOption).toAbsolutePath().normalize().toString(); + String config = getOption(args, "--config-file"); + File configFile = (config == null) ? new File(baseDir, "node.conf") : new File(config); + try { + ConfigParseOptions parseOptions = ConfigParseOptions.defaults().setAllowMissing(false); + Config defaultConfig = ConfigFactory.parseResources("reference.conf", parseOptions); + Config baseDirectoryConfig = ConfigFactory.parseMap(Collections.singletonMap("baseDirectory", baseDir)); + Config nodeConfig = ConfigFactory.parseFile(configFile, parseOptions); + return baseDirectoryConfig.withFallback(nodeConfig).withFallback(defaultConfig).resolve(); + } catch (ConfigException e) { + log(LOG_QUIET, e); + return ConfigFactory.empty(); + } + } + + private String getOption(List args, String option) { + final String lowerCaseOption = option.toLowerCase(); + int index = 0; + for (String arg : args) { + if (arg.toLowerCase().equals(lowerCaseOption)) { + if (index < args.size() - 1) { + return args.get(index + 1); + } else { + return null; + } + } + index++; + } + return null; + } + + @Override + protected ProcessBuilder prelaunch(List jvmArgs, List args) { + nodeConfig = parseConfigFile(args); + return super.prelaunch(jvmArgs, args); + } + + // Add working directory variable to capsules string replacement variables. + @Override + protected String getVarValue(String var) { + if (var.equals("baseDirectory")) { + return baseDir; + } else { + return super.getVarValue(var); + } + } + /** * Overriding the Caplet classpath generation via the intended interface in Capsule. */ @@ -25,18 +80,55 @@ public class CordaCaplet extends Capsule { if (ATTR_APP_CLASS_PATH == attr) { T cp = super.attribute(attr); - (new File("cordapps")).mkdir(); - augmentClasspath((List) cp, "cordapps"); - augmentClasspath((List) cp, "plugins"); + (new File(baseDir, "cordapps")).mkdir(); + augmentClasspath((List) cp, new File(baseDir, "cordapps")); + augmentClasspath((List) cp, new File(baseDir, "plugins")); + // Add additional directories of JARs to the classpath (at the end). e.g. for JDBC drivers + try { + List jarDirs = nodeConfig.getStringList("jarDirs"); + log(LOG_VERBOSE, "Configured JAR directories = " + jarDirs); + for (String jarDir : jarDirs) { + augmentClasspath((List) cp, new File(jarDir)); + } + } catch (ConfigException.Missing e) { + // Ignore since it's ok to be Missing. Other errors would be unexpected. + } catch (ConfigException e) { + log(LOG_QUIET, e); + } return cp; - } - return super.attribute(attr); + } else if (ATTR_JVM_ARGS == attr) { + // Read JVM args from the config if specified, else leave alone. + List jvmArgs = new ArrayList<>((List) super.attribute(attr)); + try { + List configJvmArgs = nodeConfig.getStringList("jvmArgs"); + jvmArgs.clear(); + jvmArgs.addAll(configJvmArgs); + log(LOG_VERBOSE, "Configured JVM args = " + jvmArgs); + } catch (ConfigException.Missing e) { + // Ignore since it's ok to be Missing. Other errors would be unexpected. + } catch (ConfigException e) { + log(LOG_QUIET, e); + } + return (T) jvmArgs; + } else if (ATTR_SYSTEM_PROPERTIES == attr) { + // Add system properties, if specified, from the config. + Map systemProps = new LinkedHashMap<>((Map) super.attribute(attr)); + try { + Config overrideSystemProps = nodeConfig.getConfig("systemProperties"); + log(LOG_VERBOSE, "Configured system properties = " + overrideSystemProps); + for (Map.Entry entry : overrideSystemProps.entrySet()) { + systemProps.put(entry.getKey(), entry.getValue().unwrapped().toString()); + } + } catch (ConfigException.Missing e) { + // Ignore since it's ok to be Missing. Other errors would be unexpected. + } catch (ConfigException e) { + log(LOG_QUIET, e); + } + return (T) systemProps; + } else return super.attribute(attr); } - // TODO: Make directory configurable via the capsule manifest. - // TODO: Add working directory variable to capsules string replacement variables. - private void augmentClasspath(List classpath, String dirName) { - File dir = new File(dirName); + private void augmentClasspath(List classpath, File dir) { if (dir.exists()) { File[] files = dir.listFiles(); for (File file : files) { diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 16829873aa..f7adde2604 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -11,26 +11,23 @@ import net.corda.core.flows.* import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate -import net.corda.core.internal.VisibleForTesting -import net.corda.core.internal.cert +import net.corda.core.internal.* import net.corda.core.internal.concurrent.doneFuture import net.corda.core.internal.concurrent.flatMap import net.corda.core.internal.concurrent.openFuture -import net.corda.core.internal.toX509CertHolder -import net.corda.core.internal.uncheckedCast import net.corda.core.messaging.* import net.corda.core.node.AppServiceHub import net.corda.core.node.NodeInfo import net.corda.core.node.ServiceHub import net.corda.core.node.StateLoader import net.corda.core.node.services.* -import net.corda.core.node.services.NetworkMapCache.MapChange import net.corda.core.serialization.SerializationWhitelist import net.corda.core.serialization.SerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.debug +import net.corda.core.utilities.getOrThrow import net.corda.node.VersionInfo import net.corda.node.internal.classloading.requireAnnotation import net.corda.node.internal.cordapp.CordappLoader @@ -78,6 +75,7 @@ import java.security.cert.CertificateFactory import java.security.cert.X509Certificate import java.sql.Connection import java.time.Clock +import java.util.* import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ExecutorService import java.util.concurrent.TimeUnit.SECONDS @@ -108,7 +106,7 @@ abstract class AbstractNode(config: NodeConfiguration, private class StartedNodeImpl( override val internals: N, - override val services: ServiceHubInternalImpl, + services: ServiceHubInternalImpl, override val info: NodeInfo, override val checkpointStorage: CheckpointStorage, override val smm: StateMachineManager, @@ -116,8 +114,11 @@ abstract class AbstractNode(config: NodeConfiguration, override val inNodeNetworkMapService: NetworkMapService, override val network: MessagingService, override val database: CordaPersistence, - override val rpcOps: CordaRPCOps) : StartedNode - + override val rpcOps: CordaRPCOps, + flowStarter: FlowStarter, + internal val schedulerService: NodeSchedulerService) : StartedNode { + override val services: StartedNodeServices = object : StartedNodeServices, ServiceHubInternal by services, FlowStarter by flowStarter {} + } // TODO: Persist this, as well as whether the node is registered. /** * Sequence number of changes sent to the network map service, when registering/de-registering this node. @@ -138,10 +139,12 @@ abstract class AbstractNode(config: NodeConfiguration, protected val services: ServiceHubInternal get() = _services private lateinit var _services: ServiceHubInternalImpl protected lateinit var legalIdentity: PartyAndCertificate + private lateinit var allIdentities: List protected lateinit var info: NodeInfo protected var myNotaryIdentity: PartyAndCertificate? = null protected lateinit var checkpointStorage: CheckpointStorage protected lateinit var smm: StateMachineManager + private lateinit var tokenizableServices: List protected lateinit var attachments: NodeAttachmentService protected lateinit var inNodeNetworkMapService: NetworkMapService protected lateinit var network: MessagingService @@ -167,8 +170,8 @@ abstract class AbstractNode(config: NodeConfiguration, @Volatile private var _started: StartedNode? = null /** The implementation of the [CordaRPCOps] interface used by this node. */ - open fun makeRPCOps(): CordaRPCOps { - return CordaRPCOpsImpl(services, smm, database) + open fun makeRPCOps(flowStarter: FlowStarter): CordaRPCOps { + return CordaRPCOpsImpl(services, smm, database, flowStarter) } private fun saveOwnNodeInfo() { @@ -190,7 +193,8 @@ abstract class AbstractNode(config: NodeConfiguration, log.info("Generating nodeInfo ...") val schemaService = makeSchemaService() initialiseDatabasePersistence(schemaService) { - makeServices(schemaService) + val transactionStorage = makeTransactionStorage() + makeServices(schemaService, transactionStorage, StateLoaderImpl(transactionStorage)) saveOwnNodeInfo() } } @@ -202,17 +206,13 @@ abstract class AbstractNode(config: NodeConfiguration, val schemaService = makeSchemaService() // Do all of this in a database transaction so anything that might need a connection has one. val startedImpl = initialiseDatabasePersistence(schemaService) { - val tokenizableServices = makeServices(schemaService) + val transactionStorage = makeTransactionStorage() + val stateLoader = StateLoaderImpl(transactionStorage) + val services = makeServices(schemaService, transactionStorage, stateLoader) saveOwnNodeInfo() - smm = StateMachineManager(services, - checkpointStorage, - serverThread, - database, - busyNodeLatch, - cordappLoader.appClassLoader) - - smm.tokenizableServices.addAll(tokenizableServices) - + smm = makeStateMachineManager() + val flowStarter = FlowStarterImpl(serverThread, smm) + val schedulerService = NodeSchedulerService(platformClock, this@AbstractNode.database, flowStarter, stateLoader, unfinishedSchedules = busyNodeLatch, serverThread = serverThread) if (serverThread is ExecutorService) { runOnStop += { // We wait here, even though any in-flight messages should have been drained away because the @@ -221,48 +221,60 @@ abstract class AbstractNode(config: NodeConfiguration, MoreExecutors.shutdownAndAwaitTermination(serverThread as ExecutorService, 50, SECONDS) } } - - makeVaultObservers() - - val rpcOps = makeRPCOps() + makeVaultObservers(schedulerService) + val rpcOps = makeRPCOps(flowStarter) startMessagingService(rpcOps) installCoreFlows() - - installCordaServices() + val cordaServices = installCordaServices(flowStarter) + tokenizableServices = services + cordaServices + schedulerService registerCordappFlows() _services.rpcFlows += cordappLoader.cordapps.flatMap { it.rpcFlows } FlowLogicRefFactoryImpl.classloader = cordappLoader.appClassLoader runOnStop += network::stop - StartedNodeImpl(this, _services, info, checkpointStorage, smm, attachments, inNodeNetworkMapService, network, database, rpcOps) + StartedNodeImpl(this, _services, info, checkpointStorage, smm, attachments, inNodeNetworkMapService, network, database, rpcOps, flowStarter, schedulerService) } // If we successfully loaded network data from database, we set this future to Unit. _nodeReadyFuture.captureLater(registerWithNetworkMapIfConfigured()) return startedImpl.apply { database.transaction { - smm.start() + smm.start(tokenizableServices) // Shut down the SMM so no Fibers are scheduled. runOnStop += { smm.stop(acceptableLiveFiberCountOnStop()) } - services.schedulerService.start() + schedulerService.start() } _started = this } } + protected open fun makeStateMachineManager(): StateMachineManager { + return StateMachineManagerImpl( + services, + checkpointStorage, + serverThread, + database, + busyNodeLatch, + cordappLoader.appClassLoader + ) + } + private class ServiceInstantiationException(cause: Throwable?) : CordaException("Service Instantiation Error", cause) - private fun installCordaServices() { + private fun installCordaServices(flowStarter: FlowStarter): List { val loadedServices = cordappLoader.cordapps.flatMap { it.services } - filterServicesToInstall(loadedServices).forEach { + return filterServicesToInstall(loadedServices).mapNotNull { try { - installCordaService(it) + installCordaService(flowStarter, it) } catch (e: NoSuchMethodException) { log.error("${it.name}, as a Corda service, must have a constructor with a single parameter of type " + ServiceHub::class.java.name) + null } catch (e: ServiceInstantiationException) { log.error("Corda service ${it.name} failed to instantiate", e.cause) + null } catch (e: Exception) { log.error("Unable to install Corda service ${it.name}", e) + null } } } @@ -274,8 +286,7 @@ abstract class AbstractNode(config: NodeConfiguration, require(customNotaryServiceList.size == 1) { "Attempting to install more than one notary service: ${customNotaryServiceList.joinToString()}" } - } - else return loadedServices - customNotaryServiceList + } else return loadedServices - customNotaryServiceList } return loadedServices } @@ -289,7 +300,7 @@ abstract class AbstractNode(config: NodeConfiguration, /** * This customizes the ServiceHub for each CordaService that is initiating flows */ - private class AppServiceHubImpl(val serviceHub: ServiceHubInternal) : AppServiceHub, ServiceHub by serviceHub { + private class AppServiceHubImpl(private val serviceHub: ServiceHub, private val flowStarter: FlowStarter) : AppServiceHub, ServiceHub by serviceHub { lateinit var serviceInstance: T override fun startTrackedFlow(flow: FlowLogic): FlowProgressHandle { val stateMachine = startFlowChecked(flow) @@ -305,38 +316,28 @@ abstract class AbstractNode(config: NodeConfiguration, return FlowHandleImpl(id = stateMachine.id, returnValue = stateMachine.resultFuture) } - private fun startFlowChecked(flow: FlowLogic): FlowStateMachineImpl { + private fun startFlowChecked(flow: FlowLogic): FlowStateMachine { val logicType = flow.javaClass require(logicType.isAnnotationPresent(StartableByService::class.java)) { "${logicType.name} was not designed for starting by a CordaService" } val currentUser = FlowInitiator.Service(serviceInstance.javaClass.name) - return serviceHub.startFlow(flow, currentUser) + return flowStarter.startFlow(flow, currentUser).getOrThrow() } override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is AppServiceHubImpl<*>) return false - - if (serviceHub != other.serviceHub) return false - if (serviceInstance != other.serviceInstance) return false - - return true + return serviceHub == other.serviceHub + && flowStarter == other.flowStarter + && serviceInstance == other.serviceInstance } - override fun hashCode(): Int { - var result = serviceHub.hashCode() - result = 31 * result + serviceInstance.hashCode() - return result - } + override fun hashCode() = Objects.hash(serviceHub, flowStarter, serviceInstance) } - /** - * Use this method to install your Corda services in your tests. This is automatically done by the node when it - * starts up for all classes it finds which are annotated with [CordaService]. - */ - fun installCordaService(serviceClass: Class): T { + private fun installCordaService(flowStarter: FlowStarter, serviceClass: Class): T { serviceClass.requireAnnotation() val service = try { - val serviceContext = AppServiceHubImpl(services) + val serviceContext = AppServiceHubImpl(services, flowStarter) if (isNotaryService(serviceClass)) { check(myNotaryIdentity != null) { "Trying to install a notary service but no notary identity specified" } val constructor = serviceClass.getDeclaredConstructor(AppServiceHub::class.java, PublicKey::class.java).apply { isAccessible = true } @@ -357,7 +358,6 @@ abstract class AbstractNode(config: NodeConfiguration, throw ServiceInstantiationException(e.cause) } cordappServices.putInstance(serviceClass, service) - smm.tokenizableServices += service if (service is NotaryService) handleCustomNotaryService(service) @@ -365,6 +365,12 @@ abstract class AbstractNode(config: NodeConfiguration, return service } + fun findTokenizableService(clazz: Class): T? { + return tokenizableServices.firstOrNull { clazz.isAssignableFrom(it.javaClass) }?.let { uncheckedCast(it) } + } + + inline fun findTokenizableService() = findTokenizableService(T::class.java) + private fun handleCustomNotaryService(service: NotaryService) { runOnStop += service::stop service.start() @@ -467,19 +473,22 @@ abstract class AbstractNode(config: NodeConfiguration, * Builds node internal, advertised, and plugin services. * Returns a list of tokenizable services to be added to the serialisation context. */ - private fun makeServices(schemaService: SchemaService): MutableList { + private fun makeServices(schemaService: SchemaService, transactionStorage: WritableTransactionStorage, stateLoader: StateLoader): MutableList { checkpointStorage = DBCheckpointStorage() - val transactionStorage = makeTransactionStorage() val metrics = MetricRegistry() attachments = NodeAttachmentService(metrics) val cordappProvider = CordappProviderImpl(cordappLoader, attachments) - _services = ServiceHubInternalImpl(schemaService, transactionStorage, StateLoaderImpl(transactionStorage), MonitoringService(metrics), cordappProvider) + _services = ServiceHubInternalImpl(schemaService, transactionStorage, stateLoader, MonitoringService(metrics), cordappProvider) legalIdentity = obtainIdentity(notaryConfig = null) + // TODO We keep only notary identity as additional legalIdentity if we run it on a node . Multiple identities need more design thinking. + myNotaryIdentity = getNotaryIdentity() + allIdentities = listOf(legalIdentity, myNotaryIdentity).filterNotNull() network = makeMessagingService(legalIdentity) - info = makeInfo(legalIdentity) + val addresses = myAddresses() // TODO There is no support for multiple IP addresses yet. + info = NodeInfo(addresses, allIdentities, versionInfo.platformVersion, platformClock.instant().toEpochMilli()) val networkMapCache = services.networkMapCache val tokenizableServices = mutableListOf(attachments, network, services.vaultService, - services.keyManagementService, services.identityService, platformClock, services.schedulerService, + services.keyManagementService, services.identityService, platformClock, services.auditService, services.monitoringService, networkMapCache, services.schemaService, services.transactionVerifierService, services.validatedTransactions, services.contractUpgradeService, services, cordappProvider, this) @@ -489,19 +498,10 @@ abstract class AbstractNode(config: NodeConfiguration, protected open fun makeTransactionStorage(): WritableTransactionStorage = DBTransactionStorage() - private fun makeVaultObservers() { - VaultSoftLockManager(services.vaultService, smm) - ScheduledActivityObserver(services) - HibernateObserver(services.vaultService.rawUpdates, services.database.hibernateConfig) - } - - private fun makeInfo(legalIdentity: PartyAndCertificate): NodeInfo { - // TODO We keep only notary identity as additional legalIdentity if we run it on a node . Multiple identities need more design thinking. - myNotaryIdentity = getNotaryIdentity() - val allIdentitiesList = mutableListOf(legalIdentity) - myNotaryIdentity?.let { allIdentitiesList.add(it) } - val addresses = myAddresses() // TODO There is no support for multiple IP addresses yet. - return NodeInfo(addresses, allIdentitiesList, versionInfo.platformVersion, platformClock.instant().toEpochMilli()) + private fun makeVaultObservers(schedulerService: SchedulerService) { + VaultSoftLockManager.install(services.vaultService, smm) + ScheduledActivityObserver.install(services.vaultService, schedulerService) + HibernateObserver.install(services.vaultService.rawUpdates, database.hibernateConfig) } /** @@ -553,8 +553,16 @@ abstract class AbstractNode(config: NodeConfiguration, } } + private fun setupInNodeNetworkMapService(networkMapCache: NetworkMapCacheInternal) { + inNodeNetworkMapService = + if (configuration.networkMapService == null && !configuration.noNetworkMapServiceMode) + makeNetworkMapService(network, networkMapCache) + else + NullNetworkMapService + } + private fun makeNetworkServices(network: MessagingService, networkMapCache: NetworkMapCacheInternal, tokenizableServices: MutableList) { - inNodeNetworkMapService = if (configuration.networkMapService == null) makeNetworkMapService(network, networkMapCache) else NullNetworkMapService + setupInNodeNetworkMapService(networkMapCache) configuration.notary?.let { val notaryService = makeCoreNotaryService(it) tokenizableServices.add(notaryService) @@ -613,7 +621,7 @@ abstract class AbstractNode(config: NodeConfiguration, /** This is overriden by the mock node implementation to enable operation without any network map service */ protected open fun noNetworkMapConfigured(): CordaFuture { - if (services.networkMapCache.loadDBSuccess) { + if (services.networkMapCache.loadDBSuccess || configuration.noNetworkMapServiceMode) { return doneFuture(Unit) } else { // TODO: There should be a consistent approach to configuration error exceptions. @@ -664,17 +672,7 @@ abstract class AbstractNode(config: NodeConfiguration, val caCertificates: Array = listOf(legalIdentity.certificate, clientCa?.certificate?.cert) .filterNotNull() .toTypedArray() - val service = PersistentIdentityService(info.legalIdentitiesAndCerts, trustRoot = trustRoot, caCertificates = *caCertificates) - services.networkMapCache.allNodes.forEach { it.legalIdentitiesAndCerts.forEach { service.verifyAndRegisterIdentity(it) } } - services.networkMapCache.changed.subscribe { mapChange -> - // TODO how should we handle network map removal - if (mapChange is MapChange.Added) { - mapChange.node.legalIdentitiesAndCerts.forEach { - service.verifyAndRegisterIdentity(it) - } - } - } - return service + return PersistentIdentityService(allIdentities, trustRoot = trustRoot, caCertificates = *caCertificates) } protected abstract fun makeTransactionVerifierService(): TransactionVerifierService @@ -691,6 +689,7 @@ abstract class AbstractNode(config: NodeConfiguration, toRun() } runOnStop.clear() + _started = null } protected abstract fun makeMessagingService(legalIdentity: PartyAndCertificate): MessagingService @@ -758,6 +757,9 @@ abstract class AbstractNode(config: NodeConfiguration, } protected open fun generateKeyPair() = cryptoGenerateKeyPair() + protected open fun makeVaultService(keyManagementService: KeyManagementService, stateLoader: StateLoader): VaultServiceInternal { + return NodeVaultService(platformClock, keyManagementService, stateLoader, database.hibernateConfig) + } private inner class ServiceHubInternalImpl( override val schemaService: SchemaService, @@ -770,15 +772,14 @@ abstract class AbstractNode(config: NodeConfiguration, override val stateMachineRecordedTransactionMapping = DBTransactionMappingStorage() override val auditService = DummyAuditService() override val transactionVerifierService by lazy { makeTransactionVerifierService() } - override val networkMapCache by lazy { PersistentNetworkMapCache(this) } - override val vaultService by lazy { NodeVaultService(platformClock, keyManagementService, stateLoader, this@AbstractNode.database.hibernateConfig) } + override val networkMapCache by lazy { NetworkMapCacheImpl(PersistentNetworkMapCache(this@AbstractNode.database, this@AbstractNode.configuration), identityService) } + override val vaultService by lazy { makeVaultService(keyManagementService, stateLoader) } override val contractUpgradeService by lazy { ContractUpgradeServiceImpl() } // Place the long term identity key in the KMS. Eventually, this is likely going to be separated again because // the KMS is meant for derived temporary keys used in transactions, and we're not supposed to sign things with // the identity key. But the infrastructure to make that easy isn't here yet. override val keyManagementService by lazy { makeKeyManagementService(identityService) } - override val schedulerService by lazy { NodeSchedulerService(this, unfinishedSchedules = busyNodeLatch, serverThread = serverThread) } override val identityService by lazy { val trustStore = KeyStoreWrapper(configuration.trustStoreFile, configuration.trustStorePassword) val caKeyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword) @@ -798,10 +799,6 @@ abstract class AbstractNode(config: NodeConfiguration, return cordappServices.getInstance(type) ?: throw IllegalArgumentException("Corda service ${type.name} does not exist") } - override fun startFlow(logic: FlowLogic, flowInitiator: FlowInitiator, ourIdentity: Party?): FlowStateMachineImpl { - return serverThread.fetchFrom { smm.add(logic, flowInitiator, ourIdentity) } - } - override fun getFlowFactory(initiatingFlowClass: Class>): InitiatedFlowFactory<*>? { return flowFactories[initiatingFlowClass] } @@ -815,3 +812,9 @@ abstract class AbstractNode(config: NodeConfiguration, override fun jdbcSession(): Connection = database.createSession() } } + +internal class FlowStarterImpl(private val serverThread: AffinityExecutor, private val smm: StateMachineManager) : FlowStarter { + override fun startFlow(logic: FlowLogic, flowInitiator: FlowInitiator, ourIdentity: Party?): CordaFuture> { + return serverThread.fetchFrom { smm.startFlow(logic, flowInitiator, ourIdentity) } + } +} diff --git a/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt b/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt index ee685466d3..863835c25a 100644 --- a/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt +++ b/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt @@ -10,6 +10,7 @@ import net.corda.core.flows.StartableByRPC import net.corda.core.identity.AbstractParty import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party +import net.corda.core.internal.FlowStateMachine import net.corda.core.messaging.* import net.corda.core.node.NodeInfo import net.corda.core.node.services.NetworkMapCache @@ -18,11 +19,12 @@ import net.corda.core.node.services.vault.PageSpecification import net.corda.core.node.services.vault.QueryCriteria import net.corda.core.node.services.vault.Sort import net.corda.core.transactions.SignedTransaction +import net.corda.core.utilities.getOrThrow import net.corda.node.services.FlowPermissions.Companion.startFlowPermission +import net.corda.node.services.api.FlowStarter import net.corda.node.services.api.ServiceHubInternal import net.corda.node.services.messaging.getRpcContext import net.corda.node.services.messaging.requirePermission -import net.corda.node.services.statemachine.FlowStateMachineImpl import net.corda.node.services.statemachine.StateMachineManager import net.corda.node.utilities.CordaPersistence import rx.Observable @@ -37,7 +39,8 @@ import java.time.Instant class CordaRPCOpsImpl( private val services: ServiceHubInternal, private val smm: StateMachineManager, - private val database: CordaPersistence + private val database: CordaPersistence, + private val flowStarter: FlowStarter ) : CordaRPCOps { override fun networkMapSnapshot(): List { val (snapshot, updates) = networkMapFeed() @@ -92,7 +95,7 @@ class CordaRPCOpsImpl( return database.transaction { val (allStateMachines, changes) = smm.track() DataFeed( - allStateMachines.map { stateMachineInfoFromFlowLogic(it.logic) }, + allStateMachines.map { stateMachineInfoFromFlowLogic(it) }, changes.map { stateMachineUpdateFromStateMachineChange(it) } ) } @@ -144,13 +147,13 @@ class CordaRPCOpsImpl( return FlowHandleImpl(id = stateMachine.id, returnValue = stateMachine.resultFuture) } - private fun startFlow(logicType: Class>, args: Array): FlowStateMachineImpl { + private fun startFlow(logicType: Class>, args: Array): FlowStateMachine { require(logicType.isAnnotationPresent(StartableByRPC::class.java)) { "${logicType.name} was not designed for RPC" } val rpcContext = getRpcContext() rpcContext.requirePermission(startFlowPermission(logicType)) val currentUser = FlowInitiator.RPC(rpcContext.currentUser.username) // TODO RPC flows should have mapping user -> identity that should be resolved automatically on starting flow. - return services.invokeFlowAsync(logicType, currentUser, *args) + return flowStarter.invokeFlowAsync(logicType, currentUser, *args).getOrThrow() } override fun attachmentExists(id: SecureHash): Boolean { diff --git a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt index 3bb63727da..1a52cbcadc 100644 --- a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt +++ b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt @@ -84,6 +84,7 @@ open class NodeStartup(val args: Array) { exitProcess(1) } + logger.info("Node exiting successfully") exitProcess(0) } diff --git a/node/src/main/kotlin/net/corda/node/internal/StartedNode.kt b/node/src/main/kotlin/net/corda/node/internal/StartedNode.kt index e0320d9c62..71b80d5aea 100644 --- a/node/src/main/kotlin/net/corda/node/internal/StartedNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/StartedNode.kt @@ -7,9 +7,11 @@ import net.corda.core.flows.FlowLogic import net.corda.core.messaging.CordaRPCOps import net.corda.core.node.NodeInfo import net.corda.core.node.StateLoader +import net.corda.core.node.services.CordaService import net.corda.core.node.services.TransactionStorage +import net.corda.core.serialization.SerializeAsToken import net.corda.node.services.api.CheckpointStorage -import net.corda.node.services.api.ServiceHubInternal +import net.corda.node.services.api.StartedNodeServices import net.corda.node.services.messaging.MessagingService import net.corda.node.services.network.NetworkMapService import net.corda.node.services.persistence.NodeAttachmentService @@ -18,7 +20,7 @@ import net.corda.node.utilities.CordaPersistence interface StartedNode { val internals: N - val services: ServiceHubInternal + val services: StartedNodeServices val info: NodeInfo val checkpointStorage: CheckpointStorage val smm: StateMachineManager diff --git a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt index 5fdb7993a0..de12960304 100644 --- a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt +++ b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt @@ -16,9 +16,11 @@ import net.corda.core.messaging.StateMachineTransactionMapping import net.corda.core.node.NodeInfo import net.corda.core.node.ServiceHub import net.corda.core.node.services.NetworkMapCache +import net.corda.core.node.services.NetworkMapCacheBase import net.corda.core.node.services.TransactionStorage import net.corda.core.serialization.CordaSerializable import net.corda.core.transactions.SignedTransaction +import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.loggerFor import net.corda.node.internal.InitiatedFlowFactory import net.corda.node.internal.cordapp.CordappProviderInternal @@ -28,7 +30,8 @@ import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl import net.corda.node.services.statemachine.FlowStateMachineImpl import net.corda.node.utilities.CordaPersistence -interface NetworkMapCacheInternal : NetworkMapCache { +interface NetworkMapCacheInternal : NetworkMapCache, NetworkMapCacheBaseInternal +interface NetworkMapCacheBaseInternal : NetworkMapCacheBase { /** * Deregister from updates from the given map service. * @param network the network messaging service. @@ -84,7 +87,6 @@ interface ServiceHubInternal : ServiceHub { val monitoringService: MonitoringService val schemaService: SchemaService override val networkMapCache: NetworkMapCacheInternal - val schedulerService: SchedulerService val auditService: AuditService val rpcFlows: List>> val networkService: MessagingService @@ -109,18 +111,22 @@ interface ServiceHubInternal : ServiceHub { } } + fun getFlowFactory(initiatingFlowClass: Class>): InitiatedFlowFactory<*>? +} + +interface FlowStarter { /** * Starts an already constructed flow. Note that you must be on the server thread to call this method. [FlowInitiator] * defaults to [FlowInitiator.RPC] with username "Only For Testing". */ @VisibleForTesting - fun startFlow(logic: FlowLogic): FlowStateMachine = startFlow(logic, FlowInitiator.RPC("Only For Testing")) + fun startFlow(logic: FlowLogic): FlowStateMachine = startFlow(logic, FlowInitiator.RPC("Only For Testing")).getOrThrow() /** * Starts an already constructed flow. Note that you must be on the server thread to call this method. * @param flowInitiator indicates who started the flow, see: [FlowInitiator]. */ - fun startFlow(logic: FlowLogic, flowInitiator: FlowInitiator, ourIdentity: Party? = null): FlowStateMachineImpl + fun startFlow(logic: FlowLogic, flowInitiator: FlowInitiator, ourIdentity: Party? = null): CordaFuture> /** * Will check [logicType] and [args] against a whitelist and if acceptable then construct and initiate the flow. @@ -133,15 +139,14 @@ interface ServiceHubInternal : ServiceHub { fun invokeFlowAsync( logicType: Class>, flowInitiator: FlowInitiator, - vararg args: Any?): FlowStateMachineImpl { + vararg args: Any?): CordaFuture> { val logicRef = FlowLogicRefFactoryImpl.createForRPC(logicType, *args) val logic: FlowLogic = uncheckedCast(FlowLogicRefFactoryImpl.toFlowLogic(logicRef)) return startFlow(logic, flowInitiator, ourIdentity = null) } - - fun getFlowFactory(initiatingFlowClass: Class>): InitiatedFlowFactory<*>? } +interface StartedNodeServices : ServiceHubInternal, FlowStarter /** * Thread-safe storage of transactions. */ diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt index 7e52c0361c..88f2dd855e 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt @@ -20,6 +20,7 @@ interface NodeConfiguration : NodeSSLConfiguration { * service. */ val networkMapService: NetworkMapInfo? + val noNetworkMapServiceMode: Boolean val minimumPlatformVersion: Int val emailAddress: String val exportJMXto: String @@ -78,6 +79,7 @@ data class FullNodeConfiguration( override val database: Properties?, override val certificateSigningService: URL, override val networkMapService: NetworkMapInfo?, + override val noNetworkMapServiceMode: Boolean = false, override val minimumPlatformVersion: Int = 1, override val rpcUsers: List, override val verifierType: VerifierType, diff --git a/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt b/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt index 26a8322b80..85aa36a0cb 100644 --- a/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt +++ b/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt @@ -1,9 +1,8 @@ package net.corda.node.services.events import co.paralleluniverse.fibers.Suspendable -import co.paralleluniverse.strands.SettableFuture as QuasarSettableFuture import com.google.common.util.concurrent.ListenableFuture -import com.google.common.util.concurrent.SettableFuture as GuavaSettableFuture +import com.google.common.util.concurrent.SettableFuture import net.corda.core.contracts.SchedulableState import net.corda.core.contracts.ScheduledActivity import net.corda.core.contracts.ScheduledStateRef @@ -13,16 +12,19 @@ import net.corda.core.flows.FlowInitiator import net.corda.core.flows.FlowLogic import net.corda.core.internal.ThreadBox import net.corda.core.internal.VisibleForTesting +import net.corda.core.internal.concurrent.flatMap import net.corda.core.internal.until +import net.corda.core.node.StateLoader import net.corda.core.schemas.PersistentStateRef import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.utilities.loggerFor import net.corda.core.utilities.trace import net.corda.node.internal.MutableClock +import net.corda.node.services.api.FlowStarter import net.corda.node.services.api.SchedulerService -import net.corda.node.services.api.ServiceHubInternal import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl import net.corda.node.utilities.AffinityExecutor +import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.NODE_DATABASE_PREFIX import net.corda.node.utilities.PersistentMap import org.apache.activemq.artemis.utils.ReusableLatch @@ -34,6 +36,8 @@ import javax.annotation.concurrent.ThreadSafe import javax.persistence.Column import javax.persistence.EmbeddedId import javax.persistence.Entity +import co.paralleluniverse.strands.SettableFuture as QuasarSettableFuture +import com.google.common.util.concurrent.SettableFuture as GuavaSettableFuture /** * A first pass of a simple [SchedulerService] that works with [MutableClock]s for testing, demonstrations and simulations @@ -47,12 +51,14 @@ import javax.persistence.Entity * in the nodes, maybe we can consider multiple activities and whether the activities have been completed or not, * but that starts to sound a lot like off-ledger state. * - * @param services Core node services. * @param schedulerTimerExecutor The executor the scheduler blocks on waiting for the clock to advance to the next * activity. Only replace this for unit testing purposes. This is not the executor the [FlowLogic] is launched on. */ @ThreadSafe -class NodeSchedulerService(private val services: ServiceHubInternal, +class NodeSchedulerService(private val clock: Clock, + private val database: CordaPersistence, + private val flowStarter: FlowStarter, + private val stateLoader: StateLoader, private val schedulerTimerExecutor: Executor = Executors.newSingleThreadExecutor(), private val unfinishedSchedules: ReusableLatch = ReusableLatch(), private val serverThread: AffinityExecutor) @@ -108,8 +114,8 @@ class NodeSchedulerService(private val services: ServiceHubInternal, toPersistentEntityKey = { PersistentStateRef(it.txhash.toString(), it.index) }, fromPersistentEntity = { //TODO null check will become obsolete after making DB/JPA columns not nullable - var txId = it.output.txId ?: throw IllegalStateException("DB returned null SecureHash transactionId") - var index = it.output.index ?: throw IllegalStateException("DB returned null SecureHash index") + val txId = it.output.txId ?: throw IllegalStateException("DB returned null SecureHash transactionId") + val index = it.output.index ?: throw IllegalStateException("DB returned null SecureHash index") Pair(StateRef(SecureHash.parse(txId), index), ScheduledStateRef(StateRef(SecureHash.parse(txId), index), it.scheduledAt)) }, @@ -172,7 +178,7 @@ class NodeSchedulerService(private val services: ServiceHubInternal, mutex.locked { val previousState = scheduledStates[action.ref] scheduledStates[action.ref] = action - var previousEarliest = scheduledStatesQueue.peek() + val previousEarliest = scheduledStatesQueue.peek() scheduledStatesQueue.remove(previousState) scheduledStatesQueue.add(action) if (previousState == null) { @@ -211,7 +217,7 @@ class NodeSchedulerService(private val services: ServiceHubInternal, * cancelled then we run the scheduled action. Finally we remove that action from the scheduled actions and * recompute the next scheduled action. */ - internal fun rescheduleWakeUp() { + private fun rescheduleWakeUp() { // Note, we already have the mutex but we need the scope again here val (scheduledState, ourRescheduledFuture) = mutex.alreadyLocked { rescheduled?.cancel(false) @@ -223,7 +229,7 @@ class NodeSchedulerService(private val services: ServiceHubInternal, log.trace { "Scheduling as next $scheduledState" } // This will block the scheduler single thread until the scheduled time (returns false) OR // the Future is cancelled due to rescheduling (returns true). - if (!awaitWithDeadline(services.clock, scheduledState.scheduledAt, ourRescheduledFuture)) { + if (!awaitWithDeadline(clock, scheduledState.scheduledAt, ourRescheduledFuture)) { log.trace { "Invoking as next $scheduledState" } onTimeReached(scheduledState) } else { @@ -237,11 +243,11 @@ class NodeSchedulerService(private val services: ServiceHubInternal, serverThread.execute { var flowName: String? = "(unknown)" try { - services.database.transaction { + database.transaction { val scheduledFlow = getScheduledFlow(scheduledState) if (scheduledFlow != null) { flowName = scheduledFlow.javaClass.name - val future = services.startFlow(scheduledFlow, FlowInitiator.Scheduled(scheduledState)).resultFuture + val future = flowStarter.startFlow(scheduledFlow, FlowInitiator.Scheduled(scheduledState)).flatMap { it.resultFuture } future.then { unfinishedSchedules.countDown() } @@ -265,9 +271,9 @@ class NodeSchedulerService(private val services: ServiceHubInternal, unfinishedSchedules.countDown() scheduledStates.remove(scheduledState.ref) scheduledStatesQueue.remove(scheduledState) - } else if (scheduledActivity.scheduledAt.isAfter(services.clock.instant())) { + } else if (scheduledActivity.scheduledAt.isAfter(clock.instant())) { log.info("Scheduled state $scheduledState has rescheduled to ${scheduledActivity.scheduledAt}.") - var newState = ScheduledStateRef(scheduledState.ref, scheduledActivity.scheduledAt) + val newState = ScheduledStateRef(scheduledState.ref, scheduledActivity.scheduledAt) scheduledStates[scheduledState.ref] = newState scheduledStatesQueue.remove(scheduledState) scheduledStatesQueue.add(newState) @@ -286,7 +292,7 @@ class NodeSchedulerService(private val services: ServiceHubInternal, } private fun getScheduledActivity(scheduledState: ScheduledStateRef): ScheduledActivity? { - val txState = services.loadState(scheduledState.ref) + val txState = stateLoader.loadState(scheduledState.ref) val state = txState.data as SchedulableState return try { // This can throw as running contract code. diff --git a/node/src/main/kotlin/net/corda/node/services/events/ScheduledActivityObserver.kt b/node/src/main/kotlin/net/corda/node/services/events/ScheduledActivityObserver.kt index 3509fa2d34..3020a9e528 100644 --- a/node/src/main/kotlin/net/corda/node/services/events/ScheduledActivityObserver.kt +++ b/node/src/main/kotlin/net/corda/node/services/events/ScheduledActivityObserver.kt @@ -4,18 +4,28 @@ import net.corda.core.contracts.ContractState import net.corda.core.contracts.SchedulableState import net.corda.core.contracts.ScheduledStateRef import net.corda.core.contracts.StateAndRef -import net.corda.node.services.api.ServiceHubInternal +import net.corda.core.node.services.VaultService +import net.corda.node.services.api.SchedulerService import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl /** * This observes the vault and schedules and unschedules activities appropriately based on state production and * consumption. */ -class ScheduledActivityObserver(val services: ServiceHubInternal) { - init { - services.vaultService.rawUpdates.subscribe { (consumed, produced) -> - consumed.forEach { services.schedulerService.unscheduleStateActivity(it.ref) } - produced.forEach { scheduleStateActivity(it) } +class ScheduledActivityObserver private constructor(private val schedulerService: SchedulerService) { + companion object { + @JvmStatic + fun install(vaultService: VaultService, schedulerService: SchedulerService) { + val observer = ScheduledActivityObserver(schedulerService) + vaultService.rawUpdates.subscribe { (consumed, produced) -> + consumed.forEach { schedulerService.unscheduleStateActivity(it.ref) } + produced.forEach { observer.scheduleStateActivity(it) } + } + } + + // TODO: Beware we are calling dynamically loaded contract code inside here. + private inline fun sandbox(code: () -> T?): T? { + return code() } } @@ -23,12 +33,7 @@ class ScheduledActivityObserver(val services: ServiceHubInternal) { val producedState = produced.state.data if (producedState is SchedulableState) { val scheduledAt = sandbox { producedState.nextScheduledActivity(produced.ref, FlowLogicRefFactoryImpl)?.scheduledAt } ?: return - services.schedulerService.scheduleStateActivity(ScheduledStateRef(produced.ref, scheduledAt)) + schedulerService.scheduleStateActivity(ScheduledStateRef(produced.ref, scheduledAt)) } } - - // TODO: Beware we are calling dynamically loaded contract code inside here. - private inline fun sandbox(code: () -> T?): T? { - return code() - } } diff --git a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt index c948f27732..18d074cd68 100644 --- a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt +++ b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt @@ -11,8 +11,8 @@ import net.corda.core.node.services.UnknownAnonymousPartyException import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.utilities.debug import net.corda.core.utilities.loggerFor -import net.corda.core.utilities.MAX_HASH_HEX_SIZE import net.corda.node.utilities.AppendOnlyPersistentMap +import net.corda.node.utilities.MAX_HASH_HEX_SIZE import net.corda.node.utilities.NODE_DATABASE_PREFIX import org.bouncycastle.cert.X509CertificateHolder import java.io.ByteArrayInputStream diff --git a/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt b/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt index f885383bc8..1c19ce0acb 100644 --- a/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt +++ b/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt @@ -4,12 +4,9 @@ import net.corda.core.crypto.* import net.corda.core.identity.PartyAndCertificate import net.corda.core.node.services.IdentityService import net.corda.core.node.services.KeyManagementService -import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.SingletonSerializeAsToken -import net.corda.core.serialization.deserialize -import net.corda.core.serialization.serialize -import net.corda.core.utilities.MAX_HASH_HEX_SIZE import net.corda.node.utilities.AppendOnlyPersistentMap +import net.corda.node.utilities.MAX_HASH_HEX_SIZE import net.corda.node.utilities.NODE_DATABASE_PREFIX import org.bouncycastle.operator.ContentSigner import java.security.KeyPair diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt b/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt index a708166239..1697b49047 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt @@ -83,9 +83,40 @@ interface MessagingService { * to send an ACK message back. * * @param retryId if provided the message will be scheduled for redelivery until [cancelRedelivery] is called for this id. - * Note that this feature should only be used when the target is an idempotent distributed service, e.g. a notary. + * Note that this feature should only be used when the target is an idempotent distributed service, e.g. a notary. + * @param sequenceKey an object that may be used to enable a parallel [MessagingService] implementation. Two + * subsequent send()s with the same [sequenceKey] (up to equality) are guaranteed to be delivered in the same + * sequence the send()s were called. By default this is chosen conservatively to be [target]. + * @param acknowledgementHandler if non-null this handler will be called once the sent message has been committed by + * the broker. Note that if specified [send] itself may return earlier than the commit. */ - fun send(message: Message, target: MessageRecipients, retryId: Long? = null) + fun send( + message: Message, + target: MessageRecipients, + retryId: Long? = null, + sequenceKey: Any = target, + acknowledgementHandler: (() -> Unit)? = null + ) + + /** A message with a target and sequenceKey specified. */ + data class AddressedMessage( + val message: Message, + val target: MessageRecipients, + val retryId: Long? = null, + val sequenceKey: Any = target + ) + + /** + * Sends a list of messages to the specified recipients. This function allows for an efficient batching + * implementation. + * + * @param addressedMessages The list of messages together with the recipients, retry ids and sequence keys. + * @param retryId if provided the message will be scheduled for redelivery until [cancelRedelivery] is called for this id. + * Note that this feature should only be used when the target is an idempotent distributed service, e.g. a notary. + * @param acknowledgementHandler if non-null this handler will be called once all sent messages have been committed + * by the broker. Note that if specified [send] itself may return earlier than the commit. + */ + fun send(addressedMessages: List, acknowledgementHandler: (() -> Unit)? = null) /** Cancels the scheduled message redelivery for the specified [retryId] */ fun cancelRedelivery(retryId: Long) diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt b/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt index 5a9cd04730..401709f811 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt @@ -22,7 +22,7 @@ import net.corda.node.services.RPCUserService import net.corda.node.services.api.MonitoringService import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.VerifierType -import net.corda.node.services.statemachine.StateMachineManager +import net.corda.node.services.statemachine.StateMachineManagerImpl import net.corda.node.services.transactions.InMemoryTransactionVerifierService import net.corda.node.services.transactions.OutOfProcessTransactionVerifierService import net.corda.node.utilities.* @@ -485,7 +485,7 @@ class NodeMessagingClient(override val config: NodeConfiguration, } } - override fun send(message: Message, target: MessageRecipients, retryId: Long?) { + override fun send(message: Message, target: MessageRecipients, retryId: Long?, sequenceKey: Any, acknowledgementHandler: (() -> Unit)?) { // We have to perform sending on a different thread pool, since using the same pool for messaging and // fibers leads to Netty buffer memory leaks, caused by both Netty and Quasar fiddling with thread-locals. messagingExecutor.fetchFrom { @@ -502,7 +502,7 @@ class NodeMessagingClient(override val config: NodeConfiguration, putStringProperty(HDR_DUPLICATE_DETECTION_ID, SimpleString(message.uniqueMessageId.toString())) // For demo purposes - if set then add a delay to messages in order to demonstrate that the flows are doing as intended - if (amqDelayMillis > 0 && message.topicSession.topic == StateMachineManager.sessionTopic.topic) { + if (amqDelayMillis > 0 && message.topicSession.topic == StateMachineManagerImpl.sessionTopic.topic) { putLongProperty(HDR_SCHEDULED_DELIVERY_TIME, System.currentTimeMillis() + amqDelayMillis) } } @@ -523,6 +523,14 @@ class NodeMessagingClient(override val config: NodeConfiguration, } } } + acknowledgementHandler?.invoke() + } + + override fun send(addressedMessages: List, acknowledgementHandler: (() -> Unit)?) { + for ((message, target, retryId, sequenceKey) in addressedMessages) { + send(message, target, retryId, sequenceKey, null) + } + acknowledgementHandler?.invoke() } private fun sendWithRetry(retryCount: Int, address: String, message: ClientMessage, retryId: Long) { diff --git a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt new file mode 100644 index 0000000000..2aba89084d --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt @@ -0,0 +1,70 @@ +package net.corda.node.services.network + +import com.fasterxml.jackson.databind.ObjectMapper +import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.SignedData +import net.corda.core.node.NodeInfo +import net.corda.core.serialization.deserialize +import net.corda.core.serialization.serialize +import java.net.HttpURLConnection +import java.net.URL + +interface NetworkMapClient { + /** + * Publish node info to network map service. + */ + fun publish(signedNodeInfo: SignedData) + + /** + * Retrieve [NetworkMap] from the network map service containing list of node info hashes and network parameter hash. + */ + // TODO: Use NetworkMap object when available. + fun getNetworkMap(): List + + /** + * Retrieve [NodeInfo] from network map service using the node info hash. + */ + fun getNodeInfo(nodeInfoHash: SecureHash): NodeInfo? + + // TODO: Implement getNetworkParameter when its available. + //fun getNetworkParameter(networkParameterHash: SecureHash): NetworkParameter +} + +class HTTPNetworkMapClient(private val networkMapUrl: String) : NetworkMapClient { + override fun publish(signedNodeInfo: SignedData) { + val publishURL = URL("$networkMapUrl/publish") + val conn = publishURL.openConnection() as HttpURLConnection + conn.doOutput = true + conn.requestMethod = "POST" + conn.setRequestProperty("Content-Type", "application/octet-stream") + conn.outputStream.write(signedNodeInfo.serialize().bytes) + when (conn.responseCode) { + HttpURLConnection.HTTP_OK -> return + HttpURLConnection.HTTP_UNAUTHORIZED -> throw IllegalArgumentException(conn.errorStream.bufferedReader().readLine()) + else -> throw IllegalArgumentException("Unexpected response code ${conn.responseCode}, response error message: '${conn.errorStream.bufferedReader().readLines()}'") + } + } + + override fun getNetworkMap(): List { + val conn = URL(networkMapUrl).openConnection() as HttpURLConnection + + return when (conn.responseCode) { + HttpURLConnection.HTTP_OK -> { + val response = conn.inputStream.bufferedReader().use { it.readLine() } + ObjectMapper().readValue(response, List::class.java).map { SecureHash.parse(it.toString()) } + } + else -> throw IllegalArgumentException("Unexpected response code ${conn.responseCode}, response error message: '${conn.errorStream.bufferedReader().readLines()}'") + } + } + + override fun getNodeInfo(nodeInfoHash: SecureHash): NodeInfo? { + val nodeInfoURL = URL("$networkMapUrl/$nodeInfoHash") + val conn = nodeInfoURL.openConnection() as HttpURLConnection + + return when (conn.responseCode) { + HttpURLConnection.HTTP_OK -> conn.inputStream.readBytes().deserialize() + HttpURLConnection.HTTP_NOT_FOUND -> null + else -> throw IllegalArgumentException("Unexpected response code ${conn.responseCode}, response error message: '${conn.errorStream.bufferedReader().readLines()}'") + } + } +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt b/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt index 65ef358c6d..4bd9c72cb4 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt @@ -9,6 +9,7 @@ import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.utilities.loggerFor import net.corda.core.utilities.seconds +import net.corda.nodeapi.NodeInfoFilesCopier import rx.Observable import rx.Scheduler import rx.schedulers.Schedulers @@ -55,7 +56,8 @@ class NodeInfoWatcher(private val nodePath: Path, val serializedBytes = nodeInfo.serialize() val regSig = keyManager.sign(serializedBytes.bytes, nodeInfo.legalIdentities.first().owningKey) val signedData = SignedData(serializedBytes, regSig) - signedData.serialize().open().copyTo(path / "nodeInfo-${serializedBytes.hash}") + signedData.serialize().open().copyTo( + path / "${NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX}${serializedBytes.hash}") } catch (e: Exception) { logger.warn("Couldn't write node info to file", e) } diff --git a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt index 544d9257d6..d242f3869f 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt @@ -14,6 +14,7 @@ import net.corda.core.internal.concurrent.openFuture import net.corda.core.messaging.DataFeed import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.node.NodeInfo +import net.corda.core.node.services.IdentityService import net.corda.core.node.services.NetworkMapCache.MapChange import net.corda.core.node.services.NotaryService import net.corda.core.node.services.PartyInfo @@ -27,12 +28,14 @@ import net.corda.core.utilities.loggerFor import net.corda.core.utilities.toBase58String import net.corda.node.services.api.NetworkCacheException import net.corda.node.services.api.NetworkMapCacheInternal -import net.corda.node.services.api.ServiceHubInternal +import net.corda.node.services.api.NetworkMapCacheBaseInternal +import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.messaging.MessagingService import net.corda.node.services.messaging.createMessage import net.corda.node.services.messaging.sendRequest import net.corda.node.services.network.NetworkMapService.FetchMapResponse import net.corda.node.services.network.NetworkMapService.SubscribeResponse +import net.corda.node.utilities.* import net.corda.node.utilities.AddOrRemove import net.corda.node.utilities.bufferUntilDatabaseCommit import net.corda.node.utilities.wrapWithDatabaseTransaction @@ -45,15 +48,32 @@ import java.util.* import javax.annotation.concurrent.ThreadSafe import kotlin.collections.HashMap +class NetworkMapCacheImpl(networkMapCacheBase: NetworkMapCacheBaseInternal, private val identityService: IdentityService) : NetworkMapCacheBaseInternal by networkMapCacheBase, NetworkMapCacheInternal { + init { + networkMapCacheBase.allNodes.forEach { it.legalIdentitiesAndCerts.forEach { identityService.verifyAndRegisterIdentity(it) } } + networkMapCacheBase.changed.subscribe { mapChange -> + // TODO how should we handle network map removal + if (mapChange is MapChange.Added) { + mapChange.node.legalIdentitiesAndCerts.forEach { + identityService.verifyAndRegisterIdentity(it) + } + } + } + } + + override fun getNodeByLegalIdentity(party: AbstractParty): NodeInfo? { + val wellKnownParty = identityService.wellKnownPartyFromAnonymous(party) + return wellKnownParty?.let { + getNodesByLegalIdentityKey(it.owningKey).firstOrNull() + } + } +} + /** * Extremely simple in-memory cache of the network map. - * - * @param serviceHub an optional service hub from which we'll take the identity service. We take a service hub rather - * than the identity service directly, as this avoids problems with service start sequence (network map cache - * and identity services depend on each other). Should always be provided except for unit test cases. */ @ThreadSafe -open class PersistentNetworkMapCache(private val serviceHub: ServiceHubInternal) : SingletonSerializeAsToken(), NetworkMapCacheInternal { +open class PersistentNetworkMapCache(private val database: CordaPersistence, configuration: NodeConfiguration) : SingletonSerializeAsToken(), NetworkMapCacheBaseInternal { companion object { val logger = loggerFor() } @@ -89,12 +109,12 @@ open class PersistentNetworkMapCache(private val serviceHub: ServiceHubInternal) .sortedBy { it.name.toString() } } - private val nodeInfoSerializer = NodeInfoWatcher(serviceHub.configuration.baseDirectory, - serviceHub.configuration.additionalNodeInfoPollingFrequencyMsec) + private val nodeInfoSerializer = NodeInfoWatcher(configuration.baseDirectory, + configuration.additionalNodeInfoPollingFrequencyMsec) init { loadFromFiles() - serviceHub.database.transaction { loadFromDB(session) } + database.transaction { loadFromDB(session) } } private fun loadFromFiles() { @@ -103,7 +123,7 @@ open class PersistentNetworkMapCache(private val serviceHub: ServiceHubInternal) } override fun getPartyInfo(party: Party): PartyInfo? { - val nodes = serviceHub.database.transaction { queryByIdentityKey(session, party.owningKey) } + val nodes = database.transaction { queryByIdentityKey(session, party.owningKey) } if (nodes.size == 1 && nodes[0].isLegalIdentity(party)) { return PartyInfo.SingleNode(party, nodes[0].addresses) } @@ -118,20 +138,13 @@ open class PersistentNetworkMapCache(private val serviceHub: ServiceHubInternal) } override fun getNodeByLegalName(name: CordaX500Name): NodeInfo? = getNodesByLegalName(name).firstOrNull() - override fun getNodesByLegalName(name: CordaX500Name): List = serviceHub.database.transaction { queryByLegalName(session, name) } + override fun getNodesByLegalName(name: CordaX500Name): List = database.transaction { queryByLegalName(session, name) } override fun getNodesByLegalIdentityKey(identityKey: PublicKey): List = - serviceHub.database.transaction { queryByIdentityKey(session, identityKey) } + database.transaction { queryByIdentityKey(session, identityKey) } - override fun getNodeByLegalIdentity(party: AbstractParty): NodeInfo? { - val wellKnownParty = serviceHub.identityService.wellKnownPartyFromAnonymous(party) - return wellKnownParty?.let { - getNodesByLegalIdentityKey(it.owningKey).firstOrNull() - } - } + override fun getNodeByAddress(address: NetworkHostAndPort): NodeInfo? = database.transaction { queryByAddress(session, address) } - override fun getNodeByAddress(address: NetworkHostAndPort): NodeInfo? = serviceHub.database.transaction { queryByAddress(session, address) } - - override fun getPeerCertificateByLegalName(name: CordaX500Name): PartyAndCertificate? = serviceHub.database.transaction { queryIdentityByLegalName(session, name) } + override fun getPeerCertificateByLegalName(name: CordaX500Name): PartyAndCertificate? = database.transaction { queryIdentityByLegalName(session, name) } override fun track(): DataFeed, MapChange> { synchronized(_changed) { @@ -183,13 +196,13 @@ open class PersistentNetworkMapCache(private val serviceHub: ServiceHubInternal) val previousNode = registeredNodes.put(node.legalIdentities.first().owningKey, node) // TODO hack... we left the first one as special one if (previousNode == null) { logger.info("No previous node found") - serviceHub.database.transaction { + database.transaction { updateInfoDB(node) changePublisher.onNext(MapChange.Added(node)) } } else if (previousNode != node) { logger.info("Previous node was found as: $previousNode") - serviceHub.database.transaction { + database.transaction { updateInfoDB(node) changePublisher.onNext(MapChange.Modified(node, previousNode)) } @@ -204,7 +217,7 @@ open class PersistentNetworkMapCache(private val serviceHub: ServiceHubInternal) logger.info("Removing node with info: $node") synchronized(_changed) { registeredNodes.remove(node.legalIdentities.first().owningKey) - serviceHub.database.transaction { + database.transaction { removeInfoDB(session, node) changePublisher.onNext(MapChange.Removed(node)) } @@ -239,7 +252,7 @@ open class PersistentNetworkMapCache(private val serviceHub: ServiceHubInternal) } override val allNodes: List - get() = serviceHub.database.transaction { + get() = database.transaction { getAllInfos(session).map { it.toNodeInfo() } } @@ -288,8 +301,8 @@ open class PersistentNetworkMapCache(private val serviceHub: ServiceHubInternal) private fun updateInfoDB(nodeInfo: NodeInfo) { // TODO Temporary workaround to force isolated transaction (otherwise it causes race conditions when processing // network map registration on network map node) - serviceHub.database.dataSource.connection.use { - val session = serviceHub.database.entityManagerFactory.withOptions().connection(it.apply { + database.dataSource.connection.use { + val session = database.entityManagerFactory.withOptions().connection(it.apply { transactionIsolation = 1 }).openSession() session.use { @@ -370,7 +383,7 @@ open class PersistentNetworkMapCache(private val serviceHub: ServiceHubInternal) } override fun clearNetworkMapCache() { - serviceHub.database.transaction { + database.transaction { val result = getAllInfos(session) for (nodeInfo in result) session.remove(nodeInfo) } diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/HibernateConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/persistence/HibernateConfiguration.kt index 8fb0023b3d..589684ec01 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/HibernateConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/HibernateConfiguration.kt @@ -93,6 +93,7 @@ class HibernateConfiguration(val schemaService: SchemaService, private val datab // during schema creation / update. class NodeDatabaseConnectionProvider : ConnectionProvider { override fun closeConnection(conn: Connection) { + conn.autoCommit = false val tx = DatabaseTransactionManager.current() tx.commit() tx.close() diff --git a/node/src/main/kotlin/net/corda/node/services/schema/HibernateObserver.kt b/node/src/main/kotlin/net/corda/node/services/schema/HibernateObserver.kt index 45a3334ef1..2babcc1d70 100644 --- a/node/src/main/kotlin/net/corda/node/services/schema/HibernateObserver.kt +++ b/node/src/main/kotlin/net/corda/node/services/schema/HibernateObserver.kt @@ -3,6 +3,7 @@ package net.corda.node.services.schema import net.corda.core.contracts.ContractState import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateRef +import net.corda.core.internal.VisibleForTesting import net.corda.core.node.services.Vault import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentStateRef @@ -17,14 +18,15 @@ import rx.Observable * A vault observer that extracts Object Relational Mappings for contract states that support it, and persists them with Hibernate. */ // TODO: Manage version evolution of the schemas via additional tooling. -class HibernateObserver(vaultUpdates: Observable>, val config: HibernateConfiguration) { - +class HibernateObserver private constructor(private val config: HibernateConfiguration) { companion object { - val logger = loggerFor() - } - - init { - vaultUpdates.subscribe { persist(it.produced) } + private val log = loggerFor() + @JvmStatic + fun install(vaultUpdates: Observable>, config: HibernateConfiguration): HibernateObserver { + val observer = HibernateObserver(config) + vaultUpdates.subscribe { observer.persist(it.produced) } + return observer + } } private fun persist(produced: Set>) { @@ -33,11 +35,12 @@ class HibernateObserver(vaultUpdates: Observable>, v private fun persistState(stateAndRef: StateAndRef) { val state = stateAndRef.state.data - logger.debug { "Asked to persist state ${stateAndRef.ref}" } + log.debug { "Asked to persist state ${stateAndRef.ref}" } config.schemaService.selectSchemas(state).forEach { persistStateWithSchema(state, stateAndRef.ref, it) } } - fun persistStateWithSchema(state: ContractState, stateRef: StateRef, schema: MappedSchema) { + @VisibleForTesting + internal fun persistStateWithSchema(state: ContractState, stateRef: StateRef, schema: MappedSchema) { val sessionFactory = config.sessionFactoryForSchemas(setOf(schema)) val session = sessionFactory.withOptions(). connection(DatabaseTransactionManager.current().connection). diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowLogicRefFactoryImpl.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowLogicRefFactoryImpl.kt index a3c36fa3a4..f0f10ebed4 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowLogicRefFactoryImpl.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowLogicRefFactoryImpl.kt @@ -2,7 +2,6 @@ package net.corda.node.services.statemachine import net.corda.core.internal.VisibleForTesting import com.google.common.primitives.Primitives -import net.corda.core.cordapp.CordappContext import net.corda.core.flows.* import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.SingletonSerializeAsToken @@ -34,7 +33,7 @@ data class FlowLogicRefImpl internal constructor(val flowLogicClassName: String, */ object FlowLogicRefFactoryImpl : SingletonSerializeAsToken(), FlowLogicRefFactory { // TODO: Replace with a per app classloader/cordapp provider/cordapp loader - this will do for now - var classloader = javaClass.classLoader + var classloader: ClassLoader = javaClass.classLoader override fun create(flowClass: Class>, vararg args: Any?): FlowLogicRef { if (!flowClass.isAnnotationPresent(SchedulableFlow::class.java)) { diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowSessionImpl.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowSessionImpl.kt index 044fdc0dbe..054d7c5d01 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowSessionImpl.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowSessionImpl.kt @@ -16,28 +16,46 @@ class FlowSessionImpl( internal lateinit var sessionFlow: FlowLogic<*> @Suspendable - override fun getCounterpartyFlowInfo(): FlowInfo { - return stateMachine.getFlowInfo(counterparty, sessionFlow) + override fun getCounterpartyFlowInfo(maySkipCheckpoint: Boolean): FlowInfo { + return stateMachine.getFlowInfo(counterparty, sessionFlow, maySkipCheckpoint) } @Suspendable - override fun sendAndReceive(receiveType: Class, payload: Any): UntrustworthyData { - return stateMachine.sendAndReceive(receiveType, counterparty, payload, sessionFlow) + override fun getCounterpartyFlowInfo() = getCounterpartyFlowInfo(maySkipCheckpoint = false) + + @Suspendable + override fun sendAndReceive( + receiveType: Class, + payload: Any, + maySkipCheckpoint: Boolean + ): UntrustworthyData { + return stateMachine.sendAndReceive( + receiveType, + counterparty, + payload, + sessionFlow, + retrySend = false, + maySkipCheckpoint = maySkipCheckpoint + ) } @Suspendable - internal fun sendAndReceiveWithRetry(receiveType: Class, payload: Any): UntrustworthyData { - return stateMachine.sendAndReceive(receiveType, counterparty, payload, sessionFlow, retrySend = true) + override fun sendAndReceive(receiveType: Class, payload: Any) = sendAndReceive(receiveType, payload, maySkipCheckpoint = false) + + @Suspendable + override fun receive(receiveType: Class, maySkipCheckpoint: Boolean): UntrustworthyData { + return stateMachine.receive(receiveType, counterparty, sessionFlow, maySkipCheckpoint) } @Suspendable - override fun receive(receiveType: Class): UntrustworthyData { - return stateMachine.receive(receiveType, counterparty, sessionFlow) + override fun receive(receiveType: Class) = receive(receiveType, maySkipCheckpoint = false) + + @Suspendable + override fun send(payload: Any, maySkipCheckpoint: Boolean) { + return stateMachine.send(counterparty, payload, sessionFlow, maySkipCheckpoint) } @Suspendable - override fun send(payload: Any) { - return stateMachine.send(counterparty, payload, sessionFlow) - } + override fun send(payload: Any) = send(payload, maySkipCheckpoint = false) } diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStateMachineImpl.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStateMachineImpl.kt index 89f0bf6dc6..57cc99378a 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStateMachineImpl.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStateMachineImpl.kt @@ -68,12 +68,10 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, * is not necessary. */ override val logger: Logger = LoggerFactory.getLogger("net.corda.flow.$id") - - @Transient private var _resultFuture: OpenFuture? = openFuture() + @Transient private var resultFutureTransient: OpenFuture? = openFuture() + private val _resultFuture get() = resultFutureTransient ?: openFuture().also { resultFutureTransient = it } /** This future will complete when the call method returns. */ - override val resultFuture: CordaFuture - get() = _resultFuture ?: openFuture().also { _resultFuture = it } - + override val resultFuture: CordaFuture get() = _resultFuture // This state IS serialised, as we need it to know what the fiber is waiting for. internal val openSessions = HashMap, Party>, FlowSessionInternal>() internal var waitingForResponse: WaitingRequest? = null @@ -115,7 +113,7 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, recordDuration(startTime) // This is to prevent actionOnEnd being called twice if it throws an exception actionOnEnd(Try.Success(result), false) - _resultFuture?.set(result) + _resultFuture.set(result) logic.progressTracker?.currentStep = ProgressTracker.DONE logger.debug { "Flow finished with result ${result.toString().abbreviate(300)}" } } @@ -128,7 +126,7 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, private fun processException(exception: Throwable, propagated: Boolean) { actionOnEnd(Try.Failure(exception), propagated) - _resultFuture?.setException(exception) + _resultFuture.setException(exception) logic.progressTracker?.endWithError(exception) } @@ -165,7 +163,7 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, } @Suspendable - override fun getFlowInfo(otherParty: Party, sessionFlow: FlowLogic<*>): FlowInfo { + override fun getFlowInfo(otherParty: Party, sessionFlow: FlowLogic<*>, maySkipCheckpoint: Boolean): FlowInfo { val state = getConfirmedSession(otherParty, sessionFlow).state as FlowSessionState.Initiated return state.context } @@ -175,7 +173,8 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, otherParty: Party, payload: Any, sessionFlow: FlowLogic<*>, - retrySend: Boolean): UntrustworthyData { + retrySend: Boolean, + maySkipCheckpoint: Boolean): UntrustworthyData { requireNonPrimitive(receiveType) logger.debug { "sendAndReceive(${receiveType.name}, $otherParty, ${payload.toString().abbreviate(300)}) ..." } val session = getConfirmedSessionIfPresent(otherParty, sessionFlow) @@ -194,7 +193,8 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, @Suspendable override fun receive(receiveType: Class, otherParty: Party, - sessionFlow: FlowLogic<*>): UntrustworthyData { + sessionFlow: FlowLogic<*>, + maySkipCheckpoint: Boolean): UntrustworthyData { requireNonPrimitive(receiveType) logger.debug { "receive(${receiveType.name}, $otherParty) ..." } val session = getConfirmedSession(otherParty, sessionFlow) @@ -210,7 +210,7 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, } @Suspendable - override fun send(otherParty: Party, payload: Any, sessionFlow: FlowLogic<*>) { + override fun send(otherParty: Party, payload: Any, sessionFlow: FlowLogic<*>, maySkipCheckpoint: Boolean) { logger.debug { "send($otherParty, ${payload.toString().abbreviate(300)})" } val session = getConfirmedSessionIfPresent(otherParty, sessionFlow) if (session == null) { @@ -222,7 +222,7 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, } @Suspendable - override fun waitForLedgerCommit(hash: SecureHash, sessionFlow: FlowLogic<*>): SignedTransaction { + override fun waitForLedgerCommit(hash: SecureHash, sessionFlow: FlowLogic<*>, maySkipCheckpoint: Boolean): SignedTransaction { logger.debug { "waitForLedgerCommit($hash) ..." } suspend(WaitForLedgerCommit(hash, sessionFlow.stateMachine as FlowStateMachineImpl<*>)) val stx = serviceHub.validatedTransactions.getTransaction(hash) diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManager.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManager.kt index 74697821e9..e790d28b2c 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManager.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManager.kt @@ -1,68 +1,24 @@ package net.corda.node.services.statemachine -import co.paralleluniverse.fibers.Fiber -import co.paralleluniverse.fibers.FiberExecutorScheduler -import co.paralleluniverse.fibers.Suspendable -import co.paralleluniverse.fibers.instrument.SuspendableHelper -import co.paralleluniverse.strands.Strand -import com.codahale.metrics.Gauge -import com.esotericsoftware.kryo.KryoException -import com.google.common.collect.HashMultimap -import com.google.common.util.concurrent.MoreExecutors -import net.corda.core.CordaException import net.corda.core.concurrent.CordaFuture -import net.corda.core.crypto.SecureHash -import net.corda.core.crypto.random63BitValue -import net.corda.core.flows.* +import net.corda.core.flows.FlowInitiator +import net.corda.core.flows.FlowLogic import net.corda.core.identity.Party -import net.corda.core.internal.* +import net.corda.core.internal.FlowStateMachine import net.corda.core.messaging.DataFeed -import net.corda.core.serialization.SerializationDefaults.CHECKPOINT_CONTEXT -import net.corda.core.serialization.SerializationDefaults.SERIALIZATION_FACTORY -import net.corda.core.serialization.SerializedBytes -import net.corda.core.serialization.deserialize -import net.corda.core.serialization.serialize import net.corda.core.utilities.Try -import net.corda.core.utilities.debug -import net.corda.core.utilities.loggerFor -import net.corda.core.utilities.trace -import net.corda.node.internal.InitiatedFlowFactory -import net.corda.node.services.api.Checkpoint -import net.corda.node.services.api.CheckpointStorage -import net.corda.node.services.api.ServiceHubInternal -import net.corda.node.services.messaging.ReceivedMessage -import net.corda.node.services.messaging.TopicSession -import net.corda.node.utilities.AffinityExecutor -import net.corda.node.utilities.CordaPersistence -import net.corda.node.utilities.bufferUntilDatabaseCommit -import net.corda.node.utilities.wrapWithDatabaseTransaction -import net.corda.nodeapi.internal.serialization.SerializeAsTokenContextImpl -import net.corda.nodeapi.internal.serialization.withTokenContext -import org.apache.activemq.artemis.utils.ReusableLatch -import org.slf4j.Logger import rx.Observable -import rx.subjects.PublishSubject -import java.io.NotSerializableException -import java.util.* -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.Executors -import java.util.concurrent.TimeUnit.SECONDS -import javax.annotation.concurrent.ThreadSafe -import kotlin.collections.ArrayList /** - * A StateMachineManager is responsible for coordination and persistence of multiple [FlowStateMachineImpl] objects. + * A StateMachineManager is responsible for coordination and persistence of multiple [FlowStateMachine] objects. * Each such object represents an instantiation of a (two-party) flow that has reached a particular point. * - * An implementation of this class will persist state machines to long term storage so they can survive process restarts - * and, if run with a single-threaded executor, will ensure no two state machines run concurrently with each other - * (bad for performance, good for programmer mental health!). + * An implementation of this interface will persist state machines to long term storage so they can survive process + * restarts and, if run with a single-threaded executor, will ensure no two state machines run concurrently with each + * other (bad for performance, good for programmer mental health!). * - * A "state machine" is a class with a single call method. The call method and any others it invokes are rewritten by - * a bytecode rewriting engine called Quasar, to ensure the code can be suspended and resumed at any point. - * - * The SMM will always invoke the flow fibers on the given [AffinityExecutor], regardless of which thread actually - * starts them via [add]. + * A flow is a class with a single call method. The call method and any others it invokes are rewritten by a bytecode + * rewriting engine called Quasar, to ensure the code can be suspended and resumed at any point. * * TODO: Consider the issue of continuation identity more deeply: is it a safe assumption that a serialised * continuation is always unique? @@ -72,588 +28,51 @@ import kotlin.collections.ArrayList * TODO: Ability to control checkpointing explicitly, for cases where you know replaying a message can't hurt * TODO: Don't store all active flows in memory, load from the database on demand. */ -@ThreadSafe -class StateMachineManager(val serviceHub: ServiceHubInternal, - val checkpointStorage: CheckpointStorage, - val executor: AffinityExecutor, - val database: CordaPersistence, - private val unfinishedFibers: ReusableLatch = ReusableLatch(), - private val classloader: ClassLoader = javaClass.classLoader) { +interface StateMachineManager { + /** + * Starts the state machine manager, loading and starting the state machines in storage. + */ + fun start(tokenizableServices: List) + /** + * Stops the state machine manager gracefully, waiting until all but [allowedUnsuspendedFiberCount] flows reach the + * next checkpoint. + */ + fun stop(allowedUnsuspendedFiberCount: Int) - inner class FiberScheduler : FiberExecutorScheduler("Same thread scheduler", executor) - - companion object { - private val logger = loggerFor() - internal val sessionTopic = TopicSession("platform.session") - - init { - Fiber.setDefaultUncaughtExceptionHandler { fiber, throwable -> - (fiber as FlowStateMachineImpl<*>).logger.warn("Caught exception from flow", throwable) - } - } - } + /** + * Starts a new flow. + * + * @param flowLogic The flow's code. + * @param flowInitiator The initiator of the flow. + */ + fun startFlow(flowLogic: FlowLogic, flowInitiator: FlowInitiator, ourIdentity: Party? = null): CordaFuture> + /** + * Represents an addition/removal of a state machine. + */ sealed class Change { abstract val logic: FlowLogic<*> - data class Add(override val logic: FlowLogic<*>) : Change() data class Removed(override val logic: FlowLogic<*>, val result: Try<*>) : Change() } - // A list of all the state machines being managed by this class. We expose snapshots of it via the stateMachines - // property. - private class InnerState { - var started = false - val stateMachines = LinkedHashMap, Checkpoint>() - val changesPublisher = PublishSubject.create()!! - val fibersWaitingForLedgerCommit = HashMultimap.create>()!! + /** + * Returns the list of live state machines and a stream of subsequent additions/removals of them. + */ + fun track(): DataFeed>, Change> - fun notifyChangeObservers(change: Change) { - changesPublisher.bufferUntilDatabaseCommit().onNext(change) - } - } + /** + * The stream of additions/removals of flows. + */ + val changes: Observable - private val scheduler = FiberScheduler() - private val mutex = ThreadBox(InnerState()) - // This thread (only enabled in dev mode) deserialises checkpoints in the background to shake out bugs in checkpoint restore. - private val checkpointCheckerThread = if (serviceHub.configuration.devMode) Executors.newSingleThreadExecutor() else null - - @Volatile private var unrestorableCheckpoints = false - - // True if we're shutting down, so don't resume anything. - @Volatile private var stopping = false - // How many Fibers are running and not suspended. If zero and stopping is true, then we are halted. - private val liveFibers = ReusableLatch() - - // Monitoring support. - private val metrics = serviceHub.monitoringService.metrics - - init { - metrics.register("Flows.InFlight", Gauge { mutex.content.stateMachines.size }) - } - - private val checkpointingMeter = metrics.meter("Flows.Checkpointing Rate") - private val totalStartedFlows = metrics.counter("Flows.Started") - private val totalFinishedFlows = metrics.counter("Flows.Finished") - - private val openSessions = ConcurrentHashMap() - private val recentlyClosedSessions = ConcurrentHashMap() - - internal val tokenizableServices = ArrayList() - // Context for tokenized services in checkpoints - private val serializationContext by lazy { - SerializeAsTokenContextImpl(tokenizableServices, SERIALIZATION_FACTORY, CHECKPOINT_CONTEXT, serviceHub) - } - - fun findServices(predicate: (Any) -> Boolean) = tokenizableServices.filter(predicate) - - /** Returns a list of all state machines executing the given flow logic at the top level (subflows do not count) */ - fun

, T> findStateMachines(flowClass: Class

): List>> { - return mutex.locked { - stateMachines.keys.mapNotNull { - flowClass.castIfPossible(it.logic)?.let { it to uncheckedCast, FlowStateMachineImpl>(it.stateMachine).resultFuture } - } - } - } + /** + * Returns the currently live flows of type [flowClass], and their corresponding result future. + */ + fun > findStateMachines(flowClass: Class): List>> + /** + * Returns all currently live flows. + */ val allStateMachines: List> - get() = mutex.locked { stateMachines.keys.map { it.logic } } - - /** - * An observable that emits triples of the changing flow, the type of change, and a process-specific ID number - * which may change across restarts. - * - * We use assignment here so that multiple subscribers share the same wrapped Observable. - */ - val changes: Observable = mutex.content.changesPublisher.wrapWithDatabaseTransaction() - - fun start() { - checkQuasarJavaAgentPresence() - restoreFibersFromCheckpoints() - listenToLedgerTransactions() - serviceHub.networkMapCache.nodeReady.then { executor.execute(this::resumeRestoredFibers) } - } - - private fun checkQuasarJavaAgentPresence() { - check(SuspendableHelper.isJavaAgentActive(), { - """Missing the '-javaagent' JVM argument. Make sure you run the tests with the Quasar java agent attached to your JVM. - #See https://docs.corda.net/troubleshooting.html - 'Fiber classes not instrumented' for more details.""".trimMargin("#") - }) - } - - private fun listenToLedgerTransactions() { - // Observe the stream of committed, validated transactions and resume fibers that are waiting for them. - serviceHub.validatedTransactions.updates.subscribe { stx -> - val hash = stx.id - val fibers: Set> = mutex.locked { fibersWaitingForLedgerCommit.removeAll(hash) } - if (fibers.isNotEmpty()) { - executor.executeASAP { - for (fiber in fibers) { - fiber.logger.trace { "Transaction $hash has committed to the ledger, resuming" } - fiber.waitingForResponse = null - resumeFiber(fiber) - } - } - } - } - } - - private fun decrementLiveFibers() { - liveFibers.countDown() - } - - private fun incrementLiveFibers() { - liveFibers.countUp() - } - - /** - * Start the shutdown process, bringing the [StateMachineManager] to a controlled stop. When this method returns, - * all Fibers have been suspended and checkpointed, or have completed. - * - * @param allowedUnsuspendedFiberCount Optional parameter is used in some tests. - */ - fun stop(allowedUnsuspendedFiberCount: Int = 0) { - require(allowedUnsuspendedFiberCount >= 0) - mutex.locked { - if (stopping) throw IllegalStateException("Already stopping!") - stopping = true - } - // Account for any expected Fibers in a test scenario. - liveFibers.countDown(allowedUnsuspendedFiberCount) - liveFibers.await() - checkpointCheckerThread?.let { MoreExecutors.shutdownAndAwaitTermination(it, 5, SECONDS) } - check(!unrestorableCheckpoints) { "Unrestorable checkpoints where created, please check the logs for details." } - } - - /** - * Atomic get snapshot + subscribe. This is needed so we don't miss updates between subscriptions to [changes] and - * calls to [allStateMachines] - */ - fun track(): DataFeed>, Change> { - return mutex.locked { - DataFeed(stateMachines.keys.toList(), changesPublisher.bufferUntilSubscribed().wrapWithDatabaseTransaction()) - } - } - - private fun restoreFibersFromCheckpoints() { - mutex.locked { - checkpointStorage.forEach { checkpoint -> - // If a flow is added before start() then don't attempt to restore it - if (!stateMachines.containsValue(checkpoint)) { - deserializeFiber(checkpoint, logger)?.let { - initFiber(it) - stateMachines[it] = checkpoint - } - } - true - } - } - } - - private fun resumeRestoredFibers() { - mutex.locked { - started = true - stateMachines.keys.forEach { resumeRestoredFiber(it) } - } - serviceHub.networkService.addMessageHandler(sessionTopic) { message, _ -> - executor.checkOnThread() - onSessionMessage(message) - } - } - - private fun resumeRestoredFiber(fiber: FlowStateMachineImpl<*>) { - fiber.openSessions.values.forEach { openSessions[it.ourSessionId] = it } - val waitingForResponse = fiber.waitingForResponse - if (waitingForResponse != null) { - if (waitingForResponse is WaitForLedgerCommit) { - val stx = database.transaction { - serviceHub.validatedTransactions.getTransaction(waitingForResponse.hash) - } - if (stx != null) { - fiber.logger.info("Resuming fiber as tx ${waitingForResponse.hash} has committed") - fiber.waitingForResponse = null - resumeFiber(fiber) - } else { - fiber.logger.info("Restored, pending on ledger commit of ${waitingForResponse.hash}") - mutex.locked { fibersWaitingForLedgerCommit.put(waitingForResponse.hash, fiber) } - } - } else { - fiber.logger.info("Restored, pending on receive") - } - } else { - resumeFiber(fiber) - } - } - - private fun onSessionMessage(message: ReceivedMessage) { - val sessionMessage = try { - message.data.deserialize() - } catch (ex: Exception) { - logger.error("Received corrupt SessionMessage data from ${message.peer}") - return - } - val sender = serviceHub.networkMapCache.getPeerByLegalName(message.peer) - if (sender != null) { - when (sessionMessage) { - is ExistingSessionMessage -> onExistingSessionMessage(sessionMessage, sender) - is SessionInit -> onSessionInit(sessionMessage, message, sender) - } - } else { - logger.error("Unknown peer ${message.peer} in $sessionMessage") - } - } - - private fun onExistingSessionMessage(message: ExistingSessionMessage, sender: Party) { - val session = openSessions[message.recipientSessionId] - if (session != null) { - session.fiber.logger.trace { "Received $message on $session from $sender" } - if (session.retryable) { - if (message is SessionConfirm && session.state is FlowSessionState.Initiated) { - session.fiber.logger.trace { "Ignoring duplicate confirmation for session ${session.ourSessionId} – session is idempotent" } - return - } - if (message !is SessionConfirm) { - serviceHub.networkService.cancelRedelivery(session.ourSessionId) - } - } - if (message is SessionEnd) { - openSessions.remove(message.recipientSessionId) - } - session.receivedMessages += ReceivedSessionMessage(sender, message) - if (resumeOnMessage(message, session)) { - // It's important that we reset here and not after the fiber's resumed, in case we receive another message - // before then. - session.fiber.waitingForResponse = null - updateCheckpoint(session.fiber) - session.fiber.logger.trace { "Resuming due to $message" } - resumeFiber(session.fiber) - } - } else { - val peerParty = recentlyClosedSessions.remove(message.recipientSessionId) - if (peerParty != null) { - if (message is SessionConfirm) { - logger.trace { "Received session confirmation but associated fiber has already terminated, so sending session end" } - sendSessionMessage(peerParty, NormalSessionEnd(message.initiatedSessionId)) - } else { - logger.trace { "Ignoring session end message for already closed session: $message" } - } - } else { - logger.warn("Received a session message for unknown session: $message, from $sender") - } - } - } - - // We resume the fiber if it's received a response for which it was waiting for or it's waiting for a ledger - // commit but a counterparty flow has ended with an error (in which case our flow also has to end) - private fun resumeOnMessage(message: ExistingSessionMessage, session: FlowSessionInternal): Boolean { - val waitingForResponse = session.fiber.waitingForResponse - return waitingForResponse?.shouldResume(message, session) ?: false - } - - private fun onSessionInit(sessionInit: SessionInit, receivedMessage: ReceivedMessage, sender: Party) { - logger.trace { "Received $sessionInit from $sender" } - val senderSessionId = sessionInit.initiatorSessionId - - fun sendSessionReject(message: String) = sendSessionMessage(sender, SessionReject(senderSessionId, message)) - - val (session, initiatedFlowFactory) = try { - val initiatedFlowFactory = getInitiatedFlowFactory(sessionInit) - val flowSession = FlowSessionImpl(sender) - val flow = initiatedFlowFactory.createFlow(flowSession) - val senderFlowVersion = when (initiatedFlowFactory) { - is InitiatedFlowFactory.Core -> receivedMessage.platformVersion // The flow version for the core flows is the platform version - is InitiatedFlowFactory.CorDapp -> sessionInit.flowVersion - } - val session = FlowSessionInternal( - flow, - flowSession, - random63BitValue(), - sender, - FlowSessionState.Initiated(sender, senderSessionId, FlowInfo(senderFlowVersion, sessionInit.appName))) - if (sessionInit.firstPayload != null) { - session.receivedMessages += ReceivedSessionMessage(sender, SessionData(session.ourSessionId, sessionInit.firstPayload)) - } - openSessions[session.ourSessionId] = session - // TODO Perhaps the session-init will specificy which of our multiple identies to use, which we would have to - // double-check is actually ours. However, what if we want to control how our identities gets used? - val fiber = createFiber(flow, FlowInitiator.Peer(sender)) - flowSession.sessionFlow = flow - flowSession.stateMachine = fiber - fiber.openSessions[Pair(flow, sender)] = session - updateCheckpoint(fiber) - session to initiatedFlowFactory - } catch (e: SessionRejectException) { - logger.warn("${e.logMessage}: $sessionInit") - sendSessionReject(e.rejectMessage) - return - } catch (e: Exception) { - logger.warn("Couldn't start flow session from $sessionInit", e) - sendSessionReject("Unable to establish session") - return - } - - val (ourFlowVersion, appName) = when (initiatedFlowFactory) { - // The flow version for the core flows is the platform version - is InitiatedFlowFactory.Core -> serviceHub.myInfo.platformVersion to "corda" - is InitiatedFlowFactory.CorDapp -> initiatedFlowFactory.flowVersion to initiatedFlowFactory.appName - } - - sendSessionMessage(sender, SessionConfirm(senderSessionId, session.ourSessionId, ourFlowVersion, appName), session.fiber) - session.fiber.logger.debug { "Initiated by $sender using ${sessionInit.initiatingFlowClass}" } - session.fiber.logger.trace { "Initiated from $sessionInit on $session" } - resumeFiber(session.fiber) - } - - private fun getInitiatedFlowFactory(sessionInit: SessionInit): InitiatedFlowFactory<*> { - val initiatingFlowClass = try { - Class.forName(sessionInit.initiatingFlowClass, true, classloader).asSubclass(FlowLogic::class.java) - } catch (e: ClassNotFoundException) { - throw SessionRejectException("Don't know ${sessionInit.initiatingFlowClass}") - } catch (e: ClassCastException) { - throw SessionRejectException("${sessionInit.initiatingFlowClass} is not a flow") - } - return serviceHub.getFlowFactory(initiatingFlowClass) ?: - throw SessionRejectException("$initiatingFlowClass is not registered") - } - - private fun serializeFiber(fiber: FlowStateMachineImpl<*>): SerializedBytes> { - return fiber.serialize(context = CHECKPOINT_CONTEXT.withTokenContext(serializationContext)) - } - - private fun deserializeFiber(checkpoint: Checkpoint, logger: Logger): FlowStateMachineImpl<*>? { - return try { - checkpoint.serializedFiber.deserialize(context = CHECKPOINT_CONTEXT.withTokenContext(serializationContext)).apply { - fromCheckpoint = true - } - } catch (t: Throwable) { - logger.error("Encountered unrestorable checkpoint!", t) - null - } - } - - private fun createFiber(logic: FlowLogic, flowInitiator: FlowInitiator, ourIdentity: Party? = null): FlowStateMachineImpl { - val fsm = FlowStateMachineImpl( - StateMachineRunId.createRandom(), - logic, - scheduler, - flowInitiator, - ourIdentity ?: serviceHub.myInfo.legalIdentities[0]) - initFiber(fsm) - return fsm - } - - private fun initFiber(fiber: FlowStateMachineImpl<*>) { - verifyFlowLogicIsSuspendable(fiber.logic) - fiber.database = database - fiber.serviceHub = serviceHub - fiber.ourIdentityAndCert = serviceHub.myInfo.legalIdentitiesAndCerts.find { it.party == fiber.ourIdentity } - ?: throw IllegalStateException("Identity specified by ${fiber.id} (${fiber.ourIdentity}) is not one of ours!") - fiber.actionOnSuspend = { ioRequest -> - updateCheckpoint(fiber) - // We commit on the fibers transaction that was copied across ThreadLocals during suspend - // This will free up the ThreadLocal so on return the caller can carry on with other transactions - fiber.commitTransaction() - processIORequest(ioRequest) - decrementLiveFibers() - } - fiber.actionOnEnd = { result, propagated -> - try { - mutex.locked { - stateMachines.remove(fiber)?.let { checkpointStorage.removeCheckpoint(it) } - notifyChangeObservers(Change.Removed(fiber.logic, result)) - } - endAllFiberSessions(fiber, result, propagated) - } finally { - fiber.commitTransaction() - decrementLiveFibers() - totalFinishedFlows.inc() - unfinishedFibers.countDown() - } - } - mutex.locked { - totalStartedFlows.inc() - unfinishedFibers.countUp() - notifyChangeObservers(Change.Add(fiber.logic)) - } - } - - private fun verifyFlowLogicIsSuspendable(logic: FlowLogic) { - // Quasar requires (in Java 8) that at least the call method be annotated suspendable. Unfortunately, it's - // easy to forget to add this when creating a new flow, so we check here to give the user a better error. - // - // The Kotlin compiler can sometimes generate a synthetic bridge method from a single call declaration, which - // forwards to the void method and then returns Unit. However annotations do not get copied across to this - // bridge, so we have to do a more complex scan here. - val call = logic.javaClass.methods.first { !it.isSynthetic && it.name == "call" && it.parameterCount == 0 } - if (call.getAnnotation(Suspendable::class.java) == null) { - throw FlowException("${logic.javaClass.name}.call() is not annotated as @Suspendable. Please fix this.") - } - } - - private fun endAllFiberSessions(fiber: FlowStateMachineImpl<*>, result: Try<*>, propagated: Boolean) { - openSessions.values.removeIf { session -> - if (session.fiber == fiber) { - session.endSession((result as? Try.Failure)?.exception, propagated) - true - } else { - false - } - } - } - - private fun FlowSessionInternal.endSession(exception: Throwable?, propagated: Boolean) { - val initiatedState = state as? FlowSessionState.Initiated ?: return - val sessionEnd = if (exception == null) { - NormalSessionEnd(initiatedState.peerSessionId) - } else { - val errorResponse = if (exception is FlowException && (!propagated || initiatingParty != null)) { - // Only propagate this FlowException if our local flow threw it or it was propagated to us and we only - // pass it down invocation chain to the flow that initiated us, not to flows we've started sessions with. - exception - } else { - null - } - ErrorSessionEnd(initiatedState.peerSessionId, errorResponse) - } - sendSessionMessage(initiatedState.peerParty, sessionEnd, fiber) - recentlyClosedSessions[ourSessionId] = initiatedState.peerParty - } - - /** - * Kicks off a brand new state machine of the given class. - * The state machine will be persisted when it suspends, with automated restart if the StateMachineManager is - * restarted with checkpointed state machines in the storage service. - * - * Note that you must be on the [executor] thread. - */ - fun add(logic: FlowLogic, flowInitiator: FlowInitiator, ourIdentity: Party? = null): FlowStateMachineImpl { - // TODO: Check that logic has @Suspendable on its call method. - executor.checkOnThread() - val fiber = database.transaction { - val fiber = createFiber(logic, flowInitiator, ourIdentity) - updateCheckpoint(fiber) - fiber - } - // If we are not started then our checkpoint will be picked up during start - mutex.locked { - if (started) { - resumeFiber(fiber) - } - } - return fiber - } - - private fun updateCheckpoint(fiber: FlowStateMachineImpl<*>) { - check(fiber.state != Strand.State.RUNNING) { "Fiber cannot be running when checkpointing" } - val newCheckpoint = Checkpoint(serializeFiber(fiber)) - val previousCheckpoint = mutex.locked { stateMachines.put(fiber, newCheckpoint) } - if (previousCheckpoint != null) { - checkpointStorage.removeCheckpoint(previousCheckpoint) - } - checkpointStorage.addCheckpoint(newCheckpoint) - checkpointingMeter.mark() - - checkpointCheckerThread?.execute { - // Immediately check that the checkpoint is valid by deserialising it. The idea is to plug any holes we have - // in our testing by failing any test where unrestorable checkpoints are created. - if (deserializeFiber(newCheckpoint, fiber.logger) == null) { - unrestorableCheckpoints = true - } - } - } - - private fun resumeFiber(fiber: FlowStateMachineImpl<*>) { - // Avoid race condition when setting stopping to true and then checking liveFibers - incrementLiveFibers() - if (!stopping) { - executor.executeASAP { - fiber.resume(scheduler) - } - } else { - fiber.logger.trace("Not resuming as SMM is stopping.") - decrementLiveFibers() - } - } - - private fun processIORequest(ioRequest: FlowIORequest) { - executor.checkOnThread() - when (ioRequest) { - is SendRequest -> processSendRequest(ioRequest) - is WaitForLedgerCommit -> processWaitForCommitRequest(ioRequest) - is Sleep -> processSleepRequest(ioRequest) - } - } - - private fun processSendRequest(ioRequest: SendRequest) { - val retryId = if (ioRequest.message is SessionInit) { - with(ioRequest.session) { - openSessions[ourSessionId] = this - if (retryable) ourSessionId else null - } - } else null - sendSessionMessage(ioRequest.session.state.sendToParty, ioRequest.message, ioRequest.session.fiber, retryId) - if (ioRequest !is ReceiveRequest<*>) { - // We sent a message, but don't expect a response, so re-enter the continuation to let it keep going. - resumeFiber(ioRequest.session.fiber) - } - } - - private fun processWaitForCommitRequest(ioRequest: WaitForLedgerCommit) { - // Is it already committed? - val stx = database.transaction { - serviceHub.validatedTransactions.getTransaction(ioRequest.hash) - } - if (stx != null) { - resumeFiber(ioRequest.fiber) - } else { - // No, then register to wait. - // - // We assume this code runs on the server thread, which is the only place transactions are committed - // currently. When we liberalise our threading somewhat, handing of wait requests will need to be - // reworked to make the wait atomic in another way. Otherwise there is a race between checking the - // database and updating the waiting list. - mutex.locked { - fibersWaitingForLedgerCommit[ioRequest.hash] += ioRequest.fiber - } - } - } - - private fun processSleepRequest(ioRequest: Sleep) { - // Resume the fiber now we have checkpointed, so we can sleep on the Fiber. - resumeFiber(ioRequest.fiber) - } - - private fun sendSessionMessage(party: Party, message: SessionMessage, fiber: FlowStateMachineImpl<*>? = null, retryId: Long? = null) { - val partyInfo = serviceHub.networkMapCache.getPartyInfo(party) - ?: throw IllegalArgumentException("Don't know about party $party") - val address = serviceHub.networkService.getAddressOfParty(partyInfo) - val logger = fiber?.logger ?: logger - logger.trace { "Sending $message to party $party @ $address" + if (retryId != null) " with retry $retryId" else "" } - - val serialized = try { - message.serialize() - } catch (e: Exception) { - when (e) { - // Handling Kryo and AMQP serialization problems. Unfortunately the two exception types do not share much of a common exception interface. - is KryoException, - is NotSerializableException -> { - if (message !is ErrorSessionEnd || message.errorResponse == null) throw e - logger.warn("Something in ${message.errorResponse.javaClass.name} is not serialisable. " + - "Instead sending back an exception which is serialisable to ensure session end occurs properly.", e) - // The subclass may have overridden toString so we use that - val exMessage = message.errorResponse.let { if (it.javaClass != FlowException::class.java) it.toString() else it.message } - message.copy(errorResponse = FlowException(exMessage)).serialize() - } - else -> throw e - } - } - - serviceHub.networkService.apply { - send(createMessage(sessionTopic, serialized.bytes), address, retryId = retryId) - } - } -} - -class SessionRejectException(val rejectMessage: String, val logMessage: String) : CordaException(rejectMessage) { - constructor(message: String) : this(message, message) -} +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt new file mode 100644 index 0000000000..7fdd7f920c --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt @@ -0,0 +1,634 @@ +package net.corda.node.services.statemachine + +import co.paralleluniverse.fibers.Fiber +import co.paralleluniverse.fibers.FiberExecutorScheduler +import co.paralleluniverse.fibers.Suspendable +import co.paralleluniverse.fibers.instrument.SuspendableHelper +import co.paralleluniverse.strands.Strand +import com.codahale.metrics.Gauge +import com.esotericsoftware.kryo.KryoException +import com.google.common.collect.HashMultimap +import com.google.common.util.concurrent.MoreExecutors +import net.corda.core.CordaException +import net.corda.core.concurrent.CordaFuture +import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.random63BitValue +import net.corda.core.flows.* +import net.corda.core.identity.Party +import net.corda.core.internal.* +import net.corda.core.internal.concurrent.doneFuture +import net.corda.core.messaging.DataFeed +import net.corda.core.serialization.SerializationDefaults.CHECKPOINT_CONTEXT +import net.corda.core.serialization.SerializationDefaults.SERIALIZATION_FACTORY +import net.corda.core.serialization.SerializedBytes +import net.corda.core.serialization.deserialize +import net.corda.core.serialization.serialize +import net.corda.core.utilities.Try +import net.corda.core.utilities.debug +import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.trace +import net.corda.node.internal.InitiatedFlowFactory +import net.corda.node.services.api.Checkpoint +import net.corda.node.services.api.CheckpointStorage +import net.corda.node.services.api.ServiceHubInternal +import net.corda.node.services.messaging.ReceivedMessage +import net.corda.node.services.messaging.TopicSession +import net.corda.node.utilities.AffinityExecutor +import net.corda.node.utilities.CordaPersistence +import net.corda.node.utilities.bufferUntilDatabaseCommit +import net.corda.node.utilities.wrapWithDatabaseTransaction +import net.corda.nodeapi.internal.serialization.SerializeAsTokenContextImpl +import net.corda.nodeapi.internal.serialization.withTokenContext +import org.apache.activemq.artemis.utils.ReusableLatch +import org.slf4j.Logger +import rx.Observable +import rx.subjects.PublishSubject +import java.io.NotSerializableException +import java.util.* +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit.SECONDS +import javax.annotation.concurrent.ThreadSafe + +/** + * The StateMachineManagerImpl will always invoke the flow fibers on the given [AffinityExecutor], regardless of which + * thread actually starts them via [startFlow]. + */ +@ThreadSafe +class StateMachineManagerImpl( + val serviceHub: ServiceHubInternal, + val checkpointStorage: CheckpointStorage, + val executor: AffinityExecutor, + val database: CordaPersistence, + private val unfinishedFibers: ReusableLatch = ReusableLatch(), + private val classloader: ClassLoader = StateMachineManagerImpl::class.java.classLoader +) : StateMachineManager { + inner class FiberScheduler : FiberExecutorScheduler("Same thread scheduler", executor) + + companion object { + private val logger = loggerFor() + internal val sessionTopic = TopicSession("platform.session") + + init { + Fiber.setDefaultUncaughtExceptionHandler { fiber, throwable -> + (fiber as FlowStateMachineImpl<*>).logger.warn("Caught exception from flow", throwable) + } + } + } + + // A list of all the state machines being managed by this class. We expose snapshots of it via the stateMachines + // property. + private class InnerState { + var started = false + val stateMachines = LinkedHashMap, Checkpoint>() + val changesPublisher = PublishSubject.create()!! + val fibersWaitingForLedgerCommit = HashMultimap.create>()!! + + fun notifyChangeObservers(change: StateMachineManager.Change) { + changesPublisher.bufferUntilDatabaseCommit().onNext(change) + } + } + + private val scheduler = FiberScheduler() + private val mutex = ThreadBox(InnerState()) + // This thread (only enabled in dev mode) deserialises checkpoints in the background to shake out bugs in checkpoint restore. + private val checkpointCheckerThread = if (serviceHub.configuration.devMode) Executors.newSingleThreadExecutor() else null + + @Volatile private var unrestorableCheckpoints = false + + // True if we're shutting down, so don't resume anything. + @Volatile private var stopping = false + // How many Fibers are running and not suspended. If zero and stopping is true, then we are halted. + private val liveFibers = ReusableLatch() + + // Monitoring support. + private val metrics = serviceHub.monitoringService.metrics + + init { + metrics.register("Flows.InFlight", Gauge { mutex.content.stateMachines.size }) + } + + private val checkpointingMeter = metrics.meter("Flows.Checkpointing Rate") + private val totalStartedFlows = metrics.counter("Flows.Started") + private val totalFinishedFlows = metrics.counter("Flows.Finished") + + private val openSessions = ConcurrentHashMap() + private val recentlyClosedSessions = ConcurrentHashMap() + + // Context for tokenized services in checkpoints + private lateinit var tokenizableServices: List + private val serializationContext by lazy { + SerializeAsTokenContextImpl(tokenizableServices, SERIALIZATION_FACTORY, CHECKPOINT_CONTEXT, serviceHub) + } + + /** Returns a list of all state machines executing the given flow logic at the top level (subflows do not count) */ + override fun > findStateMachines(flowClass: Class): List>> { + return mutex.locked { + stateMachines.keys.mapNotNull { + flowClass.castIfPossible(it.logic)?.let { it to uncheckedCast, FlowStateMachineImpl<*>>(it.stateMachine).resultFuture } + } + } + } + + override val allStateMachines: List> + get() = mutex.locked { stateMachines.keys.map { it.logic } } + + /** + * An observable that emits triples of the changing flow, the type of change, and a process-specific ID number + * which may change across restarts. + * + * We use assignment here so that multiple subscribers share the same wrapped Observable. + */ + override val changes: Observable = mutex.content.changesPublisher.wrapWithDatabaseTransaction() + + override fun start(tokenizableServices: List) { + this.tokenizableServices = tokenizableServices + checkQuasarJavaAgentPresence() + restoreFibersFromCheckpoints() + listenToLedgerTransactions() + serviceHub.networkMapCache.nodeReady.then { executor.execute(this::resumeRestoredFibers) } + } + + private fun checkQuasarJavaAgentPresence() { + check(SuspendableHelper.isJavaAgentActive(), { + """Missing the '-javaagent' JVM argument. Make sure you run the tests with the Quasar java agent attached to your JVM. + #See https://docs.corda.net/troubleshooting.html - 'Fiber classes not instrumented' for more details.""".trimMargin("#") + }) + } + + private fun listenToLedgerTransactions() { + // Observe the stream of committed, validated transactions and resume fibers that are waiting for them. + serviceHub.validatedTransactions.updates.subscribe { stx -> + val hash = stx.id + val fibers: Set> = mutex.locked { fibersWaitingForLedgerCommit.removeAll(hash) } + if (fibers.isNotEmpty()) { + executor.executeASAP { + for (fiber in fibers) { + fiber.logger.trace { "Transaction $hash has committed to the ledger, resuming" } + fiber.waitingForResponse = null + resumeFiber(fiber) + } + } + } + } + } + + private fun decrementLiveFibers() { + liveFibers.countDown() + } + + private fun incrementLiveFibers() { + liveFibers.countUp() + } + + /** + * Start the shutdown process, bringing the [StateMachineManagerImpl] to a controlled stop. When this method returns, + * all Fibers have been suspended and checkpointed, or have completed. + * + * @param allowedUnsuspendedFiberCount Optional parameter is used in some tests. + */ + override fun stop(allowedUnsuspendedFiberCount: Int) { + require(allowedUnsuspendedFiberCount >= 0) + mutex.locked { + if (stopping) throw IllegalStateException("Already stopping!") + stopping = true + } + // Account for any expected Fibers in a test scenario. + liveFibers.countDown(allowedUnsuspendedFiberCount) + liveFibers.await() + checkpointCheckerThread?.let { MoreExecutors.shutdownAndAwaitTermination(it, 5, SECONDS) } + check(!unrestorableCheckpoints) { "Unrestorable checkpoints where created, please check the logs for details." } + } + + /** + * Atomic get snapshot + subscribe. This is needed so we don't miss updates between subscriptions to [changes] and + * calls to [allStateMachines] + */ + override fun track(): DataFeed>, StateMachineManager.Change> { + return mutex.locked { + DataFeed(stateMachines.keys.map { it.logic }, changesPublisher.bufferUntilSubscribed().wrapWithDatabaseTransaction()) + } + } + + private fun restoreFibersFromCheckpoints() { + mutex.locked { + checkpointStorage.forEach { checkpoint -> + // If a flow is added before start() then don't attempt to restore it + if (!stateMachines.containsValue(checkpoint)) { + deserializeFiber(checkpoint, logger)?.let { + initFiber(it) + stateMachines[it] = checkpoint + } + } + true + } + } + } + + private fun resumeRestoredFibers() { + mutex.locked { + started = true + stateMachines.keys.forEach { resumeRestoredFiber(it) } + } + serviceHub.networkService.addMessageHandler(sessionTopic) { message, _ -> + executor.checkOnThread() + onSessionMessage(message) + } + } + + private fun resumeRestoredFiber(fiber: FlowStateMachineImpl<*>) { + fiber.openSessions.values.forEach { openSessions[it.ourSessionId] = it } + val waitingForResponse = fiber.waitingForResponse + if (waitingForResponse != null) { + if (waitingForResponse is WaitForLedgerCommit) { + val stx = database.transaction { + serviceHub.validatedTransactions.getTransaction(waitingForResponse.hash) + } + if (stx != null) { + fiber.logger.info("Resuming fiber as tx ${waitingForResponse.hash} has committed") + fiber.waitingForResponse = null + resumeFiber(fiber) + } else { + fiber.logger.info("Restored, pending on ledger commit of ${waitingForResponse.hash}") + mutex.locked { fibersWaitingForLedgerCommit.put(waitingForResponse.hash, fiber) } + } + } else { + fiber.logger.info("Restored, pending on receive") + } + } else { + resumeFiber(fiber) + } + } + + private fun onSessionMessage(message: ReceivedMessage) { + val sessionMessage = try { + message.data.deserialize() + } catch (ex: Exception) { + logger.error("Received corrupt SessionMessage data from ${message.peer}") + return + } + val sender = serviceHub.networkMapCache.getPeerByLegalName(message.peer) + if (sender != null) { + when (sessionMessage) { + is ExistingSessionMessage -> onExistingSessionMessage(sessionMessage, sender) + is SessionInit -> onSessionInit(sessionMessage, message, sender) + } + } else { + logger.error("Unknown peer ${message.peer} in $sessionMessage") + } + } + + private fun onExistingSessionMessage(message: ExistingSessionMessage, sender: Party) { + val session = openSessions[message.recipientSessionId] + if (session != null) { + session.fiber.logger.trace { "Received $message on $session from $sender" } + if (session.retryable) { + if (message is SessionConfirm && session.state is FlowSessionState.Initiated) { + session.fiber.logger.trace { "Ignoring duplicate confirmation for session ${session.ourSessionId} – session is idempotent" } + return + } + if (message !is SessionConfirm) { + serviceHub.networkService.cancelRedelivery(session.ourSessionId) + } + } + if (message is SessionEnd) { + openSessions.remove(message.recipientSessionId) + } + session.receivedMessages += ReceivedSessionMessage(sender, message) + if (resumeOnMessage(message, session)) { + // It's important that we reset here and not after the fiber's resumed, in case we receive another message + // before then. + session.fiber.waitingForResponse = null + updateCheckpoint(session.fiber) + session.fiber.logger.trace { "Resuming due to $message" } + resumeFiber(session.fiber) + } + } else { + val peerParty = recentlyClosedSessions.remove(message.recipientSessionId) + if (peerParty != null) { + if (message is SessionConfirm) { + logger.trace { "Received session confirmation but associated fiber has already terminated, so sending session end" } + sendSessionMessage(peerParty, NormalSessionEnd(message.initiatedSessionId)) + } else { + logger.trace { "Ignoring session end message for already closed session: $message" } + } + } else { + logger.warn("Received a session message for unknown session: $message, from $sender") + } + } + } + + // We resume the fiber if it's received a response for which it was waiting for or it's waiting for a ledger + // commit but a counterparty flow has ended with an error (in which case our flow also has to end) + private fun resumeOnMessage(message: ExistingSessionMessage, session: FlowSessionInternal): Boolean { + val waitingForResponse = session.fiber.waitingForResponse + return waitingForResponse?.shouldResume(message, session) ?: false + } + + private fun onSessionInit(sessionInit: SessionInit, receivedMessage: ReceivedMessage, sender: Party) { + logger.trace { "Received $sessionInit from $sender" } + val senderSessionId = sessionInit.initiatorSessionId + + fun sendSessionReject(message: String) = sendSessionMessage(sender, SessionReject(senderSessionId, message)) + + val (session, initiatedFlowFactory) = try { + val initiatedFlowFactory = getInitiatedFlowFactory(sessionInit) + val flowSession = FlowSessionImpl(sender) + val flow = initiatedFlowFactory.createFlow(flowSession) + val senderFlowVersion = when (initiatedFlowFactory) { + is InitiatedFlowFactory.Core -> receivedMessage.platformVersion // The flow version for the core flows is the platform version + is InitiatedFlowFactory.CorDapp -> sessionInit.flowVersion + } + val session = FlowSessionInternal( + flow, + flowSession, + random63BitValue(), + sender, + FlowSessionState.Initiated(sender, senderSessionId, FlowInfo(senderFlowVersion, sessionInit.appName))) + if (sessionInit.firstPayload != null) { + session.receivedMessages += ReceivedSessionMessage(sender, SessionData(session.ourSessionId, sessionInit.firstPayload)) + } + openSessions[session.ourSessionId] = session + // TODO Perhaps the session-init will specificy which of our multiple identies to use, which we would have to + // double-check is actually ours. However, what if we want to control how our identities gets used? + val fiber = createFiber(flow, FlowInitiator.Peer(sender)) + flowSession.sessionFlow = flow + flowSession.stateMachine = fiber + fiber.openSessions[Pair(flow, sender)] = session + updateCheckpoint(fiber) + session to initiatedFlowFactory + } catch (e: SessionRejectException) { + logger.warn("${e.logMessage}: $sessionInit") + sendSessionReject(e.rejectMessage) + return + } catch (e: Exception) { + logger.warn("Couldn't start flow session from $sessionInit", e) + sendSessionReject("Unable to establish session") + return + } + + val (ourFlowVersion, appName) = when (initiatedFlowFactory) { + // The flow version for the core flows is the platform version + is InitiatedFlowFactory.Core -> serviceHub.myInfo.platformVersion to "corda" + is InitiatedFlowFactory.CorDapp -> initiatedFlowFactory.flowVersion to initiatedFlowFactory.appName + } + + sendSessionMessage(sender, SessionConfirm(senderSessionId, session.ourSessionId, ourFlowVersion, appName), session.fiber) + session.fiber.logger.debug { "Initiated by $sender using ${sessionInit.initiatingFlowClass}" } + session.fiber.logger.trace { "Initiated from $sessionInit on $session" } + resumeFiber(session.fiber) + } + + private fun getInitiatedFlowFactory(sessionInit: SessionInit): InitiatedFlowFactory<*> { + val initiatingFlowClass = try { + Class.forName(sessionInit.initiatingFlowClass, true, classloader).asSubclass(FlowLogic::class.java) + } catch (e: ClassNotFoundException) { + throw SessionRejectException("Don't know ${sessionInit.initiatingFlowClass}") + } catch (e: ClassCastException) { + throw SessionRejectException("${sessionInit.initiatingFlowClass} is not a flow") + } + return serviceHub.getFlowFactory(initiatingFlowClass) ?: + throw SessionRejectException("$initiatingFlowClass is not registered") + } + + private fun serializeFiber(fiber: FlowStateMachineImpl<*>): SerializedBytes> { + return fiber.serialize(context = CHECKPOINT_CONTEXT.withTokenContext(serializationContext)) + } + + private fun deserializeFiber(checkpoint: Checkpoint, logger: Logger): FlowStateMachineImpl<*>? { + return try { + checkpoint.serializedFiber.deserialize(context = CHECKPOINT_CONTEXT.withTokenContext(serializationContext)).apply { + fromCheckpoint = true + } + } catch (t: Throwable) { + logger.error("Encountered unrestorable checkpoint!", t) + null + } + } + + private fun createFiber(logic: FlowLogic, flowInitiator: FlowInitiator, ourIdentity: Party? = null): FlowStateMachineImpl { + val fsm = FlowStateMachineImpl( + StateMachineRunId.createRandom(), + logic, + scheduler, + flowInitiator, + ourIdentity ?: serviceHub.myInfo.legalIdentities[0]) + initFiber(fsm) + return fsm + } + + private fun initFiber(fiber: FlowStateMachineImpl<*>) { + verifyFlowLogicIsSuspendable(fiber.logic) + fiber.database = database + fiber.serviceHub = serviceHub + fiber.ourIdentityAndCert = serviceHub.myInfo.legalIdentitiesAndCerts.find { it.party == fiber.ourIdentity } + ?: throw IllegalStateException("Identity specified by ${fiber.id} (${fiber.ourIdentity}) is not one of ours!") + fiber.actionOnSuspend = { ioRequest -> + updateCheckpoint(fiber) + // We commit on the fibers transaction that was copied across ThreadLocals during suspend + // This will free up the ThreadLocal so on return the caller can carry on with other transactions + fiber.commitTransaction() + processIORequest(ioRequest) + decrementLiveFibers() + } + fiber.actionOnEnd = { result, propagated -> + try { + mutex.locked { + stateMachines.remove(fiber)?.let { checkpointStorage.removeCheckpoint(it) } + notifyChangeObservers(StateMachineManager.Change.Removed(fiber.logic, result)) + } + endAllFiberSessions(fiber, result, propagated) + } finally { + fiber.commitTransaction() + decrementLiveFibers() + totalFinishedFlows.inc() + unfinishedFibers.countDown() + } + } + mutex.locked { + totalStartedFlows.inc() + unfinishedFibers.countUp() + notifyChangeObservers(StateMachineManager.Change.Add(fiber.logic)) + } + } + + private fun verifyFlowLogicIsSuspendable(logic: FlowLogic) { + // Quasar requires (in Java 8) that at least the call method be annotated suspendable. Unfortunately, it's + // easy to forget to add this when creating a new flow, so we check here to give the user a better error. + // + // The Kotlin compiler can sometimes generate a synthetic bridge method from a single call declaration, which + // forwards to the void method and then returns Unit. However annotations do not get copied across to this + // bridge, so we have to do a more complex scan here. + val call = logic.javaClass.methods.first { !it.isSynthetic && it.name == "call" && it.parameterCount == 0 } + if (call.getAnnotation(Suspendable::class.java) == null) { + throw FlowException("${logic.javaClass.name}.call() is not annotated as @Suspendable. Please fix this.") + } + } + + private fun endAllFiberSessions(fiber: FlowStateMachineImpl<*>, result: Try<*>, propagated: Boolean) { + openSessions.values.removeIf { session -> + if (session.fiber == fiber) { + session.endSession((result as? Try.Failure)?.exception, propagated) + true + } else { + false + } + } + } + + private fun FlowSessionInternal.endSession(exception: Throwable?, propagated: Boolean) { + val initiatedState = state as? FlowSessionState.Initiated ?: return + val sessionEnd = if (exception == null) { + NormalSessionEnd(initiatedState.peerSessionId) + } else { + val errorResponse = if (exception is FlowException && (!propagated || initiatingParty != null)) { + // Only propagate this FlowException if our local flow threw it or it was propagated to us and we only + // pass it down invocation chain to the flow that initiated us, not to flows we've started sessions with. + exception + } else { + null + } + ErrorSessionEnd(initiatedState.peerSessionId, errorResponse) + } + sendSessionMessage(initiatedState.peerParty, sessionEnd, fiber) + recentlyClosedSessions[ourSessionId] = initiatedState.peerParty + } + + /** + * Kicks off a brand new state machine of the given class. + * The state machine will be persisted when it suspends, with automated restart if the StateMachineManager is + * restarted with checkpointed state machines in the storage service. + * + * Note that you must be on the [executor] thread. + */ + override fun startFlow(flowLogic: FlowLogic, flowInitiator: FlowInitiator, ourIdentity: Party?): CordaFuture> { + // TODO: Check that logic has @Suspendable on its call method. + executor.checkOnThread() + val fiber = database.transaction { + val fiber = createFiber(flowLogic, flowInitiator, ourIdentity) + updateCheckpoint(fiber) + fiber + } + // If we are not started then our checkpoint will be picked up during start + mutex.locked { + if (started) { + resumeFiber(fiber) + } + } + return doneFuture(fiber) + } + + private fun updateCheckpoint(fiber: FlowStateMachineImpl<*>) { + check(fiber.state != Strand.State.RUNNING) { "Fiber cannot be running when checkpointing" } + val newCheckpoint = Checkpoint(serializeFiber(fiber)) + val previousCheckpoint = mutex.locked { stateMachines.put(fiber, newCheckpoint) } + if (previousCheckpoint != null) { + checkpointStorage.removeCheckpoint(previousCheckpoint) + } + checkpointStorage.addCheckpoint(newCheckpoint) + checkpointingMeter.mark() + + checkpointCheckerThread?.execute { + // Immediately check that the checkpoint is valid by deserialising it. The idea is to plug any holes we have + // in our testing by failing any test where unrestorable checkpoints are created. + if (deserializeFiber(newCheckpoint, fiber.logger) == null) { + unrestorableCheckpoints = true + } + } + } + + private fun resumeFiber(fiber: FlowStateMachineImpl<*>) { + // Avoid race condition when setting stopping to true and then checking liveFibers + incrementLiveFibers() + if (!stopping) { + executor.executeASAP { + fiber.resume(scheduler) + } + } else { + fiber.logger.trace("Not resuming as SMM is stopping.") + decrementLiveFibers() + } + } + + private fun processIORequest(ioRequest: FlowIORequest) { + executor.checkOnThread() + when (ioRequest) { + is SendRequest -> processSendRequest(ioRequest) + is WaitForLedgerCommit -> processWaitForCommitRequest(ioRequest) + is Sleep -> processSleepRequest(ioRequest) + } + } + + private fun processSendRequest(ioRequest: SendRequest) { + val retryId = if (ioRequest.message is SessionInit) { + with(ioRequest.session) { + openSessions[ourSessionId] = this + if (retryable) ourSessionId else null + } + } else null + sendSessionMessage(ioRequest.session.state.sendToParty, ioRequest.message, ioRequest.session.fiber, retryId) + if (ioRequest !is ReceiveRequest<*>) { + // We sent a message, but don't expect a response, so re-enter the continuation to let it keep going. + resumeFiber(ioRequest.session.fiber) + } + } + + private fun processWaitForCommitRequest(ioRequest: WaitForLedgerCommit) { + // Is it already committed? + val stx = database.transaction { + serviceHub.validatedTransactions.getTransaction(ioRequest.hash) + } + if (stx != null) { + resumeFiber(ioRequest.fiber) + } else { + // No, then register to wait. + // + // We assume this code runs on the server thread, which is the only place transactions are committed + // currently. When we liberalise our threading somewhat, handing of wait requests will need to be + // reworked to make the wait atomic in another way. Otherwise there is a race between checking the + // database and updating the waiting list. + mutex.locked { + fibersWaitingForLedgerCommit[ioRequest.hash] += ioRequest.fiber + } + } + } + + private fun processSleepRequest(ioRequest: Sleep) { + // Resume the fiber now we have checkpointed, so we can sleep on the Fiber. + resumeFiber(ioRequest.fiber) + } + + private fun sendSessionMessage(party: Party, message: SessionMessage, fiber: FlowStateMachineImpl<*>? = null, retryId: Long? = null) { + val partyInfo = serviceHub.networkMapCache.getPartyInfo(party) + ?: throw IllegalArgumentException("Don't know about party $party") + val address = serviceHub.networkService.getAddressOfParty(partyInfo) + val logger = fiber?.logger ?: logger + logger.trace { "Sending $message to party $party @ $address" + if (retryId != null) " with retry $retryId" else "" } + + val serialized = try { + message.serialize() + } catch (e: Exception) { + when (e) { + // Handling Kryo and AMQP serialization problems. Unfortunately the two exception types do not share much of a common exception interface. + is KryoException, + is NotSerializableException -> { + if (message !is ErrorSessionEnd || message.errorResponse == null) throw e + logger.warn("Something in ${message.errorResponse.javaClass.name} is not serialisable. " + + "Instead sending back an exception which is serialisable to ensure session end occurs properly.", e) + // The subclass may have overridden toString so we use that + val exMessage = message.errorResponse.let { if (it.javaClass != FlowException::class.java) it.toString() else it.message } + message.copy(errorResponse = FlowException(exMessage)).serialize() + } + else -> throw e + } + } + + serviceHub.networkService.apply { + send(createMessage(sessionTopic, serialized.bytes), address, retryId = retryId) + } + } +} + +class SessionRejectException(val rejectMessage: String, val logMessage: String) : CordaException(rejectMessage) { + constructor(message: String) : this(message, message) +} diff --git a/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt b/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt index d5a189b0ee..934cc2cb83 100644 --- a/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt +++ b/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt @@ -269,7 +269,7 @@ class NodeVaultService(private val clock: Clock, private val keyManagementServic update.where(stateStatusPredication, lockIdPredicate, *commonPredicates) } if (updatedRows > 0 && updatedRows == stateRefs.size) { - log.trace("Reserving soft lock states for $lockId: $stateRefs") + log.trace { "Reserving soft lock states for $lockId: $stateRefs" } FlowStateMachineImpl.currentStateMachine()?.hasSoftLockedStates = true } else { // revert partial soft locks @@ -280,7 +280,7 @@ class NodeVaultService(private val clock: Clock, private val keyManagementServic update.where(lockUpdateTime, lockIdPredicate, *commonPredicates) } if (revertUpdatedRows > 0) { - log.trace("Reverting $revertUpdatedRows partially soft locked states for $lockId") + log.trace { "Reverting $revertUpdatedRows partially soft locked states for $lockId" } } throw StatesNotAvailableException("Attempted to reserve $stateRefs for $lockId but only $updatedRows rows available") } @@ -309,7 +309,7 @@ class NodeVaultService(private val clock: Clock, private val keyManagementServic update.where(*commonPredicates) } if (update > 0) { - log.trace("Releasing $update soft locked states for $lockId") + log.trace { "Releasing $update soft locked states for $lockId" } } } else { try { @@ -320,7 +320,7 @@ class NodeVaultService(private val clock: Clock, private val keyManagementServic update.where(*commonPredicates, stateRefsPredicate) } if (updatedRows > 0) { - log.trace("Releasing $updatedRows soft locked states for $lockId and stateRefs $stateRefs") + log.trace { "Releasing $updatedRows soft locked states for $lockId and stateRefs $stateRefs" } } } catch (e: Exception) { log.error("""soft lock update error attempting to release states for $lockId and $stateRefs") diff --git a/node/src/main/kotlin/net/corda/node/services/vault/VaultSoftLockManager.kt b/node/src/main/kotlin/net/corda/node/services/vault/VaultSoftLockManager.kt index 2d063a1468..8bf233589b 100644 --- a/node/src/main/kotlin/net/corda/node/services/vault/VaultSoftLockManager.kt +++ b/node/src/main/kotlin/net/corda/node/services/vault/VaultSoftLockManager.kt @@ -1,8 +1,8 @@ package net.corda.node.services.vault +import net.corda.core.contracts.FungibleAsset import net.corda.core.contracts.StateRef import net.corda.core.flows.FlowLogic -import net.corda.core.flows.StateMachineRunId import net.corda.core.node.services.VaultService import net.corda.core.utilities.NonEmptySet import net.corda.core.utilities.loggerFor @@ -12,50 +12,50 @@ import net.corda.node.services.statemachine.FlowStateMachineImpl import net.corda.node.services.statemachine.StateMachineManager import java.util.* -class VaultSoftLockManager(val vault: VaultService, smm: StateMachineManager) { - - private companion object { - val log = loggerFor() - } - - init { - smm.changes.subscribe { change -> - if (change is StateMachineManager.Change.Removed && (FlowStateMachineImpl.currentStateMachine())?.hasSoftLockedStates == true) { - log.trace { "Remove flow name ${change.logic.javaClass} with id $change.id" } - unregisterSoftLocks(change.logic.runId, change.logic) +class VaultSoftLockManager private constructor(private val vault: VaultService) { + companion object { + private val log = loggerFor() + @JvmStatic + fun install(vault: VaultService, smm: StateMachineManager) { + val manager = VaultSoftLockManager(vault) + smm.changes.subscribe { change -> + if (change is StateMachineManager.Change.Removed) { + val logic = change.logic + // Don't run potentially expensive query if the flow didn't lock any states: + if ((logic.stateMachine as FlowStateMachineImpl<*>).hasSoftLockedStates) { + manager.unregisterSoftLocks(logic.runId.uuid, logic) + } + } } - } - - // Discussion - // - // The intent of the following approach is to support what might be a common pattern in a flow: - // 1. Create state - // 2. Do something with state - // without possibility of another flow intercepting the state between 1 and 2, - // since we cannot lock the state before it exists. e.g. Issue and then Move some Cash. - // - // The downside is we could have a long running flow that holds a lock for a long period of time. - // However, the lock can be programmatically released, like any other soft lock, - // should we want a long running flow that creates a visible state mid way through. - - vault.rawUpdates.subscribe { (_, produced, flowId) -> - flowId?.let { - if (produced.isNotEmpty()) { - registerSoftLocks(flowId, (produced.map { it.ref }).toNonEmptySet()) + // Discussion + // + // The intent of the following approach is to support what might be a common pattern in a flow: + // 1. Create state + // 2. Do something with state + // without possibility of another flow intercepting the state between 1 and 2, + // since we cannot lock the state before it exists. e.g. Issue and then Move some Cash. + // + // The downside is we could have a long running flow that holds a lock for a long period of time. + // However, the lock can be programmatically released, like any other soft lock, + // should we want a long running flow that creates a visible state mid way through. + vault.rawUpdates.subscribe { (_, produced, flowId) -> + if (flowId != null) { + val fungible = produced.filter { it.state.data is FungibleAsset<*> } + if (fungible.isNotEmpty()) { + manager.registerSoftLocks(flowId, fungible.map { it.ref }.toNonEmptySet()) + } } } } } private fun registerSoftLocks(flowId: UUID, stateRefs: NonEmptySet) { - log.trace("Reserving soft locks for flow id $flowId and states $stateRefs") + log.trace { "Reserving soft locks for flow id $flowId and states $stateRefs" } vault.softLockReserve(flowId, stateRefs) } - private fun unregisterSoftLocks(id: StateMachineRunId, logic: FlowLogic<*>) { - val flowClassName = logic.javaClass.simpleName - log.trace("Releasing soft locks for flow $flowClassName with flow id ${id.uuid}") - vault.softLockRelease(id.uuid) - + private fun unregisterSoftLocks(flowId: UUID, logic: FlowLogic<*>) { + log.trace { "Releasing soft locks for flow ${logic.javaClass.simpleName} with flow id $flowId" } + vault.softLockRelease(flowId) } } \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/shell/InteractiveShell.kt b/node/src/main/kotlin/net/corda/node/shell/InteractiveShell.kt index a671fdb1dd..d818e30b8f 100644 --- a/node/src/main/kotlin/net/corda/node/shell/InteractiveShell.kt +++ b/node/src/main/kotlin/net/corda/node/shell/InteractiveShell.kt @@ -7,23 +7,21 @@ import com.fasterxml.jackson.databind.* import com.fasterxml.jackson.databind.module.SimpleModule import com.fasterxml.jackson.dataformat.yaml.YAMLFactory import com.google.common.io.Closeables +import net.corda.client.jackson.JacksonSupport +import net.corda.client.jackson.StringToMethodCallParser +import net.corda.core.CordaException import net.corda.core.concurrent.CordaFuture import net.corda.core.contracts.UniqueIdentifier import net.corda.core.flows.FlowInitiator import net.corda.core.flows.FlowLogic -import net.corda.core.internal.FlowStateMachine +import net.corda.core.internal.* import net.corda.core.internal.concurrent.OpenFuture import net.corda.core.internal.concurrent.openFuture -import net.corda.core.internal.createDirectories -import net.corda.core.internal.div -import net.corda.core.internal.write -import net.corda.core.internal.* import net.corda.core.messaging.CordaRPCOps +import net.corda.core.messaging.DataFeed import net.corda.core.messaging.StateMachineUpdate +import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.loggerFor -import net.corda.client.jackson.JacksonSupport -import net.corda.client.jackson.StringToMethodCallParser -import net.corda.core.CordaException import net.corda.node.internal.Node import net.corda.node.internal.StartedNode import net.corda.node.services.messaging.CURRENT_RPC_CONTEXT @@ -200,7 +198,7 @@ object InteractiveShell { } private fun createOutputMapper(factory: JsonFactory): ObjectMapper { - return JacksonSupport.createNonRpcMapper(factory).apply({ + return JacksonSupport.createNonRpcMapper(factory).apply { // Register serializers for stateful objects from libraries that are special to the RPC system and don't // make sense to print out to the screen. For classes we own, annotations can be used instead. val rpcModule = SimpleModule() @@ -210,7 +208,7 @@ object InteractiveShell { disable(SerializationFeature.FAIL_ON_EMPTY_BEANS) enable(SerializationFeature.INDENT_OUTPUT) - }) + } } // TODO: This should become the default renderer rather than something used specifically by commands. @@ -237,7 +235,7 @@ object InteractiveShell { val clazz: Class> = uncheckedCast(matches.single()) try { // TODO Flow invocation should use startFlowDynamic. - val fsm = runFlowFromString({ node.services.startFlow(it, FlowInitiator.Shell) }, inputData, clazz) + val fsm = runFlowFromString({ node.services.startFlow(it, FlowInitiator.Shell).getOrThrow() }, inputData, clazz) // Show the progress tracker on the console until the flow completes or is interrupted with a // Ctrl-C keypress. val latch = CountDownLatch(1) @@ -397,7 +395,7 @@ object InteractiveShell { } private fun printAndFollowRPCResponse(response: Any?, toStream: PrintWriter): CordaFuture? { - val printerFun = { obj: Any? -> yamlMapper.writeValueAsString(obj) } + val printerFun = yamlMapper::writeValueAsString toStream.println(printerFun(response)) toStream.flush() return maybeFollow(response, printerFun, toStream) @@ -443,13 +441,9 @@ object InteractiveShell { val observable: Observable<*> = when (response) { is Observable<*> -> response - is Pair<*, *> -> when { - response.first is Observable<*> -> response.first as Observable<*> - response.second is Observable<*> -> response.second as Observable<*> - else -> null - } - else -> null - } ?: return null + is DataFeed<*, *> -> response.updates + else -> return null + } val subscriber = PrintingSubscriber(printerFun, toStream) uncheckedCast(observable).subscribe(subscriber) @@ -500,8 +494,8 @@ object InteractiveShell { gen.writeString("") } else { val path = Paths.get(toPath) - path.write { value.copyTo(it) } - gen.writeString("") + value.copyTo(path) + gen.writeString("") } } finally { try { diff --git a/node/src/main/kotlin/net/corda/node/utilities/CordaPersistence.kt b/node/src/main/kotlin/net/corda/node/utilities/CordaPersistence.kt index 77055c6073..a8c85d105d 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/CordaPersistence.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/CordaPersistence.kt @@ -20,6 +20,14 @@ import java.util.concurrent.CopyOnWriteArrayList */ const val NODE_DATABASE_PREFIX = "node_" +/** + * The maximum supported field-size for hash HEX-encoded outputs (e.g. database fields). + * This value is enough to support hash functions with outputs up to 512 bits (e.g. SHA3-512), in which + * case 128 HEX characters are required. + * 130 was selected instead of 128, to allow for 2 extra characters that will be used as hash-scheme identifiers. + */ +internal const val MAX_HASH_HEX_SIZE = 130 + //HikariDataSource implements Closeable which allows CordaPersistence to be Closeable class CordaPersistence(var dataSource: HikariDataSource, private val schemaService: SchemaService, private val createIdentityService: () -> IdentityService, databaseProperties: Properties) : Closeable { diff --git a/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt b/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt index 0020970296..ae39004b31 100644 --- a/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt +++ b/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt @@ -29,9 +29,12 @@ import net.corda.node.services.FlowPermissions.Companion.startFlowPermission import net.corda.node.services.messaging.CURRENT_RPC_CONTEXT import net.corda.node.services.messaging.RpcContext import net.corda.nodeapi.User -import net.corda.testing.* +import net.corda.testing.chooseIdentity +import net.corda.testing.expect +import net.corda.testing.expectEvents import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNetwork.MockNode +import net.corda.testing.sequence import org.apache.commons.io.IOUtils import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.junit.After @@ -65,14 +68,13 @@ class CordaRPCOpsImplTest { mockNet = MockNetwork(cordappPackages = listOf("net.corda.finance.contracts.asset")) aliceNode = mockNet.createNode() notaryNode = mockNet.createNotaryNode(validating = false) - rpc = CordaRPCOpsImpl(aliceNode.services, aliceNode.smm, aliceNode.database) + rpc = CordaRPCOpsImpl(aliceNode.services, aliceNode.smm, aliceNode.database, aliceNode.services) CURRENT_RPC_CONTEXT.set(RpcContext(User("user", "pwd", permissions = setOf( startFlowPermission(), startFlowPermission() )))) mockNet.runNetwork() - mockNet.networkMapNode.internals.ensureRegistered() notary = rpc.notaryIdentities().first() } diff --git a/node/src/test/kotlin/net/corda/node/InteractiveShellTest.kt b/node/src/test/kotlin/net/corda/node/InteractiveShellTest.kt index 54117732cf..622fc9667c 100644 --- a/node/src/test/kotlin/net/corda/node/InteractiveShellTest.kt +++ b/node/src/test/kotlin/net/corda/node/InteractiveShellTest.kt @@ -1,7 +1,6 @@ package net.corda.node import com.fasterxml.jackson.dataformat.yaml.YAMLFactory -import com.nhaarman.mockito_kotlin.mock import net.corda.client.jackson.JacksonSupport import net.corda.core.contracts.Amount import net.corda.core.crypto.SecureHash @@ -17,6 +16,7 @@ import net.corda.testing.MEGA_CORP import net.corda.testing.MEGA_CORP_IDENTITY import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices.Companion.makeTestIdentityService +import net.corda.testing.rigorousMock import org.junit.After import org.junit.Before import org.junit.Test @@ -52,7 +52,7 @@ class InteractiveShellTest { private fun check(input: String, expected: String) { var output: DummyFSM? = null InteractiveShell.runFlowFromString({ DummyFSM(it as FlowA).apply { output = this } }, input, FlowA::class.java, om) - assertEquals(expected, output!!.flowA.a, input) + assertEquals(expected, output!!.logic.a, input) } @Test @@ -83,5 +83,5 @@ class InteractiveShellTest { @Test fun party() = check("party: \"${MEGA_CORP.name}\"", MEGA_CORP.name.toString()) - class DummyFSM(val flowA: FlowA) : FlowStateMachine by mock() + class DummyFSM(override val logic: FlowA) : FlowStateMachine by rigorousMock() } diff --git a/node/src/test/kotlin/net/corda/node/internal/cordapp/CordappProviderImplTests.kt b/node/src/test/kotlin/net/corda/node/internal/cordapp/CordappProviderImplTests.kt index 9f7833f928..af830013d4 100644 --- a/node/src/test/kotlin/net/corda/node/internal/cordapp/CordappProviderImplTests.kt +++ b/node/src/test/kotlin/net/corda/node/internal/cordapp/CordappProviderImplTests.kt @@ -1,6 +1,5 @@ package net.corda.node.internal.cordapp -import com.nhaarman.mockito_kotlin.mock import net.corda.core.node.services.AttachmentStorage import net.corda.testing.node.MockAttachmentStorage import org.junit.Assert @@ -40,7 +39,7 @@ class CordappProviderImplTests { @Test fun `test that we find a cordapp class that is loaded into the store`() { val loader = CordappLoader.createDevMode(listOf(isolatedJAR)) - val provider = CordappProviderImpl(loader, mock()) + val provider = CordappProviderImpl(loader, attachmentStore) val className = "net.corda.finance.contracts.isolated.AnotherDummyContract" val expected = provider.cordapps.first() diff --git a/node/src/test/kotlin/net/corda/node/messaging/InMemoryMessagingTests.kt b/node/src/test/kotlin/net/corda/node/messaging/InMemoryMessagingTests.kt index b3a3fb32e4..f5e842347e 100644 --- a/node/src/test/kotlin/net/corda/node/messaging/InMemoryMessagingTests.kt +++ b/node/src/test/kotlin/net/corda/node/messaging/InMemoryMessagingTests.kt @@ -47,23 +47,17 @@ class InMemoryMessagingTests { @Test fun basics() { - val node1 = mockNet.networkMapNode + val node1 = mockNet.createNode() val node2 = mockNet.createNode() val node3 = mockNet.createNode() val bits = "test-content".toByteArray() var finalDelivery: Message? = null - - with(node2) { - node2.network.addMessageHandler { msg, _ -> - node2.network.send(msg, node3.network.myAddress) - } + node2.network.addMessageHandler { msg, _ -> + node2.network.send(msg, node3.network.myAddress) } - - with(node3) { - node2.network.addMessageHandler { msg, _ -> - finalDelivery = msg - } + node3.network.addMessageHandler { msg, _ -> + finalDelivery = msg } // Node 1 sends a message and it should end up in finalDelivery, after we run the network @@ -76,7 +70,7 @@ class InMemoryMessagingTests { @Test fun broadcast() { - val node1 = mockNet.networkMapNode + val node1 = mockNet.createNode() val node2 = mockNet.createNode() val node3 = mockNet.createNode() @@ -95,7 +89,7 @@ class InMemoryMessagingTests { */ @Test fun `skip unhandled messages`() { - val node1 = mockNet.networkMapNode + val node1 = mockNet.createNode() val node2 = mockNet.createNode() var received = 0 diff --git a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt index 93edb33b92..c5f893965e 100644 --- a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt @@ -13,7 +13,6 @@ import net.corda.core.internal.FlowStateMachine import net.corda.core.internal.concurrent.map import net.corda.core.internal.rootCause import net.corda.core.messaging.DataFeed -import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.messaging.StateMachineTransactionMapping import net.corda.core.node.services.Vault import net.corda.core.serialization.CordaSerializable @@ -35,17 +34,12 @@ import net.corda.finance.flows.TwoPartyTradeFlow.Buyer import net.corda.finance.flows.TwoPartyTradeFlow.Seller import net.corda.node.internal.StartedNode import net.corda.node.services.api.WritableTransactionStorage -import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.persistence.DBTransactionStorage import net.corda.node.services.persistence.checkpoints import net.corda.node.utilities.CordaPersistence -import net.corda.nodeapi.internal.ServiceInfo import net.corda.testing.* import net.corda.testing.contracts.fillWithSomeTestCash -import net.corda.testing.node.InMemoryMessagingNetwork -import net.corda.testing.node.MockNetwork -import net.corda.testing.node.MockServices -import net.corda.testing.node.pumpReceive +import net.corda.testing.node.* import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Before @@ -55,8 +49,6 @@ import org.junit.runners.Parameterized import rx.Observable import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream -import java.math.BigInteger -import java.security.KeyPair import java.util.* import java.util.jar.JarOutputStream import java.util.zip.ZipEntry @@ -75,7 +67,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { companion object { private val cordappPackages = listOf("net.corda.finance.contracts") @JvmStatic - @Parameterized.Parameters + @Parameterized.Parameters(name = "Anonymous = {0}") fun data(): Collection { return listOf(true, false) } @@ -99,7 +91,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { // We run this in parallel threads to help catch any race conditions that may exist. The other tests // we run in the unit test thread exclusively to speed things up, ensure deterministic results and // allow interruption half way through. - mockNet = MockNetwork(false, true, cordappPackages = cordappPackages) + mockNet = MockNetwork(threadPerNode = true, cordappPackages = cordappPackages) ledger(MockServices(cordappPackages), initialiseSerialization = false) { val notaryNode = mockNet.createNotaryNode() val aliceNode = mockNet.createPartyNode(ALICE_NAME) @@ -149,7 +141,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { @Test(expected = InsufficientBalanceException::class) fun `trade cash for commercial paper fails using soft locking`() { - mockNet = MockNetwork(false, true, cordappPackages = cordappPackages) + mockNet = MockNetwork(threadPerNode = true, cordappPackages = cordappPackages) ledger(MockServices(cordappPackages), initialiseSerialization = false) { val notaryNode = mockNet.createNotaryNode() val aliceNode = mockNet.createPartyNode(ALICE_NAME) @@ -205,7 +197,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { @Test fun `shutdown and restore`() { - mockNet = MockNetwork(false, cordappPackages = cordappPackages) + mockNet = MockNetwork(cordappPackages = cordappPackages) ledger(MockServices(cordappPackages), initialiseSerialization = false) { val notaryNode = mockNet.createNotaryNode() val aliceNode = mockNet.createPartyNode(ALICE_NAME) @@ -268,13 +260,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { // ... bring the node back up ... the act of constructing the SMM will re-register the message handlers // that Bob was waiting on before the reboot occurred. - bobNode = mockNet.createNode(bobAddr.id, object : MockNetwork.Factory { - override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, - id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): MockNetwork.MockNode { - return MockNetwork.MockNode(config, network, networkMapAddr, bobAddr.id, notaryIdentity, entropyRoot) - } - }, BOB_NAME) - + bobNode = mockNet.createNode(MockNodeParameters(bobAddr.id, BOB_NAME)) // Find the future representing the result of this state machine again. val bobFuture = bobNode.smm.findStateMachines(BuyerAcceptor::class.java).single().second @@ -308,31 +294,26 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { // of gets and puts. private fun makeNodeWithTracking(name: CordaX500Name): StartedNode { // Create a node in the mock network ... - return mockNet.createNode(nodeFactory = object : MockNetwork.Factory { - override fun create(config: NodeConfiguration, - network: MockNetwork, - networkMapAddr: SingleMessageRecipient?, - id: Int, notaryIdentity: Pair?, - entropyRoot: BigInteger): MockNetwork.MockNode { - return object : MockNetwork.MockNode(config, network, networkMapAddr, id, notaryIdentity, entropyRoot) { + return mockNet.createNode(MockNodeParameters(legalName = name), nodeFactory = object : MockNetwork.Factory { + override fun create(args: MockNodeArgs): MockNetwork.MockNode { + return object : MockNetwork.MockNode(args) { // That constructs a recording tx storage override fun makeTransactionStorage(): WritableTransactionStorage { return RecordingTransactionStorage(database, super.makeTransactionStorage()) } } } - }, legalName = name) + }) } @Test fun `check dependencies of sale asset are resolved`() { - mockNet = MockNetwork(false, cordappPackages = cordappPackages) + mockNet = MockNetwork(cordappPackages = cordappPackages) val notaryNode = mockNet.createNotaryNode() val aliceNode = makeNodeWithTracking(ALICE_NAME) val bobNode = makeNodeWithTracking(BOB_NAME) val bankNode = makeNodeWithTracking(BOC_NAME) mockNet.runNetwork() - notaryNode.internals.ensureRegistered() val notary = aliceNode.services.getDefaultNotary() val alice = aliceNode.info.singleIdentity() val bob = bobNode.info.singleIdentity() @@ -433,14 +414,13 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { @Test fun `track works`() { - mockNet = MockNetwork(false, cordappPackages = cordappPackages) + mockNet = MockNetwork(cordappPackages = cordappPackages) val notaryNode = mockNet.createNotaryNode() val aliceNode = makeNodeWithTracking(ALICE_NAME) val bobNode = makeNodeWithTracking(BOB_NAME) val bankNode = makeNodeWithTracking(BOC_NAME) mockNet.runNetwork() - notaryNode.internals.ensureRegistered() val notary = aliceNode.services.getDefaultNotary() val alice: Party = aliceNode.info.singleIdentity() val bank: Party = bankNode.info.singleIdentity() @@ -515,7 +495,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { @Test fun `dependency with error on buyer side`() { - mockNet = MockNetwork(false, cordappPackages = cordappPackages) + mockNet = MockNetwork(cordappPackages = cordappPackages) ledger(MockServices(cordappPackages), initialiseSerialization = false) { runWithError(true, false, "at least one cash input") } @@ -523,7 +503,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { @Test fun `dependency with error on seller side`() { - mockNet = MockNetwork(false, cordappPackages = cordappPackages) + mockNet = MockNetwork(cordappPackages = cordappPackages) ledger(MockServices(cordappPackages), initialiseSerialization = false) { runWithError(false, true, "Issuances have a time-window") } @@ -596,7 +576,6 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { val bankNode = mockNet.createPartyNode(BOC_NAME) mockNet.runNetwork() - notaryNode.internals.ensureRegistered() val notary = aliceNode.services.getDefaultNotary() val alice = aliceNode.info.singleIdentity() val bob = bobNode.info.singleIdentity() diff --git a/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt b/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt index 58aea5fac8..1beceb0855 100644 --- a/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt @@ -13,7 +13,6 @@ import net.corda.core.transactions.WireTransaction import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.seconds import net.corda.node.internal.StartedNode -import net.corda.node.services.api.ServiceHubInternal import net.corda.testing.* import net.corda.testing.contracts.DummyContract import net.corda.testing.node.MockNetwork @@ -43,7 +42,6 @@ class NotaryChangeTests { clientNodeB = mockNet.createNode() newNotaryNode = mockNet.createNotaryNode(legalName = DUMMY_NOTARY.name.copy(organisation = "Dummy Notary 2")) mockNet.runNetwork() // Clear network map registration messages - oldNotaryNode.internals.ensureRegistered() oldNotaryParty = newNotaryNode.services.networkMapCache.getNotary(DUMMY_NOTARY_SERVICE_NAME)!! newNotaryParty = newNotaryNode.services.networkMapCache.getNotary(DUMMY_NOTARY_SERVICE_NAME.copy(organisation = "Dummy Notary 2"))!! } diff --git a/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt index d54bfa8754..446976faf2 100644 --- a/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt @@ -1,6 +1,8 @@ package net.corda.node.services.events import co.paralleluniverse.fibers.Suspendable +import com.codahale.metrics.MetricRegistry +import com.nhaarman.mockito_kotlin.* import net.corda.core.contracts.* import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogicRef @@ -8,35 +10,40 @@ import net.corda.core.flows.FlowLogicRefFactory import net.corda.core.identity.AbstractParty import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party +import net.corda.core.node.NodeInfo import net.corda.core.node.ServiceHub import net.corda.core.serialization.SingletonSerializeAsToken +import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.days +import net.corda.node.internal.FlowStarterImpl +import net.corda.node.internal.StateLoaderImpl import net.corda.node.internal.cordapp.CordappLoader import net.corda.node.internal.cordapp.CordappProviderImpl -import net.corda.node.services.api.VaultServiceInternal +import net.corda.node.services.api.MonitoringService +import net.corda.node.services.api.ServiceHubInternal import net.corda.node.services.identity.InMemoryIdentityService +import net.corda.node.services.network.NetworkMapCacheImpl import net.corda.node.services.persistence.DBCheckpointStorage import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl import net.corda.node.services.statemachine.StateMachineManager +import net.corda.node.services.statemachine.StateMachineManagerImpl import net.corda.node.services.vault.NodeVaultService -import net.corda.node.testing.MockServiceHubInternal import net.corda.node.utilities.AffinityExecutor import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.configureDatabase import net.corda.testing.* import net.corda.testing.contracts.DummyContract -import net.corda.testing.node.InMemoryMessagingNetwork -import net.corda.testing.node.MockKeyManagementService +import net.corda.testing.node.* import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties import net.corda.testing.node.MockServices.Companion.makeTestIdentityService -import net.corda.testing.node.TestClock import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Before import org.junit.Test import java.nio.file.Paths +import java.security.PublicKey import java.time.Clock import java.time.Instant import java.util.concurrent.CountDownLatch @@ -45,20 +52,26 @@ import java.util.concurrent.TimeUnit import kotlin.test.assertTrue class NodeSchedulerServiceTest : SingletonSerializeAsToken() { + companion object { + private val myInfo = NodeInfo(listOf(MOCK_HOST_AND_PORT), listOf(DUMMY_IDENTITY_1), 1, serial = 1L) + } + private val realClock: Clock = Clock.systemUTC() private val stoppedClock: Clock = Clock.fixed(realClock.instant(), realClock.zone) private val testClock = TestClock(stoppedClock) private val schedulerGatedExecutor = AffinityExecutor.Gate(true) - private lateinit var services: MockServiceHubInternal + abstract class Services : ServiceHubInternal, TestReference + private lateinit var services: Services private lateinit var scheduler: NodeSchedulerService private lateinit var smmExecutor: AffinityExecutor.ServiceAffinityExecutor private lateinit var database: CordaPersistence private lateinit var countDown: CountDownLatch private lateinit var smmHasRemovedAllFlows: CountDownLatch - + private lateinit var kms: MockKeyManagementService + private lateinit var mockSMM: StateMachineManager var calls: Int = 0 /** @@ -80,35 +93,35 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() { val databaseProperties = makeTestDatabaseProperties() database = configureDatabase(dataSourceProps, databaseProperties, ::makeTestIdentityService) val identityService = InMemoryIdentityService(trustRoot = DEV_TRUST_ROOT) - val kms = MockKeyManagementService(identityService, ALICE_KEY) - + kms = MockKeyManagementService(identityService, ALICE_KEY) + val configuration = testNodeConfiguration(Paths.get("."), CordaX500Name("Alice", "London", "GB")) + val validatedTransactions = MockTransactionStorage() + val stateLoader = StateLoaderImpl(validatedTransactions) database.transaction { - val nullIdentity = CordaX500Name(organisation = "None", locality = "None", country = "GB") - val mockMessagingService = InMemoryMessagingNetwork(false).InMemoryMessaging( - false, - InMemoryMessagingNetwork.PeerHandle(0, nullIdentity), - AffinityExecutor.ServiceAffinityExecutor("test", 1), - database) - services = object : MockServiceHubInternal( - database, - testNodeConfiguration(Paths.get("."), CordaX500Name(organisation = "Alice", locality = "London", country = "GB")), - overrideClock = testClock, - keyManagement = kms, - network = mockMessagingService), TestReference { - override val vaultService: VaultServiceInternal = NodeVaultService(testClock, kms, stateLoader, database.hibernateConfig) - override val testReference = this@NodeSchedulerServiceTest - override val cordappProvider = CordappProviderImpl(CordappLoader.createWithTestPackages(listOf("net.corda.testing.contracts")), attachments) + services = rigorousMock().also { + doReturn(configuration).whenever(it).configuration + doReturn(MonitoringService(MetricRegistry())).whenever(it).monitoringService + doReturn(validatedTransactions).whenever(it).validatedTransactions + doReturn(NetworkMapCacheImpl(MockNetworkMapCache(database, configuration), identityService)).whenever(it).networkMapCache + doCallRealMethod().whenever(it).signInitialTransaction(any(), any()) + doReturn(myInfo).whenever(it).myInfo + doReturn(kms).whenever(it).keyManagementService + doReturn(CordappProviderImpl(CordappLoader.createWithTestPackages(listOf("net.corda.testing.contracts")), MockAttachmentStorage())).whenever(it).cordappProvider + doCallRealMethod().whenever(it).recordTransactions(any()) + doCallRealMethod().whenever(it).recordTransactions(any(), any()) + doCallRealMethod().whenever(it).recordTransactions(any(), any>()) + doReturn(NodeVaultService(testClock, kms, stateLoader, database.hibernateConfig)).whenever(it).vaultService + doReturn(this@NodeSchedulerServiceTest).whenever(it).testReference } smmExecutor = AffinityExecutor.ServiceAffinityExecutor("test", 1) - scheduler = NodeSchedulerService(services, schedulerGatedExecutor, serverThread = smmExecutor) - val mockSMM = StateMachineManager(services, DBCheckpointStorage(), smmExecutor, database) + mockSMM = StateMachineManagerImpl(services, DBCheckpointStorage(), smmExecutor, database) + scheduler = NodeSchedulerService(testClock, database, FlowStarterImpl(smmExecutor, mockSMM), stateLoader, schedulerGatedExecutor, serverThread = smmExecutor) mockSMM.changes.subscribe { change -> if (change is StateMachineManager.Change.Removed && mockSMM.allStateMachines.isEmpty()) { smmHasRemovedAllFlows.countDown() } } - mockSMM.start() - services.smm = mockSMM + mockSMM.start(emptyList()) scheduler.start() } } @@ -116,7 +129,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() { @After fun tearDown() { // We need to make sure the StateMachineManager is done before shutting down executors. - if (services.smm.allStateMachines.isNotEmpty()) { + if (mockSMM.allStateMachines.isNotEmpty()) { smmHasRemovedAllFlows.await() } smmExecutor.shutdown() @@ -125,6 +138,8 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() { resetTestSerialization() } + // Ignore IntelliJ when it says these properties can be private, if they are we cannot serialise them + // in AMQP. class TestState(val flowLogicRef: FlowLogicRef, val instant: Instant, val myIdentity: Party) : LinearState, SchedulableState { override val participants: List get() = listOf(myIdentity) @@ -136,7 +151,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() { } } - class TestFlowLogic(val increment: Int = 1) : FlowLogic() { + class TestFlowLogic(private val increment: Int = 1) : FlowLogic() { @Suspendable override fun call() { (serviceHub as TestReference).testReference.calls += increment @@ -279,8 +294,8 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() { var scheduledRef: ScheduledStateRef? = null database.transaction { apply { - val freshKey = services.keyManagementService.freshKey() - val state = TestState(FlowLogicRefFactoryImpl.createForRPC(TestFlowLogic::class.java, increment), instant, services.myInfo.chooseIdentity()) + val freshKey = kms.freshKey() + val state = TestState(FlowLogicRefFactoryImpl.createForRPC(TestFlowLogic::class.java, increment), instant, myInfo.chooseIdentity()) val builder = TransactionBuilder(null).apply { addOutputState(state, DummyContract.PROGRAM_ID, DUMMY_NOTARY) addCommand(Command(), freshKey) diff --git a/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt b/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt index be2fc12b96..9999b927e0 100644 --- a/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt @@ -16,8 +16,11 @@ import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.getOrThrow import net.corda.node.internal.StartedNode import net.corda.node.services.statemachine.StateMachineManager -import net.corda.testing.* +import net.corda.testing.DUMMY_NOTARY +import net.corda.testing.chooseIdentity import net.corda.testing.contracts.DummyContract +import net.corda.testing.dummyCommand +import net.corda.testing.getDefaultNotary import net.corda.testing.node.MockNetwork import org.junit.After import org.junit.Assert.* @@ -96,8 +99,6 @@ class ScheduledFlowTests { val a = mockNet.createUnstartedNode() val b = mockNet.createUnstartedNode() - notaryNode.internals.ensureRegistered() - mockNet.startNodes() nodeA = a.started!! nodeB = b.started!! diff --git a/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt b/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt index 418322b589..b7fdbd40eb 100644 --- a/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt @@ -12,10 +12,10 @@ import net.corda.node.services.RPCUserServiceImpl import net.corda.node.services.api.MonitoringService import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.configureWithDevSSLCertificate +import net.corda.node.services.network.NetworkMapCacheImpl import net.corda.node.services.network.PersistentNetworkMapCache import net.corda.node.services.network.NetworkMapService import net.corda.node.services.transactions.PersistentUniquenessProvider -import net.corda.node.testing.MockServiceHubInternal import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.configureDatabase @@ -57,7 +57,7 @@ class ArtemisMessagingTests : TestDependencyInjectionBase() { var messagingClient: NodeMessagingClient? = null var messagingServer: ArtemisMessagingServer? = null - lateinit var networkMapCache: PersistentNetworkMapCache + lateinit var networkMapCache: NetworkMapCacheImpl val rpcOps = object : RPCOps { override val protocolVersion: Int get() = throw UnsupportedOperationException() @@ -73,7 +73,7 @@ class ArtemisMessagingTests : TestDependencyInjectionBase() { LogHelper.setLevel(PersistentUniquenessProvider::class) database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), ::makeTestIdentityService) networkMapRegistrationFuture = doneFuture(Unit) - networkMapCache = PersistentNetworkMapCache(serviceHub = object : MockServiceHubInternal(database, config) {}) + networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, config), rigorousMock()) } @After diff --git a/node/src/test/kotlin/net/corda/node/services/network/AbstractNetworkMapServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/network/AbstractNetworkMapServiceTest.kt deleted file mode 100644 index b3b9a9f769..0000000000 --- a/node/src/test/kotlin/net/corda/node/services/network/AbstractNetworkMapServiceTest.kt +++ /dev/null @@ -1,281 +0,0 @@ -package net.corda.node.services.network - -import net.corda.core.concurrent.CordaFuture -import net.corda.core.identity.CordaX500Name -import net.corda.core.messaging.SingleMessageRecipient -import net.corda.core.node.NodeInfo -import net.corda.core.serialization.deserialize -import net.corda.core.utilities.getOrThrow -import net.corda.node.internal.StartedNode -import net.corda.node.services.api.NetworkMapCacheInternal -import net.corda.node.services.config.NodeConfiguration -import net.corda.node.services.messaging.MessagingService -import net.corda.node.services.messaging.send -import net.corda.node.services.messaging.sendRequest -import net.corda.node.services.network.AbstractNetworkMapServiceTest.Changed.Added -import net.corda.node.services.network.AbstractNetworkMapServiceTest.Changed.Removed -import net.corda.node.services.network.NetworkMapService.* -import net.corda.node.services.network.NetworkMapService.Companion.FETCH_TOPIC -import net.corda.node.services.network.NetworkMapService.Companion.PUSH_ACK_TOPIC -import net.corda.node.services.network.NetworkMapService.Companion.PUSH_TOPIC -import net.corda.node.services.network.NetworkMapService.Companion.QUERY_TOPIC -import net.corda.node.services.network.NetworkMapService.Companion.REGISTER_TOPIC -import net.corda.node.services.network.NetworkMapService.Companion.SUBSCRIPTION_TOPIC -import net.corda.node.utilities.AddOrRemove -import net.corda.node.utilities.AddOrRemove.ADD -import net.corda.node.utilities.AddOrRemove.REMOVE -import net.corda.nodeapi.internal.ServiceInfo -import net.corda.testing.* -import net.corda.testing.node.MockNetwork -import net.corda.testing.node.MockNetwork.MockNode -import org.assertj.core.api.Assertions.assertThat -import org.junit.After -import org.junit.Before -import org.junit.Test -import java.math.BigInteger -import java.security.KeyPair -import java.time.Instant -import java.util.* -import java.util.concurrent.LinkedBlockingQueue - -abstract class AbstractNetworkMapServiceTest { - lateinit var mockNet: MockNetwork - lateinit var mapServiceNode: StartedNode - lateinit var alice: StartedNode - - companion object { - val subscriberLegalName = CordaX500Name(organisation = "Subscriber", locality = "New York", country = "US") - } - - @Before - fun setup() { - mockNet = MockNetwork(defaultFactory = nodeFactory) - mapServiceNode = mockNet.networkMapNode - alice = mockNet.createNode(nodeFactory = nodeFactory, legalName = ALICE.name) - mockNet.runNetwork() - lastSerial = System.currentTimeMillis() - } - - @After - fun tearDown() { - mockNet.stopNodes() - } - - protected abstract val nodeFactory: MockNetwork.Factory<*> - - protected abstract val networkMapService: S - - // For persistent service, switch out the implementation for a newly instantiated one so we can check the state is preserved. - protected abstract fun swizzle() - - @Test - fun `all nodes register themselves`() { - // setup has run the network and so we immediately expect the network map service to be correctly populated - assertThat(alice.fetchMap()).containsOnly(Added(mapServiceNode), Added(alice)) - assertThat(alice.identityQuery()).isEqualTo(alice.info) - assertThat(mapServiceNode.identityQuery()).isEqualTo(mapServiceNode.info) - } - - @Test - fun `re-register the same node`() { - val response = alice.registration(ADD) - swizzle() - assertThat(response.getOrThrow().error).isNull() - assertThat(alice.fetchMap()).containsOnly(Added(mapServiceNode), Added(alice)) // Confirm it's a no-op - } - - @Test - fun `re-register with smaller serial value`() { - val response = alice.registration(ADD, serial = 1) - swizzle() - assertThat(response.getOrThrow().error).isNotNull() // Make sure send error message is sent back - assertThat(alice.fetchMap()).containsOnly(Added(mapServiceNode), Added(alice)) // Confirm it's a no-op - } - - @Test - fun `de-register node`() { - val response = alice.registration(REMOVE) - swizzle() - assertThat(response.getOrThrow().error).isNull() - assertThat(alice.fetchMap()).containsOnly(Added(mapServiceNode), Removed(alice)) - swizzle() - assertThat(alice.identityQuery()).isNull() - assertThat(mapServiceNode.identityQuery()).isEqualTo(mapServiceNode.info) - } - - @Test - fun `de-register same node again`() { - alice.registration(REMOVE) - val response = alice.registration(REMOVE) - swizzle() - assertThat(response.getOrThrow().error).isNotNull() // Make sure send error message is sent back - assertThat(alice.fetchMap()).containsOnly(Added(mapServiceNode), Removed(alice)) - } - - @Test - fun `de-register unknown node`() { - val bob = newNodeSeparateFromNetworkMap(BOB.name) - val response = bob.registration(REMOVE) - swizzle() - assertThat(response.getOrThrow().error).isNotNull() // Make sure send error message is sent back - assertThat(alice.fetchMap()).containsOnly(Added(mapServiceNode), Added(alice)) - } - - @Test - fun `subscribed while new node registers`() { - val updates = alice.subscribe() - swizzle() - val bob = addNewNodeToNetworkMap(BOB.name) - swizzle() - val update = updates.single() - assertThat(update.mapVersion).isEqualTo(networkMapService.mapVersion) - assertThat(update.wireReg.verified().toChanged()).isEqualTo(Added(bob.info)) - } - - @Test - fun `subscribed while node de-registers`() { - val bob = addNewNodeToNetworkMap(BOB.name) - val updates = alice.subscribe() - bob.registration(REMOVE) - swizzle() - assertThat(updates.map { it.wireReg.verified().toChanged() }).containsOnly(Removed(bob.info)) - } - - @Test - fun unsubscribe() { - val updates = alice.subscribe() - val bob = addNewNodeToNetworkMap(BOB.name) - alice.unsubscribe() - addNewNodeToNetworkMap(CHARLIE.name) - swizzle() - assertThat(updates.map { it.wireReg.verified().toChanged() }).containsOnly(Added(bob.info)) - } - - @Test - fun `surpass unacknowledged update limit`() { - val subscriber = newNodeSeparateFromNetworkMap(subscriberLegalName) - val updates = subscriber.subscribe() - val bob = addNewNodeToNetworkMap(BOB.name) - var serial = updates.first().wireReg.verified().serial - repeat(networkMapService.maxUnacknowledgedUpdates) { - bob.registration(ADD, serial = ++serial) - swizzle() - } - // We sent maxUnacknowledgedUpdates + 1 updates - the last one will be missed - assertThat(updates).hasSize(networkMapService.maxUnacknowledgedUpdates) - } - - @Test - fun `delay sending update ack until just before unacknowledged update limit`() { - val subscriber = newNodeSeparateFromNetworkMap(subscriberLegalName) - val updates = subscriber.subscribe() - val bob = addNewNodeToNetworkMap(BOB.name) - var serial = updates.first().wireReg.verified().serial - repeat(networkMapService.maxUnacknowledgedUpdates - 1) { - bob.registration(ADD, serial = ++serial) - swizzle() - } - // Subscriber will receive maxUnacknowledgedUpdates updates before sending ack - subscriber.ackUpdate(updates.last().mapVersion) - swizzle() - bob.registration(ADD, serial = ++serial) - assertThat(updates).hasSize(networkMapService.maxUnacknowledgedUpdates + 1) - assertThat(updates.last().wireReg.verified().serial).isEqualTo(serial) - } - - private fun StartedNode<*>.fetchMap(subscribe: Boolean = false, ifChangedSinceVersion: Int? = null): List { - val request = FetchMapRequest(subscribe, ifChangedSinceVersion, network.myAddress) - val response = services.networkService.sendRequest(FETCH_TOPIC, request, mapServiceNode.network.myAddress) - mockNet.runNetwork() - return response.getOrThrow().nodes?.map { it.toChanged() } ?: emptyList() - } - - private fun NodeRegistration.toChanged(): Changed = when (type) { - ADD -> Added(node) - REMOVE -> Removed(node) - } - - private fun StartedNode<*>.identityQuery(): NodeInfo? { - val request = QueryIdentityRequest(services.myInfo.chooseIdentityAndCert(), network.myAddress) - val response = services.networkService.sendRequest(QUERY_TOPIC, request, mapServiceNode.network.myAddress) - mockNet.runNetwork() - return response.getOrThrow().node - } - - private var lastSerial = Long.MIN_VALUE - - private fun StartedNode<*>.registration(addOrRemove: AddOrRemove, - serial: Long? = null): CordaFuture { - val distinctSerial = if (serial == null) { - ++lastSerial - } else { - lastSerial = serial - serial - } - val expires = Instant.now() + NetworkMapService.DEFAULT_EXPIRATION_PERIOD - val nodeRegistration = NodeRegistration(info, distinctSerial, addOrRemove, expires) - val request = RegistrationRequest(nodeRegistration.toWire(services.keyManagementService, info.chooseIdentity().owningKey), network.myAddress) - val response = services.networkService.sendRequest(REGISTER_TOPIC, request, mapServiceNode.network.myAddress) - mockNet.runNetwork() - return response - } - - private fun StartedNode<*>.subscribe(): Queue { - val request = SubscribeRequest(true, network.myAddress) - val updates = LinkedBlockingQueue() - services.networkService.addMessageHandler(PUSH_TOPIC) { message, _ -> - updates += message.data.deserialize() - } - val response = services.networkService.sendRequest(SUBSCRIPTION_TOPIC, request, mapServiceNode.network.myAddress) - mockNet.runNetwork() - assertThat(response.getOrThrow().confirmed).isTrue() - return updates - } - - private fun StartedNode<*>.unsubscribe() { - val request = SubscribeRequest(false, network.myAddress) - val response = services.networkService.sendRequest(SUBSCRIPTION_TOPIC, request, mapServiceNode.network.myAddress) - mockNet.runNetwork() - assertThat(response.getOrThrow().confirmed).isTrue() - } - - private fun StartedNode<*>.ackUpdate(mapVersion: Int) { - val request = UpdateAcknowledge(mapVersion, services.networkService.myAddress) - services.networkService.send(PUSH_ACK_TOPIC, MessagingService.DEFAULT_SESSION_ID, request, mapServiceNode.network.myAddress) - mockNet.runNetwork() - } - - private fun addNewNodeToNetworkMap(legalName: CordaX500Name): StartedNode { - val node = mockNet.createNode(legalName = legalName) - mockNet.runNetwork() - lastSerial = System.currentTimeMillis() - return node - } - - private fun newNodeSeparateFromNetworkMap(legalName: CordaX500Name): StartedNode { - return mockNet.createNode(legalName = legalName, nodeFactory = NoNMSNodeFactory) - } - - sealed class Changed { - data class Added(val node: NodeInfo) : Changed() { - constructor(node: StartedNode<*>) : this(node.info) - } - - data class Removed(val node: NodeInfo) : Changed() { - constructor(node: StartedNode<*>) : this(node.info) - } - } - - private object NoNMSNodeFactory : MockNetwork.Factory { - override fun create(config: NodeConfiguration, - network: MockNetwork, - networkMapAddr: SingleMessageRecipient?, - id: Int, - notaryIdentity: Pair?, - entropyRoot: BigInteger): MockNode { - return object : MockNode(config, network, null, id, notaryIdentity, entropyRoot) { - override fun makeNetworkMapService(network: MessagingService, networkMapCache: NetworkMapCacheInternal) = NullNetworkMapService - } - } - } -} diff --git a/node/src/test/kotlin/net/corda/node/services/network/HTTPNetworkMapClientTest.kt b/node/src/test/kotlin/net/corda/node/services/network/HTTPNetworkMapClientTest.kt new file mode 100644 index 0000000000..058762bb7f --- /dev/null +++ b/node/src/test/kotlin/net/corda/node/services/network/HTTPNetworkMapClientTest.kt @@ -0,0 +1,154 @@ +package net.corda.node.services.network + +import com.fasterxml.jackson.databind.ObjectMapper +import net.corda.core.crypto.* +import net.corda.core.identity.CordaX500Name +import net.corda.core.identity.PartyAndCertificate +import net.corda.core.node.NodeInfo +import net.corda.core.serialization.deserialize +import net.corda.core.serialization.serialize +import net.corda.core.utilities.NetworkHostAndPort +import net.corda.node.utilities.CertificateType +import net.corda.node.utilities.X509Utilities +import net.corda.testing.TestDependencyInjectionBase +import org.assertj.core.api.Assertions.assertThat +import org.bouncycastle.asn1.x500.X500Name +import org.bouncycastle.cert.X509CertificateHolder +import org.eclipse.jetty.server.Server +import org.eclipse.jetty.server.ServerConnector +import org.eclipse.jetty.server.handler.HandlerCollection +import org.eclipse.jetty.servlet.ServletContextHandler +import org.eclipse.jetty.servlet.ServletHolder +import org.glassfish.jersey.server.ResourceConfig +import org.glassfish.jersey.servlet.ServletContainer +import org.junit.After +import org.junit.Before +import org.junit.Test +import java.io.ByteArrayInputStream +import java.io.InputStream +import java.net.InetSocketAddress +import java.security.cert.CertPath +import java.security.cert.Certificate +import java.security.cert.CertificateFactory +import java.security.cert.X509Certificate +import javax.ws.rs.* +import javax.ws.rs.core.MediaType +import javax.ws.rs.core.Response +import javax.ws.rs.core.Response.ok +import kotlin.test.assertEquals + +class HTTPNetworkMapClientTest : TestDependencyInjectionBase() { + private lateinit var server: Server + + private lateinit var networkMapClient: NetworkMapClient + private val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + private val rootCACert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Corda Node Root CA", organisation = "R3 LTD", locality = "London", country = "GB"), rootCAKey) + private val intermediateCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + private val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootCAKey, X500Name("CN=Corda Node Intermediate CA,L=London"), intermediateCAKey.public) + + @Before + fun setUp() { + server = Server(InetSocketAddress("localhost", 0)).apply { + handler = HandlerCollection().apply { + addHandler(ServletContextHandler().apply { + contextPath = "/" + val resourceConfig = ResourceConfig().apply { + // Add your API provider classes (annotated for JAX-RS) here + register(MockNetworkMapServer()) + } + val jerseyServlet = ServletHolder(ServletContainer(resourceConfig)).apply { initOrder = 0 }// Initialise at server start + addServlet(jerseyServlet, "/api/*") + }) + } + } + server.start() + + while (!server.isStarted) { + Thread.sleep(100) + } + + val hostAndPort = server.connectors.mapNotNull { it as? ServerConnector }.first() + networkMapClient = HTTPNetworkMapClient("http://${hostAndPort.host}:${hostAndPort.localPort}/api/network-map") + } + + @After + fun tearDown() { + server.stop() + } + + @Test + fun `registered node is added to the network map`() { + // Create node info. + val signedNodeInfo = createNodeInfo("Test1") + val nodeInfo = signedNodeInfo.verified() + + networkMapClient.publish(signedNodeInfo) + + val nodeInfoHash = nodeInfo.serialize().sha256() + + assertThat(networkMapClient.getNetworkMap()).containsExactly(nodeInfoHash) + assertEquals(nodeInfo, networkMapClient.getNodeInfo(nodeInfoHash)) + + val signedNodeInfo2 = createNodeInfo("Test2") + val nodeInfo2 = signedNodeInfo2.verified() + networkMapClient.publish(signedNodeInfo2) + + val nodeInfoHash2 = nodeInfo2.serialize().sha256() + assertThat(networkMapClient.getNetworkMap()).containsExactly(nodeInfoHash, nodeInfoHash2) + assertEquals(nodeInfo2, networkMapClient.getNodeInfo(nodeInfoHash2)) + } + + private fun createNodeInfo(organisation: String): SignedData { + val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val clientCert = X509Utilities.createCertificate(CertificateType.CLIENT_CA, intermediateCACert, intermediateCAKey, CordaX500Name(organisation = organisation, locality = "London", country = "GB"), keyPair.public) + val certPath = buildCertPath(clientCert.toX509Certificate(), intermediateCACert.toX509Certificate(), rootCACert.toX509Certificate()) + val nodeInfo = NodeInfo(listOf(NetworkHostAndPort("my.$organisation.com", 1234)), listOf(PartyAndCertificate(certPath)), 1, serial = 1L) + + // Create digital signature. + val digitalSignature = DigitalSignature.WithKey(keyPair.public, Crypto.doSign(keyPair.private, nodeInfo.serialize().bytes)) + + return SignedData(nodeInfo.serialize(), digitalSignature) + } +} + +@Path("network-map") +// This is a stub implementation of the network map rest API. +internal class MockNetworkMapServer { + private val nodeInfos = mutableMapOf() + @POST + @Path("publish") + @Consumes(MediaType.APPLICATION_OCTET_STREAM) + fun publishNodeInfo(input: InputStream): Response { + val registrationData = input.readBytes().deserialize>() + val nodeInfo = registrationData.verified() + val nodeInfoHash = nodeInfo.serialize().sha256() + nodeInfos.put(nodeInfoHash, nodeInfo) + return ok().build() + } + + @GET + @Produces(MediaType.APPLICATION_JSON) + fun getNetworkMap(): Response { + return Response.ok(ObjectMapper().writeValueAsString(nodeInfos.keys.map { it.toString() })).build() + } + + @GET + @Path("{var}") + @Produces(MediaType.APPLICATION_OCTET_STREAM) + fun getNodeInfo(@PathParam("var") nodeInfoHash: String): Response { + val nodeInfo = nodeInfos[SecureHash.parse(nodeInfoHash)] + return if (nodeInfo != null) { + Response.ok(nodeInfo.serialize().bytes) + } else { + Response.status(Response.Status.NOT_FOUND) + }.build() + } +} + +private fun buildCertPath(vararg certificates: Certificate): CertPath { + return CertificateFactory.getInstance("X509").generateCertPath(certificates.asList()) +} + +private fun X509CertificateHolder.toX509Certificate(): X509Certificate { + return CertificateFactory.getInstance("X509").generateCertificate(ByteArrayInputStream(encoded)) as X509Certificate +} \ No newline at end of file diff --git a/node/src/test/kotlin/net/corda/node/services/network/InMemoryNetworkMapServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/network/InMemoryNetworkMapServiceTest.kt deleted file mode 100644 index c6d8566560..0000000000 --- a/node/src/test/kotlin/net/corda/node/services/network/InMemoryNetworkMapServiceTest.kt +++ /dev/null @@ -1,9 +0,0 @@ -package net.corda.node.services.network - -import net.corda.testing.node.MockNetwork - -class InMemoryNetworkMapServiceTest : AbstractNetworkMapServiceTest() { - override val nodeFactory get() = MockNetwork.DefaultFactory - override val networkMapService: InMemoryNetworkMapService get() = mapServiceNode.inNodeNetworkMapService as InMemoryNetworkMapService - override fun swizzle() = Unit -} diff --git a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt index 10aa2c62ac..b493466645 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt @@ -1,49 +1,34 @@ package net.corda.node.services.network import net.corda.core.node.services.NetworkMapCache -import net.corda.core.utilities.getOrThrow import net.corda.testing.ALICE import net.corda.testing.BOB import net.corda.testing.chooseIdentity import net.corda.testing.node.MockNetwork +import net.corda.testing.node.MockNodeParameters import org.assertj.core.api.Assertions.assertThat import org.junit.After -import org.junit.Before import org.junit.Test import java.math.BigInteger import kotlin.test.assertEquals class NetworkMapCacheTest { - lateinit var mockNet: MockNetwork - - @Before - fun setUp() { - mockNet = MockNetwork() - } + val mockNet: MockNetwork = MockNetwork() @After fun teardown() { mockNet.stopNodes() } - @Test - fun registerWithNetwork() { - mockNet.createNotaryNode() - val aliceNode = mockNet.createPartyNode(ALICE.name) - val future = aliceNode.services.networkMapCache.addMapService(aliceNode.network, mockNet.networkMapNode.network.myAddress, false, null) - mockNet.runNetwork() - future.getOrThrow() - } - @Test fun `key collision`() { val entropy = BigInteger.valueOf(24012017L) - val aliceNode = mockNet.createNode(nodeFactory = MockNetwork.DefaultFactory, legalName = ALICE.name, entropyRoot = entropy) + val aliceNode = mockNet.createNode(MockNodeParameters(legalName = ALICE.name, entropyRoot = entropy)) mockNet.runNetwork() // Node A currently knows only about itself, so this returns node A assertEquals(aliceNode.services.networkMapCache.getNodesByLegalIdentityKey(aliceNode.info.chooseIdentity().owningKey).singleOrNull(), aliceNode.info) - val bobNode = mockNet.createNode(nodeFactory = MockNetwork.DefaultFactory, legalName = BOB.name, entropyRoot = entropy) + val bobNode = mockNet.createNode(MockNodeParameters(legalName = BOB.name, entropyRoot = entropy)) assertEquals(aliceNode.info.chooseIdentity(), bobNode.info.chooseIdentity()) aliceNode.services.networkMapCache.addNode(bobNode.info) @@ -83,7 +68,7 @@ class NetworkMapCacheTest { val aliceNode = mockNet.createPartyNode(ALICE.name) val notaryLegalIdentity = notaryNode.info.chooseIdentity() val alice = aliceNode.info.chooseIdentity() - val notaryCache = notaryNode.services.networkMapCache as PersistentNetworkMapCache + val notaryCache = notaryNode.services.networkMapCache mockNet.runNetwork() notaryNode.database.transaction { assertThat(notaryCache.getNodeByLegalIdentity(alice) != null) diff --git a/node/src/test/kotlin/net/corda/node/services/network/PersistentNetworkMapServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/network/PersistentNetworkMapServiceTest.kt deleted file mode 100644 index 612c8e943a..0000000000 --- a/node/src/test/kotlin/net/corda/node/services/network/PersistentNetworkMapServiceTest.kt +++ /dev/null @@ -1,56 +0,0 @@ -package net.corda.node.services.network - -import net.corda.core.messaging.SingleMessageRecipient -import net.corda.node.services.api.NetworkMapCacheInternal -import net.corda.node.services.config.NodeConfiguration -import net.corda.node.services.messaging.MessagingService -import net.corda.nodeapi.internal.ServiceInfo -import net.corda.testing.node.MockNetwork -import net.corda.testing.node.MockNetwork.MockNode -import java.math.BigInteger -import java.security.KeyPair - -/** - * This class mirrors [InMemoryNetworkMapServiceTest] but switches in a [PersistentNetworkMapService] and - * repeatedly replaces it with new instances to check that the service correctly restores the most recent state. - */ -class PersistentNetworkMapServiceTest : AbstractNetworkMapServiceTest() { - - override val nodeFactory: MockNetwork.Factory<*> get() = NodeFactory - - override val networkMapService: PersistentNetworkMapService - get() = (mapServiceNode.inNodeNetworkMapService as SwizzleNetworkMapService).delegate - - override fun swizzle() { - mapServiceNode.database.transaction { - (mapServiceNode.inNodeNetworkMapService as SwizzleNetworkMapService).swizzle() - } - } - - private object NodeFactory : MockNetwork.Factory { - override fun create(config: NodeConfiguration, - network: MockNetwork, - networkMapAddr: SingleMessageRecipient?, - id: Int, - notaryIdentity: Pair?, - entropyRoot: BigInteger): MockNode { - return object : MockNode(config, network, networkMapAddr, id, notaryIdentity, entropyRoot) { - override fun makeNetworkMapService(network: MessagingService, networkMapCache: NetworkMapCacheInternal) = SwizzleNetworkMapService(network, networkMapCache) - } - } - } - - /** - * We use a special [NetworkMapService] that allows us to switch in a new instance at any time to check that the - * state within it is correctly restored. - */ - private class SwizzleNetworkMapService(private val delegateFactory: () -> PersistentNetworkMapService) : NetworkMapService { - constructor(network: MessagingService, networkMapCache: NetworkMapCacheInternal) : this({ PersistentNetworkMapService(network, networkMapCache, 1) }) - - var delegate = delegateFactory() - fun swizzle() { - delegate.unregisterNetworkHandlers() - delegate = delegateFactory() - } - } -} diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt b/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt index 2b9ff96f1c..160ea87bf3 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt @@ -45,7 +45,7 @@ class DBTransactionStorageTests : TestDependencyInjectionBase() { override val vaultService: VaultServiceInternal get() { val vaultService = NodeVaultService(clock, keyManagementService, stateLoader, database.hibernateConfig) - hibernatePersister = HibernateObserver(vaultService.rawUpdates, database.hibernateConfig) + hibernatePersister = HibernateObserver.install(vaultService.rawUpdates, database.hibernateConfig) return vaultService } diff --git a/node/src/test/kotlin/net/corda/node/services/schema/HibernateObserverTests.kt b/node/src/test/kotlin/net/corda/node/services/schema/HibernateObserverTests.kt index 33e8bcdad4..30d877ab93 100644 --- a/node/src/test/kotlin/net/corda/node/services/schema/HibernateObserverTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/schema/HibernateObserverTests.kt @@ -69,8 +69,7 @@ class HibernateObserverTests { } } val database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), ::makeTestIdentityService, schemaService) - @Suppress("UNUSED_VARIABLE") - val observer = HibernateObserver(rawUpdatesPublisher, database.hibernateConfig) + HibernateObserver.install(rawUpdatesPublisher, database.hibernateConfig) database.transaction { rawUpdatesPublisher.onNext(Vault.Update(emptySet(), setOf(StateAndRef(TransactionState(TestState(), DummyContract.PROGRAM_ID, MEGA_CORP), StateRef(SecureHash.sha256("dummy"), 0))))) val parentRowCountResult = DatabaseTransactionManager.current().connection.prepareStatement("select count(*) from Parents").executeQuery() diff --git a/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt index e65592f70b..044c953422 100644 --- a/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt @@ -33,12 +33,14 @@ import net.corda.testing.node.InMemoryMessagingNetwork.MessageTransfer import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNetwork.MockNode +import net.corda.testing.node.MockNodeParameters import net.corda.testing.node.pumpReceive import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy import org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType import org.junit.After import org.junit.Before +import org.junit.Ignore import org.junit.Test import rx.Notification import rx.Observable @@ -64,14 +66,16 @@ class FlowFrameworkTests { private lateinit var alice: Party private lateinit var bob: Party + private fun StartedNode<*>.flushSmm() { + (this.smm as StateMachineManagerImpl).executor.flush() + } + @Before fun start() { mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin(), cordappPackages = listOf("net.corda.finance.contracts", "net.corda.testing.contracts")) - aliceNode = mockNet.createNode(legalName = ALICE_NAME) - bobNode = mockNet.createNode(legalName = BOB_NAME) - + aliceNode = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME)) + bobNode = mockNet.createNode(MockNodeParameters(legalName = BOB_NAME)) mockNet.runNetwork() - aliceNode.internals.ensureRegistered() // We intentionally create our own notary and ignore the one provided by the network // Note that these notaries don't operate correctly as they don't share their state. They are only used for testing @@ -152,45 +156,19 @@ class FlowFrameworkTests { assertEquals(true, flow.flowStarted) // Now we should have run the flow } - @Test - fun `flow added before network map will be init checkpointed`() { - var charlieNode = mockNet.createNode() //create vanilla node - val flow = NoOpFlow() - charlieNode.services.startFlow(flow) - assertEquals(false, flow.flowStarted) // Not started yet as no network activity has been allowed yet - charlieNode.internals.disableDBCloseOnStop() - charlieNode.services.networkMapCache.clearNetworkMapCache() // zap persisted NetworkMapCache to force use of network. - charlieNode.dispose() - - charlieNode = mockNet.createNode(charlieNode.internals.id) - val restoredFlow = charlieNode.getSingleFlow().first - assertEquals(false, restoredFlow.flowStarted) // Not started yet as no network activity has been allowed yet - mockNet.runNetwork() // Allow network map messages to flow - charlieNode.smm.executor.flush() - assertEquals(true, restoredFlow.flowStarted) // Now we should have run the flow and hopefully cleared the init checkpoint - charlieNode.internals.disableDBCloseOnStop() - charlieNode.services.networkMapCache.clearNetworkMapCache() // zap persisted NetworkMapCache to force use of network. - charlieNode.dispose() - - // Now it is completed the flow should leave no Checkpoint. - charlieNode = mockNet.createNode(charlieNode.internals.id) - mockNet.runNetwork() // Allow network map messages to flow - charlieNode.smm.executor.flush() - assertTrue(charlieNode.smm.findStateMachines(NoOpFlow::class.java).isEmpty()) - } - @Test fun `flow loaded from checkpoint will respond to messages from before start`() { aliceNode.registerFlowFactory(ReceiveFlow::class) { InitiatedSendFlow("Hello", it) } bobNode.services.startFlow(ReceiveFlow(alice).nonTerminating()) // Prepare checkpointed receive flow // Make sure the add() has finished initial processing. - bobNode.smm.executor.flush() + bobNode.flushSmm() bobNode.internals.disableDBCloseOnStop() bobNode.dispose() // kill receiver val restoredFlow = bobNode.restartAndGetRestoredFlow() assertThat(restoredFlow.receivedPayloads[0]).isEqualTo("Hello") } + @Ignore("Some changes in startup order make this test's assumptions fail.") @Test fun `flow with send will resend on interrupted restart`() { val payload = random63BitValue() @@ -198,8 +176,7 @@ class FlowFrameworkTests { var sentCount = 0 mockNet.messagingNetwork.sentMessages.toSessionTransfers().filter { it.isPayloadTransfer }.forEach { sentCount++ } - - val charlieNode = mockNet.createNode(legalName = CHARLIE_NAME) + val charlieNode = mockNet.createNode(MockNodeParameters(legalName = CHARLIE_NAME)) val secondFlow = charlieNode.registerFlowFactory(PingPongFlow::class) { PingPongFlow(it, payload2) } mockNet.runNetwork() val charlie = charlieNode.info.singleIdentity() @@ -210,7 +187,7 @@ class FlowFrameworkTests { assertEquals(1, bobNode.checkpointStorage.checkpoints().size) } // Make sure the add() has finished initial processing. - bobNode.smm.executor.flush() + bobNode.flushSmm() bobNode.internals.disableDBCloseOnStop() // Restart node and thus reload the checkpoint and resend the message with same UUID bobNode.dispose() @@ -218,12 +195,12 @@ class FlowFrameworkTests { assertEquals(1, bobNode.checkpointStorage.checkpoints().size) // confirm checkpoint bobNode.services.networkMapCache.clearNetworkMapCache() } - val node2b = mockNet.createNode(bobNode.internals.id) + val node2b = mockNet.createNode(MockNodeParameters(bobNode.internals.id)) bobNode.internals.manuallyCloseDB() val (firstAgain, fut1) = node2b.getSingleFlow() // Run the network which will also fire up the second flow. First message should get deduped. So message data stays in sync. mockNet.runNetwork() - node2b.smm.executor.flush() + node2b.flushSmm() fut1.getOrThrow() val receivedCount = receivedSessionMessages.count { it.isPayloadTransfer } @@ -245,7 +222,7 @@ class FlowFrameworkTests { @Test fun `sending to multiple parties`() { - val charlieNode = mockNet.createNode(legalName = CHARLIE_NAME) + val charlieNode = mockNet.createNode(MockNodeParameters(legalName = CHARLIE_NAME)) mockNet.runNetwork() val charlie = charlieNode.info.singleIdentity() bobNode.registerFlowFactory(SendFlow::class) { InitiatedReceiveFlow(it).nonTerminating() } @@ -278,7 +255,7 @@ class FlowFrameworkTests { @Test fun `receiving from multiple parties`() { - val charlieNode = mockNet.createNode(legalName = CHARLIE_NAME) + val charlieNode = mockNet.createNode(MockNodeParameters(legalName = CHARLIE_NAME)) mockNet.runNetwork() val charlie = charlieNode.info.singleIdentity() val bobPayload = "Test 1" @@ -432,7 +409,7 @@ class FlowFrameworkTests { @Test fun `FlowException propagated in invocation chain`() { - val charlieNode = mockNet.createNode(legalName = CHARLIE_NAME) + val charlieNode = mockNet.createNode(MockNodeParameters(legalName = CHARLIE_NAME)) mockNet.runNetwork() val charlie = charlieNode.info.singleIdentity() @@ -447,7 +424,7 @@ class FlowFrameworkTests { @Test fun `FlowException thrown and there is a 3rd unrelated party flow`() { - val charlieNode = mockNet.createNode(legalName = CHARLIE_NAME) + val charlieNode = mockNet.createNode(MockNodeParameters(legalName = CHARLIE_NAME)) mockNet.runNetwork() val charlie = charlieNode.info.singleIdentity() @@ -696,7 +673,7 @@ class FlowFrameworkTests { private inline fun > StartedNode.restartAndGetRestoredFlow() = internals.run { disableDBCloseOnStop() // Handover DB to new node copy stop() - val newNode = mockNet.createNode(id) + val newNode = mockNet.createNode(MockNodeParameters(id)) newNode.internals.acceptableLiveFiberCountOnStop = 1 manuallyCloseDB() mockNet.runNetwork() // allow NetworkMapService messages to stabilise and thus start the state machine @@ -731,7 +708,7 @@ class FlowFrameworkTests { private fun StartedNode<*>.sendSessionMessage(message: SessionMessage, destination: Party) { services.networkService.apply { val address = getAddressOfParty(PartyInfo.SingleNode(destination, emptyList())) - send(createMessage(StateMachineManager.sessionTopic, message.serialize().bytes), address) + send(createMessage(StateMachineManagerImpl.sessionTopic, message.serialize().bytes), address) } } @@ -755,7 +732,7 @@ class FlowFrameworkTests { } private fun Observable.toSessionTransfers(): Observable { - return filter { it.message.topicSession == StateMachineManager.sessionTopic }.map { + return filter { it.message.topicSession == StateMachineManagerImpl.sessionTopic }.map { val from = it.sender.id val message = it.message.data.deserialize() SessionTransfer(from, sanitise(message), it.recipients) diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt index ec8155a18e..2510497167 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt @@ -13,10 +13,11 @@ import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.seconds -import net.corda.node.services.api.ServiceHubInternal +import net.corda.node.services.api.StartedNodeServices import net.corda.testing.* import net.corda.testing.contracts.DummyContract import net.corda.testing.node.MockNetwork +import net.corda.testing.node.MockNodeParameters import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Before @@ -28,8 +29,8 @@ import kotlin.test.assertFailsWith class NotaryServiceTests { lateinit var mockNet: MockNetwork - lateinit var notaryServices: ServiceHubInternal - lateinit var aliceServices: ServiceHubInternal + lateinit var notaryServices: StartedNodeServices + lateinit var aliceServices: StartedNodeServices lateinit var notary: Party lateinit var alice: Party @@ -37,9 +38,8 @@ class NotaryServiceTests { fun setup() { mockNet = MockNetwork(cordappPackages = listOf("net.corda.testing.contracts")) val notaryNode = mockNet.createNotaryNode(legalName = DUMMY_NOTARY.name, validating = false) - aliceServices = mockNet.createNode(legalName = ALICE_NAME).services + aliceServices = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME)).services mockNet.runNetwork() // Clear network map registration messages - notaryNode.internals.ensureRegistered() notaryServices = notaryNode.services notary = notaryServices.getDefaultNotary() alice = aliceServices.myInfo.singleIdentity() diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt index 3700848e47..1a1c74ea07 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt @@ -13,11 +13,12 @@ import net.corda.core.node.ServiceHub import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.getOrThrow -import net.corda.node.services.api.ServiceHubInternal +import net.corda.node.services.api.StartedNodeServices import net.corda.node.services.issueInvalidState import net.corda.testing.* import net.corda.testing.contracts.DummyContract import net.corda.testing.node.MockNetwork +import net.corda.testing.node.MockNodeParameters import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Before @@ -28,8 +29,8 @@ import kotlin.test.assertFailsWith class ValidatingNotaryServiceTests { lateinit var mockNet: MockNetwork - lateinit var notaryServices: ServiceHubInternal - lateinit var aliceServices: ServiceHubInternal + lateinit var notaryServices: StartedNodeServices + lateinit var aliceServices: StartedNodeServices lateinit var notary: Party lateinit var alice: Party @@ -37,9 +38,8 @@ class ValidatingNotaryServiceTests { fun setup() { mockNet = MockNetwork(cordappPackages = listOf("net.corda.testing.contracts")) val notaryNode = mockNet.createNotaryNode(legalName = DUMMY_NOTARY.name) - val aliceNode = mockNet.createNode(legalName = ALICE_NAME) + val aliceNode = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME)) mockNet.runNetwork() // Clear network map registration messages - notaryNode.internals.ensureRegistered() notaryServices = notaryNode.services aliceServices = aliceNode.services notary = notaryServices.getDefaultNotary() diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt new file mode 100644 index 0000000000..ae1ac4da8f --- /dev/null +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt @@ -0,0 +1,162 @@ +package net.corda.node.services.vault + +import co.paralleluniverse.fibers.Suspendable +import com.nhaarman.mockito_kotlin.* +import net.corda.core.contracts.* +import net.corda.core.flows.FinalityFlow +import net.corda.core.flows.FlowLogic +import net.corda.core.flows.FlowSession +import net.corda.core.flows.InitiatingFlow +import net.corda.core.identity.AbstractParty +import net.corda.core.internal.FlowStateMachine +import net.corda.core.internal.packageName +import net.corda.core.internal.uncheckedCast +import net.corda.core.node.StateLoader +import net.corda.core.node.services.KeyManagementService +import net.corda.core.node.services.queryBy +import net.corda.core.node.services.vault.QueryCriteria.SoftLockingCondition +import net.corda.core.node.services.vault.QueryCriteria.SoftLockingType.LOCKED_ONLY +import net.corda.core.node.services.vault.QueryCriteria.VaultQueryCriteria +import net.corda.core.transactions.LedgerTransaction +import net.corda.core.transactions.TransactionBuilder +import net.corda.core.utilities.NonEmptySet +import net.corda.core.utilities.OpaqueBytes +import net.corda.core.utilities.getOrThrow +import net.corda.core.utilities.unwrap +import net.corda.node.internal.InitiatedFlowFactory +import net.corda.node.services.api.VaultServiceInternal +import net.corda.testing.chooseIdentity +import net.corda.testing.node.MockNetwork +import net.corda.testing.rigorousMock +import net.corda.testing.node.MockNodeArgs +import net.corda.testing.node.MockNodeParameters +import org.junit.After +import org.junit.Test +import java.util.* +import java.util.concurrent.atomic.AtomicBoolean +import kotlin.reflect.jvm.jvmName +import kotlin.test.assertEquals + +class NodePair(private val mockNet: MockNetwork) { + private class ServerLogic(private val session: FlowSession, private val running: AtomicBoolean) : FlowLogic() { + @Suspendable + override fun call() { + running.set(true) + session.receive().unwrap { assertEquals("ping", it) } + session.send("pong") + } + } + + @InitiatingFlow + abstract class AbstractClientLogic(nodePair: NodePair) : FlowLogic() { + protected val server = nodePair.server.info.chooseIdentity() + protected abstract fun callImpl(): T + @Suspendable + override fun call() = callImpl().also { + initiateFlow(server).sendAndReceive("ping").unwrap { assertEquals("pong", it) } + } + } + + private val serverRunning = AtomicBoolean() + val server = mockNet.createNode() + var client = mockNet.createNode().apply { + internals.disableDBCloseOnStop() // Otherwise the in-memory database may disappear (taking the checkpoint with it) while we reboot the client. + } + private set + + fun communicate(clientLogic: AbstractClientLogic, rebootClient: Boolean): FlowStateMachine { + server.internals.internalRegisterFlowFactory(AbstractClientLogic::class.java, InitiatedFlowFactory.Core { ServerLogic(it, serverRunning) }, ServerLogic::class.java, false) + client.services.startFlow(clientLogic) + while (!serverRunning.get()) mockNet.runNetwork(1) + if (rebootClient) { + client.dispose() + client = mockNet.createNode(MockNodeParameters(client.internals.id)) + } + return uncheckedCast(client.smm.allStateMachines.single().stateMachine) + } +} + +class VaultSoftLockManagerTest { + private val mockVault = rigorousMock().also { + doNothing().whenever(it).softLockRelease(any(), anyOrNull()) + } + private val mockNet = MockNetwork(cordappPackages = listOf(ContractImpl::class.packageName), defaultFactory = object : MockNetwork.Factory { + override fun create(args: MockNodeArgs): MockNetwork.MockNode { + return object : MockNetwork.MockNode(args) { + override fun makeVaultService(keyManagementService: KeyManagementService, stateLoader: StateLoader): VaultServiceInternal { + val realVault = super.makeVaultService(keyManagementService, stateLoader) + return object : VaultServiceInternal by realVault { + override fun softLockRelease(lockId: UUID, stateRefs: NonEmptySet?) { + mockVault.softLockRelease(lockId, stateRefs) // No need to also call the real one for these tests. + } + } + } + } + } + }) + private val nodePair = NodePair(mockNet) + @After + fun tearDown() { + mockNet.stopNodes() + } + + object CommandDataImpl : CommandData + class ClientLogic(nodePair: NodePair, val state: ContractState) : NodePair.AbstractClientLogic>(nodePair) { + override fun callImpl() = run { + subFlow(FinalityFlow(serviceHub.signInitialTransaction(TransactionBuilder(notary = ourIdentity).apply { + addOutputState(state, ContractImpl::class.jvmName) + addCommand(CommandDataImpl, ourIdentity.owningKey) + }))) + serviceHub.vaultService.queryBy(VaultQueryCriteria(softLockingCondition = SoftLockingCondition(LOCKED_ONLY))).states.map { + it.state.data + } + } + } + + private abstract class ParticipantState(override val participants: List) : ContractState + + private class PlainOldState(participants: List) : ParticipantState(participants) { + constructor(nodePair: NodePair) : this(listOf(nodePair.client.info.chooseIdentity())) + } + + private class FungibleAssetImpl(participants: List) : ParticipantState(participants), FungibleAsset { + constructor(nodePair: NodePair) : this(listOf(nodePair.client.info.chooseIdentity())) + + override val owner get() = participants[0] + override fun withNewOwner(newOwner: AbstractParty) = throw UnsupportedOperationException() + override val amount get() = Amount(1, Issued(PartyAndReference(owner, OpaqueBytes.of(1)), Unit)) + override val exitKeys get() = throw UnsupportedOperationException() + override fun withNewOwnerAndAmount(newAmount: Amount>, newOwner: AbstractParty) = throw UnsupportedOperationException() + override fun equals(other: Any?) = other is FungibleAssetImpl && participants == other.participants + override fun hashCode() = participants.hashCode() + } + + class ContractImpl : Contract { + override fun verify(tx: LedgerTransaction) {} + } + + private fun run(expectSoftLock: Boolean, state: ContractState, checkpoint: Boolean) { + val fsm = nodePair.communicate(ClientLogic(nodePair, state), checkpoint) + mockNet.runNetwork() + if (expectSoftLock) { + assertEquals(listOf(state), fsm.resultFuture.getOrThrow()) + verify(mockVault).softLockRelease(fsm.id.uuid, null) + } else { + assertEquals(emptyList(), fsm.resultFuture.getOrThrow()) + // In this case we don't want softLockRelease called so that we avoid its expensive query, even after restore from checkpoint. + } + verifyNoMoreInteractions(mockVault) + } + + @Test + fun `plain old state is not soft locked`() = run(false, PlainOldState(nodePair), false) + + @Test + fun `plain old state is not soft locked with checkpoint`() = run(false, PlainOldState(nodePair), true) + + @Test + fun `fungible asset is soft locked`() = run(true, FungibleAssetImpl(nodePair), false) + + @Test + fun `fungible asset is soft locked with checkpoint`() = run(true, FungibleAssetImpl(nodePair), true) +} diff --git a/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkisRegistrationHelperTest.kt b/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkisRegistrationHelperTest.kt index 54ad6a4310..8454a03cd9 100644 --- a/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkisRegistrationHelperTest.kt +++ b/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkisRegistrationHelperTest.kt @@ -1,8 +1,6 @@ package net.corda.node.utilities.registration -import com.nhaarman.mockito_kotlin.any -import com.nhaarman.mockito_kotlin.eq -import com.nhaarman.mockito_kotlin.mock +import com.nhaarman.mockito_kotlin.* import net.corda.core.crypto.Crypto import net.corda.core.crypto.SecureHash import net.corda.core.identity.CordaX500Name @@ -11,6 +9,7 @@ import net.corda.node.utilities.X509Utilities import net.corda.node.utilities.getX509Certificate import net.corda.node.utilities.loadKeyStore import net.corda.testing.ALICE +import net.corda.testing.rigorousMock import net.corda.testing.testNodeConfiguration import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.style.BCStyle @@ -38,10 +37,9 @@ class NetworkRegistrationHelperTest { .map { CordaX500Name(commonName = it, organisation = "R3 Ltd", locality = "London", country = "GB") } val certs = identities.stream().map { X509Utilities.createSelfSignedCACertificate(it, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)) } .map { it.cert }.toTypedArray() - - val certService: NetworkRegistrationService = mock { - on { submitRequest(any()) }.then { id } - on { retrieveCertificates(eq(id)) }.then { certs } + val certService = rigorousMock().also { + doReturn(id).whenever(it).submitRequest(any()) + doReturn(certs).whenever(it).retrieveCertificates(eq(id)) } val config = testNodeConfiguration( diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashSelectionH2Test.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashSelectionH2Test.kt index 8929387a9e..5aa10b3179 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashSelectionH2Test.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/contracts/asset/CashSelectionH2Test.kt @@ -6,6 +6,7 @@ import com.r3.corda.enterprise.perftestcordapp.flows.CashException import com.r3.corda.enterprise.perftestcordapp.flows.CashPaymentFlow import net.corda.testing.chooseIdentity import net.corda.testing.node.MockNetwork +import net.corda.testing.node.MockNodeParameters import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Test @@ -17,11 +18,11 @@ class CashSelectionH2Test { val mockNet = MockNetwork(threadPerNode = true) try { val notaryNode = mockNet.createNotaryNode() - val bankA = mockNet.createNode(configOverrides = { existingConfig -> + val bankA = mockNet.createNode(MockNodeParameters(configOverrides = { existingConfig -> // Tweak connections to be minimal to make this easier (1 results in a hung node during start up, so use 2 connections). existingConfig.dataSourceProperties.setProperty("maximumPoolSize", "2") existingConfig - }) + })) mockNet.startNodes() diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlowTest.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlowTest.kt index ee6fc4e0e4..e050e98239 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlowTest.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlowTest.kt @@ -4,6 +4,14 @@ package com.r3.corda.enterprise.perftestcordapp.flows // from net.corda.node.messaging import co.paralleluniverse.fibers.Suspendable +import com.r3.corda.enterprise.perftestcordapp.DOLLARS +import com.r3.corda.enterprise.perftestcordapp.`issued by` +import com.r3.corda.enterprise.perftestcordapp.contracts.CommercialPaper +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.CASH +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.Cash +import com.r3.corda.enterprise.perftestcordapp.contracts.asset.fillWithSomeTestCash +import com.r3.corda.enterprise.perftestcordapp.flows.TwoPartyTradeFlow.Buyer +import com.r3.corda.enterprise.perftestcordapp.flows.TwoPartyTradeFlow.Seller import net.corda.core.concurrent.CordaFuture import net.corda.core.contracts.* import net.corda.core.crypto.* @@ -16,7 +24,6 @@ import net.corda.core.internal.FlowStateMachine import net.corda.core.internal.concurrent.map import net.corda.core.internal.rootCause import net.corda.core.messaging.DataFeed -import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.messaging.StateMachineTransactionMapping import net.corda.core.node.services.Vault import net.corda.core.serialization.CordaSerializable @@ -29,38 +36,24 @@ import net.corda.core.utilities.days import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.toNonEmptySet import net.corda.core.utilities.unwrap -import com.r3.corda.enterprise.perftestcordapp.DOLLARS -import com.r3.corda.enterprise.perftestcordapp.`issued by` -import com.r3.corda.enterprise.perftestcordapp.contracts.CommercialPaper -import com.r3.corda.enterprise.perftestcordapp.contracts.asset.CASH -import com.r3.corda.enterprise.perftestcordapp.contracts.asset.Cash -import com.r3.corda.enterprise.perftestcordapp.flows.TwoPartyTradeFlow.Buyer -import com.r3.corda.enterprise.perftestcordapp.flows.TwoPartyTradeFlow.Seller import net.corda.node.internal.StartedNode -import net.corda.node.services.api.WritableTransactionStorage -import net.corda.node.services.config.NodeConfiguration -import net.corda.node.services.persistence.DBTransactionStorage -import net.corda.node.utilities.CordaPersistence -import net.corda.nodeapi.internal.ServiceInfo -import net.corda.testing.* -import com.r3.corda.enterprise.perftestcordapp.contracts.asset.fillWithSomeTestCash import net.corda.node.services.api.Checkpoint import net.corda.node.services.api.CheckpointStorage -import net.corda.testing.node.InMemoryMessagingNetwork -import net.corda.testing.node.MockNetwork -import net.corda.testing.node.MockServices -import net.corda.testing.node.pumpReceive +import net.corda.node.services.api.WritableTransactionStorage +import net.corda.node.services.persistence.DBTransactionStorage +import net.corda.node.utilities.CordaPersistence +import net.corda.testing.* +import net.corda.testing.node.* import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Before +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized import rx.Observable import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream -import java.math.BigInteger -import java.security.KeyPair import java.util.* import java.util.jar.JarOutputStream import java.util.zip.ZipEntry @@ -88,6 +81,7 @@ internal fun CheckpointStorage.checkpoints(): List { * * We assume that Alice and Bob already found each other via some market, and have agreed the details already. */ +@Ignore @RunWith(Parameterized::class) class TwoPartyTradeFlowTests(val anonymous: Boolean) { companion object { @@ -117,7 +111,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { // We run this in parallel threads to help catch any race conditions that may exist. The other tests // we run in the unit test thread exclusively to speed things up, ensure deterministic results and // allow interruption half way through. - mockNet = MockNetwork(false, true, cordappPackages = cordappPackages) + mockNet = MockNetwork(networkSendManuallyPumped = true, cordappPackages = cordappPackages) ledger(MockServices(cordappPackages), initialiseSerialization = false) { val notaryNode = mockNet.createNotaryNode() val aliceNode = mockNet.createPartyNode(ALICE_NAME) @@ -167,7 +161,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { @Test(expected = InsufficientBalanceException::class) fun `trade cash for commercial paper fails using soft locking`() { - mockNet = MockNetwork(false, true, cordappPackages = cordappPackages) + mockNet = MockNetwork(networkSendManuallyPumped = true, cordappPackages = cordappPackages) ledger(MockServices(cordappPackages), initialiseSerialization = false) { val notaryNode = mockNet.createNotaryNode() val aliceNode = mockNet.createPartyNode(ALICE_NAME) @@ -223,7 +217,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { @Test fun `shutdown and restore`() { - mockNet = MockNetwork(false, cordappPackages = cordappPackages) + mockNet = MockNetwork(cordappPackages = cordappPackages) ledger(MockServices(cordappPackages), initialiseSerialization = false) { val notaryNode = mockNet.createNotaryNode() val aliceNode = mockNet.createPartyNode(ALICE_NAME) @@ -232,7 +226,6 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { aliceNode.internals.disableDBCloseOnStop() bobNode.internals.disableDBCloseOnStop() - val bobAddr = bobNode.network.myAddress as InMemoryMessagingNetwork.PeerHandle mockNet.runNetwork() // Clear network map registration messages val notary = notaryNode.services.getDefaultNotary() @@ -286,12 +279,11 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { // ... bring the node back up ... the act of constructing the SMM will re-register the message handlers // that Bob was waiting on before the reboot occurred. - bobNode = mockNet.createNode(bobAddr.id, object : MockNetwork.Factory { - override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, - id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): MockNetwork.MockNode { - return MockNetwork.MockNode(config, network, networkMapAddr, bobAddr.id, notaryIdentity, entropyRoot) + bobNode = mockNet.createNode(MockNodeParameters(legalName = BOB_NAME), nodeFactory = object : MockNetwork.Factory { + override fun create(args: MockNodeArgs): MockNetwork.MockNode { + return MockNetwork.MockNode(args) } - }, BOB_NAME) + }) // Find the future representing the result of this state machine again. val bobFuture = bobNode.smm.findStateMachines(BuyerAcceptor::class.java).single().second @@ -326,31 +318,26 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { // of gets and puts. private fun makeNodeWithTracking(name: CordaX500Name): StartedNode { // Create a node in the mock network ... - return mockNet.createNode(nodeFactory = object : MockNetwork.Factory { - override fun create(config: NodeConfiguration, - network: MockNetwork, - networkMapAddr: SingleMessageRecipient?, - id: Int, notaryIdentity: Pair?, - entropyRoot: BigInteger): MockNetwork.MockNode { - return object : MockNetwork.MockNode(config, network, networkMapAddr, id, notaryIdentity, entropyRoot) { + return mockNet.createNode(MockNodeParameters(legalName = name), nodeFactory = object : MockNetwork.Factory { + override fun create(args: MockNodeArgs): MockNetwork.MockNode { + return object : MockNetwork.MockNode(args) { // That constructs a recording tx storage override fun makeTransactionStorage(): WritableTransactionStorage { return RecordingTransactionStorage(database, super.makeTransactionStorage()) } } } - }, legalName = name) + }) } @Test fun `check dependencies of sale asset are resolved`() { - mockNet = MockNetwork(false, cordappPackages = cordappPackages) + mockNet = MockNetwork(cordappPackages = cordappPackages) val notaryNode = mockNet.createNotaryNode() val aliceNode = makeNodeWithTracking(ALICE_NAME) val bobNode = makeNodeWithTracking(BOB_NAME) val bankNode = makeNodeWithTracking(BOC_NAME) mockNet.runNetwork() - notaryNode.internals.ensureRegistered() val notary = aliceNode.services.getDefaultNotary() val alice = aliceNode.info.singleIdentity() val bob = bobNode.info.singleIdentity() @@ -451,14 +438,13 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { @Test fun `track works`() { - mockNet = MockNetwork(false, cordappPackages = cordappPackages) + mockNet = MockNetwork(cordappPackages = cordappPackages) val notaryNode = mockNet.createNotaryNode() val aliceNode = makeNodeWithTracking(ALICE_NAME) val bobNode = makeNodeWithTracking(BOB_NAME) val bankNode = makeNodeWithTracking(BOC_NAME) mockNet.runNetwork() - notaryNode.internals.ensureRegistered() val notary = aliceNode.services.getDefaultNotary() val alice: Party = aliceNode.info.singleIdentity() val bank: Party = bankNode.info.singleIdentity() @@ -533,7 +519,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { @Test fun `dependency with error on buyer side`() { - mockNet = MockNetwork(false, cordappPackages = cordappPackages) + mockNet = MockNetwork(cordappPackages = cordappPackages) ledger(MockServices(cordappPackages), initialiseSerialization = false) { runWithError(true, false, "at least one cash input") } @@ -541,7 +527,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { @Test fun `dependency with error on seller side`() { - mockNet = MockNetwork(false, cordappPackages = cordappPackages) + mockNet = MockNetwork(cordappPackages = cordappPackages) ledger(MockServices(cordappPackages), initialiseSerialization = false) { runWithError(false, true, "Issuances have a time-window") } @@ -614,7 +600,6 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { val bankNode = mockNet.createPartyNode(BOC_NAME) mockNet.runNetwork() - notaryNode.internals.ensureRegistered() val notary = aliceNode.services.getDefaultNotary() val alice = aliceNode.info.singleIdentity() val bob = bobNode.info.singleIdentity() diff --git a/samples/bank-of-corda-demo/build.gradle b/samples/bank-of-corda-demo/build.gradle index be0c49807d..d95d203fc4 100644 --- a/samples/bank-of-corda-demo/build.gradle +++ b/samples/bank-of-corda-demo/build.gradle @@ -55,7 +55,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { notary = [validating : true] p2pPort 10002 rpcPort 10003 - cordapps = ["net.corda:finance:$corda_release_version"] + cordapps = ["$project.group:finance:$corda_release_version"] } node { name "O=BankOfCorda,L=London,C=GB" @@ -63,7 +63,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { p2pPort 10005 rpcPort 10006 webPort 10007 - cordapps = ["net.corda:finance:$corda_release_version"] + cordapps = ["$project.group:finance:$corda_release_version"] rpcUsers = [ ['username' : "bankUser", 'password' : "test", @@ -79,7 +79,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { p2pPort 10008 rpcPort 10009 webPort 10010 - cordapps = ["net.corda:finance:$corda_release_version"] + cordapps = ["$project.group:finance:$corda_release_version"] rpcUsers = [ ['username' : "bigCorpUser", 'password' : "test", diff --git a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt index 81dbf12166..b526e580fe 100644 --- a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt +++ b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt @@ -5,6 +5,7 @@ import net.corda.bank.api.BankOfCordaClientApi import net.corda.bank.api.BankOfCordaWebApi.IssueRequestParams import net.corda.core.identity.CordaX500Name import net.corda.core.utilities.NetworkHostAndPort +import net.corda.finance.flows.CashConfigDataFlow import net.corda.finance.flows.CashExitFlow import net.corda.finance.flows.CashIssueAndPaymentFlow import net.corda.finance.flows.CashPaymentFlow @@ -23,8 +24,8 @@ fun main(args: Array) { BankOfCordaDriver().main(args) } -val BANK_USERNAME = "bankUser" -val BIGCORP_USERNAME = "bigCorpUser" +const val BANK_USERNAME = "bankUser" +const val BIGCORP_USERNAME = "bigCorpUser" val BIGCORP_LEGAL_NAME = CordaX500Name(organisation = "BigCorporation", locality = "New York", country = "US") @@ -53,41 +54,52 @@ private class BankOfCordaDriver { // The ISSUE_CASH will request some Cash from the ISSUER on behalf of Big Corporation node val role = options.valueOf(roleArg)!! - val requestParams = IssueRequestParams(options.valueOf(quantity), options.valueOf(currency), BIGCORP_LEGAL_NAME, - "1", BOC.name, DUMMY_NOTARY.name.copy(commonName = ValidatingNotaryService.id)) - try { when (role) { Role.ISSUER -> { driver(dsl = { + startNotaryNode(providedName = DUMMY_NOTARY.name, validating = true) val bankUser = User( BANK_USERNAME, "test", permissions = setOf( + startFlowPermission(), + startFlowPermission(), + startFlowPermission(), startFlowPermission(), - startFlowPermission())) - val bigCorpUser = User(BIGCORP_USERNAME, "test", - permissions = setOf( - startFlowPermission())) - startNotaryNode(DUMMY_NOTARY.name, validating = true) + startFlowPermission() + )) val bankOfCorda = startNode( providedName = BOC.name, rpcUsers = listOf(bankUser)) + val bigCorpUser = User(BIGCORP_USERNAME, "test", + permissions = setOf( + startFlowPermission(), + startFlowPermission())) startNode(providedName = BIGCORP_LEGAL_NAME, rpcUsers = listOf(bigCorpUser)) startWebserver(bankOfCorda.get()) waitForAllNodesToFinish() - }, isDebug = true) + }, isDebug = true, extraCordappPackagesToScan = listOf("net.corda.finance.contracts.asset")) } - Role.ISSUE_CASH_RPC -> { - println("Requesting Cash via RPC ...") - val result = BankOfCordaClientApi(NetworkHostAndPort("localhost", 10006)).requestRPCIssue(requestParams) - println("Success!! You transaction receipt is ${result.tx.id}") - } - Role.ISSUE_CASH_WEB -> { - println("Requesting Cash via Web ...") - val result = BankOfCordaClientApi(NetworkHostAndPort("localhost", 10007)).requestWebIssue(requestParams) - if (result) - println("Successfully processed Cash Issue request") + else -> { + val requestParams = IssueRequestParams(options.valueOf(quantity), options.valueOf(currency), BIGCORP_LEGAL_NAME, + "1", BOC.name, DUMMY_NOTARY.name.copy(commonName = ValidatingNotaryService.id)) + when(role) { + Role.ISSUE_CASH_RPC -> { + println("Requesting Cash via RPC ...") + val result = BankOfCordaClientApi(NetworkHostAndPort("localhost", 10004)).requestRPCIssue(requestParams) + println("Success!! You transaction receipt is ${result.tx.id}") + } + Role.ISSUE_CASH_WEB -> { + println("Requesting Cash via Web ...") + val result = BankOfCordaClientApi(NetworkHostAndPort("localhost", 10005)).requestWebIssue(requestParams) + if (result) + println("Successfully processed Cash Issue request") + } + else -> { + throw IllegalArgumentException("Unrecognized role: " + role) + } + } } } } catch (e: Exception) { diff --git a/samples/irs-demo/build.gradle b/samples/irs-demo/build.gradle index abbfaf3772..bcb20c214a 100644 --- a/samples/irs-demo/build.gradle +++ b/samples/irs-demo/build.gradle @@ -57,7 +57,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { p2pPort 10002 rpcPort 10003 webPort 10004 - cordapps = ["net.corda:finance:$corda_release_version"] + cordapps = ["$project.group:finance:$corda_release_version"] useTestClock true } node { @@ -65,7 +65,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { p2pPort 10005 rpcPort 10006 webPort 10007 - cordapps = ["net.corda:finance:$corda_release_version"] + cordapps = ["$project.group:finance:$corda_release_version"] useTestClock true } node { @@ -73,7 +73,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { p2pPort 10008 rpcPort 10009 webPort 10010 - cordapps = ["net.corda:finance:$corda_release_version"] + cordapps = ["$project.group:finance:$corda_release_version"] useTestClock true } } diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt b/samples/irs-demo/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt index 579de8cde6..bd694bd41b 100644 --- a/samples/irs-demo/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt +++ b/samples/irs-demo/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt @@ -145,7 +145,7 @@ object NodeInterestRates { } require(ftx.checkWithFun(::check)) - + ftx.checkCommandVisibility(services.myInfo.legalIdentities.first().owningKey) // It all checks out, so we can return a signature. // // Note that we will happily sign an invalid transaction, as we are only being presented with a filtered diff --git a/samples/irs-demo/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt b/samples/irs-demo/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt index d43192cdf8..40b84bd462 100644 --- a/samples/irs-demo/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt +++ b/samples/irs-demo/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt @@ -200,13 +200,13 @@ class NodeInterestRatesTest : TestDependencyInjectionBase() { @Test fun `network tearoff`() { - val mockNet = MockNetwork(initialiseSerialization = false, cordappPackages = listOf("net.corda.finance.contracts")) + val mockNet = MockNetwork(initialiseSerialization = false, cordappPackages = listOf("net.corda.finance.contracts", "net.corda.irs")) val n1 = mockNet.createNotaryNode() val oracleNode = mockNet.createNode().apply { internals.registerInitiatedFlow(NodeInterestRates.FixQueryHandler::class.java) internals.registerInitiatedFlow(NodeInterestRates.FixSignHandler::class.java) database.transaction { - internals.installCordaService(NodeInterestRates.Oracle::class.java).knownFixes = TEST_DATA + internals.findTokenizableService(NodeInterestRates.Oracle::class.java)!!.knownFixes = TEST_DATA } } val tx = makePartialTX() diff --git a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt index 0df1f7cdc2..95b203c7a2 100644 --- a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt +++ b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt @@ -45,7 +45,7 @@ class IRSSimulation(networkSendManuallyPumped: Boolean, runAsync: Boolean, laten private val executeOnNextIteration = Collections.synchronizedList(LinkedList<() -> Unit>()) override fun startMainSimulation(): CompletableFuture { - om = JacksonSupport.createInMemoryMapper(InMemoryIdentityService((banks + regulators + networkMap.internals + ratesOracle).flatMap { it.started!!.info.legalIdentitiesAndCerts }, trustRoot = DEV_TRUST_ROOT)) + om = JacksonSupport.createInMemoryMapper(InMemoryIdentityService((banks + regulators + ratesOracle).flatMap { it.started!!.info.legalIdentitiesAndCerts }, trustRoot = DEV_TRUST_ROOT)) registerFinanceJSONMappers(om) return startIRSDealBetween(0, 1).thenCompose { diff --git a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt index b9abcd6538..157f93eb58 100644 --- a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt +++ b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt @@ -1,26 +1,23 @@ package net.corda.netmap.simulation +import com.nhaarman.mockito_kotlin.doReturn +import com.nhaarman.mockito_kotlin.whenever import net.corda.core.flows.FlowLogic import net.corda.core.identity.CordaX500Name import net.corda.core.internal.uncheckedCast -import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.utilities.ProgressTracker import net.corda.finance.utils.CityDatabase import net.corda.finance.utils.WorldMapLocation import net.corda.irs.api.NodeInterestRates import net.corda.node.internal.StartedNode -import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.statemachine.StateMachineManager -import net.corda.nodeapi.internal.ServiceInfo -import net.corda.testing.* -import net.corda.testing.node.InMemoryMessagingNetwork -import net.corda.testing.node.MockNetwork -import net.corda.testing.node.TestClock -import net.corda.testing.node.setTo +import net.corda.testing.DUMMY_NOTARY +import net.corda.testing.DUMMY_REGULATOR +import net.corda.testing.node.* +import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import rx.Observable import rx.subjects.PublishSubject import java.math.BigInteger -import java.security.KeyPair import java.time.LocalDate import java.time.LocalDateTime import java.time.ZoneOffset @@ -38,6 +35,13 @@ import java.util.concurrent.Future abstract class Simulation(val networkSendManuallyPumped: Boolean, runAsync: Boolean, latencyInjector: InMemoryMessagingNetwork.LatencyCalculator?) { + companion object { + private val defaultParams // The get() is necessary so that entropyRoot isn't shared. + get() = MockNodeParameters(configOverrides = { + doReturn(makeTestDataSourceProperties(it.myLegalName.organisation)).whenever(it).dataSourceProperties + }) + } + init { if (!runAsync && latencyInjector != null) throw IllegalArgumentException("The latency injector is only useful when using manual pumping.") @@ -46,79 +50,29 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean, val bankLocations = listOf(Pair("London", "GB"), Pair("Frankfurt", "DE"), Pair("Rome", "IT")) // This puts together a mock network of SimulatedNodes. - - open class SimulatedNode(config: NodeConfiguration, mockNet: MockNetwork, networkMapAddress: SingleMessageRecipient?, - id: Int, notaryIdentity: Pair?, - entropyRoot: BigInteger) - : MockNetwork.MockNode(config, mockNet, networkMapAddress, id, notaryIdentity, entropyRoot) { + open class SimulatedNode(args: MockNodeArgs) : MockNetwork.MockNode(args) { override val started: StartedNode? get() = uncheckedCast(super.started) override fun findMyLocation(): WorldMapLocation? { return configuration.myLegalName.locality.let { CityDatabase[it] } } } - inner class BankFactory : MockNetwork.Factory { - var counter = 0 - - override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, - id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): SimulatedNode { - val letter = 'A' + counter - val (city, country) = bankLocations[counter++ % bankLocations.size] - - val cfg = testNodeConfiguration( - baseDirectory = config.baseDirectory, - myLegalName = CordaX500Name(organisation = "Bank $letter", locality = city, country = country)) - return SimulatedNode(cfg, network, networkMapAddr, id, notaryIdentity, entropyRoot) - } - - fun createAll(): List { - return bankLocations.mapIndexed { i, _ -> - // Use deterministic seeds so the simulation is stable. Needed so that party owning keys are stable. - mockNet.createUnstartedNode(nodeFactory = this, entropyRoot = BigInteger.valueOf(i.toLong())) - } - } - } - - val bankFactory = BankFactory() - - object NetworkMapNodeFactory : MockNetwork.Factory { - override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, - id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): SimulatedNode { - val cfg = testNodeConfiguration( - baseDirectory = config.baseDirectory, - myLegalName = DUMMY_MAP.name) - return object : SimulatedNode(cfg, network, networkMapAddr, id, notaryIdentity, entropyRoot) {} - } - } - - object NotaryNodeFactory : MockNetwork.Factory { - override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, - id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): SimulatedNode { - requireNotNull(config.notary) - val cfg = testNodeConfiguration( - baseDirectory = config.baseDirectory, - myLegalName = DUMMY_NOTARY.name, - notaryConfig = config.notary) - return SimulatedNode(cfg, network, networkMapAddr, id, notaryIdentity, entropyRoot) - } + private object SimulatedNodeFactory : MockNetwork.Factory { + override fun create(args: MockNodeArgs) = SimulatedNode(args) } object RatesOracleFactory : MockNetwork.Factory { // TODO: Make a more realistic legal name val RATES_SERVICE_NAME = CordaX500Name(organisation = "Rates Service Provider", locality = "Madrid", country = "ES") - override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, - id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): SimulatedNode { - val cfg = testNodeConfiguration( - baseDirectory = config.baseDirectory, - myLegalName = RATES_SERVICE_NAME) - return object : SimulatedNode(cfg, network, networkMapAddr, id, notaryIdentity, entropyRoot) { + override fun create(args: MockNodeArgs): SimulatedNode { + return object : SimulatedNode(args) { override fun start() = super.start().apply { registerInitiatedFlow(NodeInterestRates.FixQueryHandler::class.java) registerInitiatedFlow(NodeInterestRates.FixSignHandler::class.java) javaClass.classLoader.getResourceAsStream("net/corda/irs/simulation/example.rates.txt").use { database.transaction { - installCordaService(NodeInterestRates.Oracle::class.java).uploadFixes(it.reader().readText()) + findTokenizableService(NodeInterestRates.Oracle::class.java)!!.uploadFixes(it.reader().readText()) } } } @@ -126,31 +80,23 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean, } } - object RegulatorFactory : MockNetwork.Factory { - override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, - id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): SimulatedNode { - val cfg = testNodeConfiguration( - baseDirectory = config.baseDirectory, - myLegalName = DUMMY_REGULATOR.name) - return object : SimulatedNode(cfg, network, networkMapAddr, id, notaryIdentity, entropyRoot) { - // TODO: Regulatory nodes don't actually exist properly, this is a last minute demo request. - // So we just fire a message at a node that doesn't know how to handle it, and it'll ignore it. - // But that's fine for visualisation purposes. - } - } - } - - val mockNet = MockNetwork(networkSendManuallyPumped, runAsync, cordappPackages = listOf("net.corda.irs.contract", "net.corda.finance.contract")) - // This one must come first. - val networkMap = mockNet.startNetworkMapNode(nodeFactory = NetworkMapNodeFactory) - val notary = mockNet.createNotaryNode(validating = false, nodeFactory = NotaryNodeFactory) - val regulators = listOf(mockNet.createUnstartedNode(nodeFactory = RegulatorFactory)) - val ratesOracle = mockNet.createUnstartedNode(nodeFactory = RatesOracleFactory) - + val mockNet = MockNetwork( + networkSendManuallyPumped = networkSendManuallyPumped, + threadPerNode = runAsync, + cordappPackages = listOf("net.corda.irs.contract", "net.corda.finance.contract", "net.corda.irs")) + val notary = mockNet.createNotaryNode(defaultParams.copy(legalName = DUMMY_NOTARY.name), false, SimulatedNodeFactory) + // TODO: Regulatory nodes don't actually exist properly, this is a last minute demo request. + // So we just fire a message at a node that doesn't know how to handle it, and it'll ignore it. + // But that's fine for visualisation purposes. + val regulators = listOf(mockNet.createUnstartedNode(defaultParams.copy(legalName = DUMMY_REGULATOR.name), SimulatedNodeFactory)) + val ratesOracle = mockNet.createUnstartedNode(defaultParams.copy(legalName = RatesOracleFactory.RATES_SERVICE_NAME), RatesOracleFactory) // All nodes must be in one of these two lists for the purposes of the visualiser tool. - val serviceProviders: List = listOf(notary.internals, ratesOracle, networkMap.internals) - val banks: List = bankFactory.createAll() - + val serviceProviders: List = listOf(notary.internals, ratesOracle) + val banks: List = bankLocations.mapIndexed { i, (city, country) -> + val legalName = CordaX500Name(organisation = "Bank ${'A' + i}", locality = city, country = country) + // Use deterministic seeds so the simulation is stable. Needed so that party owning keys are stable. + mockNet.createUnstartedNode(defaultParams.copy(legalName = legalName, entropyRoot = BigInteger.valueOf(i.toLong())), SimulatedNodeFactory) + } val clocks = (serviceProviders + regulators + banks).map { it.platformClock as TestClock } // These are used from the network visualiser tool. diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt index 92669b5714..b3e295d1cb 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt @@ -19,14 +19,14 @@ import net.corda.testing.internal.demorun.notary import net.corda.testing.internal.demorun.rpcUsers import net.corda.testing.internal.demorun.runNodes -fun main(args: Array) = BFTNotaryCordform.runNodes() +fun main(args: Array) = BFTNotaryCordform().runNodes() private val clusterSize = 4 // Minimum size that tolerates a faulty replica. private val notaryNames = createNotaryNames(clusterSize) // This is not the intended final design for how to use CordformDefinition, please treat this as experimental and DO // NOT use this as a design to copy. -object BFTNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") { +class BFTNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") { private val clusterName = CordaX500Name(BFTNonValidatingNotaryService.id, "BFT", "Zurich", "CH") init { diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/Clean.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/Clean.kt index 1bab32c9a9..7ab6fff629 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/Clean.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/Clean.kt @@ -3,7 +3,7 @@ package net.corda.notarydemo import net.corda.testing.internal.demorun.clean fun main(args: Array) { - listOf(SingleNotaryCordform, RaftNotaryCordform, BFTNotaryCordform).forEach { + listOf(SingleNotaryCordform(), RaftNotaryCordform(), BFTNotaryCordform()).forEach { it.clean() } } diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/CustomNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/CustomNotaryCordform.kt index 900150da69..3cd43d7a0e 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/CustomNotaryCordform.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/CustomNotaryCordform.kt @@ -9,9 +9,9 @@ import net.corda.testing.BOB import net.corda.testing.DUMMY_NOTARY import net.corda.testing.internal.demorun.* -fun main(args: Array) = CustomNotaryCordform.runNodes() +fun main(args: Array) = CustomNotaryCordform().runNodes() -object CustomNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") { +class CustomNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") { init { node { name(ALICE.name) diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt index 198867dcf1..ff128c6edf 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt @@ -14,7 +14,7 @@ import net.corda.testing.ALICE import net.corda.testing.BOB import net.corda.testing.internal.demorun.* -fun main(args: Array) = RaftNotaryCordform.runNodes() +fun main(args: Array) = RaftNotaryCordform().runNodes() internal fun createNotaryNames(clusterSize: Int) = (0 until clusterSize).map { CordaX500Name("Notary Service $it", "Zurich", "CH") } @@ -22,7 +22,7 @@ private val notaryNames = createNotaryNames(3) // This is not the intended final design for how to use CordformDefinition, please treat this as experimental and DO // NOT use this as a design to copy. -object RaftNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") { +class RaftNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") { private val clusterName = CordaX500Name(RaftValidatingNotaryService.id, "Raft", "Zurich", "CH") init { diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/SingleNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/SingleNotaryCordform.kt index 22cc63540d..8e349494b8 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/SingleNotaryCordform.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/SingleNotaryCordform.kt @@ -17,13 +17,13 @@ import net.corda.testing.internal.demorun.notary import net.corda.testing.internal.demorun.rpcUsers import net.corda.testing.internal.demorun.runNodes -fun main(args: Array) = SingleNotaryCordform.runNodes() +fun main(args: Array) = SingleNotaryCordform().runNodes() val notaryDemoUser = User("demou", "demop", setOf(startFlowPermission(), startFlowPermission())) // This is not the intended final design for how to use CordformDefinition, please treat this as experimental and DO // NOT use this as a design to copy. -object SingleNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") { +class SingleNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") { init { node { name(ALICE.name) diff --git a/samples/simm-valuation-demo/build.gradle b/samples/simm-valuation-demo/build.gradle index 5ad72562d2..832e2eb283 100644 --- a/samples/simm-valuation-demo/build.gradle +++ b/samples/simm-valuation-demo/build.gradle @@ -68,14 +68,14 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { name "O=Notary Service,L=Zurich,C=CH" notary = [validating : true] p2pPort 10002 - cordapps = ["net.corda:finance:$corda_release_version"] + cordapps = ["$project.group:finance:$corda_release_version"] } node { name "O=Bank A,L=London,C=GB" p2pPort 10004 webPort 10005 rpcPort 10006 - cordapps = ["net.corda:finance:$corda_release_version"] + cordapps = ["$project.group:finance:$corda_release_version"] rpcUsers = ext.rpcUsers } node { @@ -83,7 +83,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { p2pPort 10007 webPort 10008 rpcPort 10009 - cordapps = ["net.corda:finance:$corda_release_version"] + cordapps = ["$project.group:finance:$corda_release_version"] rpcUsers = ext.rpcUsers } node { @@ -91,7 +91,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { p2pPort 10010 webPort 10011 rpcPort 10012 - cordapps = ["net.corda:finance:$corda_release_version"] + cordapps = ["$project.group:finance:$corda_release_version"] rpcUsers = ext.rpcUsers } } diff --git a/samples/trader-demo/build.gradle b/samples/trader-demo/build.gradle index e3d363e516..392ecd60ac 100644 --- a/samples/trader-demo/build.gradle +++ b/samples/trader-demo/build.gradle @@ -55,27 +55,27 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { name "O=Notary Service,L=Zurich,C=CH" notary = [validating : true] p2pPort 10002 - cordapps = ["net.corda:finance:$corda_release_version"] + cordapps = ["$project.group:finance:$corda_release_version"] } node { name "O=Bank A,L=London,C=GB" p2pPort 10005 rpcPort 10006 - cordapps = ["net.corda:finance:$corda_release_version"] + cordapps = ["$project.group:finance:$corda_release_version"] rpcUsers = ext.rpcUsers } node { name "O=Bank B,L=New York,C=US" p2pPort 10008 rpcPort 10009 - cordapps = ["net.corda:finance:$corda_release_version"] + cordapps = ["$project.group:finance:$corda_release_version"] rpcUsers = ext.rpcUsers } node { name "O=BankOfCorda,L=New York,C=US" p2pPort 10011 rpcPort 10012 - cordapps = ["net.corda:finance:$corda_release_version"] + cordapps = ["$project.group:finance:$corda_release_version"] rpcUsers = ext.rpcUsers } } diff --git a/testing/node-driver/src/main/kotlin/net/corda/node/testing/MockServiceHubInternal.kt b/testing/node-driver/src/main/kotlin/net/corda/node/testing/MockServiceHubInternal.kt deleted file mode 100644 index 1188e1571e..0000000000 --- a/testing/node-driver/src/main/kotlin/net/corda/node/testing/MockServiceHubInternal.kt +++ /dev/null @@ -1,90 +0,0 @@ -package net.corda.node.testing - -import com.codahale.metrics.MetricRegistry -import net.corda.core.flows.FlowInitiator -import net.corda.core.flows.FlowLogic -import net.corda.core.identity.Party -import net.corda.core.node.NodeInfo -import net.corda.core.node.StateLoader -import net.corda.core.node.services.* -import net.corda.core.serialization.SerializeAsToken -import net.corda.node.internal.InitiatedFlowFactory -import net.corda.node.internal.StateLoaderImpl -import net.corda.node.internal.cordapp.CordappLoader -import net.corda.node.internal.cordapp.CordappProviderImpl -import net.corda.node.internal.cordapp.CordappProviderInternal -import net.corda.node.serialization.NodeClock -import net.corda.node.services.api.* -import net.corda.node.services.config.NodeConfiguration -import net.corda.node.services.messaging.MessagingService -import net.corda.node.services.statemachine.FlowStateMachineImpl -import net.corda.node.services.statemachine.StateMachineManager -import net.corda.node.services.transactions.InMemoryTransactionVerifierService -import net.corda.node.utilities.CordaPersistence -import net.corda.testing.DUMMY_IDENTITY_1 -import net.corda.testing.MOCK_HOST_AND_PORT -import net.corda.testing.MOCK_IDENTITY_SERVICE -import net.corda.testing.node.MockAttachmentStorage -import net.corda.testing.node.MockNetworkMapCache -import net.corda.testing.node.MockStateMachineRecordedTransactionMappingStorage -import net.corda.testing.node.MockTransactionStorage -import java.nio.file.Paths -import java.sql.Connection -import java.time.Clock - -open class MockServiceHubInternal( - override val database: CordaPersistence, - override val configuration: NodeConfiguration, - val customVault: VaultServiceInternal? = null, - val keyManagement: KeyManagementService? = null, - val network: MessagingService? = null, - val identity: IdentityService? = MOCK_IDENTITY_SERVICE, - override val attachments: AttachmentStorage = MockAttachmentStorage(), - override val validatedTransactions: WritableTransactionStorage = MockTransactionStorage(), - override val stateMachineRecordedTransactionMapping: StateMachineRecordedTransactionMappingStorage = MockStateMachineRecordedTransactionMappingStorage(), - val mapCache: NetworkMapCacheInternal? = null, - val scheduler: SchedulerService? = null, - val overrideClock: Clock? = NodeClock(), - val customContractUpgradeService: ContractUpgradeService? = null, - val customTransactionVerifierService: TransactionVerifierService? = InMemoryTransactionVerifierService(2), - override val cordappProvider: CordappProviderInternal = CordappProviderImpl(CordappLoader.createDefault(Paths.get(".")), attachments), - protected val stateLoader: StateLoaderImpl = StateLoaderImpl(validatedTransactions) -) : ServiceHubInternal, StateLoader by stateLoader { - override val transactionVerifierService: TransactionVerifierService - get() = customTransactionVerifierService ?: throw UnsupportedOperationException() - override val vaultService: VaultServiceInternal - get() = customVault ?: throw UnsupportedOperationException() - override val contractUpgradeService: ContractUpgradeService - get() = customContractUpgradeService ?: throw UnsupportedOperationException() - override val keyManagementService: KeyManagementService - get() = keyManagement ?: throw UnsupportedOperationException() - override val identityService: IdentityService - get() = identity ?: throw UnsupportedOperationException() - override val networkService: MessagingService - get() = network ?: throw UnsupportedOperationException() - override val networkMapCache: NetworkMapCacheInternal - get() = mapCache ?: MockNetworkMapCache(this) - override val schedulerService: SchedulerService - get() = scheduler ?: throw UnsupportedOperationException() - override val clock: Clock - get() = overrideClock ?: throw UnsupportedOperationException() - override val myInfo: NodeInfo - get() = NodeInfo(listOf(MOCK_HOST_AND_PORT), listOf(DUMMY_IDENTITY_1), 1, serial = 1L) // Required to get a dummy platformVersion when required for tests. - override val monitoringService: MonitoringService = MonitoringService(MetricRegistry()) - override val rpcFlows: List>> - get() = throw UnsupportedOperationException() - override val schemaService get() = throw UnsupportedOperationException() - override val auditService: AuditService = DummyAuditService() - - lateinit var smm: StateMachineManager - - override fun cordaService(type: Class): T = throw UnsupportedOperationException() - - override fun startFlow(logic: FlowLogic, flowInitiator: FlowInitiator, ourIdentity: Party?): FlowStateMachineImpl { - return smm.executor.fetchFrom { smm.add(logic, flowInitiator, ourIdentity) } - } - - override fun getFlowFactory(initiatingFlowClass: Class>): InitiatedFlowFactory<*>? = null - - override fun jdbcSession(): Connection = database.createSession() -} diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt index 551a8075b8..768b7771c6 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt @@ -2,14 +2,16 @@ package net.corda.testing -import com.nhaarman.mockito_kotlin.spy +import com.nhaarman.mockito_kotlin.doCallRealMethod +import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever import net.corda.core.identity.CordaX500Name import net.corda.core.node.ServiceHub import net.corda.core.transactions.TransactionBuilder +import net.corda.node.services.config.CertChainPolicyConfig import net.corda.node.services.config.NodeConfiguration -import net.corda.node.services.config.NotaryConfig import net.corda.node.services.config.VerifierType +import net.corda.nodeapi.User import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties @@ -54,26 +56,31 @@ fun transaction( fun testNodeConfiguration( baseDirectory: Path, - myLegalName: CordaX500Name, - notaryConfig: NotaryConfig? = null): NodeConfiguration { + myLegalName: CordaX500Name): NodeConfiguration { abstract class MockableNodeConfiguration : NodeConfiguration // Otherwise Mockito is defeated by val getters. - - val nc = spy() - whenever(nc.baseDirectory).thenReturn(baseDirectory) - whenever(nc.myLegalName).thenReturn(myLegalName) - whenever(nc.minimumPlatformVersion).thenReturn(1) - whenever(nc.keyStorePassword).thenReturn("cordacadevpass") - whenever(nc.trustStorePassword).thenReturn("trustpass") - whenever(nc.rpcUsers).thenReturn(emptyList()) - whenever(nc.notary).thenReturn(notaryConfig) - whenever(nc.dataSourceProperties).thenReturn(makeTestDataSourceProperties(myLegalName.organisation)) - whenever(nc.database).thenReturn(makeTestDatabaseProperties()) - whenever(nc.emailAddress).thenReturn("") - whenever(nc.exportJMXto).thenReturn("") - whenever(nc.devMode).thenReturn(true) - whenever(nc.certificateSigningService).thenReturn(URL("http://localhost")) - whenever(nc.certificateChainCheckPolicies).thenReturn(emptyList()) - whenever(nc.verifierType).thenReturn(VerifierType.InMemory) - whenever(nc.messageRedeliveryDelaySeconds).thenReturn(5) - return nc + return rigorousMock().also { + doReturn(true).whenever(it).noNetworkMapServiceMode + doReturn(baseDirectory).whenever(it).baseDirectory + doReturn(myLegalName).whenever(it).myLegalName + doReturn(1).whenever(it).minimumPlatformVersion + doReturn("cordacadevpass").whenever(it).keyStorePassword + doReturn("trustpass").whenever(it).trustStorePassword + doReturn(emptyList()).whenever(it).rpcUsers + doReturn(null).whenever(it).notary + doReturn(makeTestDataSourceProperties(myLegalName.organisation)).whenever(it).dataSourceProperties + doReturn(makeTestDatabaseProperties()).whenever(it).database + doReturn("").whenever(it).emailAddress + doReturn("").whenever(it).exportJMXto + doReturn(true).whenever(it).devMode + doReturn(URL("http://localhost")).whenever(it).certificateSigningService + doReturn(emptyList()).whenever(it).certificateChainCheckPolicies + doReturn(VerifierType.InMemory).whenever(it).verifierType + doReturn(5).whenever(it).messageRedeliveryDelaySeconds + doReturn(0L).whenever(it).additionalNodeInfoPollingFrequencyMsec + doReturn(null).whenever(it).networkMapService + doCallRealMethod().whenever(it).certificatesDirectory + doCallRealMethod().whenever(it).trustStoreFile + doCallRealMethod().whenever(it).sslKeystore + doCallRealMethod().whenever(it).nodeKeystore + } } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/RPCDriver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/RPCDriver.kt index 73edc52dc8..f5375bd355 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/RPCDriver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/RPCDriver.kt @@ -228,7 +228,6 @@ fun rpcDriver( extraSystemProperties: Map = emptyMap(), useTestClock: Boolean = false, initialiseSerialization: Boolean = true, - networkMapStartStrategy: NetworkMapStartStrategy = NetworkMapStartStrategy.Dedicated(startAutomatically = false), startNodesInProcess: Boolean = false, extraCordappPackagesToScan: List = emptyList(), dsl: RPCDriverExposedDSLInterface.() -> A @@ -240,7 +239,6 @@ fun rpcDriver( extraSystemProperties = extraSystemProperties, driverDirectory = driverDirectory.toAbsolutePath(), useTestClock = useTestClock, - networkMapStartStrategy = networkMapStartStrategy, isDebug = isDebug, startNodesInProcess = startNodesInProcess, extraCordappPackagesToScan = extraCordappPackagesToScan diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt index c44b9a88a9..ebde49f03d 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -8,7 +8,6 @@ import com.typesafe.config.ConfigRenderOptions import net.corda.client.rpc.CordaRPCClient import net.corda.cordform.CordformContext import net.corda.cordform.CordformNode -import net.corda.cordform.NodeDefinition import net.corda.core.CordaException import net.corda.core.concurrent.CordaFuture import net.corda.core.concurrent.firstOf @@ -20,6 +19,8 @@ import net.corda.core.internal.div import net.corda.core.internal.times import net.corda.core.messaging.CordaRPCOps import net.corda.core.node.NodeInfo +import net.corda.core.node.services.NetworkMapCache +import net.corda.core.toFuture import net.corda.core.utilities.* import net.corda.node.internal.Node import net.corda.node.internal.NodeStartup @@ -28,6 +29,7 @@ import net.corda.node.internal.cordapp.CordappLoader import net.corda.node.services.config.* import net.corda.node.services.network.NetworkMapService import net.corda.node.utilities.ServiceIdentityGenerator +import net.corda.nodeapi.NodeInfoFilesCopier import net.corda.nodeapi.User import net.corda.nodeapi.config.parseAs import net.corda.nodeapi.config.toConfig @@ -37,6 +39,8 @@ import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO import okhttp3.OkHttpClient import okhttp3.Request import org.slf4j.Logger +import rx.Observable +import rx.observables.ConnectableObservable import java.io.File import java.net.* import java.nio.file.Path @@ -150,14 +154,6 @@ interface DriverDSLExposedInterface : CordformContext { */ fun startWebserver(handle: NodeHandle, maximumHeapSize: String): CordaFuture - /** - * Starts a network map service node. Note that only a single one should ever be running, so you will probably want - * to set networkMapStartStrategy to Dedicated(false) in your [driver] call. - * @param startInProcess Determines if the node should be started inside this process. If null the Driver-level - * value will be used. - */ - fun startDedicatedNetworkMapService(startInProcess: Boolean? = null, maximumHeapSize: String = "200m"): CordaFuture - fun waitForAllNodesToFinish() /** @@ -212,13 +208,15 @@ sealed class NodeHandle { override val configuration: FullNodeConfiguration, override val webAddress: NetworkHostAndPort, val debugPort: Int?, - val process: Process + val process: Process, + private val onStopCallback: () -> Unit ) : NodeHandle() { override fun stop(): CordaFuture { with(process) { destroy() waitFor() } + onStopCallback() return doneFuture(Unit) } } @@ -229,7 +227,8 @@ sealed class NodeHandle { override val configuration: FullNodeConfiguration, override val webAddress: NetworkHostAndPort, val node: StartedNode, - val nodeThread: Thread + val nodeThread: Thread, + private val onStopCallback: () -> Unit ) : NodeHandle() { override fun stop(): CordaFuture { node.dispose() @@ -237,6 +236,7 @@ sealed class NodeHandle { interrupt() join() } + onStopCallback() return doneFuture(Unit) } } @@ -273,9 +273,8 @@ sealed class PortAllocation { } } -/** - * Helper builder for configuring a [Node] from Java. - */ +/** Helper builder for configuring a [Node] from Java. */ +@Suppress("unused") data class NodeParameters( val providedName: CordaX500Name? = null, val rpcUsers: List = emptyList(), @@ -319,7 +318,6 @@ data class NodeParameters( * @param debugPortAllocation The port allocation strategy to use for jvm debugging. Defaults to incremental. * @param extraSystemProperties A Map of extra system properties which will be given to each new node. Defaults to empty. * @param useTestClock If true the test clock will be used in Node. - * @param networkMapStartStrategy Determines whether a network map node is started automatically. * @param startNodesInProcess Provides the default behaviour of whether new nodes should start inside this process or * not. Note that this may be overridden in [DriverDSLExposedInterface.startNode]. * @param dsl The dsl itself. @@ -334,7 +332,7 @@ fun driver( extraSystemProperties: Map = defaultParameters.extraSystemProperties, useTestClock: Boolean = defaultParameters.useTestClock, initialiseSerialization: Boolean = defaultParameters.initialiseSerialization, - networkMapStartStrategy: NetworkMapStartStrategy = defaultParameters.networkMapStartStrategy, + startNodesInProcess: Boolean = defaultParameters.startNodesInProcess, extraCordappPackagesToScan: List = defaultParameters.extraCordappPackagesToScan, dsl: DriverDSLExposedInterface.() -> A @@ -347,7 +345,6 @@ fun driver( driverDirectory = driverDirectory.toAbsolutePath(), useTestClock = useTestClock, isDebug = isDebug, - networkMapStartStrategy = networkMapStartStrategy, startNodesInProcess = startNodesInProcess, extraCordappPackagesToScan = extraCordappPackagesToScan ), @@ -371,9 +368,8 @@ fun driver( return driver(defaultParameters = parameters, dsl = dsl) } -/** - * Helper builder for configuring a [driver] from Java. - */ +/** Helper builder for configuring a [driver] from Java. */ +@Suppress("unused") data class DriverParameters( val isDebug: Boolean = false, val driverDirectory: Path = Paths.get("build", getTimestampAsDirectoryName()), @@ -382,7 +378,6 @@ data class DriverParameters( val extraSystemProperties: Map = emptyMap(), val useTestClock: Boolean = false, val initialiseSerialization: Boolean = true, - val networkMapStartStrategy: NetworkMapStartStrategy = NetworkMapStartStrategy.Dedicated(startAutomatically = true), val startNodesInProcess: Boolean = false, val extraCordappPackagesToScan: List = emptyList() ) { @@ -393,7 +388,6 @@ data class DriverParameters( fun setExtraSystemProperties(extraSystemProperties: Map) = copy(extraSystemProperties = extraSystemProperties) fun setUseTestClock(useTestClock: Boolean) = copy(useTestClock = useTestClock) fun setInitialiseSerialization(initialiseSerialization: Boolean) = copy(initialiseSerialization = initialiseSerialization) - fun setNetworkMapStartStrategy(networkMapStartStrategy: NetworkMapStartStrategy) = copy(networkMapStartStrategy = networkMapStartStrategy) fun setStartNodesInProcess(startNodesInProcess: Boolean) = copy(startNodesInProcess = startNodesInProcess) fun setExtraCordappPackagesToScan(extraCordappPackagesToScan: List) = copy(extraCordappPackagesToScan = extraCordappPackagesToScan) } @@ -608,11 +602,9 @@ class DriverDSL( val driverDirectory: Path, val useTestClock: Boolean, val isDebug: Boolean, - val networkMapStartStrategy: NetworkMapStartStrategy, val startNodesInProcess: Boolean, extraCordappPackagesToScan: List ) : DriverDSLInternalInterface { - private val dedicatedNetworkMapAddress = portAllocation.nextHostAndPort() private var _executorService: ScheduledExecutorService? = null val executorService get() = _executorService!! private var _shutdownManager: ShutdownManager? = null @@ -620,6 +612,12 @@ class DriverDSL( private val databaseNamesByNode = mutableMapOf() val systemProperties by lazy { System.getProperties().toList().map { it.first.toString() to it.second.toString() }.toMap() + extraSystemProperties } private val cordappPackages = extraCordappPackagesToScan + getCallerPackage() + // TODO: this object will copy NodeInfo files from started nodes to other nodes additional-node-infos/ + // This uses the FileSystem and adds a delay (~5 seconds) given by the time we wait before polling the file system. + // Investigate whether we can avoid that. + private val nodeInfoFilesCopier = NodeInfoFilesCopier() + // Map from a nodes legal name to an observable emitting the number of nodes in its network map. + private val countObservables = mutableMapOf>() class State { val processes = ArrayList>() @@ -676,25 +674,6 @@ class DriverDSL( } } - private fun networkMapServiceConfigLookup(networkMapCandidates: List): (CordaX500Name) -> Map? { - return networkMapStartStrategy.run { - when (this) { - is NetworkMapStartStrategy.Dedicated -> { - serviceConfig(dedicatedNetworkMapAddress).let { - { _: CordaX500Name -> it } - } - } - is NetworkMapStartStrategy.Nominated -> { - serviceConfig(networkMapCandidates.single { - it.name == legalName.toString() - }.config.getString("p2pAddress").let(NetworkHostAndPort.Companion::parse)).let { - { nodeName: CordaX500Name -> if (nodeName == legalName) null else it } - } - } - } - } - } - override fun startNode( defaultParameters: NodeParameters, providedName: CordaX500Name?, @@ -710,10 +689,6 @@ class DriverDSL( val webAddress = portAllocation.nextHostAndPort() // TODO: Derive name from the full picked name, don't just wrap the common name val name = providedName ?: CordaX500Name(organisation = "${oneOf(names).organisation}-${p2pAddress.port}", locality = "London", country = "GB") - val networkMapServiceConfigLookup = networkMapServiceConfigLookup(listOf(object : NodeDefinition { - override fun getName() = name.toString() - override fun getConfig() = configOf("p2pAddress" to p2pAddress.toString()) - })) val config = ConfigHelper.loadConfig( baseDirectory = baseDirectory(name), allowMissingConfig = true, @@ -722,10 +697,10 @@ class DriverDSL( "p2pAddress" to p2pAddress.toString(), "rpcAddress" to rpcAddress.toString(), "webAddress" to webAddress.toString(), - "networkMapService" to networkMapServiceConfigLookup(name), "useTestClock" to useTestClock, "rpcUsers" to if (rpcUsers.isEmpty()) defaultRpcUserList else rpcUsers.map { it.toConfig().root().unwrapped() }, - "verifierType" to verifierType.name + "verifierType" to verifierType.name, + "noNetworkMapServiceMode" to true ) + customOverrides ) return startNodeInternal(name, config, webAddress, startInSameProcess, maximumHeapSize, logLevel) @@ -741,7 +716,6 @@ class DriverDSL( } override fun startNodes(nodes: List, startInSameProcess: Boolean?, maximumHeapSize: String): List> { - val networkMapServiceConfigLookup = networkMapServiceConfigLookup(nodes) return nodes.map { node -> portAllocation.nextHostAndPort() // rpcAddress val webAddress = portAllocation.nextHostAndPort() @@ -752,8 +726,8 @@ class DriverDSL( baseDirectory = baseDirectory(name), allowMissingConfig = true, configOverrides = node.config + notary + mapOf( - "networkMapService" to networkMapServiceConfigLookup(name), - "rpcUsers" to if (rpcUsers.isEmpty()) defaultRpcUserList else rpcUsers + "rpcUsers" to if (rpcUsers.isEmpty()) defaultRpcUserList else rpcUsers, + "noNetworkMapServiceMode" to true ) ) startNodeInternal(name, config, webAddress, startInSameProcess, maximumHeapSize) @@ -839,9 +813,7 @@ class DriverDSL( override fun start() { _executorService = Executors.newScheduledThreadPool(2, ThreadFactoryBuilder().setNameFormat("driver-pool-thread-%d").build()) _shutdownManager = ShutdownManager(executorService) - if (networkMapStartStrategy.startDedicated) { - startDedicatedNetworkMapService().andForget(log) // Allow it to start concurrently with other nodes. - } + shutdownManager.registerShutdown { nodeInfoFilesCopier.close() } } fun baseDirectory(nodeName: CordaX500Name): Path { @@ -852,29 +824,51 @@ class DriverDSL( override fun baseDirectory(nodeName: String): Path = baseDirectory(CordaX500Name.parse(nodeName)) - override fun startDedicatedNetworkMapService(startInProcess: Boolean?, maximumHeapSize: String): CordaFuture { - val webAddress = portAllocation.nextHostAndPort() - val rpcAddress = portAllocation.nextHostAndPort() - val networkMapLegalName = networkMapStartStrategy.legalName - - val config = ConfigHelper.loadConfig( - baseDirectory = baseDirectory(networkMapLegalName), - allowMissingConfig = true, - configOverrides = configOf( - "myLegalName" to networkMapLegalName.toString(), - // TODO: remove the webAddress as NMS doesn't need to run a web server. This will cause all - // node port numbers to be shifted, so all demos and docs need to be updated accordingly. - "webAddress" to webAddress.toString(), - "rpcAddress" to rpcAddress.toString(), - "rpcUsers" to defaultRpcUserList, - "p2pAddress" to dedicatedNetworkMapAddress.toString(), - "useTestClock" to useTestClock) - ) - return startNodeInternal(networkMapLegalName, config, webAddress, startInProcess, maximumHeapSize) + /** + * @param initial number of nodes currently in the network map of a running node. + * @param networkMapCacheChangeObservable an observable returning the updates to the node network map. + * @return a [ConnectableObservable] which emits a new [Int] every time the number of registered nodes changes + * the initial value emitted is always [initial] + */ + private fun nodeCountObservable(initial: Int, networkMapCacheChangeObservable: Observable): + ConnectableObservable { + val count = AtomicInteger(initial) + return networkMapCacheChangeObservable.map { it -> + when (it) { + is NetworkMapCache.MapChange.Added -> count.incrementAndGet() + is NetworkMapCache.MapChange.Removed -> count.decrementAndGet() + is NetworkMapCache.MapChange.Modified -> count.get() + } + }.startWith(initial).replay() } - private fun startNodeInternal(name: CordaX500Name, config: Config, webAddress: NetworkHostAndPort, startInProcess: Boolean?, maximumHeapSize: String, logLevel: String? = null): CordaFuture { + /** + * @param rpc the [CordaRPCOps] of a newly started node. + * @return a [CordaFuture] which resolves when every node started by driver has in its network map a number of nodes + * equal to the number of running nodes. The future will yield the number of connected nodes. + */ + private fun allNodesConnected(rpc: CordaRPCOps): CordaFuture { + val (snapshot, updates) = rpc.networkMapFeed() + val counterObservable = nodeCountObservable(snapshot.size, updates) + countObservables.put(rpc.nodeInfo().legalIdentities.first().name, counterObservable) + /* TODO: this might not always be the exact number of nodes one has to wait for, + * for example in the following sequence + * 1 start 3 nodes in order, A, B, C. + * 2 before the future returned by this function resolves, kill B + * At that point this future won't ever resolve as it will wait for nodes to know 3 other nodes. + */ + val requiredNodes = countObservables.size + // This is an observable which yield the minimum number of nodes in each node network map. + val smallestSeenNetworkMapSize = Observable.combineLatest(countObservables.values.toList()) { args : Array -> + args.map { it as Int }.min() ?: 0 + } + val future = smallestSeenNetworkMapSize.filter { it >= requiredNodes }.toFuture() + counterObservable.connect() + return future + } + + private fun startNodeInternal(name: CordaX500Name,config: Config, webAddress: NetworkHostAndPort, startInProcess: Boolean?, maximumHeapSize: String, logLevel: String? = null): CordaFuture { val globalDataSourceProperties = mutableMapOf() val overriddenDatasourceUrl = systemProperties["dataSourceProperties.dataSource.url"] @@ -882,9 +876,13 @@ class DriverDSL( val connectionString = overriddenDatasourceUrl + "/" + databaseNamesByNode.computeIfAbsent(name, { UUID.randomUUID().toString() }) globalDataSourceProperties["dataSourceProperties.dataSource.url"] = connectionString } - val enhancedConfig = config + globalDataSourceProperties + val enhancedConfig = config+ globalDataSourceProperties val nodeConfiguration = (enhancedConfig).parseAs() - + nodeInfoFilesCopier.addConfig(nodeConfiguration.baseDirectory) + val onNodeExit: () -> Unit = { + nodeInfoFilesCopier.removeConfig(nodeConfiguration.baseDirectory) + countObservables.remove(nodeConfiguration.myLegalName) + } if (startInProcess ?: startNodesInProcess) { val nodeAndThreadFuture = startInProcessNode(executorService, nodeConfiguration, enhancedConfig, cordappPackages) shutdownManager.registerShutdown( @@ -897,8 +895,8 @@ class DriverDSL( ) return nodeAndThreadFuture.flatMap { (node, thread) -> establishRpc(nodeConfiguration, openFuture()).flatMap { rpc -> - rpc.waitUntilNetworkReady().map { - NodeHandle.InProcess(rpc.nodeInfo(), rpc, nodeConfiguration, webAddress, node, thread) + allNodesConnected(rpc).map { + NodeHandle.InProcess(rpc.nodeInfo(), rpc, nodeConfiguration, webAddress, node, thread, onNodeExit) } } } @@ -913,7 +911,7 @@ class DriverDSL( establishRpc(nodeConfiguration, processDeathFuture).flatMap { rpc -> // Call waitUntilNetworkReady in background in case RPC is failing over: val forked = executorService.fork { - rpc.waitUntilNetworkReady() + allNodesConnected(rpc) } val networkMapFuture = forked.flatMap { it } firstOf(processDeathFuture, networkMapFuture) { @@ -922,7 +920,8 @@ class DriverDSL( } processDeathFuture.cancel(false) log.info("Node handle is ready. NodeInfo: ${rpc.nodeInfo()}, WebAddress: ${webAddress}") - NodeHandle.OutOfProcess(rpc.nodeInfo(), rpc, nodeConfiguration, webAddress, debugPort, process) + NodeHandle.OutOfProcess(rpc.nodeInfo(), rpc, nodeConfiguration, webAddress, debugPort, process, + onNodeExit) } } } @@ -977,7 +976,7 @@ class DriverDSL( logLevel: String? = null ): CordaFuture { val processFuture = executorService.fork { - log.info("Starting out-of-process Node ${nodeConf.myLegalName.organisation}") + log.info("Starting out-of-process Node ${nodeConf.myLegalName.organisation}, debug port is " + debugPort ?: "not enabled") // Write node.conf writeConfig(nodeConf.baseDirectory, "node.conf", config) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/NetworkMapStartStrategy.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/NetworkMapStartStrategy.kt deleted file mode 100644 index 0086884c77..0000000000 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/NetworkMapStartStrategy.kt +++ /dev/null @@ -1,23 +0,0 @@ -package net.corda.testing.driver - -import net.corda.core.identity.CordaX500Name -import net.corda.core.utilities.NetworkHostAndPort -import net.corda.testing.DUMMY_MAP - -sealed class NetworkMapStartStrategy { - internal abstract val startDedicated: Boolean - internal abstract val legalName: CordaX500Name - internal fun serviceConfig(address: NetworkHostAndPort) = mapOf( - "address" to address.toString(), - "legalName" to legalName.toString() - ) - - class Dedicated(startAutomatically: Boolean) : NetworkMapStartStrategy() { - override val startDedicated = startAutomatically - override val legalName = DUMMY_MAP.name - } - - class Nominated(override val legalName: CordaX500Name) : NetworkMapStartStrategy() { - override val startDedicated = false - } -} diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt index a44cf1c4a5..406e72dda0 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt @@ -279,7 +279,7 @@ class InMemoryMessagingNetwork( _sentMessages.onNext(transfer) } - private data class InMemoryMessage(override val topicSession: TopicSession, + data class InMemoryMessage(override val topicSession: TopicSession, override val data: ByteArray, override val uniqueMessageId: UUID, override val debugTimestamp: Instant = Instant.now()) : Message { @@ -363,14 +363,22 @@ class InMemoryMessagingNetwork( state.locked { check(handlers.remove(registration as Handler)) } } - override fun send(message: Message, target: MessageRecipients, retryId: Long?) { + override fun send(message: Message, target: MessageRecipients, retryId: Long?, sequenceKey: Any, acknowledgementHandler: (() -> Unit)?) { check(running) msgSend(this, message, target) + acknowledgementHandler?.invoke() if (!sendManuallyPumped) { pumpSend(false) } } + override fun send(addressedMessages: List, acknowledgementHandler: (() -> Unit)?) { + for ((message, target, retryId, sequenceKey) in addressedMessages) { + send(message, target, retryId, sequenceKey, null) + } + acknowledgementHandler?.invoke() + } + override fun stop() { if (backgroundThread != null) { backgroundThread.interrupt() diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetworkMapCache.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetworkMapCache.kt index 1b82ca04c1..889f9cfd3b 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetworkMapCache.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetworkMapCache.kt @@ -7,9 +7,9 @@ import net.corda.core.identity.Party import net.corda.core.node.NodeInfo import net.corda.core.node.services.NetworkMapCache import net.corda.core.utilities.NetworkHostAndPort -import net.corda.core.utilities.NonEmptySet -import net.corda.node.services.api.ServiceHubInternal +import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.network.PersistentNetworkMapCache +import net.corda.node.utilities.CordaPersistence import net.corda.testing.getTestPartyAndCertificate import rx.Observable import rx.subjects.PublishSubject @@ -18,7 +18,7 @@ import java.math.BigInteger /** * Network map cache with no backing map service. */ -class MockNetworkMapCache(serviceHub: ServiceHubInternal) : PersistentNetworkMapCache(serviceHub) { +class MockNetworkMapCache(database: CordaPersistence, configuration: NodeConfiguration) : PersistentNetworkMapCache(database, configuration) { private companion object { val BANK_C = getTestPartyAndCertificate(CordaX500Name(organisation = "Bank C", locality = "London", country = "GB"), entropyToKeyPair(BigInteger.valueOf(1000)).public) val BANK_D = getTestPartyAndCertificate(CordaX500Name(organisation = "Bank D", locality = "London", country = "GB"), entropyToKeyPair(BigInteger.valueOf(2000)).public) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt index ae810b581a..71a9148900 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt @@ -2,13 +2,12 @@ package net.corda.testing.node import com.google.common.jimfs.Configuration.unix import com.google.common.jimfs.Jimfs +import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever import net.corda.core.crypto.entropyToKeyPair import net.corda.core.crypto.random63BitValue import net.corda.core.identity.CordaX500Name -import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate -import net.corda.core.internal.cert import net.corda.core.internal.concurrent.doneFuture import net.corda.core.internal.createDirectories import net.corda.core.internal.createDirectory @@ -18,7 +17,7 @@ import net.corda.core.messaging.RPCOps import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.node.services.IdentityService import net.corda.core.node.services.KeyManagementService -import net.corda.core.node.services.NetworkMapCache +import net.corda.core.node.services.PartyInfo import net.corda.core.serialization.SerializationWhitelist import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.getOrThrow @@ -32,9 +31,8 @@ import net.corda.node.services.api.SchemaService import net.corda.node.services.config.BFTSMaRtConfiguration import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.NotaryConfig -import net.corda.node.services.identity.PersistentIdentityService import net.corda.node.services.keys.E2ETestKeyManagementService -import net.corda.node.services.messaging.MessagingService +import net.corda.node.services.messaging.* import net.corda.node.services.network.InMemoryNetworkMapService import net.corda.node.services.network.NetworkMapService import net.corda.node.services.transactions.BFTNonValidatingNotaryService @@ -42,11 +40,13 @@ import net.corda.node.services.transactions.BFTSMaRt import net.corda.node.services.transactions.InMemoryTransactionVerifierService import net.corda.node.utilities.AffinityExecutor import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor -import net.corda.node.utilities.CertificateAndKeyPair import net.corda.nodeapi.internal.ServiceInfo -import net.corda.testing.* +import net.corda.testing.DUMMY_NOTARY +import net.corda.testing.initialiseTestSerialization import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties +import net.corda.testing.resetTestSerialization +import net.corda.testing.testNodeConfiguration import org.apache.activemq.artemis.utils.ReusableLatch import org.slf4j.Logger import java.io.Closeable @@ -54,7 +54,7 @@ import java.math.BigInteger import java.nio.file.Path import java.security.KeyPair import java.security.PublicKey -import java.security.cert.X509Certificate +import java.util.* import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicInteger @@ -62,6 +62,51 @@ fun StartedNode.pumpReceive(block: Boolean = false): InMem return (network as InMemoryMessagingNetwork.InMemoryMessaging).pumpReceive(block) } +/** Helper builder for configuring a [MockNetwork] from Java. */ +@Suppress("unused") +data class MockNetworkParameters( + val networkSendManuallyPumped: Boolean = false, + val threadPerNode: Boolean = false, + val servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = InMemoryMessagingNetwork.ServicePeerAllocationStrategy.Random(), + val defaultFactory: MockNetwork.Factory<*> = MockNetwork.DefaultFactory, + val initialiseSerialization: Boolean = true, + val cordappPackages: List = emptyList()) { + fun setNetworkSendManuallyPumped(networkSendManuallyPumped: Boolean) = copy(networkSendManuallyPumped = networkSendManuallyPumped) + fun setThreadPerNode(threadPerNode: Boolean) = copy(threadPerNode = threadPerNode) + fun setServicePeerAllocationStrategy(servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy) = copy(servicePeerAllocationStrategy = servicePeerAllocationStrategy) + fun setDefaultFactory(defaultFactory: MockNetwork.Factory<*>) = copy(defaultFactory = defaultFactory) + fun setInitialiseSerialization(initialiseSerialization: Boolean) = copy(initialiseSerialization = initialiseSerialization) + fun setCordappPackages(cordappPackages: List) = copy(cordappPackages = cordappPackages) +} + +/** + * @param notaryIdentity a set of service entries to use in place of the node's default service entries, + * for example where a node's service is part of a cluster. + * @param entropyRoot the initial entropy value to use when generating keys. Defaults to an (insecure) random value, + * but can be overridden to cause nodes to have stable or colliding identity/service keys. + * @param configOverrides add/override behaviour of the [NodeConfiguration] mock object. + */ +@Suppress("unused") +data class MockNodeParameters( + val forcedID: Int? = null, + val legalName: CordaX500Name? = null, + val notaryIdentity: Pair? = null, + val entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), + val configOverrides: (NodeConfiguration) -> Any? = {}) { + fun setForcedID(forcedID: Int?) = copy(forcedID = forcedID) + fun setLegalName(legalName: CordaX500Name?) = copy(legalName = legalName) + fun setNotaryIdentity(notaryIdentity: Pair?) = copy(notaryIdentity = notaryIdentity) + fun setEntropyRoot(entropyRoot: BigInteger) = copy(entropyRoot = entropyRoot) + fun setConfigOverrides(configOverrides: (NodeConfiguration) -> Any?) = copy(configOverrides = configOverrides) +} + +data class MockNodeArgs( + val config: NodeConfiguration, + val network: MockNetwork, + val id: Int, + val notaryIdentity: Pair?, + val entropyRoot: BigInteger) + /** * A mock node brings up a suite of in-memory services in a fast manner suitable for unit testing. * Components that do IO are either swapped out for mocks, or pointed to a [Jimfs] in memory filesystem or an in @@ -75,17 +120,16 @@ fun StartedNode.pumpReceive(block: Boolean = false): InMem * * LogHelper.setLevel("+messages") */ -class MockNetwork(private val networkSendManuallyPumped: Boolean = false, - private val threadPerNode: Boolean = false, - servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = - InMemoryMessagingNetwork.ServicePeerAllocationStrategy.Random(), - private val defaultFactory: Factory<*> = MockNetwork.DefaultFactory, - private val initialiseSerialization: Boolean = true, - private val cordappPackages: List = emptyList()) : Closeable { - companion object { - // TODO In future PR we're removing the concept of network map node so the details of this mock are not important. - val MOCK_NET_MAP = Party(CordaX500Name(organisation = "Mock Network Map", locality = "Madrid", country = "ES"), DUMMY_KEY_1.public) - } +class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParameters(), + private val networkSendManuallyPumped: Boolean = defaultParameters.networkSendManuallyPumped, + private val threadPerNode: Boolean = defaultParameters.threadPerNode, + servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = defaultParameters.servicePeerAllocationStrategy, + private val defaultFactory: Factory<*> = defaultParameters.defaultFactory, + private val initialiseSerialization: Boolean = defaultParameters.initialiseSerialization, + private val cordappPackages: List = defaultParameters.cordappPackages) : Closeable { + /** Helper constructor for creating a [MockNetwork] with custom parameters from Java. */ + constructor(parameters: MockNetworkParameters) : this(defaultParameters = parameters) + var nextNodeId = 0 private set private val filesystem = Jimfs.newFileSystem(unix()) @@ -97,9 +141,6 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false, /** A read only view of the current set of executing nodes. */ val nodes: List get() = _nodes - private var _networkMapNode: StartedNode? = null - val networkMapNode: StartedNode get() = _networkMapNode ?: startNetworkMapNode() - init { if (initialiseSerialization) initialiseTestSerialization() filesystem.getPath("/nodes").createDirectory() @@ -107,21 +148,11 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false, /** Allows customisation of how nodes are created. */ interface Factory { - /** - * @param notaryIdentity is an additional override to use in place of the node's default notary service, - * main usage is for when the node is part of a notary cluster. - * @param entropyRoot the initial entropy value to use when generating keys. Defaults to an (insecure) random value, - * but can be overriden to cause nodes to have stable or colliding identity/service keys. - */ - fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, - id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): N + fun create(args: MockNodeArgs): N } object DefaultFactory : Factory { - override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, - id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): MockNode { - return MockNode(config, network, networkMapAddr, id, notaryIdentity, entropyRoot) - } + override fun create(args: MockNodeArgs) = MockNode(args) } /** @@ -147,19 +178,17 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false, } } - /** - * @param notaryIdentity is an additional override to use in place of the node's default notary service, - * main usage is for when the node is part of a notary cluster. - * @param entropyRoot the initial entropy value to use when generating keys. Defaults to an (insecure) random value, - * but can be overriden to cause nodes to have stable or colliding identity/service keys. - */ - open class MockNode(config: NodeConfiguration, - val mockNet: MockNetwork, - override val networkMapAddress: SingleMessageRecipient?, - val id: Int, - internal val notaryIdentity: Pair?, - val entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue())) : - AbstractNode(config, TestClock(), MOCK_VERSION_INFO, CordappLoader.createDefaultWithTestPackages(config, mockNet.cordappPackages), mockNet.busyLatch) { + open class MockNode(args: MockNodeArgs) : AbstractNode( + args.config, + TestClock(), + MOCK_VERSION_INFO, + CordappLoader.createDefaultWithTestPackages(args.config, args.network.cordappPackages), + args.network.busyLatch) { + val mockNet = args.network + override val networkMapAddress = null + val id = args.id + internal val notaryIdentity = args.notaryIdentity + val entropyRoot = args.entropyRoot var counter = entropyRoot override val log: Logger = loggerFor() override val serverThread: AffinityExecutor = @@ -187,24 +216,8 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false, .getOrThrow() } - override fun makeIdentityService(trustRoot: X509Certificate, - clientCa: CertificateAndKeyPair?, - legalIdentity: PartyAndCertificate): IdentityService { - val caCertificates: Array = listOf(legalIdentity.certificate, clientCa?.certificate?.cert) - .filterNotNull() - .toTypedArray() - val identityService = PersistentIdentityService(info.legalIdentitiesAndCerts, - trustRoot = trustRoot, caCertificates = *caCertificates) - services.networkMapCache.allNodes.forEach { it.legalIdentitiesAndCerts.forEach { identityService.verifyAndRegisterIdentity(it) } } - services.networkMapCache.changed.subscribe { mapChange -> - // TODO how should we handle network map removal - if (mapChange is NetworkMapCache.MapChange.Added) { - mapChange.node.legalIdentitiesAndCerts.forEach { - identityService.verifyAndRegisterIdentity(it) - } - } - } - return identityService + fun setMessagingServiceSpy(messagingServiceSpy: MessagingServiceSpy) { + network = messagingServiceSpy } override fun makeKeyManagementService(identityService: IdentityService): KeyManagementService { @@ -262,6 +275,8 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false, dbCloser = null } + fun hasDBConnection() = dbCloser != null + // You can change this from zero if you have custom [FlowLogic] that park themselves. e.g. [StateMachineManagerTests] var acceptableLiveFiberCountOnStop: Int = 0 @@ -275,92 +290,40 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false, throw IllegalStateException("Unable to enumerate all nodes in BFT cluster.") } clusterNodes.forEach { - val notaryService = it.started!!.smm.findServices { it is BFTNonValidatingNotaryService }.single() as BFTNonValidatingNotaryService + val notaryService = it.findTokenizableService(BFTNonValidatingNotaryService::class.java)!! notaryService.waitUntilReplicaHasInitialized() } } } } - - /** - * Makes sure that the [MockNode] is correctly registered on the [MockNetwork] - * Please note that [MockNetwork.runNetwork] should be invoked to ensure that all the pending registration requests - * were duly processed - */ - fun ensureRegistered() { - _nodeReadyFuture.getOrThrow() - } } - fun startNetworkMapNode(nodeFactory: Factory? = null): StartedNode { - check(_networkMapNode == null) { "Trying to start more than one network map node" } - return uncheckedCast(createNodeImpl(networkMapAddress = null, - forcedID = null, - nodeFactory = nodeFactory ?: defaultFactory, - legalName = MOCK_NET_MAP.name, - notaryIdentity = null, - entropyRoot = BigInteger.valueOf(random63BitValue()), - configOverrides = {}, - start = true - ).started!!.apply { - _networkMapNode = this - }) - } - - fun createUnstartedNode(forcedID: Int? = null, - legalName: CordaX500Name? = null, notaryIdentity: Pair? = null, - entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), - configOverrides: (NodeConfiguration) -> Any? = {}): MockNode { - return createUnstartedNode(forcedID, defaultFactory, legalName, notaryIdentity, entropyRoot, configOverrides = configOverrides) - } - - fun createUnstartedNode(forcedID: Int? = null, nodeFactory: Factory, - legalName: CordaX500Name? = null, notaryIdentity: Pair? = null, - entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), - configOverrides: (NodeConfiguration) -> Any? = {}): N { - val networkMapAddress = networkMapNode.network.myAddress - return createNodeImpl(networkMapAddress, forcedID, nodeFactory, false, legalName, notaryIdentity, entropyRoot, configOverrides) - } - - /** - * Returns a node, optionally created by the passed factory method. - * @param notaryIdentity a set of service entries to use in place of the node's default service entries, - * for example where a node's service is part of a cluster. - * @param entropyRoot the initial entropy value to use when generating keys. Defaults to an (insecure) random value, - * but can be overridden to cause nodes to have stable or colliding identity/service keys. - * @param configOverrides add/override behaviour of the [NodeConfiguration] mock object. - */ - fun createNode(forcedID: Int? = null, - legalName: CordaX500Name? = null, notaryIdentity: Pair? = null, - entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), - configOverrides: (NodeConfiguration) -> Any? = {}): StartedNode { - return createNode(forcedID, defaultFactory, legalName, notaryIdentity, entropyRoot, configOverrides = configOverrides) + fun createUnstartedNode(parameters: MockNodeParameters = MockNodeParameters()) = createUnstartedNode(parameters, defaultFactory) + fun createUnstartedNode(parameters: MockNodeParameters = MockNodeParameters(), nodeFactory: Factory): N { + return createNodeImpl(parameters, nodeFactory, false) } + fun createNode(parameters: MockNodeParameters = MockNodeParameters()): StartedNode = createNode(parameters, defaultFactory) /** Like the other [createNode] but takes a [Factory] and propagates its [MockNode] subtype. */ - fun createNode(forcedID: Int? = null, nodeFactory: Factory, - legalName: CordaX500Name? = null, notaryIdentity: Pair? = null, - entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), - configOverrides: (NodeConfiguration) -> Any? = {}): StartedNode { - val networkMapAddress = networkMapNode.network.myAddress - return uncheckedCast(createNodeImpl(networkMapAddress, forcedID, nodeFactory, true, legalName, notaryIdentity, entropyRoot, configOverrides).started)!! + fun createNode(parameters: MockNodeParameters = MockNodeParameters(), nodeFactory: Factory): StartedNode { + val node: StartedNode = uncheckedCast(createNodeImpl(parameters, nodeFactory, true).started)!! + ensureAllNetworkMapCachesHaveAllNodeInfos() + return node } - private fun createNodeImpl(networkMapAddress: SingleMessageRecipient?, forcedID: Int?, nodeFactory: Factory, - start: Boolean, legalName: CordaX500Name?, notaryIdentity: Pair?, - entropyRoot: BigInteger, - configOverrides: (NodeConfiguration) -> Any?): N { - val id = forcedID ?: nextNodeId++ + private fun createNodeImpl(parameters: MockNodeParameters, nodeFactory: Factory, start: Boolean): N { + val id = parameters.forcedID ?: nextNodeId++ val config = testNodeConfiguration( baseDirectory = baseDirectory(id).createDirectories(), - myLegalName = legalName ?: CordaX500Name(organisation = "Mock Company $id", locality = "London", country = "GB")).also { - whenever(it.dataSourceProperties).thenReturn(makeTestDataSourceProperties("node_${id}_net_$networkId")) - configOverrides(it) + myLegalName = parameters.legalName ?: CordaX500Name(organisation = "Mock Company $id", locality = "London", country = "GB")).also { + doReturn(makeTestDataSourceProperties("node_${id}_net_$networkId")).whenever(it).dataSourceProperties + parameters.configOverrides(it) } - return nodeFactory.create(config, this, networkMapAddress, id, notaryIdentity, entropyRoot).apply { + return nodeFactory.create(MockNodeArgs(config, this, id, parameters.notaryIdentity, parameters.entropyRoot)).apply { if (start) { start() - if (threadPerNode && networkMapAddress != null) nodeReadyFuture.getOrThrow() // XXX: What about manually-started nodes? + if (threadPerNode) nodeReadyFuture.getOrThrow() // XXX: What about manually-started nodes? + ensureAllNetworkMapCachesHaveAllNodeInfos() } _nodes.add(this) } @@ -376,6 +339,7 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false, */ @JvmOverloads fun runNetwork(rounds: Int = -1) { + ensureAllNetworkMapCachesHaveAllNodeInfos() check(!networkSendManuallyPumped) fun pumpAll() = messagingNetwork.endpoints.map { it.pumpReceive(false) } @@ -391,23 +355,24 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false, @JvmOverloads fun createNotaryNode(legalName: CordaX500Name = DUMMY_NOTARY.name, validating: Boolean = true): StartedNode { - return createNode(legalName = legalName, configOverrides = { - whenever(it.notary).thenReturn(NotaryConfig(validating)) - }) + return createNode(MockNodeParameters(legalName = legalName, configOverrides = { + doReturn(NotaryConfig(validating)).whenever(it).notary + })) } - fun createNotaryNode(legalName: CordaX500Name = DUMMY_NOTARY.name, + fun createNotaryNode(parameters: MockNodeParameters = MockNodeParameters(legalName = DUMMY_NOTARY.name), validating: Boolean = true, nodeFactory: Factory): StartedNode { - return createNode(legalName = legalName, nodeFactory = nodeFactory, configOverrides = { - whenever(it.notary).thenReturn(NotaryConfig(validating)) - }) + return createNode(parameters.copy(configOverrides = { + doReturn(NotaryConfig(validating)).whenever(it).notary + parameters.configOverrides(it) + }), nodeFactory) } @JvmOverloads fun createPartyNode(legalName: CordaX500Name? = null, notaryIdentity: Pair? = null): StartedNode { - return createNode(legalName = legalName, notaryIdentity = notaryIdentity) + return createNode(MockNodeParameters(legalName = legalName, notaryIdentity = notaryIdentity)) } @Suppress("unused") // This is used from the network visualiser tool. @@ -422,9 +387,21 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false, } } + private fun ensureAllNetworkMapCachesHaveAllNodeInfos() { + val infos = nodes.mapNotNull { it.started?.info } + nodes.filter { it.hasDBConnection() } + .mapNotNull { it.started?.services?.networkMapCache } + .forEach { + for (nodeInfo in infos) { + it.addNode(nodeInfo) + } + } + } + fun startNodes() { require(nodes.isNotEmpty()) nodes.forEach { it.started ?: it.start() } + ensureAllNetworkMapCachesHaveAllNodeInfos() } fun stopNodes() { @@ -450,4 +427,16 @@ fun network(nodesCount: Int, action: MockNetwork.(nodes: List it.createPartyNode() } action(it, nodes, notary) } +} + +/** + * Extend this class in order to intercept and modify messages passing through the [MessagingService] when using the [InMemoryNetwork]. + */ +open class MessagingServiceSpy(val messagingService: MessagingService) : MessagingService by messagingService + +/** + * Attach a [MessagingServiceSpy] to the [MockNode] allowing interception and modification of messages. + */ +fun StartedNode.setMessagingServiceSpy(messagingServiceSpy: MessagingServiceSpy) { + internals.setMessagingServiceSpy(messagingServiceSpy) } \ No newline at end of file diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt index 8dc4ddebb4..7ec6b3bdd0 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt @@ -30,6 +30,7 @@ import net.corda.node.services.persistence.HibernateConfiguration import net.corda.node.services.persistence.InMemoryStateMachineRecordedTransactionMappingStorage import net.corda.node.services.schema.HibernateObserver import net.corda.node.services.schema.NodeSchemaService +import net.corda.node.services.statemachine.FlowStateMachineImpl import net.corda.node.services.transactions.InMemoryTransactionVerifierService import net.corda.node.services.vault.NodeVaultService import net.corda.node.utilities.CordaPersistence @@ -179,7 +180,7 @@ open class MockServices( fun makeVaultService(hibernateConfig: HibernateConfiguration): VaultServiceInternal { val vaultService = NodeVaultService(Clock.systemUTC(), keyManagementService, stateLoader, hibernateConfig) - hibernatePersister = HibernateObserver(vaultService.rawUpdates, hibernateConfig) + hibernatePersister = HibernateObserver.install(vaultService.rawUpdates, hibernateConfig) return vaultService } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/SimpleNode.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/SimpleNode.kt index 3b7bf9130e..6dad146916 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/SimpleNode.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/SimpleNode.kt @@ -14,8 +14,8 @@ import net.corda.node.services.identity.InMemoryIdentityService import net.corda.node.services.keys.E2ETestKeyManagementService import net.corda.node.services.messaging.ArtemisMessagingServer import net.corda.node.services.messaging.NodeMessagingClient +import net.corda.node.services.network.NetworkMapCacheImpl import net.corda.node.services.schema.NodeSchemaService -import net.corda.node.testing.MockServiceHubInternal import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.configureDatabase @@ -42,7 +42,7 @@ class SimpleNode(val config: NodeConfiguration, val address: NetworkHostAndPort val executor = ServiceAffinityExecutor(config.myLegalName.organisation, 1) // TODO: We should have a dummy service hub rather than change behaviour in tests val broker = ArtemisMessagingServer(config, address.port, rpcAddress.port, - MockNetworkMapCache(serviceHub = object : MockServiceHubInternal(database = database, configuration = config) {}), userService) + NetworkMapCacheImpl(MockNetworkMapCache(database, config), identityService), userService) val networkMapRegistrationFuture = openFuture() val network = database.transaction { NodeMessagingClient( diff --git a/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt b/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt index ae43e144db..28d1afb9bf 100644 --- a/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt +++ b/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt @@ -96,8 +96,10 @@ class NodeProcess( private fun startNode(nodeDir: Path): Process { val builder = ProcessBuilder() - .command(javaPath.toString(), "-jar", cordaJar.toString()) + .command(javaPath.toString(), "-Dcapsule.log=verbose", "-jar", cordaJar.toString()) .directory(nodeDir.toFile()) + .redirectError(ProcessBuilder.Redirect.INHERIT) + .redirectOutput(ProcessBuilder.Redirect.INHERIT) builder.environment().putAll(mapOf( "CAPSULE_CACHE_DIR" to (buildDirectory / "capsule").toString() diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt index 71be78c0bd..a1cda49a22 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt @@ -3,6 +3,7 @@ package net.corda.testing +import com.nhaarman.mockito_kotlin.mock import net.corda.core.contracts.StateRef import net.corda.core.crypto.SecureHash import net.corda.core.crypto.generateKeyPair @@ -24,6 +25,8 @@ import net.corda.node.utilities.CertificateType import net.corda.node.utilities.X509Utilities import net.corda.nodeapi.config.SSLConfiguration import net.corda.nodeapi.internal.serialization.AMQP_ENABLED +import org.mockito.internal.stubbing.answers.ThrowsException +import org.mockito.stubbing.Answer import java.nio.file.Files import java.security.KeyPair import java.security.PublicKey @@ -177,3 +180,19 @@ fun NodeInfo.singleIdentityAndCert(): PartyAndCertificate = legalIdentitiesAndCe fun NodeInfo.singleIdentity(): Party = singleIdentityAndCert().party /** Returns the identity of the first notary found on the network */ fun ServiceHub.getDefaultNotary(): Party = networkMapCache.notaryIdentities.first() + +/** + * A method on a mock was called, but no behaviour was previously specified for that method. + * You can use [com.nhaarman.mockito_kotlin.doReturn] or similar to specify behaviour, see Mockito documentation for details. + */ +class UndefinedMockBehaviorException(message: String) : RuntimeException(message) + +/** + * Create a Mockito mock that has [UndefinedMockBehaviorException] as the default behaviour of all methods. + * @param T the type to mock. Note if you want to use [com.nhaarman.mockito_kotlin.doCallRealMethod] on a Kotlin interface, + * it won't work unless you mock a (trivial) abstract implementation of that interface instead. + */ +inline fun rigorousMock() = mock(Answer { + // Use ThrowsException to hack the stack trace, and lazily so we can customise the message: + ThrowsException(UndefinedMockBehaviorException("Please specify what should happen when '${it.method}' is called, or don't call it.")).answer(it) +}) diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt index 891808e4b5..830316435e 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt @@ -103,6 +103,10 @@ class TestSerializationFactory : SerializationFactory() { return delegate!!.deserialize(byteSequence, clazz, context) } + override fun deserializeWithCompatibleContext(byteSequence: ByteSequence, clazz: Class, context: SerializationContext): ObjectWithCompatibleContext { + return delegate!!.deserializeWithCompatibleContext(byteSequence, clazz, context) + } + override fun serialize(obj: T, context: SerializationContext): SerializedBytes { return delegate!!.serialize(obj, context) } @@ -147,7 +151,7 @@ class TestSerializationContext : SerializationContext { return TestSerializationContext().apply { delegate = this@TestSerializationContext.delegate!!.withWhitelisted(clazz) } } - override fun withPreferredSerializationVersion(versionHeader: ByteSequence): SerializationContext { + override fun withPreferredSerializationVersion(versionHeader: VersionHeader): SerializationContext { return TestSerializationContext().apply { delegate = this@TestSerializationContext.delegate!!.withPreferredSerializationVersion(versionHeader) } } diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/model/DemoBenchNodeInfoFilesCopier.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/model/DemoBenchNodeInfoFilesCopier.kt new file mode 100644 index 0000000000..a0818c3532 --- /dev/null +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/model/DemoBenchNodeInfoFilesCopier.kt @@ -0,0 +1,35 @@ +package net.corda.demobench.model + +import net.corda.nodeapi.NodeInfoFilesCopier +import rx.Scheduler +import rx.schedulers.Schedulers +import tornadofx.* + +/** + * Utility class which copies nodeInfo files across a set of running nodes. + * + * This class will create paths that it needs to poll and to where it needs to copy files in case those + * don't exist yet. + */ +class DemoBenchNodeInfoFilesCopier(scheduler: Scheduler = Schedulers.io()): Controller() { + + private val nodeInfoFilesCopier = NodeInfoFilesCopier(scheduler) + + /** + * @param nodeConfig the configuration to be added. + * Add a [NodeConfig] for a node which is about to be started. + * Its nodeInfo file will be copied to other nodes' additional-node-infos directory, and conversely, + * other nodes' nodeInfo files will be copied to this node additional-node-infos directory. + */ + fun addConfig(nodeConfig: NodeConfigWrapper) : Unit = nodeInfoFilesCopier.addConfig(nodeConfig.nodeDir) + + /** + * @param nodeConfig the configuration to be removed. + * Remove the configuration of a node which is about to be stopped or already stopped. + * No files written by that node will be copied to other nodes, nor files from other nodes will be copied to this + * one. + */ + fun removeConfig(nodeConfig: NodeConfigWrapper) : Unit = nodeInfoFilesCopier.removeConfig(nodeConfig.nodeDir) + + fun reset() : Unit = nodeInfoFilesCopier.reset() +} \ No newline at end of file diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt index ddd8010bb4..2830c969b8 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt @@ -28,7 +28,7 @@ class NodeController(check: atRuntime = ::checkExists) : Controller() { private val jvm by inject() private val cordappController by inject() - private val nodeInfoFilesCopier by inject() + private val nodeInfoFilesCopier by inject() private var baseDir: Path = baseDirFor(ManagementFactory.getRuntimeMXBean().startTime) private val cordaPath: Path = jvm.applicationDir.resolve("corda").resolve("corda.jar") diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeInfoFilesCopier.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeInfoFilesCopier.kt deleted file mode 100644 index ed69e38b93..0000000000 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeInfoFilesCopier.kt +++ /dev/null @@ -1,133 +0,0 @@ -package net.corda.demobench.model - -import net.corda.cordform.CordformNode -import net.corda.core.internal.createDirectories -import net.corda.core.internal.isRegularFile -import net.corda.core.internal.list -import rx.Observable -import rx.Scheduler -import rx.schedulers.Schedulers -import tornadofx.* -import java.io.IOException -import java.nio.file.Files -import java.nio.file.Path -import java.nio.file.StandardCopyOption.COPY_ATTRIBUTES -import java.nio.file.StandardCopyOption.REPLACE_EXISTING -import java.nio.file.attribute.BasicFileAttributes -import java.nio.file.attribute.FileTime -import java.util.concurrent.TimeUnit -import java.util.logging.Level - -/** - * Utility class which copies nodeInfo files across a set of running nodes. - * - * This class will create paths that it needs to poll and to where it needs to copy files in case those - * don't exist yet. - */ -class NodeInfoFilesCopier(scheduler: Scheduler = Schedulers.io()): Controller() { - - private val nodeDataMap = mutableMapOf() - - init { - Observable.interval(5, TimeUnit.SECONDS, scheduler) - .subscribe { poll() } - } - - /** - * @param nodeConfig the configuration to be added. - * Add a [NodeConfig] for a node which is about to be started. - * Its nodeInfo file will be copied to other nodes' additional-node-infos directory, and conversely, - * other nodes' nodeInfo files will be copied to this node additional-node-infos directory. - */ - @Synchronized - fun addConfig(nodeConfig: NodeConfigWrapper) { - val newNodeFile = NodeData(nodeConfig.nodeDir) - nodeDataMap[nodeConfig.nodeDir] = newNodeFile - - for (previouslySeenFile in allPreviouslySeenFiles()) { - copy(previouslySeenFile, newNodeFile.destination.resolve(previouslySeenFile.fileName)) - } - log.info("Now watching: ${nodeConfig.nodeDir}") - } - - /** - * @param nodeConfig the configuration to be removed. - * Remove the configuration of a node which is about to be stopped or already stopped. - * No files written by that node will be copied to other nodes, nor files from other nodes will be copied to this - * one. - */ - @Synchronized - fun removeConfig(nodeConfig: NodeConfigWrapper) { - nodeDataMap.remove(nodeConfig.nodeDir) ?: return - log.info("Stopped watching: ${nodeConfig.nodeDir}") - } - - @Synchronized - fun reset() { - nodeDataMap.clear() - } - - private fun allPreviouslySeenFiles() = nodeDataMap.values.map { it.previouslySeenFiles.keys }.flatten() - - @Synchronized - private fun poll() { - for (nodeData in nodeDataMap.values) { - nodeData.nodeDir.list { paths -> - paths.filter { it.isRegularFile() } - .filter { it.fileName.toString().startsWith("nodeInfo-") } - .forEach { path -> processPath(nodeData, path) } - } - } - } - - // Takes a path under nodeData config dir and decides whether the file represented by that path needs to - // be copied. - private fun processPath(nodeData: NodeData, path: Path) { - val newTimestamp = Files.readAttributes(path, BasicFileAttributes::class.java).lastModifiedTime() - val previousTimestamp = nodeData.previouslySeenFiles.put(path, newTimestamp) ?: FileTime.fromMillis(-1) - if (newTimestamp > previousTimestamp) { - for (destination in nodeDataMap.values.filter { it.nodeDir != nodeData.nodeDir }.map { it.destination }) { - val fullDestinationPath = destination.resolve(path.fileName) - copy(path, fullDestinationPath) - } - } - } - - private fun copy(source: Path, destination: Path) { - val tempDestination = try { - Files.createTempFile(destination.parent, ".", null) - } catch (exception: IOException) { - log.log(Level.WARNING, "Couldn't create a temporary file to copy $source", exception) - throw exception - } - try { - // First copy the file to a temporary file within the appropriate directory. - Files.copy(source, tempDestination, COPY_ATTRIBUTES, REPLACE_EXISTING) - } catch (exception: IOException) { - log.log(Level.WARNING, "Couldn't copy $source to $tempDestination.", exception) - Files.delete(tempDestination) - throw exception - } - try { - // Then rename it to the desired name. This way the file 'appears' on the filesystem as an atomic operation. - Files.move(tempDestination, destination, REPLACE_EXISTING) - } catch (exception: IOException) { - log.log(Level.WARNING, "Couldn't move $tempDestination to $destination.", exception) - Files.delete(tempDestination) - throw exception - } - } - - /** - * Convenience holder for all the paths and files relative to a single node. - */ - private class NodeData(val nodeDir: Path) { - val destination: Path = nodeDir.resolve(CordformNode.NODE_INFO_DIRECTORY) - // Map from Path to its lastModifiedTime. - val previouslySeenFiles = mutableMapOf() - - init { - destination.createDirectories() - } - } -} diff --git a/tools/demobench/src/test/kotlin/net/corda/demobench/pty/ZeroFilterTest.kt b/tools/demobench/src/test/kotlin/net/corda/demobench/pty/ZeroFilterTest.kt index 3f9bb93d17..41ccda83bf 100644 --- a/tools/demobench/src/test/kotlin/net/corda/demobench/pty/ZeroFilterTest.kt +++ b/tools/demobench/src/test/kotlin/net/corda/demobench/pty/ZeroFilterTest.kt @@ -1,9 +1,12 @@ package net.corda.demobench.pty +import com.nhaarman.mockito_kotlin.doReturn +import com.nhaarman.mockito_kotlin.verify +import com.nhaarman.mockito_kotlin.whenever +import net.corda.testing.rigorousMock import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test -import org.mockito.Mockito.* import java.io.ByteArrayOutputStream import java.io.OutputStream import java.nio.charset.StandardCharsets.UTF_8 @@ -15,10 +18,8 @@ class ZeroFilterTest { @Before fun setup() { output = ByteArrayOutputStream() - - val process = mock(Process::class.java) - `when`(process.outputStream).thenReturn(output) - + val process = rigorousMock() + doReturn(output).whenever(process).outputStream filter = process.zeroFiltered().outputStream verify(process).outputStream } diff --git a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt index d711acd838..e36854d54c 100644 --- a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt +++ b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt @@ -74,7 +74,6 @@ fun verifierDriver( debugPortAllocation: PortAllocation = PortAllocation.Incremental(5005), extraSystemProperties: Map = emptyMap(), useTestClock: Boolean = false, - networkMapStartStrategy: NetworkMapStartStrategy = NetworkMapStartStrategy.Dedicated(startAutomatically = false), startNodesInProcess: Boolean = false, extraCordappPackagesToScan: List = emptyList(), dsl: VerifierExposedDSLInterface.() -> A @@ -86,7 +85,6 @@ fun verifierDriver( extraSystemProperties = extraSystemProperties, driverDirectory = driverDirectory.toAbsolutePath(), useTestClock = useTestClock, - networkMapStartStrategy = networkMapStartStrategy, isDebug = isDebug, startNodesInProcess = startNodesInProcess, extraCordappPackagesToScan = extraCordappPackagesToScan diff --git a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierTests.kt b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierTests.kt index 05bfc06673..dc228fd6f6 100644 --- a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierTests.kt +++ b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierTests.kt @@ -1,5 +1,6 @@ package net.corda.verifier +import net.corda.core.contracts.TransactionVerificationException import net.corda.core.internal.concurrent.map import net.corda.core.internal.concurrent.transpose import net.corda.core.messaging.startFlow @@ -12,12 +13,13 @@ import net.corda.finance.flows.CashIssueFlow import net.corda.finance.flows.CashPaymentFlow import net.corda.node.services.config.VerifierType import net.corda.testing.ALICE +import net.corda.testing.ALICE_NAME import net.corda.testing.DUMMY_NOTARY -import net.corda.testing.* -import net.corda.testing.driver.NetworkMapStartStrategy +import net.corda.testing.DUMMY_NOTARY_SERVICE_NAME import org.junit.Test import java.util.* import java.util.concurrent.atomic.AtomicInteger +import kotlin.test.assertTrue import kotlin.test.assertNotNull class VerifierTests { @@ -50,6 +52,21 @@ class VerifierTests { } } + @Test + fun `single verification fails`() { + verifierDriver(extraCordappPackagesToScan = listOf("net.corda.finance.contracts")) { + val aliceFuture = startVerificationRequestor(ALICE.name) + // Generate transactions as per usual, but then remove attachments making transaction invalid. + val transactions = generateTransactions(1).map { it.copy(attachments = emptyList()) } + val alice = aliceFuture.get() + startVerifier(alice) + alice.waitUntilNumberOfVerifiers(1) + val verificationRejection = transactions.map { alice.verifyTransaction(it) }.transpose().get().single() + assertTrue { verificationRejection is TransactionVerificationException.MissingAttachmentRejection} + assertTrue { verificationRejection!!.message!!.contains("Contract constraints failed, could not find attachment") } + } + } + @Test fun `multiple verifiers work with requestor`() { verifierDriver { @@ -112,10 +129,7 @@ class VerifierTests { @Test fun `single verifier works with a node`() { - verifierDriver( - networkMapStartStrategy = NetworkMapStartStrategy.Dedicated(startAutomatically = true), - extraCordappPackagesToScan = listOf("net.corda.finance.contracts") - ) { + verifierDriver(extraCordappPackagesToScan = listOf("net.corda.finance.contracts")) { val aliceFuture = startNode(providedName = ALICE.name) val notaryFuture = startNotaryNode(DUMMY_NOTARY.name, verifierType = VerifierType.OutOfProcess) val aliceNode = aliceFuture.get() diff --git a/verifier/src/main/kotlin/net/corda/verifier/Verifier.kt b/verifier/src/main/kotlin/net/corda/verifier/Verifier.kt index aaf0c3ac73..22ff4e9f60 100644 --- a/verifier/src/main/kotlin/net/corda/verifier/Verifier.kt +++ b/verifier/src/main/kotlin/net/corda/verifier/Verifier.kt @@ -17,17 +17,16 @@ import net.corda.nodeapi.VerifierApi.VERIFICATION_REQUESTS_QUEUE_NAME import net.corda.nodeapi.config.NodeSSLConfiguration import net.corda.nodeapi.config.getValue import net.corda.nodeapi.internal.addShutdownHook -import net.corda.nodeapi.internal.serialization.AbstractKryoSerializationScheme -import net.corda.nodeapi.internal.serialization.KRYO_P2P_CONTEXT -import net.corda.nodeapi.internal.serialization.KryoHeaderV0_1 -import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl +import net.corda.nodeapi.internal.serialization.* +import net.corda.nodeapi.internal.serialization.amqp.AmqpHeaderV1_0 +import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory import org.apache.activemq.artemis.api.core.client.ActiveMQClient import java.nio.file.Path import java.nio.file.Paths data class VerifierConfiguration( override val baseDirectory: Path, - val config: Config + val config: Config // NB: This property is being used via reflection. ) : NodeSSLConfiguration { val nodeHostAndPort: NetworkHostAndPort by config override val keyStorePassword: String by config @@ -66,7 +65,7 @@ class Verifier { val consumer = session.createConsumer(VERIFICATION_REQUESTS_QUEUE_NAME) val replyProducer = session.createProducer() consumer.setMessageHandler { - val request = VerifierApi.VerificationRequest.fromClientMessage(it) + val (request, context) = VerifierApi.VerificationRequest.fromClientMessage(it) log.debug { "Received verification request with id ${request.verificationId}" } val error = try { request.transaction.verify() @@ -77,7 +76,7 @@ class Verifier { } val reply = session.createMessage(false) val response = VerifierApi.VerificationResponse(request.verificationId, error) - response.writeToClientMessage(reply) + response.writeToClientMessage(reply, context) replyProducer.send(request.responseAddress, reply) it.acknowledge() } @@ -88,13 +87,18 @@ class Verifier { private fun initialiseSerialization() { SerializationDefaults.SERIALIZATION_FACTORY = SerializationFactoryImpl().apply { - registerScheme(KryoVerifierSerializationScheme()) + registerScheme(KryoVerifierSerializationScheme) + registerScheme(AMQPVerifierSerializationScheme) } + /** + * Even though default context is set to Kryo P2P, the encoding will be adjusted depending on the incoming + * request received, see use of [context] in [main] method. + */ SerializationDefaults.P2P_CONTEXT = KRYO_P2P_CONTEXT } } - class KryoVerifierSerializationScheme : AbstractKryoSerializationScheme() { + private object KryoVerifierSerializationScheme : AbstractKryoSerializationScheme() { override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean { return byteSequence == KryoHeaderV0_1 && target == SerializationContext.UseCase.P2P } @@ -102,4 +106,13 @@ class Verifier { override fun rpcClientKryoPool(context: SerializationContext) = throw UnsupportedOperationException() override fun rpcServerKryoPool(context: SerializationContext) = throw UnsupportedOperationException() } + + private object AMQPVerifierSerializationScheme : AbstractAMQPSerializationScheme() { + override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean { + return (byteSequence == AmqpHeaderV1_0 && (target == SerializationContext.UseCase.P2P)) + } + + override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory = throw UnsupportedOperationException() + override fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory = throw UnsupportedOperationException() + } } \ No newline at end of file