mirror of
https://github.com/corda/corda.git
synced 2024-12-24 07:06:44 +00:00
Merged in andrius-notary-deploy (pull request #533)
This commit is contained in:
commit
f0d4b4a572
15
.idea/runConfigurations/Notary_Demo__Run_Nodes.xml
generated
Normal file
15
.idea/runConfigurations/Notary_Demo__Run_Nodes.xml
generated
Normal file
@ -0,0 +1,15 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Notary Demo: Run Nodes" type="JetRunConfigurationType" factoryName="Kotlin" singleton="true">
|
||||
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
||||
<option name="MAIN_CLASS_NAME" value="net.corda.notarydemo.MainKt" />
|
||||
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
|
||||
<option name="PROGRAM_PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="" />
|
||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
||||
<option name="ALTERNATIVE_JRE_PATH" />
|
||||
<option name="PASS_PARENT_ENVS" value="true" />
|
||||
<module name="raft-notary-demo_main" />
|
||||
<envs />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
15
.idea/runConfigurations/Notary_Demo__Run_Notarisation.xml
generated
Normal file
15
.idea/runConfigurations/Notary_Demo__Run_Notarisation.xml
generated
Normal file
@ -0,0 +1,15 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Notary Demo: Run Notarisation" type="JetRunConfigurationType" factoryName="Kotlin" singleton="true">
|
||||
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
||||
<option name="MAIN_CLASS_NAME" value="net.corda.notarydemo.NotaryDemoKt" />
|
||||
<option name="VM_PARAMETERS" value="-ea -javaagent:lib/quasar.jar " />
|
||||
<option name="PROGRAM_PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="" />
|
||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
||||
<option name="ALTERNATIVE_JRE_PATH" />
|
||||
<option name="PASS_PARENT_ENVS" value="true" />
|
||||
<module name="raft-notary-demo_main" />
|
||||
<envs />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
@ -2,7 +2,7 @@ buildscript {
|
||||
// Our version: bump this on release.
|
||||
ext.corda_version = "0.6-SNAPSHOT"
|
||||
|
||||
ext.gradle_plugins_version = "0.5.5"
|
||||
ext.gradle_plugins_version = "0.5.6"
|
||||
ext.kotlin_version = '1.0.5'
|
||||
ext.quasar_version = '0.7.6'
|
||||
ext.asm_version = '0.5.3'
|
||||
|
@ -36,10 +36,6 @@ open class TransactionBuilder(
|
||||
|
||||
val time: Timestamp? get() = timestamp
|
||||
|
||||
init {
|
||||
notary?.let { signers.add(it.owningKey) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a copy of the builder.
|
||||
*/
|
||||
@ -72,6 +68,7 @@ open class TransactionBuilder(
|
||||
|
||||
fun setTime(newTimestamp: Timestamp) {
|
||||
check(notary != null) { "Only notarised transactions can have a timestamp" }
|
||||
signers.add(notary!!.owningKey)
|
||||
check(currentSigs.isEmpty()) { "Cannot change timestamp after signing" }
|
||||
this.timestamp = newTimestamp
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ so far. We have:
|
||||
4. The attachment demo, which demonstrates uploading attachments to nodes.
|
||||
5. The SIMM valuation demo, a large demo which shows two nodes agreeing on a portfolio and valuing the initial margin
|
||||
using the Standard Initial Margin Model.
|
||||
6. The distributed notary demo, which demonstrates a single node getting multiple transactions notarised by a distributed (Raft-based) notary.
|
||||
|
||||
.. note:: If any demos don't work please jump on our mailing list and let us know.
|
||||
|
||||
@ -116,3 +117,46 @@ To run the demo run:
|
||||
Now open http://localhost:10005/web/simmvaluationdemo and http://localhost:10007/web/simmvaluationdemo to view the two nodes that this
|
||||
will have started respectively. You can now use the demo by creating trades and agreeing the valuations.
|
||||
|
||||
Distributed Notary demo
|
||||
-----------------------
|
||||
|
||||
This is a simple demonstration showing a party getting transactions notarised by a distributed `Raft <https://raft.github.io/>`_-based notary service.
|
||||
The demo will start three distributed notary nodes, and two counterparty nodes. One of the parties will generate transactions
|
||||
that move a self-issued asset to the other party, and submit them for notarisation.
|
||||
|
||||
The output will display a list of notarised transaction ids and corresponding signer public keys. In the Raft distributed notary
|
||||
every node in the cluster services client requests, and one signature is sufficient to satisfy the notary composite key requirement.
|
||||
You will notice that subsequent transactions get signed by different members of the cluster (usually allocated in a random order).
|
||||
|
||||
To run from IntelliJ:
|
||||
|
||||
1. Open the Corda samples project in IntelliJ and run the ``Notary Demo: Run Nodes`` configuration to start the nodes.
|
||||
Once all nodes are started you will see several "Node started up and registered in ..." messages.
|
||||
2. Run ``Notary Demo: Run Notarisation`` to make a call to the "Party" node to initiate notarisation requests.
|
||||
In a few seconds you will see a message "Notarised 10 transactions" with a list of transaction ids and the signer public keys.
|
||||
|
||||
To run from the command line:
|
||||
|
||||
1. Run ``./gradlew samples:raft-notary-demo:deployNodes``, which will create node directories with configs under ``samples/raft-notary-demo/build/nodes``.
|
||||
2. Run ``./samples/raft-notary-demo/build/nodes/runnodes``, which will start the nodes in separate terminal windows/tabs.
|
||||
Wait until a "Node started up and registered in ..." appears on each of the terminals.
|
||||
3. Run ``./gradlew samples:raft-notary-demo:notarise`` to make a call to the "Party" node to initiate notarisation requests.
|
||||
In a few seconds you will see a message "Notarised 10 transactions" with a list of transaction ids and the signer public keys.
|
||||
|
||||
Notary nodes store consumed states in a replicated commit log, which is backed by a H2 database on each node.
|
||||
To ascertain that the commit log is synchronised across the cluster you access and compare each of the nodes' backing stores
|
||||
by using the H2 web console:
|
||||
|
||||
- Firstly, download `H2 web console <http://www.h2database.com/html/download.html>`_ (download the "platform-independent zip"),
|
||||
and start it using a script in the extracted folder: ``h2/bin/h2.sh`` (or ``h2.bat`` for Windows)
|
||||
|
||||
- The H2 web console should start up in a web browser tab. To connect we first need to obtain a JDBC connection string.
|
||||
Each node outputs its connection string in the terminal window as it starts up. In a terminal window where a node is running,
|
||||
look for the following string:
|
||||
|
||||
``Database connection url is : jdbc:h2:tcp://10.18.0.150:56736/node``
|
||||
|
||||
You can use the string on the right to connect to the h2 database: just paste it in to the `JDBC URL` field and click *Connect*.
|
||||
You will be presented with a web application that enumerates all the available tables and provides an interface for you to query them using SQL.
|
||||
- The committed states are stored in the ``NOTARY_COMMITTED_STATES`` table. Note that the raw data is not human-readable,
|
||||
but we're only interested in the row count for this demo.
|
@ -2,7 +2,7 @@
|
||||
// or if you are developing these plugins. See the readme for more information.
|
||||
|
||||
buildscript {
|
||||
ext.gradle_plugins_version = "0.5.5" // Our version: bump this on release.
|
||||
ext.gradle_plugins_version = "0.5.6" // Our version: bump this on release.
|
||||
ext.corda_published_version = "0.5" // Depend on our existing published publishing plugin.
|
||||
|
||||
repositories {
|
||||
|
@ -20,6 +20,11 @@ class Node {
|
||||
* A list of advertised services ID strings.
|
||||
*/
|
||||
protected List<String> advertisedServices = []
|
||||
|
||||
/**
|
||||
* If running a distributed notary, a list of node addresses for joining the Raft cluster
|
||||
*/
|
||||
protected List<String> notaryClusterAddresses = []
|
||||
/**
|
||||
* Set the list of CorDapps to install to the plugins directory. Each cordapp is a fully qualified Maven
|
||||
* dependency name, eg: com.example:product-name:0.1
|
||||
@ -104,6 +109,14 @@ class Node {
|
||||
ConfigValueFactory.fromAnyRef("$DEFAULT_HOST:$webPort".toString()))
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the port which to bind the Copycat (Raft) node to
|
||||
*/
|
||||
void notaryNodePort(Integer notaryPort) {
|
||||
config = config.withValue("notaryNodeAddress",
|
||||
ConfigValueFactory.fromAnyRef("$DEFAULT_HOST:$notaryPort".toString()))
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the network map address for this node.
|
||||
*
|
||||
@ -205,8 +218,10 @@ class Node {
|
||||
*/
|
||||
private void installConfig() {
|
||||
// Adding required default values
|
||||
config = config.withValue('extraAdvertisedServiceIds',
|
||||
ConfigValueFactory.fromAnyRef(advertisedServices.join(',')))
|
||||
config = config.withValue('extraAdvertisedServiceIds', ConfigValueFactory.fromAnyRef(advertisedServices.join(',')))
|
||||
if (notaryClusterAddresses.size() > 0) {
|
||||
config = config.withValue('notaryClusterAddresses', ConfigValueFactory.fromIterable(notaryClusterAddresses))
|
||||
}
|
||||
def configFileText = config.root().render(new ConfigRenderOptions(false, false, true, false)).split("\n").toList()
|
||||
|
||||
// Need to write a temporary file first to use the project.copy, which resolves directories correctly.
|
||||
|
@ -11,6 +11,7 @@ import net.corda.core.div
|
||||
import net.corda.core.future
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.core.node.services.ServiceType
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.node.services.User
|
||||
import net.corda.node.services.config.ConfigHelper
|
||||
@ -18,7 +19,9 @@ import net.corda.node.services.config.FullNodeConfiguration
|
||||
import net.corda.node.services.messaging.ArtemisMessagingServer
|
||||
import net.corda.node.services.messaging.NodeMessagingClient
|
||||
import net.corda.node.services.network.NetworkMapService
|
||||
import net.corda.node.services.transactions.RaftValidatingNotaryService
|
||||
import net.corda.node.utilities.JsonSupport
|
||||
import net.corda.node.utilities.ServiceIdentityGenerator
|
||||
import org.slf4j.Logger
|
||||
import java.io.File
|
||||
import java.net.*
|
||||
@ -62,7 +65,17 @@ interface DriverDSLExposedInterface {
|
||||
*/
|
||||
fun startNode(providedName: String? = null,
|
||||
advertisedServices: Set<ServiceInfo> = emptySet(),
|
||||
rpcUsers: List<User> = emptyList()): Future<NodeInfoAndConfig>
|
||||
rpcUsers: List<User> = emptyList(),
|
||||
customOverrides: Map<String, Any?> = emptyMap()): Future<NodeInfoAndConfig>
|
||||
|
||||
/**
|
||||
* Starts a distributed notary cluster.
|
||||
*
|
||||
* @param notaryName The legal name of the advertised distributed notary service.
|
||||
* @param clusterSize Number of nodes to create for the cluster.
|
||||
* @param type The advertised notary service type. Currently the only supported type is [RaftValidatingNotaryService.type].
|
||||
*/
|
||||
fun startNotaryCluster(notaryName: String, clusterSize: Int = 3, type: ServiceType = RaftValidatingNotaryService.type)
|
||||
|
||||
fun waitForAllNodesToFinish()
|
||||
}
|
||||
@ -292,31 +305,35 @@ open class DriverDSL(
|
||||
}
|
||||
}
|
||||
|
||||
override fun startNode(providedName: String?, advertisedServices: Set<ServiceInfo>, rpcUsers: List<User>): Future<NodeInfoAndConfig> {
|
||||
override fun startNode(providedName: String?, advertisedServices: Set<ServiceInfo>,
|
||||
rpcUsers: List<User>, customOverrides: Map<String, Any?>): Future<NodeInfoAndConfig> {
|
||||
val messagingAddress = portAllocation.nextHostAndPort()
|
||||
val apiAddress = portAllocation.nextHostAndPort()
|
||||
val debugPort = if (isDebug) debugPortAllocation.nextPort() else null
|
||||
val name = providedName ?: "${pickA(name)}-${messagingAddress.port}"
|
||||
|
||||
val baseDirectory = driverDirectory / name
|
||||
val configOverrides = mapOf(
|
||||
"myLegalName" to name,
|
||||
"basedir" to baseDirectory.normalize().toString(),
|
||||
"artemisAddress" to messagingAddress.toString(),
|
||||
"webAddress" to apiAddress.toString(),
|
||||
"extraAdvertisedServiceIds" to advertisedServices.joinToString(","),
|
||||
"networkMapAddress" to networkMapAddress.toString(),
|
||||
"useTestClock" to useTestClock,
|
||||
"rpcUsers" to rpcUsers.map {
|
||||
mapOf(
|
||||
"user" to it.username,
|
||||
"password" to it.password,
|
||||
"permissions" to it.permissions
|
||||
)
|
||||
}
|
||||
) + customOverrides
|
||||
|
||||
val config = ConfigHelper.loadConfig(
|
||||
baseDirectoryPath = baseDirectory,
|
||||
allowMissingConfig = true,
|
||||
configOverrides = mapOf(
|
||||
"myLegalName" to name,
|
||||
"basedir" to baseDirectory.normalize().toString(),
|
||||
"artemisAddress" to messagingAddress.toString(),
|
||||
"webAddress" to apiAddress.toString(),
|
||||
"extraAdvertisedServiceIds" to advertisedServices.joinToString(","),
|
||||
"networkMapAddress" to networkMapAddress.toString(),
|
||||
"useTestClock" to useTestClock,
|
||||
"rpcUsers" to rpcUsers.map { mapOf(
|
||||
"user" to it.username,
|
||||
"password" to it.password,
|
||||
"permissions" to it.permissions)
|
||||
}
|
||||
)
|
||||
configOverrides = configOverrides
|
||||
)
|
||||
|
||||
return future {
|
||||
@ -325,6 +342,25 @@ open class DriverDSL(
|
||||
}
|
||||
}
|
||||
|
||||
override fun startNotaryCluster(notaryName: String, clusterSize: Int, type: ServiceType) {
|
||||
val nodeNames = (1..clusterSize).map { "Notary Node $it" }
|
||||
val paths = nodeNames.map { driverDirectory / it }
|
||||
ServiceIdentityGenerator.generateToDisk(paths, type.id, notaryName)
|
||||
|
||||
val serviceInfo = ServiceInfo(type, notaryName)
|
||||
val advertisedService = setOf(serviceInfo)
|
||||
val notaryClusterAddress = portAllocation.nextHostAndPort()
|
||||
|
||||
// Start the first node that will bootstrap the cluster
|
||||
startNode(nodeNames.first(), advertisedService, emptyList(), mapOf("notaryNodeAddress" to notaryClusterAddress.toString()))
|
||||
// All other nodes will join the cluster
|
||||
nodeNames.drop(1).forEach {
|
||||
val nodeAddress = portAllocation.nextHostAndPort()
|
||||
val configOverride = mapOf("notaryNodeAddress" to nodeAddress.toString(), "notaryClusterAddresses" to listOf(notaryClusterAddress.toString()))
|
||||
startNode(it, advertisedService, emptyList(), configOverride)
|
||||
}
|
||||
}
|
||||
|
||||
override fun start() {
|
||||
startNetworkMapService()
|
||||
}
|
||||
|
@ -21,10 +21,9 @@ import java.util.*
|
||||
* to disk, and sharing them across the cluster. A new node joining the cluster will have to obtain and install a snapshot
|
||||
* containing the entire JDBC table contents.
|
||||
*/
|
||||
class DistributedImmutableMap<K : Any, V : Any>(val db: Database, tableName: String = DEFAULT_TABLE_NAME) : StateMachine(), Snapshottable {
|
||||
class DistributedImmutableMap<K : Any, V : Any>(val db: Database, tableName: String) : StateMachine(), Snapshottable {
|
||||
companion object {
|
||||
private val log = loggerFor<DistributedImmutableMap<*, *>>()
|
||||
private val DEFAULT_TABLE_NAME = "committed_states"
|
||||
}
|
||||
|
||||
object Commands {
|
||||
|
@ -44,6 +44,7 @@ class RaftUniquenessProvider(storagePath: Path, myAddress: HostAndPort, clusterA
|
||||
db: Database, config: NodeSSLConfiguration) : UniquenessProvider, SingletonSerializeAsToken() {
|
||||
companion object {
|
||||
private val log = loggerFor<RaftUniquenessProvider>()
|
||||
private val DB_TABLE_NAME = "notary_committed_states"
|
||||
}
|
||||
|
||||
private val _clientFuture: CompletableFuture<CopycatClient>
|
||||
@ -56,7 +57,7 @@ class RaftUniquenessProvider(storagePath: Path, myAddress: HostAndPort, clusterA
|
||||
|
||||
init {
|
||||
log.info("Creating Copycat server, log stored in: ${storagePath.toFile()}")
|
||||
val stateMachineFactory = { DistributedImmutableMap<String, ByteArray>(db) }
|
||||
val stateMachineFactory = { DistributedImmutableMap<String, ByteArray>(db, DB_TABLE_NAME) }
|
||||
val address = Address(myAddress.hostText, myAddress.port)
|
||||
val storage = buildStorage(storagePath)
|
||||
val transport = buildTransport(config)
|
||||
|
@ -0,0 +1,50 @@
|
||||
package net.corda.node.utilities
|
||||
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.Party
|
||||
import net.corda.core.crypto.composite
|
||||
import net.corda.core.crypto.generateKeyPair
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
|
||||
object ServiceIdentityGenerator {
|
||||
private val log = loggerFor<ServiceIdentityGenerator>()
|
||||
|
||||
/**
|
||||
* Generates signing key pairs and a common distributed service identity for a set of nodes.
|
||||
* The key pairs and the group identity get serialized to disk in the corresponding node directories.
|
||||
* This method should be called *before* any of the nodes are started.
|
||||
*
|
||||
* @param dirs List of node directories to place the generated identity and key pairs in.
|
||||
* @param serviceId The service id of the distributed service.
|
||||
* @param serviceName The legal name of the distributed service.
|
||||
* @param threshold The threshold for the generated group [CompositeKey.Node].
|
||||
*/
|
||||
fun generateToDisk(dirs: List<Path>, serviceId: String, serviceName: String, threshold: Int = 1) {
|
||||
log.trace("Generating a group identity \"serviceName\" for nodes: ${dirs.joinToString()}")
|
||||
|
||||
val keyPairs = (1..dirs.size).map { generateKeyPair() }
|
||||
val notaryKey = CompositeKey.Builder().addKeys(keyPairs.map { it.public.composite }).build(threshold)
|
||||
val notaryParty = Party(serviceName, notaryKey).serialize()
|
||||
|
||||
keyPairs.zip(dirs) { keyPair, dir ->
|
||||
Files.createDirectories(dir)
|
||||
val privateKeyFile = serviceId + "-private-key"
|
||||
val publicKeyFile = serviceId + "-public"
|
||||
notaryParty.writeToFile(dir.resolve(publicKeyFile))
|
||||
keyPair.serialize().writeToFile(dir.resolve(privateKeyFile))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val dirs = args[0].split(",").map { Paths.get(it) }
|
||||
val serviceId = args[1]
|
||||
val serviceName = args[2]
|
||||
|
||||
println("Generating service identity for \"$serviceName\"")
|
||||
ServiceIdentityGenerator.generateToDisk(dirs, serviceId, serviceName)
|
||||
}
|
@ -6,4 +6,5 @@ Please refer to `README.md` in the individual project folders. There are the fo
|
||||
* **irs-demo** A demo showing two nodes agreeing to an interest rate swap and doing fixings using an oracle.
|
||||
* **trader-demo** A simple driver for exercising the two party trading flow. In this scenario, a buyer wants to purchase some commercial paper by swapping his cash for commercial paper. The seller learns that the buyer exists, and sends them a message to kick off the trade. The seller, having obtained his CP, then quits and the buyer goes back to waiting. The buyer will sell as much CP as he can! **We recommend starting with this demo.**
|
||||
* **Network-visualiser** A tool that uses a simulation to visualise the interaction and messages between nodes on the Corda network. Currently only works for the IRS demo.
|
||||
* **simm-valudation-demo** A demo showing two nodes reaching agreement on the valuation of a derivatives portfolio.
|
||||
* **simm-valudation-demo** A demo showing two nodes reaching agreement on the valuation of a derivatives portfolio.
|
||||
* **raft-notary-demo** A simple demonstration of a node getting multiple transactions notarised by a distributed (Raft-based) notary.
|
5
samples/raft-notary-demo/README.md
Normal file
5
samples/raft-notary-demo/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# Distributed Notary (Raft) Demo
|
||||
|
||||
This program is a simple demonstration of a node getting multiple transactions notarised by a distributed (Raft-based) notary.
|
||||
|
||||
Please see docs/build/html/running-the-demos.html to learn how to use this demo.
|
143
samples/raft-notary-demo/build.gradle
Normal file
143
samples/raft-notary-demo/build.gradle
Normal file
@ -0,0 +1,143 @@
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'idea'
|
||||
apply plugin: 'net.corda.plugins.quasar-utils'
|
||||
apply plugin: 'net.corda.plugins.publish-utils'
|
||||
apply plugin: 'net.corda.plugins.cordformation'
|
||||
apply plugin: 'maven-publish'
|
||||
|
||||
ext {
|
||||
deployTo = "./build/nodes"
|
||||
notaryType = "corda.notary.validating.raft"
|
||||
notaryName = "Raft"
|
||||
advertisedNotary = "$notaryType|$notaryName"
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
maven {
|
||||
url 'https://dl.bintray.com/kotlin/exposed'
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
resources {
|
||||
srcDir "../../config/dev"
|
||||
}
|
||||
}
|
||||
test {
|
||||
resources {
|
||||
srcDir "../../config/test"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configurations {
|
||||
integrationTestCompile.extendsFrom testCompile
|
||||
integrationTestRuntime.extendsFrom testRuntime
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
testCompile group: 'junit', name: 'junit', version: '4.11'
|
||||
|
||||
// Corda integration dependencies
|
||||
compile "net.corda:corda:$corda_version" // TODO
|
||||
compile project(':core')
|
||||
compile project(':client')
|
||||
compile project(':node')
|
||||
compile project(':test-utils')
|
||||
|
||||
// Javax is required for webapis
|
||||
compile "org.glassfish.jersey.core:jersey-server:${jersey_version}"
|
||||
}
|
||||
|
||||
idea {
|
||||
module {
|
||||
downloadJavadoc = true // defaults to false
|
||||
downloadSources = true
|
||||
}
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
jarAndSources(MavenPublication) {
|
||||
from components.java
|
||||
artifactId 'raftnotarydemo'
|
||||
|
||||
artifact sourceJar
|
||||
artifact javadocJar
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task generateNotaryIdentity(type: JavaExec) {
|
||||
classpath = sourceSets.main.runtimeClasspath
|
||||
main = "net.corda.node.utilities.ServiceIdentityGeneratorKt"
|
||||
def nodeDirs = ["$deployTo/notary1", "$deployTo/notary2", "$deployTo/notary3"].join(",")
|
||||
args = [nodeDirs, notaryType, notaryName]
|
||||
}
|
||||
|
||||
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: [':install', 'build', 'generateNotaryIdentity']) {
|
||||
directory deployTo
|
||||
networkMap "Notary 1"
|
||||
node {
|
||||
name "Notary 1"
|
||||
dirName "notary1"
|
||||
nearestCity "London"
|
||||
advertisedServices = [advertisedNotary]
|
||||
artemisPort 10002
|
||||
webPort 10009
|
||||
cordapps = []
|
||||
notaryNodePort 11002
|
||||
}
|
||||
node {
|
||||
name "Notary 2"
|
||||
dirName "notary2"
|
||||
nearestCity "London"
|
||||
advertisedServices = [advertisedNotary]
|
||||
artemisPort 10004
|
||||
webPort 10005
|
||||
cordapps = []
|
||||
notaryNodePort 11004
|
||||
notaryClusterAddresses = ["localhost:11002"]
|
||||
}
|
||||
node {
|
||||
name "Notary 3"
|
||||
dirName "notary3"
|
||||
nearestCity "London"
|
||||
advertisedServices = [advertisedNotary]
|
||||
artemisPort 10006
|
||||
webPort 10007
|
||||
cordapps = []
|
||||
notaryNodePort 11006
|
||||
notaryClusterAddresses = ["localhost:11002"]
|
||||
}
|
||||
node {
|
||||
name "Party"
|
||||
dirName "party"
|
||||
nearestCity "London"
|
||||
advertisedServices = []
|
||||
artemisPort 10008
|
||||
webPort 10003
|
||||
cordapps = []
|
||||
}
|
||||
node {
|
||||
name "Counterparty"
|
||||
dirName "counterparty"
|
||||
nearestCity "New York"
|
||||
advertisedServices = []
|
||||
artemisPort 10010
|
||||
webPort 10011
|
||||
cordapps = []
|
||||
}
|
||||
}
|
||||
|
||||
task notarise(type: JavaExec) {
|
||||
classpath = sourceSets.main.runtimeClasspath
|
||||
main = 'net.corda.notarydemo.NotaryDemoKt'
|
||||
}
|
||||
|
3
samples/raft-notary-demo/gradle.properties
Normal file
3
samples/raft-notary-demo/gradle.properties
Normal file
@ -0,0 +1,3 @@
|
||||
name=Notary Demo
|
||||
group=net.corda
|
||||
kotlin.incremental=false
|
@ -0,0 +1,14 @@
|
||||
package net.corda.notarydemo
|
||||
|
||||
import net.corda.node.driver.driver
|
||||
import net.corda.node.services.transactions.RaftValidatingNotaryService
|
||||
|
||||
/** Creates and starts all nodes required for the demo. */
|
||||
fun main(args: Array<String>) {
|
||||
driver(dsl = {
|
||||
startNode("Party")
|
||||
startNode("Counterparty")
|
||||
startNotaryCluster("Raft notary", clusterSize = 3, type = RaftValidatingNotaryService.type)
|
||||
waitForAllNodesToFinish()
|
||||
}, isDebug = true)
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package net.corda.notarydemo
|
||||
|
||||
import com.google.common.net.HostAndPort
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val api = NotaryDemoClientApi(HostAndPort.fromString("localhost:10003"))
|
||||
api.startNotarisation()
|
||||
}
|
||||
|
||||
/** Interface for using the notary demo API from a client. */
|
||||
private class NotaryDemoClientApi(val hostAndPort: HostAndPort) {
|
||||
companion object {
|
||||
private val API_ROOT = "api/notarydemo"
|
||||
private val TRANSACTION_COUNT = 10
|
||||
}
|
||||
|
||||
/** Makes a call to the demo api to start transaction notarisation. */
|
||||
fun startNotarisation() {
|
||||
val request = buildRequest()
|
||||
val response = buildClient().newCall(request).execute()
|
||||
println(response.body().string())
|
||||
require(response.isSuccessful)
|
||||
}
|
||||
|
||||
private fun buildRequest() = Request.Builder().url("http://$hostAndPort/$API_ROOT/notarise/$TRANSACTION_COUNT").build()
|
||||
private fun buildClient() = OkHttpClient.Builder().connectTimeout(5, TimeUnit.SECONDS).readTimeout(60, TimeUnit.SECONDS).build()
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
package net.corda.notarydemo.api
|
||||
|
||||
import net.corda.core.contracts.DummyContract
|
||||
import net.corda.core.crypto.DigitalSignature
|
||||
import net.corda.core.crypto.toStringShort
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.node.recordTransactions
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.flows.NotaryFlow
|
||||
import java.util.*
|
||||
import javax.ws.rs.GET
|
||||
import javax.ws.rs.Path
|
||||
import javax.ws.rs.PathParam
|
||||
import javax.ws.rs.core.Response
|
||||
|
||||
@Path("notarydemo")
|
||||
class NotaryDemoApi(val services: ServiceHub) {
|
||||
private val notary by lazy {
|
||||
services.networkMapCache.getAnyNotary() ?: throw IllegalStateException("No notary found on the network")
|
||||
}
|
||||
|
||||
private val counterpartyNode by lazy {
|
||||
services.networkMapCache.getNodeByLegalName("Counterparty") ?: throw IllegalStateException("Counterparty not found")
|
||||
}
|
||||
|
||||
private val random = Random()
|
||||
|
||||
@GET
|
||||
@Path("/notarise/{count}")
|
||||
fun notarise(@PathParam("count") count: Int): Response {
|
||||
val transactions = buildTransactions(count)
|
||||
val signers = notariseTransactions(transactions)
|
||||
|
||||
val response = buildResponse(transactions, signers)
|
||||
return Response.ok(response).build()
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a number of dummy transactions (as specified by [count]). The party first self-issues a state (asset),
|
||||
* and builds a transaction to transfer the asset to the counterparty. The *move* transaction requires notarisation,
|
||||
* as it consumes the original asset and creates a copy with the new owner as its output.
|
||||
*/
|
||||
private fun buildTransactions(count: Int): List<SignedTransaction> {
|
||||
val myIdentity = services.myInfo.legalIdentity
|
||||
val myKeyPair = services.legalIdentityKey
|
||||
val moveTransactions = (1..count).map {
|
||||
// Self issue an asset
|
||||
val issueTx = DummyContract.generateInitial(myIdentity.ref(0), random.nextInt(), notary).apply {
|
||||
signWith(myKeyPair)
|
||||
}
|
||||
services.recordTransactions(issueTx.toSignedTransaction())
|
||||
// Move ownership of the asset to the counterparty
|
||||
val counterPartyKey = counterpartyNode.legalIdentity.owningKey
|
||||
val asset = issueTx.toWireTransaction().outRef<DummyContract.SingleOwnerState>(0)
|
||||
val moveTx = DummyContract.move(asset, counterPartyKey).apply {
|
||||
signWith(myKeyPair)
|
||||
}
|
||||
// We don't check signatures because we know that the notary's signature is missing
|
||||
moveTx.toSignedTransaction(checkSufficientSignatures = false)
|
||||
}
|
||||
|
||||
return moveTransactions
|
||||
}
|
||||
|
||||
/**
|
||||
* For every transactions invokes the notary flow and obtains a notary signature.
|
||||
* The signer can be any of the nodes in the notary cluster.
|
||||
*
|
||||
* @return a list of encoded signer public keys – one for every transaction
|
||||
*/
|
||||
private fun notariseTransactions(transactions: List<SignedTransaction>): List<String> {
|
||||
val signatureFutures = transactions.map {
|
||||
val protocol = NotaryFlow.Client::class.java
|
||||
services.invokeFlowAsync<DigitalSignature.WithKey>(protocol, it).resultFuture
|
||||
}
|
||||
val signers = signatureFutures.map { it.get().by.toStringShort() }
|
||||
return signers
|
||||
}
|
||||
|
||||
/** Builds a response for the caller containing the list of transaction ids and corresponding signer keys. */
|
||||
private fun buildResponse(transactions: List<SignedTransaction>, signers: List<String>): String {
|
||||
val transactionSigners = transactions.zip(signers).map {
|
||||
val (tx, signer) = it
|
||||
"Tx [${tx.tx.id.prefixChars()}..] signed by $signer"
|
||||
}.joinToString("\n")
|
||||
|
||||
val response = "Notary: \"${notary.name}\", with composite key: ${notary.owningKey}\n" +
|
||||
"Notarised ${transactions.size} transactions:\n" + transactionSigners
|
||||
return response
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package net.corda.notarydemo.plugin
|
||||
|
||||
import net.corda.core.node.CordaPluginRegistry
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.flows.NotaryFlow
|
||||
import net.corda.notarydemo.api.NotaryDemoApi
|
||||
|
||||
class NotaryDemoPlugin : CordaPluginRegistry() {
|
||||
// A list of classes that expose web APIs.
|
||||
override val webApis: List<Class<*>> = listOf(NotaryDemoApi::class.java)
|
||||
// A list of protocols that are required for this cordapp
|
||||
override val requiredFlows: Map<String, Set<String>> = mapOf(
|
||||
NotaryFlow.Client::class.java.name to setOf(SignedTransaction::class.java.name, setOf(Unit).javaClass.name)
|
||||
)
|
||||
override val servicePlugins: List<Class<*>> = listOf()
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
# Register a ServiceLoader service extending from net.corda.node.CordaPluginRegistry
|
||||
net.corda.notarydemo.plugin.NotaryDemoPlugin
|
@ -15,4 +15,5 @@ include 'samples:attachment-demo'
|
||||
include 'samples:trader-demo'
|
||||
include 'samples:irs-demo'
|
||||
include 'samples:network-visualiser'
|
||||
include 'samples:simm-valuation-demo'
|
||||
include 'samples:simm-valuation-demo'
|
||||
include 'samples:raft-notary-demo'
|
Loading…
Reference in New Issue
Block a user