Reimport samples to main repo

This commit is contained in:
Mike Hearn
2016-11-17 12:03:40 +01:00
parent 02a90014e7
commit 90b083926f
4211 changed files with 818364 additions and 2 deletions

View File

@ -0,0 +1,64 @@
# Trader Demo
Please see docs/build/html/running-the-demos.html
This program is a simple driver for exercising the two party trading protocol. Until Corda has a unified node server
programs like this are required to wire up the pieces and run a demo scenario end to end.
If you are creating a new scenario, you can use this program as a template for creating your own driver. Make sure to
copy/paste the right parts of the build.gradle file to make sure it gets a script to run it deposited in
build/install/r3prototyping/bin
In this scenario, a buyer wants to purchase some commercial paper by swapping his cash for the CP. 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!
The different roles in the scenario this program can adopt are:
This template contains the build system and an example application required to get started with [Corda](http://todo.todo).
## Prerequisites
You will need to have [JDK 8](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html)
installed and available on your path.
## Getting Started
First clone this repository and the Corda repository locally. Then open a terminal window in the Corda directory and run:
Unix:
./gradlew publishToMavenLocal
Windows:
gradle.bat publishToMavenLocal
This will publish a copy of Corda to your local Maven repository for your Cordapp to use. Next open a terminal window
in your Cordapp directory (this one) and run:
Unix:
./gradlew deployNodes
Windows:
gradlew.bat deployNodes
This command will create several nodes in `build/nodes` that you can now run with:
Unix:
cd build/nodes
./runnodes
Windows:
Windows users currently have to manually enter each directory in `build/nodes` and run `java -jar corda.jar` in each.
This will be updated soon.
This will now have nodes running on your machine running this Cordapp. You can now begin developing your Cordapp.
## Further Reading
Tutorials and developer docs for Cordapps and Corda are [here](https://docs.corda.r3cev.com).

View File

@ -0,0 +1,139 @@
buildscript {
repositories {
mavenLocal()
mavenCentral()
jcenter()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "net.corda.plugins:quasar-utils:$corda_version"
classpath "net.corda.plugins:publish-utils:$corda_version"
classpath "net.corda.plugins:cordformation:$corda_version"
}
}
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'
repositories {
mavenLocal()
mavenCentral()
jcenter()
maven {
url 'https://dl.bintray.com/kotlin/exposed'
}
}
sourceSets {
main {
resources {
srcDir "config/dev"
}
}
test {
resources {
srcDir "config/test"
}
}
integrationTest {
kotlin {
compileClasspath += main.output + test.output
runtimeClasspath += main.output + test.output
srcDir file('src/integration-test/kotlin')
}
}
}
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:client:$corda_version"
compile "net.corda:core:$corda_version"
compile "net.corda:finance:$corda_version"
compile "net.corda:node:$corda_version"
compile "net.corda:corda:$corda_version"
compile "net.corda:test-utils:$corda_version"
// Javax is required for webapis
compile "org.glassfish.jersey.core:jersey-server:${jersey_version}"
// GraphStream: For visualisation (required by ExampleClientRPC app)
compile "org.graphstream:gs-core:1.3"
compile("org.graphstream:gs-ui:1.3") {
exclude group: "bouncycastle"
}
// Cordapp dependencies
// Specify your cordapp's dependencies below, including dependent cordapps
}
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['build']) {
directory "./build/nodes"
networkMap "Controller"
node {
name "Controller"
dirName "controller"
nearestCity "London"
advertisedServices = ["corda.notary.validating"]
artemisPort 10002
webPort 10003
cordapps = []
}
node {
name "NodeA"
dirName "nodea"
nearestCity "London"
advertisedServices = []
artemisPort 10004
webPort 10005
cordapps = []
}
node {
name "NodeB"
dirName "nodeb"
nearestCity "New York"
advertisedServices = []
artemisPort 10006
webPort 10007
cordapps = []
}
}
task integrationTest(type: Test, dependsOn: []) {
testClassesDir = sourceSets.integrationTest.output.classesDir
classpath = sourceSets.integrationTest.runtimeClasspath
}
idea {
module {
downloadJavadoc = true // defaults to false
downloadSources = true
}
}
quasarScan.dependsOn('classes')
publishing {
publications {
jarAndSources(MavenPublication) {
from components.java
artifactId 'traderdemo'
artifact sourceJar
artifact javadocJar
}
}
}

View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="info">
<Properties>
<Property name="log-path">logs</Property>
<Property name="log-name">node-${hostName}</Property>
<Property name="archive">${log-path}/archive</Property>
</Properties>
<ThresholdFilter level="trace"/>
<Appenders>
<Console name="Console-Appender" target="SYSTEM_OUT">
<PatternLayout>
<pattern>
%highlight{%level{length=1} %d{HH:mm:ss} %T %c{1}.%M - %msg%n}{INFO=white,WARN=red,FATAL=bright red blink}
</pattern>>
</PatternLayout>
</Console>
<!-- Will generate up to 10 log files for a given day. During every rollover it will delete
those that are older than 60 days, but keep the most recent 10 GB -->
<RollingFile name="RollingFile-Appender"
fileName="${log-path}/${log-name}.log"
filePattern="${archive}/${log-name}.%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="[%-5level] %d{ISO8601}{GMT+0} [%t] %c{1} - %msg%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="10MB"/>
</Policies>
<DefaultRolloverStrategy min="1" max="10">
<Delete basePath="${archive}" maxDepth="1">
<IfFileName glob="${log-name}*.log.gz"/>
<IfLastModified age="60d">
<IfAny>
<IfAccumulatedFileSize exceeds="10 GB"/>
</IfAny>
</IfLastModified>
</Delete>
</DefaultRolloverStrategy>
</RollingFile>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console-Appender"/>
<AppenderRef ref="RollingFile-Appender"/>
</Root>
<Logger name="com.r3corda" level="info" additivity="false">
<AppenderRef ref="Console-Appender"/>
<AppenderRef ref="RollingFile-Appender"/>
</Logger>
</Loggers>
</Configuration>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="info">
<Appenders>
<Console name="Console-Appender" target="SYSTEM_OUT">
<PatternLayout>
<pattern>
[%-5level] %d{HH:mm:ss.SSS} [%t] %c{1}.%M - %msg%n
</pattern>>
</PatternLayout>
</Console>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console-Appender"/>
</Root>
<Logger name="com.r3corda" level="info" additivity="false">
<AppenderRef ref="Console-Appender"/>
</Logger>
</Loggers>
</Configuration>

View File

@ -0,0 +1,2 @@
name = Trader Demo
kotlin.incremental=false

View File

@ -0,0 +1,21 @@
package net.corda.traderdemo
import net.corda.core.node.services.ServiceInfo
import net.corda.node.driver.driver
import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.testing.getHostAndPort
import org.junit.Test
class TraderDemoTest {
@Test fun `runs trader demo`() {
driver(dsl = {
startNode("Notary", setOf(ServiceInfo(SimpleNotaryService.type)))
val nodeA = startNode("Bank A").get()
val nodeAApiAddr = nodeA.config.getHostAndPort("webAddress")
val nodeBApiAddr = startNode("Bank B").get().config.getHostAndPort("webAddress")
assert(TraderDemoClientApi(nodeAApiAddr).runBuyer())
assert(TraderDemoClientApi(nodeBApiAddr).runSeller(counterparty = nodeA.nodeInfo.legalIdentity.name))
}, isDebug = true)
}
}

View File

@ -0,0 +1,18 @@
package net.corda.traderdemo
import net.corda.core.node.services.ServiceInfo
import net.corda.node.driver.driver
import net.corda.node.services.transactions.SimpleNotaryService
/**
* This file is exclusively for being able to run your nodes through an IDE (as opposed to running deployNodes)
* Do not use in a production environment.
*/
fun main(args: Array<String>) {
driver(dsl = {
startNode("Notary", setOf(ServiceInfo(SimpleNotaryService.type)))
startNode("Bank A")
startNode("Bank B")
waitForAllNodesToFinish()
}, isDebug = true)
}

View File

@ -0,0 +1,59 @@
package net.corda.traderdemo
import com.google.common.net.HostAndPort
import net.corda.core.contracts.DOLLARS
import net.corda.core.utilities.loggerFor
import joptsimple.OptionParser
import org.slf4j.Logger
import kotlin.system.exitProcess
/**
* This entry point allows for command line running of the trader demo functions on nodes started by Main.kt.
*/
fun main(args: Array<String>) {
TraderDemo().main(args)
}
private class TraderDemo {
enum class Role {
BUYER,
SELLER
}
companion object {
val logger: Logger = loggerFor<TraderDemo>()
}
fun main(args: Array<String>) {
val parser = OptionParser()
val roleArg = parser.accepts("role").withRequiredArg().ofType(Role::class.java).required()
val options = try {
parser.parse(*args)
} catch (e: Exception) {
logger.error(e.message)
printHelp(parser)
exitProcess(1)
}
// What happens next depends on the role. The buyer sits around waiting for a trade to start. The seller role
// will contact the buyer and actually make something happen.
val role = options.valueOf(roleArg)!!
if (role == Role.BUYER) {
TraderDemoClientApi(HostAndPort.fromString("localhost:10005")).runBuyer()
} else {
TraderDemoClientApi(HostAndPort.fromString("localhost:10007")).runSeller(1000.DOLLARS, "Bank A")
}
}
fun printHelp(parser: OptionParser) {
println("""
Usage: trader-demo --role [BUYER|SELLER]
Please refer to the documentation in docs/build/index.html for more info.
""".trimIndent())
parser.printHelpOn(System.out)
}
}

View File

@ -0,0 +1,26 @@
package net.corda.traderdemo
import com.google.common.net.HostAndPort
import net.corda.core.contracts.Amount
import net.corda.core.contracts.DOLLARS
import net.corda.testing.http.HttpApi
import java.util.*
/**
* Interface for communicating with nodes running the trader demo.
*/
class TraderDemoClientApi(hostAndPort: HostAndPort) {
private val api = HttpApi.fromHostAndPort(hostAndPort, apiRoot)
fun runBuyer(amount: Amount<Currency> = 30000.0.DOLLARS, notary: String = "Notary"): Boolean {
return api.putJson("create-test-cash", mapOf("amount" to amount.quantity, "notary" to notary))
}
fun runSeller(amount: Amount<Currency> = 1000.0.DOLLARS, counterparty: String): Boolean {
return api.postJson("$counterparty/sell-cash", mapOf("amount" to amount.quantity))
}
private companion object {
private val apiRoot = "api/traderdemo"
}
}

View File

@ -0,0 +1,66 @@
package net.corda.traderdemo.api
import net.corda.contracts.testing.fillWithSomeTestCash
import net.corda.core.contracts.DOLLARS
import net.corda.core.node.ServiceHub
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.Emoji
import net.corda.core.utilities.loggerFor
import net.corda.traderdemo.protocol.SellerProtocol
import javax.ws.rs.*
import javax.ws.rs.core.MediaType
import javax.ws.rs.core.Response
import kotlin.test.assertEquals
// API is accessible from /api/traderdemo. All paths specified below are relative to it.
@Path("traderdemo")
class TraderDemoApi(val services: ServiceHub) {
data class TestCashParams(val amount: Int, val notary: String)
data class SellParams(val amount: Int)
private companion object {
val logger = loggerFor<TraderDemoApi>()
}
/**
* Self issue some cash.
* TODO: At some point this demo should be extended to have a central bank node.
*/
@PUT
@Path("create-test-cash")
@Consumes(MediaType.APPLICATION_JSON)
fun createTestCash(params: TestCashParams): Response {
val notary = services.networkMapCache.notaryNodes.single { it.legalIdentity.name == params.notary }.notaryIdentity
services.fillWithSomeTestCash(params.amount.DOLLARS,
outputNotary = notary,
ownedBy = services.myInfo.legalIdentity.owningKey)
return Response.status(Response.Status.CREATED).build()
}
@POST
@Path("{party}/sell-cash")
@Consumes(MediaType.APPLICATION_JSON)
fun sellCash(params: SellParams, @PathParam("party") partyName: String): Response {
val otherParty = services.identityService.partyFromName(partyName)
if (otherParty != null) {
// The seller will sell some commercial paper to the buyer, who will pay with (self issued) cash.
//
// The CP sale transaction comes with a prospectus PDF, which will tag along for the ride in an
// attachment. Make sure we have the transaction prospectus attachment loaded into our store.
//
// This can also be done via an HTTP upload, but here we short-circuit and do it from code.
if (services.storageService.attachments.openAttachment(SellerProtocol.PROSPECTUS_HASH) == null) {
javaClass.classLoader.getResourceAsStream("bank-of-london-cp.jar").use {
val id = services.storageService.attachments.importAttachment(it)
assertEquals(SellerProtocol.PROSPECTUS_HASH, id)
}
}
// The line below blocks and waits for the future to resolve.
val stx = services.invokeProtocolAsync<SignedTransaction>(SellerProtocol::class.java, otherParty, params.amount.DOLLARS).get()
logger.info("Sale completed - we have a happy customer!\n\nFinal transaction is:\n\n${Emoji.renderIfSupported(stx.tx)}")
return Response.status(Response.Status.OK).build()
} else {
return Response.status(Response.Status.BAD_REQUEST).build()
}
}
}

View File

@ -0,0 +1,18 @@
package net.corda.traderdemo.plugin
import net.corda.core.contracts.Amount
import net.corda.core.crypto.Party
import net.corda.core.node.CordaPluginRegistry
import net.corda.traderdemo.api.TraderDemoApi
import net.corda.traderdemo.protocol.BuyerProtocol
import net.corda.traderdemo.protocol.SellerProtocol
class TraderDemoPlugin : CordaPluginRegistry() {
// A list of classes that expose web APIs.
override val webApis: List<Class<*>> = listOf(TraderDemoApi::class.java)
// A list of protocols that are required for this cordapp
override val requiredProtocols: Map<String, Set<String>> = mapOf(
SellerProtocol::class.java.name to setOf(Party::class.java.name, Amount::class.java.name)
)
override val servicePlugins: List<Class<*>> = listOf(BuyerProtocol.Service::class.java)
}

View File

@ -0,0 +1,86 @@
package net.corda.traderdemo.protocol
import co.paralleluniverse.fibers.Suspendable
import net.corda.contracts.CommercialPaper
import net.corda.core.contracts.Amount
import net.corda.core.contracts.TransactionGraphSearch
import net.corda.core.crypto.Party
import net.corda.core.node.NodeInfo
import net.corda.core.node.PluginServiceHub
import net.corda.core.protocols.ProtocolLogic
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.Emoji
import net.corda.core.utilities.ProgressTracker
import net.corda.node.services.api.ServiceHubInternal
import net.corda.node.services.persistence.NodeAttachmentService
import net.corda.protocols.TwoPartyTradeProtocol
import java.nio.file.Path
import java.util.*
class BuyerProtocol(val otherParty: Party,
private val attachmentsPath: Path,
override val progressTracker: ProgressTracker = ProgressTracker(STARTING_BUY)) : ProtocolLogic<Unit>() {
object STARTING_BUY : ProgressTracker.Step("Seller connected, purchasing commercial paper asset")
class Service(services: PluginServiceHub) : SingletonSerializeAsToken() {
init {
// Buyer will fetch the attachment from the seller automatically when it resolves the transaction.
// For demo purposes just extract attachment jars when saved to disk, so the user can explore them.
val attachmentsPath = (services.storageService.attachments as NodeAttachmentService).let {
it.automaticallyExtractAttachments = true
it.storePath
}
services.registerProtocolInitiator(SellerProtocol::class) { BuyerProtocol(it, attachmentsPath) }
}
}
@Suspendable
override fun call() {
progressTracker.currentStep = STARTING_BUY
// Receive the offered amount and automatically agree to it (in reality this would be a longer negotiation)
val amount = receive<Amount<Currency>>(otherParty).unwrap { it }
val notary: NodeInfo = serviceHub.networkMapCache.notaryNodes[0]
val buyer = TwoPartyTradeProtocol.Buyer(
otherParty,
notary.notaryIdentity,
amount,
CommercialPaper.State::class.java)
// This invokes the trading protocol and out pops our finished transaction.
val tradeTX: SignedTransaction = subProtocol(buyer, shareParentSessions = true)
// TODO: This should be moved into the protocol itself.
serviceHub.recordTransactions(listOf(tradeTX))
logger.info("Purchase complete - we are a happy customer! Final transaction is: " +
"\n\n${Emoji.renderIfSupported(tradeTX.tx)}")
logIssuanceAttachment(tradeTX)
logBalance()
}
private fun logBalance() {
val balances = serviceHub.vaultService.cashBalances.entries.map { "${it.key.currencyCode} ${it.value}" }
logger.info("Remaining balance: ${balances.joinToString()}")
}
private fun logIssuanceAttachment(tradeTX: SignedTransaction) {
// Find the original CP issuance.
val search = TransactionGraphSearch(serviceHub.storageService.validatedTransactions, listOf(tradeTX.tx))
search.query = TransactionGraphSearch.Query(withCommandOfType = CommercialPaper.Commands.Issue::class.java,
followInputsOfType = CommercialPaper.State::class.java)
val cpIssuance = search.call().single()
cpIssuance.attachments.first().let {
val p = attachmentsPath.toAbsolutePath().resolve("$it.jar")
logger.info("""
The issuance of the commercial paper came with an attachment. You can find it expanded in this directory:
$p
${Emoji.renderIfSupported(cpIssuance)}""")
}
}
}

View File

@ -0,0 +1,110 @@
package net.corda.traderdemo.protocol
import co.paralleluniverse.fibers.Suspendable
import net.corda.contracts.CommercialPaper
import net.corda.contracts.asset.DUMMY_CASH_ISSUER
import net.corda.core.contracts.*
import net.corda.core.crypto.*
import net.corda.core.days
import net.corda.core.node.NodeInfo
import net.corda.core.protocols.ProtocolLogic
import net.corda.core.seconds
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.ProgressTracker
import net.corda.protocols.NotaryProtocol
import net.corda.protocols.TwoPartyTradeProtocol
import java.security.PublicKey
import java.time.Instant
import java.util.*
class SellerProtocol(val otherParty: Party,
val amount: Amount<Currency>,
override val progressTracker: ProgressTracker = tracker()) : ProtocolLogic<SignedTransaction>() {
companion object {
val PROSPECTUS_HASH = SecureHash.parse("decd098666b9657314870e192ced0c3519c2c9d395507a238338f8d003929de9")
object SELF_ISSUING : ProgressTracker.Step("Got session ID back, issuing and timestamping some commercial paper")
object TRADING : ProgressTracker.Step("Starting the trade protocol") {
override fun childProgressTracker(): ProgressTracker = TwoPartyTradeProtocol.Seller.tracker()
}
// We vend a progress tracker that already knows there's going to be a TwoPartyTradingProtocol involved at some
// point: by setting up the tracker in advance, the user can see what's coming in more detail, instead of being
// surprised when it appears as a new set of tasks below the current one.
fun tracker() = ProgressTracker(SELF_ISSUING, TRADING)
}
@Suspendable
override fun call(): SignedTransaction {
progressTracker.currentStep = SELF_ISSUING
val notary: NodeInfo = serviceHub.networkMapCache.notaryNodes[0]
val cpOwnerKey = serviceHub.legalIdentityKey
val commercialPaper = selfIssueSomeCommercialPaper(cpOwnerKey.public.tree, notary)
progressTracker.currentStep = TRADING
// Send the offered amount.
send(otherParty, amount)
val seller = TwoPartyTradeProtocol.Seller(
otherParty,
notary,
commercialPaper,
amount,
cpOwnerKey,
progressTracker.getChildProgressTracker(TRADING)!!)
val tradeTX: SignedTransaction = subProtocol(seller, shareParentSessions = true)
serviceHub.recordTransactions(listOf(tradeTX))
return tradeTX
}
@Suspendable
fun selfIssueSomeCommercialPaper(ownedBy: PublicKeyTree, notaryNode: NodeInfo): StateAndRef<CommercialPaper.State> {
// Make a fake company that's issued its own paper.
val keyPair = generateKeyPair()
val party = Party("Bank of London", keyPair.public)
val issuance: SignedTransaction = run {
val tx = CommercialPaper().generateIssue(party.ref(1, 2, 3), 1100.DOLLARS `issued by` DUMMY_CASH_ISSUER,
Instant.now() + 10.days, notaryNode.notaryIdentity)
// TODO: Consider moving these two steps below into generateIssue.
// Attach the prospectus.
tx.addAttachment(serviceHub.storageService.attachments.openAttachment(PROSPECTUS_HASH)!!.id)
// Requesting timestamping, all CP must be timestamped.
tx.setTime(Instant.now(), 30.seconds)
// Sign it as ourselves.
tx.signWith(keyPair)
// Get the notary to sign the timestamp
val notarySig = subProtocol(NotaryProtocol.Client(tx.toSignedTransaction(false)))
tx.addSignatureUnchecked(notarySig)
// Commit it to local storage.
val stx = tx.toSignedTransaction(true)
serviceHub.recordTransactions(listOf(stx))
stx
}
// Now make a dummy transaction that moves it to a new key, just to show that resolving dependencies works.
val move: SignedTransaction = run {
val builder = TransactionType.General.Builder(notaryNode.notaryIdentity)
CommercialPaper().generateMove(builder, issuance.tx.outRef(0), ownedBy)
builder.signWith(keyPair)
val notarySignature = subProtocol(NotaryProtocol.Client(builder.toSignedTransaction(false)))
builder.addSignatureUnchecked(notarySignature)
val tx = builder.toSignedTransaction(true)
serviceHub.recordTransactions(listOf(tx))
tx
}
return move.tx.outRef(0)
}
}

View File

@ -0,0 +1,2 @@
# Register a ServiceLoader service extending from net.corda.node.CordaPluginRegistry
net.corda.traderdemo.plugin.TraderDemoPlugin

View File

@ -0,0 +1 @@
These certificates are used for development mode only (and are copies of those contained within the TraderDemo jar file)