mirror of
https://github.com/corda/corda.git
synced 2025-06-15 05:38:14 +00:00
CORDA-2361: Split samples into contracts and workflows (#4575)
This commit is contained in:
committed by
GitHub
parent
82f5a756fe
commit
35acbc8107
@ -1,44 +0,0 @@
|
||||
package net.corda.attachmentdemo
|
||||
|
||||
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 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)) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
@ -1,218 +0,0 @@
|
||||
package net.corda.attachmentdemo
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import joptsimple.OptionParser
|
||||
import net.corda.client.rpc.CordaRPCClient
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.contracts.Contract
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.TypeOnlyCommandData
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.*
|
||||
import net.corda.core.identity.AbstractParty
|
||||
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.LedgerTransaction
|
||||
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 net.corda.testing.core.DUMMY_BANK_B_NAME
|
||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||
import net.corda.testing.node.internal.poll
|
||||
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)
|
||||
val executor = Executors.newScheduledThreadPool(2)
|
||||
try {
|
||||
sender(rpc, inputStream, hash, executor)
|
||||
} finally {
|
||||
executor.shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
private fun sender(rpc: CordaRPCOps, inputStream: InputStream, hash: SecureHash.SHA256, executor: ScheduledExecutorService) {
|
||||
// Get the identity key of the other side (the recipient).
|
||||
val notaryFuture: CordaFuture<Party> = poll(executor, DUMMY_NOTARY_NAME.toString()) { rpc.wellKnownPartyFromX500Name(DUMMY_NOTARY_NAME) }
|
||||
val otherSideFuture: CordaFuture<Party> = poll(executor, DUMMY_BANK_B_NAME.toString()) { rpc.wellKnownPartyFromX500Name(DUMMY_BANK_B_NAME) }
|
||||
// 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, otherSideFuture.get(), notaryFuture.get(), 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)
|
||||
}
|
||||
|
||||
const val ATTACHMENT_PROGRAM_ID = "net.corda.attachmentdemo.AttachmentContract"
|
||||
|
||||
class AttachmentContract : Contract {
|
||||
override fun verify(tx: LedgerTransaction) {
|
||||
val state = tx.outputsOfType<AttachmentContract.State>().single()
|
||||
// we check that at least one has the matching hash, the other will be the contract
|
||||
require(tx.attachments.any { it.id == state.hash }) {"At least one attachment in transaction must match hash ${state.hash}"}
|
||||
}
|
||||
|
||||
object Command : TypeOnlyCommandData()
|
||||
|
||||
data class State(val hash: SecureHash.SHA256) : ContractState {
|
||||
override val participants: List<AbstractParty> = emptyList()
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package net.corda.attachmentdemo
|
||||
|
||||
import net.corda.core.internal.div
|
||||
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.node.User
|
||||
|
||||
/**
|
||||
* 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>) {
|
||||
val demoUser = listOf(User("demo", "demo", setOf("StartFlow.net.corda.flows.FinalityFlow")))
|
||||
driver(DriverParameters(driverDirectory = "build" / "attachment-demo-nodes", waitForAllNodesToFinish = true)) {
|
||||
startNode(providedName = DUMMY_BANK_A_NAME, rpcUsers = demoUser)
|
||||
startNode(providedName = DUMMY_BANK_B_NAME, rpcUsers = demoUser)
|
||||
}
|
||||
}
|
Binary file not shown.
@ -1 +0,0 @@
|
||||
These certificates are used for development mode only (and are copies of those contained within the TraderDemo jar file)
|
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user