eAuction – Creating a sample auction house CorDapp from scratch! (Part2)
May 04, 2020
By Ashutosh Meher, Developer Evangelist at R3
This is a continuation of my previous blog post here where we discussed the states and contracts implementation of the auction CorDapp. This post will cover the flow and completion of the CorDapp. So let’s continue!
Flows
OK… Let’s put it all together and bring the CorDapp to life with flows.
1. CreateAssetFlow
Creating an asset is straight forward. It is the standard template code of a flow. The only thing to note is that we are doing a self-issue, hence there is no session in the FinalityFlow, nor do we need a Responder Flow.
2. CreateAuctionFlow
The next step is to create the auction. This is straightforward as well. The only challenge is that we have to do a broadcast to all of the participants in the network. We want everyone in the network to be aware of the auction availability.
We can’t really do a broadcast as Corda doesn’t support it, but we can get such behavior by adding all of the participants in the network as bidders to the auction. We can do that by fetching all parties from the network-map and adding them as bidders. Remember to remove the current node identity (auctioneer) and the notary.
List<Party> bidders = getServiceHub().getNetworkMapCache().getAllNodes().stream() .map(nodeInfo -> nodeInfo.getLegalIdentities().get(0)) .collect(Collectors.toList()); bidders.remove(auctioneer); bidders.remove(notary);
Here we create a broadcast by adding all participants to the bidder’s list of the Auction. There are other ways to achieve this as well but we will stick to this one for now.
Another important point is to add the Asset to the output AuctionState using a StatePointer . Below is how we can create a LinearPoiner (a subtype of StatePointer). This would add the asset as a reference state in the transaction and therefore it won’t be spent in the transaction.
new LinearPointer<>(new UniqueIdentifier(null, auctionItem), Asset.class)
Learn more about reference states here: https://docs.https://corda.net/docs/corda-os/4.0/design/reference-states/design.html
Here’s the full code of the flow.
3. BidFlow
Time to bid on the auction hence BidFlow. This is going to be pretty straightforward as well. All we need to do is:
- Fetch the correct AuctionState from the vault based on the auctionId. That becomes the input of our transaction.
List<StateAndRef<AuctionState>> auntionStateAndRefs = getServiceHub().getVaultService() .queryBy(AuctionState.class).getStates();
StateAndRef<AuctionState> inputStateAndRef = auntionStateAndRefs.stream().filter(auctionStateAndRef -> { AuctionState auctionState = auctionStateAndRef.getState().getData(); return auctionState.getAuctionId().equals(auctionId); }).findAny().orElseThrow(() -> new IllegalArgumentException("Auction Not Found"));
- Create the output state by copying the properties from the input just fetched, while making sure that the bidAmount is updated in the output.
AuctionState output = new AuctionState( input.getAuctionItem(), input.getAuctionId(), input.getBasePrice(), bidAmount, getOurIdentity(), input.getBidEndTime(), null, true, input.getAuctioneer(), input.getBidders(), null );
- Rest is a template code to build the transaction, verify, sign, and call the finality flow. Make sure to pass the correct sessions to the finality flow. It should contain sessions of all the participants in the network other than the node that’s bidding and the notary. Remember we don’t want to create a session of a node with itself, that would result in an exception.
List<FlowSession> allSessions = new ArrayList<>(); List<Party> bidders = new ArrayList<>(input.getBidders()); bidders.remove(getOurIdentity()); for(Party bidder: bidders) allSessions.add(initiateFlow(bidder));
allSessions.add(initiateFlow(input.getAuctioneer()));
That’s it. The bid flow is ready. Here the final version:
4. EndAuctionFlow
Next up, end the auction when the deadline is reached. This flow is triggered automatically and no manual intervention is required. We already scheduled it when we issued the AuctionState as a SchedulableState.
The flow itself is pretty simple, just fetch the AuctionState from the vault and use it as input. Create an output with the active flag set to false and follow the regular flow template.
One point to mention is that this flow must be annotated the @SchedulableFlow. Also, we don’t want all the nodes to start executing this flow, just the auctioneer should execute, so a small check there would be useful.
Here’s how it looks like:
5. AuctionSettlementFlow
Now coming to the final bit, the settlement. It should be initiated. by the highest bidder (auction winner) as he needs to pay for the trade. It actually needs to be completed in two separate steps.
- AuctionDVPFlow: The DVP transaction where the auctioned asset and the cash is changed hands between the auctioneer and the highest bidder.
- AuctionExitFlow: The exit transaction of the AuctionState. Once the settlement is concluded or the auction deadline is reached without bids, we can consume the AuctionState.
So, the AuctionSettlementFlow doesn’t do much by itself. It just calls the AuctionDVPFlow and AuctionExitFlow as subflows. Have a look at it below.
5a. AuctionDVPFlow
A Delivery-vs-Payment (DvP) transaction is where we do two legs of trade as an atomic transaction. Below are the steps involved for the Auction DvP:
- Fetch the AuctionState from the vault based on auctioned as described earlier. However, this time we are not using it as an input. We are going to use it to resolve the StatePointer pointing to the Asset to fetch the actual Asset state, which will be used as an input to the transaction.
StateAndRef<Asset> assetStateAndRef = auctionStateAndRef.getState().getData().getAuctionItem() .resolve(getServiceHub());
- Call the Asset’s withNewOwner() to fetch one of the command and output state of the transaction.
CommandAndState commandAndState = assetStateAndRef.getState() .getData().withNewOwner(auctionState.getWinner());
- Create an instance of TransactionBuilder.
- At this point, we have the input state, output state, and command for one leg of the trade. The other leg is cash transfer. We are using the Corda-finance CorDapp for the cash transactions. So you can utilize the CashUtils.generateSpend() to get that input, output and command required for the cash transaction. Note that the generateSpend() method takes the transaction builder and populates it with the input, output, and command corresponding to the cash spend transaction. It also returns a list of public keys to be used to sign the transaction. It generates a new key-pair for the change (If you spend 75 out of 100, 25 returns to you) to be returned to the spender, so that it’s untraceable.
Pair<TransactionBuilder, List<PublicKey>> txAndKeysPair = CashUtils.generateSpend(getServiceHub(), transactionBuilder, payment, getOurIdentityAndCert(), auctionState.getAuctioneer(), Collections.emptySet() );
- Build the transition by adding the asset’s input, output, and command to the transaction builder.
- The remaining is straight forward: verify, sign, collect signature, and finalize the transaction. One point to note is you also need to sign the transaction with the fresh key returned from the generateSpend() method, as it’s included in the command as a required signer.
SignedTransaction selfSignedTransaction = getServiceHub().signInitialTransaction(transactionBuilder, txAndKeysPair.getSecond());
Finally, we are done and it’s time to take a look at the final version.
5a. AuctionExitFlow
And here comes the final piece of the puzzle. We are finally ready to exit the auction. This would happen in two scenarios:
- Auction is settled.
- Auction has no bids till the deadline.
In the first case, this flow is called as a subflow of AuctionSettlementFlow by the highest bidder, in the later, it is called directly by the auctioneer.
Below is what we need to do:
- As always fetch the auction from the vault.
- Decide on the signers. Remember what we have in the contract’s verifyExit() if the auction has bids we need signatures of winner as well as the auctioneer, and if no bids are received, only the auctioneer signature is required.
List<PublicKey> signers = new ArrayList<>(); signers.add(auctionState.getAuctioneer().getOwningKey()); if(auctionState.getWinner()!=null){ signers.add(auctionState.getWinner().getOwningKey()); }
- Next just build, verify, and sign the transaction, collect signature, and call finality.
However, collecting signatures requires a little thinking as we are handling two different scenarios here. We don’t need to collect signature if it has been initiated by the auctioneer. However, we need to collect the auctioneer’s signature if its initiated by the auction winner (highest bidder).
if(auctionState.getWinner()!=null) { FlowSession auctioneerSession = initiateFlow(auctionState.getAuctioneer()); auctioneerSession.send(true); allSessions.add(auctioneerSession); signedTransaction = subFlow( new CollectSignaturesFlow(signedTransaction, Collections.singletonList(auctioneerSession)) ); }
Note that we also send a flag to the responder flow to indicate if the corresponding SignTransactionFlow is required to be executed. Below code at the responder end.
boolean flag = otherPartySession.receive(Boolean.class).unwrap(it -> it);
if(flag) { subFlow(new SignTransactionFlow(otherPartySession) {
@Override protected void checkTransaction( @NotNull SignedTransaction stx) throws FlowException { } }); }
That pretty much concludes our AuctionExitFlow. Here’s the final version.
That brings us to the end of this post and the CorDapp as well!
Source Code
The source code discussed in the post can be found below. It contains the completed CorDapp with the states, contracts, and flows all implemented. It also has a UI and a client implemented to play around. Check it out!
What’s next?
Want to learn more about building awesome blockchain applications on Corda? Be sure to visit https://corda.net, check out our community page to learn how to connect with other Corda developers, and sign up for one of our newsletters for the latest updates.
— Ashutosh Meher is a Developer Evangelist at R3, an enterprise blockchain software firm working with a global ecosystem of more than 350 participants across multiple industries from both the private and public sectors to develop on Corda, its open-source blockchain platform, and Corda Enterprise, a commercial version of Corda for enterprise usage.
Follow Ashutosh on Twitter: @iashutoshmeher, LinkedIn: @iashutoshmeher