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 commit 2caa134)

* Set adequate permissions for the nodes such that NodeExplorer can connect

(cherry picked from commit ae88242)

* Set adequate permissions for the nodes such that NodeExplorer can connect

(cherry picked from commit ae88242)

* 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:
josecoll
2017-10-25 13:54:34 +01:00
committed by GitHub
parent 01d8ad41b4
commit ef7ccd3147
200 changed files with 4549 additions and 3192 deletions

View 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

View File

@ -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 {

View File

@ -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.
}

View File

@ -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)

View File

@ -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>

View File

@ -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

View File

@ -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()
}

View File

@ -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

View File

@ -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

View File

@ -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.
*

View File

@ -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

View File

@ -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)

View File

@ -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(

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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. */

View File

@ -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

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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>

View File

@ -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 {

View File

@ -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.
*/

View File

@ -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<*>

View File

@ -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())

View File

@ -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>

View File

@ -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
}
}

View File

@ -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:

View File

@ -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
}
}

View File

@ -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")) }
}
}

View File

@ -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)

View File

@ -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()

View File

@ -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

View File

@ -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()

View File

@ -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)

View File

@ -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 }
}
}

View File

@ -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")