mirror of
https://github.com/corda/corda.git
synced 2025-05-02 08:43:15 +00:00
Merge branch 'master' into feature/CORDA-2570/revert-to-one-attachment-per-contract
# Conflicts: # core/src/main/kotlin/net/corda/core/internal/TransactionVerifierServiceInternal.kt # core/src/test/kotlin/net/corda/core/contracts/ConstraintsPropagationTests.kt
This commit is contained in:
commit
00ddb5793d
@ -1132,10 +1132,6 @@ public static final class net.corda.core.contracts.TransactionVerificationExcept
|
||||
public <init>(net.corda.core.crypto.SecureHash, net.corda.core.contracts.TransactionState<? extends net.corda.core.contracts.ContractState>)
|
||||
##
|
||||
@CordaSerializable
|
||||
public static final class net.corda.core.contracts.TransactionVerificationException$TransactionVerificationVersionException extends net.corda.core.contracts.TransactionVerificationException
|
||||
public <init>(net.corda.core.crypto.SecureHash, String, String, String)
|
||||
##
|
||||
@CordaSerializable
|
||||
public abstract class net.corda.core.contracts.TypeOnlyCommandData extends java.lang.Object implements net.corda.core.contracts.CommandData
|
||||
public <init>()
|
||||
public boolean equals(Object)
|
||||
|
@ -275,15 +275,6 @@ abstract class TransactionVerificationException(val txId: SecureHash, message: S
|
||||
@KeepForDJVM
|
||||
class OverlappingAttachmentsException(txId: SecureHash, path: String) : TransactionVerificationException(txId, "Multiple attachments define a file at $path.", null)
|
||||
|
||||
/**
|
||||
* Thrown when a transaction appears to be trying to downgrade a state to an earlier version of the app that defines it.
|
||||
* This could be an attempt to exploit a bug in the app, so we prevent it.
|
||||
*/
|
||||
@KeepForDJVM
|
||||
class TransactionVerificationVersionException(txId: SecureHash, contractClassName: ContractClassName, inputVersion: String, outputVersion: String)
|
||||
: TransactionVerificationException(txId, "No-Downgrade Rule has been breached for contract class $contractClassName. " +
|
||||
"The output state contract version '$outputVersion' is lower than the version of the input state '$inputVersion'.", null)
|
||||
|
||||
/**
|
||||
* Thrown to indicate that a contract attachment is not signed by the network-wide package owner. Please note that
|
||||
* the [txId] will always be [SecureHash.zeroHash] because package ownership is an error with a particular attachment,
|
||||
|
@ -375,6 +375,24 @@ class ConstraintsPropagationTests {
|
||||
SignableData(wireTransaction.id, SignatureMetadata(4, Crypto.findSignatureScheme(nodeKey).schemeNumberID)), nodeKey))
|
||||
recordTransactions(SignedTransaction(wireTransaction, sigs))
|
||||
}
|
||||
@Test
|
||||
fun `Input state contract version may be incompatible with lower version`() {
|
||||
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||
ledgerServices.recordTransaction(transaction {
|
||||
attachment(Cash.PROGRAM_ID, SecureHash.allOnesHash, listOf(hashToSignatureConstraintsKey), mapOf(Attributes.Name.IMPLEMENTATION_VERSION.toString() to "2"))
|
||||
output(Cash.PROGRAM_ID, "c1", DUMMY_NOTARY, null, SignatureAttachmentConstraint(hashToSignatureConstraintsKey), Cash.State(1000.POUNDS `issued by` ALICE_PARTY.ref(1), ALICE_PARTY))
|
||||
command(ALICE_PUBKEY, Cash.Commands.Issue())
|
||||
verifies()
|
||||
})
|
||||
transaction {
|
||||
attachment(Cash.PROGRAM_ID, SecureHash.zeroHash, listOf(hashToSignatureConstraintsKey), mapOf(Attributes.Name.IMPLEMENTATION_VERSION.toString() to "1"))
|
||||
input("c1")
|
||||
output(Cash.PROGRAM_ID, "c2", DUMMY_NOTARY, null, SignatureAttachmentConstraint(hashToSignatureConstraintsKey), Cash.State(1000.POUNDS `issued by` ALICE_PARTY.ref(1), BOB_PARTY))
|
||||
command(ALICE_PUBKEY, Cash.Commands.Move())
|
||||
verifies()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Input state contract version is compatible with the same version`() {
|
||||
@ -414,6 +432,50 @@ class ConstraintsPropagationTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Input states contract version may be lower that current contract version`() {
|
||||
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||
ledgerServices.recordTransaction(transaction {
|
||||
attachment(Cash.PROGRAM_ID, SecureHash.allOnesHash, listOf(hashToSignatureConstraintsKey), mapOf(Attributes.Name.IMPLEMENTATION_VERSION.toString() to "1"))
|
||||
output(Cash.PROGRAM_ID, "c1", DUMMY_NOTARY, null, SignatureAttachmentConstraint(hashToSignatureConstraintsKey), Cash.State(1000.POUNDS `issued by` ALICE_PARTY.ref(1), ALICE_PARTY))
|
||||
command(ALICE_PUBKEY, Cash.Commands.Issue())
|
||||
verifies()
|
||||
})
|
||||
ledgerServices.recordTransaction(transaction {
|
||||
attachment(Cash.PROGRAM_ID, SecureHash.zeroHash, listOf(hashToSignatureConstraintsKey), mapOf(Attributes.Name.IMPLEMENTATION_VERSION.toString() to "2"))
|
||||
output(Cash.PROGRAM_ID, "c2", DUMMY_NOTARY, null, SignatureAttachmentConstraint(hashToSignatureConstraintsKey), Cash.State(1000.POUNDS `issued by` ALICE_PARTY.ref(1), ALICE_PARTY))
|
||||
command(ALICE_PUBKEY, Cash.Commands.Issue())
|
||||
verifies()
|
||||
})
|
||||
transaction {
|
||||
input("c1")
|
||||
input("c2")
|
||||
output(Cash.PROGRAM_ID, "c3", DUMMY_NOTARY, null, SignatureAttachmentConstraint(hashToSignatureConstraintsKey), Cash.State(2000.POUNDS `issued by` ALICE_PARTY.ref(1), BOB_PARTY))
|
||||
command(ALICE_PUBKEY, Cash.Commands.Move())
|
||||
verifies()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Input state with contract version can be downgraded to no version`() {
|
||||
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||
ledgerServices.recordTransaction(transaction {
|
||||
attachment(Cash.PROGRAM_ID, SecureHash.allOnesHash, listOf(hashToSignatureConstraintsKey), mapOf(Attributes.Name.IMPLEMENTATION_VERSION.toString() to "2"))
|
||||
output(Cash.PROGRAM_ID, "c1", DUMMY_NOTARY, null, SignatureAttachmentConstraint(hashToSignatureConstraintsKey), Cash.State(1000.POUNDS `issued by` ALICE_PARTY.ref(1), ALICE_PARTY))
|
||||
command(ALICE_PUBKEY, Cash.Commands.Issue())
|
||||
verifies()
|
||||
})
|
||||
transaction {
|
||||
attachment(Cash.PROGRAM_ID, SecureHash.zeroHash, listOf(hashToSignatureConstraintsKey), emptyMap())
|
||||
input("c1")
|
||||
output(Cash.PROGRAM_ID, "c2", DUMMY_NOTARY, null, SignatureAttachmentConstraint(hashToSignatureConstraintsKey), Cash.State(1000.POUNDS `issued by` ALICE_PARTY.ref(1), BOB_PARTY))
|
||||
command(ALICE_PUBKEY, Cash.Commands.Move())
|
||||
verifies()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Input state without contract version is compatible with any version`() {
|
||||
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||
|
@ -116,7 +116,7 @@ CorDapp is 4 or greater, then transaction verification will fail with a ``Transa
|
||||
the owning ``Contract`` *can* be identified, but the ``ContractState`` has been bundled with a different contract, then
|
||||
transaction verification will fail with a ``TransactionContractConflictException``.
|
||||
|
||||
.. _contract_non-downgrade_rule_ref:
|
||||
.. _contract_downgrade_rule_ref:
|
||||
|
||||
App versioning with signature constraints
|
||||
-----------------------------------------
|
||||
|
@ -100,10 +100,7 @@ properly for future releases.
|
||||
future not hold true. You should know the platform version of the node releases you want to target.
|
||||
|
||||
The new ``versionId`` number is a version code for **your** app, and is unrelated to Corda's own versions.
|
||||
It is used to block state downgrades: when a state constraint can be satisfied
|
||||
by multiple attachments, the version is tracked in the ledger and cannot decrement. This ensures security
|
||||
fixes in CorDapps stick and can't be reversed by downgrading states to an earlier version. See
|
||||
":ref:`contract_non-downgrade_rule_ref`" for more information.
|
||||
It is used to informative purposes only. See ":ref:`contract_downgrade_rule_ref`" for more information.
|
||||
|
||||
**Split your app into contract and workflow JARs.** The duplication between ``contract`` and ``workflow`` blocks exists because you should split your app into
|
||||
two separate JARs/modules, one that contains on-ledger validation code like states and contracts, and one
|
||||
|
@ -50,10 +50,6 @@ Version 4.0
|
||||
|
||||
* ``JacksonSupport.createInMemoryMapper`` was incorrectly marked as deprecated and is no longer so.
|
||||
|
||||
* Transaction building and verification enforces new contract attachment version non-downgrade rule.
|
||||
For a given contract class, the contract attachment of the output states must be of the same or newer version than the contract attachment of the input states.
|
||||
See :ref:`Contract attachment non-downgrade rule <contract_non-downgrade_rule_ref>` for further information.
|
||||
|
||||
* Standardised CorDapp version identifiers in jar manifests (aligned with associated cordapp Gradle plugin changes).
|
||||
Updated all samples to reflect new conventions.
|
||||
|
||||
|
@ -122,9 +122,6 @@ allows tool developers to assume that if a class name appears to be owned by an
|
||||
semantics of that class actually *were* defined by that organisation, thus eliminating edge cases that
|
||||
might otherwise cause confusion.
|
||||
|
||||
**No downgrades.** Transaction building and verification enforces new contract attachment version non-downgrade rule.
|
||||
For a given contract class, the contract attachment of the output states must be of the same or newer version than
|
||||
the contract attachment of the input states. See :ref:`Contract attachment non-downgrade rule <contract_non-downgrade_rule_ref>` for further information.
|
||||
|
||||
Network parameters in transactions
|
||||
++++++++++++++++++++++++++++++++++
|
||||
|
@ -59,32 +59,72 @@ The example CorDapp has the following structure:
|
||||
.. sourcecode:: none
|
||||
|
||||
.
|
||||
├── LICENCE
|
||||
├── README.md
|
||||
├── TRADEMARK
|
||||
├── build.gradle
|
||||
├── clients
|
||||
│ ├── build.gradle
|
||||
│ └── src
|
||||
│ └── main
|
||||
│ └── kotlin
|
||||
│ └── com.example.server
|
||||
│ ├── MainController.kt
|
||||
│ ├── NodeRPCConnection.kt
|
||||
│ └── Server.kt
|
||||
│ ├── resources
|
||||
│ ├── public
|
||||
│ ├── js
|
||||
│ ├── kotlin
|
||||
│ │ └── com
|
||||
│ │ └── example
|
||||
│ │ └── server
|
||||
│ │ ├── MainController.kt
|
||||
│ │ ├── NodeRPCConnection.kt
|
||||
│ │ └── Server.kt
|
||||
│ └── resources
|
||||
│ ├── application.properties
|
||||
│ └── public
|
||||
│ ├── index.html
|
||||
│ └── js
|
||||
│ └── angular-module.js
|
||||
│ └── index.html
|
||||
│ └── application.properties
|
||||
│ └── build.gradle
|
||||
│
|
||||
├── config
|
||||
│ ├── dev
|
||||
│ │ └── log4j2.xml
|
||||
│ └── test
|
||||
│ └── log4j2.xml
|
||||
├── contracts-java
|
||||
│ ├── build.gradle
|
||||
│ └── src
|
||||
│ └── main
|
||||
│ └── java
|
||||
│ └── com
|
||||
│ └── example
|
||||
│ ├── contract
|
||||
│ │ └── IOUContract.java
|
||||
│ ├── schema
|
||||
│ │ ├── IOUSchema.java
|
||||
│ │ └── IOUSchemaV1.java
|
||||
│ └── state
|
||||
│ └── IOUState.java
|
||||
├── contracts-kotlin
|
||||
│ ├── build.gradle
|
||||
│ └── src
|
||||
│ └── main
|
||||
│ └── kotlin
|
||||
│ └── com
|
||||
│ └── example
|
||||
│ ├── contract
|
||||
│ │ └── IOUContract.kt
|
||||
│ ├── schema
|
||||
│ │ └── IOUSchema.kt
|
||||
│ └── state
|
||||
│ └── IOUState.kt
|
||||
├── cordapp-example.iml
|
||||
├── gradle
|
||||
│ └── wrapper
|
||||
│ ├── gradle-wrapper.jar
|
||||
│ └── gradle-wrapper.properties
|
||||
├── java-source
|
||||
├── gradle.properties
|
||||
├── gradlew
|
||||
├── gradlew.bat
|
||||
├── lib
|
||||
│ ├── README.txt
|
||||
│ └── quasar.jar
|
||||
├── settings.gradle
|
||||
├── workflows-java
|
||||
│ ├── build.gradle
|
||||
│ └── src
|
||||
│ ├── integrationTest
|
||||
@ -93,26 +133,11 @@ The example CorDapp has the following structure:
|
||||
│ │ └── example
|
||||
│ │ └── DriverBasedTests.java
|
||||
│ ├── main
|
||||
│ │ ── java
|
||||
│ │ └── com
|
||||
│ │ └── example
|
||||
│ │ ├── api
|
||||
│ │ │ └── ExampleApi.java
|
||||
│ │ ├── client
|
||||
│ │ │ └── ExampleClientRPC.java
|
||||
│ │ ├── contract
|
||||
│ │ │ └── IOUContract.java
|
||||
│ │ ├── flow
|
||||
│ │ │ └── ExampleFlow.java
|
||||
│ │ ├── plugin
|
||||
│ │ │ └── ExamplePlugin.java
|
||||
│ │ ├── schema
|
||||
│ │ │ ├── IOUSchema.java
|
||||
│ │ │ └── IOUSchemaV1.java
|
||||
│ │ └── state
|
||||
│ │ └── IOUState.java
|
||||
│ │
|
||||
│ │
|
||||
│ │ └── java
|
||||
│ │ └── com
|
||||
│ │ └── example
|
||||
│ │ └── flow
|
||||
│ │ └── ExampleFlow.java
|
||||
│ └── test
|
||||
│ └── java
|
||||
│ └── com
|
||||
@ -122,20 +147,29 @@ The example CorDapp has the following structure:
|
||||
│ │ └── IOUContractTests.java
|
||||
│ └── flow
|
||||
│ └── IOUFlowTests.java
|
||||
├── kotlin-source
|
||||
│ ├── ...
|
||||
├── lib
|
||||
│ ├── README.txt
|
||||
│ └── quasar.jar
|
||||
├── .gitignore
|
||||
├── LICENCE
|
||||
├── README.md
|
||||
├── TRADEMARK
|
||||
└── workflows-kotlin
|
||||
├── build.gradle
|
||||
├── gradle.properties
|
||||
├── gradlew
|
||||
├── gradlew.bat
|
||||
└── settings.gradle
|
||||
└── src
|
||||
├── integrationTest
|
||||
│ └── kotlin
|
||||
│ └── com
|
||||
│ └── example
|
||||
│ └── DriverBasedTests.kt
|
||||
├── main
|
||||
│ └── kotlin
|
||||
│ └── com
|
||||
│ └── example
|
||||
│ └── flow
|
||||
│ └── ExampleFlow.kt
|
||||
└── test
|
||||
└── kotlin
|
||||
└── com
|
||||
└── example
|
||||
├── NodeDriver.kt
|
||||
├── contract
|
||||
│ └── IOUContractTests.kt
|
||||
└── flow
|
||||
└── IOUFlowTests.kt
|
||||
|
||||
The key files and directories are as follows:
|
||||
|
||||
@ -145,13 +179,8 @@ The key files and directories are as follows:
|
||||
about which version is required
|
||||
* **lib** contains the Quasar jar which rewrites our CorDapp's flows to be checkpointable
|
||||
* **clients** contains the source code for spring boot integration
|
||||
* **java-source** contains the source code for the example CorDapp written in Java
|
||||
|
||||
* **java-source/src/main/java** contains the source code for the example CorDapp
|
||||
* **java-source/src/test/java** contains unit tests for the contracts and flows, and the driver to run the nodes
|
||||
via IntelliJ
|
||||
|
||||
* **kotlin-source** contains the same source code, but written in Kotlin. CorDapps can be developed in either Java and Kotlin
|
||||
* **contracts-java** and **workflows-java** contain the source code for the example CorDapp written in Java
|
||||
* **contracts-kotlin** and **workflows-kotlin** contain the same source code, but written in Kotlin. CorDapps can be developed in either Java and Kotlin
|
||||
|
||||
Running the example CorDapp
|
||||
---------------------------
|
||||
@ -177,10 +206,10 @@ Building the example CorDapp
|
||||
* Windows: ``gradlew.bat deployNodes``
|
||||
|
||||
.. note:: CorDapps can be written in any language targeting the JVM. In our case, we've provided the example source in
|
||||
both Kotlin (``/kotlin-source/src``) and Java (``/java-source/src``). Since both sets of source files are
|
||||
functionally identical, we will refer to the Kotlin version throughout the documentation.
|
||||
both Kotlin and Java. Since both sets of source files are functionally identical, we will refer to the Kotlin version
|
||||
throughout the documentation.
|
||||
|
||||
* After the build finishes, you will see the following output in the ``kotlin-source/build/nodes`` folder:
|
||||
* After the build finishes, you will see the following output in the ``workflows-kotlin/build/nodes`` folder:
|
||||
|
||||
* A folder for each generated node
|
||||
* A ``runnodes`` shell script for running all the nodes simultaneously on osX
|
||||
@ -211,12 +240,13 @@ Building the example CorDapp
|
||||
|
||||
Running the example CorDapp
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Start the nodes and Spring Boot servers by running the following command from the root of the ``cordapp-example`` folder:
|
||||
Start the nodes by running the following command from the root of the ``cordapp-example`` folder:
|
||||
|
||||
* Unix/Mac OSX: ``kotlin-source/build/nodes/runnodes``
|
||||
* Windows: ``call kotlin-source\build\nodes\runnodes.bat``
|
||||
* Unix/Mac OSX: ``workflows-kotlin/build/nodes/runnodes``
|
||||
* Windows: ``call workflows-kotlin\build\nodes\runnodes.bat``
|
||||
|
||||
Each Spring Boot server needs to be started in it's own terminal, replace X with A, B and C:
|
||||
|
||||
Each Spring Boot server needs to be started in it's own terminal, replace X with A, B and C
|
||||
* Unix/Mac OSX: ``./gradlew runPartyXServer``
|
||||
* Windows: ``gradlew.bat runPartyXServer``
|
||||
|
||||
@ -236,7 +266,7 @@ For each node, the ``runnodes`` script creates a node tab/window:
|
||||
--- Corda Open Source corda-3.0 (4157c25) -----------------------------------------------
|
||||
|
||||
|
||||
Logs can be found in : /Users/joeldudley/Desktop/cordapp-example/kotlin-source/build/nodes/PartyA/logs
|
||||
Logs can be found in : /Users/joeldudley/Desktop/cordapp-example/workflows-kotlin/build/nodes/PartyA/logs
|
||||
Database connection url is : jdbc:h2:tcp://localhost:59472/node
|
||||
Incoming connection address : localhost:10005
|
||||
Listening on port : 10005
|
||||
@ -432,13 +462,13 @@ The nodes can be configured to communicate as a network even when distributed ac
|
||||
* Unix/Mac OSX: ``./gradlew deployNodes``
|
||||
* Windows: ``gradlew.bat deployNodes``
|
||||
|
||||
* Navigate to the build folder (``kotlin-source/build/nodes``)
|
||||
* Navigate to the build folder (``workflows-kotlin/build/nodes``)
|
||||
* For each node, open its ``node.conf`` file and change ``localhost`` in its ``p2pAddress`` to the IP address of the machine
|
||||
where the node will be run (e.g. ``p2pAddress="10.18.0.166:10007"``)
|
||||
* These changes require new node-info files to be distributed amongst the nodes. Use the network bootstrapper tool
|
||||
(see :doc:`network-bootstrapper`) to update the files and have them distributed locally:
|
||||
|
||||
``java -jar network-bootstrapper.jar kotlin-source/build/nodes``
|
||||
``java -jar network-bootstrapper.jar workflows-kotlin/build/nodes``
|
||||
|
||||
* Move the node folders to their individual machines (e.g. using a USB key). It is important that none of the
|
||||
nodes - including the notary - end up on more than one machine. Each computer should also have a copy of ``runnodes``
|
||||
|
@ -96,9 +96,7 @@ It's entirely expected and reasonable to have an open source contracts module an
|
||||
sophisticated or proprietary business logic, machine learning models, even user interface code. There's nothing that restricts it to just
|
||||
being Corda flows or services.
|
||||
|
||||
.. important:: The ``versionId`` specified for the JAR manifest is checked by the platform. Downgrades are not allowed: you cannot take a state
|
||||
that was created with version 5 of your app, and then create a state with version 4. This is to prevent attacks in which bugs
|
||||
are fixed, but an adversary uses an old version of the app to continue exploiting them. Version tracking in states is handled for you
|
||||
automatically as long as the information is provided in your Gradle file. See ":ref:`contract_non-downgrade_rule_ref`" for more information.
|
||||
.. important:: The ``versionId`` specified for the JAR manifest is checked by the platform and is used for informative purposes only.
|
||||
See ":ref:`contract_downgrade_rule_ref`" for more information.
|
||||
|
||||
.. note:: You can read the original design doc here: :doc:`design/targetversion/design`.
|
Loading…
x
Reference in New Issue
Block a user