CORDA-2361: Split samples into contracts and workflows (#4575)

This commit is contained in:
Katarzyna Streich
2019-01-23 13:26:33 +00:00
committed by GitHub
parent 82f5a756fe
commit 35acbc8107
73 changed files with 562 additions and 383 deletions

View File

@ -0,0 +1,51 @@
package net.corda.attachmentdemo
import net.corda.attachmentdemo.workflows.recipient
import net.corda.attachmentdemo.workflows.sender
import net.corda.client.rpc.CordaRPCClient
import net.corda.core.utilities.getOrThrow
import net.corda.node.services.Permissions.Companion.all
import net.corda.testing.core.DUMMY_BANK_A_NAME
import net.corda.testing.core.DUMMY_BANK_B_NAME
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.driver
import net.corda.testing.driver.internal.incrementalPortAllocation
import net.corda.testing.node.User
import net.corda.testing.node.internal.findCordapp
import org.junit.Test
import java.util.concurrent.CompletableFuture.supplyAsync
class AttachmentDemoTest {
// run with a 10,000,000 bytes in-memory zip file. In practice, a slightly bigger file will be used (~10,002,000 bytes).
@Test
fun `attachment demo using a 10MB zip file`() {
val numOfExpectedBytes = 10_000_000
driver(DriverParameters(
portAllocation = incrementalPortAllocation(20000),
startNodesInProcess = true,
cordappsForAllNodes = listOf(findCordapp("net.corda.attachmentdemo.contracts"), findCordapp("net.corda.attachmentdemo.workflows")))
) {
val demoUser = listOf(User("demo", "demo", setOf(all())))
val (nodeA, nodeB) = listOf(
startNode(providedName = DUMMY_BANK_A_NAME, rpcUsers = demoUser, maximumHeapSize = "1g"),
startNode(providedName = DUMMY_BANK_B_NAME, rpcUsers = demoUser, maximumHeapSize = "1g")
).map { it.getOrThrow() }
val webserverHandle = startWebserver(nodeB).getOrThrow()
val senderThread = supplyAsync {
CordaRPCClient(nodeA.rpcAddress).start(demoUser[0].username, demoUser[0].password).use {
sender(it.proxy, numOfExpectedBytes)
}
}
val recipientThread = supplyAsync {
CordaRPCClient(nodeB.rpcAddress).start(demoUser[0].username, demoUser[0].password).use {
recipient(it.proxy, webserverHandle.listenAddress.port)
}
}
senderThread.getOrThrow()
recipientThread.getOrThrow()
}
}
}

View File

@ -0,0 +1,189 @@
package net.corda.attachmentdemo.workflows
import co.paralleluniverse.fibers.Suspendable
import joptsimple.OptionParser
import net.corda.attachmentdemo.contracts.ATTACHMENT_PROGRAM_ID
import net.corda.attachmentdemo.contracts.AttachmentContract
import net.corda.client.rpc.CordaRPCClient
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.*
import net.corda.core.identity.Party
import net.corda.core.internal.Emoji
import net.corda.core.internal.InputStreamAndHash
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.startTrackedFlow
import net.corda.core.node.StatesToRecord
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.getOrThrow
import java.io.InputStream
import java.net.HttpURLConnection
import java.net.URL
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledExecutorService
import java.util.jar.JarInputStream
import javax.servlet.http.HttpServletResponse.SC_OK
import javax.ws.rs.core.HttpHeaders.CONTENT_DISPOSITION
import javax.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM
import kotlin.system.exitProcess
internal enum class Role {
SENDER,
RECIPIENT
}
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) {
println(e.message)
printHelp(parser)
exitProcess(1)
}
val role = options.valueOf(roleArg)!!
when (role) {
Role.SENDER -> {
val host = NetworkHostAndPort("localhost", 10006)
println("Connecting to sender node ($host)")
CordaRPCClient(host).start("demo", "demo").use {
sender(it.proxy)
}
}
Role.RECIPIENT -> {
val host = NetworkHostAndPort("localhost", 10009)
println("Connecting to the recipient node ($host)")
CordaRPCClient(host).start("demo", "demo").use {
recipient(it.proxy, 10010)
}
}
}
}
/** An in memory test zip attachment of at least numOfClearBytes size, will be used. */
// DOCSTART 2
fun sender(rpc: CordaRPCOps, numOfClearBytes: Int = 1024) { // default size 1K.
val (inputStream, hash) = InputStreamAndHash.createInMemoryTestZip(numOfClearBytes, 0)
sender(rpc, inputStream, hash)
}
private fun sender(rpc: CordaRPCOps, inputStream: InputStream, hash: SecureHash.SHA256) {
// Get the identity key of the other side (the recipient).
val notaryParty = rpc.partiesFromName("Notary", false).firstOrNull() ?: throw IllegalArgumentException("Couldn't find notary party")
val bankBParty = rpc.partiesFromName("Bank B", false).firstOrNull() ?: throw IllegalArgumentException("Couldn't find Bank B party")
// Make sure we have the file in storage
if (!rpc.attachmentExists(hash)) {
inputStream.use {
val id = rpc.uploadAttachment(it)
require(hash == id) { "Id was '$id' instead of '$hash'" }
}
require(rpc.attachmentExists(hash)) { "Attachment matching hash: $hash does not exist" }
}
val flowHandle = rpc.startTrackedFlow(::AttachmentDemoFlow, bankBParty, notaryParty, hash)
flowHandle.progress.subscribe(::println)
val stx = flowHandle.returnValue.getOrThrow()
println("Sent ${stx.id}")
}
// DOCEND 2
@InitiatingFlow
@StartableByRPC
class AttachmentDemoFlow(private val otherSide: Party,
private val notary: Party,
private val attachId: SecureHash.SHA256) : FlowLogic<SignedTransaction>() {
object SIGNING : ProgressTracker.Step("Signing transaction")
override val progressTracker: ProgressTracker = ProgressTracker(SIGNING)
@Suspendable
override fun call(): SignedTransaction {
// Create a trivial transaction with an output that describes the attachment, and the attachment itself
val ptx = TransactionBuilder(notary)
.addOutputState(AttachmentContract.State(attachId), ATTACHMENT_PROGRAM_ID)
.addCommand(AttachmentContract.Command, ourIdentity.owningKey)
.addAttachment(attachId)
progressTracker.currentStep = SIGNING
val stx = serviceHub.signInitialTransaction(ptx)
// Send the transaction to the other recipient
return subFlow(FinalityFlow(stx, initiateFlow(otherSide)))
}
}
@InitiatedBy(AttachmentDemoFlow::class)
class StoreAttachmentFlow(private val otherSide: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
// As a non-participant to the transaction we need to record all states
subFlow(ReceiveFinalityFlow(otherSide, statesToRecord = StatesToRecord.ALL_VISIBLE))
}
}
@StartableByRPC
@StartableByService
class NoProgressTrackerShellDemo : FlowLogic<String>() {
@Suspendable
override fun call(): String {
return "You Called me!"
}
}
@Suppress("DEPRECATION")
// DOCSTART 1
fun recipient(rpc: CordaRPCOps, webPort: Int) {
println("Waiting to receive transaction ...")
val stx = rpc.internalVerifiedTransactionsFeed().updates.toBlocking().first()
val wtx = stx.tx
if (wtx.attachments.isNotEmpty()) {
if (wtx.outputs.isNotEmpty()) {
val state = wtx.outputsOfType<AttachmentContract.State>().single()
require(rpc.attachmentExists(state.hash)) { "attachment matching hash: ${state.hash} does not exist" }
// Download the attachment via the Web endpoint.
val connection = URL("http://localhost:$webPort/attachments/${state.hash}").openConnection() as HttpURLConnection
try {
require(connection.responseCode == SC_OK) { "HTTP status code was ${connection.responseCode}" }
require(connection.contentType == APPLICATION_OCTET_STREAM) { "Content-Type header was ${connection.contentType}" }
require(connection.getHeaderField(CONTENT_DISPOSITION) == "attachment; filename=\"${state.hash}.zip\"") {
"Content-Disposition header was ${connection.getHeaderField(CONTENT_DISPOSITION)}"
}
// Write out the entries inside this jar.
println("Attachment JAR contains these entries:")
JarInputStream(connection.inputStream).use { it ->
while (true) {
val e = it.nextJarEntry ?: break
println("Entry> ${e.name}")
it.closeEntry()
}
}
} finally {
connection.disconnect()
}
println("File received - we're happy!\n\nFinal transaction is:\n\n${Emoji.renderIfSupported(wtx)}")
} else {
println("Error: no output state found in ${wtx.id}")
}
} else {
println("Error: no attachments found in ${wtx.id}")
}
}
// DOCEND 1
private fun printHelp(parser: OptionParser) {
println("""
Usage: attachment-demo --role [RECIPIENT|SENDER] [options]
Please refer to the documentation in docs/build/index.html for more info.
""".trimIndent())
parser.printHelpOn(System.out)
}

View File

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