0
votes

I have a scenario where I have upgraded my obligations from V1 to V2 and pointed to the correct V2 contracts successfully. Next I tried to do an SettleObligation on these upgraded V2 states. When the transaction is formed and sent out in CollectSignatureFlow, there's a java.lang.NoSuchMethodError found on my isGreaterThan method which is called in the verifySettle command in the contract.

This particular function initially exist on the package "com.example.base" in a file BaseHelper.kt, along the way, in V2, we migrated the function to another file MathHelper.kt, this move did not cause any unresolved references because the package is the same.

Would like to understand:

  1. Does the contract reference the function location by file name instead of package name? and it's unchangeable once you compile the contract-v1.jar?
  2. Why do V2 states still try to deserialise V1 contracts? Is this being done via walking the chain somehow?

Stack trace below

[WARN ] 2018-11-13T00:05:12,777Z [Node thread-1] flow.[cd538d42-1715-4ed3-bde6-38eca94ef79f].run - Flow ended due to receiving exception {}
net.corda.core.contracts.TransactionVerificationException$ContractRejection: Contract verification failed: com.example.base.BaseHelperKt.isGreaterThan(Ljava/math/BigDecimal;Ljava/math/BigDecimal;)Z, 
contract: com.example.contracts.ObligationContractV1, transaction: 8B8780A16D330A93A361F747B77C227442BD310C9DAAA561376DED69F580C794
        at net.corda.node.services.statemachine.FlowStateMachineImpl.erroredEnd(FlowStateMachineImpl.kt:497) ~[corda-node-3.2.1847-corda.jar:?]
        at net.corda.node.services.statemachine.FlowStateMachineImpl.confirmNoError(FlowStateMachineImpl.kt:481) ~[corda-node-3.2.1847-corda.jar:?]
        at net.corda.node.services.statemachine.FlowStateMachineImpl.waitForMessage(FlowStateMachineImpl.kt:444) ~[corda-node-3.2.1847-corda.jar:?]
        at net.corda.node.services.statemachine.FlowStateMachineImpl.receiveInternal(FlowStateMachineImpl.kt:376) ~[corda-node-3.2.1847-corda.jar:?]
        at net.corda.node.services.statemachine.FlowStateMachineImpl.receive(FlowStateMachineImpl.kt:229) ~[corda-node-3.2.1847-corda.jar:?]
        at net.corda.node.services.statemachine.FlowSessionImpl.receive(FlowSessionImpl.kt:44) ~[corda-node-3.2.1847-corda.jar:?]
        at net.corda.node.services.statemachine.FlowSessionImpl.receive(FlowSessionImpl.kt:48) ~[corda-node-3.2.1847-corda.jar:?]
        at net.corda.core.flows.CollectSignatureFlow.call(CollectSignaturesFlow.kt:290) ~[corda-core-3.2.1847-corda.jar:?]
        at net.corda.core.flows.CollectSignatureFlow.call(CollectSignaturesFlow.kt:135) ~[corda-core-3.2.1847-corda.jar:?]
        at net.corda.core.flows.FlowLogic.subFlow(FlowLogic.kt:290) ~[corda-core-3.2.1847-corda.jar:?]
        at net.corda.core.flows.CollectSignaturesFlow.call(CollectSignaturesFlow.kt:114) ~[corda-core-3.2.1847-corda.jar:?]
        at net.corda.core.flows.CollectSignaturesFlow.call(CollectSignaturesFlow.kt:64) ~[corda-core-3.2.1847-corda.jar:?]
        at net.corda.core.flows.FlowLogic.subFlow(FlowLogic.kt:290) ~[corda-core-3.2.1847-corda.jar:?]
        at com.example.flows.flows.SettleObligation$Initiator.collectSignature(SettleObligation.kt:178) ~[obligation-1.0.jar:?]
        at com.example.flows.flows.SettleObligation$Initiator.call(SettleObligation.kt:87) ~[obligation-1.0.jar:?]
        at com.example.flows.flows.SettleObligation$Initiator.call(SettleObligation.kt:51) ~[obligation-1.0.jar:?]
        at net.corda.node.services.statemachine.FlowStateMachineImpl.run(FlowStateMachineImpl.kt:96) [corda-node-3.2.1847-corda.jar:?]
        at net.corda.node.services.statemachine.FlowStateMachineImpl.run(FlowStateMachineImpl.kt:44) [corda-node-3.2.1847-corda.jar:?]
        at co.paralleluniverse.fibers.Fiber.run1(Fiber.java:1092) [quasar-core-0.7.9-jdk8.jar:0.7.9]
        at co.paralleluniverse.fibers.Fiber.exec(Fiber.java:788) [quasar-core-0.7.9-jdk8.jar:0.7.9]
        at co.paralleluniverse.fibers.RunnableFiberTask.doExec(RunnableFiberTask.java:100) [quasar-core-0.7.9-jdk8.jar:0.7.9]
        at co.paralleluniverse.fibers.RunnableFiberTask.run(RunnableFiberTask.java:91) [quasar-core-0.7.9-jdk8.jar:0.7.9]
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [?:1.8.0_181]
        at java.util.concurrent.FutureTask.run(FutureTask.java:266) [?:1.8.0_181]
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180) [?:1.8.0_181]
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) [?:1.8.0_181]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [?:1.8.0_181]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [?:1.8.0_181]
        at net.corda.node.utilities.AffinityExecutor$ServiceAffinityExecutor$1$thread$1.run(AffinityExecutor.kt:62) [corda-node-3.2.1847-corda.jar:?]
Caused by: net.corda.core.CordaRuntimeException: java.lang.NoSuchMethodError: com.example.base.BaseHelperKt.isGreaterThan(Ljava/math/BigDecimal;Ljava/math/BigDecimal;)Z
[INFO ] 2018-11-13T00:05:12,807Z [RxIoScheduler-2] network.PersistentNetworkMapCache.addNode - Previous node was identical to incoming one - doing nothing {}
[INFO ] 2018-11-13T00:05:12,807Z [RxIoScheduler-2] network.PersistentNetworkMapCache.addNode - Done adding node with info: NodeInfo
2

2 Answers

1
votes

In Kotlin, each file is compiled to a JVM-level class. A file named Foobar.kt will become a class named FoobarKt.

When you move a top level function from one file to another, therefore, the name of the file is baked into the compiled code and such a change is not binary compatible even though it's source compatible.

Unfortunately this is one of the hidden complexities of all software development - the way the source compiler resolves symbol scopes isn't always identical to the way the runtime linker does. It happens in every language and runtime, although there are ways for the JetBrains guys to fix it in the very long run, if they choose to do so. Sorry. You'll have to move the function back, or provide an alias (look at the @JvmName annotation).

1
votes

The resolveTransactionFlow.kt (when walking down the chain) will loop in sequence to fetch the Tx while verifying it. In that case one would need all the old contract codes (as a cordapp) to be present, else you won't be able to resolve the provenance of the chain.

val result = topologicalSort(newTxns)
    result.forEach {
        // For each transaction, verify it and insert it into the database. As we are iterating over them in a
        // depth-first order, we should not encounter any verification failures due to missing data. If we fail
        // half way through, it's no big deal, although it might result in us attempting to re-download data
        // redundantly next time we attempt verification.
        it.verify(serviceHub)
        serviceHub.recordTransactions(StatesToRecord.NONE, listOf(it))
    }