How to write a Base contract extendable by other contracts in Corda


abdulqawiy.ayandiran@...
 

I need to build a cordapp with reusable/extendable contract (interface)

I have decided to build a re-usable Cordapp platform that will serve the need of multiple co-operates with minimal updates (per co-operate) when necessary.

I plan to achieve this by creating base classes for the States and Contracts which shall contain every possible common properties for each kind of Payment Instrument and will be extended by other regular States and Contracts. As for the flows, I can have all basic generic flows ready. Customized flows can then be written upon requirement and this (hopefully) would only be minimal additions to be made upfront.

In order to achieve the above plans, I recently tried it out with a test Cordapp.

For States, I observed that I could possibly have a base (Interface) State with all possible common parameters (for a particular Payment Instrument), which can then be extended by other regular States as planned.

For Contracts, I had a bit of challenge.

While having an Interface Contract IXContract being extended (by an open class) XContract, I got a transaction error “Contract verification failed: Required com.template.contracts.IXContract.Commands command, contract: com.template.contracts.XContract”. This seem to be caused by a conflict in contracts being used by a single XState.

I also modified the code to having IXContract as an open class and and XContract as abstract class. This returned a successful transaction, however the invalid rule in the IXContract was not caught and this is not the expected behavior of the project.

To avoid contract conflicts, I tried the usual programming style of simple classes. With base class IXContract, this time not extending the Contract interface (which makes it not a contract class), but contains a BaseContract method with contract rules. I then autowired this class in the regular XContract class (still extending the contract interface) and called the BaseContract method to apply the contract rule within it. This was tested successfully and the contract rules were caught as required. For more assurance, the flow was adjusted to align with the contract rules and the test transaction was then successful.

I however noted that this would obviously lead to us having a huge file of BaseContract class in future, but these have been our test trials so far.

This post contains attachment of the logs from the various results observed as explained in above.

 

The file ‘error.log’ is the result of a failed transaction due to conflict in double contracts (XContract and IXContract) used by a single state,

 

The file ‘expected_error’ is the result of a failed transaction when the IXContract file was declared as a (non-contract) class with methods containing a (deliberately) wrong contract rule.

 

The file ‘expected_success’ is the result of a successful transaction when the IXContract file was declared as a (non-contract) class with methods containing the correct contract rule.

Is this a good route towards implementing a reliable project with no possible risk upfront?

Kindly advice on a best way to achieve it if you can already foresee a disadvantage in our test trials.


Mike Hearn
 

Great question!

There are a few things to be aware of:

  1. Corda's support for "on ledger libraries" as we call them is not 100% complete. Tudor Malene is working on this and can comment further. Be aware that for now your code will end up fat-jarred/statically linked into the apps that use it, and this will mean you can't easily combine different apps that use the same base types together. So your potential for truly generic code will be somewhat limited.

    This matters to us a lot and we've put a lot of infrastructure in place over the years to allow this scenario, but we're not 100% there yet. Make sure you understand the limitations.

  2. You mention "autowiring". Corda doesn't have any kind of DI framework and doesn't use anything like Spring out of the box. Even if you add it, and it works today, it won't work in the DJVM in future. That will be an opt-in change but DJVM compatibility will be important for scaling to larger networks and more apps, so please bear that in mind.

  3. Creating an abstract base class then overriding it and using supercalls should work. This is all just normal OOP. I'm not sure why it didn't for you.

    The model is simple enough - a state specifies which contract class it "belongs to". If not explicitly annotated this is implied to be the outer class, if compatible. That contract class must implement the Contract interface and have the verify method as a result, but can otherwise be any normal class that can be constructed by the platform with a no-args constructor. Therefore your contract cannot be abstract. That won't work. Perhaps we have a misleading error message in this case.


Suhas Chatekar
 

Hi Mike,

Sorry for hijacking this thread but I wanted to understand your second comment better where you are saying that Spring will not work with DJVM in future. We are currently considering swapping out Jetty for Spring as a long term stable solution. It would be good to know if we should not do that. Also, in that case, what are our options? 

Regards,
Suhas Chatekar
Skype - suhas.chatekar


On Thu, Jul 25, 2019 at 12:23 PM Mike Hearn via Groups.Io <mike=r3.com@groups.io> wrote:
Great question!

There are a few things to be aware of:

  1. Corda's support for "on ledger libraries" as we call them is not 100% complete. Tudor Malene is working on this and can comment further. Be aware that for now your code will end up fat-jarred/statically linked into the apps that use it, and this will mean you can't easily combine different apps that use the same base types together. So your potential for truly generic code will be somewhat limited.

    This matters to us a lot and we've put a lot of infrastructure in place over the years to allow this scenario, but we're not 100% there yet. Make sure you understand the limitations.

  2. You mention "autowiring". Corda doesn't have any kind of DI framework and doesn't use anything like Spring out of the box. Even if you add it, and it works today, it won't work in the DJVM in future. That will be an opt-in change but DJVM compatibility will be important for scaling to larger networks and more apps, so please bear that in mind.

  3. Creating an abstract base class then overriding it and using supercalls should work. This is all just normal OOP. I'm not sure why it didn't for you.

    The model is simple enough - a state specifies which contract class it "belongs to". If not explicitly annotated this is implied to be the outer class, if compatible. That contract class must implement the Contract interface and have the verify method as a result, but can otherwise be any normal class that can be constructed by the platform with a no-args constructor. Therefore your contract cannot be abstract. That won't work. Perhaps we have a misleading error message in this case.


Mike Hearn
 

Sorry for the confusion.

To be clear I'm only talking about smart contract / flow logic here. Code outside the node that uses RPC to connect to it can run whatever you like. So Spring is perfectly usable for web servers that talk to the node.


abdulqawiy.ayandiran@...
 
Edited

@Mike:

1) Here is my code for the IXContract class which was abstract

abstract class IXContract : Contract {

// A transaction is valid if the verify() function of the contract of all the transaction's input and output states
// does not throw an exception.
override fun verify(tx: LedgerTransaction) {
// Verification logic goes here.
val command = tx.commands.requireSingleCommand<Commands>()
val signers = command.signers.toSet()

when (command.value) {
is Commands.TestXState -> testXState(tx, signers)
else -> throw IllegalArgumentException("Unrecognised command.")
}
}

private fun testXState(tx: LedgerTransaction, signers: Set<PublicKey>) = requireThat {
"Transaction should have 1 inputs" using (tx.inputStates.size == 1)
}

// Used to indicate the transaction's intent.
interface Commands : CommandData {
class TestXState : Commands
}
}

and the regular contract is

open class XContract : IXContract() {
companion object {
// Used to identify our contract when building a transaction.
const val ID = "com.interswitch.contracts.XContract"
}

override fun verify(tx: LedgerTransaction) {
super.verify(tx)

//custom verification
val command = tx.commands.requireSingleCommand<Commands>()
val signers = command.signers.toSet()

when (command.value) {
is Commands.TestBaseXState -> testBaseXState(tx, signers)
else -> throw IllegalArgumentException("Unrecognised command.")
}
}

private fun testBaseXState(tx: LedgerTransaction, signers: Set<PublicKey>) = requireThat {
"Transaction should have 1 output" using (tx.outputStates.size==1)
}

interface Commands : CommandData {
class TestBaseXState : Commands
}

}
The error occurs at the super.verify(tx)

I must be making a mistake some where, Kindly assist.

2) As for the number 1 point above. We plan to have different cordapps sharing that base contract. That point I think implies that it is not possible and is better avoided. Am I correct?

3) As for the Autowiring, Sorry I used that term. I didn't use it the spring way per-say. Its like IXContract().testBaseXState(tx, signers)  while IXContract is just a class not extending Contract, so its just calling method testBaseXState method with the class.


Stefano Franz
 

I suspect there might be an issue with the attachments to the transaction. In corda4 you must attach both the jar providing the super class and the actual contract. The actual contract will be automatically attached, but the other will not.

The easiest way to do this, would be to locate the jar which provides the super contract, and then perform a sha256 on it. We do this in TokensSDK to allow people to define new TokenTypes, the code is here.

Now be warned, that this adds a new complexity - Corda will ensure that the Contract for the state is constrained (IE, cannot change between input and output states of same type) - but the super class providing jar is not automatically pinned (as it has no relationship to an input/output state). You would need to pin this manually,  we do this in Tokens here



From: corda-dev@groups.io <corda-dev@groups.io> on behalf of abdulqawiy.ayandiran via Groups.Io <abdulqawiy.ayandiran@...>
Sent: 25 July 2019 14:55
To: corda-dev@groups.io <corda-dev@groups.io>
Subject: Re: [corda-dev] How to write a Base contract extendable by other contracts in Corda
 

[Edited Message Follows]

@Mike:

1) Here is my code for the IXContract class which was abstract

abstract class IXContract : Contract {

// A transaction is valid if the verify() function of the contract of all the transaction's input and output states
// does not throw an exception.
override fun verify(tx: LedgerTransaction) {
// Verification logic goes here.
val command = tx.commands.requireSingleCommand<Commands>()
val signers = command.signers.toSet()

when (command.value) {
is Commands.TestXState -> testXState(tx, signers)
else -> throw IllegalArgumentException("Unrecognised command.")
}
}

private fun testXState(tx: LedgerTransaction, signers: Set<PublicKey>) = requireThat {
"Transaction should have 1 inputs" using (tx.inputStates.size == 1)
}

// Used to indicate the transaction's intent.
interface Commands : CommandData {
class TestXState : Commands
}
}

and the regular contract is

open class XContract : IXContract() {
companion object {
// Used to identify our contract when building a transaction.
const val ID = "com.interswitch.contracts.XContract"
}

override fun verify(tx: LedgerTransaction) {
super.verify(tx)

//custom verification
val command = tx.commands.requireSingleCommand<Commands>()
val signers = command.signers.toSet()

when (command.value) {
is Commands.TestBaseXState -> testBaseXState(tx, signers)
else -> throw IllegalArgumentException("Unrecognised command.")
}
}

private fun testBaseXState(tx: LedgerTransaction, signers: Set<PublicKey>) = requireThat {
"Transaction should have 1 output" using (tx.outputStates.size==1)
}

interface Commands : CommandData {
class TestBaseXState : Commands
}

}
The error occurs at the super.verify(tx)

I must be making a mistake some where, Kindly assist.

2) As for the number 1 point above. We plan to have different cordapps sharing that base contract. That point I think implies that it is not possible and is better avoided. Am I correct?

3) As for the Autowiring, Sorry I used that term. I didn't use it the spring way per-say. Its like IXContract().testBaseXState(tx, signers)  while IXContract is just a class not extending Contract, so its just calling method testBaseXState method with the class.


abdulqawiy.ayandiran@...
 

Thanks Stefano, but I don't get this "I suspect there might be an issue with the attachments to the transaction. In corda4 you must attach both the jar providing the super class and the actual contract. The actual contract will be automatically attached, but the other will not. ".

To test, I deploy so I can have a new node build entirely (with 'gradle clean deployNodes'), which I expect to have attached everything necessary before doing my 'runnodes' within the build path.

So after all of this, the abstract contract could have been excluded?