Merge remote-tracking branch 'origin/release/os/4.6' into christians/update-fb-2020-06-12

This commit is contained in:
Christian Sailer 2020-06-12 14:51:43 +01:00
commit d00dc42b18
83 changed files with 424 additions and 5641 deletions

View File

@ -0,0 +1,9 @@
FROM azul/zulu-openjdk:11
RUN apt-get update && apt-get install -y curl apt-transport-https \
ca-certificates \
curl \
gnupg2 \
software-properties-common \
wget
ARG USER="stresstester"
RUN useradd -m ${USER}

View File

@ -0,0 +1,38 @@
@Library('corda-shared-build-pipeline-steps')
import static com.r3.build.BuildControl.killAllExistingBuildsForJob
killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger())
pipeline {
agent {
dockerfile {
label 'k8s'
additionalBuildArgs "--build-arg USER=stresstester"
filename '.ci/dev/compatibility/DockerfileJDK11Compile'
}
}
options {
timestamps()
timeout(time: 3, unit: 'HOURS')
}
stages {
stage('JDK 11 Compile') {
steps {
sh "./gradlew --no-daemon -Pcompilation.allWarningsAsErrors=true -Ptests.failFast=false " +
"-Ptests.ignoreFailures=true clean compileAll --stacktrace"
}
}
stage('Deploy nodes') {
steps {
sh "./gradlew --no-daemon deployNodes"
}
}
}
post {
cleanup {
deleteDir() /* clean up our workspace */
}
}
}

View File

@ -31,7 +31,7 @@ pipeline {
"-Ddocker.push.password=\"\${DOCKER_PUSH_PWD}\" " + "-Ddocker.push.password=\"\${DOCKER_PUSH_PWD}\" " +
"-Ddocker.work.dir=\"/tmp/\${EXECUTOR_NUMBER}\" " + "-Ddocker.work.dir=\"/tmp/\${EXECUTOR_NUMBER}\" " +
"-Ddocker.build.tag=\"\${DOCKER_TAG_TO_USE}\"" + "-Ddocker.build.tag=\"\${DOCKER_TAG_TO_USE}\"" +
" clean pushBuildImage --stacktrace" " clean jar deployNodes install pushBuildImage --stacktrace"
} }
sh "kubectl auth can-i get pods" sh "kubectl auth can-i get pods"
} }

View File

@ -23,7 +23,7 @@ pipeline {
"-Ddocker.push.password=\"\${DOCKER_PUSH_PWD}\" " + "-Ddocker.push.password=\"\${DOCKER_PUSH_PWD}\" " +
"-Ddocker.work.dir=\"/tmp/\${EXECUTOR_NUMBER}\" " + "-Ddocker.work.dir=\"/tmp/\${EXECUTOR_NUMBER}\" " +
"-Ddocker.build.tag=\"\${DOCKER_TAG_TO_USE}\"" + "-Ddocker.build.tag=\"\${DOCKER_TAG_TO_USE}\"" +
" clean pushBuildImage --stacktrace" " clean jar deployNodes install pushBuildImage --stacktrace"
} }
sh "kubectl auth can-i get pods" sh "kubectl auth can-i get pods"
} }
@ -57,36 +57,6 @@ pipeline {
" allParallelSlowIntegrationTest --stacktrace" " allParallelSlowIntegrationTest --stacktrace"
} }
} }
stage('Generate sonarqube report') {
steps {
script {
try {
// running this step here is the only way to not majorly affect the distributed test plugin,
// as now that neither returns build files nor runs jacoco reports
sh "./gradlew --no-daemon build jacocoRootReport --stacktrace"
withSonarQubeEnv('sq01') {
sh "./gradlew --no-daemon sonarqube -x test --stacktrace"
}
timeout(time: 3, unit: 'MINUTES') {
script {
try {
def qg = waitForQualityGate();
if (qg.status != 'OK') {
error "Pipeline aborted due to quality gate failure: ${qg.status}"
}
} catch (org.jenkinsci.plugins.workflow.steps.FlowInterruptedException e) {
println('No sonarqube webhook response within timeout. Please check the webhook configuration in sonarqube.')
// continue the pipeline
}
}
}
} catch (err) {
println('Error while trying to execute sonarqube analysis, will be skipped.')
}
}
}
}
} }
} }
} }

14
.github/workflows/check-pr-title.yml vendored Normal file
View File

@ -0,0 +1,14 @@
name: 'PR title check'
on:
pull_request:
types: [opened, edited, reopened]
jobs:
check-pr-title:
runs-on: ubuntu-latest
steps:
- uses: morrisoncole/pr-lint-action@v1.1.1
with:
title-regex: '^((CORDA|EG|ENT|INFRA)-\d+|NOTICK)(.*)'
on-failed-regex-comment: "PR title failed to match regex -> `%regex%`"
repo-token: "${{ secrets.GITHUB_TOKEN }}"

2
Jenkinsfile vendored
View File

@ -27,7 +27,7 @@ pipeline {
"-Ddocker.push.password=\"\${DOCKER_PUSH_PWD}\" " + "-Ddocker.push.password=\"\${DOCKER_PUSH_PWD}\" " +
"-Ddocker.work.dir=\"/tmp/\${EXECUTOR_NUMBER}\" " + "-Ddocker.work.dir=\"/tmp/\${EXECUTOR_NUMBER}\" " +
"-Ddocker.build.tag=\"\${DOCKER_TAG_TO_USE}\"" + "-Ddocker.build.tag=\"\${DOCKER_TAG_TO_USE}\"" +
" clean pushBuildImage preAllocateForAllParallelIntegrationTest preAllocateForAllParallelIntegrationTest --stacktrace" " clean jar deployNodes pushBuildImage preAllocateForAllParallelIntegrationTest preAllocateForAllParallelIntegrationTest --stacktrace"
} }
sh "kubectl auth can-i get pods" sh "kubectl auth can-i get pods"
} }

View File

@ -293,4 +293,27 @@ class CordaRPCClientReconnectionTest {
.isInstanceOf(RPCException::class.java) .isInstanceOf(RPCException::class.java)
} }
} }
@Test(timeout=300_000)
fun `rpc client does not attempt to reconnect after shutdown`() {
driver(DriverParameters(cordappsForAllNodes = emptyList())) {
val address = NetworkHostAndPort("localhost", portAllocator.nextPort())
fun startNode(): NodeHandle {
return startNode(
providedName = CHARLIE_NAME,
rpcUsers = listOf(CordaRPCClientTest.rpcUser),
customOverrides = mapOf("rpcSettings.address" to address.toString())
).getOrThrow()
}
val node = startNode()
val client = CordaRPCClient(node.rpcAddress, config)
(client.start(rpcUser.username, rpcUser.password, gracefulReconnect = gracefulReconnect)).use {
val rpcOps = it.proxy as ReconnectingCordaRPCOps
rpcOps.shutdown()
// If we get here we know we're not stuck in a reconnect cycle with a node that's been shut down
assertThat(rpcOps.reconnectingRPCConnection.isClosed())
}
}
}
} }

View File

@ -311,13 +311,18 @@ class ReconnectingCordaRPCOps private constructor(
checkIfClosed() checkIfClosed()
var remainingAttempts = maxNumberOfAttempts var remainingAttempts = maxNumberOfAttempts
var lastException: Throwable? = null var lastException: Throwable? = null
while (remainingAttempts != 0) { while (remainingAttempts != 0 && !reconnectingRPCConnection.isClosed()) {
try { try {
log.debug { "Invoking RPC $method..." } log.debug { "Invoking RPC $method..." }
return method.invoke(reconnectingRPCConnection.proxy, *(args ?: emptyArray())).also { return method.invoke(reconnectingRPCConnection.proxy, *(args ?: emptyArray())).also {
log.debug { "RPC $method invoked successfully." } log.debug { "RPC $method invoked successfully." }
} }
} catch (e: InvocationTargetException) { } catch (e: InvocationTargetException) {
if (method.name.equals("shutdown", true)) {
log.debug("Shutdown invoked, stop reconnecting.", e)
reconnectingRPCConnection.notifyServerAndClose()
break
}
when (e.targetException) { when (e.targetException) {
is RejectedCommandException -> { is RejectedCommandException -> {
log.warn("Node is being shutdown. Operation ${method.name} rejected. Retrying when node is up...", e) log.warn("Node is being shutdown. Operation ${method.name} rejected. Retrying when node is up...", e)
@ -349,6 +354,7 @@ class ReconnectingCordaRPCOps private constructor(
} }
} }
if (reconnectingRPCConnection.isClosed()) return null
throw MaxRpcRetryException(maxNumberOfAttempts, method, lastException) throw MaxRpcRetryException(maxNumberOfAttempts, method, lastException)
} }

View File

@ -1,4 +1,4 @@
errorTemplate = Target versionId attribute {0} not specified. Please specify a whole number starting from 1. errorTemplate = Version identifier {0} was not specified. Please specify a whole number starting from 1.
shortDescription = A required version attribute was not specified in the manifest of the CorDapp JAR. shortDescription = A required version attribute was not specified in the manifest of the CorDapp JAR.
actionsToFix = Investigate the logs to find out which version attribute was not specified, and add that version attribute to the CorDapp manifest. actionsToFix = Investigate the logs to find out which version attribute was not specified, and add that version attribute to the CorDapp manifest.
aliases = 1nskd37 aliases = 1nskd37

View File

@ -1,3 +1,3 @@
errorTemplate = Target versionId attribute {0} not specified. Please specify a whole number starting from 1. errorTemplate = Version identifier {0} was not specified. Please specify a whole number starting from 1.
shortDescription = A required version attribute was not specified in the manifest of the CorDapp JAR. shortDescription = A required version attribute was not specified in the manifest of the CorDapp JAR.
actionsToFix = Investigate the logs to find out which version attribute was not specified, and add that version attribute to the CorDapp manifest. actionsToFix = Investigate the logs to find out which version attribute was not specified, and add that version attribute to the CorDapp manifest.

View File

@ -0,0 +1,5 @@
# Code examples for the documentation
The code-examples directory has been removed as the documentation has been moved to its own repo (see [here](../README.md)).
If you're looking for this code, look at the history of this document, it was introduced in the same commit as the code was removed.

View File

@ -1,112 +0,0 @@
apply plugin: 'kotlin'
apply plugin: 'application'
apply plugin: 'net.corda.plugins.cordformation'
apply plugin: 'net.corda.plugins.quasar-utils'
configurations {
integrationTestCompile.extendsFrom testCompile
integrationTestRuntime.extendsFrom testRuntime
compile {
// We already have a SLF4J implementation on our runtime classpath,
// and we don't need another one.
exclude group: "org.apache.logging.log4j"
}
}
sourceSets {
integrationTest {
kotlin {
compileClasspath += main.output + test.output
runtimeClasspath += main.output + test.output
srcDir file('src/integration-test/kotlin')
}
java {
compileClasspath += main.output + test.output
runtimeClasspath += main.output + test.output
srcDir file('src/integration-test/java')
}
}
}
compileTestJava.dependsOn tasks.getByPath(':node:capsule:buildCordaJAR')
dependencies {
// Cordformation needs a SLF4J implementation when executing the Network
// Bootstrapper, but Log4J doesn't shutdown completely from within Gradle.
// Use a much simpler SLF4J implementation here instead.
cordaRuntime "org.slf4j:slf4j-simple:$slf4j_version"
compile project(':core')
compile project(':client:jfx')
compile project(':node-driver')
compile project(':testing:testserver')
testCompile project(':test-utils')
compile "org.graphstream:gs-core:1.3"
compile("org.graphstream:gs-ui:1.3") {
exclude group: "bouncycastle"
exclude group: "junit"
}
cordaRuntime project(path: ":node:capsule", configuration: 'runtimeArtifacts')
cordaRuntime project(path: ":testing:testserver:testcapsule:", configuration: 'runtimeArtifacts')
// CorDapps: dependent flows and services
compile project(':finance:contracts')
compile project(':finance:workflows')
}
mainClassName = "net.corda.docs.ClientRpcTutorialKt"
task getClientRpcTutorial(type: CreateStartScripts) {
dependsOn(classes)
mainClassName = "net.corda.docs.ClientRpcTutorialKt"
applicationName = "client-rpc-tutorial"
defaultJvmOpts = []
outputDir = new File(project.buildDir, 'scripts')
classpath = jar.outputs.files + project.configurations.runtime
}
applicationDistribution.into("bin") {
from(getClientRpcTutorial)
fileMode = 0755
}
task integrationTest(type: Test) {
testClassesDirs = sourceSets.integrationTest.output.classesDirs
classpath = sourceSets.integrationTest.runtimeClasspath
}
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
directory "./build/nodes"
node {
name "O=Notary Service,OU=corda,L=London,C=GB"
notary = [validating : true]
p2pPort 10002
rpcSettings {
address "localhost:10003"
adminAddress "localhost:10013"
}
webPort 10004
extraConfig = ['h2Settings.address' : 'localhost:10014']
cordapps = []
}
node {
name "O=Alice Corp,L=London,C=GB"
p2pPort 10005
rpcSettings {
address "localhost:10006"
adminAddress "localhost:10016"
}
webPort 10007
extraConfig = ['h2Settings.address' : 'localhost:10017']
cordapps = []
rpcUsers = [
['username' : "user",
'password' : "password",
'permissions' : ["StartFlow.net.corda.finance.flows.CashFlow"]]
]
}
}

View File

@ -1,125 +0,0 @@
package net.corda.docs.java.tutorial.test;
import net.corda.client.rpc.CordaRPCClient;
import net.corda.core.concurrent.CordaFuture;
import net.corda.core.contracts.Amount;
import net.corda.core.contracts.Issued;
import net.corda.core.contracts.Structures;
import net.corda.core.messaging.CordaRPCOps;
import net.corda.core.node.services.Vault;
import net.corda.core.utilities.OpaqueBytes;
import net.corda.finance.contracts.asset.Cash;
import net.corda.finance.flows.CashIssueAndPaymentFlow;
import net.corda.finance.flows.CashPaymentFlow;
import net.corda.testing.driver.DriverParameters;
import net.corda.testing.driver.NodeHandle;
import net.corda.testing.driver.NodeParameters;
import net.corda.testing.node.User;
import org.junit.Test;
import rx.Observable;
import java.util.Currency;
import java.util.HashSet;
import java.util.List;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static net.corda.finance.Currencies.DOLLARS;
import static net.corda.node.services.Permissions.invokeRpc;
import static net.corda.node.services.Permissions.startFlow;
import static net.corda.testing.core.ExpectKt.expect;
import static net.corda.testing.core.ExpectKt.expectEvents;
import static net.corda.testing.core.TestConstants.ALICE_NAME;
import static net.corda.testing.core.TestConstants.BOB_NAME;
import static net.corda.testing.driver.Driver.driver;
import static net.corda.testing.node.internal.InternalTestUtilsKt.FINANCE_CORDAPPS;
import static org.junit.Assert.assertEquals;
public class JavaIntegrationTestingTutorial {
@Test
public void aliceBobCashExchangeExample() {
// START 1
driver(new DriverParameters()
.withStartNodesInProcess(true)
.withCordappsForAllNodes(FINANCE_CORDAPPS), dsl -> {
User aliceUser = new User("aliceUser", "testPassword1", new HashSet<>(asList(
startFlow(CashIssueAndPaymentFlow.class),
invokeRpc("vaultTrack")
)));
User bobUser = new User("bobUser", "testPassword2", new HashSet<>(asList(
startFlow(CashPaymentFlow.class),
invokeRpc("vaultTrack")
)));
try {
List<CordaFuture<NodeHandle>> nodeHandleFutures = asList(
dsl.startNode(new NodeParameters().withProvidedName(ALICE_NAME).withRpcUsers(singletonList(aliceUser))),
dsl.startNode(new NodeParameters().withProvidedName(BOB_NAME).withRpcUsers(singletonList(bobUser)))
);
NodeHandle alice = nodeHandleFutures.get(0).get();
NodeHandle bob = nodeHandleFutures.get(1).get();
// END 1
// START 2
CordaRPCClient aliceClient = new CordaRPCClient(alice.getRpcAddress());
CordaRPCOps aliceProxy = aliceClient.start("aliceUser", "testPassword1").getProxy();
CordaRPCClient bobClient = new CordaRPCClient(bob.getRpcAddress());
CordaRPCOps bobProxy = bobClient.start("bobUser", "testPassword2").getProxy();
// END 2
// START 3
Observable<Vault.Update<Cash.State>> bobVaultUpdates = bobProxy.vaultTrack(Cash.State.class).getUpdates();
Observable<Vault.Update<Cash.State>> aliceVaultUpdates = aliceProxy.vaultTrack(Cash.State.class).getUpdates();
// END 3
// START 4
OpaqueBytes issueRef = OpaqueBytes.of((byte)0);
aliceProxy.startFlowDynamic(
CashIssueAndPaymentFlow.class,
DOLLARS(1000),
issueRef,
bob.getNodeInfo().getLegalIdentities().get(0),
true,
dsl.getDefaultNotaryIdentity()
).getReturnValue().get();
@SuppressWarnings("unchecked")
Class<Vault.Update<Cash.State>> cashVaultUpdateClass = (Class<Vault.Update<Cash.State>>)(Class<?>)Vault.Update.class;
expectEvents(bobVaultUpdates, true, () ->
expect(cashVaultUpdateClass, update -> true, update -> {
System.out.println("Bob got vault update of " + update);
Amount<Issued<Currency>> amount = update.getProduced().iterator().next().getState().getData().getAmount();
assertEquals(DOLLARS(1000), Structures.withoutIssuer(amount));
return null;
})
);
// END 4
// START 5
bobProxy.startFlowDynamic(
CashPaymentFlow.class,
DOLLARS(1000),
alice.getNodeInfo().getLegalIdentities().get(0)
).getReturnValue().get();
expectEvents(aliceVaultUpdates, true, () ->
expect(cashVaultUpdateClass, update -> true, update -> {
System.out.println("Alice got vault update of " + update);
Amount<Issued<Currency>> amount = update.getProduced().iterator().next().getState().getData().getAmount();
assertEquals(DOLLARS(1000), Structures.withoutIssuer(amount));
return null;
})
);
// END 5
} catch (Exception e) {
throw new RuntimeException("Exception thrown in driver DSL", e);
}
return null;
});
}
}

View File

@ -1,41 +0,0 @@
package net.corda.docs.java.tutorial.test;
import net.corda.client.rpc.CordaRPCClient;
import net.corda.core.messaging.CordaRPCOps;
import net.corda.core.utilities.KotlinUtilsKt;
import net.corda.docs.java.tutorial.flowstatemachines.ExampleSummingFlow;
import net.corda.node.services.Permissions;
import net.corda.testing.driver.*;
import net.corda.testing.node.User;
import org.junit.Test;
import java.util.concurrent.Future;
import static java.util.Collections.singleton;
import static java.util.Collections.singletonList;
import static net.corda.testing.core.TestConstants.ALICE_NAME;
import static net.corda.testing.driver.Driver.driver;
import static net.corda.testing.node.internal.InternalTestUtilsKt.cordappWithPackages;
import static org.junit.Assert.assertEquals;
public class TutorialFlowAsyncOperationTest {
// DOCSTART summingWorks
@Test
public void summingWorks() {
driver(new DriverParameters(singletonList(cordappWithPackages("net.corda.docs.java.tutorial.flowstatemachines"))), (DriverDSL dsl) -> {
User aliceUser = new User("aliceUser", "testPassword1", singleton(Permissions.all()));
Future<NodeHandle> aliceFuture = dsl.startNode(new NodeParameters()
.withProvidedName(ALICE_NAME)
.withRpcUsers(singletonList(aliceUser))
);
NodeHandle alice = KotlinUtilsKt.getOrThrow(aliceFuture, null);
CordaRPCClient aliceClient = new CordaRPCClient(alice.getRpcAddress());
CordaRPCOps aliceProxy = aliceClient.start("aliceUser", "testPassword1").getProxy();
Future<Integer> answerFuture = aliceProxy.startFlowDynamic(ExampleSummingFlow.class).getReturnValue();
int answer = KotlinUtilsKt.getOrThrow(answerFuture, null);
assertEquals(3, answer);
return null;
});
}
// DOCEND summingWorks
}

View File

@ -1,95 +0,0 @@
package net.corda.docs.kotlin.tutorial.test
import net.corda.client.rpc.CordaRPCClient
import net.corda.core.contracts.Amount
import net.corda.core.contracts.Issued
import net.corda.core.contracts.withoutIssuer
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.startFlow
import net.corda.core.messaging.vaultTrackBy
import net.corda.core.node.services.Vault
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
import net.corda.finance.DOLLARS
import net.corda.finance.contracts.asset.Cash
import net.corda.finance.flows.CashIssueAndPaymentFlow
import net.corda.finance.flows.CashPaymentFlow
import net.corda.node.services.Permissions.Companion.invokeRpc
import net.corda.node.services.Permissions.Companion.startFlow
import net.corda.testing.core.*
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.driver
import net.corda.testing.node.User
import net.corda.testing.node.internal.FINANCE_CORDAPPS
import org.junit.Test
import rx.Observable
import java.util.*
import kotlin.test.assertEquals
class KotlinIntegrationTestingTutorial {
@Test(timeout=300_000)
fun `alice bob cash exchange example`() {
// START 1
driver(DriverParameters(startNodesInProcess = true, cordappsForAllNodes = FINANCE_CORDAPPS)) {
val aliceUser = User("aliceUser", "testPassword1", permissions = setOf(
startFlow<CashIssueAndPaymentFlow>(),
invokeRpc("vaultTrackBy")
))
val bobUser = User("bobUser", "testPassword2", permissions = setOf(
startFlow<CashPaymentFlow>(),
invokeRpc("vaultTrackBy")
))
val (alice, bob) = listOf(
startNode(providedName = ALICE_NAME, rpcUsers = listOf(aliceUser)),
startNode(providedName = BOB_NAME, rpcUsers = listOf(bobUser))
).map { it.getOrThrow() }
// END 1
// START 2
val aliceClient = CordaRPCClient(alice.rpcAddress)
val aliceProxy: CordaRPCOps = aliceClient.start("aliceUser", "testPassword1").proxy
val bobClient = CordaRPCClient(bob.rpcAddress)
val bobProxy: CordaRPCOps = bobClient.start("bobUser", "testPassword2").proxy
// END 2
// START 3
val bobVaultUpdates: Observable<Vault.Update<Cash.State>> = bobProxy.vaultTrackBy<Cash.State>().updates
val aliceVaultUpdates: Observable<Vault.Update<Cash.State>> = aliceProxy.vaultTrackBy<Cash.State>().updates
// END 3
// START 4
val issueRef = OpaqueBytes.of(0)
aliceProxy.startFlow(::CashIssueAndPaymentFlow,
1000.DOLLARS,
issueRef,
bob.nodeInfo.singleIdentity(),
true,
defaultNotaryIdentity
).returnValue.getOrThrow()
bobVaultUpdates.expectEvents {
expect { update ->
println("Bob got vault update of $update")
val amount: Amount<Issued<Currency>> = update.produced.first().state.data.amount
assertEquals(1000.DOLLARS, amount.withoutIssuer())
}
}
// END 4
// START 5
bobProxy.startFlow(::CashPaymentFlow, 1000.DOLLARS, alice.nodeInfo.singleIdentity()).returnValue.getOrThrow()
aliceVaultUpdates.expectEvents {
expect { update ->
println("Alice got vault update of $update")
val amount: Amount<Issued<Currency>> = update.produced.first().state.data.amount
assertEquals(1000.DOLLARS, amount.withoutIssuer())
}
}
// END 5
}
}
}

View File

@ -1,30 +0,0 @@
package net.corda.docs.kotlin.tutorial.test
import net.corda.client.rpc.CordaRPCClient
import net.corda.core.messaging.startFlow
import net.corda.core.utilities.getOrThrow
import net.corda.docs.kotlin.tutorial.flowstatemachines.ExampleSummingFlow
import net.corda.node.services.Permissions
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.driver
import net.corda.testing.node.User
import net.corda.testing.node.internal.cordappWithPackages
import org.junit.Test
import kotlin.test.assertEquals
class TutorialFlowAsyncOperationTest {
// DOCSTART summingWorks
@Test(timeout=300_000)
fun summingWorks() {
driver(DriverParameters(startNodesInProcess = true, cordappsForAllNodes = listOf(cordappWithPackages("net.corda.docs.kotlin.tutorial.flowstatemachines")))) {
val aliceUser = User("aliceUser", "testPassword1", permissions = setOf(Permissions.all()))
val alice = startNode(providedName = ALICE_NAME, rpcUsers = listOf(aliceUser)).getOrThrow()
val aliceClient = CordaRPCClient(alice.rpcAddress)
val aliceProxy = aliceClient.start("aliceUser", "testPassword1").proxy
val answer = aliceProxy.startFlow(::ExampleSummingFlow).returnValue.getOrThrow()
assertEquals(3, answer)
}
}
// DOCEND summingWorks
}

View File

@ -1,23 +0,0 @@
// We purposefully have this template here as part of progressing through the tutorial
package com.template;
import net.corda.core.contracts.CommandData;
import net.corda.core.contracts.Contract;
import net.corda.core.transactions.LedgerTransaction;
import org.jetbrains.annotations.NotNull;
public class TemplateContract implements Contract {
// This is used to identify our contract when building a transaction.
public static final String ID = "com.template.TemplateContract";
/**
* A transaction is considered valid if the verify() function of the contract of each of the transaction's input
* and output states does not throw an exception.
*/
@Override
public void verify(@NotNull LedgerTransaction tx) {}
public interface Commands extends CommandData {
class Action implements Commands {}
}
}

View File

@ -1,31 +0,0 @@
package net.corda.docs.java;
// START 1
import net.corda.client.rpc.CordaRPCClient;
import net.corda.client.rpc.CordaRPCConnection;
import net.corda.core.messaging.CordaRPCOps;
import net.corda.core.utilities.NetworkHostAndPort;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class ClientRpcExample {
private static final Logger logger = LoggerFactory.getLogger(ClientRpcExample.class);
public static void main(String[] args) {
if (args.length != 3) {
throw new IllegalArgumentException("Usage: TemplateClient <node address> <username> <password>");
}
final NetworkHostAndPort nodeAddress = NetworkHostAndPort.parse(args[0]);
String username = args[1];
String password = args[2];
final CordaRPCClient client = new CordaRPCClient(nodeAddress);
final CordaRPCConnection connection = client.start(username, password);
final CordaRPCOps cordaRPCOperations = connection.getProxy();
logger.info(cordaRPCOperations.currentNodeTime().toString());
connection.notifyServerAndClose();
}
}
// END 1

View File

@ -1,137 +0,0 @@
package net.corda.docs.java;
import co.paralleluniverse.fibers.Suspendable;
import net.corda.core.flows.*;
import net.corda.core.identity.Party;
import net.corda.core.transactions.SignedTransaction;
import org.jetbrains.annotations.NotNull;
import static java.util.Collections.singletonList;
@SuppressWarnings("ALL")
public class FinalityFlowMigration {
public static SignedTransaction dummyTransactionWithParticipant(Party party) {
throw new UnsupportedOperationException();
}
// DOCSTART SimpleFlowUsingOldApi
public static class SimpleFlowUsingOldApi extends FlowLogic<SignedTransaction> {
private final Party counterparty;
@Suspendable
@Override
public SignedTransaction call() throws FlowException {
SignedTransaction stx = dummyTransactionWithParticipant(counterparty);
return subFlow(new FinalityFlow(stx));
}
// DOCEND SimpleFlowUsingOldApi
public SimpleFlowUsingOldApi(Party counterparty) {
this.counterparty = counterparty;
}
}
// DOCSTART SimpleFlowUsingNewApi
// Notice how the flow *must* now be an initiating flow even when it wasn't before.
@InitiatingFlow
public static class SimpleFlowUsingNewApi extends FlowLogic<SignedTransaction> {
private final Party counterparty;
@Suspendable
@Override
public SignedTransaction call() throws FlowException {
SignedTransaction stx = dummyTransactionWithParticipant(counterparty);
// For each non-local participant in the transaction we must initiate a flow session with them.
FlowSession session = initiateFlow(counterparty);
return subFlow(new FinalityFlow(stx, session));
}
// DOCEND SimpleFlowUsingNewApi
public SimpleFlowUsingNewApi(Party counterparty) {
this.counterparty = counterparty;
}
}
// DOCSTART SimpleNewResponderFlow
// All participants will run this flow to receive and record the finalised transaction into their vault.
@InitiatedBy(SimpleFlowUsingNewApi.class)
public static class SimpleNewResponderFlow extends FlowLogic<Void> {
private final FlowSession otherSide;
@Suspendable
@Override
public Void call() throws FlowException {
subFlow(new ReceiveFinalityFlow(otherSide));
return null;
}
// DOCEND SimpleNewResponderFlow
public SimpleNewResponderFlow(FlowSession otherSide) {
this.otherSide = otherSide;
}
}
// DOCSTART ExistingInitiatingFlow
// Assuming the previous version of the flow was 1 (the default if none is specified), we increment the version number to 2
// to allow for backwards compatibility with nodes running the old CorDapp.
@InitiatingFlow(version = 2)
public static class ExistingInitiatingFlow extends FlowLogic<SignedTransaction> {
private final Party counterparty;
@Suspendable
@Override
public SignedTransaction call() throws FlowException {
SignedTransaction partiallySignedTx = dummyTransactionWithParticipant(counterparty);
FlowSession session = initiateFlow(counterparty);
SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow(partiallySignedTx, singletonList(session)));
// Determine which version of the flow that other side is using.
if (session.getCounterpartyFlowInfo().getFlowVersion() == 1) {
// Use the old API if the other side is using the previous version of the flow.
return subFlow(new FinalityFlow(fullySignedTx));
} else {
// Otherwise they're at least on version 2 and so we can send the finalised transaction on the existing session.
return subFlow(new FinalityFlow(fullySignedTx, session));
}
}
// DOCEND ExistingInitiatingFlow
public ExistingInitiatingFlow(Party counterparty) {
this.counterparty = counterparty;
}
}
@InitiatedBy(ExistingInitiatingFlow.class)
public static class ExistingResponderFlow extends FlowLogic<Void> {
private final FlowSession otherSide;
public ExistingResponderFlow(FlowSession otherSide) {
this.otherSide = otherSide;
}
@Suspendable
@Override
public Void call() throws FlowException {
// DOCSTART ExistingResponderFlow
// First we have to run the SignTransactionFlow, which will return a SignedTransaction.
SignedTransaction txWeJustSigned = subFlow(new SignTransactionFlow(otherSide) {
@Suspendable
@Override
protected void checkTransaction(@NotNull SignedTransaction stx) throws FlowException {
// Implement responder flow transaction checks here
}
});
if (otherSide.getCounterpartyFlowInfo().getFlowVersion() >= 2) {
// The other side is not using the old CorDapp so call ReceiveFinalityFlow to record the finalised transaction.
// If SignTransactionFlow is used then we can verify the tranaction we receive for recording is the same one
// that was just signed by passing the transaction id to ReceiveFinalityFlow.
subFlow(new ReceiveFinalityFlow(otherSide, txWeJustSigned.getId()));
} else {
// Otherwise the other side is running the old CorDapp and so we don't need to do anything further. The node
// will automatically record the finalised transaction using the old insecure mechanism.
}
// DOCEND ExistingResponderFlow
return null;
}
}
}

View File

@ -1,689 +0,0 @@
package net.corda.docs.java;
import co.paralleluniverse.fibers.Suspendable;
import com.google.common.collect.ImmutableList;
import net.corda.core.contracts.*;
import net.corda.core.crypto.SecureHash;
import net.corda.core.crypto.TransactionSignature;
import net.corda.core.flows.*;
import net.corda.core.identity.CordaX500Name;
import net.corda.core.identity.Party;
import net.corda.core.identity.PartyAndCertificate;
import net.corda.core.internal.FetchDataFlow;
import net.corda.core.node.services.Vault;
import net.corda.core.node.services.Vault.Page;
import net.corda.core.node.services.vault.QueryCriteria.VaultQueryCriteria;
import net.corda.core.transactions.LedgerTransaction;
import net.corda.core.transactions.SignedTransaction;
import net.corda.core.transactions.TransactionBuilder;
import net.corda.core.utilities.ProgressTracker;
import net.corda.core.utilities.ProgressTracker.Step;
import net.corda.core.utilities.UntrustworthyData;
import net.corda.finance.contracts.asset.Cash;
import net.corda.testing.contracts.DummyContract;
import net.corda.testing.contracts.DummyState;
import org.jetbrains.annotations.NotNull;
import java.security.GeneralSecurityException;
import java.security.PublicKey;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.List;
import static com.google.common.base.Preconditions.checkArgument;
import static java.util.Collections.*;
import static net.corda.core.contracts.ContractsDSL.requireThat;
import static net.corda.core.crypto.Crypto.generateKeyPair;
@SuppressWarnings("unused")
public class FlowCookbook {
// ``InitiatorFlow`` is our first flow, and will communicate with
// ``ResponderFlow``, below.
// We mark ``InitiatorFlow`` as an ``InitiatingFlow``, allowing it to be
// started directly by the node.
@InitiatingFlow
// We also mark ``InitiatorFlow`` as ``StartableByRPC``, allowing the
// node's owner to start the flow via RPC.
@StartableByRPC
// Every flow must subclass ``FlowLogic``. The generic indicates the
// flow's return type.
public static class InitiatorFlow extends FlowLogic<Void> {
private final boolean arg1;
private final int arg2;
private final Party counterparty;
private final Party regulator;
public InitiatorFlow(boolean arg1, int arg2, Party counterparty, Party regulator) {
this.arg1 = arg1;
this.arg2 = arg2;
this.counterparty = counterparty;
this.regulator = regulator;
}
/*----------------------------------
* WIRING UP THE PROGRESS TRACKER *
----------------------------------*/
// Giving our flow a progress tracker allows us to see the flow's
// progress visually in our node's CRaSH shell.
// DOCSTART 17
private static final Step ID_OTHER_NODES = new Step("Identifying other nodes on the network.");
private static final Step SENDING_AND_RECEIVING_DATA = new Step("Sending data between parties.");
private static final Step EXTRACTING_VAULT_STATES = new Step("Extracting states from the vault.");
private static final Step OTHER_TX_COMPONENTS = new Step("Gathering a transaction's other components.");
private static final Step TX_BUILDING = new Step("Building a transaction.");
private static final Step TX_SIGNING = new Step("Signing a transaction.");
private static final Step TX_VERIFICATION = new Step("Verifying a transaction.");
private static final Step SIGS_GATHERING = new Step("Gathering a transaction's signatures.") {
// Wiring up a child progress tracker allows us to see the
// subflow's progress steps in our flow's progress tracker.
@Override
public ProgressTracker childProgressTracker() {
return CollectSignaturesFlow.tracker();
}
};
private static final Step VERIFYING_SIGS = new Step("Verifying a transaction's signatures.");
private static final Step FINALISATION = new Step("Finalising a transaction.") {
@Override
public ProgressTracker childProgressTracker() {
return FinalityFlow.tracker();
}
};
private final ProgressTracker progressTracker = new ProgressTracker(
ID_OTHER_NODES,
SENDING_AND_RECEIVING_DATA,
EXTRACTING_VAULT_STATES,
OTHER_TX_COMPONENTS,
TX_BUILDING,
TX_SIGNING,
TX_VERIFICATION,
SIGS_GATHERING,
FINALISATION
);
// DOCEND 17
@Suspendable
@Override
public Void call() throws FlowException {
// We'll be using a dummy public key for demonstration purposes.
PublicKey dummyPubKey = generateKeyPair().getPublic();
/*---------------------------
* IDENTIFYING OTHER NODES *
---------------------------*/
// DOCSTART 18
progressTracker.setCurrentStep(ID_OTHER_NODES);
// DOCEND 18
// Every transaction needs a notary:
// - To prevent double-spends if the transaction has inputs
// - To serve as a timestamping authority if the transaction has a
// time-window
// We retrieve a notary from the network map.
// DOCSTART 01
CordaX500Name notaryName = new CordaX500Name("Notary Service", "London", "GB");
Party notary = getServiceHub().getNetworkMapCache().getNotary(notaryName);
// DOCEND 01
// We may also need to identify a specific counterparty. We do so
// using the identity service.
// DOCSTART 02
CordaX500Name counterPartyName = new CordaX500Name("NodeA", "London", "GB");
Party namedCounterparty = getServiceHub().getIdentityService().wellKnownPartyFromX500Name(counterPartyName);
Party keyedCounterparty = getServiceHub().getIdentityService().partyFromKey(dummyPubKey);
// DOCEND 02
/*------------------------------
* SENDING AND RECEIVING DATA *
------------------------------*/
progressTracker.setCurrentStep(SENDING_AND_RECEIVING_DATA);
// We start by initiating a flow session with the counterparty. We
// will use this session to send and receive messages from the
// counterparty.
// DOCSTART initiateFlow
FlowSession counterpartySession = initiateFlow(counterparty);
// DOCEND initiateFlow
// We can send arbitrary data to a counterparty.
// If this is the first ``send``, the counterparty will either:
// 1. Ignore the message if they are not registered to respond
// to messages from this flow.
// 2. Start the flow they have registered to respond to this flow,
// and run the flow until the first call to ``receive``, at
// which point they process the message.
// In other words, we are assuming that the counterparty is
// registered to respond to this flow, and has a corresponding
// ``receive`` call.
// DOCSTART 04
counterpartySession.send(new Object());
// DOCEND 04
// We can wait to receive arbitrary data of a specific type from a
// counterparty. Again, this implies a corresponding ``send`` call
// in the counterparty's flow. A few scenarios:
// - We never receive a message back. In the current design, the
// flow is paused until the node's owner kills the flow.
// - Instead of sending a message back, the counterparty throws a
// ``FlowException``. This exception is propagated back to us,
// and we can use the error message to establish what happened.
// - We receive a message back, but it's of the wrong type. In
// this case, a ``FlowException`` is thrown.
// - We receive back a message of the correct type. All is good.
//
// Upon calling ``receive()`` (or ``sendAndReceive()``), the
// ``FlowLogic`` is suspended until it receives a response.
//
// We receive the data wrapped in an ``UntrustworthyData``
// instance. This is a reminder that the data we receive may not
// be what it appears to be! We must unwrap the
// ``UntrustworthyData`` using a lambda.
// DOCSTART 05
UntrustworthyData<Integer> packet1 = counterpartySession.receive(Integer.class);
Integer integer = packet1.unwrap(data -> {
// Perform checking on the object received.
// T O D O: Check the received object.
// Return the object.
return data;
});
// DOCEND 05
// We can also use a single call to send data to a counterparty
// and wait to receive data of a specific type back. The type of
// data sent doesn't need to match the type of the data received
// back.
// DOCSTART 07
UntrustworthyData<Boolean> packet2 = counterpartySession.sendAndReceive(Boolean.class, "You can send and receive any class!");
Boolean bool = packet2.unwrap(data -> {
// Perform checking on the object received.
// T O D O: Check the received object.
// Return the object.
return data;
});
// DOCEND 07
// We're not limited to sending to and receiving from a single
// counterparty. A flow can send messages to as many parties as it
// likes, and each party can invoke a different response flow.
// DOCSTART 06
FlowSession regulatorSession = initiateFlow(regulator);
regulatorSession.send(new Object());
UntrustworthyData<Object> packet3 = regulatorSession.receive(Object.class);
// DOCEND 06
/*------------------------------------
* EXTRACTING STATES FROM THE VAULT *
------------------------------------*/
progressTracker.setCurrentStep(EXTRACTING_VAULT_STATES);
// Let's assume there are already some ``DummyState``s in our
// node's vault, stored there as a result of running past flows,
// and we want to consume them in a transaction. There are many
// ways to extract these states from our vault.
// For example, we would extract any unconsumed ``DummyState``s
// from our vault as follows:
VaultQueryCriteria criteria = new VaultQueryCriteria(Vault.StateStatus.UNCONSUMED);
Page<DummyState> results = getServiceHub().getVaultService().queryBy(DummyState.class, criteria);
List<StateAndRef<DummyState>> dummyStates = results.getStates();
// For a full list of the available ways of extracting states from
// the vault, see the Vault Query docs page.
// When building a transaction, input states are passed in as
// ``StateRef`` instances, which pair the hash of the transaction
// that generated the state with the state's index in the outputs
// of that transaction. In practice, we'd pass the transaction hash
// or the ``StateRef`` as a parameter to the flow, or extract the
// ``StateRef`` from our vault.
// DOCSTART 20
StateRef ourStateRef = new StateRef(SecureHash.sha256("DummyTransactionHash"), 0);
// DOCEND 20
// A ``StateAndRef`` pairs a ``StateRef`` with the state it points to.
// DOCSTART 21
StateAndRef ourStateAndRef = getServiceHub().toStateAndRef(ourStateRef);
// DOCEND 21
/*------------------------------------------
* GATHERING OTHER TRANSACTION COMPONENTS *
------------------------------------------*/
progressTracker.setCurrentStep(OTHER_TX_COMPONENTS);
// Reference input states are constructed from StateAndRefs.
// DOCSTART 55
ReferencedStateAndRef referenceState = ourStateAndRef.referenced();
// DOCEND 55
// Output states are constructed from scratch.
// DOCSTART 22
DummyState ourOutputState = new DummyState();
// DOCEND 22
// Or as copies of other states with some properties changed.
// DOCSTART 23
DummyState ourOtherOutputState = ourOutputState.copy(77);
// DOCEND 23
// We then need to pair our output state with a contract.
// DOCSTART 47
StateAndContract ourOutput = new StateAndContract(ourOutputState, DummyContract.PROGRAM_ID);
// DOCEND 47
// Commands pair a ``CommandData`` instance with a list of
// public keys. To be valid, the transaction requires a signature
// matching every public key in all of the transaction's commands.
// DOCSTART 24
DummyContract.Commands.Create commandData = new DummyContract.Commands.Create();
PublicKey ourPubKey = getServiceHub().getMyInfo().getLegalIdentitiesAndCerts().get(0).getOwningKey();
PublicKey counterpartyPubKey = counterparty.getOwningKey();
List<PublicKey> requiredSigners = ImmutableList.of(ourPubKey, counterpartyPubKey);
Command<DummyContract.Commands.Create> ourCommand = new Command<>(commandData, requiredSigners);
// DOCEND 24
// ``CommandData`` can either be:
// 1. Of type ``TypeOnlyCommandData``, in which case it only
// serves to attach signers to the transaction and possibly
// fork the contract's verification logic.
TypeOnlyCommandData typeOnlyCommandData = new DummyContract.Commands.Create();
// 2. Include additional data which can be used by the contract
// during verification, alongside fulfilling the roles above
CommandData commandDataWithData = new Cash.Commands.Issue();
// Attachments are identified by their hash.
// The attachment with the corresponding hash must have been
// uploaded ahead of time via the node's RPC interface.
// DOCSTART 25
SecureHash ourAttachment = SecureHash.sha256("DummyAttachment");
// DOCEND 25
// Time windows represent the period of time during which a
// transaction must be notarised. They can have a start and an end
// time, or be open at either end.
// DOCSTART 26
TimeWindow ourTimeWindow = TimeWindow.between(Instant.MIN, Instant.MAX);
TimeWindow ourAfter = TimeWindow.fromOnly(Instant.MIN);
TimeWindow ourBefore = TimeWindow.untilOnly(Instant.MAX);
// DOCEND 26
// We can also define a time window as an ``Instant`` +/- a time
// tolerance (e.g. 30 seconds):
// DOCSTART 42
TimeWindow ourTimeWindow2 = TimeWindow.withTolerance(getServiceHub().getClock().instant(), Duration.ofSeconds(30));
// DOCEND 42
// Or as a start-time plus a duration:
// DOCSTART 43
TimeWindow ourTimeWindow3 = TimeWindow.fromStartAndDuration(getServiceHub().getClock().instant(), Duration.ofSeconds(30));
// DOCEND 43
/*------------------------
* TRANSACTION BUILDING *
------------------------*/
progressTracker.setCurrentStep(TX_BUILDING);
// If our transaction has input states or a time-window, we must instantiate it with a
// notary.
// DOCSTART 19
TransactionBuilder txBuilder = new TransactionBuilder(notary);
// DOCEND 19
// Otherwise, we can choose to instantiate it without one:
// DOCSTART 46
TransactionBuilder txBuilderNoNotary = new TransactionBuilder();
// DOCEND 46
// We add items to the transaction builder using ``TransactionBuilder.withItems``:
// DOCSTART 27
txBuilder.withItems(
// Inputs, as ``StateAndRef``s that reference to the outputs of previous transactions
ourStateAndRef,
// Outputs, as ``StateAndContract``s
ourOutput,
// Commands, as ``Command``s
ourCommand,
// Attachments, as ``SecureHash``es
ourAttachment,
// A time-window, as ``TimeWindow``
ourTimeWindow
);
// DOCEND 27
// We can also add items using methods for the individual components.
// The individual methods for adding input states and attachments:
// DOCSTART 28
txBuilder.addInputState(ourStateAndRef);
txBuilder.addAttachment(ourAttachment);
// DOCEND 28
// An output state can be added as a ``ContractState``, contract class name and notary.
// DOCSTART 49
txBuilder.addOutputState(ourOutputState, DummyContract.PROGRAM_ID, notary);
// DOCEND 49
// We can also leave the notary field blank, in which case the transaction's default
// notary is used.
// DOCSTART 50
txBuilder.addOutputState(ourOutputState, DummyContract.PROGRAM_ID);
// DOCEND 50
// Or we can add the output state as a ``TransactionState``, which already specifies
// the output's contract and notary.
// DOCSTART 51
TransactionState txState = new TransactionState(ourOutputState, DummyContract.PROGRAM_ID, notary);
// DOCEND 51
// Commands can be added as ``Command``s.
// DOCSTART 52
txBuilder.addCommand(ourCommand);
// DOCEND 52
// Or as ``CommandData`` and a ``vararg PublicKey``.
// DOCSTART 53
txBuilder.addCommand(commandData, ourPubKey, counterpartyPubKey);
// DOCEND 53
// We can set a time-window directly.
// DOCSTART 44
txBuilder.setTimeWindow(ourTimeWindow);
// DOCEND 44
// Or as a start time plus a duration (e.g. 45 seconds).
// DOCSTART 45
txBuilder.setTimeWindow(getServiceHub().getClock().instant(), Duration.ofSeconds(45));
// DOCEND 45
/*-----------------------
* TRANSACTION SIGNING *
-----------------------*/
progressTracker.setCurrentStep(TX_SIGNING);
// We finalise the transaction by signing it,
// converting it into a ``SignedTransaction``.
// DOCSTART 29
SignedTransaction onceSignedTx = getServiceHub().signInitialTransaction(txBuilder);
// DOCEND 29
// We can also sign the transaction using a different public key:
// DOCSTART 30
PartyAndCertificate otherIdentity = getServiceHub().getKeyManagementService().freshKeyAndCert(getOurIdentityAndCert(), false);
SignedTransaction onceSignedTx2 = getServiceHub().signInitialTransaction(txBuilder, otherIdentity.getOwningKey());
// DOCEND 30
// If instead this was a ``SignedTransaction`` that we'd received
// from a counterparty and we needed to sign it, we would add our
// signature using:
// DOCSTART 38
SignedTransaction twiceSignedTx = getServiceHub().addSignature(onceSignedTx);
// DOCEND 38
// Or, if we wanted to use a different public key:
PartyAndCertificate otherIdentity2 = getServiceHub().getKeyManagementService().freshKeyAndCert(getOurIdentityAndCert(), false);
// DOCSTART 39
SignedTransaction twiceSignedTx2 = getServiceHub().addSignature(onceSignedTx, otherIdentity2.getOwningKey());
// DOCEND 39
// We can also generate a signature over the transaction without
// adding it to the transaction itself. We may do this when
// sending just the signature in a flow instead of returning the
// entire transaction with our signature. This way, the receiving
// node does not need to check we haven't changed anything in the
// transaction.
// DOCSTART 40
TransactionSignature sig = getServiceHub().createSignature(onceSignedTx);
// DOCEND 40
// And again, if we wanted to use a different public key:
// DOCSTART 41
TransactionSignature sig2 = getServiceHub().createSignature(onceSignedTx, otherIdentity2.getOwningKey());
// DOCEND 41
/*----------------------------
* TRANSACTION VERIFICATION *
----------------------------*/
progressTracker.setCurrentStep(TX_VERIFICATION);
// Verifying a transaction will also verify every transaction in
// the transaction's dependency chain, which will require
// transaction data access on counterparty's node. The
// ``SendTransactionFlow`` can be used to automate the sending and
// data vending process. The ``SendTransactionFlow`` will listen
// for data request until the transaction is resolved and verified
// on the other side:
// DOCSTART 12
subFlow(new SendTransactionFlow(counterpartySession, twiceSignedTx));
// Optional request verification to further restrict data access.
subFlow(new SendTransactionFlow(counterpartySession, twiceSignedTx) {
@Override
protected void verifyDataRequest(@NotNull FetchDataFlow.Request.Data dataRequest) {
// Extra request verification.
}
});
// DOCEND 12
// We can receive the transaction using ``ReceiveTransactionFlow``,
// which will automatically download all the dependencies and verify
// the transaction and then record in our vault
// DOCSTART 13
SignedTransaction verifiedTransaction = subFlow(new ReceiveTransactionFlow(counterpartySession));
// DOCEND 13
// We can also send and receive a `StateAndRef` dependency chain and automatically resolve its dependencies.
// DOCSTART 14
subFlow(new SendStateAndRefFlow(counterpartySession, dummyStates));
// On the receive side ...
List<StateAndRef<DummyState>> resolvedStateAndRef = subFlow(new ReceiveStateAndRefFlow<>(counterpartySession));
// DOCEND 14
try {
// We can now verify the transaction to ensure that it satisfies
// the contracts of all the transaction's input and output states.
// DOCSTART 33
twiceSignedTx.verify(getServiceHub());
// DOCEND 33
// We'll often want to perform our own additional verification
// too. Just because a transaction is valid based on the contract
// rules and requires our signature doesn't mean we have to
// sign it! We need to make sure the transaction represents an
// agreement we actually want to enter into.
// To do this, we need to convert our ``SignedTransaction``
// into a ``LedgerTransaction``. This will use our ServiceHub
// to resolve the transaction's inputs and attachments into
// actual objects, rather than just references.
// DOCSTART 32
LedgerTransaction ledgerTx = twiceSignedTx.toLedgerTransaction(getServiceHub());
// DOCEND 32
// We can now perform our additional verification.
// DOCSTART 34
DummyState outputState = ledgerTx.outputsOfType(DummyState.class).get(0);
if (outputState.getMagicNumber() != 777) {
// ``FlowException`` is a special exception type. It will be
// propagated back to any counterparty flows waiting for a
// message from this flow, notifying them that the flow has
// failed.
throw new FlowException("We expected a magic number of 777.");
}
// DOCEND 34
} catch (GeneralSecurityException e) {
// Handle this as required.
}
// Of course, if you are not a required signer on the transaction,
// you have no power to decide whether it is valid or not. If it
// requires signatures from all the required signers and is
// contractually valid, it's a valid ledger update.
/*------------------------
* GATHERING SIGNATURES *
------------------------*/
progressTracker.setCurrentStep(SIGS_GATHERING);
// The list of parties who need to sign a transaction is dictated
// by the transaction's commands. Once we've signed a transaction
// ourselves, we can automatically gather the signatures of the
// other required signers using ``CollectSignaturesFlow``.
// The responder flow will need to call ``SignTransactionFlow``.
// DOCSTART 15
SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow(twiceSignedTx, emptySet(), SIGS_GATHERING.childProgressTracker()));
// DOCEND 15
/*------------------------
* VERIFYING SIGNATURES *
------------------------*/
progressTracker.setCurrentStep(VERIFYING_SIGS);
try {
// We can verify that a transaction has all the required
// signatures, and that they're all valid, by running:
// DOCSTART 35
fullySignedTx.verifyRequiredSignatures();
// DOCEND 35
// If the transaction is only partially signed, we have to pass in
// a vararg of the public keys corresponding to the missing
// signatures, explicitly telling the system not to check them.
// DOCSTART 36
onceSignedTx.verifySignaturesExcept(counterpartyPubKey);
// DOCEND 36
// There is also an overload of ``verifySignaturesExcept`` which accepts
// a ``Collection`` of the public keys corresponding to the missing
// signatures. In the example below, we could also use
// ``Arrays.asList(counterpartyPubKey)`` instead of
// ``Collections.singletonList(counterpartyPubKey)``.
// DOCSTART 54
onceSignedTx.verifySignaturesExcept(singletonList(counterpartyPubKey));
// DOCEND 54
// We can also choose to only check the signatures that are
// present. BE VERY CAREFUL - this function provides no guarantees
// that the signatures are correct, or that none are missing.
// DOCSTART 37
twiceSignedTx.checkSignaturesAreValid();
// DOCEND 37
} catch (GeneralSecurityException e) {
// Handle this as required.
}
/*------------------------------
* FINALISING THE TRANSACTION *
------------------------------*/
progressTracker.setCurrentStep(FINALISATION);
// We notarise the transaction and get it recorded in the vault of
// the participants of all the transaction's states.
// DOCSTART 09
SignedTransaction notarisedTx1 = subFlow(new FinalityFlow(fullySignedTx, singleton(counterpartySession), FINALISATION.childProgressTracker()));
// DOCEND 09
// We can also choose to send it to additional parties who aren't one
// of the state's participants.
// DOCSTART 10
List<FlowSession> partySessions = Arrays.asList(counterpartySession, initiateFlow(regulator));
SignedTransaction notarisedTx2 = subFlow(new FinalityFlow(fullySignedTx, partySessions, FINALISATION.childProgressTracker()));
// DOCEND 10
// DOCSTART FlowSession porting
send(regulator, new Object()); // Old API
// becomes
FlowSession session = initiateFlow(regulator);
session.send(new Object());
// DOCEND FlowSession porting
return null;
}
}
// ``ResponderFlow`` is our second flow, and will communicate with
// ``InitiatorFlow``.
// We mark ``ResponderFlow`` as an ``InitiatedByFlow``, meaning that it
// can only be started in response to a message from its initiating flow.
// That's ``InitiatorFlow`` in this case.
// Each node also has several flow pairs registered by default - see
// ``AbstractNode.installCoreFlows``.
@InitiatedBy(InitiatorFlow.class)
public static class ResponderFlow extends FlowLogic<Void> {
private final FlowSession counterpartySession;
public ResponderFlow(FlowSession counterpartySession) {
this.counterpartySession = counterpartySession;
}
private static final Step RECEIVING_AND_SENDING_DATA = new Step("Sending data between parties.");
private static final Step SIGNING = new Step("Responding to CollectSignaturesFlow.");
private static final Step FINALISATION = new Step("Finalising a transaction.");
private final ProgressTracker progressTracker = new ProgressTracker(
RECEIVING_AND_SENDING_DATA,
SIGNING,
FINALISATION
);
@Suspendable
@Override
public Void call() throws FlowException {
// The ``ResponderFlow` has all the same APIs available. It looks
// up network information, sends and receives data, and constructs
// transactions in exactly the same way.
/*------------------------------
* SENDING AND RECEIVING DATA *
-----------------------------*/
progressTracker.setCurrentStep(RECEIVING_AND_SENDING_DATA);
// We need to respond to the messages sent by the initiator:
// 1. They sent us an ``Object`` instance
// 2. They waited to receive an ``Integer`` instance back
// 3. They sent a ``String`` instance and waited to receive a
// ``Boolean`` instance back
// Our side of the flow must mirror these calls.
// DOCSTART 08
Object obj = counterpartySession.receive(Object.class).unwrap(data -> data);
String string = counterpartySession.sendAndReceive(String.class, 99).unwrap(data -> data);
counterpartySession.send(true);
// DOCEND 08
/*-----------------------------------------
* RESPONDING TO COLLECT_SIGNATURES_FLOW *
-----------------------------------------*/
progressTracker.setCurrentStep(SIGNING);
// The responder will often need to respond to a call to
// ``CollectSignaturesFlow``. It does so my invoking its own
// ``SignTransactionFlow`` subclass.
// DOCSTART 16
class SignTxFlow extends SignTransactionFlow {
private SignTxFlow(FlowSession otherSession, ProgressTracker progressTracker) {
super(otherSession, progressTracker);
}
@Override
protected void checkTransaction(SignedTransaction stx) {
requireThat(require -> {
// Any additional checking we see fit...
DummyState outputState = (DummyState) stx.getTx().getOutputs().get(0).getData();
checkArgument(outputState.getMagicNumber() == 777);
return null;
});
}
}
SecureHash idOfTxWeSigned = subFlow(new SignTxFlow(counterpartySession, SignTransactionFlow.tracker())).getId();
// DOCEND 16
/*------------------------------
* FINALISING THE TRANSACTION *
------------------------------*/
progressTracker.setCurrentStep(FINALISATION);
// As the final step the responder waits to receive the notarised transaction from the sending party
// Since it knows the ID of the transaction it just signed, the transaction ID is specified to ensure the correct
// transaction is received and recorded.
// DOCSTART ReceiveFinalityFlow
subFlow(new ReceiveFinalityFlow(counterpartySession, idOfTxWeSigned));
// DOCEND ReceiveFinalityFlow
return null;
}
}
}

View File

@ -1,133 +0,0 @@
package net.corda.docs.java;
import co.paralleluniverse.fibers.Suspendable;
import net.corda.core.flows.*;
import net.corda.core.identity.Party;
@SuppressWarnings("ALL")
// DOCSTART LaunchSpaceshipFlow
@InitiatingFlow
class LaunchSpaceshipFlow extends FlowLogic<Void> {
@Suspendable
@Override
public Void call() throws FlowException {
boolean shouldLaunchSpaceship = receive(Boolean.class, getPresident()).unwrap(s -> s);
if (shouldLaunchSpaceship) {
launchSpaceship();
}
return null;
}
public void launchSpaceship() {
}
public Party getPresident() {
throw new AbstractMethodError();
}
}
@InitiatedBy(LaunchSpaceshipFlow.class)
@InitiatingFlow
class PresidentSpaceshipFlow extends FlowLogic<Void> {
private final Party launcher;
public PresidentSpaceshipFlow(Party launcher) {
this.launcher = launcher;
}
@Suspendable
@Override
public Void call() {
boolean needCoffee = true;
send(getSecretary(), needCoffee);
boolean shouldLaunchSpaceship = false;
send(launcher, shouldLaunchSpaceship);
return null;
}
public Party getSecretary() {
throw new AbstractMethodError();
}
}
@InitiatedBy(PresidentSpaceshipFlow.class)
class SecretaryFlow extends FlowLogic<Void> {
private final Party president;
public SecretaryFlow(Party president) {
this.president = president;
}
@Suspendable
@Override
public Void call() {
// ignore
return null;
}
}
// DOCEND LaunchSpaceshipFlow
@SuppressWarnings("ALL")
// DOCSTART LaunchSpaceshipFlowCorrect
@InitiatingFlow
class LaunchSpaceshipFlowCorrect extends FlowLogic<Void> {
@Suspendable
@Override
public Void call() throws FlowException {
FlowSession presidentSession = initiateFlow(getPresident());
boolean shouldLaunchSpaceship = presidentSession.receive(Boolean.class).unwrap(s -> s);
if (shouldLaunchSpaceship) {
launchSpaceship();
}
return null;
}
public void launchSpaceship() {
}
public Party getPresident() {
throw new AbstractMethodError();
}
}
@InitiatedBy(LaunchSpaceshipFlowCorrect.class)
@InitiatingFlow
class PresidentSpaceshipFlowCorrect extends FlowLogic<Void> {
private final FlowSession launcherSession;
public PresidentSpaceshipFlowCorrect(FlowSession launcherSession) {
this.launcherSession = launcherSession;
}
@Suspendable
@Override
public Void call() {
boolean needCoffee = true;
FlowSession secretarySession = initiateFlow(getSecretary());
secretarySession.send(needCoffee);
boolean shouldLaunchSpaceship = false;
launcherSession.send(shouldLaunchSpaceship);
return null;
}
public Party getSecretary() {
throw new AbstractMethodError();
}
}
@InitiatedBy(PresidentSpaceshipFlowCorrect.class)
class SecretaryFlowCorrect extends FlowLogic<Void> {
private final FlowSession presidentSession;
public SecretaryFlowCorrect(FlowSession presidentSession) {
this.presidentSession = presidentSession;
}
@Suspendable
@Override
public Void call() {
// ignore
return null;
}
}
// DOCEND LaunchSpaceshipFlowCorrect

View File

@ -1,35 +0,0 @@
package net.corda.docs.java;
// DOCSTART 1
import net.corda.core.identity.CordaX500Name;
import net.corda.testing.node.MockNetwork;
import net.corda.testing.node.MockNetworkParameters;
import net.corda.testing.node.StartedMockNode;
import org.junit.After;
import org.junit.Before;
import static java.util.Collections.singletonList;
import static net.corda.testing.node.TestCordapp.findCordapp;
public class MockNetworkTestsTutorial {
private final MockNetwork mockNet = new MockNetwork(new MockNetworkParameters(singletonList(findCordapp("com.mycordapp.package"))));
@After
public void cleanUp() {
mockNet.stopNodes();
}
// DOCEND 1
// DOCSTART 2
private StartedMockNode nodeA;
private StartedMockNode nodeB;
@Before
public void setUp() {
nodeA = mockNet.createNode();
// We can optionally give the node a name.
nodeB = mockNet.createNode(new CordaX500Name("Bank B", "London", "GB"));
}
// DOCEND 2
}

View File

@ -1,101 +0,0 @@
package net.corda.docs.java.tutorial.contract;
import net.corda.core.contracts.*;
import net.corda.core.transactions.LedgerTransaction;
import net.corda.core.transactions.LedgerTransaction.InOutGroup;
import java.time.Instant;
import java.util.Currency;
import java.util.List;
import static net.corda.core.contracts.ContractsDSL.requireSingleCommand;
import static net.corda.core.contracts.ContractsDSL.requireThat;
import static net.corda.finance.contracts.utils.StateSumming.sumCashBy;
public class CommercialPaper implements Contract {
// DOCSTART 1
public static final String IOU_CONTRACT_ID = "com.example.contract.IOUContract";
// DOCEND 1
// DOCSTART 3
@Override
public void verify(LedgerTransaction tx) {
List<InOutGroup<State, State>> groups = tx.groupStates(State.class, State::withoutOwner);
CommandWithParties<Commands> cmd = requireSingleCommand(tx.getCommands(), Commands.class);
// DOCEND 3
// DOCSTART 4
TimeWindow timeWindow = tx.getTimeWindow();
for (InOutGroup group : groups) {
List<State> inputs = group.getInputs();
List<State> outputs = group.getOutputs();
if (cmd.getValue() instanceof Commands.Move) {
State input = inputs.get(0);
requireThat(require -> {
require.using("the transaction is signed by the owner of the CP", cmd.getSigners().contains(input.getOwner().getOwningKey()));
require.using("the state is propagated", outputs.size() == 1);
// Don't need to check anything else, as if outputs.size == 1 then the output is equal to
// the input ignoring the owner field due to the grouping.
return null;
});
} else if (cmd.getValue() instanceof Commands.Redeem) {
// Redemption of the paper requires movement of on-ledger cash.
State input = inputs.get(0);
Amount<Issued<Currency>> received = sumCashBy(tx.getOutputStates(), input.getOwner());
if (timeWindow == null) throw new IllegalArgumentException("Redemptions must be timestamped");
Instant time = timeWindow.getFromTime();
requireThat(require -> {
require.using("the paper must have matured", time.isAfter(input.getMaturityDate()));
require.using("the received amount equals the face value", received == input.getFaceValue());
require.using("the paper must be destroyed", outputs.isEmpty());
require.using("the transaction is signed by the owner of the CP", cmd.getSigners().contains(input.getOwner().getOwningKey()));
return null;
});
} else if (cmd.getValue() instanceof Commands.Issue) {
State output = outputs.get(0);
if (timeWindow == null) throw new IllegalArgumentException("Issuances must have a time-window");
Instant time = timeWindow.getUntilTime();
requireThat(require -> {
// Don't allow people to issue commercial paper under other entities identities.
require.using("output states are issued by a command signer", cmd.getSigners().contains(output.getIssuance().getParty().getOwningKey()));
require.using("output values sum to more than the inputs", output.getFaceValue().getQuantity() > 0);
require.using("the maturity date is not in the past", time.isBefore(output.getMaturityDate()));
// Don't allow an existing CP state to be replaced by this issuance.
require.using("can't reissue an existing state", inputs.isEmpty());
return null;
});
} else {
throw new IllegalArgumentException("Unrecognised command");
}
}
// DOCEND 4
}
// DOCSTART 2
public static class Commands implements CommandData {
public static class Move extends Commands {
@Override
public boolean equals(Object obj) {
return obj instanceof Move;
}
}
public static class Redeem extends Commands {
@Override
public boolean equals(Object obj) {
return obj instanceof Redeem;
}
}
public static class Issue extends Commands {
@Override
public boolean equals(Object obj) {
return obj instanceof Issue;
}
}
}
// DOCEND 2
}

View File

@ -1,90 +0,0 @@
package net.corda.docs.java.tutorial.contract;
import com.google.common.collect.ImmutableList;
import net.corda.core.contracts.*;
import net.corda.core.crypto.NullKeys;
import net.corda.core.identity.AbstractParty;
import net.corda.core.identity.AnonymousParty;
import org.jetbrains.annotations.NotNull;
import java.time.Instant;
import java.util.Currency;
import java.util.List;
// DOCSTART 1
public class State implements OwnableState {
private PartyAndReference issuance;
private AbstractParty owner;
private Amount<Issued<Currency>> faceValue;
private Instant maturityDate;
public State() {
} // For serialization
public State(PartyAndReference issuance, AbstractParty owner, Amount<Issued<Currency>> faceValue,
Instant maturityDate) {
this.issuance = issuance;
this.owner = owner;
this.faceValue = faceValue;
this.maturityDate = maturityDate;
}
public State copy() {
return new State(this.issuance, this.owner, this.faceValue, this.maturityDate);
}
public State withoutOwner() {
return new State(this.issuance, new AnonymousParty(NullKeys.NullPublicKey.INSTANCE), this.faceValue, this.maturityDate);
}
@NotNull
@Override
public CommandAndState withNewOwner(@NotNull AbstractParty newOwner) {
return new CommandAndState(new CommercialPaper.Commands.Move(), new State(this.issuance, newOwner, this.faceValue, this.maturityDate));
}
public PartyAndReference getIssuance() {
return issuance;
}
public AbstractParty getOwner() {
return owner;
}
public Amount<Issued<Currency>> getFaceValue() {
return faceValue;
}
public Instant getMaturityDate() {
return maturityDate;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
State state = (State) o;
if (issuance != null ? !issuance.equals(state.issuance) : state.issuance != null) return false;
if (owner != null ? !owner.equals(state.owner) : state.owner != null) return false;
if (faceValue != null ? !faceValue.equals(state.faceValue) : state.faceValue != null) return false;
return !(maturityDate != null ? !maturityDate.equals(state.maturityDate) : state.maturityDate != null);
}
@Override
public int hashCode() {
int result = issuance != null ? issuance.hashCode() : 0;
result = 31 * result + (owner != null ? owner.hashCode() : 0);
result = 31 * result + (faceValue != null ? faceValue.hashCode() : 0);
result = 31 * result + (maturityDate != null ? maturityDate.hashCode() : 0);
return result;
}
@NotNull
@Override
public List<AbstractParty> getParticipants() {
return ImmutableList.of(this.owner);
}
}
// DOCEND 1

View File

@ -1,19 +0,0 @@
package net.corda.docs.java.tutorial.flowstatemachines;
import co.paralleluniverse.fibers.Suspendable;
import net.corda.core.flows.FlowLogic;
import net.corda.core.flows.StartableByRPC;
import net.corda.core.internal.FlowAsyncOperationKt;
import org.jetbrains.annotations.NotNull;
// DOCSTART ExampleSummingFlow
@StartableByRPC
public final class ExampleSummingFlow extends FlowLogic<Integer> {
@Suspendable
@NotNull
@Override
public Integer call() {
return FlowAsyncOperationKt.executeAsync(this, new SummingOperation(1, 2), false);
}
}
// DOCEND ExampleSummingFlow

View File

@ -1,32 +0,0 @@
package net.corda.docs.java.tutorial.flowstatemachines;
import net.corda.core.concurrent.CordaFuture;
import net.corda.core.internal.FlowAsyncOperation;
import net.corda.core.internal.concurrent.CordaFutureImplKt;
import org.jetbrains.annotations.NotNull;
// DOCSTART SummingOperation
public final class SummingOperation implements FlowAsyncOperation<Integer> {
private final int a;
private final int b;
@NotNull
@Override
public CordaFuture<Integer> execute(String deduplicationId) {
return CordaFutureImplKt.doneFuture(this.a + this.b);
}
public final int getA() {
return this.a;
}
public final int getB() {
return this.b;
}
public SummingOperation(int a, int b) {
this.a = a;
this.b = b;
}
}
// DOCEND SummingOperation

View File

@ -1,31 +0,0 @@
package net.corda.docs.java.tutorial.flowstatemachines;
import net.corda.core.concurrent.CordaFuture;
import net.corda.core.internal.FlowAsyncOperation;
import org.jetbrains.annotations.NotNull;
// DOCSTART SummingOperationThrowing
public final class SummingOperationThrowing implements FlowAsyncOperation<Integer> {
private final int a;
private final int b;
@NotNull
@Override
public CordaFuture<Integer> execute(String deduplicationId) {
throw new IllegalStateException("You shouldn't be calling me");
}
public final int getA() {
return this.a;
}
public final int getB() {
return this.b;
}
public SummingOperationThrowing(int a, int b) {
this.a = a;
this.b = b;
}
}
// DOCEND SummingOperationThrowing

View File

@ -1,39 +0,0 @@
package net.corda.docs.java.tutorial.flowstatemachines;
import net.corda.core.flows.SignTransactionFlow;
import net.corda.core.utilities.ProgressTracker;
import org.jetbrains.annotations.Nullable;
@SuppressWarnings("ALL")
public class TutorialFlowStateMachines {
// DOCSTART 1
private final ProgressTracker progressTracker = new ProgressTracker(
RECEIVING,
VERIFYING,
SIGNING,
COLLECTING_SIGNATURES,
RECORDING
);
private static final ProgressTracker.Step RECEIVING = new ProgressTracker.Step(
"Waiting for seller trading info");
private static final ProgressTracker.Step VERIFYING = new ProgressTracker.Step(
"Verifying seller assets");
private static final ProgressTracker.Step SIGNING = new ProgressTracker.Step(
"Generating and signing transaction proposal");
private static final ProgressTracker.Step COLLECTING_SIGNATURES = new ProgressTracker.Step(
"Collecting signatures from other parties");
private static final ProgressTracker.Step RECORDING = new ProgressTracker.Step(
"Recording completed transaction");
// DOCEND 1
// DOCSTART 2
private static final ProgressTracker.Step VERIFYING_AND_SIGNING = new ProgressTracker.Step("Verifying and signing transaction proposal") {
@Nullable
@Override
public ProgressTracker childProgressTracker() {
return SignTransactionFlow.Companion.tracker();
}
};
// DOCEND 2
}

View File

@ -1,67 +0,0 @@
package net.corda.docs.java.tutorial.helloworld;
import co.paralleluniverse.fibers.Suspendable;
import com.template.TemplateContract;
import net.corda.core.flows.*;
import net.corda.core.utilities.ProgressTracker;
// DOCSTART 01
// Add these imports:
import net.corda.core.contracts.Command;
import net.corda.core.identity.Party;
import net.corda.core.transactions.SignedTransaction;
import net.corda.core.transactions.TransactionBuilder;
// Replace Initiator's definition with:
@InitiatingFlow
@StartableByRPC
public class IOUFlow extends FlowLogic<Void> {
private final Integer iouValue;
private final Party otherParty;
/**
* The progress tracker provides checkpoints indicating the progress of the flow to observers.
*/
private final ProgressTracker progressTracker = new ProgressTracker();
public IOUFlow(Integer iouValue, Party otherParty) {
this.iouValue = iouValue;
this.otherParty = otherParty;
}
@Override
public ProgressTracker getProgressTracker() {
return progressTracker;
}
/**
* The flow logic is encapsulated within the call() method.
*/
@Suspendable
@Override
public Void call() throws FlowException {
// We retrieve the notary identity from the network map.
Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0);
// We create the transaction components.
IOUState outputState = new IOUState(iouValue, getOurIdentity(), otherParty);
Command command = new Command<>(new TemplateContract.Commands.Action(), getOurIdentity().getOwningKey());
// We create a transaction builder and add the components.
TransactionBuilder txBuilder = new TransactionBuilder(notary)
.addOutputState(outputState, TemplateContract.ID)
.addCommand(command);
// Signing the transaction.
SignedTransaction signedTx = getServiceHub().signInitialTransaction(txBuilder);
// Creating a session with the other party.
FlowSession otherPartySession = initiateFlow(otherParty);
// We finalise the transaction and then send it to the counterparty.
subFlow(new FinalityFlow(signedTx, otherPartySession));
return null;
}
}
// DOCEND 01

View File

@ -1,25 +0,0 @@
package net.corda.docs.java.tutorial.helloworld;
import co.paralleluniverse.fibers.Suspendable;
import net.corda.core.flows.*;
@SuppressWarnings("unused")
// DOCSTART 01
// Replace Responder's definition with:
@InitiatedBy(IOUFlow.class)
public class IOUFlowResponder extends FlowLogic<Void> {
private final FlowSession otherPartySession;
public IOUFlowResponder(FlowSession otherPartySession) {
this.otherPartySession = otherPartySession;
}
@Suspendable
@Override
public Void call() throws FlowException {
subFlow(new ReceiveFinalityFlow(otherPartySession));
return null;
}
}
// DOCEND 01

View File

@ -1,41 +0,0 @@
package net.corda.docs.java.tutorial.helloworld;
import net.corda.core.contracts.ContractState;
import net.corda.core.identity.AbstractParty;
import java.util.Arrays;
import java.util.List;
// DOCSTART 01
// Add this import:
import net.corda.core.identity.Party;
// Replace TemplateState's definition with:
public class IOUState implements ContractState {
private final int value;
private final Party lender;
private final Party borrower;
public IOUState(int value, Party lender, Party borrower) {
this.value = value;
this.lender = lender;
this.borrower = borrower;
}
public int getValue() {
return value;
}
public Party getLender() {
return lender;
}
public Party getBorrower() {
return borrower;
}
@Override
public List<AbstractParty> getParticipants() {
return Arrays.asList(lender, borrower);
}
}
// DOCEND 01

View File

@ -1,55 +0,0 @@
package net.corda.docs.java.tutorial.twoparty;
import net.corda.core.contracts.CommandData;
import net.corda.core.contracts.Contract;
import net.corda.core.transactions.LedgerTransaction;
// DOCSTART 01
// Add these imports:
import net.corda.core.contracts.CommandWithParties;
import net.corda.core.identity.Party;
import java.security.PublicKey;
import java.util.Arrays;
import java.util.List;
import static net.corda.core.contracts.ContractsDSL.requireSingleCommand;
// Replace TemplateContract's definition with:
public class IOUContract implements Contract {
public static final String ID = "com.template.IOUContract";
// Our Create command.
public static class Create implements CommandData {
}
@Override
public void verify(LedgerTransaction tx) {
final CommandWithParties<IOUContract.Create> command = requireSingleCommand(tx.getCommands(), IOUContract.Create.class);
// Constraints on the shape of the transaction.
if (!tx.getInputs().isEmpty())
throw new IllegalArgumentException("No inputs should be consumed when issuing an IOU.");
if (!(tx.getOutputs().size() == 1))
throw new IllegalArgumentException("There should be one output state of type IOUState.");
// IOU-specific constraints.
final IOUState output = tx.outputsOfType(IOUState.class).get(0);
final Party lender = output.getLender();
final Party borrower = output.getBorrower();
if (output.getValue() <= 0)
throw new IllegalArgumentException("The IOU's value must be non-negative.");
if (lender.equals(borrower))
throw new IllegalArgumentException("The lender and the borrower cannot be the same entity.");
// Constraints on the signers.
final List<PublicKey> requiredSigners = command.getSigners();
final List<PublicKey> expectedSigners = Arrays.asList(borrower.getOwningKey(), lender.getOwningKey());
if (requiredSigners.size() != 2)
throw new IllegalArgumentException("There must be two signers.");
if (!(requiredSigners.containsAll(expectedSigners)))
throw new IllegalArgumentException("The borrower and lender must be signers.");
}
}
// DOCEND 01

View File

@ -1,77 +0,0 @@
package net.corda.docs.java.tutorial.twoparty;
// DOCSTART 01
import co.paralleluniverse.fibers.Suspendable;
import net.corda.core.contracts.Command;
import net.corda.core.flows.*;
import net.corda.core.identity.Party;
import net.corda.core.transactions.SignedTransaction;
import net.corda.core.transactions.TransactionBuilder;
import net.corda.core.utilities.ProgressTracker;
import java.security.PublicKey;
import java.util.Arrays;
import java.util.List;
// DOCEND 01
@InitiatingFlow
@StartableByRPC
public class IOUFlow extends FlowLogic<Void> {
private final Integer iouValue;
private final Party otherParty;
/**
* The progress tracker provides checkpoints indicating the progress of the flow to observers.
*/
private final ProgressTracker progressTracker = new ProgressTracker();
public IOUFlow(Integer iouValue, Party otherParty) {
this.iouValue = iouValue;
this.otherParty = otherParty;
}
@Override
public ProgressTracker getProgressTracker() {
return progressTracker;
}
/**
* The flow logic is encapsulated within the call() method.
*/
@Suspendable
@Override
public Void call() throws FlowException {
// DOCSTART 02
// We retrieve the notary identity from the network map.
Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0);
// We create the transaction components.
IOUState outputState = new IOUState(iouValue, getOurIdentity(), otherParty);
List<PublicKey> requiredSigners = Arrays.asList(getOurIdentity().getOwningKey(), otherParty.getOwningKey());
Command command = new Command<>(new IOUContract.Create(), requiredSigners);
// We create a transaction builder and add the components.
TransactionBuilder txBuilder = new TransactionBuilder(notary)
.addOutputState(outputState, IOUContract.ID)
.addCommand(command);
// Verifying the transaction.
txBuilder.verify(getServiceHub());
// Signing the transaction.
SignedTransaction signedTx = getServiceHub().signInitialTransaction(txBuilder);
// Creating a session with the other party.
FlowSession otherPartySession = initiateFlow(otherParty);
// Obtaining the counterparty's signature.
SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow(
signedTx, Arrays.asList(otherPartySession), CollectSignaturesFlow.tracker()));
// Finalising the transaction.
subFlow(new FinalityFlow(fullySignedTx, otherPartySession));
return null;
// DOCEND 02
}
}

View File

@ -1,49 +0,0 @@
package net.corda.docs.java.tutorial.twoparty;
import co.paralleluniverse.fibers.Suspendable;
import net.corda.core.contracts.ContractState;
import net.corda.core.crypto.SecureHash;
import net.corda.core.flows.*;
import net.corda.core.transactions.SignedTransaction;
import static net.corda.core.contracts.ContractsDSL.requireThat;
@SuppressWarnings("unused")
// Define IOUFlowResponder:
@InitiatedBy(IOUFlow.class)
public class IOUFlowResponder extends FlowLogic<Void> {
private final FlowSession otherPartySession;
public IOUFlowResponder(FlowSession otherPartySession) {
this.otherPartySession = otherPartySession;
}
// DOCSTART 1
@Suspendable
@Override
public Void call() throws FlowException {
class SignTxFlow extends SignTransactionFlow {
private SignTxFlow(FlowSession otherPartySession) {
super(otherPartySession);
}
@Override
protected void checkTransaction(SignedTransaction stx) {
requireThat(require -> {
ContractState output = stx.getTx().getOutputs().get(0).getData();
require.using("This must be an IOU transaction.", output instanceof IOUState);
IOUState iou = (IOUState) output;
require.using("The IOU's value can't be too high.", iou.getValue() < 100);
return null;
});
}
}
SecureHash expectedTxId = subFlow(new SignTxFlow(otherPartySession)).getId();
subFlow(new ReceiveFinalityFlow(otherPartySession, expectedTxId));
return null;
}
// DOCEND 1
}

View File

@ -1,37 +0,0 @@
package net.corda.docs.java.tutorial.twoparty;
import net.corda.core.contracts.ContractState;
import net.corda.core.identity.AbstractParty;
import net.corda.core.identity.Party;
import java.util.Arrays;
import java.util.List;
public class IOUState implements ContractState {
private final int value;
private final Party lender;
private final Party borrower;
public IOUState(int value, Party lender, Party borrower) {
this.value = value;
this.lender = lender;
this.borrower = borrower;
}
public int getValue() {
return value;
}
public Party getLender() {
return lender;
}
public Party getBorrower() {
return borrower;
}
@Override
public List<AbstractParty> getParticipants() {
return Arrays.asList(lender, borrower);
}
}

View File

@ -1,31 +0,0 @@
@file:Suppress("unused")
package net.corda.docs.kotlin
// START 1
import net.corda.client.rpc.CordaRPCClient
import net.corda.core.utilities.NetworkHostAndPort.Companion.parse
import net.corda.core.utilities.loggerFor
import org.slf4j.Logger
class ClientRpcExample {
companion object {
val logger: Logger = loggerFor<ClientRpcExample>()
}
fun main(args: Array<String>) {
require(args.size == 3) { "Usage: TemplateClient <node address> <username> <password>" }
val nodeAddress = parse(args[0])
val username = args[1]
val password = args[2]
val client = CordaRPCClient(nodeAddress)
val connection = client.start(username, password)
val cordaRPCOperations = connection.proxy
logger.info(cordaRPCOperations.currentNodeTime().toString())
connection.notifyServerAndClose()
}
}
// END 1

View File

@ -1,150 +0,0 @@
@file:Suppress("unused")
package net.corda.docs.kotlin
import net.corda.client.rpc.CordaRPCClient
import net.corda.core.contracts.Amount
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.startFlow
import net.corda.core.messaging.vaultQueryBy
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SerializationWhitelist
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.OpaqueBytes
import net.corda.finance.USD
import net.corda.finance.contracts.asset.Cash
import net.corda.finance.flows.CashExitFlow
import net.corda.finance.flows.CashIssueFlow
import net.corda.finance.flows.CashPaymentFlow
import net.corda.node.services.Permissions.Companion.invokeRpc
import net.corda.node.services.Permissions.Companion.startFlow
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.driver.DriverParameters
import net.corda.testing.node.User
import net.corda.testing.driver.driver
import net.corda.testing.node.internal.FINANCE_CORDAPPS
import org.graphstream.graph.Edge
import org.graphstream.graph.Node
import org.graphstream.graph.implementations.MultiGraph
import rx.Observable
import java.nio.file.Paths
import java.util.*
import kotlin.concurrent.thread
/**
* This is example code for the Client RPC API tutorial. The START/END comments are important and used by the documentation!
*/
// START 1
enum class PrintOrVisualise {
Print,
Visualise
}
@Suppress("DEPRECATION")
fun main(args: Array<String>) {
require(args.isNotEmpty()) { "Usage: <binary> [Print|Visualise]" }
val printOrVisualise = PrintOrVisualise.valueOf(args[0])
val baseDirectory = Paths.get("build/rpc-api-tutorial")
val user = User("user", "password", permissions = setOf(startFlow<CashIssueFlow>(),
startFlow<CashPaymentFlow>(),
startFlow<CashExitFlow>(),
invokeRpc(CordaRPCOps::nodeInfo)
))
driver(DriverParameters(driverDirectory = baseDirectory, cordappsForAllNodes = FINANCE_CORDAPPS, waitForAllNodesToFinish = true)) {
val node = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).get()
// END 1
// START 2
val client = CordaRPCClient(node.rpcAddress)
val proxy = client.start("user", "password").proxy
thread {
generateTransactions(proxy)
}
// END 2
// START 3
val (transactions: List<SignedTransaction>, futureTransactions: Observable<SignedTransaction>) = proxy.internalVerifiedTransactionsFeed()
// END 3
// START 4
when (printOrVisualise) {
PrintOrVisualise.Print -> {
futureTransactions.startWith(transactions).subscribe { transaction ->
println("NODE ${transaction.id}")
transaction.tx.inputs.forEach { (txhash) ->
println("EDGE $txhash ${transaction.id}")
}
}
}
// END 4
// START 5
PrintOrVisualise.Visualise -> {
val graph = MultiGraph("transactions")
transactions.forEach { transaction ->
graph.addNode<Node>("${transaction.id}")
}
transactions.forEach { transaction ->
transaction.tx.inputs.forEach { ref ->
graph.addEdge<Edge>("$ref", "${ref.txhash}", "${transaction.id}")
}
}
futureTransactions.subscribe { transaction ->
graph.addNode<Node>("${transaction.id}")
transaction.tx.inputs.forEach { ref ->
graph.addEdge<Edge>("$ref", "${ref.txhash}", "${transaction.id}")
}
}
graph.display()
}
}
// END 5
Unit
}
}
// START 6
fun generateTransactions(proxy: CordaRPCOps) {
val vault = proxy.vaultQueryBy<Cash.State>().states
var ownedQuantity = vault.fold(0L) { sum, state ->
sum + state.state.data.amount.quantity
}
val issueRef = OpaqueBytes.of(0)
val notary = proxy.notaryIdentities().first()
val me = proxy.nodeInfo().legalIdentities.first()
while (true) {
Thread.sleep(1000)
val random = SplittableRandom()
val n = random.nextDouble()
if (ownedQuantity > 10000 && n > 0.8) {
val quantity = Math.abs(random.nextLong()) % 2000
proxy.startFlow(::CashExitFlow, Amount(quantity, USD), issueRef)
ownedQuantity -= quantity
} else if (ownedQuantity > 1000 && n < 0.7) {
val quantity = Math.abs(random.nextLong() % Math.min(ownedQuantity, 2000))
proxy.startFlow(::CashPaymentFlow, Amount(quantity, USD), me)
} else {
val quantity = Math.abs(random.nextLong() % 1000)
proxy.startFlow(::CashIssueFlow, Amount(quantity, USD), issueRef, notary)
ownedQuantity += quantity
}
}
}
// END 6
// START 7
// Not annotated, so need to whitelist manually.
data class ExampleRPCValue(val foo: String)
// Annotated, so no need to whitelist manually.
@CordaSerializable
data class ExampleRPCValue2(val bar: Int)
class ExampleRPCSerializationWhitelist : SerializationWhitelist {
// Add classes like this.
override val whitelist = listOf(ExampleRPCValue::class.java)
}
// END 7

View File

@ -1,93 +0,0 @@
@file:Suppress("DEPRECATION", "unused", "UNUSED_PARAMETER")
package net.corda.docs.kotlin
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.flows.*
import net.corda.core.identity.Party
import net.corda.core.transactions.SignedTransaction
private fun dummyTransactionWithParticipant(party: Party): SignedTransaction = TODO()
// DOCSTART SimpleFlowUsingOldApi
class SimpleFlowUsingOldApi(private val counterparty: Party) : FlowLogic<SignedTransaction>() {
@Suspendable
override fun call(): SignedTransaction {
val stx = dummyTransactionWithParticipant(counterparty)
return subFlow(FinalityFlow(stx))
}
}
// DOCEND SimpleFlowUsingOldApi
// DOCSTART SimpleFlowUsingNewApi
// Notice how the flow *must* now be an initiating flow even when it wasn't before.
@InitiatingFlow
class SimpleFlowUsingNewApi(private val counterparty: Party) : FlowLogic<SignedTransaction>() {
@Suspendable
override fun call(): SignedTransaction {
val stx = dummyTransactionWithParticipant(counterparty)
// For each non-local participant in the transaction we must initiate a flow session with them.
val session = initiateFlow(counterparty)
return subFlow(FinalityFlow(stx, session))
}
}
// DOCEND SimpleFlowUsingNewApi
// DOCSTART SimpleNewResponderFlow
// All participants will run this flow to receive and record the finalised transaction into their vault.
@InitiatedBy(SimpleFlowUsingNewApi::class)
class SimpleNewResponderFlow(private val otherSide: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
subFlow(ReceiveFinalityFlow(otherSide))
}
}
// DOCEND SimpleNewResponderFlow
// DOCSTART ExistingInitiatingFlow
// Assuming the previous version of the flow was 1 (the default if none is specified), we increment the version number to 2
// to allow for backwards compatibility with nodes running the old CorDapp.
@InitiatingFlow(version = 2)
class ExistingInitiatingFlow(private val counterparty: Party) : FlowLogic<SignedTransaction>() {
@Suspendable
override fun call(): SignedTransaction {
val partiallySignedTx = dummyTransactionWithParticipant(counterparty)
val session = initiateFlow(counterparty)
val fullySignedTx = subFlow(CollectSignaturesFlow(partiallySignedTx, listOf(session)))
// Determine which version of the flow that other side is using.
return if (session.getCounterpartyFlowInfo().flowVersion == 1) {
// Use the old API if the other side is using the previous version of the flow.
subFlow(FinalityFlow(fullySignedTx))
} else {
// Otherwise they're at least on version 2 and so we can send the finalised transaction on the existing session.
subFlow(FinalityFlow(fullySignedTx, session))
}
}
}
// DOCEND ExistingInitiatingFlow
@InitiatedBy(ExistingInitiatingFlow::class)
class ExistingResponderFlow(private val otherSide: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
// DOCSTART ExistingResponderFlow
// First we have to run the SignTransactionFlow, which will return a SignedTransaction.
val txWeJustSigned = subFlow(object : SignTransactionFlow(otherSide) {
@Suspendable
override fun checkTransaction(stx: SignedTransaction) {
// Implement responder flow transaction checks here
}
})
if (otherSide.getCounterpartyFlowInfo().flowVersion >= 2) {
// The other side is not using the old CorDapp so call ReceiveFinalityFlow to record the finalised transaction.
// If SignTransactionFlow is used then we can verify the tranaction we receive for recording is the same one
// that was just signed.
subFlow(ReceiveFinalityFlow(otherSide, expectedTxId = txWeJustSigned.id))
} else {
// Otherwise the other side is running the old CorDapp and so we don't need to do anything further. The node
// will automatically record the finalised transaction using the old insecure mechanism.
}
// DOCEND ExistingResponderFlow
}
}

View File

@ -1,664 +0,0 @@
@file:Suppress("UNUSED_VARIABLE", "unused", "DEPRECATION")
package net.corda.docs.kotlin
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.TransactionSignature
import net.corda.core.crypto.generateKeyPair
import net.corda.core.flows.*
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.FetchDataFlow
import net.corda.core.node.services.Vault.Page
import net.corda.core.node.services.queryBy
import net.corda.core.node.services.vault.QueryCriteria.VaultQueryCriteria
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.ProgressTracker.Step
import net.corda.core.utilities.UntrustworthyData
import net.corda.core.utilities.seconds
import net.corda.core.utilities.unwrap
import net.corda.finance.contracts.asset.Cash
import net.corda.testing.contracts.DummyContract
import net.corda.testing.contracts.DummyState
import java.security.PublicKey
import java.security.Signature
import java.time.Instant
// ``InitiatorFlow`` is our first flow, and will communicate with
// ``ResponderFlow``, below.
// We mark ``InitiatorFlow`` as an ``InitiatingFlow``, allowing it to be
// started directly by the node.
@InitiatingFlow
// We also mark ``InitiatorFlow`` as ``StartableByRPC``, allowing the
// node's owner to start the flow via RPC.
@StartableByRPC
// Every flow must subclass ``FlowLogic``. The generic indicates the
// flow's return type.
class InitiatorFlow(val arg1: Boolean, val arg2: Int, private val counterparty: Party, val regulator: Party) : FlowLogic<Unit>() {
/**---------------------------------
* WIRING UP THE PROGRESS TRACKER *
---------------------------------**/
// Giving our flow a progress tracker allows us to see the flow's
// progress visually in our node's CRaSH shell.
// DOCSTART 17
companion object {
object ID_OTHER_NODES : Step("Identifying other nodes on the network.")
object SENDING_AND_RECEIVING_DATA : Step("Sending data between parties.")
object EXTRACTING_VAULT_STATES : Step("Extracting states from the vault.")
object OTHER_TX_COMPONENTS : Step("Gathering a transaction's other components.")
object TX_BUILDING : Step("Building a transaction.")
object TX_SIGNING : Step("Signing a transaction.")
object TX_VERIFICATION : Step("Verifying a transaction.")
object SIGS_GATHERING : Step("Gathering a transaction's signatures.") {
// Wiring up a child progress tracker allows us to see the
// subflow's progress steps in our flow's progress tracker.
override fun childProgressTracker() = CollectSignaturesFlow.tracker()
}
object VERIFYING_SIGS : Step("Verifying a transaction's signatures.")
object FINALISATION : Step("Finalising a transaction.") {
override fun childProgressTracker() = FinalityFlow.tracker()
}
fun tracker() = ProgressTracker(
ID_OTHER_NODES,
SENDING_AND_RECEIVING_DATA,
EXTRACTING_VAULT_STATES,
OTHER_TX_COMPONENTS,
TX_BUILDING,
TX_SIGNING,
TX_VERIFICATION,
SIGS_GATHERING,
VERIFYING_SIGS,
FINALISATION
)
}
// DOCEND 17
override val progressTracker: ProgressTracker = tracker()
@Suppress("RemoveExplicitTypeArguments")
@Suspendable
override fun call() {
// We'll be using a dummy public key for demonstration purposes.
val dummyPubKey: PublicKey = generateKeyPair().public
/**--------------------------
* IDENTIFYING OTHER NODES *
--------------------------**/
// DOCSTART 18
progressTracker.currentStep = ID_OTHER_NODES
// DOCEND 18
// Every transaction needs a notary:
// - To prevent double-spends if the transaction has inputs
// - To serve as a timestamping authority if the transaction has a
// time-window
// We retrieve the notary from the network map.
// DOCSTART 01
val notaryName: CordaX500Name = CordaX500Name(
organisation = "Notary Service",
locality = "London",
country = "GB")
val notary: Party = serviceHub.networkMapCache.getNotary(notaryName)!!
// DOCEND 01
// We may also need to identify a specific counterparty. We do so
// using the identity service.
// DOCSTART 02
val counterpartyName: CordaX500Name = CordaX500Name(
organisation = "NodeA",
locality = "London",
country = "GB")
val namedCounterparty: Party = serviceHub.identityService.wellKnownPartyFromX500Name(counterpartyName) ?:
throw IllegalArgumentException("Couldn't find counterparty for NodeA in identity service")
val keyedCounterparty: Party = serviceHub.identityService.partyFromKey(dummyPubKey) ?:
throw IllegalArgumentException("Couldn't find counterparty with key: $dummyPubKey in identity service")
// DOCEND 02
/**-----------------------------
* SENDING AND RECEIVING DATA *
-----------------------------**/
progressTracker.currentStep = SENDING_AND_RECEIVING_DATA
// We start by initiating a flow session with the counterparty. We
// will use this session to send and receive messages from the
// counterparty.
// DOCSTART initiateFlow
val counterpartySession: FlowSession = initiateFlow(counterparty)
// DOCEND initiateFlow
// We can send arbitrary data to a counterparty.
// If this is the first ``send``, the counterparty will either:
// 1. Ignore the message if they are not registered to respond
// to messages from this flow.
// 2. Start the flow they have registered to respond to this flow,
// and run the flow until the first call to ``receive``, at
// which point they process the message.
// In other words, we are assuming that the counterparty is
// registered to respond to this flow, and has a corresponding
// ``receive`` call.
// DOCSTART 04
counterpartySession.send(Any())
// DOCEND 04
// We can wait to receive arbitrary data of a specific type from a
// counterparty. Again, this implies a corresponding ``send`` call
// in the counterparty's flow. A few scenarios:
// - We never receive a message back. In the current design, the
// flow is paused until the node's owner kills the flow.
// - Instead of sending a message back, the counterparty throws a
// ``FlowException``. This exception is propagated back to us,
// and we can use the error message to establish what happened.
// - We receive a message back, but it's of the wrong type. In
// this case, a ``FlowException`` is thrown.
// - We receive back a message of the correct type. All is good.
//
// Upon calling ``receive()`` (or ``sendAndReceive()``), the
// ``FlowLogic`` is suspended until it receives a response.
//
// We receive the data wrapped in an ``UntrustworthyData``
// instance. This is a reminder that the data we receive may not
// be what it appears to be! We must unwrap the
// ``UntrustworthyData`` using a lambda.
// DOCSTART 05
val packet1: UntrustworthyData<Int> = counterpartySession.receive<Int>()
val int: Int = packet1.unwrap { data ->
// Perform checking on the object received.
// T O D O: Check the received object.
// Return the object.
data
}
// DOCEND 05
// We can also use a single call to send data to a counterparty
// and wait to receive data of a specific type back. The type of
// data sent doesn't need to match the type of the data received
// back.
// DOCSTART 07
val packet2: UntrustworthyData<Boolean> = counterpartySession.sendAndReceive<Boolean>("You can send and receive any class!")
val boolean: Boolean = packet2.unwrap { data ->
// Perform checking on the object received.
// T O D O: Check the received object.
// Return the object.
data
}
// DOCEND 07
// We're not limited to sending to and receiving from a single
// counterparty. A flow can send messages to as many parties as it
// likes, and each party can invoke a different response flow.
// DOCSTART 06
val regulatorSession: FlowSession = initiateFlow(regulator)
regulatorSession.send(Any())
val packet3: UntrustworthyData<Any> = regulatorSession.receive<Any>()
// DOCEND 06
// We may also batch receives in order to increase performance. This
// ensures that only a single checkpoint is created for all received
// messages.
// Type-safe variant:
val signatures: List<UntrustworthyData<Signature>> =
receiveAll(Signature::class.java, listOf(counterpartySession, regulatorSession))
// Dynamic variant:
val messages: Map<FlowSession, UntrustworthyData<*>> =
receiveAllMap(mapOf(
counterpartySession to Boolean::class.java,
regulatorSession to String::class.java
))
/**-----------------------------------
* EXTRACTING STATES FROM THE VAULT *
-----------------------------------**/
progressTracker.currentStep = EXTRACTING_VAULT_STATES
// Let's assume there are already some ``DummyState``s in our
// node's vault, stored there as a result of running past flows,
// and we want to consume them in a transaction. There are many
// ways to extract these states from our vault.
// For example, we would extract any unconsumed ``DummyState``s
// from our vault as follows:
val criteria: VaultQueryCriteria = VaultQueryCriteria() // default is UNCONSUMED
val results: Page<DummyState> = serviceHub.vaultService.queryBy<DummyState>(criteria)
val dummyStates: List<StateAndRef<DummyState>> = results.states
// For a full list of the available ways of extracting states from
// the vault, see the Vault Query docs page.
// When building a transaction, input states are passed in as
// ``StateRef`` instances, which pair the hash of the transaction
// that generated the state with the state's index in the outputs
// of that transaction. In practice, we'd pass the transaction hash
// or the ``StateRef`` as a parameter to the flow, or extract the
// ``StateRef`` from our vault.
// DOCSTART 20
val ourStateRef: StateRef = StateRef(SecureHash.sha256("DummyTransactionHash"), 0)
// DOCEND 20
// A ``StateAndRef`` pairs a ``StateRef`` with the state it points to.
// DOCSTART 21
val ourStateAndRef: StateAndRef<DummyState> = serviceHub.toStateAndRef<DummyState>(ourStateRef)
// DOCEND 21
/**-----------------------------------------
* GATHERING OTHER TRANSACTION COMPONENTS *
-----------------------------------------**/
progressTracker.currentStep = OTHER_TX_COMPONENTS
// Reference input states are constructed from StateAndRefs.
// DOCSTART 55
val referenceState: ReferencedStateAndRef<DummyState> = ourStateAndRef.referenced()
// DOCEND 55
// Output states are constructed from scratch.
// DOCSTART 22
val ourOutputState: DummyState = DummyState()
// DOCEND 22
// Or as copies of other states with some properties changed.
@Suppress("MagicNumber") // literally a magic number
// DOCSTART 23
val ourOtherOutputState: DummyState = ourOutputState.copy(magicNumber = 77)
// DOCEND 23
// We then need to pair our output state with a contract.
// DOCSTART 47
val ourOutput: StateAndContract = StateAndContract(ourOutputState, DummyContract.PROGRAM_ID)
// DOCEND 47
// Commands pair a ``CommandData`` instance with a list of
// public keys. To be valid, the transaction requires a signature
// matching every public key in all of the transaction's commands.
// DOCSTART 24
val commandData: DummyContract.Commands.Create = DummyContract.Commands.Create()
val ourPubKey: PublicKey = serviceHub.myInfo.legalIdentitiesAndCerts.first().owningKey
val counterpartyPubKey: PublicKey = counterparty.owningKey
val requiredSigners: List<PublicKey> = listOf(ourPubKey, counterpartyPubKey)
val ourCommand: Command<DummyContract.Commands.Create> = Command(commandData, requiredSigners)
// DOCEND 24
// ``CommandData`` can either be:
// 1. Of type ``TypeOnlyCommandData``, in which case it only
// serves to attach signers to the transaction and possibly
// fork the contract's verification logic.
val typeOnlyCommandData: TypeOnlyCommandData = DummyContract.Commands.Create()
// 2. Include additional data which can be used by the contract
// during verification, alongside fulfilling the roles above.
val commandDataWithData: CommandData = Cash.Commands.Issue()
// Attachments are identified by their hash.
// The attachment with the corresponding hash must have been
// uploaded ahead of time via the node's RPC interface.
// DOCSTART 25
val ourAttachment: SecureHash = SecureHash.sha256("DummyAttachment")
// DOCEND 25
// Time windows can have a start and end time, or be open at either end.
// DOCSTART 26
val ourTimeWindow: TimeWindow = TimeWindow.between(Instant.MIN, Instant.MAX)
val ourAfter: TimeWindow = TimeWindow.fromOnly(Instant.MIN)
val ourBefore: TimeWindow = TimeWindow.untilOnly(Instant.MAX)
// DOCEND 26
// We can also define a time window as an ``Instant`` +/- a time
// tolerance (e.g. 30 seconds):
// DOCSTART 42
val ourTimeWindow2: TimeWindow = TimeWindow.withTolerance(serviceHub.clock.instant(), 30.seconds)
// DOCEND 42
// Or as a start-time plus a duration:
// DOCSTART 43
val ourTimeWindow3: TimeWindow = TimeWindow.fromStartAndDuration(serviceHub.clock.instant(), 30.seconds)
// DOCEND 43
/**-----------------------
* TRANSACTION BUILDING *
-----------------------**/
progressTracker.currentStep = TX_BUILDING
// If our transaction has input states or a time-window, we must instantiate it with a
// notary.
// DOCSTART 19
val txBuilder: TransactionBuilder = TransactionBuilder(notary)
// DOCEND 19
// Otherwise, we can choose to instantiate it without one:
// DOCSTART 46
val txBuilderNoNotary: TransactionBuilder = TransactionBuilder()
// DOCEND 46
// We add items to the transaction builder using ``TransactionBuilder.withItems``:
// DOCSTART 27
txBuilder.withItems(
// Inputs, as ``StateAndRef``s that reference the outputs of previous transactions
ourStateAndRef,
// Outputs, as ``StateAndContract``s
ourOutput,
// Commands, as ``Command``s
ourCommand,
// Attachments, as ``SecureHash``es
ourAttachment,
// A time-window, as ``TimeWindow``
ourTimeWindow
)
// DOCEND 27
// We can also add items using methods for the individual components.
// The individual methods for adding input states and attachments:
// DOCSTART 28
txBuilder.addInputState(ourStateAndRef)
txBuilder.addAttachment(ourAttachment)
// DOCEND 28
// An output state can be added as a ``ContractState``, contract class name and notary.
// DOCSTART 49
txBuilder.addOutputState(ourOutputState, DummyContract.PROGRAM_ID, notary)
// DOCEND 49
// We can also leave the notary field blank, in which case the transaction's default
// notary is used.
// DOCSTART 50
txBuilder.addOutputState(ourOutputState, DummyContract.PROGRAM_ID)
// DOCEND 50
// Or we can add the output state as a ``TransactionState``, which already specifies
// the output's contract and notary.
// DOCSTART 51
val txState: TransactionState<DummyState> = TransactionState(ourOutputState, DummyContract.PROGRAM_ID, notary)
// DOCEND 51
// Commands can be added as ``Command``s.
// DOCSTART 52
txBuilder.addCommand(ourCommand)
// DOCEND 52
// Or as ``CommandData`` and a ``vararg PublicKey``.
// DOCSTART 53
txBuilder.addCommand(commandData, ourPubKey, counterpartyPubKey)
// DOCEND 53
// We can set a time-window directly.
// DOCSTART 44
txBuilder.setTimeWindow(ourTimeWindow)
// DOCEND 44
// Or as a start time plus a duration (e.g. 45 seconds).
// DOCSTART 45
txBuilder.setTimeWindow(serviceHub.clock.instant(), 45.seconds)
// DOCEND 45
/**----------------------
* TRANSACTION SIGNING *
----------------------**/
progressTracker.currentStep = TX_SIGNING
// We finalise the transaction by signing it, converting it into a
// ``SignedTransaction``.
// DOCSTART 29
val onceSignedTx: SignedTransaction = serviceHub.signInitialTransaction(txBuilder)
// DOCEND 29
// We can also sign the transaction using a different public key:
// DOCSTART 30
val otherIdentity: PartyAndCertificate = serviceHub.keyManagementService.freshKeyAndCert(ourIdentityAndCert, false)
val onceSignedTx2: SignedTransaction = serviceHub.signInitialTransaction(txBuilder, otherIdentity.owningKey)
// DOCEND 30
// If instead this was a ``SignedTransaction`` that we'd received
// from a counterparty and we needed to sign it, we would add our
// signature using:
// DOCSTART 38
val twiceSignedTx: SignedTransaction = serviceHub.addSignature(onceSignedTx)
// DOCEND 38
// Or, if we wanted to use a different public key:
val otherIdentity2: PartyAndCertificate = serviceHub.keyManagementService.freshKeyAndCert(ourIdentityAndCert, false)
// DOCSTART 39
val twiceSignedTx2: SignedTransaction = serviceHub.addSignature(onceSignedTx, otherIdentity2.owningKey)
// DOCEND 39
// We can also generate a signature over the transaction without
// adding it to the transaction itself. We may do this when
// sending just the signature in a flow instead of returning the
// entire transaction with our signature. This way, the receiving
// node does not need to check we haven't changed anything in the
// transaction.
// DOCSTART 40
val sig: TransactionSignature = serviceHub.createSignature(onceSignedTx)
// DOCEND 40
// And again, if we wanted to use a different public key:
// DOCSTART 41
val sig2: TransactionSignature = serviceHub.createSignature(onceSignedTx, otherIdentity2.owningKey)
// DOCEND 41
// In practice, however, the process of gathering every signature
// but the first can be automated using ``CollectSignaturesFlow``.
// See the "Gathering Signatures" section below.
/**---------------------------
* TRANSACTION VERIFICATION *
---------------------------**/
progressTracker.currentStep = TX_VERIFICATION
// Verifying a transaction will also verify every transaction in
// the transaction's dependency chain, which will require
// transaction data access on counterparty's node. The
// ``SendTransactionFlow`` can be used to automate the sending and
// data vending process. The ``SendTransactionFlow`` will listen
// for data request until the transaction is resolved and verified
// on the other side:
// DOCSTART 12
subFlow(SendTransactionFlow(counterpartySession, twiceSignedTx))
// Optional request verification to further restrict data access.
subFlow(object : SendTransactionFlow(counterpartySession, twiceSignedTx) {
override fun verifyDataRequest(dataRequest: FetchDataFlow.Request.Data) {
// Extra request verification.
}
})
// DOCEND 12
// We can receive the transaction using ``ReceiveTransactionFlow``,
// which will automatically download all the dependencies and verify
// the transaction
// DOCSTART 13
val verifiedTransaction = subFlow(ReceiveTransactionFlow(counterpartySession))
// DOCEND 13
// We can also send and receive a `StateAndRef` dependency chain
// and automatically resolve its dependencies.
// DOCSTART 14
subFlow(SendStateAndRefFlow(counterpartySession, dummyStates))
// On the receive side ...
val resolvedStateAndRef = subFlow(ReceiveStateAndRefFlow<DummyState>(counterpartySession))
// DOCEND 14
// We can now verify the transaction to ensure that it satisfies
// the contracts of all the transaction's input and output states.
// DOCSTART 33
twiceSignedTx.verify(serviceHub)
// DOCEND 33
// We'll often want to perform our own additional verification
// too. Just because a transaction is valid based on the contract
// rules and requires our signature doesn't mean we have to
// sign it! We need to make sure the transaction represents an
// agreement we actually want to enter into.
// To do this, we need to convert our ``SignedTransaction``
// into a ``LedgerTransaction``. This will use our ServiceHub
// to resolve the transaction's inputs and attachments into
// actual objects, rather than just references.
// DOCSTART 32
val ledgerTx: LedgerTransaction = twiceSignedTx.toLedgerTransaction(serviceHub)
// DOCEND 32
// We can now perform our additional verification.
// DOCSTART 34
val outputState: DummyState = ledgerTx.outputsOfType<DummyState>().single()
if (outputState.magicNumber == 777) {
// ``FlowException`` is a special exception type. It will be
// propagated back to any counterparty flows waiting for a
// message from this flow, notifying them that the flow has
// failed.
throw FlowException("We expected a magic number of 777.")
}
// DOCEND 34
// Of course, if you are not a required signer on the transaction,
// you have no power to decide whether it is valid or not. If it
// requires signatures from all the required signers and is
// contractually valid, it's a valid ledger update.
/**-----------------------
* GATHERING SIGNATURES *
-----------------------**/
progressTracker.currentStep = SIGS_GATHERING
// The list of parties who need to sign a transaction is dictated
// by the transaction's commands. Once we've signed a transaction
// ourselves, we can automatically gather the signatures of the
// other required signers using ``CollectSignaturesFlow``.
// The responder flow will need to call ``SignTransactionFlow``.
// DOCSTART 15
val fullySignedTx: SignedTransaction = subFlow(CollectSignaturesFlow(twiceSignedTx, setOf(counterpartySession, regulatorSession), SIGS_GATHERING.childProgressTracker()))
// DOCEND 15
/**-----------------------
* VERIFYING SIGNATURES *
-----------------------**/
progressTracker.currentStep = VERIFYING_SIGS
// We can verify that a transaction has all the required
// signatures, and that they're all valid, by running:
// DOCSTART 35
fullySignedTx.verifyRequiredSignatures()
// DOCEND 35
// If the transaction is only partially signed, we have to pass in
// a vararg of the public keys corresponding to the missing
// signatures, explicitly telling the system not to check them.
// DOCSTART 36
onceSignedTx.verifySignaturesExcept(counterpartyPubKey)
// DOCEND 36
// There is also an overload of ``verifySignaturesExcept`` which accepts
// a ``Collection`` of the public keys corresponding to the missing
// signatures.
// DOCSTART 54
onceSignedTx.verifySignaturesExcept(listOf(counterpartyPubKey))
// DOCEND 54
// We can also choose to only check the signatures that are
// present. BE VERY CAREFUL - this function provides no guarantees
// that the signatures are correct, or that none are missing.
// DOCSTART 37
twiceSignedTx.checkSignaturesAreValid()
// DOCEND 37
/**-----------------------------
* FINALISING THE TRANSACTION *
-----------------------------**/
progressTracker.currentStep = FINALISATION
// We notarise the transaction and get it recorded in the vault of
// the participants of all the transaction's states.
// DOCSTART 09
val notarisedTx1: SignedTransaction = subFlow(FinalityFlow(fullySignedTx, listOf(counterpartySession), FINALISATION.childProgressTracker()))
// DOCEND 09
// We can also choose to send it to additional parties who aren't one
// of the state's participants.
// DOCSTART 10
val partySessions: List<FlowSession> = listOf(counterpartySession, initiateFlow(regulator))
val notarisedTx2: SignedTransaction = subFlow(FinalityFlow(fullySignedTx, partySessions, FINALISATION.childProgressTracker()))
// DOCEND 10
// DOCSTART FlowSession porting
send(regulator, Any()) // Old API
// becomes
val session = initiateFlow(regulator)
session.send(Any())
// DOCEND FlowSession porting
}
}
// ``ResponderFlow`` is our second flow, and will communicate with
// ``InitiatorFlow``.
// We mark ``ResponderFlow`` as an ``InitiatedByFlow``, meaning that it
// can only be started in response to a message from its initiating flow.
// That's ``InitiatorFlow`` in this case.
// Each node also has several flow pairs registered by default - see
// ``AbstractNode.installCoreFlows``.
@InitiatedBy(InitiatorFlow::class)
class ResponderFlow(val counterpartySession: FlowSession) : FlowLogic<Unit>() {
companion object {
object RECEIVING_AND_SENDING_DATA : Step("Sending data between parties.")
object SIGNING : Step("Responding to CollectSignaturesFlow.")
object FINALISATION : Step("Finalising a transaction.")
fun tracker() = ProgressTracker(
RECEIVING_AND_SENDING_DATA,
SIGNING,
FINALISATION
)
}
override val progressTracker: ProgressTracker = tracker()
@Suspendable
override fun call() {
// The ``ResponderFlow` has all the same APIs available. It looks
// up network information, sends and receives data, and constructs
// transactions in exactly the same way.
/**-----------------------------
* SENDING AND RECEIVING DATA *
-----------------------------**/
progressTracker.currentStep = RECEIVING_AND_SENDING_DATA
// We need to respond to the messages sent by the initiator:
// 1. They sent us an ``Any`` instance
// 2. They waited to receive an ``Integer`` instance back
// 3. They sent a ``String`` instance and waited to receive a
// ``Boolean`` instance back
// Our side of the flow must mirror these calls.
// DOCSTART 08
val any: Any = counterpartySession.receive<Any>().unwrap { data -> data }
val string: String = counterpartySession.sendAndReceive<String>(99).unwrap { data -> data }
counterpartySession.send(true)
// DOCEND 08
/**----------------------------------------
* RESPONDING TO COLLECT_SIGNATURES_FLOW *
----------------------------------------**/
progressTracker.currentStep = SIGNING
// The responder will often need to respond to a call to
// ``CollectSignaturesFlow``. It does so my invoking its own
// ``SignTransactionFlow`` subclass.
// DOCSTART 16
val signTransactionFlow: SignTransactionFlow = object : SignTransactionFlow(counterpartySession) {
override fun checkTransaction(stx: SignedTransaction) = requireThat {
// Any additional checking we see fit...
val outputState = stx.tx.outputsOfType<DummyState>().single()
require(outputState.magicNumber == 777)
}
}
val idOfTxWeSigned = subFlow(signTransactionFlow).id
// DOCEND 16
/**-----------------------------
* FINALISING THE TRANSACTION *
-----------------------------**/
progressTracker.currentStep = FINALISATION
// As the final step the responder waits to receive the notarised transaction from the sending party
// Since it knows the ID of the transaction it just signed, the transaction ID is specified to ensure the correct
// transaction is received and recorded.
// DOCSTART ReceiveFinalityFlow
subFlow(ReceiveFinalityFlow(counterpartySession, expectedTxId = idOfTxWeSigned))
// DOCEND ReceiveFinalityFlow
}
}

View File

@ -1,259 +0,0 @@
package net.corda.docs.kotlin
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.Amount
import net.corda.core.contracts.Issued
import net.corda.core.contracts.StateAndContract
import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.withoutIssuer
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.TransactionSignature
import net.corda.core.flows.FinalityFlow
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowSession
import net.corda.core.flows.InitiatedBy
import net.corda.core.flows.InitiatingFlow
import net.corda.core.flows.ReceiveFinalityFlow
import net.corda.core.flows.ReceiveStateAndRefFlow
import net.corda.core.flows.ReceiveTransactionFlow
import net.corda.core.flows.SendStateAndRefFlow
import net.corda.core.flows.SendTransactionFlow
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party
import net.corda.core.node.ServiceHub
import net.corda.core.node.services.vault.QueryCriteria
import net.corda.core.node.services.vault.builder
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.unwrap
import net.corda.finance.contracts.asset.Cash
import net.corda.finance.schemas.CashSchemaV1
import net.corda.testing.core.singleIdentity
import java.util.*
@CordaSerializable
private data class FxRequest(val tradeId: String,
val amount: Amount<Issued<Currency>>,
val owner: Party,
val counterparty: Party,
val notary: Party)
// DOCSTART 1
// This is equivalent to the Cash.generateSpend
// Which is brought here to make the filtering logic more visible in the example
private fun gatherOurInputs(serviceHub: ServiceHub,
lockId: UUID,
amountRequired: Amount<Issued<Currency>>,
notary: Party?): Pair<List<StateAndRef<Cash.State>>, Long> {
// extract our identity for convenience
val ourKeys = serviceHub.keyManagementService.keys
val ourParties = ourKeys.map { serviceHub.identityService.partyFromKey(it) ?: throw IllegalStateException("Unable to resolve party from key") }
val fungibleCriteria = QueryCriteria.FungibleAssetQueryCriteria(owner = ourParties)
val vaultCriteria: QueryCriteria = QueryCriteria.VaultQueryCriteria(notary = listOf(notary as AbstractParty))
val logicalExpression = builder { CashSchemaV1.PersistentCashState::currency.equal(amountRequired.token.product.currencyCode) }
val cashCriteria = QueryCriteria.VaultCustomQueryCriteria(logicalExpression)
val fullCriteria = fungibleCriteria.and(vaultCriteria).and(cashCriteria)
val eligibleStates = serviceHub.vaultService.tryLockFungibleStatesForSpending(lockId, fullCriteria, amountRequired.withoutIssuer(), Cash.State::class.java)
check(eligibleStates.isNotEmpty()) { "Insufficient funds" }
val amount = eligibleStates.fold(0L) { tot, (state) -> tot + state.data.amount.quantity }
val change = amount - amountRequired.quantity
return Pair(eligibleStates, change)
}
// DOCEND 1
private fun prepareOurInputsAndOutputs(serviceHub: ServiceHub, lockId: UUID, request: FxRequest): Pair<List<StateAndRef<Cash.State>>, List<Cash.State>> {
// Create amount with correct issuer details
val sellAmount = request.amount
// DOCSTART 2
// Gather our inputs. We would normally use VaultService.generateSpend
// to carry out the build in a single step. To be more explicit
// we will use query manually in the helper function below.
// Putting this into a non-suspendable function also prevents issues when
// the flow is suspended.
val (inputs, residual) = gatherOurInputs(serviceHub, lockId, sellAmount, request.notary)
// Build and an output state for the counterparty
val transferredFundsOutput = Cash.State(sellAmount, request.counterparty)
val outputs = if (residual > 0L) {
// Build an output state for the residual change back to us
val residualAmount = Amount(residual, sellAmount.token)
val residualOutput = Cash.State(residualAmount, serviceHub.myInfo.singleIdentity())
listOf(transferredFundsOutput, residualOutput)
} else {
listOf(transferredFundsOutput)
}
return Pair(inputs, outputs)
// DOCEND 2
}
// A flow representing creating a transaction that
// carries out exchange of cash assets.
@InitiatingFlow
class ForeignExchangeFlow(private val tradeId: String,
private val baseCurrencyAmount: Amount<Issued<Currency>>,
private val quoteCurrencyAmount: Amount<Issued<Currency>>,
private val counterparty: Party,
private val weAreBaseCurrencySeller: Boolean,
private val notary: Party) : FlowLogic<SecureHash>() {
@Suspendable
override fun call(): SecureHash {
// Select correct sides of the Fx exchange to query for.
// Specifically we own the assets we wish to sell.
// Also prepare the other side query
val (localRequest, remoteRequest) = if (weAreBaseCurrencySeller) {
val local = FxRequest(tradeId, baseCurrencyAmount, ourIdentity, counterparty, notary)
val remote = FxRequest(tradeId, quoteCurrencyAmount, counterparty, ourIdentity, notary)
Pair(local, remote)
} else {
val local = FxRequest(tradeId, quoteCurrencyAmount, ourIdentity, counterparty, notary)
val remote = FxRequest(tradeId, baseCurrencyAmount, counterparty, ourIdentity, notary)
Pair(local, remote)
}
// Call the helper method to identify suitable inputs and make the outputs
val (ourInputStates, ourOutputStates) = prepareOurInputsAndOutputs(serviceHub, runId.uuid, localRequest)
// identify the notary for our states
val notary = ourInputStates.first().state.notary
// ensure request to other side is for a consistent notary
val remoteRequestWithNotary = remoteRequest.copy(notary = notary)
// Send the request to the counterparty to verify and call their version of prepareOurInputsAndOutputs
// Then they can return their candidate states
val counterpartySession = initiateFlow(counterparty)
counterpartySession.send(remoteRequestWithNotary)
val theirInputStates = subFlow(ReceiveStateAndRefFlow<Cash.State>(counterpartySession))
val theirOutputStates = counterpartySession.receive<List<Cash.State>>().unwrap {
require(theirInputStates.all { it.state.notary == notary }) {
"notary of remote states must be same as for our states"
}
require(theirInputStates.all { it.state.data.amount.token == remoteRequestWithNotary.amount.token }) {
"Inputs not of the correct currency"
}
require(it.all { it.amount.token == remoteRequestWithNotary.amount.token }) {
"Outputs not of the correct currency"
}
require(theirInputStates.map { it.state.data.amount.quantity }.sum()
>= remoteRequestWithNotary.amount.quantity) {
"the provided inputs don't provide sufficient funds"
}
val sum = it.filter { it.owner.let { it is Party && serviceHub.myInfo.isLegalIdentity(it) } }.map { it.amount.quantity }.sum()
require(sum == remoteRequestWithNotary.amount.quantity) {
"the provided outputs don't provide the request quantity"
}
it // return validated response
}
// having collated the data create the full transaction.
val signedTransaction = buildTradeProposal(ourInputStates, ourOutputStates, theirInputStates, theirOutputStates)
// pass transaction details to the counterparty to revalidate and confirm with a signature
// Allow counterparty to access our data to resolve the transaction.
subFlow(SendTransactionFlow(counterpartySession, signedTransaction))
val allPartySignedTx = counterpartySession.receive<TransactionSignature>().unwrap {
val withNewSignature = signedTransaction + it
// check all signatures are present except the notary
withNewSignature.verifySignaturesExcept(withNewSignature.tx.notary!!.owningKey)
// This verifies that the transaction is contract-valid, even though it is missing signatures.
// In a full solution there would be states tracking the trade request which
// would be included in the transaction and enforce the amounts and tradeId
withNewSignature.tx.toLedgerTransaction(serviceHub).verify()
withNewSignature // return the almost complete transaction
}
// Initiate the standard protocol to notarise and distribute to the involved parties.
subFlow(FinalityFlow(allPartySignedTx, counterpartySession))
return allPartySignedTx.id
}
// DOCSTART 3
private fun buildTradeProposal(ourInputStates: List<StateAndRef<Cash.State>>,
ourOutputState: List<Cash.State>,
theirInputStates: List<StateAndRef<Cash.State>>,
theirOutputState: List<Cash.State>): SignedTransaction {
// This is the correct way to create a TransactionBuilder,
// do not construct directly.
// We also set the notary to match the input notary
val builder = TransactionBuilder(ourInputStates.first().state.notary)
// Add the move commands and key to indicate all the respective owners and need to sign
val ourSigners = ourInputStates.map { it.state.data.owner.owningKey }.toSet()
val theirSigners = theirInputStates.map { it.state.data.owner.owningKey }.toSet()
builder.addCommand(Cash.Commands.Move(), (ourSigners + theirSigners).toList())
// Build and add the inputs and outputs
builder.withItems(*ourInputStates.toTypedArray())
builder.withItems(*theirInputStates.toTypedArray())
builder.withItems(*ourOutputState.map { StateAndContract(it, Cash.PROGRAM_ID) }.toTypedArray())
builder.withItems(*theirOutputState.map { StateAndContract(it, Cash.PROGRAM_ID) }.toTypedArray())
// We have already validated their response and trust our own data
// so we can sign. Note the returned SignedTransaction is still not fully signed
// and would not pass full verification yet.
return serviceHub.signInitialTransaction(builder, ourSigners.single())
}
// DOCEND 3
}
@InitiatedBy(ForeignExchangeFlow::class)
class ForeignExchangeRemoteFlow(private val source: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
// Initial receive from remote party
val request = source.receive<FxRequest>().unwrap {
// We would need to check that this is a known trade ID here!
// Also that the amounts and source are correct with the trade details.
// In a production system there would be other Corda contracts tracking
// the lifecycle of the Fx trades which would be included in the transaction
// Check request is for us
require(serviceHub.myInfo.isLegalIdentity(it.owner)) {
"Request does not include the correct counterparty"
}
require(source.counterparty == it.counterparty) {
"Request does not include the correct counterparty"
}
it // return validated request
}
// Gather our inputs. We would normally use VaultService.generateSpend
// to carry out the build in a single step. To be more explicit
// we will use query manually in the helper function below.
// Putting this into a non-suspendable function also prevent issues when
// the flow is suspended.
val (ourInputState, ourOutputState) = prepareOurInputsAndOutputs(serviceHub, runId.uuid, request)
// Send back our proposed states and await the full transaction to verify
val ourKey = serviceHub.keyManagementService.filterMyKeys(ourInputState.flatMap { it.state.data.participants }.map { it.owningKey }).single()
// SendStateAndRefFlow allows counterparty to access our transaction data to resolve the transaction.
subFlow(SendStateAndRefFlow(source, ourInputState))
source.send(ourOutputState)
val proposedTrade = subFlow(ReceiveTransactionFlow(source, checkSufficientSignatures = false)).let {
val wtx = it.tx
// check all signatures are present except our own and the notary
it.verifySignaturesExcept(ourKey, wtx.notary!!.owningKey)
it // return the SignedTransaction
}
// assuming we have completed state and business level validation we can sign the trade
val ourSignature = serviceHub.createSignature(proposedTrade, ourKey)
// send the other side our signature.
source.send(ourSignature)
// and then finally stored the finalised transaction into our vault
subFlow(ReceiveFinalityFlow(source))
}
}

View File

@ -1,101 +0,0 @@
@file:Suppress("DEPRECATION", "MemberVisibilityCanBePrivate", "unused")
package net.corda.docs.kotlin
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowSession
import net.corda.core.flows.InitiatedBy
import net.corda.core.flows.InitiatingFlow
import net.corda.core.identity.Party
import net.corda.core.utilities.unwrap
// DOCSTART LaunchSpaceshipFlow
@InitiatingFlow
class LaunchSpaceshipFlow : FlowLogic<Unit>() {
@Suspendable
override fun call() {
val shouldLaunchSpaceship = receive<Boolean>(getPresident()).unwrap { it }
if (shouldLaunchSpaceship) {
launchSpaceship()
}
}
fun launchSpaceship() {
}
fun getPresident(): Party {
TODO()
}
}
@InitiatedBy(LaunchSpaceshipFlow::class)
@InitiatingFlow
class PresidentSpaceshipFlow(val launcher: Party) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
val needCoffee = true
send(getSecretary(), needCoffee)
val shouldLaunchSpaceship = false
send(launcher, shouldLaunchSpaceship)
}
fun getSecretary(): Party {
TODO()
}
}
@InitiatedBy(PresidentSpaceshipFlow::class)
class SecretaryFlow(val president: Party) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
// ignore
}
}
// DOCEND LaunchSpaceshipFlow
// DOCSTART LaunchSpaceshipFlowCorrect
@InitiatingFlow
class LaunchSpaceshipFlowCorrect : FlowLogic<Unit>() {
@Suspendable
override fun call() {
val presidentSession = initiateFlow(getPresident())
val shouldLaunchSpaceship = presidentSession.receive<Boolean>().unwrap { it }
if (shouldLaunchSpaceship) {
launchSpaceship()
}
}
fun launchSpaceship() {
}
fun getPresident(): Party {
TODO()
}
}
@InitiatedBy(LaunchSpaceshipFlowCorrect::class)
@InitiatingFlow
class PresidentSpaceshipFlowCorrect(val launcherSession: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
val needCoffee = true
val secretarySession = initiateFlow(getSecretary())
secretarySession.send(needCoffee)
val shouldLaunchSpaceship = false
launcherSession.send(shouldLaunchSpaceship)
}
fun getSecretary(): Party {
TODO()
}
}
@InitiatedBy(PresidentSpaceshipFlowCorrect::class)
class SecretaryFlowCorrect(val presidentSession: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
// ignore
}
}
// DOCEND LaunchSpaceshipFlowCorrect

View File

@ -1,33 +0,0 @@
package net.corda.docs.kotlin
// DOCSTART 1
import net.corda.core.identity.CordaX500Name
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.MockNetworkParameters
import net.corda.testing.node.StartedMockNode
import net.corda.testing.node.TestCordapp.Companion.findCordapp
import org.junit.After
import org.junit.Before
class MockNetworkTestsTutorial {
private val mockNet = MockNetwork(MockNetworkParameters(listOf(findCordapp("com.mycordapp.package"))))
@After
fun cleanUp() {
mockNet.stopNodes()
}
// DOCEND 1
// DOCSTART 2
private lateinit var nodeA: StartedMockNode
private lateinit var nodeB: StartedMockNode
@Before
fun setUp() {
nodeA = mockNet.createNode()
// We can optionally give the node a name.
nodeB = mockNet.createNode(CordaX500Name("Bank B", "London", "GB"))
}
// DOCEND 2
}

View File

@ -1,136 +0,0 @@
package net.corda.docs.kotlin.tutorial.contract
import net.corda.core.contracts.*
import net.corda.core.crypto.NullKeys
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party
import net.corda.core.node.ServiceHub
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.finance.contracts.utils.sumCashBy
import net.corda.finance.workflows.asset.CashUtils
import net.corda.testing.core.singleIdentityAndCert
import java.time.Instant
import java.util.*
class CommercialPaper : Contract {
// DOCSTART 8
companion object {
const val CP_PROGRAM_ID: ContractClassName = "net.corda.finance.contracts.CommercialPaper"
}
// DOCEND 8
// DOCSTART 3
override fun verify(tx: LedgerTransaction) {
// Group by everything except owner: any modification to the CP at all is considered changing it fundamentally.
val groups = tx.groupStates(State::withoutOwner)
// There are two possible things that can be done with this CP. The first is trading it. The second is redeeming
// it for cash on or after the maturity date.
val command = tx.commands.requireSingleCommand<CommercialPaper.Commands>()
// DOCEND 3
// DOCSTART 4
val timeWindow: TimeWindow? = tx.timeWindow
for ((inputs, outputs, _) in groups) {
when (command.value) {
is Commands.Move -> {
val input = inputs.single()
requireThat {
"the transaction is signed by the owner of the CP" using (input.owner.owningKey in command.signers)
"the state is propagated" using (outputs.size == 1)
// Don't need to check anything else, as if outputs.size == 1 then the output is equal to
// the input ignoring the owner field due to the grouping.
}
}
is Commands.Redeem -> {
// Redemption of the paper requires movement of on-ledger cash.
val input = inputs.single()
val received = tx.outputs.map { it.data }.sumCashBy(input.owner)
val time = timeWindow?.fromTime ?: throw IllegalArgumentException("Redemptions must be timestamped")
requireThat {
"the paper must have matured" using (time >= input.maturityDate)
"the received amount equals the face value" using (received == input.faceValue)
"the paper must be destroyed" using outputs.isEmpty()
"the transaction is signed by the owner of the CP" using (input.owner.owningKey in command.signers)
}
}
is Commands.Issue -> {
val output = outputs.single()
val time = timeWindow?.untilTime ?: throw IllegalArgumentException("Issuances must be timestamped")
requireThat {
// Don't allow people to issue commercial paper under other entities identities.
"output states are issued by a command signer" using (output.issuance.party.owningKey in command.signers)
"output values sum to more than the inputs" using (output.faceValue.quantity > 0)
"the maturity date is not in the past" using (time < output.maturityDate)
// Don't allow an existing CP state to be replaced by this issuance.
"can't reissue an existing state" using inputs.isEmpty()
}
}
else -> throw IllegalArgumentException("Unrecognised command")
}
}
// DOCEND 4
}
// DOCSTART 2
interface Commands : CommandData {
class Move : TypeOnlyCommandData(), Commands
class Redeem : TypeOnlyCommandData(), Commands
class Issue : TypeOnlyCommandData(), Commands
}
// DOCEND 2
// DOCSTART 5
fun generateIssue(issuance: PartyAndReference, faceValue: Amount<Issued<Currency>>, maturityDate: Instant,
notary: Party): TransactionBuilder {
val state = State(issuance, issuance.party, faceValue, maturityDate)
val stateAndContract = StateAndContract(state, CP_PROGRAM_ID)
return TransactionBuilder(notary = notary).withItems(stateAndContract, Command(Commands.Issue(), issuance.party.owningKey))
}
// DOCEND 5
// DOCSTART 6
fun generateMove(tx: TransactionBuilder, paper: StateAndRef<State>, newOwner: AbstractParty) {
tx.addInputState(paper)
val outputState = paper.state.data.withNewOwner(newOwner).ownableState
tx.addOutputState(outputState, CP_PROGRAM_ID)
tx.addCommand(Command(Commands.Move(), paper.state.data.owner.owningKey))
}
// DOCEND 6
// DOCSTART 7
@Throws(InsufficientBalanceException::class)
fun generateRedeem(tx: TransactionBuilder, paper: StateAndRef<State>, services: ServiceHub) {
// Add the cash movement using the states in our vault.
CashUtils.generateSpend(
services = services,
tx = tx,
amount = paper.state.data.faceValue.withoutIssuer(),
ourIdentity = services.myInfo.singleIdentityAndCert(),
to = paper.state.data.owner
)
tx.addInputState(paper)
tx.addCommand(Command(Commands.Redeem(), paper.state.data.owner.owningKey))
}
// DOCEND 7
}
// DOCSTART 1
data class State(
val issuance: PartyAndReference,
override val owner: AbstractParty,
val faceValue: Amount<Issued<Currency>>,
val maturityDate: Instant
) : OwnableState {
override val participants = listOf(owner)
fun withoutOwner() = copy(owner = AnonymousParty(NullKeys.NullPublicKey))
override fun withNewOwner(newOwner: AbstractParty) = CommandAndState(CommercialPaper.Commands.Move(), copy(owner = newOwner))
}
// DOCEND 1

View File

@ -1,31 +0,0 @@
package net.corda.docs.kotlin.tutorial.flowstatemachines
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.flows.FlowExternalAsyncOperation
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.StartableByRPC
import java.util.concurrent.CompletableFuture
class SummingOperation(val a: Int, val b: Int) : FlowExternalAsyncOperation<Int> {
override fun execute(deduplicationId: String): CompletableFuture<Int> {
return CompletableFuture.completedFuture(a + b)
}
}
// DOCSTART SummingOperationThrowing
class SummingOperationThrowing(val a: Int, val b: Int) : FlowExternalAsyncOperation<Int> {
override fun execute(deduplicationId: String): CompletableFuture<Int> {
throw IllegalStateException("You shouldn't be calling me")
}
}
// DOCEND SummingOperationThrowing
@StartableByRPC
class ExampleSummingFlow : FlowLogic<Int>() {
@Suspendable
override fun call(): Int {
val answer = await(SummingOperation(1, 2))
return answer // hopefully 3
}
}

View File

@ -1,66 +0,0 @@
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
package net.corda.docs.kotlin.tutorial.flowstatemachines
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.Amount
import net.corda.core.contracts.OwnableState
import net.corda.core.contracts.StateAndRef
import net.corda.core.flows.FlowException
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowSession
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.ProgressTracker
import net.corda.finance.flows.TwoPartyTradeFlow
import java.util.*
// DOCSTART 1
object TwoPartyTradeFlow {
class UnacceptablePriceException(givenPrice: Amount<Currency>) : FlowException("Unacceptable price: $givenPrice")
class AssetMismatchException(val expectedTypeName: String, val typeName: String) : FlowException() {
override fun toString() = "The submitted asset didn't match the expected type: $expectedTypeName vs $typeName"
}
/**
* This object is serialised to the network and is the first flow message the seller sends to the buyer.
*
* @param payToIdentity anonymous identity of the seller, for payment to be sent to.
*/
@CordaSerializable
data class SellerTradeInfo(
val price: Amount<Currency>,
val payToIdentity: PartyAndCertificate
)
open class Seller(private val otherSideSession: FlowSession,
private val assetToSell: StateAndRef<OwnableState>,
private val price: Amount<Currency>,
private val myParty: PartyAndCertificate,
override val progressTracker: ProgressTracker = TwoPartyTradeFlow.Seller.tracker()) : FlowLogic<SignedTransaction>() {
companion object {
fun tracker() = ProgressTracker()
}
@Suspendable
override fun call(): SignedTransaction {
TODO()
}
}
open class Buyer(private val sellerSession: FlowSession,
private val notary: Party,
private val acceptablePrice: Amount<Currency>,
private val typeToBuy: Class<out OwnableState>,
private val anonymous: Boolean) : FlowLogic<SignedTransaction>() {
@Suspendable
override fun call(): SignedTransaction {
TODO()
}
}
}
// DOCEND 1

View File

@ -1,53 +0,0 @@
@file:Suppress("MemberVisibilityCanBePrivate")
package net.corda.docs.kotlin.tutorial.helloworld
import co.paralleluniverse.fibers.Suspendable
import com.template.TemplateContract
import net.corda.core.flows.FinalityFlow
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow
import net.corda.core.flows.StartableByRPC
import net.corda.core.utilities.ProgressTracker
// DOCSTART 01
// Add these imports:
import net.corda.core.contracts.Command
import net.corda.core.identity.Party
import net.corda.core.transactions.TransactionBuilder
// Replace Initiator's definition with:
@InitiatingFlow
@StartableByRPC
class IOUFlow(val iouValue: Int,
val otherParty: Party) : FlowLogic<Unit>() {
/** The progress tracker provides checkpoints indicating the progress of the flow to observers. */
override val progressTracker = ProgressTracker()
/** The flow logic is encapsulated within the call() method. */
@Suspendable
override fun call() {
// We retrieve the notary identity from the network map.
val notary = serviceHub.networkMapCache.notaryIdentities[0]
// We create the transaction components.
val outputState = IOUState(iouValue, ourIdentity, otherParty)
val command = Command(TemplateContract.Commands.Action(), ourIdentity.owningKey)
// We create a transaction builder and add the components.
val txBuilder = TransactionBuilder(notary = notary)
.addOutputState(outputState, TemplateContract.ID)
.addCommand(command)
// We sign the transaction.
val signedTx = serviceHub.signInitialTransaction(txBuilder)
// Creating a session with the other party.
val otherPartySession = initiateFlow(otherParty)
// We finalise the transaction and then send it to the counterparty.
subFlow(FinalityFlow(signedTx, otherPartySession))
}
}
// DOCEND 01

View File

@ -1,20 +0,0 @@
@file:Suppress("unused")
package net.corda.docs.kotlin.tutorial.helloworld
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowSession
import net.corda.core.flows.InitiatedBy
import net.corda.core.flows.ReceiveFinalityFlow
// DOCSTART 01
// Replace Responder's definition with:
@InitiatedBy(IOUFlow::class)
class IOUFlowResponder(private val otherPartySession: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
subFlow(ReceiveFinalityFlow(otherPartySession))
}
}
// DOCEND 01

View File

@ -1,17 +0,0 @@
@file:Suppress("MemberVisibilityCanBePrivate")
package net.corda.docs.kotlin.tutorial.helloworld
import net.corda.core.contracts.ContractState
// DOCSTART 01
// Add this import:
import net.corda.core.identity.Party
// Replace TemplateState's definition with:
class IOUState(val value: Int,
val lender: Party,
val borrower: Party) : ContractState {
override val participants get() = listOf(lender, borrower)
}
// DOCEND 01

View File

@ -1,47 +0,0 @@
@file:Suppress("UNUSED_VARIABLE")
package net.corda.docs.kotlin.tutorial.tearoffs
import net.corda.core.contracts.Command
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TimeWindow
import net.corda.core.crypto.MerkleTreeException
import net.corda.core.identity.AbstractParty
import net.corda.core.transactions.FilteredTransaction
import net.corda.core.transactions.FilteredTransactionVerificationException
import net.corda.core.transactions.SignedTransaction
import net.corda.finance.contracts.Fix
import java.util.function.Predicate
fun main(args: Array<String>) {
// Type alias to make the example coherent.
val oracle = Any() as AbstractParty
val stx = Any() as SignedTransaction
// DOCSTART 1
val filtering = Predicate<Any> {
when (it) {
is Command<*> -> oracle.owningKey in it.signers && it.value is Fix
else -> false
}
}
// DOCEND 1
// DOCSTART 2
val ftx: FilteredTransaction = stx.buildFilteredTransaction(filtering)
// DOCEND 2
// DOCSTART 3
// Direct access to included commands, inputs, outputs, attachments etc.
val cmds: List<Command<*>> = ftx.commands
val ins: List<StateRef> = ftx.inputs
val timeWindow: TimeWindow? = ftx.timeWindow
// ...
// DOCEND 3
try {
ftx.verify()
} catch (e: FilteredTransactionVerificationException) {
throw MerkleTreeException("Rate Fix Oracle: Couldn't verify partial Merkle tree.")
}
}

View File

@ -1,39 +0,0 @@
package net.corda.docs.kotlin.tutorial.twoparty
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.Contract
import net.corda.core.transactions.LedgerTransaction
// DOCSTART 01
// Add this import:
import net.corda.core.contracts.*
class IOUContract : Contract {
companion object {
const val ID = "com.template.IOUContract"
}
// Our Create command.
class Create : CommandData
override fun verify(tx: LedgerTransaction) {
val command = tx.commands.requireSingleCommand<Create>()
requireThat {
// Constraints on the shape of the transaction.
"No inputs should be consumed when issuing an IOU." using (tx.inputs.isEmpty())
"There should be one output state of type IOUState." using (tx.outputs.size == 1)
// IOU-specific constraints.
val output = tx.outputsOfType<IOUState>().single()
"The IOU's value must be non-negative." using (output.value > 0)
"The lender and the borrower cannot be the same entity." using (output.lender != output.borrower)
// Constraints on the signers.
val expectedSigners = listOf(output.borrower.owningKey, output.lender.owningKey)
"There must be two signers." using (command.signers.toSet().size == 2)
"The borrower and lender must be signers." using (command.signers.containsAll(expectedSigners))
}
}
}
// DOCEND 01

View File

@ -1,58 +0,0 @@
@file:Suppress("MemberVisibilityCanBePrivate")
package net.corda.docs.kotlin.tutorial.twoparty
// DOCSTART 01
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.Command
import net.corda.core.flows.CollectSignaturesFlow
import net.corda.core.flows.FinalityFlow
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow
import net.corda.core.flows.StartableByRPC
import net.corda.core.identity.Party
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker
// DOCEND 01
@InitiatingFlow
@StartableByRPC
class IOUFlow(val iouValue: Int,
val otherParty: Party) : FlowLogic<Unit>() {
/** The progress tracker provides checkpoints indicating the progress of the flow to observers. */
override val progressTracker = ProgressTracker()
/** The flow logic is encapsulated within the call() method. */
@Suspendable
override fun call() {
// DOCSTART 02
// We retrieve the notary identity from the network map.
val notary = serviceHub.networkMapCache.notaryIdentities[0]
// We create the transaction components.
val outputState = IOUState(iouValue, ourIdentity, otherParty)
val command = Command(IOUContract.Create(), listOf(ourIdentity.owningKey, otherParty.owningKey))
// We create a transaction builder and add the components.
val txBuilder = TransactionBuilder(notary = notary)
.addOutputState(outputState, IOUContract.ID)
.addCommand(command)
// Verifying the transaction.
txBuilder.verify(serviceHub)
// Signing the transaction.
val signedTx = serviceHub.signInitialTransaction(txBuilder)
// Creating a session with the other party.
val otherPartySession = initiateFlow(otherParty)
// Obtaining the counterparty's signature.
val fullySignedTx = subFlow(CollectSignaturesFlow(signedTx, listOf(otherPartySession), CollectSignaturesFlow.tracker()))
// Finalising the transaction.
subFlow(FinalityFlow(fullySignedTx, otherPartySession))
// DOCEND 02
}
}

View File

@ -1,32 +0,0 @@
@file:Suppress("unused")
package net.corda.docs.kotlin.tutorial.twoparty
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.flows.*
import net.corda.docs.kotlin.tutorial.helloworld.IOUFlow
import net.corda.docs.kotlin.tutorial.helloworld.IOUState
// Add these imports:
import net.corda.core.contracts.requireThat
import net.corda.core.transactions.SignedTransaction
// Define IOUFlowResponder:
@InitiatedBy(IOUFlow::class)
class IOUFlowResponder(val otherPartySession: FlowSession) : FlowLogic<Unit>() {
// DOCSTART 1
@Suspendable
override fun call() {
val signTransactionFlow = object : SignTransactionFlow(otherPartySession) {
override fun checkTransaction(stx: SignedTransaction) = requireThat {
val output = stx.tx.outputs.single().data
"This must be an IOU transaction." using (output is IOUState)
val iou = output as IOUState
"The IOU's value can't be too high." using (iou.value < 100)
}
}
val expectedTxId = subFlow(signTransactionFlow).id
subFlow(ReceiveFinalityFlow(otherPartySession, expectedTxId))
}
// DOCEND 1
}

View File

@ -1,10 +0,0 @@
package net.corda.docs.kotlin.tutorial.twoparty
import net.corda.core.contracts.ContractState
import net.corda.core.identity.Party
class IOUState(val value: Int,
val lender: Party,
val borrower: Party) : ContractState {
override val participants get() = listOf(lender, borrower)
}

View File

@ -1,262 +0,0 @@
@file:Suppress("unused")
package net.corda.docs.kotlin.txbuild
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.Command
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.Contract
import net.corda.core.contracts.LinearState
import net.corda.core.contracts.StateAndContract
import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TypeOnlyCommandData
import net.corda.core.contracts.UniqueIdentifier
import net.corda.core.contracts.requireSingleCommand
import net.corda.core.contracts.requireThat
import net.corda.core.crypto.TransactionSignature
import net.corda.core.flows.FinalityFlow
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowSession
import net.corda.core.flows.InitiatedBy
import net.corda.core.flows.InitiatingFlow
import net.corda.core.flows.ReceiveFinalityFlow
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party
import net.corda.core.node.services.queryBy
import net.corda.core.node.services.vault.QueryCriteria.VaultQueryCriteria
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.seconds
import net.corda.core.utilities.unwrap
// Minimal state model of a manual approval process
@CordaSerializable
enum class WorkflowState {
NEW,
APPROVED,
REJECTED
}
const val TRADE_APPROVAL_PROGRAM_ID = "net.corda.docs.kotlin.txbuild.TradeApprovalContract"
/**
* Minimal contract to encode a simple workflow with one initial state and two possible eventual states.
* It is assumed one party unilaterally submits and the other manually retrieves the deal and completes it.
*/
data class TradeApprovalContract(val blank: Unit? = null) : Contract {
interface Commands : CommandData {
class Issue : TypeOnlyCommandData(), Commands // Record receipt of deal details
class Completed : TypeOnlyCommandData(), Commands // Record match
}
/**
* Truly minimal state that just records a tradeId string and the parties involved.
*/
data class State(val tradeId: String,
val source: Party,
val counterparty: Party,
val state: WorkflowState = WorkflowState.NEW,
override val linearId: UniqueIdentifier = UniqueIdentifier(tradeId)) : LinearState {
override val participants: List<AbstractParty> get() = listOf(source, counterparty)
}
/**
* The verify method locks down the allowed transactions to contain just a single proposal being
* created/modified and the only modification allowed is to the state field.
*/
override fun verify(tx: LedgerTransaction) {
val command = tx.commands.requireSingleCommand<TradeApprovalContract.Commands>()
requireNotNull(tx.timeWindow) { "must have a time-window" }
when (command.value) {
is Commands.Issue -> {
requireThat {
"Issue of new WorkflowContract must not include any inputs" using (tx.inputs.isEmpty())
"Issue of new WorkflowContract must be in a unique transaction" using (tx.outputs.size == 1)
}
val issued = tx.outputsOfType<TradeApprovalContract.State>().single()
requireThat {
"Issue requires the source Party as signer" using (command.signers.contains(issued.source.owningKey))
"Initial Issue state must be NEW" using (issued.state == WorkflowState.NEW)
}
}
is Commands.Completed -> {
val stateGroups = tx.groupStates(TradeApprovalContract.State::class.java) { it.linearId }
require(stateGroups.size == 1) { "Must be only a single proposal in transaction" }
for ((inputs, outputs) in stateGroups) {
val before = inputs.single()
val after = outputs.single()
requireThat {
"Only a non-final trade can be modified" using (before.state == WorkflowState.NEW)
"Output must be a final state" using (after.state in setOf(WorkflowState.APPROVED, WorkflowState.REJECTED))
"Completed command can only change state" using (before == after.copy(state = before.state))
"Completed command requires the source Party as signer" using (command.signers.contains(before.source.owningKey))
"Completed command requires the counterparty as signer" using (command.signers.contains(before.counterparty.owningKey))
}
}
}
else -> throw IllegalArgumentException("Unrecognised Command $command")
}
}
}
/**
* Simple flow to create a workflow state, sign and notarise it.
* The protocol then sends a copy to the other node. We don't require the other party to sign
* as their approval/rejection is to follow.
*/
@InitiatingFlow
class SubmitTradeApprovalFlow(private val tradeId: String,
private val counterparty: Party,
private val notary: Party) : FlowLogic<StateAndRef<TradeApprovalContract.State>>() {
@Suspendable
override fun call(): StateAndRef<TradeApprovalContract.State> {
// Manufacture an initial state
val tradeProposal = TradeApprovalContract.State(tradeId, ourIdentity, counterparty)
// Create the TransactionBuilder and populate with the new state.
val tx = TransactionBuilder(notary).withItems(
StateAndContract(tradeProposal, TRADE_APPROVAL_PROGRAM_ID),
Command(TradeApprovalContract.Commands.Issue(), listOf(tradeProposal.source.owningKey)))
tx.setTimeWindow(serviceHub.clock.instant(), 60.seconds)
// We can automatically sign as there is no untrusted data.
val signedTx = serviceHub.signInitialTransaction(tx)
// Notarise and distribute.
subFlow(FinalityFlow(signedTx, initiateFlow(counterparty)))
// Return the initial state
return signedTx.tx.outRef(0)
}
}
@InitiatedBy(SubmitTradeApprovalFlow::class)
class SubmitTradeApprovalResponderFlow(private val otherSide: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
subFlow(ReceiveFinalityFlow(otherSide))
}
}
/**
* Simple flow to complete a proposal submitted by another party and ensure both nodes
* end up with a fully signed copy of the state either as APPROVED, or REJECTED
*/
@InitiatingFlow
class SubmitCompletionFlow(private val ref: StateRef, private val verdict: WorkflowState) : FlowLogic<StateAndRef<TradeApprovalContract.State>>() {
init {
require(verdict in setOf(WorkflowState.APPROVED, WorkflowState.REJECTED)) {
"Verdict must be a final state"
}
}
@Suspendable
override fun call(): StateAndRef<TradeApprovalContract.State> {
// DOCSTART 1
val criteria = VaultQueryCriteria(stateRefs = listOf(ref))
val latestRecord = serviceHub.vaultService.queryBy<TradeApprovalContract.State>(criteria).states.single()
// DOCEND 1
// Check the protocol hasn't already been run
require(latestRecord.ref == ref) {
"Input trade $ref is not latest version $latestRecord"
}
// Require that the state is still modifiable
require(latestRecord.state.data.state == WorkflowState.NEW) {
"Input trade not modifiable ${latestRecord.state.data.state}"
}
// Check we are the correct Party to run the protocol. Note they will counter check this too.
require(serviceHub.myInfo.isLegalIdentity(latestRecord.state.data.counterparty)) {
"The counterparty must give the verdict"
}
// DOCSTART 2
// Modify the state field for new output. We use copy, to ensure no other modifications.
// It is especially important for a LinearState that the linearId is copied across,
// not accidentally assigned a new random id.
val newState = latestRecord.state.data.copy(state = verdict)
// We have to use the original notary for the new transaction
val notary = latestRecord.state.notary
// Get and populate the new TransactionBuilder
// To destroy the old proposal state and replace with the new completion state.
// Also add the Completed command with keys of all parties to signal the Tx purpose
// to the Contract verify method.
val tx = TransactionBuilder(notary).
withItems(
latestRecord,
StateAndContract(newState, TRADE_APPROVAL_PROGRAM_ID),
Command(TradeApprovalContract.Commands.Completed(),
listOf(ourIdentity.owningKey, latestRecord.state.data.source.owningKey)))
tx.setTimeWindow(serviceHub.clock.instant(), 60.seconds)
// We can sign this transaction immediately as we have already checked all the fields and the decision
// is ultimately a manual one from the caller.
// As a SignedTransaction we can pass the data around certain that it cannot be modified,
// although we do require further signatures to complete the process.
val selfSignedTx = serviceHub.signInitialTransaction(tx)
//DOCEND 2
// Send the signed transaction to the originator and await their signature to confirm
val sourceSession = initiateFlow(newState.source)
val allPartySignedTx = sourceSession.sendAndReceive<TransactionSignature>(selfSignedTx).unwrap {
// Add their signature to our unmodified transaction. To check they signed the same tx.
val agreedTx = selfSignedTx + it
// Receive back their signature and confirm that it is for an unmodified transaction
// Also that the only missing signature is from teh Notary
agreedTx.verifySignaturesExcept(notary.owningKey)
// Recheck the data of the transaction. Note we run toLedgerTransaction on the WireTransaction
// as we do not have all the signature.
agreedTx.tx.toLedgerTransaction(serviceHub).verify()
// return the SignedTransaction to notarise
agreedTx
}
// DOCSTART 4
// Notarise and distribute the completed transaction.
subFlow(FinalityFlow(allPartySignedTx, sourceSession))
// DOCEND 4
// Return back the details of the completed state/transaction.
return allPartySignedTx.tx.outRef(0)
}
}
/**
* Simple flow to receive the final decision on a proposal.
* Then after checking to sign it and eventually store the fully notarised
* transaction to the ledger.
*/
@InitiatedBy(SubmitCompletionFlow::class)
class RecordCompletionFlow(private val sourceSession: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
// DOCSTART 3
// First we receive the verdict transaction signed by their single key
val completeTx = sourceSession.receive<SignedTransaction>().unwrap {
// Check the transaction is signed apart from our own key and the notary
it.verifySignaturesExcept(ourIdentity.owningKey, it.tx.notary!!.owningKey)
// Check the transaction data is correctly formed
val ltx = it.toLedgerTransaction(serviceHub, false)
ltx.verify()
// Confirm that this is the expected type of transaction
require(ltx.commands.single().value is TradeApprovalContract.Commands.Completed) {
"Transaction must represent a workflow completion"
}
// Check the context dependent parts of the transaction as the
// Contract verify method must not use serviceHub queries.
val state = ltx.outRef<TradeApprovalContract.State>(0)
require(serviceHub.myInfo.isLegalIdentity(state.state.data.source)) {
"Proposal not one of our original proposals"
}
require(state.state.data.counterparty == sourceSession.counterparty) {
"Proposal not for sent from correct source"
}
it
}
// DOCEND 3
// Having verified the SignedTransaction passed to us we can sign it too
val ourSignature = serviceHub.createSignature(completeTx)
// Send our signature to the other party.
sourceSession.send(ourSignature)
subFlow(ReceiveFinalityFlow(sourceSession))
}
}

View File

@ -1,149 +0,0 @@
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
package net.corda.docs.kotlin.vault
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.Amount
import net.corda.core.flows.*
import net.corda.core.identity.Party
import net.corda.core.node.AppServiceHub
import net.corda.core.node.services.CordaService
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.*
import net.corda.finance.flows.AbstractCashFlow
import net.corda.finance.flows.CashException
import net.corda.finance.flows.CashIssueFlow
import net.corda.finance.flows.CashPaymentFlow
import java.util.*
// DOCSTART CustomVaultQuery
object CustomVaultQuery {
@CordaService
class Service(val services: AppServiceHub) : SingletonSerializeAsToken() {
private companion object {
private val log = contextLogger()
}
fun rebalanceCurrencyReserves(): List<Amount<Currency>> {
val nativeQuery = """
select
cashschema.ccy_code,
sum(cashschema.pennies)
from
vault_states vaultschema
join
contract_cash_states cashschema
where
vaultschema.output_index=cashschema.output_index
and vaultschema.transaction_id=cashschema.transaction_id
and vaultschema.state_status=0
group by
cashschema.ccy_code
order by
sum(cashschema.pennies) desc
"""
log.info("SQL to execute: $nativeQuery")
val session = services.jdbcSession()
return session.prepareStatement(nativeQuery).use { prepStatement ->
prepStatement.executeQuery().use { rs ->
val topUpLimits: MutableList<Amount<Currency>> = mutableListOf()
while (rs.next()) {
val currencyStr = rs.getString(1)
val amount = rs.getLong(2)
log.info("$currencyStr : $amount")
topUpLimits.add(Amount(amount, Currency.getInstance(currencyStr)))
}
topUpLimits
}
}
}
}
}
// DOCEND CustomVaultQuery
/**
* This is a slightly modified version of the IssuerFlow, which uses a 3rd party custom query to
* retrieve a list of currencies and top up amounts to be used in the issuance.
*/
object TopupIssuerFlow {
@CordaSerializable
data class TopupRequest(val issueToParty: Party,
val issuerPartyRef: OpaqueBytes,
val notaryParty: Party)
@InitiatingFlow
@StartableByRPC
class TopupIssuanceRequester(val issueToParty: Party,
val issueToPartyRef: OpaqueBytes,
val issuerBankParty: Party,
val notaryParty: Party) : FlowLogic<List<AbstractCashFlow.Result>>() {
@Suspendable
@Throws(CashException::class)
override fun call(): List<AbstractCashFlow.Result> {
val topupRequest = TopupRequest(issueToParty, issueToPartyRef, notaryParty)
return initiateFlow(issuerBankParty).sendAndReceive<List<AbstractCashFlow.Result>>(topupRequest).unwrap { it }
}
}
@InitiatedBy(TopupIssuanceRequester::class)
class TopupIssuer(val otherPartySession: FlowSession) : FlowLogic<List<SignedTransaction>>() {
companion object {
object AWAITING_REQUEST : ProgressTracker.Step("Awaiting issuance request")
object ISSUING : ProgressTracker.Step("Issuing asset")
object TRANSFERRING : ProgressTracker.Step("Transferring asset to issuance requester")
object SENDING_TOP_UP_ISSUE_REQUEST : ProgressTracker.Step("Requesting asset issue top up")
fun tracker() = ProgressTracker(AWAITING_REQUEST, ISSUING, TRANSFERRING, SENDING_TOP_UP_ISSUE_REQUEST)
}
override val progressTracker: ProgressTracker = tracker()
// DOCSTART TopupIssuer
@Suspendable
@Throws(CashException::class)
override fun call(): List<SignedTransaction> {
progressTracker.currentStep = AWAITING_REQUEST
val topupRequest = otherPartySession.receive<TopupRequest>().unwrap {
it
}
val customVaultQueryService = serviceHub.cordaService(CustomVaultQuery.Service::class.java)
val reserveLimits = customVaultQueryService.rebalanceCurrencyReserves()
val txns: List<SignedTransaction> = reserveLimits.map { amount ->
// request asset issue
logger.info("Requesting currency issue $amount")
val txn = issueCashTo(amount, topupRequest.issueToParty, topupRequest.issuerPartyRef, topupRequest.notaryParty)
progressTracker.currentStep = SENDING_TOP_UP_ISSUE_REQUEST
return@map txn.stx
}
otherPartySession.send(txns)
return txns
}
// DOCEND TopupIssuer
@Suspendable
private fun issueCashTo(amount: Amount<Currency>,
issueTo: Party,
issuerPartyRef: OpaqueBytes,
notaryParty: Party): AbstractCashFlow.Result {
// invoke Cash subflow to issue Asset
progressTracker.currentStep = ISSUING
val issueCashFlow = CashIssueFlow(amount, issuerPartyRef, notaryParty)
val issueTx = subFlow(issueCashFlow)
// NOTE: issueCashFlow performs a Broadcast (which stores a local copy of the txn to the ledger)
// short-circuit when issuing to self
if (serviceHub.myInfo.isLegalIdentity(issueTo))
return issueTx
// now invoke Cash subflow to Move issued assetType to issue requester
progressTracker.currentStep = TRANSFERRING
val moveCashFlow = CashPaymentFlow(amount, issueTo, anonymous = false)
// NOTE: CashFlow PayCash calls FinalityFlow which performs a Broadcast (which stores a local copy of the txn to the ledger)
return subFlow(moveCashFlow)
}
}
}

View File

@ -1,25 +0,0 @@
myLegalName = "O=Bank A,L=London,C=GB"
keyStorePassword = "cordacadevpass"
trustStorePassword = "trustpass"
crlCheckSoftFail = true
dataSourceProperties {
dataSourceClassName = org.h2.jdbcx.JdbcDataSource
dataSource.url = "jdbc:h2:file:"${baseDirectory}"/persistence"
dataSource.user = sa
dataSource.password = ""
}
p2pAddress = "my-corda-node:10002"
rpcSettings {
useSsl = false
standAloneBroker = false
address = "my-corda-node:10003"
adminAddress = "my-corda-node:10004"
}
rpcUsers = [
{ username=user1, password=letmein, permissions=[ StartFlow.net.corda.protocols.CashProtocol ] }
]
devMode = false
networkServices {
doormanURL = "https://registration.example.com"
networkMapURL = "https://cz.example.com"
}

View File

@ -1,21 +0,0 @@
myLegalName = "O=Bank A,L=London,C=GB"
keyStorePassword = "cordacadevpass"
trustStorePassword = "trustpass"
crlCheckSoftFail = true
dataSourceProperties {
dataSourceClassName = org.h2.jdbcx.JdbcDataSource
dataSource.url = "jdbc:h2:file:"${baseDirectory}"/persistence"
dataSource.user = sa
dataSource.password = ""
}
p2pAddress = "my-corda-node:10002"
rpcSettings {
useSsl = false
standAloneBroker = false
address = "my-corda-node:10003"
adminAddress = "my-corda-node:10004"
}
rpcUsers = [
{ username=user1, password=letmein, permissions=[ StartFlow.net.corda.protocols.CashProtocol ] }
]
devMode = true

View File

@ -1,3 +0,0 @@
nodeHostAndPort = "my-corda-node:10002"
keyStorePassword = "cordacadevpass"
trustStorePassword = "trustpass"

View File

@ -1,334 +0,0 @@
package net.corda.docs.java.tutorial.testdsl;
import kotlin.Unit;
import net.corda.core.contracts.PartyAndReference;
import net.corda.core.contracts.TransactionVerificationException;
import net.corda.core.identity.CordaX500Name;
import net.corda.finance.contracts.ICommercialPaperState;
import net.corda.finance.contracts.JavaCommercialPaper;
import net.corda.finance.contracts.asset.Cash;
import net.corda.testing.core.TestIdentity;
import net.corda.testing.node.MockServices;
import org.junit.Before;
import org.junit.Test;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import static java.util.Collections.singletonList;
import static net.corda.finance.Currencies.DOLLARS;
import static net.corda.finance.Currencies.issuedBy;
import static net.corda.finance.contracts.JavaCommercialPaper.JCP_PROGRAM_ID;
import static net.corda.testing.core.TestConstants.ALICE_NAME;
import static net.corda.testing.core.TestConstants.BOB_NAME;
import static net.corda.testing.node.MockServicesKt.makeTestIdentityService;
import static net.corda.testing.node.NodeTestUtils.ledger;
import static net.corda.testing.node.NodeTestUtils.transaction;
public class TutorialTestDSL {
private static final TestIdentity alice = new TestIdentity(ALICE_NAME, 70L);
// DOCSTART 14
private static final TestIdentity bigCorp = new TestIdentity(new CordaX500Name("BigCorp", "New York", "GB"));
// DOCEND 14
private static final TestIdentity bob = new TestIdentity(BOB_NAME, 80L);
private static final TestIdentity megaCorp = new TestIdentity(new CordaX500Name("MegaCorp", "London", "GB"));
private final byte[] defaultRef = {123};
private static final Instant TEST_TX_TIME = Instant.parse("2015-04-17T12:00:00.00Z");
private MockServices ledgerServices;
@Before
public void setUp() {
// DOCSTART 11
ledgerServices = new MockServices(
// A list of packages to scan for cordapps
singletonList("net.corda.finance.contracts"),
// The identity represented by this set of mock services. Defaults to a test identity.
// You can also use the alternative parameter initialIdentityName which accepts a
// [CordaX500Name]
megaCorp,
// An implementation of [IdentityService], which contains a list of all identities known
// to the node. Use [makeTestIdentityService] which returns an implementation of
// [InMemoryIdentityService] with the given identities
makeTestIdentityService(megaCorp.getIdentity())
);
// DOCEND 11
}
@SuppressWarnings("unused")
// DOCSTART 12
private final MockServices simpleLedgerServices = new MockServices(
// This is the identity of the node
megaCorp,
// Other identities the test node knows about
bigCorp,
alice
);
// DOCEND 12
// DOCSTART 1
private ICommercialPaperState getPaper() {
return new JavaCommercialPaper.State(
megaCorp.ref(defaultRef),
megaCorp.getParty(),
issuedBy(DOLLARS(1000), megaCorp.ref(defaultRef)),
TEST_TX_TIME.plus(7, ChronoUnit.DAYS)
);
}
// DOCEND 1
// DOCSTART 2
// This example test will fail with this exception.
@Test(expected = IllegalStateException.class)
public void simpleCP() {
ICommercialPaperState inState = getPaper();
ledger(ledgerServices, l -> {
l.transaction(tx -> {
tx.attachments(JCP_PROGRAM_ID);
tx.input(JCP_PROGRAM_ID, inState);
return tx.verifies();
});
return Unit.INSTANCE;
});
}
// DOCEND 2
// DOCSTART 3
// This example test will fail with this exception.
@Test(expected = TransactionVerificationException.ContractRejection.class)
public void simpleCPMove() {
ICommercialPaperState inState = getPaper();
ledger(ledgerServices, l -> {
l.transaction(tx -> {
tx.input(JCP_PROGRAM_ID, inState);
tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move());
tx.attachments(JCP_PROGRAM_ID);
return tx.verifies();
});
return Unit.INSTANCE;
});
}
// DOCEND 3
// DOCSTART 4
@Test
public void simpleCPMoveFails() {
ICommercialPaperState inState = getPaper();
ledger(ledgerServices, l -> {
l.transaction(tx -> {
tx.input(JCP_PROGRAM_ID, inState);
tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move());
tx.attachments(JCP_PROGRAM_ID);
return tx.failsWith("the state is propagated");
});
return Unit.INSTANCE;
});
}
// DOCEND 4
// DOCSTART 5
@Test
public void simpleCPMoveSuccessAndFailure() {
ICommercialPaperState inState = getPaper();
ledger(ledgerServices, l -> {
l.transaction(tx -> {
tx.input(JCP_PROGRAM_ID, inState);
tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move());
tx.attachments(JCP_PROGRAM_ID);
tx.failsWith("the state is propagated");
tx.output(JCP_PROGRAM_ID, "alice's paper", inState.withOwner(alice.getParty()));
return tx.verifies();
});
return Unit.INSTANCE;
});
}
// DOCEND 5
// DOCSTART 13
@Test
public void simpleCPMoveSuccess() {
ICommercialPaperState inState = getPaper();
ledger(ledgerServices, l -> {
l.transaction(tx -> {
tx.input(JCP_PROGRAM_ID, inState);
tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move());
tx.attachments(JCP_PROGRAM_ID);
tx.timeWindow(TEST_TX_TIME);
tx.output(JCP_PROGRAM_ID, "alice's paper", inState.withOwner(alice.getParty()));
return tx.verifies();
});
return Unit.INSTANCE;
});
}
// DOCEND 13
// DOCSTART 6
@Test
public void simpleIssuanceWithTweak() {
ledger(ledgerServices, l -> {
l.transaction(tx -> {
tx.output(JCP_PROGRAM_ID, "paper", getPaper()); // Some CP is issued onto the ledger by MegaCorp.
tx.attachments(JCP_PROGRAM_ID);
tx.tweak(tw -> {
tw.command(bigCorp.getPublicKey(), new JavaCommercialPaper.Commands.Issue());
tw.timeWindow(TEST_TX_TIME);
return tw.failsWith("output states are issued by a command signer");
});
tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Issue());
tx.timeWindow(TEST_TX_TIME);
return tx.verifies();
});
return Unit.INSTANCE;
});
}
// DOCEND 6
// DOCSTART 7
@Test
public void simpleIssuanceWithTweakTopLevelTx() {
transaction(ledgerServices, tx -> {
tx.output(JCP_PROGRAM_ID, "paper", getPaper()); // Some CP is issued onto the ledger by MegaCorp.
tx.attachments(JCP_PROGRAM_ID);
tx.tweak(tw -> {
tw.command(bigCorp.getPublicKey(), new JavaCommercialPaper.Commands.Issue());
tw.timeWindow(TEST_TX_TIME);
return tw.failsWith("output states are issued by a command signer");
});
tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Issue());
tx.timeWindow(TEST_TX_TIME);
return tx.verifies();
});
}
// DOCEND 7
// DOCSTART 8
@Test
public void chainCommercialPaper() {
PartyAndReference issuer = megaCorp.ref(defaultRef);
ledger(ledgerServices, l -> {
l.unverifiedTransaction(tx -> {
tx.output(Cash.PROGRAM_ID, "alice's $900",
new Cash.State(issuedBy(DOLLARS(900), issuer), alice.getParty()));
tx.attachments(Cash.PROGRAM_ID);
return Unit.INSTANCE;
});
// Some CP is issued onto the ledger by MegaCorp.
l.transaction("Issuance", tx -> {
tx.output(JCP_PROGRAM_ID, "paper", getPaper());
tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Issue());
tx.attachments(JCP_PROGRAM_ID);
tx.timeWindow(TEST_TX_TIME);
return tx.verifies();
});
l.transaction("Trade", tx -> {
tx.input("paper");
tx.input("alice's $900");
tx.output(Cash.PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), megaCorp.getParty()));
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(alice.getParty()));
tx.command(alice.getPublicKey(), new Cash.Commands.Move());
tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move());
return tx.verifies();
});
return Unit.INSTANCE;
});
}
// DOCEND 8
// DOCSTART 9
@Test
public void chainCommercialPaperDoubleSpend() {
PartyAndReference issuer = megaCorp.ref(defaultRef);
ledger(ledgerServices, l -> {
l.unverifiedTransaction(tx -> {
tx.output(Cash.PROGRAM_ID, "alice's $900",
new Cash.State(issuedBy(DOLLARS(900), issuer), alice.getParty()));
tx.attachments(Cash.PROGRAM_ID);
return Unit.INSTANCE;
});
// Some CP is issued onto the ledger by MegaCorp.
l.transaction("Issuance", tx -> {
tx.output(JCP_PROGRAM_ID, "paper", getPaper());
tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Issue());
tx.attachments(JCP_PROGRAM_ID);
tx.timeWindow(TEST_TX_TIME);
return tx.verifies();
});
l.transaction("Trade", tx -> {
tx.input("paper");
tx.input("alice's $900");
tx.output(Cash.PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), megaCorp.getParty()));
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(alice.getParty()));
tx.command(alice.getPublicKey(), new Cash.Commands.Move());
tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move());
return tx.verifies();
});
l.transaction(tx -> {
tx.input("paper");
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
// We moved a paper to other pubkey.
tx.output(JCP_PROGRAM_ID, "bob's paper", inputPaper.withOwner(bob.getParty()));
tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move());
return tx.verifies();
});
l.fails();
return Unit.INSTANCE;
});
}
// DOCEND 9
// DOCSTART 10
@Test
public void chainCommercialPaperTweak() {
PartyAndReference issuer = megaCorp.ref(defaultRef);
ledger(ledgerServices, l -> {
l.unverifiedTransaction(tx -> {
tx.output(Cash.PROGRAM_ID, "alice's $900",
new Cash.State(issuedBy(DOLLARS(900), issuer), alice.getParty()));
tx.attachments(Cash.PROGRAM_ID);
return Unit.INSTANCE;
});
// Some CP is issued onto the ledger by MegaCorp.
l.transaction("Issuance", tx -> {
tx.output(JCP_PROGRAM_ID, "paper", getPaper());
tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Issue());
tx.attachments(JCP_PROGRAM_ID);
tx.timeWindow(TEST_TX_TIME);
return tx.verifies();
});
l.transaction("Trade", tx -> {
tx.input("paper");
tx.input("alice's $900");
tx.output(Cash.PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), megaCorp.getParty()));
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(alice.getParty()));
tx.command(alice.getPublicKey(), new Cash.Commands.Move(JavaCommercialPaper.class));
tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move());
return tx.verifies();
});
l.tweak(lw -> {
lw.transaction(tx -> {
tx.input("paper");
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
// We moved a paper to another pubkey.
tx.output(JCP_PROGRAM_ID, "bob's paper", inputPaper.withOwner(bob.getParty()));
tx.command(megaCorp.getPublicKey(), new JavaCommercialPaper.Commands.Move());
return tx.verifies();
});
lw.fails();
return Unit.INSTANCE;
});
l.verifies();
return Unit.INSTANCE;
});
}
// DOCEND 10
}

View File

@ -1,36 +0,0 @@
package net.corda.docs
import net.corda.core.internal.toPath
import net.corda.node.services.config.ConfigHelper
import net.corda.node.services.config.parseAsNodeConfiguration
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.reflect.KVisibility
import kotlin.reflect.full.declaredMemberProperties
class ExampleConfigTest {
private fun <A : Any> readAndCheckConfigurations(vararg configFilenames: String, loadConfig: (Path) -> A) {
configFilenames.forEach {
println("Checking $it")
val configFileResource = ExampleConfigTest::class.java.classLoader.getResource(it)
val config = loadConfig(configFileResource.toPath())
// Force the config fields as they are resolved lazily
config.javaClass.kotlin.declaredMemberProperties.forEach { member ->
if (member.visibility == KVisibility.PUBLIC) {
member.get(config)
}
}
}
}
@Test(timeout=300_000)
fun `example node_confs parses fine`() {
readAndCheckConfigurations("example-node.conf") {
val baseDirectory = Paths.get("some-example-base-dir")
assertThat(ConfigHelper.loadConfig(baseDirectory = baseDirectory, configFile = it).parseAsNodeConfiguration().isValid).isTrue()
}
}
}

View File

@ -1,92 +0,0 @@
package net.corda.docs.kotlin
import net.corda.core.identity.Party
import net.corda.core.toFuture
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
import net.corda.finance.*
import net.corda.finance.workflows.getCashBalances
import net.corda.finance.flows.CashIssueFlow
import net.corda.testing.core.singleIdentity
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.StartedMockNode
import org.junit.After
import org.junit.Before
import org.junit.Test
import kotlin.test.assertEquals
class FxTransactionBuildTutorialTest {
private lateinit var mockNet: MockNetwork
private lateinit var nodeA: StartedMockNode
private lateinit var nodeB: StartedMockNode
private lateinit var notary: Party
@Before
fun setup() {
mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.finance"))
nodeA = mockNet.createPartyNode()
nodeB = mockNet.createPartyNode()
nodeB.registerInitiatedFlow(ForeignExchangeRemoteFlow::class.java)
notary = mockNet.defaultNotaryIdentity
}
@After
fun cleanUp() {
mockNet.stopNodes()
}
@Test(timeout=300_000)
fun `Run ForeignExchangeFlow to completion`() {
// Use NodeA as issuer and create some dollars and wait for the flow to stop
nodeA.startFlow(CashIssueFlow(DOLLARS(1000),
OpaqueBytes.of(0x01),
notary)).getOrThrow()
printBalances()
// Using NodeB as Issuer create some pounds and wait for the flow to stop
nodeB.startFlow(CashIssueFlow(POUNDS(1000),
OpaqueBytes.of(0x01),
notary)).getOrThrow()
printBalances()
// Setup some futures on the vaults to await the arrival of the exchanged funds at both nodes
val nodeAVaultUpdate = nodeA.services.vaultService.updates.toFuture()
val nodeBVaultUpdate = nodeB.services.vaultService.updates.toFuture()
// Now run the actual Fx exchange and wait for the flow to finish
nodeA.startFlow(ForeignExchangeFlow("trade1",
POUNDS(100).issuedBy(nodeB.info.singleIdentity().ref(0x01)),
DOLLARS(200).issuedBy(nodeA.info.singleIdentity().ref(0x01)),
nodeB.info.singleIdentity(),
weAreBaseCurrencySeller = false,
notary = mockNet.defaultNotaryIdentity)).getOrThrow()
// wait for the flow to finish and the vault updates to be done
// Get the balances when the vault updates
nodeAVaultUpdate.get()
val balancesA = nodeA.transaction {
nodeA.services.getCashBalances()
}
nodeBVaultUpdate.get()
val balancesB = nodeB.transaction {
nodeB.services.getCashBalances()
}
println("BalanceA\n$balancesA")
println("BalanceB\n$balancesB")
// Verify the transfers occurred as expected
assertEquals(POUNDS(100), balancesA[GBP])
assertEquals(DOLLARS(1000 - 200), balancesA[USD])
assertEquals(POUNDS(1000 - 100), balancesB[GBP])
assertEquals(DOLLARS(200), balancesB[USD])
}
private fun printBalances() {
// Print out the balances
nodeA.transaction {
println("BalanceA\n" + nodeA.services.getCashBalances())
}
nodeB.transaction {
println("BalanceB\n" + nodeB.services.getCashBalances())
}
}
}

View File

@ -1,316 +0,0 @@
@file:Suppress("MemberVisibilityCanBePrivate")
package net.corda.docs.kotlin.tutorial.testdsl
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.contracts.TransactionVerificationException
import net.corda.core.identity.CordaX500Name
import net.corda.core.node.services.IdentityService
import net.corda.core.utilities.days
import net.corda.finance.DOLLARS
import net.corda.finance.`issued by`
import net.corda.finance.contracts.CP_PROGRAM_ID
import net.corda.finance.contracts.CommercialPaper
import net.corda.finance.contracts.ICommercialPaperState
import net.corda.finance.contracts.asset.CASH
import net.corda.finance.contracts.asset.Cash
import net.corda.testing.core.*
import net.corda.testing.node.MockServices
import net.corda.testing.node.ledger
import net.corda.testing.node.transaction
import org.junit.Rule
import org.junit.Test
import java.time.Instant
class TutorialTestDSL {
private companion object {
val alice = TestIdentity(ALICE_NAME, 70)
val bob = TestIdentity(BOB_NAME, 80)
// DOCSTART 14
val bigCorp = TestIdentity((CordaX500Name("BigCorp", "New York", "GB")))
// DOCEND 14
val dummyNotary = TestIdentity(DUMMY_NOTARY_NAME, 20)
val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB"))
val TEST_TX_TIME: Instant = Instant.parse("2015-04-17T12:00:00.00Z")
}
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule()
// DOCSTART 11
private val ledgerServices = MockServices(
// A list of packages to scan for cordapps
listOf("net.corda.finance.contracts"),
// The identity represented by this set of mock services. Defaults to a test identity.
// You can also use the alternative parameter initialIdentityName which accepts a
// [CordaX500Name]
megaCorp,
mock<IdentityService>().also {
doReturn(megaCorp.party).whenever(it).partyFromKey(megaCorp.publicKey)
doReturn(null).whenever(it).partyFromKey(bigCorp.publicKey)
doReturn(null).whenever(it).partyFromKey(alice.publicKey)
})
// DOCEND 11
// DOCSTART 12
@Suppress("unused")
private val simpleLedgerServices = MockServices(
// This is the identity of the node
megaCorp,
// Other identities the test node knows about
bigCorp,
alice
)
// DOCEND 12
// DOCSTART 1
fun getPaper(): ICommercialPaperState = CommercialPaper.State(
issuance = megaCorp.party.ref(123),
owner = megaCorp.party,
faceValue = 1000.DOLLARS `issued by` megaCorp.party.ref(123),
maturityDate = TEST_TX_TIME + 7.days
)
// DOCEND 1
// DOCSTART 2
// This example test will fail with this exception.
@Test(expected = IllegalStateException::class, timeout=300_000)
fun simpleCP() {
val inState = getPaper()
ledgerServices.ledger(dummyNotary.party) {
transaction {
attachments(CP_PROGRAM_ID)
input(CP_PROGRAM_ID, inState)
verifies()
}
}
}
// DOCEND 2
// DOCSTART 3
// This example test will fail with this exception.
@Test(expected = TransactionVerificationException.ContractRejection::class, timeout=300_000)
fun simpleCPMove() {
val inState = getPaper()
ledgerServices.ledger(dummyNotary.party) {
transaction {
input(CP_PROGRAM_ID, inState)
command(megaCorp.publicKey, CommercialPaper.Commands.Move())
attachments(CP_PROGRAM_ID)
verifies()
}
}
}
// DOCEND 3
// DOCSTART 4
@Test(timeout=300_000)
fun simpleCPMoveFails() {
val inState = getPaper()
ledgerServices.ledger(dummyNotary.party) {
transaction {
input(CP_PROGRAM_ID, inState)
command(megaCorp.publicKey, CommercialPaper.Commands.Move())
attachments(CP_PROGRAM_ID)
`fails with`("the state is propagated")
}
}
}
// DOCEND 4
// DOCSTART 5
@Test(timeout=300_000)
fun simpleCPMoveFailureAndSuccess() {
val inState = getPaper()
ledgerServices.ledger(dummyNotary.party) {
transaction {
input(CP_PROGRAM_ID, inState)
command(megaCorp.publicKey, CommercialPaper.Commands.Move())
attachments(CP_PROGRAM_ID)
`fails with`("the state is propagated")
output(CP_PROGRAM_ID, "alice's paper", inState.withOwner(alice.party))
verifies()
}
}
}
// DOCEND 5
// DOCSTART 13
@Test(timeout=300_000)
fun simpleCPMoveSuccess() {
val inState = getPaper()
ledgerServices.ledger(dummyNotary.party) {
transaction {
input(CP_PROGRAM_ID, inState)
command(megaCorp.publicKey, CommercialPaper.Commands.Move())
attachments(CP_PROGRAM_ID)
timeWindow(TEST_TX_TIME)
output(CP_PROGRAM_ID, "alice's paper", inState.withOwner(alice.party))
verifies()
}
}
}
// DOCEND 13
// DOCSTART 6
@Test(timeout=300_000)
fun `simple issuance with tweak`() {
ledgerServices.ledger(dummyNotary.party) {
transaction {
output(CP_PROGRAM_ID, "paper", getPaper()) // Some CP is issued onto the ledger by MegaCorp.
attachments(CP_PROGRAM_ID)
tweak {
// The wrong pubkey.
command(bigCorp.publicKey, CommercialPaper.Commands.Issue())
timeWindow(TEST_TX_TIME)
`fails with`("output states are issued by a command signer")
}
command(megaCorp.publicKey, CommercialPaper.Commands.Issue())
timeWindow(TEST_TX_TIME)
verifies()
}
}
}
// DOCEND 6
// DOCSTART 7
@Test(timeout=300_000)
fun `simple issuance with tweak and top level transaction`() {
ledgerServices.transaction(dummyNotary.party) {
output(CP_PROGRAM_ID, "paper", getPaper()) // Some CP is issued onto the ledger by MegaCorp.
attachments(CP_PROGRAM_ID)
tweak {
// The wrong pubkey.
command(bigCorp.publicKey, CommercialPaper.Commands.Issue())
timeWindow(TEST_TX_TIME)
`fails with`("output states are issued by a command signer")
}
command(megaCorp.publicKey, CommercialPaper.Commands.Issue())
timeWindow(TEST_TX_TIME)
verifies()
}
}
// DOCEND 7
// DOCSTART 8
@Test(timeout=300_000)
fun `chain commercial paper`() {
val issuer = megaCorp.party.ref(123)
ledgerServices.ledger(dummyNotary.party) {
unverifiedTransaction {
attachments(Cash.PROGRAM_ID)
output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH issuedBy issuer ownedBy alice.party)
}
// Some CP is issued onto the ledger by MegaCorp.
transaction("Issuance") {
output(CP_PROGRAM_ID, "paper", getPaper())
command(megaCorp.publicKey, CommercialPaper.Commands.Issue())
attachments(CP_PROGRAM_ID)
timeWindow(TEST_TX_TIME)
verifies()
}
transaction("Trade") {
input("paper")
input("alice's $900")
output(Cash.PROGRAM_ID, "borrowed $900", 900.DOLLARS.CASH issuedBy issuer ownedBy megaCorp.party)
output(CP_PROGRAM_ID, "alice's paper", "paper".output<ICommercialPaperState>().withOwner(alice.party))
command(alice.publicKey, Cash.Commands.Move())
command(megaCorp.publicKey, CommercialPaper.Commands.Move())
verifies()
}
}
}
// DOCEND 8
// DOCSTART 9
@Test(timeout=300_000)
fun `chain commercial paper double spend`() {
val issuer = megaCorp.party.ref(123)
ledgerServices.ledger(dummyNotary.party) {
unverifiedTransaction {
attachments(Cash.PROGRAM_ID)
output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH issuedBy issuer ownedBy alice.party)
}
// Some CP is issued onto the ledger by MegaCorp.
transaction("Issuance") {
output(CP_PROGRAM_ID, "paper", getPaper())
command(megaCorp.publicKey, CommercialPaper.Commands.Issue())
attachments(CP_PROGRAM_ID)
timeWindow(TEST_TX_TIME)
verifies()
}
transaction("Trade") {
input("paper")
input("alice's $900")
output(Cash.PROGRAM_ID, "borrowed $900", 900.DOLLARS.CASH issuedBy issuer ownedBy megaCorp.party)
output(CP_PROGRAM_ID, "alice's paper", "paper".output<ICommercialPaperState>().withOwner(alice.party))
command(alice.publicKey, Cash.Commands.Move())
command(megaCorp.publicKey, CommercialPaper.Commands.Move())
verifies()
}
transaction {
input("paper")
// We moved a paper to another pubkey.
output(CP_PROGRAM_ID, "bob's paper", "paper".output<ICommercialPaperState>().withOwner(bob.party))
command(megaCorp.publicKey, CommercialPaper.Commands.Move())
verifies()
}
fails()
}
}
// DOCEND 9
// DOCSTART 10
@Test(timeout=300_000)
fun `chain commercial tweak`() {
val issuer = megaCorp.party.ref(123)
ledgerServices.ledger(dummyNotary.party) {
unverifiedTransaction {
attachments(Cash.PROGRAM_ID)
output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH issuedBy issuer ownedBy alice.party)
}
// Some CP is issued onto the ledger by MegaCorp.
transaction("Issuance") {
output(CP_PROGRAM_ID, "paper", getPaper())
command(megaCorp.publicKey, CommercialPaper.Commands.Issue())
attachments(CP_PROGRAM_ID)
timeWindow(TEST_TX_TIME)
verifies()
}
transaction("Trade") {
input("paper")
input("alice's $900")
output(Cash.PROGRAM_ID, "borrowed $900", 900.DOLLARS.CASH issuedBy issuer ownedBy megaCorp.party)
output(CP_PROGRAM_ID, "alice's paper", "paper".output<ICommercialPaperState>().withOwner(alice.party))
command(alice.publicKey, Cash.Commands.Move(CommercialPaper::class.java))
command(megaCorp.publicKey, CommercialPaper.Commands.Move())
verifies()
}
tweak {
transaction {
input("paper")
// We moved a paper to another pubkey.
output(CP_PROGRAM_ID, "bob's paper", "paper".output<ICommercialPaperState>().withOwner(bob.party))
command(megaCorp.publicKey, CommercialPaper.Commands.Move())
verifies()
}
fails()
}
verifies()
}
}
// DOCEND 10
}

View File

@ -1,99 +0,0 @@
package net.corda.docs.kotlin.txbuild
import net.corda.core.contracts.LinearState
import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.UniqueIdentifier
import net.corda.core.identity.Party
import net.corda.core.internal.packageName_
import net.corda.core.node.ServiceHub
import net.corda.core.node.services.queryBy
import net.corda.core.node.services.vault.QueryCriteria
import net.corda.core.toFuture
import net.corda.core.utilities.getOrThrow
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.StartedMockNode
import org.junit.After
import org.junit.Before
import org.junit.Test
import kotlin.test.assertEquals
class WorkflowTransactionBuildTutorialTest {
private lateinit var mockNet: MockNetwork
private lateinit var aliceNode: StartedMockNode
private lateinit var bobNode: StartedMockNode
private lateinit var alice: Party
private lateinit var bob: Party
// Helper method to locate the latest Vault version of a LinearState
private inline fun <reified T : LinearState> ServiceHub.latest(ref: UniqueIdentifier): StateAndRef<T> {
val linearHeads = vaultService.queryBy<T>(QueryCriteria.LinearStateQueryCriteria(uuid = listOf(ref.id)))
return linearHeads.states.single()
}
@Before
fun setup() {
mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf(javaClass.packageName_))
aliceNode = mockNet.createPartyNode(ALICE_NAME)
bobNode = mockNet.createPartyNode(BOB_NAME)
alice = aliceNode.services.myInfo.identityFromX500Name(ALICE_NAME)
bob = bobNode.services.myInfo.identityFromX500Name(BOB_NAME)
}
@After
fun cleanUp() {
mockNet.stopNodes()
}
@Test(timeout=300_000)
fun `Run workflow to completion`() {
// Setup a vault subscriber to wait for successful upload of the proposal to NodeB
val nodeBVaultUpdate = bobNode.services.vaultService.updates.toFuture()
// Kick of the proposal flow
val flow1 = aliceNode.startFlow(SubmitTradeApprovalFlow("1234", bob, mockNet.defaultNotaryIdentity))
// Wait for the flow to finish
val proposalRef = flow1.getOrThrow()
val proposalLinearId = proposalRef.state.data.linearId
// Wait for NodeB to include it's copy in the vault
nodeBVaultUpdate.get()
// Fetch the latest copy of the state from both nodes
val latestFromA = aliceNode.transaction {
aliceNode.services.latest<TradeApprovalContract.State>(proposalLinearId)
}
val latestFromB = bobNode.transaction {
bobNode.services.latest<TradeApprovalContract.State>(proposalLinearId)
}
// Confirm the state as as expected
assertEquals(WorkflowState.NEW, proposalRef.state.data.state)
assertEquals("1234", proposalRef.state.data.tradeId)
assertEquals(alice, proposalRef.state.data.source)
assertEquals(bob, proposalRef.state.data.counterparty)
assertEquals(proposalRef, latestFromA)
assertEquals(proposalRef, latestFromB)
// Setup a vault subscriber to pause until the final update is in NodeA and NodeB
val nodeAVaultUpdate = aliceNode.services.vaultService.updates.toFuture()
val secondNodeBVaultUpdate = bobNode.services.vaultService.updates.toFuture()
// Run the manual completion flow from NodeB
val flow2 = bobNode.startFlow(SubmitCompletionFlow(latestFromB.ref, WorkflowState.APPROVED))
// wait for the flow to end
val completedRef = flow2.getOrThrow()
// wait for the vault updates to stabilise
nodeAVaultUpdate.get()
secondNodeBVaultUpdate.get()
// Fetch the latest copies from the vault
val finalFromA = aliceNode.transaction {
aliceNode.services.latest<TradeApprovalContract.State>(proposalLinearId)
}
val finalFromB = bobNode.transaction {
bobNode.services.latest<TradeApprovalContract.State>(proposalLinearId)
}
// Confirm the state is as expected
assertEquals(WorkflowState.APPROVED, completedRef.state.data.state)
assertEquals("1234", completedRef.state.data.tradeId)
assertEquals(alice, completedRef.state.data.source)
assertEquals(bob, completedRef.state.data.counterparty)
assertEquals(completedRef, finalFromA)
assertEquals(completedRef, finalFromB)
}
}

View File

@ -1,108 +0,0 @@
package net.corda.docs.kotlin.vault
import net.corda.core.contracts.Amount
import net.corda.core.contracts.ContractState
import net.corda.core.identity.Party
import net.corda.core.internal.packageName
import net.corda.core.internal.packageName_
import net.corda.core.node.services.queryBy
import net.corda.core.node.services.vault.*
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
import net.corda.docs.kotlin.tutorial.helloworld.IOUFlow
import net.corda.finance.*
import net.corda.finance.workflows.getCashBalances
import net.corda.finance.flows.CashIssueFlow
import net.corda.node.services.vault.VaultSchemaV1
import net.corda.testing.core.singleIdentity
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.StartedMockNode
import org.assertj.core.api.Assertions.assertThatCode
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import java.util.*
class CustomVaultQueryTest {
private lateinit var mockNet: MockNetwork
private lateinit var nodeA: StartedMockNode
private lateinit var nodeB: StartedMockNode
private lateinit var notary: Party
@Before
fun setup() {
mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.finance", IOUFlow::class.packageName, javaClass.packageName_, "com.template"))
nodeA = mockNet.createPartyNode()
nodeB = mockNet.createPartyNode()
notary = mockNet.defaultNotaryIdentity
}
@After
fun cleanUp() {
mockNet.stopNodes()
}
@Test(timeout=300_000)
fun `query by max recorded time`() {
nodeA.startFlow(IOUFlow(1000, nodeB.info.singleIdentity())).getOrThrow()
nodeA.startFlow(IOUFlow(500, nodeB.info.singleIdentity())).getOrThrow()
val max = builder { VaultSchemaV1.VaultStates::recordedTime.max() }
val maxCriteria = QueryCriteria.VaultCustomQueryCriteria(max)
val results = nodeA.transaction {
val pageSpecification = PageSpecification(pageNumber = DEFAULT_PAGE_NUM, pageSize = DEFAULT_PAGE_SIZE)
nodeA.services.vaultService.queryBy<ContractState>(criteria = maxCriteria, paging = pageSpecification)
}
assertThatCode { results.otherResults.single() }.doesNotThrowAnyException()
}
@Test(timeout=300_000)
fun `test custom vault query`() {
// issue some cash in several currencies
issueCashForCurrency(POUNDS(1000))
issueCashForCurrency(DOLLARS(900))
issueCashForCurrency(SWISS_FRANCS(800))
val (cashBalancesOriginal, _) = getBalances()
// top up all currencies (by double original)
topUpCurrencies()
val (cashBalancesAfterTopup, _) = getBalances()
assertEquals(cashBalancesOriginal[GBP]?.times(2), cashBalancesAfterTopup[GBP])
assertEquals(cashBalancesOriginal[USD]?.times(2) , cashBalancesAfterTopup[USD])
assertEquals(cashBalancesOriginal[CHF]?.times( 2), cashBalancesAfterTopup[CHF])
}
private fun issueCashForCurrency(amountToIssue: Amount<Currency>) {
// Use NodeA as issuer and create some dollars
nodeA.startFlow(CashIssueFlow(amountToIssue,
OpaqueBytes.of(0x01),
notary)).getOrThrow()
}
private fun topUpCurrencies() {
nodeA.startFlow(TopupIssuerFlow.TopupIssuanceRequester(
nodeA.info.singleIdentity(),
OpaqueBytes.of(0x01),
nodeA.info.singleIdentity(),
notary)
).getOrThrow()
}
private fun getBalances(): Pair<Map<Currency, Amount<Currency>>, Map<Currency, Amount<Currency>>> {
// Print out the balances
val balancesNodesA = nodeA.transaction {
nodeA.services.getCashBalances()
}
println("BalanceA\n$balancesNodesA")
val balancesNodesB = nodeB.transaction {
nodeB.services.getCashBalances()
}
println("BalanceB\n$balancesNodesB")
return Pair(balancesNodesA, balancesNodesB)
}
}

View File

@ -122,6 +122,19 @@ abstract class StatemachineErrorHandlingTest {
} }
} }
@StartableByRPC
class GetNumberOfHospitalizedCheckpointsFlow : FlowLogic<Long>() {
override fun call(): Long {
val sqlStatement = "select count(*) from node_checkpoints where status in (${Checkpoint.FlowStatus.HOSPITALIZED.ordinal})"
return serviceHub.jdbcSession().prepareStatement(sqlStatement).use { ps ->
ps.executeQuery().use { rs ->
rs.next()
rs.getLong(1)
}
}
}
}
// Internal use for testing only!! // Internal use for testing only!!
@StartableByRPC @StartableByRPC
class GetHospitalCountersFlow : FlowLogic<HospitalCounts>() { class GetHospitalCountersFlow : FlowLogic<HospitalCounts>() {

View File

@ -1178,9 +1178,9 @@ class StatemachineGeneralErrorHandlingTest : StatemachineErrorHandlingTest() {
// 1 for the flow that is waiting for the errored counterparty flow to finish and 1 for GetNumberOfCheckpointsFlow // 1 for the flow that is waiting for the errored counterparty flow to finish and 1 for GetNumberOfCheckpointsFlow
assertEquals(2, aliceClient.startFlow(StatemachineErrorHandlingTest::GetNumberOfUncompletedCheckpointsFlow).returnValue.get()) assertEquals(2, aliceClient.startFlow(StatemachineErrorHandlingTest::GetNumberOfUncompletedCheckpointsFlow).returnValue.get())
// 1 for GetNumberOfCheckpointsFlow // 1 for GetNumberOfCheckpointsFlow
// the checkpoint is not persisted since it kept failing the original checkpoint commit // a hospitalized flow is saved as the original checkpoint kept failing to commit
// the flow will recover since artemis will keep the events and replay them on node restart // the flow will recover since artemis will keep the events and replay them on node restart
assertEquals(1, charlieClient.startFlow(StatemachineErrorHandlingTest::GetNumberOfUncompletedCheckpointsFlow).returnValue.get()) assertEquals(1, charlieClient.startFlow(StatemachineErrorHandlingTest::GetNumberOfHospitalizedCheckpointsFlow).returnValue.get())
} }
} }

View File

@ -339,8 +339,7 @@ open class NodeStartup : NodeStartupLogging {
if (devMode) return true if (devMode) return true
if (!certDirectory.isDirectory()) { if (!certDirectory.isDirectory()) {
printError("Unable to access certificates directory ${certDirectory}. This could be because the node has not been registered with the Identity Operator.") logger.error("Unable to access certificates directory ${certDirectory}. This could be because the node has not been registered with the Identity Operator. Node will now shutdown")
printError("Node will now shutdown.")
return false return false
} }
return true return true
@ -510,6 +509,7 @@ interface NodeStartupLogging {
fun CliWrapperBase.initLogging(baseDirectory: Path): Boolean { fun CliWrapperBase.initLogging(baseDirectory: Path): Boolean {
System.setProperty("defaultLogLevel", specifiedLogLevel) // These properties are referenced from the XML config file. System.setProperty("defaultLogLevel", specifiedLogLevel) // These properties are referenced from the XML config file.
System.setProperty("log-path", (baseDirectory / NodeCliCommand.LOGS_DIRECTORY_NAME).toString())
if (verbose) { if (verbose) {
System.setProperty("consoleLoggingEnabled", "true") System.setProperty("consoleLoggingEnabled", "true")
System.setProperty("consoleLogLevel", specifiedLogLevel) System.setProperty("consoleLogLevel", specifiedLogLevel)
@ -532,7 +532,6 @@ fun CliWrapperBase.initLogging(baseDirectory: Path): Boolean {
return false return false
} }
System.setProperty("log-path", (baseDirectory / NodeCliCommand.LOGS_DIRECTORY_NAME).toString())
SLF4JBridgeHandler.removeHandlersForRootLogger() // The default j.u.l config adds a ConsoleHandler. SLF4JBridgeHandler.removeHandlersForRootLogger() // The default j.u.l config adds a ConsoleHandler.
SLF4JBridgeHandler.install() SLF4JBridgeHandler.install()
return true return true

View File

@ -5,6 +5,7 @@ import io.github.classgraph.ClassInfo
import io.github.classgraph.ScanResult import io.github.classgraph.ScanResult
import net.corda.common.logging.errorReporting.CordappErrors import net.corda.common.logging.errorReporting.CordappErrors
import net.corda.common.logging.errorReporting.ErrorCode import net.corda.common.logging.errorReporting.ErrorCode
import net.corda.core.CordaRuntimeException
import net.corda.core.cordapp.Cordapp import net.corda.core.cordapp.Cordapp
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256 import net.corda.core.crypto.sha256
@ -105,12 +106,15 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
} }
private fun loadCordapps(): List<CordappImpl> { private fun loadCordapps(): List<CordappImpl> {
val invalidCordapps = mutableMapOf<String, URL>()
val cordapps = cordappJarPaths val cordapps = cordappJarPaths
.map { url -> scanCordapp(url).use { it.toCordapp(url) } } .map { url -> scanCordapp(url).use { it.toCordapp(url) } }
.filter { .filter {
if (it.minimumPlatformVersion > versionInfo.platformVersion) { if (it.minimumPlatformVersion > versionInfo.platformVersion) {
logger.warn("Not loading CorDapp ${it.info.shortName} (${it.info.vendor}) as it requires minimum " + logger.warn("Not loading CorDapp ${it.info.shortName} (${it.info.vendor}) as it requires minimum " +
"platform version ${it.minimumPlatformVersion} (This node is running version ${versionInfo.platformVersion}).") "platform version ${it.minimumPlatformVersion} (This node is running version ${versionInfo.platformVersion}).")
invalidCordapps.put("CorDapp requires minimumPlatformVersion: ${it.minimumPlatformVersion}, but was: ${versionInfo.platformVersion}", it.jarPath)
false false
} else { } else {
true true
@ -125,12 +129,19 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
if (certificates.isEmpty() || (certificates - blockedCertificates).isNotEmpty()) if (certificates.isEmpty() || (certificates - blockedCertificates).isNotEmpty())
true // Cordapp is not signed or it is signed by at least one non-blacklisted certificate true // Cordapp is not signed or it is signed by at least one non-blacklisted certificate
else { else {
logger.warn("Not loading CorDapp ${it.info.shortName} (${it.info.vendor}) as it is signed by development key(s) only: " + logger.warn("Not loading CorDapp ${it.info.shortName} (${it.info.vendor}) as it is signed by blacklisted key(s) only (probably development key): " +
"${blockedCertificates.map { it.publicKey }}.") "${blockedCertificates.map { it.publicKey }}.")
invalidCordapps.put("Corresponding contracts are signed by blacklisted key(s) only (probably development key),", it.jarPath)
false false
} }
} }
} }
if (invalidCordapps.isNotEmpty()) {
throw InvalidCordappException("Invalid Cordapps found, that couldn't be loaded: " +
"${invalidCordapps.map { "Problem: ${it.key} in Cordapp ${it.value}" }}, ")
}
cordapps.forEach(::register) cordapps.forEach(::register)
return cordapps return cordapps
} }
@ -447,7 +458,7 @@ class MultipleCordappsForFlowException(
message: String, message: String,
flowName: String, flowName: String,
jars: String jars: String
) : Exception(message), ErrorCode<CordappErrors> { ) : CordaRuntimeException(message), ErrorCode<CordappErrors> {
override val code = CordappErrors.MULTIPLE_CORDAPPS_FOR_FLOW override val code = CordappErrors.MULTIPLE_CORDAPPS_FOR_FLOW
override val parameters = listOf(flowName, jars) override val parameters = listOf(flowName, jars)
} }
@ -459,19 +470,24 @@ class CordappInvalidVersionException(
msg: String, msg: String,
override val code: CordappErrors, override val code: CordappErrors,
override val parameters: List<Any> = listOf() override val parameters: List<Any> = listOf()
) : Exception(msg), ErrorCode<CordappErrors> ) : CordaRuntimeException(msg), ErrorCode<CordappErrors>
/** /**
* Thrown if duplicate CorDapps are installed on the node * Thrown if duplicate CorDapps are installed on the node
*/ */
class DuplicateCordappsInstalledException(app: Cordapp, duplicates: Set<Cordapp>) class DuplicateCordappsInstalledException(app: Cordapp, duplicates: Set<Cordapp>)
: IllegalStateException("The CorDapp (name: ${app.info.shortName}, file: ${app.name}) " + : CordaRuntimeException("IllegalStateExcepion", "The CorDapp (name: ${app.info.shortName}, file: ${app.name}) " +
"is installed multiple times on the node. The following files correspond to the exact same content: " + "is installed multiple times on the node. The following files correspond to the exact same content: " +
"${duplicates.map { it.name }}"), ErrorCode<CordappErrors> { "${duplicates.map { it.name }}", null), ErrorCode<CordappErrors> {
override val code = CordappErrors.DUPLICATE_CORDAPPS_INSTALLED override val code = CordappErrors.DUPLICATE_CORDAPPS_INSTALLED
override val parameters = listOf(app.info.shortName, app.name, duplicates.map { it.name }) override val parameters = listOf(app.info.shortName, app.name, duplicates.map { it.name })
} }
/**
* Thrown if an exception occurs during loading cordapps.
*/
class InvalidCordappException(message: String) : CordaRuntimeException(message)
abstract class CordappLoaderTemplate : CordappLoader { abstract class CordappLoaderTemplate : CordappLoader {
companion object { companion object {

View File

@ -9,16 +9,13 @@ import liquibase.statement.core.UpdateStatement
import net.corda.core.crypto.toStringShort import net.corda.core.crypto.toStringShort
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.PartyAndCertificate import net.corda.core.identity.PartyAndCertificate
import net.corda.core.utilities.contextLogger
import net.corda.node.services.identity.PersistentIdentityService
import net.corda.nodeapi.internal.crypto.X509CertificateFactory import net.corda.nodeapi.internal.crypto.X509CertificateFactory
class PersistentIdentityMigration : CustomSqlChange { class PersistentIdentityMigration : CustomSqlChange {
companion object { companion object {
private val logger = contextLogger() const val PUB_KEY_HASH_TO_PARTY_AND_CERT_TABLE = "node_identities"
const val PUB_KEY_HASH_TO_PARTY_AND_CERT_TABLE = PersistentIdentityService.HASH_TO_IDENTITY_TABLE_NAME const val X500_NAME_TO_PUB_KEY_HASH_TABLE = "node_named_identities"
const val X500_NAME_TO_PUB_KEY_HASH_TABLE = PersistentIdentityService.NAME_TO_HASH_TABLE_NAME
} }
override fun validate(database: Database?): ValidationErrors? { override fun validate(database: Database?): ValidationErrors? {

View File

@ -1,28 +1,27 @@
package net.corda.node.migration package net.corda.node.migration
import liquibase.change.custom.CustomTaskChange
import liquibase.database.Database import liquibase.database.Database
import liquibase.database.jvm.JdbcConnection import liquibase.database.jvm.JdbcConnection
import liquibase.exception.ValidationErrors import liquibase.exception.ValidationErrors
import liquibase.resource.ResourceAccessor import liquibase.resource.ResourceAccessor
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.PartyAndCertificate import net.corda.core.identity.PartyAndCertificate
import net.corda.core.schemas.MappedSchema
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.node.internal.DBNetworkParametersStorage
import net.corda.node.services.identity.PersistentIdentityService
import net.corda.node.services.persistence.DBTransactionStorage
import net.corda.node.services.persistence.NodeAttachmentService
import net.corda.nodeapi.internal.crypto.X509CertificateFactory import net.corda.nodeapi.internal.crypto.X509CertificateFactory
import net.corda.nodeapi.internal.persistence.SchemaMigration
import java.security.PublicKey import java.security.PublicKey
/** /**
* Migration that reads data from the [PersistentIdentityCert] table, extracts the parameters required to insert into the [PersistentIdentity] table. * Migration that reads data from the [PersistentIdentityCert] table, extracts the parameters required to insert into the [PersistentIdentity] table.
*/ */
class PersistentIdentityMigrationNewTable : CordaMigration() { class PersistentIdentityMigrationNewTable : CustomTaskChange {
companion object { companion object {
private val logger = contextLogger() private val logger = contextLogger()
} }
private lateinit var ourName: CordaX500Name
override fun execute(database: Database?) { override fun execute(database: Database?) {
logger.info("Migrating persistent identities with certificates table into persistent table with no certificate data.") logger.info("Migrating persistent identities with certificates table into persistent table with no certificate data.")
@ -30,7 +29,7 @@ class PersistentIdentityMigrationNewTable : CordaMigration() {
logger.error("Cannot migrate persistent identities: Liquibase failed to provide a suitable database connection") logger.error("Cannot migrate persistent identities: Liquibase failed to provide a suitable database connection")
throw PersistentIdentitiesMigrationException("Cannot migrate persistent identities as liquibase failed to provide a suitable database connection") throw PersistentIdentitiesMigrationException("Cannot migrate persistent identities as liquibase failed to provide a suitable database connection")
} }
initialiseNodeServices(database, setOf(PersistentIdentitiesMigrationSchemaBuilder.getMappedSchema())) ourName = CordaX500Name.parse(System.getProperty(SchemaMigration.NODE_X500_NAME))
val connection = database.connection as JdbcConnection val connection = database.connection as JdbcConnection
val hashToKeyAndName = extractKeyAndName(connection) val hashToKeyAndName = extractKeyAndName(connection)
@ -68,7 +67,7 @@ class PersistentIdentityMigrationNewTable : CordaMigration() {
it.setString(2, name.toString()) it.setString(2, name.toString())
it.executeUpdate() it.executeUpdate()
} }
if (name !in identityService.ourNames) { if (name != ourName) {
connection.prepareStatement("INSERT INTO node_hash_to_key (pk_hash, public_key) VALUES (?,?)").use { connection.prepareStatement("INSERT INTO node_hash_to_key (pk_hash, public_key) VALUES (?,?)").use {
it.setString(1, publicKeyHash) it.setString(1, publicKeyHash)
it.setBytes(2, publicKey.encoded) it.setBytes(2, publicKey.encoded)
@ -92,26 +91,4 @@ class PersistentIdentityMigrationNewTable : CordaMigration() {
} }
} }
/**
* A minimal set of schema for retrieving data from the database.
*
* Note that adding an extra schema here may cause migrations to fail if it ends up creating a table before the same table
* is created in a migration script. As such, this migration must be run after the tables for the following have been created (and,
* if they are removed in the future, before they are deleted).
*/
object PersistentIdentitiesMigrationSchema
object PersistentIdentitiesMigrationSchemaBuilder {
fun getMappedSchema() =
MappedSchema(schemaFamily = PersistentIdentitiesMigrationSchema.javaClass, version = 1,
mappedTypes = listOf(
DBTransactionStorage.DBTransaction::class.java,
PersistentIdentityService.PersistentPublicKeyHashToCertificate::class.java,
PersistentIdentityService.PersistentPartyToPublicKeyHash::class.java,
PersistentIdentityService.PersistentPublicKeyHashToParty::class.java,
PersistentIdentityService.PersistentHashToPublicKey::class.java,
NodeAttachmentService.DBAttachment::class.java,
DBNetworkParametersStorage.PersistentNetworkParameters::class.java
))
}
class PersistentIdentitiesMigrationException(msg: String, cause: Exception? = null) : Exception(msg, cause) class PersistentIdentitiesMigrationException(msg: String, cause: Exception? = null) : Exception(msg, cause)

View File

@ -77,7 +77,7 @@ internal object V1NodeConfigurationSpec : Configuration.Specification<NodeConfig
val messagingServerExternal = config[messagingServerExternal] ?: Defaults.messagingServerExternal(config[messagingServerAddress]) val messagingServerExternal = config[messagingServerExternal] ?: Defaults.messagingServerExternal(config[messagingServerAddress])
val database = config[database] ?: Defaults.database(config[devMode]) val database = config[database] ?: Defaults.database(config[devMode])
val baseDirectoryPath = config[baseDirectory] val baseDirectoryPath = config[baseDirectory]
val cordappDirectories = config[cordappDirectories] ?: Defaults.cordappsDirectories(baseDirectoryPath) val cordappDirectories = config[cordappDirectories]?.map { baseDirectoryPath.resolve(it) } ?: Defaults.cordappsDirectories(baseDirectoryPath)
val result = try { val result = try {
valid<NodeConfigurationImpl, Configuration.Validation.Error>(NodeConfigurationImpl( valid<NodeConfigurationImpl, Configuration.Validation.Error>(NodeConfigurationImpl(
baseDirectory = baseDirectoryPath, baseDirectory = baseDirectoryPath,
@ -127,7 +127,7 @@ internal object V1NodeConfigurationSpec : Configuration.Specification<NodeConfig
attachmentContentCacheSizeMegaBytes = config[attachmentContentCacheSizeMegaBytes], attachmentContentCacheSizeMegaBytes = config[attachmentContentCacheSizeMegaBytes],
h2port = config[h2port], h2port = config[h2port],
jarDirs = config[jarDirs], jarDirs = config[jarDirs],
cordappDirectories = cordappDirectories.map { baseDirectoryPath.resolve(it) }, cordappDirectories = cordappDirectories,
cordappSignerKeyFingerprintBlacklist = config[cordappSignerKeyFingerprintBlacklist], cordappSignerKeyFingerprintBlacklist = config[cordappSignerKeyFingerprintBlacklist],
blacklistedAttachmentSigningKeys = config[blacklistedAttachmentSigningKeys], blacklistedAttachmentSigningKeys = config[blacklistedAttachmentSigningKeys],
networkParameterAcceptanceSettings = config[networkParameterAcceptanceSettings], networkParameterAcceptanceSettings = config[networkParameterAcceptanceSettings],

View File

@ -24,13 +24,12 @@
<include file="migration/node-core.changelog-v13.xml"/> <include file="migration/node-core.changelog-v13.xml"/>
<!-- This change should be done before the v14-data migration. --> <!-- This change should be done before the v14-data migration. -->
<include file="migration/node-core.changelog-v15.xml"/> <include file="migration/node-core.changelog-v15.xml"/>
<include file="migration/node-core.changelog-v14-data.xml"/>
<include file="migration/node-core.changelog-v16.xml"/> <include file="migration/node-core.changelog-v16.xml"/>
<!-- This must run after node-core.changelog-init.xml, to prevent database columns being created twice. --> <!-- This must run after node-core.changelog-init.xml, to prevent database columns being created twice. -->
<include file="migration/vault-schema.changelog-v9.xml"/> <include file="migration/vault-schema.changelog-v9.xml"/>
<include file="migration/node-core.changelog-v14-data.xml"/>
<include file="migration/node-core.changelog-v19.xml"/> <include file="migration/node-core.changelog-v19.xml"/>
<include file="migration/node-core.changelog-v19-postgres.xml"/> <include file="migration/node-core.changelog-v19-postgres.xml"/>
<include file="migration/node-core.changelog-v19-keys.xml"/> <include file="migration/node-core.changelog-v19-keys.xml"/>

View File

@ -2,14 +2,19 @@ package net.corda.node.internal
import net.corda.cliutils.CommonCliConstants import net.corda.cliutils.CommonCliConstants
import net.corda.core.internal.div import net.corda.core.internal.div
import net.corda.core.internal.exists
import net.corda.nodeapi.internal.config.UnknownConfigKeysPolicy import net.corda.nodeapi.internal.config.UnknownConfigKeysPolicy
import org.assertj.core.api.Assertions import org.assertj.core.api.Assertions
import org.junit.BeforeClass import org.junit.BeforeClass
import org.junit.Test import org.junit.Test
import org.slf4j.LoggerFactory
import org.slf4j.event.Level import org.slf4j.event.Level
import picocli.CommandLine import picocli.CommandLine
import java.nio.file.Files
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.Paths import java.nio.file.Paths
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class NodeStartupCliTest { class NodeStartupCliTest {
private val startup = NodeStartupCli() private val startup = NodeStartupCli()
@ -49,4 +54,17 @@ class NodeStartupCliTest {
Assertions.assertThat(startup.cmdLineOptions.configFile).isEqualTo(workingDirectory / "another-base-dir" / "node.conf") Assertions.assertThat(startup.cmdLineOptions.configFile).isEqualTo(workingDirectory / "another-base-dir" / "node.conf")
Assertions.assertThat(startup.cmdLineOptions.networkRootTrustStorePathParameter).isEqualTo(null) Assertions.assertThat(startup.cmdLineOptions.networkRootTrustStorePathParameter).isEqualTo(null)
} }
@Test(timeout=3_000)
fun `test logs are written to correct location correctly if verbose flag set`() {
val node = NodeStartupCli()
val dir = Files.createTempDirectory("verboseLoggingTest")
node.verbose = true
// With verbose set, initLogging can accidentally attempt to access a logger before all required system properties are set. This
// causes the logging config to be parsed too early, resulting in logs being written to the wrong directory
node.initLogging(dir)
LoggerFactory.getLogger("").debug("Test message")
assertTrue(dir.resolve("logs").exists())
assertFalse(Paths.get("./logs").exists())
}
} }

View File

@ -203,7 +203,7 @@ class CordappProviderImplTests {
Files.copy(signedJarPath, duplicateJarPath) Files.copy(signedJarPath, duplicateJarPath)
val urls = asList(signedJarPath.toUri().toURL(), duplicateJarPath.toUri().toURL()) val urls = asList(signedJarPath.toUri().toURL(), duplicateJarPath.toUri().toURL())
JarScanningCordappLoader.fromJarUrls(urls, VersionInfo.UNKNOWN).use { JarScanningCordappLoader.fromJarUrls(urls, VersionInfo.UNKNOWN).use {
assertFailsWith<IllegalStateException> { assertFailsWith<DuplicateCordappsInstalledException> {
CordappProviderImpl(it, stubConfigProvider, attachmentStore).apply { start() } CordappProviderImpl(it, stubConfigProvider, attachmentStore).apply { start() }
} }
} }

View File

@ -137,11 +137,10 @@ class JarScanningCordappLoaderTest {
assertThat(cordapp.minimumPlatformVersion).isEqualTo(2) assertThat(cordapp.minimumPlatformVersion).isEqualTo(2)
} }
@Test(timeout=300_000) @Test(expected = InvalidCordappException::class, timeout = 300_000)
fun `cordapp classloader does not load apps when their min platform version is greater than the node platform version`() { fun `cordapp classloader does not load apps when their min platform version is greater than the node platform version`() {
val jar = JarScanningCordappLoaderTest::class.java.getResource("versions/min-2-no-target.jar")!! val jar = JarScanningCordappLoaderTest::class.java.getResource("versions/min-2-no-target.jar")!!
val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), VersionInfo.UNKNOWN.copy(platformVersion = 1)) JarScanningCordappLoader.fromJarUrls(listOf(jar), VersionInfo.UNKNOWN.copy(platformVersion = 1)).cordapps
assertThat(loader.cordapps).hasSize(0)
} }
@Test(timeout=300_000) @Test(timeout=300_000)
@ -165,11 +164,10 @@ class JarScanningCordappLoaderTest {
assertThat(loader.cordapps).hasSize(1) assertThat(loader.cordapps).hasSize(1)
} }
@Test(timeout=300_000) @Test(expected = InvalidCordappException::class, timeout = 300_000)
fun `cordapp classloader does not load app signed by blacklisted certificate`() { fun `cordapp classloader does not load app signed by blacklisted certificate`() {
val jar = JarScanningCordappLoaderTest::class.java.getResource("signed/signed-by-dev-key.jar")!! val jar = JarScanningCordappLoaderTest::class.java.getResource("signed/signed-by-dev-key.jar")!!
val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), cordappsSignerKeyFingerprintBlacklist = DEV_PUB_KEY_HASHES) JarScanningCordappLoader.fromJarUrls(listOf(jar), cordappsSignerKeyFingerprintBlacklist = DEV_PUB_KEY_HASHES).cordapps
assertThat(loader.cordapps).hasSize(0)
} }
@Test(timeout=300_000) @Test(timeout=300_000)

View File

@ -1,5 +1,7 @@
package net.corda.node.migration package net.corda.node.migration
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever
import liquibase.database.core.H2Database import liquibase.database.core.H2Database
import liquibase.database.jvm.JdbcConnection import liquibase.database.jvm.JdbcConnection
import net.corda.core.crypto.toStringShort import net.corda.core.crypto.toStringShort
@ -7,7 +9,8 @@ import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.PartyAndCertificate import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.hash import net.corda.core.internal.hash
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.node.services.identity.PersistentIdentityService import net.corda.coretesting.internal.rigorousMock
import net.corda.node.services.api.SchemaService
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.nodeapi.internal.persistence.contextTransactionOrNull import net.corda.nodeapi.internal.persistence.contextTransactionOrNull
@ -44,11 +47,16 @@ class IdentityServiceToStringShortMigrationTest {
@Before @Before
fun setUp() { fun setUp() {
val schemaService = rigorousMock<SchemaService>()
doReturn(setOf(IdentityTestSchemaV1)).whenever(schemaService).schemas
cordaDB = configureDatabase( cordaDB = configureDatabase(
makeTestDataSourceProperties(), makeTestDataSourceProperties(),
DatabaseConfig(), DatabaseConfig(),
{ null }, { null },
{ null }, { null },
schemaService = schemaService,
internalSchemas = setOf(),
ourName = BOB_IDENTITY.name) ourName = BOB_IDENTITY.name)
liquibaseDB = H2Database() liquibaseDB = H2Database()
liquibaseDB.connection = JdbcConnection(cordaDB.dataSource.connection) liquibaseDB.connection = JdbcConnection(cordaDB.dataSource.connection)
@ -66,8 +74,8 @@ class IdentityServiceToStringShortMigrationTest {
cordaDB.transaction { cordaDB.transaction {
val groupedIdentities = identities.groupBy { it.name } val groupedIdentities = identities.groupBy { it.name }
groupedIdentities.forEach { name, certs -> groupedIdentities.forEach { name, certs ->
val persistentIDs = certs.map { PersistentIdentityService.PersistentPublicKeyHashToCertificate(it.owningKey.hash.toString(), it.certPath.encoded) } val persistentIDs = certs.map { IdentityTestSchemaV1.NodeIdentities(it.owningKey.hash.toString(), it.certPath.encoded) }
val persistentName = PersistentIdentityService.PersistentPartyToPublicKeyHash(name.toString(), certs.first().owningKey.hash.toString()) val persistentName = IdentityTestSchemaV1.NodeNamedIdentities(name.toString(), certs.first().owningKey.hash.toString())
persistentIDs.forEach { persistentIDs.forEach {
session.persist(it) session.persist(it)
} }
@ -87,7 +95,7 @@ class IdentityServiceToStringShortMigrationTest {
identities.forEach { identities.forEach {
logger.info("Checking: ${it.name}") logger.info("Checking: ${it.name}")
cordaDB.transaction { cordaDB.transaction {
val hashToIdentityStatement = database.dataSource.connection.prepareStatement("SELECT ${PersistentIdentityService.PK_HASH_COLUMN_NAME} FROM ${PersistentIdentityService.HASH_TO_IDENTITY_TABLE_NAME} WHERE pk_hash=?") val hashToIdentityStatement = database.dataSource.connection.prepareStatement("SELECT pk_hash FROM node_identities WHERE pk_hash=?")
hashToIdentityStatement.setString(1, it.owningKey.toStringShort()) hashToIdentityStatement.setString(1, it.owningKey.toStringShort())
val hashToIdentityResultSet = hashToIdentityStatement.executeQuery() val hashToIdentityResultSet = hashToIdentityStatement.executeQuery()
@ -96,7 +104,7 @@ class IdentityServiceToStringShortMigrationTest {
//check that the pk_hash actually matches what we expect (kinda redundant, but deserializing the whole PartyAndCertificate feels like overkill) //check that the pk_hash actually matches what we expect (kinda redundant, but deserializing the whole PartyAndCertificate feels like overkill)
Assert.assertThat(hashToIdentityResultSet.getString(1), `is`(it.owningKey.toStringShort())) Assert.assertThat(hashToIdentityResultSet.getString(1), `is`(it.owningKey.toStringShort()))
val nameToHashStatement = connection.prepareStatement("SELECT ${PersistentIdentityService.NAME_COLUMN_NAME} FROM ${PersistentIdentityService.NAME_TO_HASH_TABLE_NAME} WHERE pk_hash=?") val nameToHashStatement = connection.prepareStatement("SELECT name FROM node_named_identities WHERE pk_hash=?")
nameToHashStatement.setString(1, it.owningKey.toStringShort()) nameToHashStatement.setString(1, it.owningKey.toStringShort())
val nameToHashResultSet = nameToHashStatement.executeQuery() val nameToHashResultSet = nameToHashStatement.executeQuery()

View File

@ -0,0 +1,78 @@
package net.corda.node.migration
import net.corda.core.schemas.MappedSchema
import net.corda.core.utilities.MAX_HASH_HEX_SIZE
import org.apache.commons.lang3.ArrayUtils
import org.hibernate.annotations.Type
import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.Id
import javax.persistence.Table
object MigrationTestSchema
/**
* Schema definition for testing PersistentIdentityService custom migration scripts at the moment when scripts were written.
* Used to break dependency on the latest PersistentIdentityService which schema version may be different.
*
* This will allow:
* - to fix the position of relevant scripts in the node-core.changelog-master.xml (instead of placing them at the end)
* - to perform further modifications of PersistentIdentityService schema without impacting existing migration scripts and their tests
*/
object IdentityTestSchemaV1 : MappedSchema(
schemaFamily = MigrationTestSchema::class.java,
version = 1,
mappedTypes = listOf(
NodeIdentities::class.java,
NodeNamedIdentities::class.java,
NodeIdentitiesNoCert::class.java,
NodeHashToKey::class.java
)
) {
@Entity
@Table(name = "node_identities")
class NodeIdentities(
@Id
@Column(name = "pk_hash", length = MAX_HASH_HEX_SIZE, nullable = false)
var publicKeyHash: String = "",
@Type(type = "corda-blob")
@Column(name = "identity_value", nullable = false)
var identity: ByteArray = ArrayUtils.EMPTY_BYTE_ARRAY
)
@Entity
@Table(name = "node_named_identities")
class NodeNamedIdentities(
@Id
@Suppress("MagicNumber") // database column width
@Column(name = "name", length = 128, nullable = false)
var name: String = "",
@Column(name = "pk_hash", length = MAX_HASH_HEX_SIZE, nullable = false)
var publicKeyHash: String = ""
)
@Entity
@Table(name = "node_identities_no_cert")
class NodeIdentitiesNoCert(
@Id
@Column(name = "pk_hash", length = MAX_HASH_HEX_SIZE, nullable = false)
var publicKeyHash: String = "",
@Column(name = "name", length = 128, nullable = false)
var name: String = ""
)
@Entity
@Table(name = "node_hash_to_key")
class NodeHashToKey(
@Id
@Column(name = "pk_hash", length = MAX_HASH_HEX_SIZE, nullable = false)
var publicKeyHash: String = "",
@Type(type = "corda-blob")
@Column(name = "public_key", nullable = false)
var publicKey: ByteArray = ArrayUtils.EMPTY_BYTE_ARRAY
)
}

View File

@ -1,48 +1,35 @@
package net.corda.node.migration package net.corda.node.migration
import liquibase.database.Database import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever
import liquibase.database.core.H2Database
import liquibase.database.jvm.JdbcConnection import liquibase.database.jvm.JdbcConnection
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.Crypto
import net.corda.core.identity.CordaX500Name import net.corda.core.crypto.toStringShort
import net.corda.core.identity.PartyAndCertificate import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.hash import net.corda.coretesting.internal.rigorousMock
import net.corda.core.internal.signWithCert import net.corda.node.services.api.SchemaService
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NotaryInfo
import net.corda.node.internal.DBNetworkParametersStorage
import net.corda.node.migration.VaultStateMigrationTest.Companion.CHARLIE
import net.corda.node.migration.VaultStateMigrationTest.Companion.DUMMY_NOTARY
import net.corda.node.services.identity.PersistentIdentityService
import net.corda.node.services.keys.BasicHSMKeyManagementService
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.nodeapi.internal.persistence.SchemaMigration
import net.corda.testing.core.* import net.corda.testing.core.*
import net.corda.testing.internal.configureDatabase import net.corda.testing.internal.configureDatabase
import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices
import net.corda.testing.node.makeTestIdentityService import org.assertj.core.api.Assertions.assertThat
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.ClassRule import org.junit.ClassRule
import org.junit.Test import org.junit.Test
import org.mockito.Mockito
import java.security.KeyPair
import java.sql.Connection
import java.time.Clock
import java.time.Duration
import java.util.*
class PersistentIdentityMigrationNewTableTest{ class PersistentIdentityMigrationNewTableTest {
companion object { companion object {
val alice = TestIdentity(ALICE_NAME, 70) val alice = TestIdentity(ALICE_NAME, 70)
val bankOfCorda = TestIdentity(BOC_NAME) val bankOfCorda = TestIdentity(BOC_NAME)
val bob = TestIdentity(BOB_NAME, 80) val bob = TestIdentity(BOB_NAME, 80)
val dummyCashIssuer = TestIdentity(CordaX500Name("Snake Oil Issuer", "London", "GB"), 10)
val dummyNotary = TestIdentity(DUMMY_NOTARY_NAME, 20) val dummyNotary = TestIdentity(DUMMY_NOTARY_NAME, 20)
val ALICE_IDENTITY get() = alice.identity val ALICE_IDENTITY get() = alice.identity
val BOB_IDENTITY get() = bob.identity val BOB_IDENTITY get() = bob.identity
val BOC_IDENTITY get() = bankOfCorda.identity val BOC_IDENTITY get() = bankOfCorda.identity
val BOC_KEY get() = bankOfCorda.keyPair
val bob2 = TestIdentity(BOB_NAME, 40) val bob2 = TestIdentity(BOB_NAME, 40)
val BOB2_IDENTITY = bob2.identity val BOB2_IDENTITY = bob2.identity
@ -51,122 +38,62 @@ class PersistentIdentityMigrationNewTableTest{
val testSerialization = SerializationEnvironmentRule() val testSerialization = SerializationEnvironmentRule()
} }
lateinit var liquidBaseDB: Database lateinit var liquibaseDB: H2Database
lateinit var cordaDB: CordaPersistence lateinit var cordaDB: CordaPersistence
lateinit var notaryServices: MockServices
@Before @Before
fun setUp() { fun setUp() {
val identityService = makeTestIdentityService(PersistentIdentityMigrationNewTableTest.dummyNotary.identity, BOB_IDENTITY, ALICE_IDENTITY) val schemaService = rigorousMock<SchemaService>()
notaryServices = MockServices(listOf("net.corda.finance.contracts"), dummyNotary, identityService, dummyCashIssuer.keyPair, BOC_KEY) doReturn(setOf(IdentityTestSchemaV1)).whenever(schemaService).schemas
System.setProperty(SchemaMigration.NODE_X500_NAME, BOB_IDENTITY.name.toString())
// Runs migration tasks
cordaDB = configureDatabase( cordaDB = configureDatabase(
MockServices.makeTestDataSourceProperties(), MockServices.makeTestDataSourceProperties(),
DatabaseConfig(), DatabaseConfig(),
notaryServices.identityService::wellKnownPartyFromX500Name, { null },
notaryServices.identityService::wellKnownPartyFromAnonymous, { null },
schemaService = schemaService,
internalSchemas = setOf(),
ourName = BOB_IDENTITY.name) ourName = BOB_IDENTITY.name)
val liquidbaseConnection = Mockito.mock(JdbcConnection::class.java) liquibaseDB = H2Database()
Mockito.`when`(liquidbaseConnection.url).thenReturn(cordaDB.jdbcUrl) liquibaseDB.connection = JdbcConnection(cordaDB.dataSource.connection)
Mockito.`when`(liquidbaseConnection.wrappedConnection).thenReturn(cordaDB.dataSource.connection) liquibaseDB.isAutoCommit = true
liquidBaseDB = Mockito.mock(Database::class.java)
Mockito.`when`(liquidBaseDB.connection).thenReturn(liquidbaseConnection)
cordaDB.dataSource.connection
saveOurKeys(listOf(bob.keyPair, bob2.keyPair))
saveAllIdentities(listOf(BOB_IDENTITY, ALICE_IDENTITY, BOC_IDENTITY, dummyNotary.identity, BOB2_IDENTITY))
addNetworkParameters()
} }
@After @After
fun `close`() { fun close() {
cordaDB.close() cordaDB.close()
} }
@Test(timeout=300_000) @Test(timeout = 300_000)
fun `migrate identities to new table`() { fun `migrate identities to new table`() {
val pkHash = addTestMapping(cordaDB.dataSource.connection, alice) val identities = listOf(BOB_IDENTITY, ALICE_IDENTITY, BOC_IDENTITY, dummyNotary.identity, BOB2_IDENTITY)
PersistentIdentityMigrationNewTable() saveAllIdentities(identities)
verifyTestMigration(cordaDB.dataSource.connection, pkHash, alice.name.toString())
PersistentIdentityMigrationNewTable().execute(liquibaseDB)
val expectedParties = identities.map { it.owningKey.toStringShort() to it.toString() }
val actualParties = selectAll<IdentityTestSchemaV1.NodeIdentitiesNoCert>().map { it.publicKeyHash to it.name }
assertThat(actualParties).isEqualTo(expectedParties)
val expectedKeys = listOf(ALICE_IDENTITY, BOC_IDENTITY, dummyNotary.identity).map { it.owningKey.toStringShort() to it.owningKey }
val actualKeys = selectAll<IdentityTestSchemaV1.NodeHashToKey>().map { it.publicKeyHash to Crypto.decodePublicKey(it.publicKey) }
assertThat(actualKeys).isEqualTo(expectedKeys)
} }
private fun saveAllIdentities(identities: List<PartyAndCertificate>) { private fun saveAllIdentities(identities: List<PartyAndCertificate>) {
cordaDB.transaction { cordaDB.transaction {
identities.forEach { identities.forEach {
session.save(PersistentIdentityService.PersistentPublicKeyHashToCertificate(it.owningKey.hash.toString(), it.certPath.encoded)) session.save(IdentityTestSchemaV1.NodeIdentities(it.owningKey.toStringShort(), it.certPath.encoded))
} }
} }
} }
private fun saveOurKeys(keys: List<KeyPair>) { private inline fun <reified T> selectAll(): List<T> {
cordaDB.transaction { return cordaDB.transaction {
keys.forEach { val criteria = session.criteriaBuilder.createQuery(T::class.java)
val persistentKey = BasicHSMKeyManagementService.PersistentKey(it.public, it.private) criteria.select(criteria.from(T::class.java))
session.save(persistentKey) session.createQuery(criteria).resultList
}
}
}
private fun addNetworkParameters() {
cordaDB.transaction {
val clock = Clock.systemUTC()
val params = NetworkParameters(
1,
listOf(NotaryInfo(DUMMY_NOTARY, false), NotaryInfo(CHARLIE, false)),
1,
1,
clock.instant(),
1,
mapOf(),
Duration.ZERO,
mapOf()
)
val signedParams = params.signWithCert(bob.keyPair.private, BOB_IDENTITY.certificate)
val persistentParams = DBNetworkParametersStorage.PersistentNetworkParameters(
SecureHash.allOnesHash.toString(),
params.epoch,
signedParams.raw.bytes,
signedParams.sig.bytes,
signedParams.sig.by.encoded,
X509Utilities.buildCertPath(signedParams.sig.parentCertsChain).encoded
)
session.save(persistentParams)
}
}
private fun addTestMapping(connection: Connection, testIdentity: TestIdentity): String {
val pkHash = UUID.randomUUID().toString()
val cert = testIdentity.identity.certPath.encoded
connection.prepareStatement("INSERT INTO node_identities (pk_hash, identity_value) VALUES (?,?)").use {
it.setString(1, pkHash)
it.setBytes(2, cert)
it.executeUpdate()
}
return pkHash
}
// private fun deleteTestMapping(connection: Connection, pkHash: String) {
// connection.prepareStatement("DELETE FROM node_identities WHERE pk_hash = ?").use {
// it.setString(1, pkHash)
// it.executeUpdate()
// }
// }
private fun verifyTestMigration(connection: Connection, pk: String, name: String) {
connection.createStatement().use {
try {
val rs = it.executeQuery("SELECT (pk_hash, name) FROM node_identities_no_cert")
while (rs.next()) {
val result = rs.getString(1)
require(result.contains(pk))
require(result.contains(name))
}
rs.close()
} catch (e: Exception) {
println(e.localizedMessage)
}
} }
} }
} }

View File

@ -21,6 +21,7 @@ import org.junit.Assert.assertNotNull
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.rules.TemporaryFolder import org.junit.rules.TemporaryFolder
import java.io.File
import java.net.URI import java.net.URI
import java.net.URL import java.net.URL
import java.nio.file.Paths import java.nio.file.Paths
@ -34,7 +35,7 @@ class NodeConfigurationImplTest {
@JvmField @JvmField
val tempFolder = TemporaryFolder() val tempFolder = TemporaryFolder()
@Test(timeout=300_000) @Test(timeout=3_000)
fun `can't have dev mode options if not in dev mode`() { fun `can't have dev mode options if not in dev mode`() {
val debugOptions = DevModeOptions() val debugOptions = DevModeOptions()
configDebugOptions(true, debugOptions) configDebugOptions(true, debugOptions)
@ -43,7 +44,7 @@ class NodeConfigurationImplTest {
configDebugOptions(false, null) configDebugOptions(false, null)
} }
@Test(timeout=300_000) @Test(timeout=3_000)
fun `can't have tlsCertCrlDistPoint null when tlsCertCrlIssuer is given`() { fun `can't have tlsCertCrlDistPoint null when tlsCertCrlIssuer is given`() {
val configValidationResult = configTlsCertCrlOptions(null, "C=US, L=New York, OU=Corda, O=R3 HoldCo LLC, CN=Corda Root CA").validate() val configValidationResult = configTlsCertCrlOptions(null, "C=US, L=New York, OU=Corda, O=R3 HoldCo LLC, CN=Corda Root CA").validate()
assertTrue { configValidationResult.isNotEmpty() } assertTrue { configValidationResult.isNotEmpty() }
@ -51,7 +52,7 @@ class NodeConfigurationImplTest {
assertThat(configValidationResult.first()).contains("tlsCertCrlIssuer") assertThat(configValidationResult.first()).contains("tlsCertCrlIssuer")
} }
@Test(timeout=300_000) @Test(timeout=3_000)
fun `can't have tlsCertCrlDistPoint null when crlCheckSoftFail is false`() { fun `can't have tlsCertCrlDistPoint null when crlCheckSoftFail is false`() {
val configValidationResult = configTlsCertCrlOptions(null, null, false).validate() val configValidationResult = configTlsCertCrlOptions(null, null, false).validate()
assertTrue { configValidationResult.isNotEmpty() } assertTrue { configValidationResult.isNotEmpty() }
@ -59,7 +60,7 @@ class NodeConfigurationImplTest {
assertThat(configValidationResult.first()).contains("crlCheckSoftFail") assertThat(configValidationResult.first()).contains("crlCheckSoftFail")
} }
@Test(timeout=300_000) @Test(timeout=3_000)
fun `check devModeOptions flag helper`() { fun `check devModeOptions flag helper`() {
assertTrue { configDebugOptions(true, null).shouldCheckCheckpoints() } assertTrue { configDebugOptions(true, null).shouldCheckCheckpoints() }
assertTrue { configDebugOptions(true, DevModeOptions()).shouldCheckCheckpoints() } assertTrue { configDebugOptions(true, DevModeOptions()).shouldCheckCheckpoints() }
@ -67,7 +68,7 @@ class NodeConfigurationImplTest {
assertFalse { configDebugOptions(true, DevModeOptions(true)).shouldCheckCheckpoints() } assertFalse { configDebugOptions(true, DevModeOptions(true)).shouldCheckCheckpoints() }
} }
@Test(timeout=300_000) @Test(timeout=3_000)
fun `check crashShell flags helper`() { fun `check crashShell flags helper`() {
assertFalse { testConfiguration.copy(sshd = null).shouldStartSSHDaemon() } assertFalse { testConfiguration.copy(sshd = null).shouldStartSSHDaemon() }
assertTrue { testConfiguration.copy(sshd = SSHDConfiguration(1234)).shouldStartSSHDaemon() } assertTrue { testConfiguration.copy(sshd = SSHDConfiguration(1234)).shouldStartSSHDaemon() }
@ -79,7 +80,7 @@ class NodeConfigurationImplTest {
assertFalse { testConfiguration.copy(noLocalShell = true, sshd = null).shouldInitCrashShell() } assertFalse { testConfiguration.copy(noLocalShell = true, sshd = null).shouldInitCrashShell() }
} }
@Test(timeout=300_000) @Test(timeout=3_000)
fun `Dev mode is autodetected correctly`() { fun `Dev mode is autodetected correctly`() {
val os = System.getProperty("os.name") val os = System.getProperty("os.name")
@ -102,20 +103,20 @@ class NodeConfigurationImplTest {
System.setProperty("os.name", os) System.setProperty("os.name", os)
} }
@Test(timeout=300_000) @Test(timeout=3_000)
fun `Dev mode is read from the config over the autodetect logic`() { fun `Dev mode is read from the config over the autodetect logic`() {
assertTrue(getConfig("test-config-DevMode.conf").getBooleanCaseInsensitive("devMode")) assertTrue(getConfig("test-config-DevMode.conf").getBooleanCaseInsensitive("devMode"))
assertFalse(getConfig("test-config-noDevMode.conf").getBooleanCaseInsensitive("devMode")) assertFalse(getConfig("test-config-noDevMode.conf").getBooleanCaseInsensitive("devMode"))
} }
@Test(timeout=300_000) @Test(timeout=3_000)
fun `Dev mode is true if overriden`() { fun `Dev mode is true if overriden`() {
assertTrue(getConfig("test-config-DevMode.conf", ConfigFactory.parseMap(mapOf("devMode" to true))).getBooleanCaseInsensitive("devMode")) assertTrue(getConfig("test-config-DevMode.conf", ConfigFactory.parseMap(mapOf("devMode" to true))).getBooleanCaseInsensitive("devMode"))
assertTrue(getConfig("test-config-noDevMode.conf", ConfigFactory.parseMap(mapOf("devMode" to true))).getBooleanCaseInsensitive("devMode")) assertTrue(getConfig("test-config-noDevMode.conf", ConfigFactory.parseMap(mapOf("devMode" to true))).getBooleanCaseInsensitive("devMode"))
assertTrue(getConfig("test-config-empty.conf", ConfigFactory.parseMap(mapOf("devMode" to true))).getBooleanCaseInsensitive("devMode")) assertTrue(getConfig("test-config-empty.conf", ConfigFactory.parseMap(mapOf("devMode" to true))).getBooleanCaseInsensitive("devMode"))
} }
@Test(timeout=300_000) @Test(timeout=3_000)
fun `Dev mode is false if overriden`() { fun `Dev mode is false if overriden`() {
assertFalse(getConfig("test-config-DevMode.conf", ConfigFactory.parseMap(mapOf("devMode" to false))).getBooleanCaseInsensitive("devMode")) assertFalse(getConfig("test-config-DevMode.conf", ConfigFactory.parseMap(mapOf("devMode" to false))).getBooleanCaseInsensitive("devMode"))
assertFalse(getConfig("test-config-noDevMode.conf", ConfigFactory.parseMap(mapOf("devMode" to false))).getBooleanCaseInsensitive("devMode")) assertFalse(getConfig("test-config-noDevMode.conf", ConfigFactory.parseMap(mapOf("devMode" to false))).getBooleanCaseInsensitive("devMode"))
@ -131,7 +132,7 @@ class NodeConfigurationImplTest {
) )
} }
@Test(timeout=300_000) @Test(timeout=3_000)
fun `validation has error when compatibilityZoneURL is present and devMode is true`() { fun `validation has error when compatibilityZoneURL is present and devMode is true`() {
val configuration = testConfiguration.copy( val configuration = testConfiguration.copy(
devMode = true, devMode = true,
@ -142,7 +143,7 @@ class NodeConfigurationImplTest {
assertThat(errors).hasOnlyOneElementSatisfying { error -> error.contains("compatibilityZoneURL") && error.contains("devMode") } assertThat(errors).hasOnlyOneElementSatisfying { error -> error.contains("compatibilityZoneURL") && error.contains("devMode") }
} }
@Test(timeout=300_000) @Test(timeout=3_000)
fun `validation succeeds when compatibilityZoneURL is present and devMode is true and allowCompatibilityZoneURL is set`() { fun `validation succeeds when compatibilityZoneURL is present and devMode is true and allowCompatibilityZoneURL is set`() {
val configuration = testConfiguration.copy( val configuration = testConfiguration.copy(
devMode = true, devMode = true,
@ -153,7 +154,7 @@ class NodeConfigurationImplTest {
assertThat(errors).isEmpty() assertThat(errors).isEmpty()
} }
@Test(timeout=300_000) @Test(timeout=3_000)
fun `errors for nested config keys contain path`() { fun `errors for nested config keys contain path`() {
var rawConfig = ConfigFactory.parseResources("working-config.conf", ConfigParseOptions.defaults().setAllowMissing(false)) var rawConfig = ConfigFactory.parseResources("working-config.conf", ConfigParseOptions.defaults().setAllowMissing(false))
val missingPropertyPath = "rpcSettings.address" val missingPropertyPath = "rpcSettings.address"
@ -165,7 +166,7 @@ class NodeConfigurationImplTest {
} }
} }
@Test(timeout=300_000) @Test(timeout=3_000)
fun `validation has error when compatibilityZone is present and devMode is true`() { fun `validation has error when compatibilityZone is present and devMode is true`() {
val configuration = testConfiguration.copy(devMode = true, networkServices = NetworkServicesConfig( val configuration = testConfiguration.copy(devMode = true, networkServices = NetworkServicesConfig(
URL("https://r3.com.doorman"), URL("https://r3.com.doorman"),
@ -176,7 +177,7 @@ class NodeConfigurationImplTest {
assertThat(errors).hasOnlyOneElementSatisfying { error -> error.contains("networkServices") && error.contains("devMode") } assertThat(errors).hasOnlyOneElementSatisfying { error -> error.contains("networkServices") && error.contains("devMode") }
} }
@Test(timeout=300_000) @Test(timeout=3_000)
fun `validation has error when both compatibilityZoneURL and networkServices are configured`() { fun `validation has error when both compatibilityZoneURL and networkServices are configured`() {
val configuration = testConfiguration.copy( val configuration = testConfiguration.copy(
devMode = false, devMode = false,
@ -192,7 +193,7 @@ class NodeConfigurationImplTest {
} }
} }
@Test(timeout=300_000) @Test(timeout=3_000)
fun `rpcAddress and rpcSettings_address are equivalent`() { fun `rpcAddress and rpcSettings_address are equivalent`() {
var rawConfig = ConfigFactory.parseResources("working-config.conf", ConfigParseOptions.defaults().setAllowMissing(false)) var rawConfig = ConfigFactory.parseResources("working-config.conf", ConfigParseOptions.defaults().setAllowMissing(false))
rawConfig = rawConfig.withoutPath("rpcSettings.address") rawConfig = rawConfig.withoutPath("rpcSettings.address")
@ -201,8 +202,8 @@ class NodeConfigurationImplTest {
assertThat(rawConfig.parseAsNodeConfiguration().isValid).isTrue() assertThat(rawConfig.parseAsNodeConfiguration().isValid).isTrue()
} }
@Test(timeout=300_000) @Test(timeout=3_000)
fun `relative path correctly parsed`() { fun `absolute base directory leads to correct cordapp directories`() {
val rawConfig = ConfigFactory.parseResources("working-config.conf", ConfigParseOptions.defaults().setAllowMissing(false)) val rawConfig = ConfigFactory.parseResources("working-config.conf", ConfigParseOptions.defaults().setAllowMissing(false))
// Override base directory to have predictable experience on diff OSes // Override base directory to have predictable experience on diff OSes
@ -213,13 +214,69 @@ class NodeConfigurationImplTest {
.resolve() .resolve()
val nodeConfiguration = finalConfig.parseAsNodeConfiguration() val nodeConfiguration = finalConfig.parseAsNodeConfiguration()
assertThat(nodeConfiguration.isValid).isTrue() assertTrue(nodeConfiguration.isValid)
val baseDirPath = tempFolder.root.toPath() val baseDirPath = tempFolder.root.toPath()
assertEquals(listOf(baseDirPath / "./myCorDapps1", baseDirPath / "./myCorDapps2"), nodeConfiguration.value().cordappDirectories) assertEquals(listOf(baseDirPath / "./myCorDapps1", baseDirPath / "./myCorDapps2"), nodeConfiguration.value().cordappDirectories)
} }
@Test(timeout=300_000) @Test(timeout=3_000)
fun `absolute base directory leads to correct default cordapp directory`() {
val rawConfig = ConfigFactory.parseResources("working-config-no-cordapps.conf", ConfigParseOptions.defaults().setAllowMissing(false))
// Override base directory to have predictable experience on diff OSes
val finalConfig = configOf(
// Add substitution values here
"baseDirectory" to tempFolder.root.canonicalPath)
.withFallback(rawConfig)
.resolve()
val nodeConfiguration = finalConfig.parseAsNodeConfiguration()
assertTrue(nodeConfiguration.isValid)
val baseDirPath = tempFolder.root.toPath()
assertEquals(listOf(baseDirPath / "cordapps"), nodeConfiguration.value().cordappDirectories)
}
@Test(timeout=3_000)
fun `relative base dir leads to correct cordapp directories`() {
val rawConfig = ConfigFactory.parseResources("working-config.conf", ConfigParseOptions.defaults().setAllowMissing(false))
val path = tempFolder.root.relativeTo(tempFolder.root.parentFile).toString()
val fullPath = File(".").resolve(path).toString()
// Override base directory to have predictable experience on diff OSes
val finalConfig = configOf(
// Add substitution values here
"baseDirectory" to fullPath)
.withFallback(rawConfig)
.resolve()
val nodeConfiguration = finalConfig.parseAsNodeConfiguration()
assertTrue(nodeConfiguration.isValid)
assertEquals(listOf(fullPath / "./myCorDapps1", fullPath / "./myCorDapps2"), nodeConfiguration.value().cordappDirectories)
}
@Test(timeout=3_000)
fun `relative base dir leads to correct default cordapp directory`() {
val rawConfig = ConfigFactory.parseResources("working-config-no-cordapps.conf", ConfigParseOptions.defaults().setAllowMissing(false))
val path = tempFolder.root.relativeTo(tempFolder.root.parentFile).toString()
val fullPath = File(".").resolve(path).toString()
// Override base directory to have predictable experience on diff OSes
val finalConfig = configOf(
// Add substitution values here
"baseDirectory" to fullPath)
.withFallback(rawConfig)
.resolve()
val nodeConfiguration = finalConfig.parseAsNodeConfiguration()
assertTrue(nodeConfiguration.isValid)
assertEquals(listOf(fullPath / "cordapps"), nodeConfiguration.value().cordappDirectories)
}
@Test(timeout=3_000)
fun `missing rpcSettings_adminAddress cause a graceful failure`() { fun `missing rpcSettings_adminAddress cause a graceful failure`() {
var rawConfig = ConfigFactory.parseResources("working-config.conf", ConfigParseOptions.defaults().setAllowMissing(false)) var rawConfig = ConfigFactory.parseResources("working-config.conf", ConfigParseOptions.defaults().setAllowMissing(false))
rawConfig = rawConfig.withoutPath("rpcSettings.adminAddress") rawConfig = rawConfig.withoutPath("rpcSettings.adminAddress")
@ -229,7 +286,7 @@ class NodeConfigurationImplTest {
assertThat(config.errors.asSequence().map(Configuration.Validation.Error::message).filter { it.contains("rpcSettings.adminAddress") }.toList()).isNotEmpty assertThat(config.errors.asSequence().map(Configuration.Validation.Error::message).filter { it.contains("rpcSettings.adminAddress") }.toList()).isNotEmpty
} }
@Test(timeout=300_000) @Test(timeout=3_000)
fun `compatibilityZoneURL populates NetworkServices`() { fun `compatibilityZoneURL populates NetworkServices`() {
val compatibilityZoneURL = URI.create("https://r3.com").toURL() val compatibilityZoneURL = URI.create("https://r3.com").toURL()
val configuration = testConfiguration.copy( val configuration = testConfiguration.copy(
@ -241,14 +298,14 @@ class NodeConfigurationImplTest {
assertEquals(compatibilityZoneURL, configuration.networkServices!!.networkMapURL) assertEquals(compatibilityZoneURL, configuration.networkServices!!.networkMapURL)
} }
@Test(timeout=300_000) @Test(timeout=3_000)
fun `jmxReporterType is null and defaults to Jokolia`() { fun `jmxReporterType is null and defaults to Jokolia`() {
val rawConfig = getConfig("working-config.conf", ConfigFactory.parseMap(mapOf("devMode" to true))) val rawConfig = getConfig("working-config.conf", ConfigFactory.parseMap(mapOf("devMode" to true)))
val nodeConfig = rawConfig.parseAsNodeConfiguration().value() val nodeConfig = rawConfig.parseAsNodeConfiguration().value()
assertEquals(JmxReporterType.JOLOKIA.toString(), nodeConfig.jmxReporterType.toString()) assertEquals(JmxReporterType.JOLOKIA.toString(), nodeConfig.jmxReporterType.toString())
} }
@Test(timeout=300_000) @Test(timeout=3_000)
fun `jmxReporterType is not null and is set to New Relic`() { fun `jmxReporterType is not null and is set to New Relic`() {
var rawConfig = getConfig("working-config.conf", ConfigFactory.parseMap(mapOf("devMode" to true))) var rawConfig = getConfig("working-config.conf", ConfigFactory.parseMap(mapOf("devMode" to true)))
rawConfig = rawConfig.withValue("jmxReporterType", ConfigValueFactory.fromAnyRef("NEW_RELIC")) rawConfig = rawConfig.withValue("jmxReporterType", ConfigValueFactory.fromAnyRef("NEW_RELIC"))
@ -256,7 +313,7 @@ class NodeConfigurationImplTest {
assertEquals(JmxReporterType.NEW_RELIC.toString(), nodeConfig.jmxReporterType.toString()) assertEquals(JmxReporterType.NEW_RELIC.toString(), nodeConfig.jmxReporterType.toString())
} }
@Test(timeout=300_000) @Test(timeout=3_000)
fun `jmxReporterType is not null and set to Jokolia`() { fun `jmxReporterType is not null and set to Jokolia`() {
var rawConfig = getConfig("working-config.conf", ConfigFactory.parseMap(mapOf("devMode" to true))) var rawConfig = getConfig("working-config.conf", ConfigFactory.parseMap(mapOf("devMode" to true)))
rawConfig = rawConfig.withValue("jmxReporterType", ConfigValueFactory.fromAnyRef("JOLOKIA")) rawConfig = rawConfig.withValue("jmxReporterType", ConfigValueFactory.fromAnyRef("JOLOKIA"))
@ -264,7 +321,7 @@ class NodeConfigurationImplTest {
assertEquals(JmxReporterType.JOLOKIA.toString(), nodeConfig.jmxReporterType.toString()) assertEquals(JmxReporterType.JOLOKIA.toString(), nodeConfig.jmxReporterType.toString())
} }
@Test(timeout=300_000) @Test(timeout=3_000)
fun `network services`() { fun `network services`() {
val rawConfig = getConfig("test-config-with-networkservices.conf") val rawConfig = getConfig("test-config-with-networkservices.conf")
val nodeConfig = rawConfig.parseAsNodeConfiguration().value() val nodeConfig = rawConfig.parseAsNodeConfiguration().value()
@ -276,7 +333,7 @@ class NodeConfigurationImplTest {
} }
} }
@Test(timeout=300_000) @Test(timeout=3_000)
fun `check crlCheckArtemisServer flag`() { fun `check crlCheckArtemisServer flag`() {
assertFalse(getConfig("working-config.conf").parseAsNodeConfiguration().value().crlCheckArtemisServer) assertFalse(getConfig("working-config.conf").parseAsNodeConfiguration().value().crlCheckArtemisServer)
val rawConfig = getConfig("working-config.conf", ConfigFactory.parseMap(mapOf("crlCheckArtemisServer" to true))) val rawConfig = getConfig("working-config.conf", ConfigFactory.parseMap(mapOf("crlCheckArtemisServer" to true)))

View File

@ -0,0 +1,31 @@
myLegalName = "O=Alice Corp, L=Madrid, C=ES"
emailAddress = "admin@company.com"
keyStorePassword = "cordacadevpass"
trustStorePassword = "trustpass"
crlCheckSoftFail = true
baseDirectory = "/opt/corda"
dataSourceProperties = {
dataSourceClassName = org.h2.jdbcx.JdbcDataSource
dataSource.url = "jdbc:h2:file:blah"
dataSource.user = "sa"
dataSource.password = ""
}
database = {
transactionIsolationLevel = "REPEATABLE_READ"
exportHibernateJMXStatistics = "false"
}
p2pAddress = "localhost:2233"
h2port = 0
useTestClock = false
verifierType = InMemory
rpcSettings = {
address = "locahost:3418"
adminAddress = "localhost:3419"
useSsl = false
standAloneBroker = false
}
flowTimeout {
timeout = 30 seconds
maxRestartCount = 3
backoffBase = 2.0
}

View File

@ -27,6 +27,7 @@ object UpdateBusinessDayFlow {
override fun call() { override fun call() {
val message = otherPartySession.receive<UpdateBusinessDayMessage>().unwrap { it } val message = otherPartySession.receive<UpdateBusinessDayMessage>().unwrap { it }
(serviceHub.clock as DemoClock).updateDate(message.date) (serviceHub.clock as DemoClock).updateDate(message.date)
otherPartySession.send(true) // Let's Broadcast know we've updated the clock
} }
} }
@ -64,7 +65,7 @@ object UpdateBusinessDayFlow {
@Suspendable @Suspendable
private fun doNextRecipient(recipient: Party) { private fun doNextRecipient(recipient: Party) {
initiateFlow(recipient).send(UpdateBusinessDayMessage(date)) initiateFlow(recipient).sendAndReceive<Boolean>(UpdateBusinessDayMessage(date))
} }
} }
} }

View File

@ -61,8 +61,6 @@ include 'tools:network-builder'
include 'tools:cliutils' include 'tools:cliutils'
include 'tools:worldmap' include 'tools:worldmap'
include 'tools:checkpoint-agent' include 'tools:checkpoint-agent'
include 'example-code'
project(':example-code').projectDir = file("$settingsDir/docs/source/example-code")
include 'samples:attachment-demo:contracts' include 'samples:attachment-demo:contracts'
include 'samples:attachment-demo:workflows' include 'samples:attachment-demo:workflows'
include 'samples:trader-demo:workflows-trader' include 'samples:trader-demo:workflows-trader'

View File

@ -1,4 +1,4 @@
FROM corda/corda-zulu-4.1 FROM corda/corda-zulu-java1.8-4.4
# Copy corda files # Copy corda files
ADD --chown=corda:corda node.conf /opt/corda/node.conf ADD --chown=corda:corda node.conf /opt/corda/node.conf
@ -18,4 +18,4 @@ WORKDIR /opt/corda
ENV HOME=/opt/corda ENV HOME=/opt/corda
# Start it # Start it
CMD ["run-corda"] CMD ["run-corda"]