Corda Spaceship Auction-Part 3 (Workflows)

Mabel Oza
InsatiableMinds
Published in
19 min readSep 11, 2020

--

Photo by Morning Brew on Unsplash

Now that we put together our contract and states in part 2 of this series, it’s time for the muscle of our CorDapp, workflows!

You can always refer back to the previous tutorial:

The code we are writing up in these tutorials can always be found in our Github project:

We’re going to start putting things into motion with our workflow code. Delete the packages under the Kotlin folder and create our packages, com.force.auction.workflows.

Before we get started, let’s configure our module by going into the build.gradle file for contracts and copying in the following:

apply plugin: 'net.corda.plugins.cordapp'
apply plugin: 'net.corda.plugins.quasar-utils'
cordapp {
targetPlatformVersion corda_platform_version.toInteger()
minimumPlatformVersion corda_platform_version.toInteger()
workflow {
name "Auction CorDapp - Flows"
vendor "Corda Open Source"
licence "Apache License, Version 2.0"
versionId 1
}
}
sourceSets {
main {
resources {
srcDir rootProject.file("config/dev")
}
}
test {
resources {
srcDir rootProject.file("config/test")
}
}
integrationTest {
kotlin {
compileClasspath += main.output + test.output
runtimeClasspath += main.output + test.output
srcDir file('src/integrationTest/kotlin')
}
}
}
configurations {
integrationTestCompile.extendsFrom testCompile
integrationTestRuntime.extendsFrom testRuntime
}
dependencies {
testCompile "junit:junit:$junit_version"
// Corda dependencies.
cordaCompile "$corda_release_group:corda-core:$corda_release_version"
cordaCompile "$corda_release_group:corda-finance-workflows:$corda_release_version"
cordaRuntime "$corda_release_group:corda:$corda_release_version"
testCompile "$corda_release_group:corda-node-driver:$corda_release_version"
// CorDapp dependencies.
cordapp project(":contracts")
}
task integrationTest(type: Test, dependsOn: []) {
testClassesDirs = sourceSets.integrationTest.output.classesDirs
classpath = sourceSets.integrationTest.runtimeClasspath
}

There isn’t anything too exciting in our gradle file besides the part where we are referencing the contracts module we created earlier (cordapp project(“:contracts”)).

Now for the fun stuff! We have seven different flows in this project, so by the end of this tutorial, you should Corda Flow Jedis. DON’T FEAR! I will cover most of the details about flows in the beginning and whiz through the later ones. We will write up the flows in the order below:

  1. CreateSpaceshipFlow.kt
  2. CreateAuctionFlow.kt
  3. BidFlow.kt
  4. EndAuctionFlow.kt
  5. AuctionDvPFlow.kt
  6. AuctionExitFlow.kt
  7. AuctionSettlementFlow.kt

Create Spaceship Flow

Our first flow is to create the spaceships we are auctioning off in our CorDapp.

Before we start any flow we need to make two decisions:

Will we ever use an RPC interface to start this flow? If yes, then add @StartableByRPC

Are you going to initiate other flows using this flow? Is yes, then add @InitiatingFlow

Then you go into adding in the variable that will be part of this specific flow; in this case, they are all the variables we specified earlier in our Spaceship state. Once we include all the variables that will go into this flow, we will extend the flow using FlowLogic<SignedTransactions>(), Signed Transactions is the type we are expecting back from the flow.

You will notice throughout our flow code a progress tracker (override val progressTracker = ProgressTracker()). Progress Tracker tells any API connected to this CorDapp where we are in the transaction.

At this point, you would notice that the line class CreateSpaceshipFlow has a compilation error, it’s because we extended FlowLogic and need to implement the member override fun call(): SignedTransaction {}.

On top of the call member you need to add the annotation @Suspendable, suspendable says that this flow can pause while the other parties are taking care of their business. Even though this flow isn’t explicitly interacting with other parties, we include it because it may be interacting Corda flows in the backend.

So far have the lines below:

import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow
import net.corda.core.flows.StartableByRPC
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.ProgressTracker
@InitiatingFlow
@StartableByRPC
class CreateSpaceshipFlow(
private val title: String,
private val name: String,
private val description: String,
private val imageUrl: String,
private val batteryLifeHours: Int,
private val speedMPH: Double,
private val size: String,
private val luxury: String) : FlowLogic<SignedTransaction>() {
override val progressTracker = ProgressTracker()@Suspendable
override fun call(): SignedTransaction {
TODO("Not yet implemented")
}
}

Now we’re ready to add in the meat to our flow, the call method (override fun call(): SignedTransaction {…}. We are going to take the following 7 steps to complete this flow:

1. Get the notary

val notary = serviceHub.networkMapCache.notaryIdentities.single()

2. Create the desired output state, what state do you want at the end of this?

val output = Spaceship(UniqueIdentifier(), name, description, imageUrl, batteryLifeHours, speedMPH, size, luxury, ourIdentity)

3. Get the Command your using this flow from the Contract

val command = SpaceshipContract.Commands.CreateSpaceship()

4. Build the transaction with the notary, output state, and the command

val txBuilder = TransactionBuilder(notary)
.addOutputState(output)
.addCommand(command, listOf(ourIdentity.owningKey))

5. Verify the transaction

txBuilder.verify(serviceHub)

6. Sign the transaction

val stx = serviceHub.signInitialTransaction(txBuilder)

7. Notarize the transaction and record the state in the ledger

return subFlow(FinalityFlow(stx, listOf()))

After the above is added your final code for CreateSpaceship.kt look like the one below:

package com.force.auction.workflowsimport co.paralleluniverse.fibers.Suspendable
import com.force.auction.contracts.SpaceshipContract
import com.force.auction.states.Spaceship
import net.corda.core.contracts.UniqueIdentifier
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.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker
@InitiatingFlow
@StartableByRPC
class CreateSpaceshipFlow(
private val name: String,
private val description: String,
private val imageUrl: String,
private val batteryLifeHours: String,
private val speedMPH: Double,
private val size: String,
private val luxury: String) : FlowLogic<SignedTransaction>() {
override val progressTracker = ProgressTracker()@Suspendable
override fun call(): SignedTransaction {
//Get the notary
val notary = serviceHub.networkMapCache.notaryIdentities.single()
// val notary = serviceHub.networkMapCache.getNotary(CordaX500Name.parse("O=Notary,P=Venus")) // METHOD 2 is what you would use in production//Create desired output state
val output = Spaceship(UniqueIdentifier(), name, description, imageUrl, batteryLifeHours, speedMPH, size, luxury, ourIdentity)
val command = SpaceshipContract.Commands.CreateSpaceship()//Build the transaction with notary, output, and command
val txBuilder = TransactionBuilder(notary)
.addOutputState(output)
.addCommand(command, listOf(ourIdentity.owningKey))
//Verify transaction
txBuilder.verify(serviceHub)
//Sign the transaction
val stx = serviceHub.signInitialTransaction(txBuilder)
//Notarize the transaction record the state in the ledger
return subFlow(FinalityFlow(stx, listOf()))
}
}

As you noticed above ServiceHub is used repeatedly, it’s a special gift from Corda, it provides you with many services you can do inside a node. When in doubt, always typing serviceHub dot will guide you to the operation you are looking for (https://api.corda.net/api/corda-os/4.5/html/api/kotlin/corda/net.corda.core.node/-service-hub/index.html).

NetworkMapCache is the network node know-all. If you want to know anything about a node, use NetworkMapCache. Network Map cache is a way you can look into the network map.

A network map contains lists of nodes on the network along with information about their identity keys, services they provide and host names or IP addresses where they can be connected to.

The cache wraps around a map fetched from an authoritative service, and adds easy lookup of the data stored within it. Generally it would be initialised with a specified network map service, which it fetches data from and then subscribes to updates of.

After pulling up all our necessary materials: notary, output state, and the command (from a contract) with the associated keys in the transaction, we build our transaction using TransactionBuilder. Once the transaction is built, we verify the transaction with the transaction builder, then we use one of ServiceHub’s many services, signInitialTransaction, to sign off on the transaction.

Now that the transaction is built, verified, and signed off, it’s time to send the transaction over to the notary to be notarized and recorded in the ledger, the “FINAL” step; this leads us to return a subFlow called FinalityFlow.

When the transaction goes to the notary does the following:

  1. Review the transaction and decides if it can accept the transaction (consensus comes into play here)
  2. Commits the transaction to the ledger and vault if it accepted the transaction
  3. Pass out the update to all parties that are in a “need to know” basis

Verifies the given transaction, then sends it to the named notary. If the notary agrees that the transaction is acceptable then it is from that point onwards committed to the ledger, and will be written through to the vault. Additionally it will be distributed to the parties reflected in the participants list of the states.

Create Auction Flow

This flow is straightforward, the big twist from the last one is that this one also has a responder flow.

Below is the initiating flow:

...
@Suspendable
override fun call(): SignedTransaction {
//Get notary
val notary = serviceHub.networkMapCache.notaryIdentities.single()
// val notary = serviceHub.networkMapCache.getNotary(CordaX500Name.parse("O=Notary,P=Venus")) // METHOD 2//Stating that the party that creates the auction, is the auction
//So the auctioneer is the identity of the party initiating the create auction flow
val auctioneer = ourIdentity
//All nodes except the auctioneer and notary can be bidders in the auction
val bidders = serviceHub.networkMapCache.allNodes.map {
it.legalIdentities.get(0)
} - auctioneer - notary
//Design output state
//The nulls are the values that are yet to be determined by the auction's results
//We don't know who is going to be our winning bidder, what's the highest bid, etc.
val output = AuctionState(
LinearPointer(UniqueIdentifier(null, auctionItem), LinearState::class.java),
UUID.randomUUID(),
basePrice,
null,
null,
bidDeadline.atZone(ZoneId.systemDefault()).toInstant(),
null,
true,
auctioneer,
bidders,
null)
//Build transaction
val txBuilder = TransactionBuilder(notary)
.addOutputState(output)
.addCommand(AuctionContract.Commands.CreateAuction(), listOf(auctioneer.owningKey))
//Verify Transaction
txBuilder.verify(serviceHub)
//Sign the transaction
val stx = serviceHub.signInitialTransaction(txBuilder)
//Create a bidder session, which is the bidder initiation the flow
val bidderSessions = bidders.map{initiateFlow(it)}
//Notarize and record transaction
return subFlow(FinalityFlow(stx, bidderSessions))
}

A few highlights are:

  • The auctioneer is the one that will create the auction, so when a party initiates the create auction flow, they are the auctioneer.
val auctioneer = ourIdentity
  • The bidders are everyone in the network except the auctioneer and notary nodes.
val bidders = serviceHub.networkMapCache.allNodes.map {
it.legalIdentities.get(0)
} - auctioneer - notary
  • When we are creating the output state, we mention a State Pointer, LinearPointer. The Linear pointer comes in handy when one state depends on the data in another state and needs to refer to the latest version of that state. In this case, the auction state depends on the spaceship state.
val output = AuctionState(
LinearPointer(UniqueIdentifier(null, auctionItem), LinearState::class.java), ...

An example of a LinearPointer could be a VehicleState pointing to LoanState, as the loan is gradually paid-off the LoanState changes, but the VehicleState would always point to the latest version of the LoanState.

https://www.corda.net/blog/linking-corda-states-using-statepointer/

If you want to dive deep into State Pointer, check out the article below:

  • Initiating the bidder’s session, and you will later be returning this in the finality flow along with the signed transaction.
val bidderSessions = bidders.map{initiateFlow(it)}

Now let’s get to the responding party and write out our responder flow, see the code below:

@InitiatedBy(CreateAuctionFlow::class)
class CreateAuctionFlowResponder(val counterPartySession: FlowSession) : FlowLogic<SignedTransaction>() {
@Suspendable
override fun call() : SignedTransaction {
return subFlow(ReceiveFinalityFlow(counterPartySession))
}
}

Nothing too exciting here, we are mentioning who is initiating this flow using the @InitiatedBy annotation, and we are returning a RecieveFinalityFlow instead of a Finality Flow.

The final code for the CreateAuctionFlow should look like the following:

package com.force.auction.workflowsimport co.paralleluniverse.fibers.Suspendable
import com.force.auction.contracts.AuctionContract
import com.force.auction.states.AuctionState
import net.corda.core.contracts.Amount
import net.corda.core.contracts.LinearPointer
import net.corda.core.contracts.LinearState
import net.corda.core.contracts.UniqueIdentifier
import net.corda.core.flows.*
import net.corda.core.identity.Party
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker
import java.time.Instant
import java.time.LocalDateTime
import java.time.ZoneId
import java.util.*
@InitiatingFlow
@StartableByRPC
class CreateAuctionFlow(
private val basePrice: Amount<Currency>,
private val auctionItem: UUID,
private val bidDeadline: LocalDateTime) : FlowLogic<SignedTransaction>() {
override val progressTracker = ProgressTracker()@Suspendable
override fun call(): SignedTransaction {
//Get notary
val notary = serviceHub.networkMapCache.notaryIdentities.single()
// val notary = serviceHub.networkMapCache.getNotary(CordaX500Name.parse("O=Notary,P=Venus")) // METHOD 2//Stating that the party that creates the auction, is the auction
//So the auctioneer is the identity of the party initiating the create auction flow
val auctioneer = ourIdentity
//All nodes except the auctioneer and notary can be bidders in the auction
val bidders = serviceHub.networkMapCache.allNodes.map {
it
.legalIdentities.get(0)
} - auctioneer - notary
//Design output state
//The nulls are the values that are yet to be determined by the auction's results
//We don't know who is going to be our winning bidder, what's the highest bid, etc.
val output = AuctionState(
LinearPointer(UniqueIdentifier(null, auctionItem), LinearState::class.java),
UUID.randomUUID(),
basePrice,
null,
null,
bidDeadline.atZone(ZoneId.systemDefault()).toInstant(),
null,
true,
auctioneer,
bidders,
null)
//Build transaction
val txBuilder = TransactionBuilder(notary)
.addOutputState(output)
.addCommand(AuctionContract.Commands.CreateAuction(), listOf(auctioneer.owningKey))
//Verify Transaction
txBuilder.verify(serviceHub)
//Sign the transaction
val stx = serviceHub.signInitialTransaction(txBuilder)
//Create a bidder session, which is the bidder initiation the flow
val bidderSessions = bidders.map{initiateFlow(it)}
//Notarize and record transaction
return subFlow(FinalityFlow(stx, bidderSessions))
}
}@InitiatedBy(CreateAuctionFlow::class)
class CreateAuctionFlowResponder(val counterPartySession: FlowSession) : FlowLogic<SignedTransaction>() {
@Suspendable
override fun call() : SignedTransaction {
return subFlow(ReceiveFinalityFlow(counterPartySession))
}
}

Bid Flow

Now for the bidding wars to begin! The Bid Flow is straightforward, it has an initiating flow and a responding flow, and we are dealing with a specific item in this flow.

Below is the code to the BidFlow.kt

package com.force.auction.workflows

import co.paralleluniverse.fibers.Suspendable
import com.force.auction.contracts.AuctionContract
import com.force.auction.states.AuctionState
import net.corda.core.contracts.Amount
import net.corda.core.flows.*
import net.corda.core.node.services.queryBy
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker
import java.util.*

@StartableByRPC
@InitiatingFlow
class BidFlow(
private val auctionId : UUID,
private val bidAmount : Amount<Currency>
): FlowLogic<SignedTransaction>() {

override val progressTracker = ProgressTracker()

@Suspendable
override fun call(): SignedTransaction {

//Getting a List of Auction States
val auctionStateAndRefs = serviceHub.vaultService.queryBy<AuctionState>().states

//Filtering the list of Auction States for the auction ID reference
val inputStateAndRef = auctionStateAndRefs.filter {
val auctionState = it.state.data
auctionState.auctionId == this.auctionId
}[0]

//Use the reference above to pull the Input State
val inputState = inputStateAndRef.state.data

val output = inputState.copy(highestBid = bidAmount, highestBidder = ourIdentity)

//Get Notary
val notary = inputStateAndRef.state.notary

//Pull Command from the Auction Contract interface
val command = AuctionContract.Commands.Bid()

//Build the transaction with the notary, input, output, and command
val txBuilder = TransactionBuilder(notary)
.addInputState(inputStateAndRef)
.addOutputState(output)
.addCommand(command, ourIdentity.owningKey)

//Verify Transaction
txBuilder.verify(serviceHub)

//Create the bidder session, the bidder session excludes initiator of this flow and adds in the auctioneer
val bidderSession = inputState.bidders
.minus(ourIdentity)
.plus(inputState.auctioneer)
.map {
initiateFlow(it)
}

//Sign Transaction
val stx = serviceHub.signInitialTransaction(txBuilder)

//Notarize and "FINALIZE" the transaction
return subFlow(FinalityFlow(stx, bidderSession))

}


}

//The other side responds back to the BidFlow
@InitiatedBy(BidFlow::class)
class BidFlowResponder(val counterPartySession: FlowSession) : FlowLogic<SignedTransaction>() {
override fun call(): SignedTransaction {
return subFlow(ReceiveFinalityFlow(counterPartySession))
}
}

Below are a few highlights from the BidFlow code above:

Filtering a State

Filter the Auction State Reference to find the current auction item we passed into this flow. This time we will also use the input state in our transaction builder.

//Getting a List of Auction State References
val auctionStateAndRefs = serviceHub.vaultService.queryBy<AuctionState>().states

//Filtering the list of Auction States for the auction ID Reference
val inputStateAndRef = auctionStateAndRefs.filter {
val auctionState = it.state.data
auctionState.auctionId == this.auctionId
}[0]
//Use the reference above to pull the actual Input State
val inputState = inputStateAndRef.state.data

Pulling Notaries Based on State

This time we are pulling the notary that’s associated with the current auction item’s state.

val notary = inputStateAndRef.state.notary

Copying and Modifying States

In the BidFlow our output is a copy of the input state the only difference is the highest Bid and highest bidder values.

val output = inputState.copy(highestBid = bidAmount, highestBidder = ourIdentity)

End Auction Flow

Going once, going twice, going thrice… And sold to the… (hitting the gavel), we’re ending our auction because found our highest bidder, winner, and winning bid.

Luckily for you, this code is straightforward, there is one big call-out in the End Auction Flow:

Schedulable

We use @SchedulableFlow because we need a fair way to figure out when we should end the auction.

Do you recall how we used the nextScheduledActivity in our Auction State earlier? Everything ties together here, this is how you implement event scheduled events (https://docs.corda.net/docs/corda-os/4.5/event-scheduling.html):

Have your ContractState implementation also implement SchedulableState. This requires a method named nextScheduledActivity to be implemented which returns an optional ScheduledActivity instance. ScheduledActivity captures what FlowLogic instance each node will run, to perform the activity, and when it will run is described by a java.time.Instant. Once your state implements this interface and is tracked by the vault, it can expect to be queried for the next activity when committed to the vault. The FlowLogic must be annotated with @SchedulableFlow.

Below is the full code for EndFlow:

package com.force.auction.workflows

import co.paralleluniverse.fibers.Suspendable
import com.force.auction.contracts.AuctionContract
import com.force.auction.states.AuctionState
import net.corda.core.flows.*
import net.corda.core.node.services.queryBy
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import java.util.*

@InitiatingFlow
@StartableByRPC
@SchedulableFlow
class EndFlow(private val auctionId: UUID) : FlowLogic<SignedTransaction?>() {

@Suspendable
override fun call(): SignedTransaction? {

//Get Command
val command = AuctionContract.Commands.EndAuction()

//Get List of Auction State references
val auctionStateAndRefs = serviceHub.vaultService.queryBy<AuctionState>().states

//Get the Input State for the Auction Id passed in
val inputStateAndRef = auctionStateAndRefs.filter {
val auctionState = it.state.data
auctionState.auctionId == this.auctionId
}[0]

val inputState = inputStateAndRef.state.data

//Get Notary for the item with the auction id passed in
val notary = inputStateAndRef.state.notary

//Get Output, only the auctioneer can find the output
if(ourIdentity.owningKey == inputState.auctioneer.owningKey) {
val output = inputState.copy(
active = false, //Auction is no longer happening
highestBidder = inputState.highestBidder,
winner = inputState.winner,
winningBid = inputState.highestBid)


//Build Transaction
val txBuilder = TransactionBuilder(notary)
.addInputState(inputStateAndRef)
.addOutputState(output)
.addCommand(command, ourIdentity.owningKey)

//Verify Transaction
txBuilder.verify(serviceHub)

//Sign Transaction
val stx = serviceHub.signInitialTransaction(txBuilder)

//Create the bidder session
val bidderSesssion = inputState.bidders.map {
initiateFlow(it)
}

//Notarize Transaction
return subFlow(FinalityFlow(stx, bidderSesssion))
}
return null
}
}

@InitiatedBy(EndFlow::class)
class EndFlowResponder(val counterPartySession:FlowSession) : FlowLogic<SignedTransaction>() {
override fun call(): SignedTransaction {

return subFlow(ReceiveFinalityFlow(counterPartySession))
}

}

Auction Delivery and Payment Flow

Now it’s time to deliver and pay up, we’ll be using the AuctionDvPFlow to tackle this.

This time we’re not using the auction state as an input, instead we’re using the auction state input to pull the Spaceship state. We’re grabbing the auction item that corresponds to the auction id and all items that are unconsumed using QueryCriteria.LinearStateQueryCriteria.

...
//Query the vault to fetch a list of AuctionStates
val auctionStateAndRefs = serviceHub.vaultService.queryBy<AuctionState>().states

//Filter the list by the presented auction id
val inputStateAndRef = auctionStateAndRefs.filter {
val auctionState = it.state.data
auctionState.auctionId == this.auctionId
}[0]

val auctionState = inputStateAndRef.state.data


//Create Query Criteria to query the Spaceship, the Spaceship should correspond to the auction id or Unconsumed
val queryCriteria = QueryCriteria.LinearStateQueryCriteria(
null,
listOf(inputStateAndRef.state.data.auctionItem.resolve(serviceHub).state.data.linearId.id),
null,
Vault.StateStatus.UNCONSUMED)

//Query the vault to fetch the Spaceship that's Unconsumed and corresponds to the item in Auction (see queryCriteria)
val spaceshipStateAndRef = serviceHub.vaultService.queryBy<Spaceship>(queryCriteria).states[0]
...

Change the owner of the spaceship to the new owner, which is the auction winner.

val commandAndState = spaceshipStateAndRef.state.data.withNewOwner(auctionState.winner!!)

The CashUtils.generateSpend method generates a transaction that moves the payment from the current party, ourIdentityAndCert, to the auctioneer. The results of CashUtils.generateSpend is a pair with the transaction builder and a list of public keys, hence the name txAndKeysPair.

val txAndKeysPair = CashUtils.generateSpend(
serviceHub,
txBuilderPre,
payment,
ourIdentityAndCert,
auctionState.auctioneer,
emptySet())

This time when we are signing a transaction, we’re going to use a key pair because now we’re dealing with $MONEY MONEY$. Corda uses a UTXO (Unspent Transaction Output) model, similar to Bitcoin, UTXO’s big requirement is that we generate a new key pair (keysToSign) for every monetary transaction. This key pair is for the change coming back to you.

val keysToSign = txAndKeysPair.second.plus(ourIdentity.owningKey)val stx = serviceHub.signInitialTransaction(txBuilder, keysToSign)

Now that we signed the transaction we’ll need to grab the Auctioneer’s signature.

//Collect the counterparty's signature
val auctioneerFlow = initiateFlow(auctionState.auctioneer)

val ftx = subFlow(CollectSignaturesFlow(stx, listOf(auctioneerFlow)))

return subFlow(FinalityFlow(ftx, (auctioneerFlow)))

Below is the full code for the AuctionDvPFlow class:

package com.force.auction.workflows

import co.paralleluniverse.fibers.Suspendable
import com.force.auction.states.AuctionState
import com.force.auction.states.Spaceship
import net.corda.core.contracts.Amount
import net.corda.core.contracts.CommandAndState
import net.corda.core.flows.*
import net.corda.core.identity.CordaX500Name
import net.corda.core.node.services.Vault
import net.corda.core.node.services.queryBy
import net.corda.core.node.services.vault.QueryCriteria
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker
import net.corda.finance.workflows.asset.CashUtils
import java.util.*
import javax.annotation.Signed

@StartableByRPC
@InitiatingFlow
class AuctionDvPFlow(
private val auctionId: UUID,
private val payment: Amount<Currency>
): FlowLogic<SignedTransaction>() {

override val progressTracker = ProgressTracker()

@Suspendable
override fun call():SignedTransaction {

//Query the vault to fetch a list of AuctionStates
val auctionStateAndRefs = serviceHub.vaultService.queryBy<AuctionState>().states

//Filter the list by the presented auction id
val inputStateAndRef = auctionStateAndRefs.filter {
val auctionState = it.state.data
auctionState.auctionId == this.auctionId
}[0]

val auctionState = inputStateAndRef.state.data

//Create Query Criteria to query the Spaceship, the Spaceship should correspond to the auction id or Unconsumed
val queryCriteria = QueryCriteria.LinearStateQueryCriteria(
null,
listOf(inputStateAndRef.state.data.auctionItem.resolve(serviceHub).state.data.linearId.id),
null,
Vault.StateStatus.UNCONSUMED)

//Query the vault to fetch the Spaceship that's Unconsumed and corresponds to the item in Auction (see queryCriteria)
val spaceshipStateAndRef = serviceHub.vaultService.queryBy<Spaceship>(queryCriteria).states[0]

//Get Notary
val notary = serviceHub.networkMapCache.notaryIdentities.single()
//val notary = serviceHub.networkMapCache.getNotary(CordaX500Name.parse("O=Notary, L=London, C=GB"))

val commandAndState = spaceshipStateAndRef.state.data.withNewOwner(auctionState.winner!!)

val txBuilderPre = TransactionBuilder(notary)

//Generating Spend for the Cash using CashUtils
//CashUtil's generateSpend method is used to update the txBuilder with inputs and outputs that reflect the cash being spent.
//A new keypair is generated to sign the transaction, so that the change returned to the send after the case is spent is untraceable
//Flows the UTXO Model, similar to Bitcoin
val txAndKeysPair = CashUtils.generateSpend(
serviceHub,
txBuilderPre,
payment,
ourIdentityAndCert,
auctionState.auctioneer,
emptySet())

val txBuilder = txAndKeysPair.first

//Building the transaction with the Spaceship input, output, the command
txBuilder.addInputState(spaceshipStateAndRef)
.addOutputState(commandAndState.ownableState)
.addCommand(commandAndState.command, listOf(auctionState.auctioneer.owningKey))

//Verifying Transaction
txBuilder.verify(serviceHub)

/*Signing the transaction with the new key pair generated
- KeysToSign Generates the new keypair using our Identity
- stx is when we sign the transaction with our new key pairs
*/
val keysToSign = txAndKeysPair.second.plus(ourIdentity.owningKey)

val stx = serviceHub.signInitialTransaction(txBuilder, keysToSign)

//Collect the counterparty's signature
val auctioneerFlow = initiateFlow(auctionState.auctioneer)

val ftx = subFlow(CollectSignaturesFlow(stx, listOf(auctioneerFlow)))

return subFlow(FinalityFlow(ftx, (auctioneerFlow)))

}
}

@InitiatedBy(AuctionDvPFlow::class)
class AuctionDvPFlowResponder(val counterpartySession: FlowSession) : FlowLogic<SignedTransaction>() {

@Suspendable
override fun call():SignedTransaction {
subFlow(object: SignTransactionFlow(counterpartySession) {
@Throws(FlowException::class)
override fun checkTransaction(stx: SignedTransaction) {
}
})
return subFlow(ReceiveFinalityFlow(counterpartySession))
}
}

Auction Exit Flow

We’re coming in here when an item was sold or we hit the auction item’s deadline without a sale.

Since we’re at the end we need to grab the necessary signatures. First of all we need the auctioneer’s signature (owningKey) if we have a winner, we’ll also need to add on winner’s signature to our list of signatures.

val signers = listOf(auctionState.auctioneer.owningKey)

if(auctionState.winner != null) {
signers.plus(auctionState.winner!!.owningKey)
}

Sessions

The next steps is to create our flow sessions.

What are Flow Session? Flow sessions handles conmmunication between two paired flows, possibly running on separate nodes.

If we have a winner and we’re the auctioneer then we will have the winner initiate the flow and call that the winner session. With the winner session we are going to queue the payload to send it to a counterparty without suspending (winnerSession.send(true)).

Note on Send: The other party may receive the message at some arbitrary later point or not at all: if counterparty is offline then message delivery will be retried until it comes back or until the message is older than the network’s event horizon time.

Otherwise, if we’re not the auctioneer we’ll have the auctioneer initiate the flow and call that the auctioneer session. We’ll do the same things we did with the auctioneer session as mentioned earlier with the winner session. After all that logic we will create a list for all sessions. We’ll be using this allSession list in our next piece of code.

var stx = serviceHub.signInitialTransaction(txBuilder)

if(auctionState.winner != null) {

if(auctionState.auctioneer == ourIdentity) {

val winnerSession = initiateFlow(auctionState.winner!!)

winnerSession.send(true)

stx = subFlow(CollectSignaturesFlow(stx, listOf(winnerSession)))

} else {

val auctioneerSession = initiateFlow(auctionState.auctioneer)

auctioneerSession.send(true)

stx = subFlow(CollectSignaturesFlow(stx, listOf(auctioneerSession)))
}
}

val allSession: MutableList<FlowSession> = mutableListOf<FlowSession>().toMutableList()

Here we are running a for loop through our auction state participants, if the participant doesn’t match our identity then we will go ahead and create a session (initiateFlow(party)).

for (party in auctionState.participants.filter { it != ourIdentity }) {

val session = initiateFlow(party)
session.send(false)
allSession += session
}

Now we have entered the responder flow AuctionExitResponder, we’re creating a flag that tells us if the counterparty sends us a message. The receive flow session method suspends the session until the counterparty sends us a message.

@InitiatedBy(AuctionExitFlow::class)
class AuctionExitFlowResponder(val counterpartySession: FlowSession) : FlowLogic<SignedTransaction>() {

@Suspendable
override fun call():SignedTransaction {

val flag = counterpartySession.receive<Boolean>().unwrap{ it -> it}

if(flag) {

subFlow(object : SignTransactionFlow(counterpartySession) {
override fun checkTransaction(stx: SignedTransaction) {

}
})
}
return subFlow(ReceiveFinalityFlow(counterpartySession))
}
}

Below is the full code for the Auction Exit Flow:

package com.force.auction.workflowsimport co.paralleluniverse.fibers.Suspendable
import com.force.auction.contracts.AuctionContract
import com.force.auction.states.AuctionState
import net.corda.core.flows.*
import net.corda.core.node.services.queryBy
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.unwrap
import java.util.*
@InitiatingFlow
@StartableByRPC
class AuctionExitFlow(private val auctionId: UUID) : FlowLogic<SignedTransaction>() {
override val progressTracker = ProgressTracker() @Suspendable
override fun call(): SignedTransaction {
//Query the vault to get all the Auction States
val auctionStateAndRefs = serviceHub.vaultService.queryBy<AuctionState>().states
//Use the AuctionState to fetch all results based on auction Id
val inputStateAndRefs = auctionStateAndRefs.filter {
val auctionState = it.state.data
auctionState.auctionId == this.auctionId
}[0]
val auctionState = inputStateAndRefs.state.data //Figure out who is the signers of the transaction based on whether the auction has received bids.
//Highest bidder must sign to avoid consuming an auction that's not settled yet
val signers = listOf(auctionState.auctioneer.owningKey)
if(auctionState.winner != null) {
signers.plus(auctionState.winner!!.owningKey)
}
//Build the transaction with the notary, input state reference, and the command (along with the signers)
val txBuilder = TransactionBuilder(inputStateAndRefs.state.notary)
.addInputState(inputStateAndRefs)
.addCommand(AuctionContract.Commands.Exit(), signers)
//Verify the transaction
txBuilder.verify(serviceHub)
//Sign the transaction
var stx = serviceHub.signInitialTransaction(txBuilder)
if(auctionState.winner != null) { if(auctionState.auctioneer == ourIdentity) { val winnerSession = initiateFlow(auctionState.winner!!) winnerSession.send(true) stx = subFlow(CollectSignaturesFlow(stx, listOf(winnerSession))) } else { val auctioneerSession = initiateFlow(auctionState.auctioneer) auctioneerSession.send(true) stx = subFlow(CollectSignaturesFlow(stx, listOf(auctioneerSession)))
}
}
val allSession: MutableList<FlowSession> = mutableListOf<FlowSession>().toMutableList() for (party in auctionState.participants.filter { it != ourIdentity }) { val session = initiateFlow(party)
session.send(false)
allSession += session
}
return subFlow(FinalityFlow(stx, allSession))
}
}
@InitiatedBy(AuctionExitFlow::class)
class AuctionExitFlowResponder(val counterpartySession: FlowSession) : FlowLogic<SignedTransaction>() {
@Suspendable
override fun call():SignedTransaction {
val flag = counterpartySession.receive<Boolean>().unwrap{ it -> it} if(flag) { subFlow(object : SignTransactionFlow(counterpartySession) {
override fun checkTransaction(stx: SignedTransaction) {
}
})
}
return subFlow(ReceiveFinalityFlow(counterpartySession))
}
}

Auction Settlement Flow

The Auction Settlement Flow is rather boring, all we’re really doing here is calling our two flows, AuctionDvPFlow and AuctionExitFlow. Below is the code for the Auction Settlement Flow:

@StartableByRPC
class AuctionSettlementFlow(private val auctionId: UUID,
private val amount: Amount<Currency>) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
subFlow(AuctionDvPFlow(auctionId, amount))
subFlow(AuctionExitFlow(auctionId))
}

}

Finally, we’re done with all of our CorDapp backend code, we completed our States, Contracts, and Flows. Now we have our data models, business logic and workflows all together for our CorDapp. Let’s check out our network, to build your network you need to run the command below, we’re building our project and running our deployNode task we defined in the first tutorial (https://medium.com/insatiableminds/corda-spaceship-auction-4bf43402bd9d)

gradle clean build deployNodes -x test

We’re excluding the test because we haven’t written any tests at this point.

After running the command above you should see the build folder with separate folders for each node, just like the image below. Each node has its respective log folders, if you are looking for details on a specific node or need to debug checkout the logs here. To better understand the description of each node, check out the node.conf file. Each node has a corda jar file, these jar files represent the nodes, to run all the corda jars at the same time you would use the runnodes script.

To use the runnodes script you would enter the command below:

./build/nodes/runnodes

Running this command will get us all our node’s corda shell windows up, like the image below, there will one for each party and our notary. The shell windows have the configurations we set in the deployNodes task in our build.gradle file.

With all the corda shells up we can start playing with our network. To figure out the node’s name you can run the command run nodeInfo. If you run the command flow list, we can check out all the flows a node has, including the flows we just created.

We’ll use the CreateSpacehsipFlow and add in a new spaceship into our network. We’ll use flow start to start a workflow and put together our spaceship with the command below:

flow start CreateSpaceshipFlow name: Falcon, description: best ship in the universe, imageUrl: https://spacenews.com/wp-content/uploads/2018/05/Falcon-9-Block-5-879x485.png, batteryLifeHours: 4, speedMPH: 300, size: large, luxury: medium

We know the variables we need to fill to initiate this flow because our flow, CreateSpaceshipFlow, requires us to add in the values below.

class CreateSpaceshipFlow(
private val name: String,
private val description: String,
private val imageUrl: String,
private val batteryLifeHours: String,
private val speedMPH: Double,
private val size: String,
private val luxury: String) : FlowLogic<SignedTransaction>() {

After running the command above you should get a message like the one below, it would give you the Signed Transaction with the transaction’s id.

If you want to take it a step further you can even check out the transaction in the node’s database, for Rocket Moon the JDBC is jdbc:h2:tcp://localhost:20045/node (username: sa, keep the password blank).

In the table STATE_PARTY you can find that the TRANSACTION_ID matches up with the SignedTransaction above, ‘B958….’. If your curiosity is peaked, check out the other tables to better understand how the transaction data is stored.

Now we can take a deep breath and enjoy the fact that we just completed our network and have it running on our local machine (via mock network). YEAHHH! High Five my friend!

Photo by Raquel García on Unsplash

The next step is to connect our backend code with another application so we can have a UI and interact with it from a visually appealing site.

Stay tuned for that article in the next few weeks!

--

--

Mabel Oza
InsatiableMinds

Making the financial world more secure, accessible, and transparent.