Responder flow validation (part 3 - overriding)

January 06, 2020

cordakotlindltdistributed ledger technologyblockchain

To finish off my trilogy of blog posts on including validation inside your responder flows, I present to you the final topic. How to override a responder flow with your own custom implementation. You will need to go down this route if you are leveraging an external CorDapp that does not allow you to extend their flows. It is not ideal, as you will not be able to make use of any of the responder code that was written. But, at least it will provide you with a way to implement your requirements while still allowing your version to communicate to the paired initiating flow. To cement what I am saying, I will say it one more time, you should only override if you cannot extend.

Before I get to the good stuff and show you what you need to do, if you really do need to go down this route, you should send an angry email to whoever wrote the CorDapp you are utilising. Moreover, you can tell them to read Responder flow validation (part 2 - extension) with some passive-aggressive message to go along with it 😎.

The content of this post is really a rehashing on what I already wrote in Extending and Overriding Flows from external CorDapps, except that I will focus precisely on overriding a responder flow.

When to override

So what sort of flow will you need to override?

Below is a flow that cannot be extended:

@InitiatedBy(SendMessageFlow::class)
class SendMessageResponder(private val session: FlowSession) : FlowLogic<SignedTransaction>() {

  @Suspendable
  override fun call(): SignedTransaction {
    val stx = subFlow(object : SignTransactionFlow(session) {
      override fun checkTransaction(stx: SignedTransaction) {
      }
    })
    return subFlow(ReceiveFinalityFlow(otherSideSession = session, expectedTxId = stx.id))
  }
}

As this flow is written in Kotlin, the class is final and cannot be extended. In Java, the class would need to be marked as final explicitly to prevent you from extending it. In other words, all language rules around extending classes apply here and nothing else. If you tried to write a new flow that extended the one above, you would find a few compiler errors blocking your way.

Since this flow cannot be extended, the only solution is overriding it (or not using it at all).

How to override

To override the flow, you must do the following:

  • Write your own flow from scratch
  • Include @InitiatedBy and reference the initiating flow that your implementation should respond to
  • Add the flowOverrides configuration to your node.conf

Once these steps are taken, the node will start routing all calls to the original responder flow to your custom implementation.

Let’s look into each point a bit further:

  • Write your own flow from scratch

    • This could look like:
    @InitiatedBy(SendMessageFlow::class)
    class ValidatingSendMessageResponder(private val session: FlowSession) : FlowLogic<SignedTransaction>() {
    
      @Suspendable
      override fun call(): SignedTransaction {
        val stx = subFlow(object : SignTransactionFlow(session) {
          override fun checkTransaction(stx: SignedTransaction) {
            val message = stx.coreTransaction.outputStates.single() as MessageState
            check(message.recipient == ourIdentity) { "I think you got the wrong person" }
            check(!message.contents.containsSwearWords()) { "Mind your language" }
            check(!message.contents.containsMemes()) { "Only serious messages are accepted" }
            check(message.sender.name.organisation != "Nigerian Prince") { "Spam message detected" }
          }
        })
        return subFlow(ReceiveFinalityFlow(otherSideSession = session, expectedTxId = stx.id))
      }
    }

Note that there is no mention of the original responder flow

  • Include @InitiatedBy and reference the initiating flow that your implementation should respond to

    • This was shown in the code block above:
    @InitiatedBy(SendMessageFlow::class)
    class ValidatingSendMessageResponder(private val session: FlowSession) : FlowLogic<SignedTransaction>() {
  • Add the flowOverrides configuration to your node.conf

    • Add the following to your node.conf:
    flowOverrides {
      overrides=[
        {
          initiator="dev.lankydan.tutorial.flows.SendMessageFlow"
          responder="dev.lankydan.tutorial.flows.ValidatingSendMessageResponder"
        }
      ]
    }

    This links SendMessnageFlow to ValidatingSendMessageResponder instead of the original SendMessageResponder

    • You can also add this configuration to the deployNodes build task:
    node {
      name "O=PartyB,L=London,C=GB"
      p2pPort 10003
      flowOverride(
        "dev.lankydan.tutorial.flows.SendMessageFlow",
        "dev.lankydan.tutorial.flows.ValidatingSendMessageResponder"
      )
    }

When all these steps have been made, you should see a similar line in your node’s log file:

[INFO ] 2020-01-05T10:07:03,093Z [main] internal.NodeFlowManager. - Registered dev.lankydan.tutorial.flows.SendMessageFlow
 to initiate dev.lankydan.tutorial.flows.ValidatingSendMessageResponder (version 1)

This shows that the flow has been successfully switched out for the overridden version.

Forgetting to make step 2 or 3 will lead to errors.

  • Forgetting to add the @InitiatedBy annotation will cause the node to keep using the original flow

  • Forgetting to include the flowOverrides configuration will lead to an error due to multiple flows with the same @InitiatedBy annotation:

    [ERROR] 2020-01-05T10:07:35,276Z [main] internal.NodeStartupLogging. - Exception during node startup: Unable to determine which flow to use when responding to:
    dev.lankydan.tutorial.flows.SendMessageFlow. [dev.lankydan.tutorial.flows.SendMessageResponder, dev.lankydan.tutorial.flows.ValidatingSendMessageResponder] are all
    registered with equal weight.

Conclusion

That brings us to the end of this trilogy of blog posts on responder flow validation.

You should now have a good understanding of why you should include validation in your responder flows, how to write extendable flows for other developers to leverage and how to override a flow if can’t be extended.

If you enjoyed this post or found it helpful (or both) then please feel free to follow me on Twitter at @LankyDanDev and remember to share with anyone else who might find this useful!



Responder flow validation (part 2 - extension)

December 10, 2019
cordakotlindltdistributed ledger technologyblockchain

In Responder flow validation I closed the post with the following: How can I enforce the rules of my business when the logic inside a…

Responder flow validation

November 25, 2019
cordakotlindltdistributed ledger technologyblockchain

Do you want to prevent your node from accepting transactions that are not important to your business? Do you want to prevent your node from…

Common CorDapp mistakes and how to fix them

November 23, 2019
cordadltdistributed ledger technologyblockchainpresentationconference talk

This is the talk I gave at CordaCon in October. Title: A tale of Corda Slack adventures - Common CorDapp mistakes and how to fix them The…

Sometimes, it's better to not do your work

November 20, 2019
workculturehelpingteamwork

It’s 11 o’clock and I am finally doing my own work… I typically say this line to my colleague almost every day. I used to mean it (albeit…

Augmenting a Spring Data repository through delegation

September 14, 2019
springspring datakotlinjavar2dbcspring data r2dbcreactivereactive streamsspring boot

I have recently written several posts about Kotlin’s delegation. In doing so, I realised a useful way to apply it to Spring Data…