mirror of
https://github.com/corda/corda.git
synced 2025-06-13 04:38:19 +00:00
Merge Open Source to Enterprise (#79)
* Check array size before accessing * Review fixes * CORDA-540: Make Verifier work in AMQP mode (#1870) * reference to finance module via not hardcoded group ID (#1515) * generic way to reference to group id when loading finance.jar via cordapp * Fixed the node shell to work with the DataFeed class * Attempt to make NodeStatePersistenceTests more stable (#1895) By ensuring that the nodes are properly started and aware of each other before firing any flows through them. Also minor refactoring. * Disable unstable test on Windows (#1899) * CORDA-530 Don't soft-lock non-fungible states (#1794) * Don't run unlock query if nothing was locked * Constructors should not have side-effects * [CORDA-442] let Driver run without network map (#1890) * [CORDA-442] let Driver run without network map - Nodes started by driver run without a networkMapNode. - Driver does not take a networkMapStartStrategy anymore - a new parameter in the configuration "noNetworkMapServiceMode" allows for a node not to be a networkMapNode nor to connect to one. - Driver now waits for each node to write its own NodeInfo file to disk and then copies it into each other node. - When driver starts a node N, it waits for every node to be have N nodes in their network map. Note: the code to copy around the NodeInfo files was already in DemoBench, the NodeInfoFilesCopier class was just moved from DemoBench into core (I'm very open to core not being the best place, please advise) * Added missing cordappPackage dependencies. (#1894) * Eliminate circular dependency of NodeSchedulerService on ServiceHub. (#1891) * Update customSchemas documentation. (#1902) * [CORDA-694] Commands visibility for Oracles (without sacrificing privacy) (#1835) new checkCommandVisibility feature for Oracles * CORDA-599 PersistentNetworkMapCache no longer circularly depends on SH (#1652) * CORDA-725 - Change AMQP identifier to officially assigned value This does change our header format so pre-cached test files need regenerating * CORDA-725 - update changelog * CORDA-680 Update cordapp packages documentation (#1901) * Introduce MockNetworkParameters * Cordformation in Kotlin (#1873) Cordformation rewritten in kotlin. * Kotlin migration * Review Comments * CORDA-704: Implement `@DoNotImplement` annotation (#1903) * Enhance the API Scanner plugin to monitor class annotations. * Implement @DoNotImplement annotation, and apply it. * Update API definition. * Update API change detection to handle @DoNotImplement. * Document the `@DoNotImplement` annotation. * Experimental support for PostgreSQL (#1525) * Cash selection refactoring such that 3d party DB providers are only required to implement Coin Selection SQL logic. * Re-added debug logging statement. * Updated to include PR review feedback from VK * Refactoring following rebase from master. * Fix broken JUnits following rebase. * Use JDBC ResultSet getBlob() and added custom serializer to address concern raised by tomtau in PR. * Fix failing JUnits. * Experimental support for PostgreSQL: CashSelection done using window functions * Moved postgresql version information into corda/build.gradle * Using a PreparedStatement in CashSelectionPostgreSQLImpl * Changed the PostgreSQL Cash Selection implementation to use the new refactored AbstractCashSelection * * Retire MockServiceHubInternal (#1909) * Introduce rigorousMock * Add test-utils and node-driver to generated documentation * Fix-up: Bank Of Corda sample (#1912) In the previous version when running with `--role ISSUER` the application failed to start. The reason was that in spite of `quantity` and `currency` were optional, un-necessary `requestParams` been constructed regardless. * move SMM * Interface changes for multi-threading * CORDA-351: added dependency check plugin to gradle build script (#1911) * CORDA-351: added dependency check plugin to gradle build script * CORDA-351: Added suppression stub file with example * CORDA-351: added suppresionFile property * CORDA-435 - Ensure Kryo only tests use Kryo serializatin context Also correct lambda typos (from lamba) * Network map service REST API wrapper (#1907) * Network map client - WIP * Java doc and doc for doc site * remove javax.ws dependency * NetworkParameter -> NetworkParameters * move network map client to node * Fix jetty test dependencies * NetworkParameter -> NetworkParameters * Address PR issues * Address PR issues and unit test fix * Address PR issues * Fixing Bank-Of-Corda Demo in `master` (#1922) * Fix-up: Bank Of Corda sample Use correct CorDapp packages to scan (cherry picked from commit2caa134
) * Set adequate permissions for the nodes such that NodeExplorer can connect (cherry picked from commitae88242
) * Set adequate permissions for the nodes such that NodeExplorer can connect (cherry picked from commitae88242
) * Correct run configuration * Fix-up port numbers * CORDA-435 - AMQP serialisation cannot work with private vals They won't be reported as properties by the introspector and thus we will fail to find a constructor for them. This makes sense as we will be unable to serialise an object whose members we cannot read * CORDA-435 - AMQP enablement fixes AMQP has different serialization rules than Kryo surrounding the way we introspect objects to work out how to construct them * [CORDA-442] make MockNetwork not start a networkmap node (#1908) * [CORDA-442] make MockNetwork not start a networkmap node Now MockNetwork will put the appropriate NodeInfos inside each running node networkMapCache. Tests relating to networkmap node starting and interaction have been removed since they where relaying on MockNetwork * Minor fix for api checker script to support macOS * Retrofit changes from Enterprise PR #61 (#1934) * Introduce MockNodeParameters/Args (#1923) * CORDA-736 Add some new features to corda.jar via node.conf for testing (#1926) * CORDA-699 Add injection or modification of memory network messages (#1920) * Updated API stability changeset to reflect new schema attribute name.
This commit is contained in:
18
core/src/main/kotlin/net/corda/core/DoNotImplement.kt
Normal file
18
core/src/main/kotlin/net/corda/core/DoNotImplement.kt
Normal file
@ -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
|
@ -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 {
|
||||
|
@ -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.
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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<String>
|
||||
|
@ -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
|
||||
|
@ -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<Boolean>()
|
||||
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>): 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<Boolean>) =
|
||||
pathList.mapIndexed { index, value -> if (value) (1 shl index) else 0 }.sum()
|
||||
}
|
||||
|
@ -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<out T> {
|
||||
/** This is where you should log things to. */
|
||||
@ -123,7 +133,7 @@ abstract class FlowLogic<out T> {
|
||||
*/
|
||||
@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<out T> {
|
||||
@Deprecated("Use FlowSession.sendAndReceive()", level = DeprecationLevel.WARNING)
|
||||
@Suspendable
|
||||
open fun <R : Any> sendAndReceive(receiveType: Class<R>, otherParty: Party, payload: Any): UntrustworthyData<R> {
|
||||
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<out T> {
|
||||
*/
|
||||
@Deprecated("Use FlowSession.sendAndReceiveWithRetry()", level = DeprecationLevel.WARNING)
|
||||
internal inline fun <reified R : Any> sendAndReceiveWithRetry(otherParty: Party, payload: Any): UntrustworthyData<R> {
|
||||
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 <R : Any> FlowSession.sendAndReceiveWithRetry(receiveType: Class<R>, payload: Any): UntrustworthyData<R> {
|
||||
return stateMachine.sendAndReceive(receiveType, counterparty, payload, flowUsedForSessions, retrySend = true)
|
||||
return stateMachine.sendAndReceive(receiveType, counterparty, payload, flowUsedForSessions, retrySend = true, maySkipCheckpoint = false)
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
internal inline fun <reified R : Any> FlowSession.sendAndReceiveWithRetry(payload: Any): UntrustworthyData<R> {
|
||||
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<out T> {
|
||||
@Deprecated("Use FlowSession.receive()", level = DeprecationLevel.WARNING)
|
||||
@Suspendable
|
||||
open fun <R : Any> receive(receiveType: Class<R>, otherParty: Party): UntrustworthyData<R> {
|
||||
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<out T> {
|
||||
*/
|
||||
@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<out T> {
|
||||
* 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
|
||||
|
@ -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<out FlowLogic<*>>, 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
|
@ -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 <R : Any> sendAndReceive(
|
||||
receiveType: Class<R>,
|
||||
payload: Any, maySkipCheckpoint: Boolean
|
||||
): UntrustworthyData<R>
|
||||
|
||||
/**
|
||||
* 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 <R : Any> receive(receiveType: Class<R>, maySkipCheckpoint: Boolean): UntrustworthyData<R>
|
||||
|
||||
/**
|
||||
* Suspends until [counterparty] sends us a message of type [receiveType].
|
||||
*
|
||||
@ -114,6 +159,18 @@ abstract class FlowSession {
|
||||
@Suspendable
|
||||
abstract fun <R : Any> receive(receiveType: Class<R>): UntrustworthyData<R>
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
|
@ -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
|
||||
|
@ -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<R> {
|
||||
@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<R> {
|
||||
otherParty: Party,
|
||||
payload: Any,
|
||||
sessionFlow: FlowLogic<*>,
|
||||
retrySend: Boolean = false): UntrustworthyData<T>
|
||||
retrySend: Boolean,
|
||||
maySkipCheckpoint: Boolean): UntrustworthyData<T>
|
||||
|
||||
@Suspendable
|
||||
fun <T : Any> receive(receiveType: Class<T>, otherParty: Party, sessionFlow: FlowLogic<*>): UntrustworthyData<T>
|
||||
fun <T : Any> receive(receiveType: Class<T>, otherParty: Party, sessionFlow: FlowLogic<*>, maySkipCheckpoint: Boolean): UntrustworthyData<T>
|
||||
|
||||
@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)
|
||||
|
@ -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(
|
||||
|
@ -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<A> : AutoCloseable {
|
||||
val id: StateMachineRunId
|
||||
val returnValue: CordaFuture<A>
|
||||
|
@ -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
|
||||
|
@ -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 <T : ContractState> toStateAndRef(stateRef: StateRef): StateAndRef<T> {
|
||||
val stx = validatedTransactions.getTransaction(stateRef.txhash) ?: throw TransactionResolutionException(stateRef.txhash)
|
||||
return stx.resolveBaseTransaction(this).outRef<T>(stateRef.index)
|
||||
return stx.resolveBaseTransaction(this).outRef(stateRef.index)
|
||||
}
|
||||
|
||||
private val legalIdentityKey: PublicKey get() = this.myInfo.legalIdentitiesAndCerts.first().owningKey
|
||||
|
@ -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
|
||||
|
@ -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. */
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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<MapChange>
|
||||
val changed: Observable<NetworkMapCache.MapChange>
|
||||
/** Future to track completion of the NetworkMapService registration. */
|
||||
val nodeReady: CordaFuture<Void?>
|
||||
|
||||
@ -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<List<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: [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<List<NodeInfo>, NetworkMapCache.MapChange>
|
||||
|
||||
/**
|
||||
* Look up the node info for a legal name.
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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<out T : ContractState>(val states: Iterable<StateAndRef<T>>) {
|
||||
*
|
||||
* 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.
|
||||
|
@ -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<Predicate>
|
||||
fun parseCriteria(criteria: QueryCriteria.FungibleAssetQueryCriteria): Collection<Predicate>
|
||||
|
@ -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<SortColumn>) {
|
||||
}
|
||||
|
||||
@CordaSerializable
|
||||
@DoNotImplement
|
||||
interface Attribute
|
||||
|
||||
enum class CommonStateAttribute(val attributeParent: String, val attributeChild: String?) : Attribute {
|
||||
|
@ -8,6 +8,8 @@ import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.sequence
|
||||
import java.sql.Blob
|
||||
|
||||
data class ObjectWithCompatibleContext<out T : Any>(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 <T : Any> deserialize(byteSequence: ByteSequence, clazz: Class<T>, 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 <T : Any> deserializeWithCompatibleContext(byteSequence: ByteSequence, clazz: Class<T>, context: SerializationContext): ObjectWithCompatibleContext<T>
|
||||
|
||||
/**
|
||||
* 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 <reified T : Any> 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 <reified T : Any> ByteSequence.deserializeWithCompatibleContext(serializationFactory: SerializationFactory = SerializationFactory.defaultFactory,
|
||||
context: SerializationContext = serializationFactory.defaultContext): ObjectWithCompatibleContext<T> {
|
||||
return serializationFactory.deserializeWithCompatibleContext(this, T::class.java, context)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience extension method for deserializing SerializedBytes with type matching, utilising the defaults.
|
||||
*/
|
||||
|
@ -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<*>
|
||||
|
@ -25,7 +25,7 @@ abstract class TraversableTransaction(open val componentGroups: List<ComponentGr
|
||||
override val outputs: List<TransactionState<ContractState>> = deserialiseComponentGroup(ComponentGroupEnum.OUTPUTS_GROUP, { SerializedBytes<TransactionState<ContractState>>(it).deserialize(context = SerializationFactory.defaultFactory.defaultContext.withAttachmentsClassLoader(attachments)) })
|
||||
|
||||
/** Ordered list of ([CommandData], [PublicKey]) pairs that instruct the contracts what to do. */
|
||||
val commands: List<Command<*>> = deserialiseComponentGroup(ComponentGroupEnum.COMMANDS_GROUP, { SerializedBytes<Command<*>>(it).deserialize(context = SerializationFactory.defaultFactory.defaultContext.withAttachmentsClassLoader(attachments)) })
|
||||
val commands: List<Command<*>> = deserialiseCommands()
|
||||
|
||||
override val notary: Party? = let {
|
||||
val notaries: List<Party> = deserialiseComponentGroup(ComponentGroupEnum.NOTARY_GROUP, { SerializedBytes<Party>(it).deserialize() })
|
||||
@ -74,6 +74,31 @@ abstract class TraversableTransaction(open val componentGroups: List<ComponentGr
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
// Method to deserialise Commands from its two groups:
|
||||
// COMMANDS_GROUP which contains the CommandData part
|
||||
// and SIGNERS_GROUP which contains the Signers part.
|
||||
private fun deserialiseCommands(): List<Command<*>> {
|
||||
// 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<List<PublicKey>>(it).deserialize() })
|
||||
val commandDataList = deserialiseComponentGroup(ComponentGroupEnum.COMMANDS_GROUP, { SerializedBytes<CommandData>(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<Int, MutableList<OpaqueBytes>> = hashMapOf()
|
||||
val filteredComponentNonces: MutableMap<Int, MutableList<SecureHash>> = hashMapOf()
|
||||
val filteredComponentHashes: MutableMap<Int, MutableList<SecureHash>> = hashMapOf() // Required for partial Merkle tree generation.
|
||||
var signersIncluded = false
|
||||
|
||||
fun <T : Any> 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<PublicKey> {
|
||||
try {
|
||||
return SerializedBytes<List<PublicKey>>(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())
|
||||
|
@ -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<TransactionSignature>
|
||||
|
||||
|
@ -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<ComponentGroup>, val privacySalt: Pr
|
||||
val componentGroupMap: MutableList<ComponentGroup> = 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
|
||||
}
|
||||
}
|
||||
|
@ -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<Int>()
|
||||
private val f2 = openFuture<Double>()
|
||||
private var invocations = 0
|
||||
private val log = mock<Logger>()
|
||||
private val log = rigorousMock<Logger>().also {
|
||||
doNothing().whenever(it).error(any(), any<Throwable>())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `firstOf short circuit`() {
|
||||
// Order not significant in this case:
|
||||
|
@ -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<ComponentVisibilityException> { 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<ComponentVisibilityException> { noCommandsFtx.checkCommandVisibility(DUMMY_KEY_1.public) }
|
||||
key1CommandsFtx.checkCommandVisibility(DUMMY_KEY_1.public)
|
||||
assertFailsWith<ComponentVisibilityException> { oneKey1CommandFtxA.checkCommandVisibility(DUMMY_KEY_1.public) }
|
||||
assertFailsWith<ComponentVisibilityException> { oneKey1CommandFtxB.checkCommandVisibility(DUMMY_KEY_1.public) }
|
||||
|
||||
allCommandsFtx.checkAllComponentsVisible(SIGNERS_GROUP)
|
||||
assertFailsWith<ComponentVisibilityException> { 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<IllegalStateException> { 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<IllegalStateException> { 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<ComponentVisibilityException> { 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<FilteredTransactionVerificationException> { ftxNoLastSigner.verify() }
|
||||
// checkCommandVisibility() will not pass.
|
||||
assertFailsWith<ComponentVisibilityException> { 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<ComponentVisibilityException> { 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<FilteredTransactionVerificationException> { ftxAlterSigner.verify() }
|
||||
// Also, checkAllComponentsVisible() will not pass (groupHash matching will fail).
|
||||
assertFailsWith<ComponentVisibilityException> { 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<FilteredTransactionVerificationException> { ftxAlterSignerB.verify() }
|
||||
// Also, checkAllComponentsVisible() will not pass (top level Merkle tree cannot be verified against transaction's id).
|
||||
assertFailsWith<ComponentVisibilityException> { ftxAlterSignerB.checkCommandVisibility(DUMMY_KEY_1.public) }
|
||||
|
||||
ftxConstructor.isAccessible = false
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<MerkleTreeException> { PartialMerkleTree.build(merkleTree, listOf<SecureHash>(SecureHash.sha256("20"))) }
|
||||
// One of the provided hashes is not in the tree.
|
||||
assertFailsWith<MerkleTreeException> { PartialMerkleTree.build(merkleTree, listOf<SecureHash>(SecureHash.sha256("20"), SecureHash.sha256("1"), SecureHash.sha256("5"))) }
|
||||
|
||||
val pmt = PartialMerkleTree.build(merkleTree, listOf<SecureHash>(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<MerkleTreeException> { 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<MerkleTreeException> { pmt.leafIndex(SecureHash.sha256("30")) }
|
||||
|
||||
val pmtFirstElementOnly = PartialMerkleTree.build(merkleTree, listOf<SecureHash>(SecureHash.sha256("0")))
|
||||
assertEquals(0, pmtFirstElementOnly.leafIndex(SecureHash.sha256("0")))
|
||||
// The provided hash is not in the tree.
|
||||
assertFailsWith<MerkleTreeException> { pmtFirstElementOnly.leafIndex(SecureHash.sha256("10")) }
|
||||
|
||||
val pmtLastElementOnly = PartialMerkleTree.build(merkleTree, listOf<SecureHash>(SecureHash.sha256("19")))
|
||||
assertEquals(19, pmtLastElementOnly.leafIndex(SecureHash.sha256("19")))
|
||||
// The provided hash is not in the tree.
|
||||
assertFailsWith<MerkleTreeException> { pmtLastElementOnly.leafIndex(SecureHash.sha256("10")) }
|
||||
|
||||
val pmtOneElement = PartialMerkleTree.build(merkleTree, listOf<SecureHash>(SecureHash.sha256("5")))
|
||||
assertEquals(5, pmtOneElement.leafIndex(SecureHash.sha256("5")))
|
||||
// The provided hash is not in the tree.
|
||||
assertFailsWith<MerkleTreeException> { 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<MerkleTreeException> { pmtAllIncluded.leafIndex(SecureHash.sha256("30")) }
|
||||
}
|
||||
}
|
||||
|
@ -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<MockNetwork.MockNode> {
|
||||
override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?,
|
||||
id: Int, notaryIdentity: Pair<ServiceInfo, KeyPair>?,
|
||||
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<MockNetwork.MockNode> {
|
||||
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)
|
||||
|
@ -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()
|
||||
|
@ -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<CordaRPCOps>(
|
||||
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
|
||||
|
@ -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()
|
||||
|
@ -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<Int>()
|
||||
val x = Exception()
|
||||
val log = mock<Logger>()
|
||||
val log = rigorousMock<Logger>()
|
||||
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<Int>()
|
||||
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<Int>()
|
||||
val g = f.flatMap(block)
|
||||
val x = Exception()
|
||||
@ -102,7 +103,8 @@ class CordaFutureTest {
|
||||
|
||||
@Test
|
||||
fun `andForget works`() {
|
||||
val log = mock<Logger>()
|
||||
val log = rigorousMock<Logger>()
|
||||
doNothing().whenever(log).error(any(), any<Throwable>())
|
||||
val throwable = Exception("Boom")
|
||||
val executor = Executors.newSingleThreadExecutor()
|
||||
executor.fork { throw throwable }.andForget(log)
|
||||
|
@ -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<MockNetwork.MockNode> {
|
||||
override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?,
|
||||
id: Int, notaryIdentity: Pair<ServiceInfo, KeyPair>?, entropyRoot: BigInteger): MockNetwork.MockNode {
|
||||
return object : MockNetwork.MockNode(config, network, networkMapAddr, id, notaryIdentity, entropyRoot) {
|
||||
client = mockNet.createNode(MockNodeParameters(client.internals.id), object : MockNetwork.Factory<MockNetwork.MockNode> {
|
||||
override fun create(args: MockNodeArgs): MockNetwork.MockNode {
|
||||
return object : MockNetwork.MockNode(args) {
|
||||
override fun start() = super.start().apply { attachments.checkAttachmentsOnLoad = checkAttachmentsOnLoad }
|
||||
}
|
||||
}
|
||||
|
@ -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")
|
||||
|
Reference in New Issue
Block a user