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,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)