Merge remote-tracking branch 'corda-public/master'

This commit is contained in:
Chris Rankin 2017-01-26 10:16:47 +00:00
commit f2df9d36fa
9095 changed files with 444278 additions and 150156 deletions

View File

@ -3,7 +3,7 @@
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<option name="MAIN_CLASS_NAME" value="net.corda.attachmentdemo.AttachmentDemoKt" />
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
<option name="PROGRAM_PARAMETERS" value="--role=RECIPIENT" />
<option name="PROGRAM_PARAMETERS" value="--role=RECIPIENT --certificates=&quot;build/attachment-demo-nodes/Bank B/certificates&quot;" />
<option name="WORKING_DIRECTORY" value="" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
@ -12,4 +12,4 @@
<envs />
<method />
</configuration>
</component>
</component>

View File

@ -3,7 +3,7 @@
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<option name="MAIN_CLASS_NAME" value="net.corda.attachmentdemo.AttachmentDemoKt" />
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
<option name="PROGRAM_PARAMETERS" value="--role SENDER" />
<option name="PROGRAM_PARAMETERS" value="--role SENDER --certificates=&quot;build/attachment-demo-nodes/Bank A/certificates&quot;" />
<option name="WORKING_DIRECTORY" value="" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
@ -12,4 +12,4 @@
<envs />
<method />
</configuration>
</component>
</component>

View File

@ -0,0 +1,15 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Explorer - demo nodes" type="JetRunConfigurationType" factoryName="Kotlin" singleton="true">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<option name="MAIN_CLASS_NAME" value="net.corda.explorer.MainKt" />
<option name="VM_PARAMETERS" value="" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
<option name="ALTERNATIVE_JRE_PATH" value="1.8" />
<option name="PASS_PARENT_ENVS" value="true" />
<module name="explorer_main" />
<envs />
<method />
</configuration>
</component>

View File

@ -0,0 +1,15 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Explorer - demo nodes (simulation)" type="JetRunConfigurationType" factoryName="Kotlin" singleton="true">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<option name="MAIN_CLASS_NAME" value="net.corda.explorer.MainKt" />
<option name="VM_PARAMETERS" value="" />
<option name="PROGRAM_PARAMETERS" value="-S" />
<option name="WORKING_DIRECTORY" value="" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
<option name="ALTERNATIVE_JRE_PATH" value="1.8" />
<option name="PASS_PARENT_ENVS" value="true" />
<module name="explorer_main" />
<envs />
<method />
</configuration>
</component>

View File

@ -1,15 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Notary Demo: Run Nodes" type="JetRunConfigurationType" factoryName="Kotlin" singleton="true">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<option name="MAIN_CLASS_NAME" value="net.corda.notarydemo.MainKt" />
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PASS_PARENT_ENVS" value="true" />
<module name="raft-notary-demo_main" />
<envs />
<method />
</configuration>
</component>

View File

@ -1,15 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Notary Demo: Run Notarisation" type="JetRunConfigurationType" factoryName="Kotlin" singleton="true">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<option name="MAIN_CLASS_NAME" value="net.corda.notarydemo.NotaryDemoKt" />
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PASS_PARENT_ENVS" value="true" />
<module name="raft-notary-demo_main" />
<envs />
<method />
</configuration>
</component>

View File

@ -0,0 +1,15 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Raft Notary Demo: Run Nodes" type="JetRunConfigurationType" factoryName="Kotlin" singleton="true">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<option name="MAIN_CLASS_NAME" value="net.corda.notarydemo.MainKt" />
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PASS_PARENT_ENVS" value="true" />
<module name="raft-notary-demo_main" />
<envs />
<method />
</configuration>
</component>

View File

@ -0,0 +1,15 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Raft Notary Demo: Run Notarisation" type="JetRunConfigurationType" factoryName="Kotlin" singleton="true">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<option name="MAIN_CLASS_NAME" value="net.corda.notarydemo.NotaryDemoKt" />
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PASS_PARENT_ENVS" value="true" />
<module name="raft-notary-demo_main" />
<envs />
<method />
</configuration>
</component>

View File

@ -3,7 +3,7 @@
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<option name="MAIN_CLASS_NAME" value="net.corda.traderdemo.TraderDemoKt" />
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
<option name="PROGRAM_PARAMETERS" value="--role BUYER" />
<option name="PROGRAM_PARAMETERS" value="--role BUYER --certificates=&quot;build/trader-demo-nodes/Bank A/certificates&quot;" />
<option name="WORKING_DIRECTORY" value="" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
@ -12,4 +12,4 @@
<envs />
<method />
</configuration>
</component>
</component>

View File

@ -3,7 +3,7 @@
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<option name="MAIN_CLASS_NAME" value="net.corda.traderdemo.TraderDemoKt" />
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
<option name="PROGRAM_PARAMETERS" value="--role SELLER" />
<option name="PROGRAM_PARAMETERS" value="--role SELLER --certificates=&quot;build/trader-demo-nodes/Bank B/certificates&quot;" />
<option name="WORKING_DIRECTORY" value="" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
@ -12,4 +12,4 @@
<envs />
<method />
</configuration>
</component>
</component>

View File

@ -19,7 +19,13 @@ Read our full and planned feature list [here](https://docs.corda.net/inthebox.ht
Firstly, read the [Getting started](https://docs.corda.net/getting-set-up.html) documentation.
Watching the following webinars will give you a great introduction to Corda:
Next, use the following guides to set up your dev environment:
* If you are on **Windows** [use this getting started guide](https://www.corda.net/wp-content/uploads/2017/01/Corda-Windows-Quick-start-guide-1.pdf) which also explains through how to run the sample apps.
* Alternatively if you are on **Mac/Linux**, [watch this brief Webinar](https://vimeo.com/200167665) which walks through getting Corda, installing it, building it, running nodes and opening projects in IntelliJ.
After the above, watching the following webinars will give you a great introduction to Corda:
### Webinar 1 [Introduction to Corda](https://vimeo.com/192757743/c2ec39c1e1)
@ -72,3 +78,11 @@ Please read [here](./CONTRIBUTING.md).
## License
[Apache 2.0](./LICENCE)
## Acknowledgements
![YourKit](https://www.yourkit.com/images/yklogo.png)
YourKit supports open source projects with its full-featured Java Profiler.
YourKit, LLC is the creator of [YourKit Java Profiler](https://www.yourkit.com/java/profiler/) and [YourKit .NET Profiler](https://www.yourkit.com/.net/profiler/), innovative and intelligent tools for profiling Java and .NET applications.

View File

@ -4,19 +4,32 @@ buildscript {
file("publish.properties").withInputStream { props.load(it) }
// Our version: bump this on release.
ext.corda_version = "0.7-SNAPSHOT"
ext.corda_version = "0.8-SNAPSHOT"
ext.gradle_plugins_version = props.getProperty("gradlePluginsVersion")
// Dependency versions. Can run 'gradle dependencyUpdates' to find new versions of things.
//
// TODO: Sort this alphabetically.
ext.kotlin_version = '1.0.5-2'
ext.quasar_version = '0.7.6'
ext.quasar_version = '0.7.6' // TODO: Upgrade to 0.7.7+ when Quasar bug 238 is resolved.
ext.asm_version = '0.5.3'
ext.artemis_version = '1.4.0'
ext.jackson_version = '2.8.0.rc2'
ext.artemis_version = '1.5.1'
ext.jackson_version = '2.8.5'
ext.jetty_version = '9.3.9.v20160517'
ext.jersey_version = '2.23.1'
ext.jolokia_version = '2.0.0-M1'
ext.assertj_version = '3.5.1'
ext.log4j_version = '2.6.2'
ext.bouncycastle_version = '1.54'
ext.jersey_version = '2.25'
ext.jolokia_version = '2.0.0-M3'
ext.assertj_version = '3.6.1'
ext.log4j_version = '2.7'
ext.bouncycastle_version = '1.56'
ext.guava_version = '19.0'
ext.quickcheck_version = '0.7'
ext.okhttp_version = '3.5.0'
ext.typesafe_config_version = '1.3.1'
ext.junit_version = '4.12'
ext.jopt_simple_version = '5.0.2'
ext.jansi_version = '1.14'
ext.hibernate_version = '5.2.6.Final'
ext.dokka_version = '0.9.13'
repositories {
mavenLocal()
@ -33,9 +46,8 @@ buildscript {
classpath "net.corda.plugins:publish-utils:$gradle_plugins_version"
classpath "net.corda.plugins:quasar-utils:$gradle_plugins_version"
classpath "net.corda.plugins:cordformation:$gradle_plugins_version"
// Can run 'gradle dependencyUpdates' to find new versions of things.
classpath 'com.github.ben-manes:gradle-versions-plugin:0.12.0'
classpath 'com.github.ben-manes:gradle-versions-plugin:0.13.0'
classpath "org.jetbrains.dokka:dokka-gradle-plugin:${dokka_version}"
}
}
@ -51,6 +63,7 @@ apply plugin: 'com.github.ben-manes.versions'
apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'net.corda.plugins.quasar-utils'
apply plugin: 'net.corda.plugins.cordformation'
apply plugin: 'org.jetbrains.dokka'
// We need the following three lines even though they're inside an allprojects {} block below because otherwise
// IntelliJ gets confused when importing the project and ends up erasing and recreating the .idea directory, along
@ -95,7 +108,7 @@ repositories {
// Required for building out the fat JAR.
dependencies {
compile project(':node')
compile "com.google.guava:guava:19.0"
compile "com.google.guava:guava:$guava_version"
runtime project(path: ":node:capsule", configuration: 'runtimeArtifacts')
}
@ -129,7 +142,6 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['build']) {
networkMap "Controller"
node {
name "Controller"
dirName "controller"
nearestCity "London"
advertisedServices = ["corda.notary.validating"]
artemisPort 10002
@ -138,7 +150,6 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['build']) {
}
node {
name "Bank A"
dirName "nodea"
nearestCity "London"
advertisedServices = []
artemisPort 10004
@ -147,7 +158,6 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['build']) {
}
node {
name "Bank B"
dirName "nodeb"
nearestCity "New York"
advertisedServices = []
artemisPort 10006
@ -177,4 +187,23 @@ bintrayConfig {
name = 'R3'
email = 'dev@corda.net'
}
}
}
// API docs
dokka {
moduleName = 'corda'
outputDirectory = 'docs/build/html/api/kotlin'
processConfigurations = ['compile']
sourceDirs = files('core/src/main/kotlin', 'client/src/main/kotlin', 'node/src/main/kotlin', 'finance/src/main/kotlin')
}
task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaTask) {
moduleName = 'corda'
outputFormat = "javadoc"
outputDirectory = 'docs/build/html/api/javadoc'
processConfigurations = ['compile']
sourceDirs = files('core/src/main/kotlin', 'client/src/main/kotlin', 'node/src/main/kotlin', 'finance/src/main/kotlin')
}
task apidocs(dependsOn: ['dokka', 'dokkaJavadoc'])

View File

@ -5,5 +5,6 @@ repositories {
}
dependencies {
compile "com.google.guava:guava:19.0"
// Cannot use ext.guava_version here :(
compile "com.google.guava:guava:20.0"
}

View File

@ -51,7 +51,7 @@ dependencies {
compile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}"
compile "org.apache.logging.log4j:log4j-core:${log4j_version}"
compile "com.google.guava:guava:19.0"
compile "com.google.guava:guava:$guava_version"
// ReactFX: Functional reactive UI programming.
compile 'org.reactfx:reactfx:2.0-M5'
@ -61,16 +61,16 @@ dependencies {
compile "org.apache.activemq:artemis-core-client:${artemis_version}"
// Unit testing helpers.
testCompile 'junit:junit:4.12'
testCompile "junit:junit:$junit_version"
testCompile "org.assertj:assertj-core:${assertj_version}"
testCompile project(':test-utils')
// Integration test helpers
integrationTestCompile 'junit:junit:4.12'
integrationTestCompile "junit:junit:$junit_version"
}
task integrationTest(type: Test) {
testClassesDir = sourceSets.integrationTest.output.classesDir
classpath = sourceSets.integrationTest.runtimeClasspath
}
}

View File

@ -8,48 +8,26 @@ import net.corda.core.random63BitValue
import net.corda.core.serialization.OpaqueBytes
import net.corda.flows.CashCommand
import net.corda.flows.CashFlow
import net.corda.node.driver.NodeInfoAndConfig
import net.corda.node.driver.DriverBasedTest
import net.corda.node.driver.NodeHandle
import net.corda.node.driver.driver
import net.corda.node.services.User
import net.corda.node.services.config.configureTestSSL
import net.corda.node.services.messaging.ArtemisMessagingComponent.Companion.toHostAndPort
import net.corda.node.services.messaging.CordaRPCClient
import net.corda.node.services.startFlowPermission
import net.corda.node.services.transactions.ValidatingNotaryService
import org.apache.activemq.artemis.api.core.ActiveMQSecurityException
import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.junit.After
import org.junit.Before
import org.junit.Test
import java.util.concurrent.CountDownLatch
import kotlin.concurrent.thread
class CordaRPCClientTest {
class CordaRPCClientTest : DriverBasedTest() {
private val rpcUser = User("user1", "test", permissions = setOf(startFlowPermission<CashFlow>()))
private val stopDriver = CountDownLatch(1)
private var driverThread: Thread? = null
private lateinit var node: NodeHandle
private lateinit var client: CordaRPCClient
private lateinit var driverInfo: NodeInfoAndConfig
@Before
fun start() {
val driverStarted = CountDownLatch(1)
driverThread = thread {
driver(isDebug = true) {
driverInfo = startNode(rpcUsers = listOf(rpcUser), advertisedServices = setOf(ServiceInfo(ValidatingNotaryService.type))).getOrThrow()
client = CordaRPCClient(toHostAndPort(driverInfo.nodeInfo.address), configureTestSSL())
driverStarted.countDown()
stopDriver.await()
}
}
driverStarted.await()
}
@After
fun stop() {
stopDriver.countDown()
driverThread?.join()
override fun setup() = driver(isDebug = true) {
node = startNode(rpcUsers = listOf(rpcUser), advertisedServices = setOf(ServiceInfo(ValidatingNotaryService.type))).getOrThrow()
client = node.rpcClientToNode()
runTest()
}
@Test
@ -78,7 +56,9 @@ class CordaRPCClientTest {
println("Creating proxy")
val proxy = client.proxy()
println("Starting flow")
val flowHandle = proxy.startFlow(::CashFlow, CashCommand.IssueCash(20.DOLLARS, OpaqueBytes.of(0), driverInfo.nodeInfo.legalIdentity, driverInfo.nodeInfo.legalIdentity))
val flowHandle = proxy.startFlow(
::CashFlow,
CashCommand.IssueCash(20.DOLLARS, OpaqueBytes.of(0), node.nodeInfo.legalIdentity, node.nodeInfo.legalIdentity))
println("Started flow, waiting on result")
flowHandle.progress.subscribe {
println("PROGRESS $it")

View File

@ -9,7 +9,9 @@ import net.corda.core.contracts.PartyAndReference
import net.corda.core.contracts.USD
import net.corda.core.flows.StateMachineRunId
import net.corda.core.getOrThrow
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.StateMachineUpdate
import net.corda.core.messaging.startFlow
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.NetworkMapCache
import net.corda.core.node.services.ServiceInfo
@ -19,6 +21,7 @@ import net.corda.core.serialization.OpaqueBytes
import net.corda.core.transactions.SignedTransaction
import net.corda.flows.CashCommand
import net.corda.flows.CashFlow
import net.corda.node.driver.DriverBasedTest
import net.corda.node.driver.driver
import net.corda.node.services.User
import net.corda.node.services.config.configureTestSSL
@ -29,63 +32,42 @@ import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.testing.expect
import net.corda.testing.expectEvents
import net.corda.testing.sequence
import org.junit.After
import org.junit.Before
import org.junit.Test
import rx.Observable
import rx.Observer
import java.util.concurrent.CountDownLatch
import kotlin.concurrent.thread
class NodeMonitorModelTest {
class NodeMonitorModelTest : DriverBasedTest() {
lateinit var aliceNode: NodeInfo
lateinit var notaryNode: NodeInfo
val stopDriver = CountDownLatch(1)
var driverThread: Thread? = null
lateinit var rpc: CordaRPCOps
lateinit var stateMachineTransactionMapping: Observable<StateMachineTransactionMapping>
lateinit var stateMachineUpdates: Observable<StateMachineUpdate>
lateinit var progressTracking: Observable<ProgressTrackingEvent>
lateinit var transactions: Observable<SignedTransaction>
lateinit var vaultUpdates: Observable<Vault.Update>
lateinit var networkMapUpdates: Observable<NetworkMapCache.MapChange>
lateinit var clientToService: Observer<CashCommand>
lateinit var newNode: (String) -> NodeInfo
@Before
fun start() {
val driverStarted = CountDownLatch(1)
driverThread = thread {
driver {
val cashUser = User("user1", "test", permissions = setOf(startFlowPermission<CashFlow>()))
val aliceNodeFuture = startNode("Alice", rpcUsers = listOf(cashUser))
val notaryNodeFuture = startNode("Notary", advertisedServices = setOf(ServiceInfo(SimpleNotaryService.type)))
override fun setup() = driver {
val cashUser = User("user1", "test", permissions = setOf(startFlowPermission<CashFlow>()))
val aliceNodeFuture = startNode("Alice", rpcUsers = listOf(cashUser))
val notaryNodeFuture = startNode("Notary", advertisedServices = setOf(ServiceInfo(SimpleNotaryService.type)))
aliceNode = aliceNodeFuture.getOrThrow().nodeInfo
notaryNode = notaryNodeFuture.getOrThrow().nodeInfo
newNode = { nodeName -> startNode(nodeName).getOrThrow().nodeInfo }
val monitor = NodeMonitorModel()
aliceNode = aliceNodeFuture.getOrThrow().nodeInfo
notaryNode = notaryNodeFuture.getOrThrow().nodeInfo
newNode = { nodeName -> startNode(nodeName).getOrThrow().nodeInfo }
val monitor = NodeMonitorModel()
stateMachineTransactionMapping = monitor.stateMachineTransactionMapping.bufferUntilSubscribed()
stateMachineUpdates = monitor.stateMachineUpdates.bufferUntilSubscribed()
progressTracking = monitor.progressTracking.bufferUntilSubscribed()
transactions = monitor.transactions.bufferUntilSubscribed()
vaultUpdates = monitor.vaultUpdates.bufferUntilSubscribed()
networkMapUpdates = monitor.networkMap.bufferUntilSubscribed()
clientToService = monitor.clientToService
stateMachineTransactionMapping = monitor.stateMachineTransactionMapping.bufferUntilSubscribed()
stateMachineUpdates = monitor.stateMachineUpdates.bufferUntilSubscribed()
progressTracking = monitor.progressTracking.bufferUntilSubscribed()
transactions = monitor.transactions.bufferUntilSubscribed()
vaultUpdates = monitor.vaultUpdates.bufferUntilSubscribed()
networkMapUpdates = monitor.networkMap.bufferUntilSubscribed()
monitor.register(ArtemisMessagingComponent.toHostAndPort(aliceNode.address), configureTestSSL(), cashUser.username, cashUser.password)
driverStarted.countDown()
stopDriver.await()
}
}
driverStarted.await()
}
@After
fun stop() {
stopDriver.countDown()
driverThread?.join()
monitor.register(ArtemisMessagingComponent.toHostAndPort(aliceNode.address), configureTestSSL(), cashUser.username, cashUser.password)
rpc = monitor.proxyObservable.value!!
runTest()
}
@Test
@ -112,7 +94,7 @@ class NodeMonitorModelTest {
@Test
fun `cash issue works end to end`() {
clientToService.onNext(CashCommand.IssueCash(
rpc.startFlow(::CashFlow, CashCommand.IssueCash(
amount = Amount(100, USD),
issueRef = OpaqueBytes(ByteArray(1, { 1 })),
recipient = aliceNode.legalIdentity,
@ -137,14 +119,14 @@ class NodeMonitorModelTest {
@Test
fun `cash issue and move`() {
clientToService.onNext(CashCommand.IssueCash(
rpc.startFlow(::CashFlow, CashCommand.IssueCash(
amount = Amount(100, USD),
issueRef = OpaqueBytes(ByteArray(1, { 1 })),
recipient = aliceNode.legalIdentity,
notary = notaryNode.notaryIdentity
))
clientToService.onNext(CashCommand.PayCash(
rpc.startFlow(::CashFlow, CashCommand.PayCash(
amount = Amount(100, Issued(PartyAndReference(aliceNode.legalIdentity, OpaqueBytes(ByteArray(1, { 1 }))), USD)),
recipient = aliceNode.legalIdentity
))

View File

@ -7,6 +7,7 @@ import javafx.collections.FXCollections
import javafx.collections.ObservableList
import javafx.collections.ObservableMap
import rx.Observable
import java.util.concurrent.TimeUnit
/**
* Simple utilities for converting an [rx.Observable] into a javafx [ObservableValue]/[ObservableList]
@ -29,76 +30,46 @@ fun <A, B> Observable<A>.foldToObservableValue(initial: B, folderFun: (A, B) ->
}
/**
* [foldToObservableList] takes an [rx.Observable] stream and creates an [ObservableList] out of it, while maintaining
* an accumulator.
* @param initialAccumulator The initial value of the accumulator.
* [fold] takes an [rx.Observable] stream and applies fold function on it, and collects all elements using the accumulator.
* @param accumulator The accumulator for accumulating elements.
* @param folderFun The transformation function to be called on the observable list when a new element is emitted on
* the stream, which should modify the list as needed.
*/
fun <A, B, C> Observable<A>.foldToObservableList(
initialAccumulator: C, folderFun: (A, C, ObservableList<B>) -> C
): ObservableList<B> {
val result = FXCollections.observableArrayList<B>()
fun <T, R> Observable<T>.fold(accumulator: R, folderFun: (R, T) -> Unit): R {
/**
* This capture is fine, as [Platform.runLater] runs closures in order
* This capture is fine, as [Platform.runLater] runs closures in order.
* The buffer is to avoid flooding FX thread with runnable.
*/
var currentAccumulator = initialAccumulator
subscribe {
Platform.runLater {
currentAccumulator = folderFun(it, currentAccumulator, result)
buffer(1, TimeUnit.SECONDS).subscribe {
if (it.isNotEmpty()) {
Platform.runLater {
it.fold(accumulator) { list, item ->
folderFun.invoke(list, item)
list
}
}
}
}
return result
return accumulator
}
/**
* [recordInSequence] records incoming events on the [rx.Observable] in sequence.
*/
fun <A> Observable<A>.recordInSequence(): ObservableList<A> {
return foldToObservableList(Unit) { newElement, _unit, list ->
return fold(FXCollections.observableArrayList()) { list, newElement ->
list.add(newElement)
}
}
/**
* [foldToObservableMap] takes an [rx.Observable] stream and creates an [ObservableMap] out of it, while maintaining
* an accumulator.
* @param initialAccumulator The initial value of the accumulator.
* @param folderFun The transformation function to be called on the observable map when a new element is emitted on
* the stream, which should modify the map as needed.
*/
fun <A, B, K, C> Observable<A>.foldToObservableMap(
initialAccumulator: C, folderFun: (A, C, ObservableMap<K, B>) -> C
): ObservableMap<K, out B> {
val result = FXCollections.observableHashMap<K, B>()
/**
* This capture is fine, as [Platform.runLater] runs closures in order
*/
var currentAccumulator = initialAccumulator
subscribe {
Platform.runLater {
currentAccumulator = folderFun(it, currentAccumulator, result)
}
}
return result
}
/**
* This variant simply associates each event with its key.
* @param toKey Function retrieving the key to associate with.
* @param merge The function to be called if there is an existing element at the key.
*/
fun <A, K> Observable<A>.recordAsAssociation(
toKey: (A) -> K,
merge: (K, oldValue: A, newValue: A) -> A = { _key, _oldValue, newValue -> newValue }
): ObservableMap<K, out A> {
return foldToObservableMap(Unit) { newElement, _unit, map ->
val key = toKey(newElement)
val oldValue = map.get(key)
if (oldValue != null) {
map.set(key, merge(key, oldValue, newElement))
} else {
map.set(key, newElement)
}
fun <A, K> Observable<A>.recordAsAssociation(toKey: (A) -> K, merge: (K, oldValue: A, newValue: A) -> A = { _key, _oldValue, newValue -> newValue }): ObservableMap<K, A> {
return fold(FXCollections.observableHashMap<K, A>()) { map, item ->
val key = toKey(item)
map[key] = map[key]?.let { merge(key, it, item) } ?: item
}
}

View File

@ -6,6 +6,7 @@ import net.corda.core.crypto.Party
import net.corda.core.serialization.OpaqueBytes
import net.corda.core.transactions.TransactionBuilder
import net.corda.flows.CashCommand
import java.util.*
/**
* [Generator]s for incoming/outgoing events to/from the [WalletMonitorService]. Internally it keeps track of owned
@ -13,15 +14,15 @@ import net.corda.flows.CashCommand
*/
class EventGenerator(
val parties: List<Party>,
val notary: Party
val notary: Party,
val currencies: List<Currency> = listOf(USD, GBP, CHF),
val issuers: List<Party> = parties
) {
private var vault = listOf<StateAndRef<Cash.State>>()
val issuerGenerator =
Generator.pickOne(parties).combine(Generator.intRange(0, 1)) { party, ref -> party.ref(ref.toByte()) }
Generator.pickOne(issuers).combine(Generator.intRange(0, 1)) { party, ref -> party.ref(ref.toByte()) }
val currencies = setOf(USD, GBP, CHF).toList() // + Currency.getAvailableCurrencies().toList().subList(0, 3).toSet()).toList()
val currencyGenerator = Generator.pickOne(currencies)
val issuedGenerator = issuerGenerator.combine(currencyGenerator) { issuer, currency -> Issued(issuer, currency) }
@ -93,8 +94,11 @@ class EventGenerator(
1.0 to moveCashGenerator
)
val bankOfCordaCommandGenerator = Generator.frequency(
0.6 to issueCashGenerator,
val bankOfCordaExitGenerator = Generator.frequency(
0.4 to exitCashGenerator
)
val bankOfCordaIssueGenerator = Generator.frequency(
0.6 to issueCashGenerator
)
}

View File

@ -1,19 +1,19 @@
package net.corda.client.model
import javafx.collections.FXCollections
import javafx.collections.ObservableList
import kotlinx.support.jdk8.collections.removeIf
import net.corda.client.fxutils.foldToObservableList
import net.corda.client.fxutils.fold
import net.corda.client.fxutils.map
import net.corda.contracts.asset.Cash
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.StateRef
import net.corda.core.node.services.Vault
import rx.Observable
data class Diff<out T : ContractState>(
val added: Collection<StateAndRef<T>>,
val removed: Collection<StateRef>
val removed: Collection<StateAndRef<T>>
)
/**
@ -26,14 +26,12 @@ class ContractStateModel {
Diff(it.produced, it.consumed)
}
private val cashStatesDiff: Observable<Diff<Cash.State>> = contractStatesDiff.map {
// We can't filter removed hashes here as we don't have type info
Diff(it.added.filterCashStateAndRefs(), it.removed)
Diff(it.added.filterCashStateAndRefs(), it.removed.filterCashStateAndRefs())
}
val cashStates: ObservableList<StateAndRef<Cash.State>> = cashStatesDiff.fold(FXCollections.observableArrayList()) { list, statesDiff ->
list.removeIf { it in statesDiff.removed }
list.addAll(statesDiff.added)
}
val cashStates: ObservableList<StateAndRef<Cash.State>> =
cashStatesDiff.foldToObservableList(Unit) { statesDiff, _accumulator, observableList ->
observableList.removeIf { it.ref in statesDiff.removed }
observableList.addAll(statesDiff.added)
}
val cash = cashStates.map { it.state.data.amount }

View File

@ -1,115 +0,0 @@
package net.corda.client.model
import javafx.beans.property.SimpleObjectProperty
import javafx.beans.value.ObservableValue
import javafx.collections.ObservableList
import javafx.collections.ObservableMap
import net.corda.client.fxutils.*
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.StateRef
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.StateMachineRunId
import net.corda.core.messaging.StateMachineUpdate
import net.corda.core.transactions.SignedTransaction
import org.fxmisc.easybind.EasyBind
data class GatheredTransactionData(
val transaction: PartiallyResolvedTransaction,
val stateMachines: ObservableList<out StateMachineData>
)
/**
* [PartiallyResolvedTransaction] holds a [SignedTransaction] that has zero or more inputs resolved. The intent is
* to prepare clients for cases where an input can only be resolved in the future/cannot be resolved at all (for example
* because of permissioning)
*/
data class PartiallyResolvedTransaction(
val transaction: SignedTransaction,
val inputs: List<ObservableValue<InputResolution>>) {
val id = transaction.id
sealed class InputResolution(val stateRef: StateRef) {
class Unresolved(stateRef: StateRef) : InputResolution(stateRef)
class Resolved(val stateAndRef: StateAndRef<ContractState>) : InputResolution(stateAndRef.ref)
}
companion object {
fun fromSignedTransaction(
transaction: SignedTransaction,
transactions: ObservableMap<SecureHash, SignedTransaction>
) = PartiallyResolvedTransaction(
transaction = transaction,
inputs = transaction.tx.inputs.map { stateRef ->
EasyBind.map(transactions.getObservableValue(stateRef.txhash)) {
if (it == null) {
InputResolution.Unresolved(stateRef)
} else {
InputResolution.Resolved(it.tx.outRef(stateRef.index))
}
}
}
)
}
}
sealed class TransactionCreateStatus(val message: String?) {
class Started(message: String?) : TransactionCreateStatus(message)
class Failed(message: String?) : TransactionCreateStatus(message)
override fun toString(): String = message ?: javaClass.simpleName
}
data class FlowStatus(
val status: String
)
sealed class StateMachineStatus(val stateMachineName: String) {
class Added(stateMachineName: String) : StateMachineStatus(stateMachineName)
class Removed(stateMachineName: String) : StateMachineStatus(stateMachineName)
override fun toString(): String = "${javaClass.simpleName}($stateMachineName)"
}
data class StateMachineData(
val id: StateMachineRunId,
val flowStatus: ObservableValue<FlowStatus?>,
val stateMachineStatus: ObservableValue<StateMachineStatus>
)
/**
* This model provides an observable list of transactions and what state machines/flows recorded them
*/
class GatheredTransactionDataModel {
private val transactions by observable(NodeMonitorModel::transactions)
private val stateMachineUpdates by observable(NodeMonitorModel::stateMachineUpdates)
private val progressTracking by observable(NodeMonitorModel::progressTracking)
private val stateMachineTransactionMapping by observable(NodeMonitorModel::stateMachineTransactionMapping)
private val collectedTransactions = transactions.recordInSequence()
private val transactionMap = collectedTransactions.associateBy(SignedTransaction::id)
private val progressEvents = progressTracking.recordAsAssociation(ProgressTrackingEvent::stateMachineId)
private val stateMachineStatus = stateMachineUpdates.foldToObservableMap(Unit) { update, _unit, map: ObservableMap<StateMachineRunId, SimpleObjectProperty<StateMachineStatus>> ->
when (update) {
is StateMachineUpdate.Added -> {
val added: SimpleObjectProperty<StateMachineStatus> =
SimpleObjectProperty(StateMachineStatus.Added(update.stateMachineInfo.flowLogicClassName))
map[update.id] = added
}
is StateMachineUpdate.Removed -> {
val added = map[update.id]
added ?: throw Exception("State machine removed with unknown id ${update.id}")
added.set(StateMachineStatus.Removed(added.value.stateMachineName))
}
}
}
private val stateMachineDataList = LeftOuterJoinedMap(stateMachineStatus, progressEvents) { id, status, progress ->
StateMachineData(id, progress.map { it?.let { FlowStatus(it.message) } }, status)
}.getObservableValues()
// TODO : Create a new screen for state machines.
private val stateMachineDataMap = stateMachineDataList.associateBy(StateMachineData::id)
private val smTxMappingList = stateMachineTransactionMapping.recordInSequence()
val partiallyResolvedTransactions = collectedTransactions.map {
PartiallyResolvedTransaction.fromSignedTransaction(it, transactionMap)
}
}

View File

@ -1,11 +1,12 @@
package net.corda.client.model
import javafx.beans.value.ObservableValue
import javafx.collections.FXCollections
import javafx.collections.ObservableList
import kotlinx.support.jdk8.collections.removeIf
import net.corda.client.fxutils.firstOrDefault
import net.corda.client.fxutils.firstOrNullObservable
import net.corda.client.fxutils.foldToObservableList
import net.corda.client.fxutils.fold
import net.corda.client.fxutils.map
import net.corda.core.crypto.CompositeKey
import net.corda.core.node.NodeInfo
@ -17,15 +18,15 @@ class NetworkIdentityModel {
private val networkIdentityObservable by observable(NodeMonitorModel::networkMap)
val networkIdentities: ObservableList<NodeInfo> =
networkIdentityObservable.foldToObservableList(Unit) { update, _accumulator, observableList ->
observableList.removeIf {
networkIdentityObservable.fold(FXCollections.observableArrayList()) { list, update ->
list.removeIf {
when (update) {
is MapChange.Removed -> it == update.node
is MapChange.Modified -> it == update.previousNode
else -> false
}
}
observableList.addAll(update.node)
list.addAll(update.node)
}
private val rpcProxy by observableValue(NodeMonitorModel::proxyObservable)

View File

@ -6,14 +6,11 @@ import net.corda.core.flows.StateMachineRunId
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.StateMachineInfo
import net.corda.core.messaging.StateMachineUpdate
import net.corda.core.messaging.startFlow
import net.corda.core.node.services.NetworkMapCache.MapChange
import net.corda.core.node.services.StateMachineTransactionMapping
import net.corda.core.node.services.Vault
import net.corda.core.transactions.SignedTransaction
import net.corda.flows.CashCommand
import net.corda.flows.CashFlow
import net.corda.node.services.config.NodeSSLConfiguration
import net.corda.node.services.config.SSLConfiguration
import net.corda.node.services.messaging.CordaRPCClient
import rx.Observable
import rx.subjects.PublishSubject
@ -48,16 +45,13 @@ class NodeMonitorModel {
val progressTracking: Observable<ProgressTrackingEvent> = progressTrackingSubject
val networkMap: Observable<MapChange> = networkMapSubject
private val clientToServiceSource = PublishSubject.create<CashCommand>()
val clientToService: PublishSubject<CashCommand> = clientToServiceSource
val proxyObservable = SimpleObjectProperty<CordaRPCOps?>()
/**
* Register for updates to/from a given vault.
* TODO provide an unsubscribe mechanism
*/
fun register(nodeHostAndPort: HostAndPort, sslConfig: NodeSSLConfiguration, username: String, password: String) {
fun register(nodeHostAndPort: HostAndPort, sslConfig: SSLConfiguration, username: String, password: String) {
val client = CordaRPCClient(nodeHostAndPort, sslConfig)
client.start(username, password)
val proxy = client.proxy()
@ -98,10 +92,6 @@ class NodeMonitorModel {
val (parties, futurePartyUpdate) = proxy.networkMapUpdates()
futurePartyUpdate.startWith(parties.map { MapChange.Added(it) }).subscribe(networkMapSubject)
// Client -> Service
clientToServiceSource.subscribe {
proxy.startFlow(::CashFlow, it)
}
proxyObservable.set(proxy)
}
}

View File

@ -0,0 +1,116 @@
package net.corda.client.model
import javafx.beans.property.SimpleObjectProperty
import javafx.beans.value.ObservableValue
import javafx.collections.FXCollections
import javafx.collections.ObservableList
import javafx.collections.ObservableMap
import net.corda.client.fxutils.*
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.StateRef
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.StateMachineRunId
import net.corda.core.messaging.StateMachineUpdate
import net.corda.core.transactions.SignedTransaction
import org.fxmisc.easybind.EasyBind
data class GatheredTransactionData(
val transaction: PartiallyResolvedTransaction,
val stateMachines: ObservableList<out StateMachineData>
)
/**
* [PartiallyResolvedTransaction] holds a [SignedTransaction] that has zero or more inputs resolved. The intent is
* to prepare clients for cases where an input can only be resolved in the future/cannot be resolved at all (for example
* because of permissioning)
*/
data class PartiallyResolvedTransaction(
val transaction: SignedTransaction,
val inputs: List<ObservableValue<InputResolution>>) {
val id = transaction.id
sealed class InputResolution(val stateRef: StateRef) {
class Unresolved(stateRef: StateRef) : InputResolution(stateRef)
class Resolved(val stateAndRef: StateAndRef<ContractState>) : InputResolution(stateAndRef.ref)
}
companion object {
fun fromSignedTransaction(
transaction: SignedTransaction,
transactions: ObservableMap<SecureHash, SignedTransaction>
) = PartiallyResolvedTransaction(
transaction = transaction,
inputs = transaction.tx.inputs.map { stateRef ->
EasyBind.map(transactions.getObservableValue(stateRef.txhash)) {
if (it == null) {
InputResolution.Unresolved(stateRef)
} else {
InputResolution.Resolved(it.tx.outRef(stateRef.index))
}
}
}
)
}
}
sealed class TransactionCreateStatus(val message: String?) {
class Started(message: String?) : TransactionCreateStatus(message)
class Failed(message: String?) : TransactionCreateStatus(message)
override fun toString(): String = message ?: javaClass.simpleName
}
data class FlowStatus(
val status: String
)
sealed class StateMachineStatus(val stateMachineName: String) {
class Added(stateMachineName: String) : StateMachineStatus(stateMachineName)
class Removed(stateMachineName: String) : StateMachineStatus(stateMachineName)
override fun toString(): String = "${javaClass.simpleName}($stateMachineName)"
}
data class StateMachineData(
val id: StateMachineRunId,
val flowStatus: ObservableValue<FlowStatus?>,
val stateMachineStatus: ObservableValue<StateMachineStatus>
)
/**
* This model provides an observable list of transactions and what state machines/flows recorded them
*/
class TransactionDataModel {
private val transactions by observable(NodeMonitorModel::transactions)
private val stateMachineUpdates by observable(NodeMonitorModel::stateMachineUpdates)
private val progressTracking by observable(NodeMonitorModel::progressTracking)
private val stateMachineTransactionMapping by observable(NodeMonitorModel::stateMachineTransactionMapping)
private val collectedTransactions = transactions.recordInSequence()
private val transactionMap = collectedTransactions.associateBy(SignedTransaction::id)
private val progressEvents = progressTracking.recordAsAssociation(ProgressTrackingEvent::stateMachineId)
private val stateMachineStatus = stateMachineUpdates.fold(FXCollections.observableHashMap<StateMachineRunId, SimpleObjectProperty<StateMachineStatus>>()) { map, update ->
when (update) {
is StateMachineUpdate.Added -> {
val added: SimpleObjectProperty<StateMachineStatus> =
SimpleObjectProperty(StateMachineStatus.Added(update.stateMachineInfo.flowLogicClassName))
map[update.id] = added
}
is StateMachineUpdate.Removed -> {
val added = map[update.id]
added ?: throw Exception("State machine removed with unknown id ${update.id}")
added.set(StateMachineStatus.Removed(added.value.stateMachineName))
}
}
}
private val stateMachineDataList = LeftOuterJoinedMap(stateMachineStatus, progressEvents) { id, status, progress ->
StateMachineData(id, progress.map { it?.let { FlowStatus(it.message) } }, status)
}.getObservableValues()
// TODO : Create a new screen for state machines.
private val stateMachineDataMap = stateMachineDataList.associateBy(StateMachineData::id)
private val smTxMappingList = stateMachineTransactionMapping.recordInSequence()
val partiallyResolvedTransactions = collectedTransactions.map {
PartiallyResolvedTransaction.fromSignedTransaction(it, transactionMap)
}
}

View File

@ -1,4 +1,3 @@
basedir : "./nodea"
myLegalName : "Bank A"
nearestCity : "London"
keyStorePassword : "cordacadevpass"
@ -6,5 +5,8 @@ trustStorePassword : "trustpass"
artemisAddress : "localhost:31337"
webAddress : "localhost:31339"
extraAdvertisedServiceIds: "corda.interest_rates"
networkMapAddress : "localhost:12345"
networkMapService : {
address : "localhost:12345"
legalName : "Network Map Service"
}
useHTTPS : false

View File

@ -1,4 +1,3 @@
basedir : "./nodeb"
myLegalName : "Bank B"
nearestCity : "London"
keyStorePassword : "cordacadevpass"
@ -6,5 +5,8 @@ trustStorePassword : "trustpass"
artemisAddress : "localhost:31338"
webAddress : "localhost:31340"
extraAdvertisedServiceIds: "corda.interest_rates"
networkMapAddress : "localhost:12345"
networkMapService : {
address : "localhost:12345"
legalName : "Network Map Service"
}
useHTTPS : false

View File

@ -1,4 +1,3 @@
basedir : "./nameserver"
myLegalName : "Notary Service"
nearestCity : "London"
keyStorePassword : "cordacadevpass"

View File

@ -31,11 +31,11 @@ sourceSets {
}
dependencies {
testCompile 'junit:junit:4.12'
testCompile "junit:junit:$junit_version"
testCompile "commons-fileupload:commons-fileupload:1.3.2"
// Guava: Google test library (collections test suite)
testCompile "com.google.guava:guava-testlib:19.0"
testCompile "com.google.guava:guava-testlib:$guava_version"
// Bring in the MockNode infrastructure for writing protocol unit tests.
testCompile project(":node")
@ -43,7 +43,7 @@ dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
compile "org.jetbrains.kotlinx:kotlinx-support-jdk8:0.2"
compile "org.jetbrains.kotlinx:kotlinx-support-jdk8:0.3"
compile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
// Thread safety annotations
@ -56,18 +56,18 @@ dependencies {
// AssertJ: for fluent assertions for testing
testCompile "org.assertj:assertj-core:${assertj_version}"
compile 'com.pholser:junit-quickcheck-core:0.6'
compile 'com.pholser:junit-quickcheck-generators:0.6'
compile "com.pholser:junit-quickcheck-core:$quickcheck_version"
compile "com.pholser:junit-quickcheck-generators:$quickcheck_version"
// Guava: Google utilities library.
compile "com.google.guava:guava:19.0"
compile "com.google.guava:guava:$guava_version"
// RxJava: observable streams of events.
compile "io.reactivex:rxjava:1.1.6"
compile "io.reactivex:rxjava:1.2.4"
// Kryo: object graph serialization.
compile "com.esotericsoftware:kryo:4.0.0"
compile "de.javakaffee:kryo-serializers:0.38"
compile "de.javakaffee:kryo-serializers:0.41"
// Apache JEXL: An embeddable expression evaluation library.
// This may be temporary until we experiment with other ways to do on-the-fly contract specialisation via an API.
@ -88,4 +88,4 @@ dependencies {
// RS API: Response type and codes for ApiUtils.
compile "javax.ws.rs:javax.ws.rs-api:2.0"
}
}

View File

@ -1,3 +1,4 @@
// TODO Move out the Kotlin specific stuff into a separate file
@file:JvmName("Utils")
package net.corda.core
@ -26,10 +27,7 @@ import java.nio.file.*
import java.nio.file.attribute.FileAttribute
import java.time.Duration
import java.time.temporal.Temporal
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ExecutionException
import java.util.concurrent.Executor
import java.util.concurrent.Future
import java.util.concurrent.*
import java.util.concurrent.locks.ReentrantLock
import java.util.function.BiConsumer
import java.util.stream.Stream
@ -67,9 +65,9 @@ infix fun Long.checkedAdd(b: Long) = Math.addExact(this, b)
fun random63BitValue(): Long = Math.abs(newSecureRandom().nextLong())
/** Same as [Future.get] but with a more descriptive name, and doesn't throw [ExecutionException], instead throwing its cause */
fun <T> Future<T>.getOrThrow(): T {
try {
return get()
fun <T> Future<T>.getOrThrow(timeout: Duration? = null): T {
return try {
if (timeout == null) get() else get(timeout.toNanos(), TimeUnit.NANOSECONDS)
} catch (e: ExecutionException) {
throw e.cause!!
}
@ -204,19 +202,31 @@ fun <T> List<T>.randomOrNull(predicate: (T) -> Boolean) = filter(predicate).rand
// An alias that can sometimes make code clearer to read.
val RunOnCallerThread: Executor = MoreExecutors.directExecutor()
inline fun elapsedTime(block: () -> Unit): Duration {
val start = System.nanoTime()
block()
val end = System.nanoTime()
return Duration.ofNanos(end-start)
}
// TODO: Add inline back when a new Kotlin version is released and check if the java.lang.VerifyError
// returns in the IRSSimulationTest. If not, commit the inline back.
fun <T> logElapsedTime(label: String, logger: Logger? = null, body: () -> T): T {
val now = System.currentTimeMillis()
val r = body()
val elapsed = System.currentTimeMillis() - now
if (logger != null)
logger.info("$label took $elapsed msec")
else
println("$label took $elapsed msec")
return r
// Use nanoTime as it's monotonic.
val now = System.nanoTime()
try {
return body()
} finally {
val elapsed = Duration.ofNanos(System.nanoTime() - now).toMillis()
if (logger != null)
logger.info("$label took $elapsed msec")
else
println("$label took $elapsed msec")
}
}
fun <T> Logger.logElapsedTime(label: String, body: () -> T): T = logElapsedTime(label, this, body)
/**
* A threadbox is a simple utility that makes it harder to forget to take a lock before accessing some shared state.
* Simply define a private class to hold the data that must be grouped under the same lock, and then pass the only
@ -271,19 +281,17 @@ class TransientProperty<out T>(private val initializer: () -> T) {
/**
* Given a path to a zip file, extracts it to the given directory.
*/
fun extractZipFile(zipPath: Path, toPath: Path) {
val normalisedToPath = toPath.normalize()
normalisedToPath.createDirectories()
fun extractZipFile(zipFile: Path, toDirectory: Path) {
val normalisedDirectory = toDirectory.normalize().createDirectories()
zipPath.read {
zipFile.read {
val zip = ZipInputStream(BufferedInputStream(it))
while (true) {
val e = zip.nextEntry ?: break
val outPath = normalisedToPath / e.name
val outPath = (normalisedDirectory / e.name).normalize()
// Security checks: we should reject a zip that contains tricksy paths that try to escape toPath.
if (!outPath.normalize().startsWith(normalisedToPath))
throw IllegalStateException("ZIP contained a path that resolved incorrectly: ${e.name}")
// Security checks: we should reject a zip that contains tricksy paths that try to escape toDirectory.
check(outPath.startsWith(normalisedDirectory)) { "ZIP contained a path that resolved incorrectly: ${e.name}" }
if (e.isDirectory) {
outPath.createDirectories()
@ -355,8 +363,7 @@ data class ErrorOr<out A> private constructor(val value: A?, val error: Throwabl
}
/**
* Returns an observable that buffers events until subscribed.
*
* Returns an Observable that buffers events until subscribed.
* @see UnicastSubject
*/
fun <T> Observable<T>.bufferUntilSubscribed(): Observable<T> {
@ -375,5 +382,18 @@ fun <T> Observer<T>.tee(vararg teeTo: Observer<T>): Observer<T> {
return subject
}
/** Allows summing big decimals that are in iterable collections */
/**
* Returns a [ListenableFuture] bound to the *first* item emitted by this Observable. The future will complete with a
* NoSuchElementException if no items are emitted or any other error thrown by the Observable.
*/
fun <T> Observable<T>.toFuture(): ListenableFuture<T> {
val future = SettableFuture.create<T>()
first().subscribe(
{ future.set(it) },
{ future.setException(it) }
)
return future
}
/** Return the sum of an Iterable of [BigDecimal]s. */
fun Iterable<BigDecimal>.sum(): BigDecimal = fold(BigDecimal.ZERO) { a, b -> a + b }

View File

@ -119,7 +119,7 @@ inline fun <reified T : MoveCommand> verifyMoveCommand(inputs: List<OwnableState
val command = commands.requireSingleCommand<T>()
val keysThatSigned = command.signers.toSet()
requireThat {
"the owning keys are the same as the signing keys" by keysThatSigned.containsAll(owningPubKeys)
"the owning keys are a subset of the signing keys" by keysThatSigned.containsAll(owningPubKeys)
}
return command.value
}

View File

@ -10,6 +10,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import com.google.common.annotations.VisibleForTesting
import java.math.BigDecimal
import java.math.BigInteger
import java.time.DayOfWeek
import java.time.LocalDate
import java.time.format.DateTimeFormatter
@ -34,6 +35,19 @@ import java.util.*
* @param T the type of the token, for example [Currency].
*/
data class Amount<T>(val quantity: Long, val token: T) : Comparable<Amount<T>> {
companion object {
/**
* Build an amount from a decimal representation. For example, with an input of "12.34" GBP,
* returns an amount with a quantity of "1234".
*
* @see Amount<Currency>.toDecimal
*/
fun fromDecimal(quantity: BigDecimal, currency: Currency) : Amount<Currency> {
val longQuantity = quantity.movePointRight(currency.defaultFractionDigits).toLong()
return Amount(longQuantity, currency)
}
}
init {
// Negative amounts are of course a vital part of any ledger, but negative values are only valid in certain
// contexts: you cannot send a negative amount of cash, but you can (sometimes) have a negative balance.
@ -41,7 +55,12 @@ data class Amount<T>(val quantity: Long, val token: T) : Comparable<Amount<T>> {
require(quantity >= 0) { "Negative amounts are not allowed: $quantity" }
}
constructor(amount: BigDecimal, currency: T) : this(amount.toLong(), currency)
/**
* Construct the amount using the given decimal value as quantity. Any fractional part
* is discarded. To convert and use the fractional part, see [fromDecimal].
*/
constructor(quantity: BigDecimal, token: T) : this(quantity.toLong(), token)
constructor(quantity: BigInteger, token: T) : this(quantity.toLong(), token)
operator fun plus(other: Amount<T>): Amount<T> {
checkCurrency(other)
@ -70,6 +89,14 @@ data class Amount<T>(val quantity: Long, val token: T) : Comparable<Amount<T>> {
}
}
/**
* Convert a currency [Amount] to a decimal representation. For example, with an amount with a quantity
* of "1234" GBP, returns "12.34".
*
* @see Amount.Companion.fromDecimal
*/
fun Amount<Currency>.toDecimal() : BigDecimal = BigDecimal(quantity).movePointLeft(token.defaultFractionDigits)
fun <T> Iterable<Amount<T>>.sumOrNull() = if (!iterator().hasNext()) null else sumOrThrow()
fun <T> Iterable<Amount<T>>.sumOrThrow() = reduce { left, right -> left + right }
fun <T> Iterable<Amount<T>>.sumOrZero(currency: T) = if (iterator().hasNext()) sumOrThrow() else Amount(0, currency)

View File

@ -114,42 +114,41 @@ interface ContractState {
* list should just contain the owner.
*/
val participants: List<CompositeKey>
/**
* All contract states may be _encumbered_ by up to one other state.
*
* The encumbrance state, if present, forces additional controls over the encumbered state, since the platform checks
* that the encumbrance state is present as an input in the same transaction that consumes the encumbered state, and
* the contract code and rules of the encumbrance state will also be verified during the execution of the transaction.
* For example, a cash contract state could be encumbered with a time-lock contract state; the cash state is then only
* processable in a transaction that verifies that the time specified in the encumbrance time-lock has passed.
*
* The encumbered state refers to another by index, and the referred encumbrance state
* is an output state in a particular position on the same transaction that created the encumbered state. An alternative
* implementation would be encumber by reference to a StateRef., which would allow the specification of encumbrance
* by a state created in a prior transaction.
*
* Note that an encumbered state that is being consumed must have its encumbrance consumed in the same transaction,
* otherwise the transaction is not valid.
*/
val encumbrance: Int? get() = null
}
/**
* A wrapper for [ContractState] containing additional platform-level state information.
* This is the definitive state that is stored on the ledger and used in transaction outputs.
*/
data class TransactionState<out T : ContractState>(
data class TransactionState<out T : ContractState> @JvmOverloads constructor(
/** The custom contract state */
val data: T,
/** Identity of the notary that ensures the state is not used as an input to a transaction more than once */
val notary: Party) {
val notary: Party,
/**
* All contract states may be _encumbered_ by up to one other state.
*
* The encumbrance state, if present, forces additional controls over the encumbered state, since the platform checks
* that the encumbrance state is present as an input in the same transaction that consumes the encumbered state, and
* the contract code and rules of the encumbrance state will also be verified during the execution of the transaction.
* For example, a cash contract state could be encumbered with a time-lock contract state; the cash state is then only
* processable in a transaction that verifies that the time specified in the encumbrance time-lock has passed.
*
* The encumbered state refers to another by index, and the referred encumbrance state
* is an output state in a particular position on the same transaction that created the encumbered state. An alternative
* implementation would be encumbering by reference to a [StateRef], which would allow the specification of encumbrance
* by a state created in a prior transaction.
*
* Note that an encumbered state that is being consumed must have its encumbrance consumed in the same transaction,
* otherwise the transaction is not valid.
*/
val encumbrance: Int? = null) {
/**
* Copies the underlying state, replacing the notary field with the new value.
* To replace the notary, we need an approval (signature) from _all_ participants of the [ContractState].
*/
fun withNotary(newNotary: Party) = TransactionState(this.data, newNotary)
fun withNotary(newNotary: Party) = TransactionState(this.data, newNotary, encumbrance)
}
/** Wraps the [ContractState] in a [TransactionState] object */

View File

@ -19,6 +19,8 @@ sealed class TransactionType {
*/
fun verify(tx: LedgerTransaction) {
require(tx.notary != null || tx.timestamp == null) { "Transactions with timestamps must be notarised." }
val duplicates = detectDuplicateInputs(tx)
if (duplicates.isNotEmpty()) throw TransactionVerificationException.DuplicateInputStates(tx, duplicates)
val missing = verifySigners(tx)
if (missing.isNotEmpty()) throw TransactionVerificationException.SignersMissing(tx, missing.toList())
verifyTransaction(tx)
@ -35,6 +37,19 @@ sealed class TransactionType {
return missing
}
/** Check that the inputs are unique. */
private fun detectDuplicateInputs(tx: LedgerTransaction): Set<StateRef> {
var seenInputs = emptySet<StateRef>()
var duplicates = emptySet<StateRef>()
tx.inputs.forEach { state ->
if (seenInputs.contains(state.ref)) {
duplicates += state.ref
}
seenInputs += state.ref
}
return duplicates
}
/**
* Return the list of public keys that that require signatures for the transaction type.
* Note: the notary key is checked separately for all transactions and need not be included.
@ -74,14 +89,14 @@ sealed class TransactionType {
private fun verifyEncumbrances(tx: LedgerTransaction) {
// Validate that all encumbrances exist within the set of input states.
val encumberedInputs = tx.inputs.filter { it.state.data.encumbrance != null }
val encumberedInputs = tx.inputs.filter { it.state.encumbrance != null }
encumberedInputs.forEach { encumberedInput ->
val encumbranceStateExists = tx.inputs.any {
it.ref.txhash == encumberedInput.ref.txhash && it.ref.index == encumberedInput.state.data.encumbrance
it.ref.txhash == encumberedInput.ref.txhash && it.ref.index == encumberedInput.state.encumbrance
}
if (!encumbranceStateExists) {
throw TransactionVerificationException.TransactionMissingEncumbranceException(
tx, encumberedInput.state.data.encumbrance!!,
tx, encumberedInput.state.encumbrance!!,
TransactionVerificationException.Direction.INPUT
)
}
@ -90,7 +105,7 @@ sealed class TransactionType {
// Check that, in the outputs, an encumbered state does not refer to itself as the encumbrance,
// and that the number of outputs can contain the encumbrance.
for ((i, output) in tx.outputs.withIndex()) {
val encumbranceIndex = output.data.encumbrance ?: continue
val encumbranceIndex = output.encumbrance ?: continue
if (encumbranceIndex == i || encumbranceIndex >= tx.outputs.size) {
throw TransactionVerificationException.TransactionMissingEncumbranceException(
tx, encumbranceIndex,

View File

@ -97,6 +97,9 @@ sealed class TransactionVerificationException(val tx: LedgerTransaction, cause:
class SignersMissing(tx: LedgerTransaction, val missing: List<CompositeKey>) : TransactionVerificationException(tx, null) {
override fun toString() = "Signers missing: ${missing.joinToString()}"
}
class DuplicateInputStates(tx: LedgerTransaction, val duplicates: Set<StateRef>) : TransactionVerificationException(tx, null) {
override fun toString() = "Duplicate inputs: ${duplicates.joinToString()}"
}
class InvalidNotaryChange(tx: LedgerTransaction) : TransactionVerificationException(tx, null)
class NotaryChangeInWrongTransactionType(tx: LedgerTransaction, val outputNotary: Party) : TransactionVerificationException(tx, null) {

View File

@ -1,39 +1,10 @@
package net.corda.core.contracts.clauses
import net.corda.core.contracts.AuthenticatedObject
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.TransactionForContract
import java.util.*
/**
* Compose a number of clauses, such that all of the clauses must run for verification to pass.
*/
// TODO: Rename to AllOf
class AllComposition<S : ContractState, C : CommandData, K : Any>(firstClause: Clause<S, C, K>, vararg remainingClauses: Clause<S, C, K>) : CompositeClause<S, C, K>() {
override val clauses = ArrayList<Clause<S, C, K>>()
init {
clauses.add(firstClause)
clauses.addAll(remainingClauses)
}
override fun matchedClauses(commands: List<AuthenticatedObject<C>>): List<Clause<S, C, K>> {
clauses.forEach { clause ->
check(clause.matches(commands)) { "Failed to match clause ${clause}" }
}
return clauses
}
override fun verify(tx: TransactionForContract,
inputs: List<S>,
outputs: List<S>,
commands: List<AuthenticatedObject<C>>,
groupingKey: K?): Set<C> {
return matchedClauses(commands).flatMapTo(HashSet<C>()) { clause ->
clause.verify(tx, inputs, outputs, commands, groupingKey)
}
}
override fun toString() = "All: $clauses.toList()"
}
@Deprecated("Use AllOf")
class AllComposition<S : ContractState, C : CommandData, K : Any>(firstClause: Clause<S, C, K>, vararg remainingClauses: Clause<S, C, K>) : AllOf<S, C, K>(firstClause, *remainingClauses)

View File

@ -0,0 +1,38 @@
package net.corda.core.contracts.clauses
import net.corda.core.contracts.AuthenticatedObject
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.TransactionForContract
import java.util.*
/**
* Compose a number of clauses, such that all of the clauses must run for verification to pass.
*/
open class AllOf<S : ContractState, C : CommandData, K : Any>(firstClause: Clause<S, C, K>, vararg remainingClauses: Clause<S, C, K>) : CompositeClause<S, C, K>() {
override val clauses = ArrayList<Clause<S, C, K>>()
init {
clauses.add(firstClause)
clauses.addAll(remainingClauses)
}
override fun matchedClauses(commands: List<AuthenticatedObject<C>>): List<Clause<S, C, K>> {
clauses.forEach { clause ->
check(clause.matches(commands)) { "Failed to match clause ${clause}" }
}
return clauses
}
override fun verify(tx: TransactionForContract,
inputs: List<S>,
outputs: List<S>,
commands: List<AuthenticatedObject<C>>,
groupingKey: K?): Set<C> {
return matchedClauses(commands).flatMapTo(HashSet<C>()) { clause ->
clause.verify(tx, inputs, outputs, commands, groupingKey)
}
}
override fun toString() = "All: $clauses.toList()"
}

View File

@ -1,25 +1,10 @@
package net.corda.core.contracts.clauses
import net.corda.core.contracts.AuthenticatedObject
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.TransactionForContract
import java.util.*
/**
* Compose a number of clauses, such that any number of the clauses can run.
*/
// TODO: Rename to AnyOf
class AnyComposition<in S : ContractState, C : CommandData, in K : Any>(vararg rawClauses: Clause<S, C, K>) : CompositeClause<S, C, K>() {
override val clauses: List<Clause<S, C, K>> = rawClauses.asList()
override fun matchedClauses(commands: List<AuthenticatedObject<C>>): List<Clause<S, C, K>> = clauses.filter { it.matches(commands) }
override fun verify(tx: TransactionForContract, inputs: List<S>, outputs: List<S>, commands: List<AuthenticatedObject<C>>, groupingKey: K?): Set<C> {
return matchedClauses(commands).flatMapTo(HashSet<C>()) { clause ->
clause.verify(tx, inputs, outputs, commands, groupingKey)
}
}
override fun toString(): String = "Or: ${clauses.toList()}"
}
@Deprecated("Use AnyOf instead, although note that any of requires at least one matched clause")
class AnyComposition<in S : ContractState, C : CommandData, in K : Any>(vararg rawClauses: Clause<S, C, K>) : AnyOf<S, C, K>(*rawClauses)

View File

@ -0,0 +1,28 @@
package net.corda.core.contracts.clauses
import net.corda.core.contracts.AuthenticatedObject
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.TransactionForContract
import java.util.*
/**
* Compose a number of clauses, such that one or more of the clauses can run.
*/
open class AnyOf<in S : ContractState, C : CommandData, in K : Any>(vararg rawClauses: Clause<S, C, K>) : CompositeClause<S, C, K>() {
override val clauses: List<Clause<S, C, K>> = rawClauses.toList()
override fun matchedClauses(commands: List<AuthenticatedObject<C>>): List<Clause<S, C, K>> {
val matched = clauses.filter { it.matches(commands) }
require(matched.isNotEmpty()) { "At least one clause must match" }
return matched
}
override fun verify(tx: TransactionForContract, inputs: List<S>, outputs: List<S>, commands: List<AuthenticatedObject<C>>, groupingKey: K?): Set<C> {
return matchedClauses(commands).flatMapTo(HashSet<C>()) { clause ->
clause.verify(tx, inputs, outputs, commands, groupingKey)
}
}
override fun toString(): String = "Any: ${clauses.toList()}"
}

View File

@ -26,7 +26,11 @@ abstract class Clause<in S : ContractState, C : CommandData, in K : Any> {
/**
* Determine the subclauses which will be verified as a result of verifying this clause.
*
* @throws IllegalStateException if the given commands do not result in a valid execution (for example no match
* with [FirstOf]).
*/
@Throws(IllegalStateException::class)
open fun getExecutionPath(commands: List<AuthenticatedObject<C>>): List<Clause<*, *, *>>
= listOf(this)

View File

@ -14,6 +14,12 @@ abstract class CompositeClause<in S : ContractState, C : CommandData, in K : Any
override fun getExecutionPath(commands: List<AuthenticatedObject<C>>): List<Clause<*, *, *>>
= matchedClauses(commands).flatMap { it.getExecutionPath(commands) }
/** Determine which clauses are matched by the supplied commands */
/**
* Determine which clauses are matched by the supplied commands.
*
* @throws IllegalStateException if the given commands do not result in a valid execution (for example no match
* with [FirstOf]).
*/
@Throws(IllegalStateException::class)
abstract fun matchedClauses(commands: List<AuthenticatedObject<C>>): List<Clause<S, C, K>>
}

View File

@ -10,7 +10,7 @@ import java.util.*
/**
* Compose a number of clauses, such that the first match is run, and it errors if none is run.
*/
// TODO: Rename to FirstOf
@Deprecated("Use FirstOf instead")
class FirstComposition<S : ContractState, C : CommandData, K : Any>(val firstClause: Clause<S, C, K>, vararg remainingClauses: Clause<S, C, K>) : CompositeClause<S, C, K>() {
companion object {
val logger = loggerFor<FirstComposition<*, *, *>>()

View File

@ -0,0 +1,41 @@
package net.corda.core.contracts.clauses
import net.corda.core.contracts.AuthenticatedObject
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.TransactionForContract
import net.corda.core.utilities.loggerFor
import java.util.*
/**
* Compose a number of clauses, such that the first match is run, and it errors if none is run.
*/
class FirstOf<S : ContractState, C : CommandData, K : Any>(val firstClause: Clause<S, C, K>, vararg remainingClauses: Clause<S, C, K>) : CompositeClause<S, C, K>() {
companion object {
val logger = loggerFor<FirstOf<*, *, *>>()
}
override val clauses = ArrayList<Clause<S, C, K>>()
/**
* Get the single matched clause from the set this composes, based on the given commands. This is provided as
* helper method for internal use, rather than using the exposed [matchedClauses] function which unnecessarily
* wraps the clause in a list.
*/
private fun matchedClause(commands: List<AuthenticatedObject<C>>): Clause<S, C, K> {
return clauses.firstOrNull { it.matches(commands) } ?: throw IllegalStateException("No delegate clause matched in first composition")
}
override fun matchedClauses(commands: List<AuthenticatedObject<C>>) = listOf(matchedClause(commands))
init {
clauses.add(firstClause)
clauses.addAll(remainingClauses)
}
override fun verify(tx: TransactionForContract, inputs: List<S>, outputs: List<S>, commands: List<AuthenticatedObject<C>>, groupingKey: K?): Set<C> {
return matchedClause(commands).verify(tx, inputs, outputs, commands, groupingKey)
}
override fun toString() = "First: ${clauses.toList()}"
}

View File

@ -1,185 +0,0 @@
package net.corda.core.crypto
import sun.security.util.HostnameChecker
import java.net.InetAddress
import java.net.Socket
import java.net.UnknownHostException
import java.security.KeyStore
import java.security.Provider
import java.security.Security
import java.security.cert.CertificateException
import java.security.cert.X509Certificate
import java.util.concurrent.ConcurrentHashMap
import javax.net.ssl.*
/**
* Call this to change the default verification algorithm and this use the WhitelistTrustManager
* implementation. This is a work around to the fact that ArtemisMQ and probably many other libraries
* don't correctly configure the SSLParameters with setEndpointIdentificationAlgorithm and thus don't check
* that the certificate matches with the DNS entry requested. This exposes us to man in the middle attacks.
* The issue has been raised with ArtemisMQ: https://issues.apache.org/jira/browse/ARTEMIS-656
*/
fun registerWhitelistTrustManager() {
if (Security.getProvider("WhitelistTrustManager") == null) {
WhitelistTrustManagerProvider.register()
}
// Forcibly change the TrustManagerFactory defaultAlgorithm to be us
// This will apply to all code using TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
// Which includes the standard HTTPS implementation and most other SSL code
// TrustManagerFactory.getInstance(WhitelistTrustManagerProvider.originalTrustProviderAlgorithm)) will
// allow access to the original implementation which is normally "PKIX"
Security.setProperty("ssl.TrustManagerFactory.algorithm", "whitelistTrustManager")
}
/**
* Custom Security Provider that forces the TrustManagerFactory to be our custom one.
* Also holds the identity of the original TrustManager algorithm so
* that we can delegate most of the checking to the proper Java code. We simply add some more checks.
*
* The whitelist automatically includes the local server DNS name and IP address
*
*/
object WhitelistTrustManagerProvider : Provider("WhitelistTrustManager",
1.0,
"Provider for custom trust manager that always validates certificate names") {
val originalTrustProviderAlgorithm = Security.getProperty("ssl.TrustManagerFactory.algorithm")
private val _whitelist = ConcurrentHashMap.newKeySet<String>()
val whitelist: Set<String> get() = _whitelist.toSet() // The acceptable IP and DNS names for clients and servers.
init {
// Add ourselves to whitelist as currently we have to connect to a local ArtemisMQ broker
val host = InetAddress.getLocalHost()
addWhitelistEntry(host.hostName)
}
/**
* Security provider registration function for WhitelistTrustManagerProvider
*/
fun register() {
Security.addProvider(WhitelistTrustManagerProvider)
// Register our custom TrustManagerFactorySpi
put("TrustManagerFactory.whitelistTrustManager", "net.corda.core.crypto.WhitelistTrustManagerSpi")
}
/**
* Adds an extra name to the whitelist if not already present
* If this is a new entry it will internally request a DNS lookup which may block the calling thread.
*/
fun addWhitelistEntry(serverName: String) {
if (!_whitelist.contains(serverName)) { // Safe as we never delete from the set
addWhitelistEntries(listOf(serverName))
}
}
/**
* Adds a list of servers to the whitelist and also adds their fully resolved name/ip address after DNS lookup
* If the server name is not an actual DNS name this is silently ignored.
* The DNS request may block the calling thread.
*/
fun addWhitelistEntries(serverNames: List<String>) {
_whitelist.addAll(serverNames)
for (name in serverNames) {
try {
val addresses = InetAddress.getAllByName(name).toList()
_whitelist.addAll(addresses.map { y -> y.canonicalHostName })
_whitelist.addAll(addresses.map { y -> y.hostAddress })
} catch (ex: UnknownHostException) {
// Ignore if the server name is not resolvable e.g. for wildcard addresses, or addresses that can only be resolved externally
}
}
}
}
/**
* Registered TrustManagerFactorySpi
*/
class WhitelistTrustManagerSpi : TrustManagerFactorySpi() {
// Get the original implementation to delegate to (can't use Kotlin delegation on abstract classes unfortunately).
val originalProvider = TrustManagerFactory.getInstance(WhitelistTrustManagerProvider.originalTrustProviderAlgorithm)
override fun engineInit(keyStore: KeyStore?) {
originalProvider.init(keyStore)
}
override fun engineInit(managerFactoryParameters: ManagerFactoryParameters?) {
originalProvider.init(managerFactoryParameters)
}
override fun engineGetTrustManagers(): Array<out TrustManager> {
val parent = originalProvider.trustManagers.first() as X509ExtendedTrustManager
// Wrap original provider in ours and return
return arrayOf(WhitelistTrustManager(parent))
}
}
/**
* Our TrustManager extension takes the standard certificate checker and first delegates all the
* chain checking to that. If everything is well formed we then simply add a check against our whitelist
*/
class WhitelistTrustManager(val originalProvider: X509ExtendedTrustManager) : X509ExtendedTrustManager() {
// Use same Helper class as standard HTTPS library validator
val checker = HostnameChecker.getInstance(HostnameChecker.TYPE_TLS)
private fun checkIdentity(hostname: String?, cert: X509Certificate) {
// Based on standard code in sun.security.ssl.X509TrustManagerImpl.checkIdentity
// if IPv6 strip off the "[]"
if ((hostname != null) && hostname.startsWith("[") && hostname.endsWith("]")) {
checker.match(hostname.substring(1, hostname.length - 1), cert)
} else {
checker.match(hostname, cert)
}
}
/**
* scan whitelist and confirm the certificate matches at least one entry
*/
private fun checkWhitelist(cert: X509Certificate) {
for (whiteListEntry in WhitelistTrustManagerProvider.whitelist) {
try {
checkIdentity(whiteListEntry, cert)
return // if we get here without throwing we had a match
} catch(ex: CertificateException) {
// Ignore and check the next entry until we find a match, or exhaust the whitelist
}
}
throw CertificateException("Certificate not on whitelist ${cert.subjectDN}")
}
override fun checkClientTrusted(chain: Array<out X509Certificate>, authType: String, socket: Socket?) {
originalProvider.checkClientTrusted(chain, authType, socket)
checkWhitelist(chain[0])
}
override fun checkClientTrusted(chain: Array<out X509Certificate>, authType: String, engine: SSLEngine?) {
originalProvider.checkClientTrusted(chain, authType, engine)
checkWhitelist(chain[0])
}
override fun checkClientTrusted(chain: Array<out X509Certificate>, authType: String) {
originalProvider.checkClientTrusted(chain, authType)
checkWhitelist(chain[0])
}
override fun checkServerTrusted(chain: Array<out X509Certificate>, authType: String, socket: Socket?) {
originalProvider.checkServerTrusted(chain, authType, socket)
checkWhitelist(chain[0])
}
override fun checkServerTrusted(chain: Array<out X509Certificate>, authType: String, engine: SSLEngine?) {
originalProvider.checkServerTrusted(chain, authType, engine)
checkWhitelist(chain[0])
}
override fun checkServerTrusted(chain: Array<out X509Certificate>, authType: String) {
originalProvider.checkServerTrusted(chain, authType)
checkWhitelist(chain[0])
}
override fun getAcceptedIssuers(): Array<out X509Certificate> {
return originalProvider.acceptedIssuers
}
}

View File

@ -26,21 +26,18 @@ import rx.Observable
* it to the [subFlow] method. It will return the result of that flow when it completes.
*/
abstract class FlowLogic<out T> {
/** Reference to the [Fiber] instance that is the top level controller for the entire flow. */
lateinit var fsm: FlowStateMachine<*>
/** This is where you should log things to. */
val logger: Logger get() = fsm.logger
val logger: Logger get() = stateMachine.logger
/** Returns a wrapped [UUID] object that identifies this state machine run (i.e. subflows have the same identifier as their parents). */
val runId: StateMachineRunId get() = stateMachine.id
/**
* Provides access to big, heavy classes that may be reconstructed from time to time, e.g. across restarts. It is
* only available once the flow has started, which means it cannnot be accessed in the constructor. Either
* access this lazily or from inside [call].
*/
val serviceHub: ServiceHub get() = fsm.serviceHub
private var sessionFlow: FlowLogic<*> = this
val serviceHub: ServiceHub get() = stateMachine.serviceHub
/**
* Return the marker [Class] which [party] has used to register the counterparty flow that is to execute on the
@ -49,35 +46,73 @@ abstract class FlowLogic<out T> {
*/
open fun getCounterpartyMarker(party: Party): Class<*> = javaClass
// Kotlin helpers that allow the use of generic types.
inline fun <reified T : Any> sendAndReceive(otherParty: Party, payload: Any): UntrustworthyData<T> {
return sendAndReceive(otherParty, payload, T::class.java)
}
// TODO: Move the receiveType param to first position for readability
/**
* Serializes and queues the given [payload] object for sending to the [otherParty]. Suspends until a response
* is received, which must be of the given [R] type.
*
* 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.
*
* @returns an [UntrustworthyData] wrapper around the received object.
*/
inline fun <reified R : Any> sendAndReceive(otherParty: Party, payload: Any) = sendAndReceive(R::class.java, otherParty, payload)
/**
* Serializes and queues the given [payload] object for sending to the [otherParty]. 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
* use this when you expect to do a message swap than do use [send] and then [receive] in turn.
*
* @returns an [UntrustworthyData] wrapper around the received object.
*/
@Suspendable
fun <T : Any> sendAndReceive(otherParty: Party, payload: Any, receiveType: Class<T>): UntrustworthyData<T> {
return fsm.sendAndReceive(otherParty, payload, receiveType, sessionFlow)
}
inline fun <reified T : Any> receive(otherParty: Party): UntrustworthyData<T> = receive(otherParty, T::class.java)
// TODO: Move the receiveType param to first position for readability
@Suspendable
fun <T : Any> receive(otherParty: Party, receiveType: Class<T>): UntrustworthyData<T> {
return fsm.receive(otherParty, receiveType, sessionFlow)
}
@Suspendable
fun send(otherParty: Party, payload: Any) {
fsm.send(otherParty, payload, sessionFlow)
open fun <T : Any> sendAndReceive(receiveType: Class<T>, otherParty: Party, payload: Any): UntrustworthyData<T> {
return stateMachine.sendAndReceive(receiveType, otherParty, payload, sessionFlow)
}
/**
* Invokes the given subflow by simply passing through this [FlowLogic]s reference to the
* [FlowStateMachine] and then calling the [call] method.
* Suspends until the specified [otherParty] sends us a message of type [R].
*
* 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.
*/
inline fun <reified R : Any> receive(otherParty: Party): UntrustworthyData<R> = receive(R::class.java, otherParty)
/**
* Suspends until the specified [otherParty] 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.
*/
@Suspendable
open fun <T : Any> receive(receiveType: Class<T>, otherParty: Party): UntrustworthyData<T> {
return stateMachine.receive(receiveType, otherParty, sessionFlow)
}
/**
* Queues the given [payload] for sending to the [otherParty] and continues without suspending.
*
* Note that the other party may receive the message at some arbitrary later point or not at all: if [otherParty]
* 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.
*/
@Suspendable
open fun send(otherParty: Party, payload: Any) = stateMachine.send(otherParty, payload, sessionFlow)
/**
* Invokes the given subflow. This function returns once the subflow completes successfully with the result
* returned by that subflows [call] method. If the subflow has a progress tracker, it is attached to the
* current step in this flow's progress tracker.
*
* @param shareParentSessions In certain situations the need arises to use the same sessions the parent flow has
* already established. However this also prevents the subflow from creating new sessions with those parties.
* For this reason the default value is false.
@ -85,8 +120,9 @@ abstract class FlowLogic<out T> {
// TODO Rethink the default value for shareParentSessions
// TODO shareParentSessions is a bit too low-level and perhaps can be expresed in a better way
@Suspendable
fun <R> subFlow(subLogic: FlowLogic<R>, shareParentSessions: Boolean = false): R {
subLogic.fsm = fsm
@JvmOverloads
open fun <R> subFlow(subLogic: FlowLogic<R>, shareParentSessions: Boolean = false): R {
subLogic.stateMachine = stateMachine
maybeWireUpProgressTracking(subLogic)
if (shareParentSessions) {
subLogic.sessionFlow = this
@ -97,6 +133,52 @@ abstract class FlowLogic<out T> {
return result
}
/**
* Override this to provide a [ProgressTracker]. If one is provided and stepped, the framework will do something
* helpful with the progress reports. If this flow is invoked as a subflow of another, then the
* tracker will be made a child of the current step in the parent. If it's null, this flow doesn't track
* progress.
*
* Note that this has to return a tracker before the flow is invoked. You can't change your mind half way
* through.
*/
open val progressTracker: ProgressTracker? = null
/**
* This is where you fill out your business logic. The returned object will usually be ignored, but can be
* helpful if this flow is meant to be used as a subflow.
*/
@Suspendable
abstract fun call(): T
/**
* Returns a pair of the current progress step, as a string, and an observable of stringified changes to the
* [progressTracker].
*
* @return Returns null if this flow has no progress tracker.
*/
fun track(): Pair<String, Observable<String>>? {
// TODO this is not threadsafe, needs an atomic get-step-and-subscribe
return progressTracker?.let {
Pair(it.currentStep.toString(), it.changes.map { it.toString() })
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
private var _stateMachine: FlowStateMachine<*>? = null
/**
* Internal only. Reference to the [Fiber] instance that is the top level controller for the entire flow. When
* inside a flow this is equivalent to [Strand.currentStrand]. This is public only because it must be accessed
* across module boundaries.
*/
var stateMachine: FlowStateMachine<*>
get() = _stateMachine ?: throw IllegalStateException("This can only be done after the flow has been started.")
set(value) { _stateMachine = value }
// This points to the outermost flow and is changed when a subflow is invoked.
private var sessionFlow: FlowLogic<*> = this
private fun maybeWireUpProgressTracking(subLogic: FlowLogic<*>) {
val ours = progressTracker
@ -109,27 +191,4 @@ abstract class FlowLogic<out T> {
ours.setChildProgressTracker(ours.currentStep, theirs)
}
}
/**
* Override this to provide a [ProgressTracker]. If one is provided and stepped, the framework will do something
* helpful with the progress reports. If this flow is invoked as a sub-flow of another, then the
* tracker will be made a child of the current step in the parent. If it's null, this flow doesn't track
* progress.
*
* Note that this has to return a tracker before the flow is invoked. You can't change your mind half way
* through.
*/
open val progressTracker: ProgressTracker? = null
/** This is where you fill out your business logic. */
@Suspendable
abstract fun call(): T
// TODO this is not threadsafe, needs an atomic get-step-and-subscribe
fun track(): Pair<String, Observable<String>>? {
return progressTracker?.let {
Pair(it.currentStep.toString(), it.changes.map { it.toString() })
}
}
}

View File

@ -8,8 +8,11 @@ import net.corda.core.utilities.UntrustworthyData
import org.slf4j.Logger
import java.util.*
/**
* A unique identifier for a single state machine run, valid across node restarts. Note that a single run always
* has at least one flow, but that flow may also invoke sub-flows: they all share the same run id.
*/
data class StateMachineRunId private constructor(val uuid: UUID) {
companion object {
fun createRandom(): StateMachineRunId = StateMachineRunId(UUID.randomUUID())
fun wrap(uuid: UUID): StateMachineRunId = StateMachineRunId(uuid)
@ -18,34 +21,24 @@ data class StateMachineRunId private constructor(val uuid: UUID) {
override fun toString(): String = "[$uuid]"
}
/**
* A FlowStateMachine instance is a suspendable fiber that delegates all actual logic to a [FlowLogic] instance.
* For any given flow there is only one PSM, even if that flow invokes subflows.
*
* These classes are created by the [StateMachineManager] when a new flow is started at the topmost level. If
* a flow invokes a sub-flow, then it will pass along the PSM to the child. The call method of the topmost
* logic element gets to return the value that the entire state machine resolves to.
*/
/** This is an internal interface that is implemented by code in the node module. You should look at [FlowLogic]. */
interface FlowStateMachine<R> {
@Suspendable
fun <T : Any> sendAndReceive(otherParty: Party,
fun <T : Any> sendAndReceive(receiveType: Class<T>,
otherParty: Party,
payload: Any,
receiveType: Class<T>,
sessionFlow: FlowLogic<*>): UntrustworthyData<T>
@Suspendable
fun <T : Any> receive(otherParty: Party, receiveType: Class<T>, sessionFlow: FlowLogic<*>): UntrustworthyData<T>
fun <T : Any> receive(receiveType: Class<T>, otherParty: Party, sessionFlow: FlowLogic<*>): UntrustworthyData<T>
@Suspendable
fun send(otherParty: Party, payload: Any, sessionFlow: FlowLogic<*>)
val serviceHub: ServiceHub
val logger: Logger
/** Unique ID for this machine run, valid across restarts */
val id: StateMachineRunId
/** This future will complete when the call method returns. */
val resultFuture: ListenableFuture<R>
}
class FlowSessionException(message: String) : Exception(message)
class FlowException(message: String) : RuntimeException(message)

View File

@ -23,8 +23,12 @@ data class StateMachineInfo(
)
sealed class StateMachineUpdate(val id: StateMachineRunId) {
class Added(val stateMachineInfo: StateMachineInfo) : StateMachineUpdate(stateMachineInfo.id)
class Removed(id: StateMachineRunId) : StateMachineUpdate(id)
class Added(val stateMachineInfo: StateMachineInfo) : StateMachineUpdate(stateMachineInfo.id) {
override fun toString() = "Added($id, ${stateMachineInfo.flowLogicClassName})"
}
class Removed(id: StateMachineRunId) : StateMachineUpdate(id) {
override fun toString() = "Removed($id)"
}
}
/**
@ -172,5 +176,6 @@ inline fun <T : Any, A, B, C, D, reified R : FlowLogic<T>> CordaRPCOps.startFlow
data class FlowHandle<A>(
val id: StateMachineRunId,
val progress: Observable<String>,
// TODO This should be ListenableFuture<A>
val returnValue: Observable<A>
)

View File

@ -4,6 +4,7 @@ import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.SettableFuture
import net.corda.core.catch
import net.corda.core.node.services.DEFAULT_SESSION_ID
import net.corda.core.node.services.PartyInfo
import net.corda.core.serialization.DeserializeAsKotlinObjectDef
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
@ -79,6 +80,9 @@ interface MessagingService {
*/
fun createMessage(topicSession: TopicSession, data: ByteArray, uuid: UUID = UUID.randomUUID()): Message
/** Given information about either a specific node or a service returns its corresponding address */
fun getAddressOfParty(partyInfo: PartyInfo): MessageRecipients
/** Returns an address that refers to this node. */
val myAddress: SingleMessageRecipient
}
@ -127,7 +131,7 @@ inline fun MessagingService.runOnNextMessage(topicSession: TopicSession, crossin
/**
* Returns a [ListenableFuture] of the next message payload ([Message.data]) which is received on the given topic and sessionId.
* The payload is deserilaized to an object of type [M]. Any exceptions thrown will be captured by the future.
* The payload is deserialized to an object of type [M]. Any exceptions thrown will be captured by the future.
*/
fun <M : Any> MessagingService.onNext(topic: String, sessionId: Long): ListenableFuture<M> {
val messageFuture = SettableFuture.create<M>()

View File

@ -58,6 +58,7 @@ interface ServiceHub {
/**
* Will check [logicType] and [args] against a whitelist and if acceptable then construct and initiate the flow.
* Note that you must be on the server thread to call this method.
*
* @throws IllegalFlowLogicException or IllegalArgumentException if there are problems with the [logicType] or [args].
*/
@ -81,7 +82,6 @@ interface ServiceHub {
* Typical use is during signing in flows and for unit test signing.
*/
val notaryIdentityKey: KeyPair get() = this.keyManagementService.toKeyPair(this.myInfo.notaryIdentity.owningKey.keys)
}
/**

View File

@ -5,9 +5,11 @@ import com.google.common.util.concurrent.ListenableFuture
import net.corda.core.contracts.Contract
import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.Party
import net.corda.core.messaging.MessageRecipients
import net.corda.core.messaging.MessagingService
import net.corda.core.messaging.SingleMessageRecipient
import net.corda.core.node.NodeInfo
import net.corda.core.node.ServiceEntry
import net.corda.core.randomOrNull
import rx.Observable
@ -63,31 +65,27 @@ interface NetworkMapCache {
/** Look up the node info for a legal name. */
fun getNodeByLegalName(name: String): NodeInfo? = partyNodes.singleOrNull { it.legalIdentity.name == name }
/** Look up the node info for a composite key. */
fun getNodeByCompositeKey(compositeKey: CompositeKey): NodeInfo? {
/**
* In general, nodes can advertise multiple identities: a legal identity, and separate identities for each of
* the services it provides. In case of a distributed service run by multiple nodes each participant advertises
* the identity of the *whole group*.
*/
/** Look up the node info for a specific peer key. */
fun getNodeByLegalIdentityKey(compositeKey: CompositeKey): NodeInfo? {
// Although we should never have more than one match, it is theoretically possible. Report an error if it happens.
val candidates = partyNodes.filter {
(it.legalIdentity.owningKey == compositeKey)
|| it.advertisedServices.any { it.identity.owningKey == compositeKey }
}
val candidates = partyNodes.filter { it.legalIdentity.owningKey == compositeKey }
check(candidates.size <= 1) { "Found more than one match for key $compositeKey" }
return candidates.singleOrNull()
}
/**
* Given a [party], returns a node advertising it as an identity. If more than one node found the result
* is chosen at random.
*
* In general, nodes can advertise multiple identities: a legal identity, and separate identities for each of
* the services it provides. In case of a distributed service run by multiple nodes each participant advertises
* the identity of the *whole group*. If the provided [party] is a group identity, multiple nodes advertising it
* will be found, and this method will return a randomly chosen one. If [party] is an individual (legal) identity,
* we currently assume that it will be advertised by one node only, which will be returned as the result.
*/
fun getRepresentativeNode(party: Party): NodeInfo? {
return partyNodes.randomOrNull { it.legalIdentity == party || it.advertisedServices.any { it.identity == party } }
/** Look up all nodes advertising the service owned by [compositeKey] */
fun getNodesByAdvertisedServiceIdentityKey(compositeKey: CompositeKey): List<NodeInfo> {
return partyNodes.filter { it.advertisedServices.any { it.identity.owningKey == compositeKey } }
}
/** Returns information about the party, which may be a specific node or a service */
fun getPartyInfo(party: Party): PartyInfo?
/** Gets a notary identity by the given name. */
fun getNotary(name: String): Party? {
val notaryNode = notaryNodes.randomOrNull {

View File

@ -0,0 +1,18 @@
package net.corda.core.node.services
import net.corda.core.crypto.Party
import net.corda.core.node.NodeInfo
import net.corda.core.node.ServiceEntry
/**
* Holds information about a [Party], which may refer to either a specific node or a service.
*/
sealed class PartyInfo() {
abstract val party: Party
class Node(val node: NodeInfo) : PartyInfo() {
override val party = node.legalIdentity
}
class Service(val service: ServiceEntry) : PartyInfo() {
override val party = service.identity
}
}

View File

@ -11,15 +11,9 @@ sealed class ServiceType(val id: String) {
//
// * IDs must start with a lower case letter
// * IDs can only contain alphanumeric, full stop and underscore ASCII characters
require(id.matches(Regex("[a-z][a-zA-Z0-9._]+")))
}
private class ServiceTypeImpl(baseId: String, subTypeId: String) : ServiceType("$baseId.$subTypeId") {
init {
// only allowed one level of subtype
require(subTypeId.matches(Regex("[a-z]\\w+")))
}
require(id.matches(Regex("[a-z][a-zA-Z0-9._]+"))) { id }
}
private class ServiceTypeImpl(baseId: String, subTypeId: String) : ServiceType("$baseId.$subTypeId")
private class ServiceTypeDirect(id: String) : ServiceType(id)

View File

@ -1,12 +1,12 @@
package net.corda.core.node.services
import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.SettableFuture
import net.corda.core.contracts.*
import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.Party
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.toStringShort
import net.corda.core.toFuture
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.transactions.WireTransaction
import rx.Observable
@ -38,7 +38,7 @@ val DEFAULT_SESSION_ID = 0L
* Active means they haven't been consumed yet (or we don't know about it).
* Relevant means they contain at least one of our pubkeys.
*/
class Vault(val states: Iterable<StateAndRef<ContractState>>) {
class Vault(val states: List<StateAndRef<ContractState>>) {
@Suppress("UNCHECKED_CAST")
inline fun <reified T : ContractState> statesOfType() = states.filter { it.state.data is T } as List<StateAndRef<T>>
@ -50,7 +50,10 @@ class Vault(val states: Iterable<StateAndRef<ContractState>>) {
* If the vault observes multiple transactions simultaneously, where some transactions consume the outputs of some of the
* other transactions observed, then the changes are observed "net" of those.
*/
data class Update(val consumed: Set<StateRef>, val produced: Set<StateAndRef<ContractState>>) {
data class Update(val consumed: Set<StateAndRef<ContractState>>, val produced: Set<StateAndRef<ContractState>>) {
/** Checks whether the update contains a state of the specified type. */
inline fun <reified T : ContractState> containsType() = consumed.any { it.state.data is T } || produced.any { it.state.data is T }
/**
* Combine two updates into a single update with the combined inputs and outputs of the two updates but net
* any outputs of the left-hand-side (this) that are consumed by the inputs of the right-hand-side (rhs).
@ -58,12 +61,10 @@ class Vault(val states: Iterable<StateAndRef<ContractState>>) {
* i.e. the net effect in terms of state live-ness of receiving the combined update is the same as receiving this followed by rhs.
*/
operator fun plus(rhs: Update): Update {
val previouslyProduced = produced.map { it.ref }
val previouslyConsumed = consumed
val combined = Vault.Update(
previouslyConsumed + (rhs.consumed - previouslyProduced),
consumed + (rhs.consumed - produced),
// The ordering below matters to preserve ordering of consumed/produced Sets when they are insertion order dependent implementations.
produced.filter { it.ref !in rhs.consumed }.toSet() + rhs.produced)
produced.filter { it !in rhs.consumed }.toSet() + rhs.produced)
return combined
}
@ -120,15 +121,7 @@ interface VaultService {
* Returns a map of how much cash we have in each currency, ignoring details like issuer. Note: currencies for
* which we have no cash evaluate to null (not present in map), not 0.
*/
@Suppress("UNCHECKED_CAST")
val cashBalances: Map<Currency, Amount<Currency>>
get() = currentVault.states.
// Select the states we own which are cash, ignore the rest, take the amounts.
mapNotNull { (it.state.data as? FungibleAsset<Currency>)?.amount }.
// Turn into a Map<Currency, List<Amount>> like { GBP -> (£100, £500, etc), USD -> ($2000, $50) }
groupBy { it.token.product }.
// Collapse to Map<Currency, Amount> by summing all the amounts of the same currency together.
mapValues { it.value.map { Amount(it.quantity, it.token.product) }.sumOrThrow() }
/**
* Atomically get the current vault and a stream of updates. Note that the Observable buffers updates until the
@ -158,24 +151,18 @@ interface VaultService {
* Possibly update the vault by marking as spent states that these transactions consume, and adding any relevant
* new states that they create. You should only insert transactions that have been successfully verified here!
*
* Returns the new vault that resulted from applying the transactions (note: it may quickly become out of date).
*
* TODO: Consider if there's a good way to enforce the must-be-verified requirement in the type system.
*/
fun notifyAll(txns: Iterable<WireTransaction>): Vault
fun notifyAll(txns: Iterable<WireTransaction>)
/** Same as notifyAll but with a single transaction. */
fun notify(tx: WireTransaction): Vault = notifyAll(listOf(tx))
fun notify(tx: WireTransaction) = notifyAll(listOf(tx))
/**
* Provide a [Future] for when a [StateRef] is consumed, which can be very useful in building tests.
*/
fun whenConsumed(ref: StateRef): ListenableFuture<Vault.Update> {
val future = SettableFuture.create<Vault.Update>()
updates.filter { ref in it.consumed }.first().subscribe {
future.set(it)
}
return future
return updates.filter { it.consumed.any { it.ref == ref } }.toFuture()
}
/**

View File

@ -410,10 +410,8 @@ fun createKryo(k: Kryo = Kryo()): Kryo {
}
})
// Some things where the JRE provides an efficient custom serialisation.
val keyPair = generateKeyPair()
register(keyPair.public.javaClass, Ed25519PublicKeySerializer)
register(keyPair.private.javaClass, Ed25519PrivateKeySerializer)
register(EdDSAPublicKey::class.java, Ed25519PublicKeySerializer)
register(EdDSAPrivateKey::class.java, Ed25519PrivateKeySerializer)
register(Instant::class.java, ReferencesAwareJavaSerializer)
// Using a custom serializer for compactness

View File

@ -159,11 +159,12 @@ open class TransactionBuilder(
return outputs.size - 1
}
fun addOutputState(state: ContractState, notary: Party) = addOutputState(TransactionState(state, notary))
@JvmOverloads
fun addOutputState(state: ContractState, notary: Party, encumbrance: Int? = null) = addOutputState(TransactionState(state, notary, encumbrance))
/** A default notary must be specified during builder construction to use this method */
fun addOutputState(state: ContractState): Int {
checkNotNull(notary) { "Need to specify a Notary for the state, or set a default one on TransactionBuilder initialisation" }
checkNotNull(notary) { "Need to specify a notary for the state, or set a default one on TransactionBuilder initialisation" }
return addOutputState(state, notary!!)
}

View File

@ -5,8 +5,9 @@ package net.corda.core.utilities
*/
object Emoji {
// Unfortunately only Apple has a terminal that can do colour emoji AND an emoji font installed by default.
val hasEmojiTerminal by lazy { System.getenv("TERM_PROGRAM") == "Apple_Terminal" }
val hasEmojiTerminal by lazy { listOf("Apple_Terminal", "iTerm.app").contains(System.getenv("TERM_PROGRAM")) }
const val CODE_SANTA_CLAUS = "\ud83c\udf85"
const val CODE_DIAMOND = "\ud83d\udd37"
const val CODE_BAG_OF_CASH = "\ud83d\udcb0"
const val CODE_NEWSPAPER = "\ud83d\udcf0"
@ -22,6 +23,7 @@ object Emoji {
*/
val emojiMode = ThreadLocal<Any>()
val santaClaus: String get() = if (emojiMode.get() != null) "$CODE_SANTA_CLAUS " else ""
val diamond: String get() = if (emojiMode.get() != null) "$CODE_DIAMOND " else ""
val bagOfCash: String get() = if (emojiMode.get() != null) "$CODE_BAG_OF_CASH " else ""
val newspaper: String get() = if (emojiMode.get() != null) "$CODE_NEWSPAPER " else ""

View File

@ -67,12 +67,12 @@ abstract class AbstractStateReplacementFlow<T> {
}
abstract protected fun assembleProposal(stateRef: StateRef, modification: T, stx: SignedTransaction): Proposal<T>
abstract protected fun assembleTx(): Pair<SignedTransaction, List<CompositeKey>>
abstract protected fun assembleTx(): Pair<SignedTransaction, Iterable<CompositeKey>>
@Suspendable
private fun collectSignatures(participants: List<CompositeKey>, stx: SignedTransaction): List<DigitalSignature.WithKey> {
private fun collectSignatures(participants: Iterable<CompositeKey>, stx: SignedTransaction): List<DigitalSignature.WithKey> {
val parties = participants.map {
val participantNode = serviceHub.networkMapCache.getNodeByCompositeKey(it) ?:
val participantNode = serviceHub.networkMapCache.getNodeByLegalIdentityKey(it) ?:
throw IllegalStateException("Participant $it to state $originalState not found on the network")
participantNode.legalIdentity
}

View File

@ -1,13 +1,11 @@
package net.corda.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TransactionType
import net.corda.core.contracts.*
import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.Party
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.UntrustworthyData
import net.corda.flows.NotaryChangeFlow.Acceptor
@ -36,17 +34,66 @@ object NotaryChangeFlow : AbstractStateReplacementFlow<Party>() {
override fun assembleProposal(stateRef: StateRef, modification: Party, stx: SignedTransaction): AbstractStateReplacementFlow.Proposal<Party>
= Proposal(stateRef, modification, stx)
override fun assembleTx(): Pair<SignedTransaction, List<CompositeKey>> {
override fun assembleTx(): Pair<SignedTransaction, Iterable<CompositeKey>> {
val state = originalState.state
val newState = state.withNotary(modification)
val participants = state.data.participants
val tx = TransactionType.NotaryChange.Builder(originalState.state.notary).withItems(originalState, newState)
val tx = TransactionType.NotaryChange.Builder(originalState.state.notary)
val participants: Iterable<CompositeKey>
if (state.encumbrance == null) {
val modifiedState = TransactionState(state.data, modification)
tx.addInputState(originalState)
tx.addOutputState(modifiedState)
participants = state.data.participants
} else {
participants = resolveEncumbrances(tx)
}
val myKey = serviceHub.legalIdentityKey
tx.signWith(myKey)
val stx = tx.toSignedTransaction(false)
return Pair(stx, participants)
}
/**
* Adds the notary change state transitions to the [tx] builder for the [originalState] and its encumbrance
* state chain (encumbrance states might be themselves encumbered by other states).
*
* @return union of all added states' participants
*/
private fun resolveEncumbrances(tx: TransactionBuilder): Iterable<CompositeKey> {
val stateRef = originalState.ref
val txId = stateRef.txhash
val issuingTx = serviceHub.storageService.validatedTransactions.getTransaction(txId) ?: throw IllegalStateException("Transaction $txId not found")
val outputs = issuingTx.tx.outputs
val participants = mutableSetOf<CompositeKey>()
var nextStateIndex = stateRef.index
var newOutputPosition = tx.outputStates().size
while (true) {
val nextState = outputs[nextStateIndex]
tx.addInputState(StateAndRef(nextState, StateRef(txId, nextStateIndex)))
participants.addAll(nextState.data.participants)
if (nextState.encumbrance == null) {
val modifiedState = TransactionState(nextState.data, modification)
tx.addOutputState(modifiedState)
break
} else {
val modifiedState = TransactionState(nextState.data, modification, newOutputPosition + 1)
tx.addOutputState(modifiedState)
nextStateIndex = nextState.encumbrance
}
newOutputPosition++
}
return participants
}
}
class Acceptor(otherSide: Party,

View File

@ -1,7 +1,10 @@
package net.corda.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.crypto.*
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.Party
import net.corda.core.crypto.SignedData
import net.corda.core.crypto.signWithECDSA
import net.corda.core.flows.FlowLogic
import net.corda.core.node.services.TimestampChecker
import net.corda.core.node.services.UniquenessException
@ -47,7 +50,7 @@ object NotaryFlow {
try {
stx.verifySignatures(notaryParty.owningKey)
} catch (ex: SignedTransaction.SignaturesMissingException) {
throw NotaryException(NotaryError.SignaturesMissing(ex.missing))
throw NotaryException(NotaryError.SignaturesMissing(ex))
}
val response = sendAndReceive<Result>(notaryParty, SignRequest(stx))
@ -129,13 +132,22 @@ object NotaryFlow {
open fun beforeCommit(stx: SignedTransaction) {
}
/**
* A NotaryException is thrown if any of the states have been consumed by a different transaction. Note that
* this method does not throw an exception when input states are present multiple times within the transaction.
*/
private fun commitInputStates(tx: WireTransaction) {
try {
uniquenessProvider.commit(tx.inputs, tx.id, otherSide)
} catch (e: UniquenessException) {
val conflictData = e.error.serialize()
val signedConflict = SignedData(conflictData, sign(conflictData.bytes))
throw NotaryException(NotaryError.Conflict(tx, signedConflict))
val conflicts = tx.inputs.filterIndexed { i, stateRef ->
val consumingTx = e.error.stateHistory[stateRef]
consumingTx != null && consumingTx != UniquenessProvider.ConsumingTx(tx.id, i, otherSide)
}
if (conflicts.isNotEmpty()) {
// TODO: Create a new UniquenessException that only contains the conflicts filtered above.
throw notaryException(tx, e)
}
}
}
@ -143,6 +155,12 @@ object NotaryFlow {
val mySigningKey = serviceHub.notaryIdentityKey
return mySigningKey.signWithECDSA(bits)
}
private fun notaryException(tx: WireTransaction, e: UniquenessException): NotaryException {
val conflictData = e.error.serialize()
val signedConflict = SignedData(conflictData, sign(conflictData.bytes))
return NotaryException(NotaryError.Conflict(tx, signedConflict))
}
}
data class SignRequest(val tx: SignedTransaction)
@ -166,9 +184,10 @@ sealed class NotaryError {
/** Thrown if the time specified in the timestamp command is outside the allowed tolerance */
class TimestampInvalid : NotaryError()
class TransactionInvalid : NotaryError()
class TransactionInvalid(val msg: String) : NotaryError()
class SignaturesInvalid(val msg: String): NotaryError()
class SignaturesMissing(val missingSigners: Set<CompositeKey>) : NotaryError() {
override fun toString() = "Missing signatures from: ${missingSigners.map { it.toBase58String() }}"
class SignaturesMissing(val cause: SignedTransaction.SignaturesMissingException) : NotaryError() {
override fun toString() = cause.toString()
}
}

View File

@ -1,10 +1,7 @@
package net.corda.flows
import com.google.common.util.concurrent.ListenableFuture
import net.corda.core.messaging.MessagingService
import net.corda.core.messaging.SingleMessageRecipient
import net.corda.core.messaging.onNext
import net.corda.core.messaging.send
import net.corda.core.messaging.*
import net.corda.core.node.services.DEFAULT_SESSION_ID
/**
@ -21,7 +18,7 @@ interface ServiceRequestMessage {
*/
fun <R : Any> MessagingService.sendRequest(topic: String,
request: ServiceRequestMessage,
target: SingleMessageRecipient): ListenableFuture<R> {
target: MessageRecipients): ListenableFuture<R> {
val responseFuture = onNext<R>(topic, request.sessionID)
send(topic, DEFAULT_SESSION_ID, request, target)
return responseFuture

View File

@ -29,8 +29,8 @@ class ValidatingNotaryFlow(otherSide: Party,
wtx.toLedgerTransaction(serviceHub).verify()
} catch (e: Exception) {
when (e) {
is TransactionVerificationException,
is SignatureException -> throw NotaryException(NotaryError.TransactionInvalid())
is TransactionVerificationException -> NotaryException(NotaryError.TransactionInvalid(e.toString()))
is SignatureException -> throw NotaryException(NotaryError.SignaturesInvalid(e.toString()))
else -> throw e
}
}
@ -40,7 +40,7 @@ class ValidatingNotaryFlow(otherSide: Party,
try {
stx.verifySignatures(serviceHub.myInfo.notaryIdentity.owningKey)
} catch(e: SignedTransaction.SignaturesMissingException) {
throw NotaryException(NotaryError.SignaturesMissing(e.missing))
throw NotaryException(NotaryError.SignaturesMissing(e))
}
}

View File

@ -0,0 +1,47 @@
package net.corda.core
import org.assertj.core.api.Assertions.*
import org.junit.Test
import rx.subjects.PublishSubject
import java.util.*
class UtilsTest {
@Test
fun `toFuture - single item observable`() {
val subject = PublishSubject.create<String>()
val future = subject.toFuture()
subject.onNext("Hello")
assertThat(future.getOrThrow()).isEqualTo("Hello")
}
@Test
fun `toFuture - empty obserable`() {
val subject = PublishSubject.create<String>()
val future = subject.toFuture()
subject.onCompleted()
assertThatExceptionOfType(NoSuchElementException::class.java).isThrownBy {
future.getOrThrow()
}
}
@Test
fun `toFuture - more than one item observable`() {
val subject = PublishSubject.create<String>()
val future = subject.toFuture()
subject.onNext("Hello")
subject.onNext("World")
subject.onCompleted()
assertThat(future.getOrThrow()).isEqualTo("Hello")
}
@Test
fun `toFuture - erroring observable`() {
val subject = PublishSubject.create<String>()
val future = subject.toFuture()
val exception = Exception("Error")
subject.onError(exception)
assertThatThrownBy {
future.getOrThrow()
}.isSameAs(exception)
}
}

View File

@ -0,0 +1,26 @@
package net.corda.core.contracts
import org.junit.Test
import java.math.BigDecimal
import kotlin.test.assertEquals
/**
* Tests of the [Amount] class.
*/
class AmountTests {
@Test
fun basicCurrency() {
val expected = 1000L
val amount = Amount(expected, GBP)
assertEquals(expected, amount.quantity)
}
@Test
fun decimalConversion() {
val quantity = 1234L
val amount = Amount(quantity, GBP)
val expected = BigDecimal("12.34")
assertEquals(expected, amount.toDecimal())
assertEquals(amount, Amount.fromDecimal(amount.toDecimal(), amount.token))
}
}

View File

@ -12,32 +12,28 @@ import org.junit.Test
import java.time.Instant
import java.time.temporal.ChronoUnit
val TEST_TIMELOCK_ID = TransactionEncumbranceTests.TestTimeLock()
val TEST_TIMELOCK_ID = TransactionEncumbranceTests.DummyTimeLock()
class TransactionEncumbranceTests {
val defaultIssuer = MEGA_CORP.ref(1)
val encumberedState = Cash.State(
amount = 1000.DOLLARS `issued by` defaultIssuer,
owner = DUMMY_PUBKEY_1,
encumbrance = 1
)
val unencumberedState = Cash.State(
val state = Cash.State(
amount = 1000.DOLLARS `issued by` defaultIssuer,
owner = DUMMY_PUBKEY_1
)
val stateWithNewOwner = encumberedState.copy(owner = DUMMY_PUBKEY_2)
val stateWithNewOwner = state.copy(owner = DUMMY_PUBKEY_2)
val FOUR_PM = Instant.parse("2015-04-17T16:00:00.00Z")
val FIVE_PM = FOUR_PM.plus(1, ChronoUnit.HOURS)
val FIVE_PM_TIMELOCK = TestTimeLock.State(FIVE_PM)
val timeLock = DummyTimeLock.State(FIVE_PM)
class TestTimeLock : Contract {
override val legalContractReference = SecureHash.sha256("TestTimeLock")
class DummyTimeLock : Contract {
override val legalContractReference = SecureHash.sha256("DummyTimeLock")
override fun verify(tx: TransactionForContract) {
val timeLockInput = tx.inputs.filterIsInstance<State>().singleOrNull() ?: return
val time = tx.timestamp?.before ?: throw IllegalArgumentException("Transactions containing time-locks must be timestamped")
requireThat {
"the time specified in the time-lock has passed" by
(time >= tx.inputs.filterIsInstance<TestTimeLock.State>().single().validFrom)
"the time specified in the time-lock has passed" by (time >= timeLockInput.validFrom)
}
}
@ -50,110 +46,110 @@ class TransactionEncumbranceTests {
}
@Test
fun trivial() {
// A transaction containing an input state that is encumbered must fail if the encumbrance has not been presented
// on the input states.
transaction {
input { encumberedState }
output { unencumberedState }
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
this `fails with` "Missing required encumbrance 1 in INPUT"
fun `state can be encumbered`() {
ledger {
transaction {
input { state }
output(encumbrance = 1) { stateWithNewOwner }
output("5pm time-lock") { timeLock }
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
verifies()
}
}
// An encumbered state must not be encumbered by itself.
transaction {
input { unencumberedState }
input { unencumberedState }
output { unencumberedState }
// The encumbered state refers to an encumbrance in position 1, so what follows is wrong.
output { encumberedState }
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
this `fails with` "Missing required encumbrance 1 in OUTPUT"
}
// An encumbered state must not reference an index greater than the size of the output states.
// In this test, the output encumbered state refers to an encumbrance in position 1, but there is only one output.
transaction {
input { unencumberedState }
// The encumbered state refers to an encumbrance in position 1, so there should be at least 2 outputs.
output { encumberedState }
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
this `fails with` "Missing required encumbrance 1 in OUTPUT"
}
}
@Test
fun testEncumbranceEffects() {
// This test fails because the encumbered state is pointing to the ordinary cash state as the encumbrance,
// instead of the timelock by mistake, so when we try and use it the transaction fails as we didn't include the
// encumbrance cash state.
fun `state can transition if encumbrance rules are met`() {
ledger {
unverifiedTransaction {
output("state encumbered by 5pm time-lock") { encumberedState }
output { unencumberedState }
output("5pm time-lock") { FIVE_PM_TIMELOCK }
}
transaction {
input("state encumbered by 5pm time-lock")
input("5pm time-lock")
output { stateWithNewOwner }
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
timestamp(FIVE_PM)
this `fails with` "Missing required encumbrance 1 in INPUT"
}
}
// A transaction containing an input state that is encumbered must fail if the encumbrance is not in the correct
// transaction. In this test, the intended encumbrance is presented alongside the encumbered state for consumption,
// although the encumbered state always refers to the encumbrance produced in the same transaction, and the in this case
// the encumbrance was created in a separate transaction.
ledger {
unverifiedTransaction {
output("state encumbered by 5pm time-lock") { encumberedState }
output { unencumberedState }
}
unverifiedTransaction {
output("5pm time-lock") { FIVE_PM_TIMELOCK }
}
transaction {
input("state encumbered by 5pm time-lock")
input("5pm time-lock")
output { stateWithNewOwner }
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
timestamp(FIVE_PM)
this `fails with` "Missing required encumbrance 1 in INPUT"
}
}
// A transaction with an input state that is encumbered must succeed if the rules of the encumbrance are met.
ledger {
unverifiedTransaction {
output("state encumbered by 5pm time-lock") { encumberedState }
output("5pm time-lock") { FIVE_PM_TIMELOCK }
output("state encumbered by 5pm time-lock") { state }
output("5pm time-lock") { timeLock }
}
// Un-encumber the output if the time of the transaction is later than the timelock.
transaction {
input("state encumbered by 5pm time-lock")
input("5pm time-lock")
output { unencumberedState }
output { stateWithNewOwner }
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
timestamp(FIVE_PM)
this.verifies()
verifies()
}
}
// A transaction with an input state that is encumbered must fail if the rules of the encumbrance are not met.
// In this test, the time-lock encumbrance is being processed in a transaction before the time allowed.
}
@Test
fun `state cannot transition if the encumbrance contract fails to verify`() {
ledger {
unverifiedTransaction {
output("state encumbered by 5pm time-lock") { encumberedState }
output("5pm time-lock") { FIVE_PM_TIMELOCK }
output("state encumbered by 5pm time-lock") { state }
output("5pm time-lock") { timeLock }
}
// The time of the transaction is earlier than the time specified in the encumbering timelock.
transaction {
input("state encumbered by 5pm time-lock")
input("5pm time-lock")
output { unencumberedState }
output { state }
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
timestamp(FOUR_PM)
this `fails with` "the time specified in the time-lock has passed"
}
}
}
@Test
fun `state must be consumed along with its encumbrance`() {
ledger {
unverifiedTransaction {
output("state encumbered by 5pm time-lock", encumbrance = 1) { state }
output("5pm time-lock") { timeLock }
}
transaction {
input("state encumbered by 5pm time-lock")
output { stateWithNewOwner }
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
timestamp(FIVE_PM)
this `fails with` "Missing required encumbrance 1 in INPUT"
}
}
}
@Test
fun `state cannot be encumbered by itself`() {
transaction {
input { state }
output(encumbrance = 0) { stateWithNewOwner }
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
this `fails with` "Missing required encumbrance 0 in OUTPUT"
}
}
@Test
fun `encumbrance state index must be valid`() {
transaction {
input { state }
output(encumbrance = 2) { stateWithNewOwner }
output { timeLock }
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
this `fails with` "Missing required encumbrance 2 in OUTPUT"
}
}
@Test
fun `correct encumbrance state must be provided`() {
ledger {
unverifiedTransaction {
output("state encumbered by some other state", encumbrance = 1) { state }
output("some other state") { state }
output("5pm time-lock") { timeLock }
}
transaction {
input("state encumbered by some other state")
input("5pm time-lock")
output { stateWithNewOwner }
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
timestamp(FIVE_PM)
this `fails with` "Missing required encumbrance 1 in INPUT"
}
}
}
}

View File

@ -82,6 +82,33 @@ class TransactionTests {
transaction.type.verify(transaction)
}
@Test
fun `transaction verification fails for duplicate inputs`() {
val baseOutState = TransactionState(DummyContract.SingleOwnerState(0, ALICE_PUBKEY), DUMMY_NOTARY)
val stateRef = StateRef(SecureHash.randomSHA256(), 0)
val stateAndRef = StateAndRef(baseOutState, stateRef)
val inputs = listOf(stateAndRef, stateAndRef)
val outputs = listOf(baseOutState)
val commands = emptyList<AuthenticatedObject<CommandData>>()
val attachments = emptyList<Attachment>()
val id = SecureHash.randomSHA256()
val signers = listOf(DUMMY_NOTARY_KEY.public.composite)
val timestamp: Timestamp? = null
val transaction: LedgerTransaction = LedgerTransaction(
inputs,
outputs,
commands,
attachments,
id,
DUMMY_NOTARY,
signers,
timestamp,
TransactionType.General()
)
assertFailsWith<TransactionVerificationException.DuplicateInputStates> { transaction.type.verify(transaction) }
}
@Test
fun `general transactions cannot change notary`() {
val notary: Party = DUMMY_NOTARY

View File

@ -1,31 +0,0 @@
package net.corda.core.contracts.clauses
import net.corda.core.contracts.AuthenticatedObject
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.TransactionForContract
import net.corda.core.crypto.SecureHash
import org.junit.Test
import java.util.concurrent.atomic.AtomicInteger
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class AllCompositionTests {
@Test
fun minimal() {
val counter = AtomicInteger(0)
val clause = AllComposition(matchedClause(counter), matchedClause(counter))
val tx = TransactionForContract(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256())
verifyClause(tx, clause, emptyList<AuthenticatedObject<CommandData>>())
// Check that we've run the verify() function of two clauses
assertEquals(2, counter.get())
}
@Test
fun `not all match`() {
val clause = AllComposition(matchedClause(), unmatchedClause())
val tx = TransactionForContract(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256())
assertFailsWith<IllegalStateException> { verifyClause(tx, clause, emptyList<AuthenticatedObject<CommandData>>()) }
}
}

View File

@ -0,0 +1,31 @@
package net.corda.core.contracts.clauses
import net.corda.core.contracts.AuthenticatedObject
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.TransactionForContract
import net.corda.core.crypto.SecureHash
import org.junit.Test
import java.util.concurrent.atomic.AtomicInteger
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class AllOfTests {
@Test
fun minimal() {
val counter = AtomicInteger(0)
val clause = AllOf(matchedClause(counter), matchedClause(counter))
val tx = TransactionForContract(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256())
verifyClause(tx, clause, emptyList<AuthenticatedObject<CommandData>>())
// Check that we've run the verify() function of two clauses
assertEquals(2, counter.get())
}
@Test
fun `not all match`() {
val clause = AllOf(matchedClause(), unmatchedClause())
val tx = TransactionForContract(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256())
assertFailsWith<IllegalStateException> { verifyClause(tx, clause, emptyList<AuthenticatedObject<CommandData>>()) }
}
}

View File

@ -1,44 +0,0 @@
package net.corda.core.contracts.clauses
import net.corda.core.contracts.AuthenticatedObject
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.TransactionForContract
import net.corda.core.crypto.SecureHash
import org.junit.Test
import java.util.concurrent.atomic.AtomicInteger
import kotlin.test.assertEquals
class AnyCompositionTests {
@Test
fun minimal() {
val counter = AtomicInteger(0)
val clause = AnyComposition(matchedClause(counter), matchedClause(counter))
val tx = TransactionForContract(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256())
verifyClause(tx, clause, emptyList<AuthenticatedObject<CommandData>>())
// Check that we've run the verify() function of two clauses
assertEquals(2, counter.get())
}
@Test
fun `not all match`() {
val counter = AtomicInteger(0)
val clause = AnyComposition(matchedClause(counter), unmatchedClause(counter))
val tx = TransactionForContract(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256())
verifyClause(tx, clause, emptyList<AuthenticatedObject<CommandData>>())
// Check that we've run the verify() function of one clause
assertEquals(1, counter.get())
}
@Test
fun `none match`() {
val counter = AtomicInteger(0)
val clause = AnyComposition(unmatchedClause(counter), unmatchedClause(counter))
val tx = TransactionForContract(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256())
verifyClause(tx, clause, emptyList<AuthenticatedObject<CommandData>>())
// Check that we've run the verify() function of neither clause
assertEquals(0, counter.get())
}
}

View File

@ -0,0 +1,44 @@
package net.corda.core.contracts.clauses
import net.corda.core.contracts.AuthenticatedObject
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.TransactionForContract
import net.corda.core.crypto.SecureHash
import org.junit.Test
import java.util.concurrent.atomic.AtomicInteger
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class AnyOfTests {
@Test
fun minimal() {
val counter = AtomicInteger(0)
val clause = AnyOf(matchedClause(counter), matchedClause(counter))
val tx = TransactionForContract(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256())
verifyClause(tx, clause, emptyList<AuthenticatedObject<CommandData>>())
// Check that we've run the verify() function of two clauses
assertEquals(2, counter.get())
}
@Test
fun `not all match`() {
val counter = AtomicInteger(0)
val clause = AnyOf(matchedClause(counter), unmatchedClause(counter))
val tx = TransactionForContract(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256())
verifyClause(tx, clause, emptyList<AuthenticatedObject<CommandData>>())
// Check that we've run the verify() function of one clause
assertEquals(1, counter.get())
}
@Test
fun `none match`() {
val counter = AtomicInteger(0)
val clause = AnyOf(unmatchedClause(counter), unmatchedClause(counter))
val tx = TransactionForContract(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256())
assertFailsWith(IllegalArgumentException::class) {
verifyClause(tx, clause, emptyList<AuthenticatedObject<CommandData>>())
}
}
}

View File

@ -7,6 +7,7 @@ import net.corda.core.contracts.TransactionForContract
import java.util.concurrent.atomic.AtomicInteger
internal fun matchedClause(counter: AtomicInteger? = null) = object : Clause<ContractState, CommandData, Unit>() {
override val requiredCommands: Set<Class<out CommandData>> = emptySet()
override fun verify(tx: TransactionForContract,
inputs: List<ContractState>,
outputs: List<ContractState>,

View File

@ -1,205 +0,0 @@
package net.corda.core.crypto
import org.junit.BeforeClass
import org.junit.Test
import java.net.Socket
import java.security.KeyStore
import java.security.cert.CertificateException
import java.security.cert.X509Certificate
import javax.net.ssl.SSLEngine
import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.X509ExtendedTrustManager
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
// TODO: This suppress is needed due to KT-260, fixed in Kotlin 1.0.4 so remove after upgrading.
@Suppress("CAST_NEVER_SUCCEEDS")
class WhitelistTrustManagerTest {
companion object {
@BeforeClass
@JvmStatic
fun registerTrustManager() {
// Validate original factory
assertEquals("PKIX", TrustManagerFactory.getDefaultAlgorithm())
//register for all tests
registerWhitelistTrustManager()
}
}
private fun getTrustmanagerAndCert(whitelist: String, certificateName: String): Pair<X509ExtendedTrustManager, X509Certificate> {
WhitelistTrustManagerProvider.addWhitelistEntry(whitelist)
val caCertAndKey = X509Utilities.createSelfSignedCACert(certificateName)
val keyStore = KeyStore.getInstance(KeyStore.getDefaultType())
keyStore.load(null, null)
keyStore.setCertificateEntry("cacert", caCertAndKey.certificate)
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
trustManagerFactory.init(keyStore)
return Pair(trustManagerFactory.trustManagers.first() as X509ExtendedTrustManager, caCertAndKey.certificate)
}
private fun getTrustmanagerAndUntrustedChainCert(): Pair<X509ExtendedTrustManager, X509Certificate> {
WhitelistTrustManagerProvider.addWhitelistEntry("test.r3corda.com")
val otherCaCertAndKey = X509Utilities.createSelfSignedCACert("bad root")
val caCertAndKey = X509Utilities.createSelfSignedCACert("good root")
val subject = X509Utilities.getDevX509Name("test.r3corda.com")
val serverKey = X509Utilities.generateECDSAKeyPairForSSL()
val serverCert = X509Utilities.createServerCert(subject,
serverKey.public,
otherCaCertAndKey,
listOf(),
listOf())
val keyStore = KeyStore.getInstance(KeyStore.getDefaultType())
keyStore.load(null, null)
keyStore.setCertificateEntry("cacert", caCertAndKey.certificate)
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
trustManagerFactory.init(keyStore)
return Pair(trustManagerFactory.trustManagers.first() as X509ExtendedTrustManager, serverCert)
}
@Test
fun `getDefaultAlgorithm TrustManager is WhitelistTrustManager`() {
registerWhitelistTrustManager() // Check double register is safe
assertEquals("whitelistTrustManager", TrustManagerFactory.getDefaultAlgorithm())
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
trustManagerFactory.init(null as KeyStore?)
val trustManagers = trustManagerFactory.trustManagers
assertTrue { trustManagers.all { it is WhitelistTrustManager } }
}
@Test
fun `check certificate works for whitelisted certificate and specific domain`() {
val (trustManager, cert) = getTrustmanagerAndCert("test.r3corda.com", "test.r3corda.com")
trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM)
trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as Socket?)
trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as SSLEngine?)
trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM)
trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as Socket?)
trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as SSLEngine?)
}
@Test
fun `check certificate works for specific certificate and wildcard permitted domain`() {
val (trustManager, cert) = getTrustmanagerAndCert("*.r3corda.com", "test.r3corda.com")
trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM)
trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as Socket?)
trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as SSLEngine?)
trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM)
trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as Socket?)
trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as SSLEngine?)
}
@Test
fun `check certificate works for wildcard certificate and non wildcard domain`() {
val (trustManager, cert) = getTrustmanagerAndCert("*.r3corda.com", "test.r3corda.com")
trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM)
trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as Socket?)
trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as SSLEngine?)
trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM)
trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as Socket?)
trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as SSLEngine?)
}
@Test
fun `check unknown certificate rejected`() {
val (trustManager, cert) = getTrustmanagerAndCert("test.r3corda.com", "test.notr3.com")
assertFailsWith<CertificateException> { trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM) }
assertFailsWith<CertificateException> { trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as Socket?) }
assertFailsWith<CertificateException> { trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as SSLEngine?) }
assertFailsWith<CertificateException> { trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM) }
assertFailsWith<CertificateException> { trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as Socket?) }
assertFailsWith<CertificateException> { trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as SSLEngine?) }
}
@Test
fun `check unknown wildcard certificate rejected`() {
val (trustManager, cert) = getTrustmanagerAndCert("test.r3corda.com", "*.notr3.com")
assertFailsWith<CertificateException> { trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM) }
assertFailsWith<CertificateException> { trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as Socket?) }
assertFailsWith<CertificateException> { trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as SSLEngine?) }
assertFailsWith<CertificateException> { trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM) }
assertFailsWith<CertificateException> { trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as Socket?) }
assertFailsWith<CertificateException> { trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as SSLEngine?) }
}
@Test
fun `check unknown certificate rejected against mismatched wildcard`() {
val (trustManager, cert) = getTrustmanagerAndCert("*.r3corda.com", "test.notr3.com")
assertFailsWith<CertificateException> { trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM) }
assertFailsWith<CertificateException> { trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as Socket?) }
assertFailsWith<CertificateException> { trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as SSLEngine?) }
assertFailsWith<CertificateException> { trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM) }
assertFailsWith<CertificateException> { trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as Socket?) }
assertFailsWith<CertificateException> { trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as SSLEngine?) }
}
@Test
fun `check certificate signed by untrusted root is still rejected, despite matched name`() {
val (trustManager, cert) = getTrustmanagerAndUntrustedChainCert()
assertFailsWith<CertificateException> { trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM) }
assertFailsWith<CertificateException> { trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as Socket?) }
assertFailsWith<CertificateException> { trustManager.checkServerTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as SSLEngine?) }
assertFailsWith<CertificateException> { trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM) }
assertFailsWith<CertificateException> { trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as Socket?) }
assertFailsWith<CertificateException> { trustManager.checkClientTrusted(arrayOf(cert), X509Utilities.SIGNATURE_ALGORITHM, null as SSLEngine?) }
}
}

View File

@ -46,7 +46,7 @@ class VaultUpdateTests {
@Test
fun `something plus nothing is something`() {
val before = Vault.Update(setOf(stateRef0, stateRef1), setOf(stateAndRef2, stateAndRef3))
val before = Vault.Update(setOf(stateAndRef0, stateAndRef1), setOf(stateAndRef2, stateAndRef3))
val after = before + Vault.NoUpdate
assertEquals(before, after)
}
@ -54,32 +54,32 @@ class VaultUpdateTests {
@Test
fun `nothing plus something is something`() {
val before = Vault.NoUpdate
val after = before + Vault.Update(setOf(stateRef0, stateRef1), setOf(stateAndRef2, stateAndRef3))
val expected = Vault.Update(setOf(stateRef0, stateRef1), setOf(stateAndRef2, stateAndRef3))
val after = before + Vault.Update(setOf(stateAndRef0, stateAndRef1), setOf(stateAndRef2, stateAndRef3))
val expected = Vault.Update(setOf(stateAndRef0, stateAndRef1), setOf(stateAndRef2, stateAndRef3))
assertEquals(expected, after)
}
@Test
fun `something plus consume state 0 is something without state 0 output`() {
val before = Vault.Update(setOf(stateRef2, stateRef3), setOf(stateAndRef0, stateAndRef1))
val after = before + Vault.Update(setOf(stateRef0), setOf())
val expected = Vault.Update(setOf(stateRef2, stateRef3), setOf(stateAndRef1))
val before = Vault.Update(setOf(stateAndRef2, stateAndRef3), setOf(stateAndRef0, stateAndRef1))
val after = before + Vault.Update(setOf(stateAndRef0), setOf())
val expected = Vault.Update(setOf(stateAndRef2, stateAndRef3), setOf(stateAndRef1))
assertEquals(expected, after)
}
@Test
fun `something plus produce state 4 is something with additional state 4 output`() {
val before = Vault.Update(setOf(stateRef2, stateRef3), setOf(stateAndRef0, stateAndRef1))
val before = Vault.Update(setOf(stateAndRef2, stateAndRef3), setOf(stateAndRef0, stateAndRef1))
val after = before + Vault.Update(setOf(), setOf(stateAndRef4))
val expected = Vault.Update(setOf(stateRef2, stateRef3), setOf(stateAndRef0, stateAndRef1, stateAndRef4))
val expected = Vault.Update(setOf(stateAndRef2, stateAndRef3), setOf(stateAndRef0, stateAndRef1, stateAndRef4))
assertEquals(expected, after)
}
@Test
fun `something plus consume states 0 and 1, and produce state 4, is something without state 0 and 1 outputs and only state 4 output`() {
val before = Vault.Update(setOf(stateRef2, stateRef3), setOf(stateAndRef0, stateAndRef1))
val after = before + Vault.Update(setOf(stateRef0, stateRef1), setOf(stateAndRef4))
val expected = Vault.Update(setOf(stateRef2, stateRef3), setOf(stateAndRef4))
val before = Vault.Update(setOf(stateAndRef2, stateAndRef3), setOf(stateAndRef0, stateAndRef1))
val after = before + Vault.Update(setOf(stateAndRef0, stateAndRef1), setOf(stateAndRef4))
val expected = Vault.Update(setOf(stateAndRef2, stateAndRef3), setOf(stateAndRef4))
assertEquals(expected, after)
}
}

View File

@ -69,7 +69,7 @@ class TransactionSerializationTests {
signedTX.verifySignatures()
// Corrupt the data and ensure the signature catches the problem.
signedTX.id.bytes[5] = 0
signedTX.id.bytes[5] = signedTX.id.bytes[5].inc()
assertFailsWith(SignatureException::class) {
signedTX.verifySignatures()
}

Binary file not shown.

BIN
docs/build/doctrees/azure-vm.doctree vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More