mirror of
https://github.com/corda/corda.git
synced 2024-12-18 20:47:57 +00:00
Issue commercial paper from the Bank of Corda node (#1196)
* Remove hard coded commercial paper issuer from trader demo * Issue commercial paper from the Bank of Corda node, as per previous hard-coded issuer name * Issue cash from the Bank of Corda node rather than in response to the buyer node requesting issuance
This commit is contained in:
parent
0977ffca54
commit
4602739e93
@ -32,6 +32,9 @@ UNRELEASED
|
||||
of different soft locking retrieval behaviours (exclusive of soft locked states, soft locked states only, specified
|
||||
by set of lock ids)
|
||||
|
||||
* Trader demo now issues cash and commercial paper directly from the bank node, rather than the seller node self-issuing
|
||||
commercial paper but labelling it as if issued by the bank.
|
||||
|
||||
Milestone 14
|
||||
------------
|
||||
|
||||
|
@ -32,7 +32,7 @@ To run from the command line in Unix:
|
||||
|
||||
1. Run ``./gradlew samples:trader-demo:deployNodes`` to create a set of configs and installs under ``samples/trader-demo/build/nodes``
|
||||
2. Run ``./samples/trader-demo/build/nodes/runnodes`` to open up four new terminals with the four nodes
|
||||
3. Run ``./gradlew samples:trader-demo:runBuyer`` to instruct the buyer node to request issuance of some cash from the Bank of Corda node
|
||||
3. Run ``./gradlew samples:trader-demo:runBank`` to instruct the bank node to issue cash and commercial paper to the buyer and seller nodes respectively.
|
||||
4. Run ``./gradlew samples:trader-demo:runSeller`` to trigger the transaction. If you entered ``flow watch``
|
||||
you can see flows running on both sides of transaction. Additionally you should see final trade information displayed
|
||||
to your terminal.
|
||||
|
@ -142,7 +142,8 @@ object TwoPartyTradeFlow {
|
||||
// Put together a proposed transaction that performs the trade, and sign it.
|
||||
progressTracker.currentStep = SIGNING
|
||||
val (ptx, cashSigningPubKeys) = assembleSharedTX(assetForSale, tradeRequest)
|
||||
val partSignedTx = signWithOurKeys(cashSigningPubKeys, ptx)
|
||||
// Now sign the transaction with whatever keys we need to move the cash.
|
||||
val partSignedTx = serviceHub.signInitialTransaction(ptx, cashSigningPubKeys)
|
||||
|
||||
// Send the signed transaction to the seller, who must then sign it themselves and commit
|
||||
// it to the ledger by sending it to the notary.
|
||||
@ -168,10 +169,6 @@ object TwoPartyTradeFlow {
|
||||
}
|
||||
}
|
||||
|
||||
private fun signWithOurKeys(cashSigningPubKeys: List<PublicKey>, ptx: TransactionBuilder): SignedTransaction {
|
||||
// Now sign the transaction with whatever keys we need to move the cash.
|
||||
return serviceHub.signInitialTransaction(ptx, cashSigningPubKeys)
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
private fun assembleSharedTX(assetForSale: StateAndRef<OwnableState>, tradeRequest: SellerTradeInfo): Pair<TransactionBuilder, List<PublicKey>> {
|
||||
|
@ -40,8 +40,9 @@ dependencies {
|
||||
|
||||
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
||||
ext.rpcUsers = [['username': "demo", 'password': "demo", 'permissions': [
|
||||
'StartFlow.net.corda.flows.IssuerFlow$IssuanceRequester',
|
||||
"StartFlow.net.corda.traderdemo.flow.SellerFlow"
|
||||
'StartFlow.net.corda.flows.CashIssueFlow',
|
||||
'StartFlow.net.corda.traderdemo.flow.CommercialPaperIssueFlow',
|
||||
'StartFlow.net.corda.traderdemo.flow.SellerFlow'
|
||||
]]]
|
||||
|
||||
directory "./build/nodes"
|
||||
@ -74,7 +75,9 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
||||
name "CN=BankOfCorda,O=R3,L=New York,C=US"
|
||||
advertisedServices = []
|
||||
p2pPort 10011
|
||||
rpcPort 10012
|
||||
cordapps = []
|
||||
rpcUsers = ext.rpcUsers
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,11 +105,11 @@ publishing {
|
||||
}
|
||||
}
|
||||
|
||||
task runBuyer(type: JavaExec) {
|
||||
task runBank(type: JavaExec) {
|
||||
classpath = sourceSets.main.runtimeClasspath
|
||||
main = 'net.corda.traderdemo.TraderDemoKt'
|
||||
args '--role'
|
||||
args 'BUYER'
|
||||
args 'BANK'
|
||||
}
|
||||
|
||||
task runSeller(type: JavaExec) {
|
||||
|
@ -6,6 +6,7 @@ import net.corda.core.utilities.millis
|
||||
import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.core.internal.concurrent.transpose
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.flows.CashIssueFlow
|
||||
import net.corda.testing.DUMMY_BANK_A
|
||||
import net.corda.testing.DUMMY_BANK_B
|
||||
import net.corda.testing.DUMMY_NOTARY
|
||||
@ -17,6 +18,7 @@ import net.corda.testing.BOC
|
||||
import net.corda.testing.driver.poll
|
||||
import net.corda.testing.node.NodeBasedTest
|
||||
import net.corda.traderdemo.flow.BuyerFlow
|
||||
import net.corda.traderdemo.flow.CommercialPaperIssueFlow
|
||||
import net.corda.traderdemo.flow.SellerFlow
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Test
|
||||
@ -25,15 +27,13 @@ import java.util.concurrent.Executors
|
||||
class TraderDemoTest : NodeBasedTest() {
|
||||
@Test
|
||||
fun `runs trader demo`() {
|
||||
val permissions = setOf(
|
||||
startFlowPermission<IssuerFlow.IssuanceRequester>(),
|
||||
startFlowPermission<SellerFlow>())
|
||||
val demoUser = listOf(User("demo", "demo", permissions))
|
||||
val user = User("user1", "test", permissions = setOf(startFlowPermission<IssuerFlow.IssuanceRequester>()))
|
||||
val (nodeA, nodeB) = listOf(
|
||||
startNode(DUMMY_BANK_A.name, rpcUsers = demoUser),
|
||||
startNode(DUMMY_BANK_B.name, rpcUsers = demoUser),
|
||||
startNode(BOC.name, rpcUsers = listOf(user)),
|
||||
val demoUser = User("demo", "demo", setOf(startFlowPermission<SellerFlow>()))
|
||||
val bankUser = User("user1", "test", permissions = setOf(startFlowPermission<CashIssueFlow>(),
|
||||
startFlowPermission<CommercialPaperIssueFlow>()))
|
||||
val (nodeA, nodeB, bankNode, notaryNode) = listOf(
|
||||
startNode(DUMMY_BANK_A.name, rpcUsers = listOf(demoUser)),
|
||||
startNode(DUMMY_BANK_B.name, rpcUsers = listOf(demoUser)),
|
||||
startNode(BOC.name, rpcUsers = listOf(bankUser)),
|
||||
startNode(DUMMY_NOTARY.name, advertisedServices = setOf(ServiceInfo(SimpleNotaryService.type)))
|
||||
).transpose().getOrThrow()
|
||||
|
||||
@ -41,19 +41,24 @@ class TraderDemoTest : NodeBasedTest() {
|
||||
|
||||
val (nodeARpc, nodeBRpc) = listOf(nodeA, nodeB).map {
|
||||
val client = CordaRPCClient(it.configuration.rpcAddress!!, initialiseSerialization = false)
|
||||
client.start(demoUser[0].username, demoUser[0].password).proxy
|
||||
client.start(demoUser.username, demoUser.password).proxy
|
||||
}
|
||||
val nodeBankRpc = let {
|
||||
val client = CordaRPCClient(bankNode.configuration.rpcAddress!!, initialiseSerialization = false)
|
||||
client.start(bankUser.username, bankUser.password).proxy
|
||||
}
|
||||
|
||||
val clientA = TraderDemoClientApi(nodeARpc)
|
||||
val clientB = TraderDemoClientApi(nodeBRpc)
|
||||
val clientBank = TraderDemoClientApi(nodeBankRpc)
|
||||
|
||||
val originalACash = clientA.cashCount // A has random number of issued amount
|
||||
val expectedBCash = clientB.cashCount + 1
|
||||
val expectedPaper = listOf(clientA.commercialPaperCount + 1, clientB.commercialPaperCount)
|
||||
|
||||
// TODO: Enable anonymisation
|
||||
clientA.runBuyer(amount = 100.DOLLARS, anonymous = false)
|
||||
clientB.runSeller(counterparty = nodeA.info.legalIdentity.name, amount = 5.DOLLARS)
|
||||
clientBank.runIssuer(amount = 100.DOLLARS, buyerName = nodeA.info.legalIdentity.name, sellerName = nodeB.info.legalIdentity.name, notaryName = notaryNode.info.legalIdentity.name)
|
||||
clientB.runSeller(buyerName = nodeA.info.legalIdentity.name, amount = 5.DOLLARS)
|
||||
|
||||
assertThat(clientA.cashCount).isGreaterThan(originalACash)
|
||||
assertThat(clientB.cashCount).isEqualTo(expectedBCash)
|
||||
|
@ -4,8 +4,10 @@ import joptsimple.OptionParser
|
||||
import net.corda.client.rpc.CordaRPCClient
|
||||
import net.corda.core.contracts.DOLLARS
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.testing.DUMMY_BANK_A
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.testing.DUMMY_BANK_A
|
||||
import net.corda.testing.DUMMY_BANK_B
|
||||
import net.corda.testing.DUMMY_NOTARY
|
||||
import org.slf4j.Logger
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
@ -18,12 +20,18 @@ fun main(args: Array<String>) {
|
||||
|
||||
private class TraderDemo {
|
||||
enum class Role {
|
||||
BUYER,
|
||||
BANK,
|
||||
SELLER
|
||||
}
|
||||
|
||||
companion object {
|
||||
val logger: Logger = loggerFor<TraderDemo>()
|
||||
val buyerName = DUMMY_BANK_A.name
|
||||
val sellerName = DUMMY_BANK_B.name
|
||||
val notaryName = DUMMY_NOTARY.name
|
||||
val buyerRpcPort = 10006
|
||||
val sellerRpcPort = 10009
|
||||
val bankRpcPort = 10012
|
||||
}
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
@ -41,15 +49,15 @@ private class TraderDemo {
|
||||
// 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) {
|
||||
val host = NetworkHostAndPort("localhost", 10006)
|
||||
CordaRPCClient(host).start("demo", "demo").use {
|
||||
TraderDemoClientApi(it.proxy).runBuyer()
|
||||
if (role == Role.BANK) {
|
||||
val bankHost = NetworkHostAndPort("localhost", bankRpcPort)
|
||||
CordaRPCClient(bankHost).use("demo", "demo") {
|
||||
TraderDemoClientApi(it.proxy).runIssuer(1100.DOLLARS, buyerName, sellerName, notaryName)
|
||||
}
|
||||
} else {
|
||||
val host = NetworkHostAndPort("localhost", 10009)
|
||||
CordaRPCClient(host).use("demo", "demo") {
|
||||
TraderDemoClientApi(it.proxy).runSeller(1000.DOLLARS, DUMMY_BANK_A.name)
|
||||
val sellerHost = NetworkHostAndPort("localhost", sellerRpcPort)
|
||||
CordaRPCClient(sellerHost).use("demo", "demo") {
|
||||
TraderDemoClientApi(it.proxy).runSeller(1000.DOLLARS, buyerName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,11 +16,11 @@ import net.corda.core.node.services.vault.builder
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.flows.IssuerFlow.IssuanceRequester
|
||||
import net.corda.flows.CashIssueFlow
|
||||
import net.corda.node.services.vault.VaultSchemaV1
|
||||
import net.corda.testing.BOC
|
||||
import net.corda.testing.DUMMY_NOTARY
|
||||
import net.corda.testing.contracts.calculateRandomlySizedAmounts
|
||||
import net.corda.traderdemo.flow.CommercialPaperIssueFlow
|
||||
import net.corda.traderdemo.flow.SellerFlow
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import java.util.*
|
||||
@ -47,25 +47,42 @@ class TraderDemoClientApi(val rpc: CordaRPCOps) {
|
||||
return rpc.vaultQueryBy<CommercialPaper.State>(countCriteria).otherResults.single() as Long
|
||||
}
|
||||
|
||||
fun runBuyer(amount: Amount<Currency> = 30000.DOLLARS, anonymous: Boolean = false) {
|
||||
val bankOfCordaParty = rpc.partyFromX500Name(BOC.name)
|
||||
?: throw IllegalStateException("Unable to locate ${BOC.name} in Network Map Service")
|
||||
fun runIssuer(amount: Amount<Currency> = 1100.0.DOLLARS, buyerName: X500Name, sellerName: X500Name, notaryName: X500Name) {
|
||||
val ref = OpaqueBytes.of(1)
|
||||
val buyer = rpc.partyFromX500Name(buyerName) ?: throw IllegalStateException("Don't know $buyerName")
|
||||
val seller = rpc.partyFromX500Name(sellerName) ?: throw IllegalStateException("Don't know $sellerName")
|
||||
val notaryLegalIdentity = rpc.partyFromX500Name(DUMMY_NOTARY.name)
|
||||
?: throw IllegalStateException("Unable to locate ${DUMMY_NOTARY.name} in Network Map Service")
|
||||
val notaryNode = rpc.nodeIdentityFromParty(notaryLegalIdentity)
|
||||
?: throw IllegalStateException("Unable to locate notary node in network map cache")
|
||||
val me = rpc.nodeIdentity()
|
||||
val amounts = calculateRandomlySizedAmounts(amount, 3, 10, Random())
|
||||
// issuer random amounts of currency totaling 30000.DOLLARS in parallel
|
||||
val anonymous = false
|
||||
// issue random amounts of currency up to the requested amount, in parallel
|
||||
val resultFutures = amounts.map { pennies ->
|
||||
rpc.startFlow(::IssuanceRequester, Amount(pennies, amount.token), me.legalIdentity, OpaqueBytes.of(1), bankOfCordaParty, notaryNode.notaryIdentity, anonymous).returnValue
|
||||
rpc.startFlow(::CashIssueFlow, amount.copy(quantity = pennies), OpaqueBytes.of(1), buyer, notaryNode.notaryIdentity, anonymous).returnValue
|
||||
}
|
||||
|
||||
resultFutures.transpose().getOrThrow()
|
||||
println("Cash issued to buyer")
|
||||
|
||||
// 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 (!rpc.attachmentExists(SellerFlow.PROSPECTUS_HASH)) {
|
||||
javaClass.classLoader.getResourceAsStream("bank-of-london-cp.jar").use {
|
||||
val id = rpc.uploadAttachment(it)
|
||||
check(SellerFlow.PROSPECTUS_HASH == id)
|
||||
}
|
||||
}
|
||||
|
||||
fun runSeller(amount: Amount<Currency> = 1000.0.DOLLARS, counterparty: X500Name) {
|
||||
val otherParty = rpc.partyFromX500Name(counterparty) ?: throw IllegalStateException("Don't know $counterparty")
|
||||
// The line below blocks and waits for the future to resolve.
|
||||
val stx = rpc.startFlow(::CommercialPaperIssueFlow, amount, ref, seller, notaryNode.notaryIdentity).returnValue.getOrThrow()
|
||||
println("Commercial paper issued to seller")
|
||||
}
|
||||
|
||||
fun runSeller(amount: Amount<Currency> = 1000.0.DOLLARS, buyerName: X500Name) {
|
||||
val otherParty = rpc.partyFromX500Name(buyerName) ?: throw IllegalStateException("Don't know $buyerName")
|
||||
// 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
|
||||
|
@ -0,0 +1,76 @@
|
||||
package net.corda.traderdemo.flow
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.contracts.CommercialPaper
|
||||
import net.corda.contracts.asset.DUMMY_CASH_ISSUER
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.`issued by`
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.FinalityFlow
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.core.utilities.days
|
||||
import net.corda.core.utilities.seconds
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Flow for the Bank of Corda node to issue some commercial paper to the seller's node, to sell to the buyer.
|
||||
*/
|
||||
@InitiatingFlow
|
||||
@StartableByRPC
|
||||
class CommercialPaperIssueFlow(val amount: Amount<Currency>,
|
||||
val issueRef: OpaqueBytes,
|
||||
val recipient: Party,
|
||||
val notary: Party,
|
||||
override val progressTracker: ProgressTracker) : FlowLogic<SignedTransaction>() {
|
||||
constructor(amount: Amount<Currency>, issueRef: OpaqueBytes, recipient: Party, notary: Party) : this(amount, issueRef, recipient, notary, tracker())
|
||||
|
||||
companion object {
|
||||
val PROSPECTUS_HASH = SecureHash.parse("decd098666b9657314870e192ced0c3519c2c9d395507a238338f8d003929de9")
|
||||
object ISSUING : ProgressTracker.Step("Issuing and timestamping some commercial paper")
|
||||
fun tracker() = ProgressTracker(ISSUING)
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
override fun call(): SignedTransaction {
|
||||
progressTracker.currentStep = ISSUING
|
||||
|
||||
val me = serviceHub.myInfo.legalIdentity
|
||||
val issuance: SignedTransaction = run {
|
||||
val tx = CommercialPaper().generateIssue(me.ref(issueRef), amount `issued by` me.ref(issueRef),
|
||||
Instant.now() + 10.days, notary)
|
||||
|
||||
// TODO: Consider moving these two steps below into generateIssue.
|
||||
|
||||
// Attach the prospectus.
|
||||
tx.addAttachment(serviceHub.attachments.openAttachment(PROSPECTUS_HASH)!!.id)
|
||||
|
||||
// Requesting a time-window to be set, all CP must have a validation window.
|
||||
tx.setTimeWindow(Instant.now(), 30.seconds)
|
||||
|
||||
// Sign it as ourselves.
|
||||
val stx = serviceHub.signInitialTransaction(tx)
|
||||
|
||||
subFlow(FinalityFlow(stx)).single()
|
||||
}
|
||||
|
||||
// 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 = TransactionBuilder(notary)
|
||||
CommercialPaper().generateMove(builder, issuance.tx.outRef(0), recipient)
|
||||
val stx = serviceHub.signInitialTransaction(builder)
|
||||
subFlow(FinalityFlow(stx)).single()
|
||||
}
|
||||
|
||||
return move
|
||||
}
|
||||
|
||||
}
|
@ -2,25 +2,17 @@ package net.corda.traderdemo.flow
|
||||
|
||||
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.contracts.Amount
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.utilities.days
|
||||
import net.corda.core.flows.FinalityFlow
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.utilities.seconds
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.flows.TwoPartyTradeFlow
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
|
||||
@InitiatingFlow
|
||||
@ -51,7 +43,8 @@ class SellerFlow(val otherParty: Party,
|
||||
|
||||
val notary: NodeInfo = serviceHub.networkMapCache.notaryNodes[0]
|
||||
val cpOwnerKey = serviceHub.keyManagementService.freshKey()
|
||||
val commercialPaper = selfIssueSomeCommercialPaper(serviceHub.myInfo.legalIdentity, notary)
|
||||
val commercialPaper = serviceHub.vaultQueryService.queryBy(CommercialPaper.State::class.java).states.first()
|
||||
|
||||
|
||||
progressTracker.currentStep = TRADING
|
||||
|
||||
@ -66,39 +59,4 @@ class SellerFlow(val otherParty: Party,
|
||||
progressTracker.getChildProgressTracker(TRADING)!!)
|
||||
return subFlow(seller)
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
fun selfIssueSomeCommercialPaper(ownedBy: AbstractParty, notaryNode: NodeInfo): StateAndRef<CommercialPaper.State> {
|
||||
// Make a fake company that's issued its own paper.
|
||||
val party = Party(X500Name("CN=BankOfCorda,O=R3,L=New York,C=US"), serviceHub.legalIdentityKey)
|
||||
|
||||
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.attachments.openAttachment(PROSPECTUS_HASH)!!.id)
|
||||
|
||||
// Requesting a time-window to be set, all CP must have a validation window.
|
||||
tx.setTimeWindow(Instant.now(), 30.seconds)
|
||||
|
||||
// Sign it as ourselves.
|
||||
val stx = serviceHub.signInitialTransaction(tx)
|
||||
|
||||
subFlow(FinalityFlow(stx)).single()
|
||||
}
|
||||
|
||||
// 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 = TransactionBuilder(notaryNode.notaryIdentity)
|
||||
CommercialPaper().generateMove(builder, issuance.tx.outRef(0), ownedBy)
|
||||
val stx = serviceHub.signInitialTransaction(builder)
|
||||
subFlow(FinalityFlow(stx)).single()
|
||||
}
|
||||
|
||||
return move.tx.outRef(0)
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user