Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added manual settlement rail #20

Merged
merged 32 commits into from
Jun 4, 2019
Merged

Conversation

countfloyd
Copy link

This PR is a result of the work I did working Corda-settler into our Cordapp.

  1. The main change is the addition of a manual settlement rail. Since the oracle can't be used to verify manual payments, I added a flow to change a payment status. Also, made the settlementOracle field optional and the oracle will not be consulted if it's null

  2. Updated deprecated flow constructors to use 4.0 constructors

  3. Fixed some warnings

  4. Updated kittinunf.fuel to latest version

I've been testing quite extensively (except for the oracle, swift and ripple modules). Still need to add some unit test for the manual module. Hope this PR isn't too big :)

Copy link

@roger-that-dev roger-that-dev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR looks great! Thanks for the contribution. Just had a few comments. Let me know if you have any questions on the comments. Cheers!


import net.corda.core.contracts.Contract
import net.corda.core.transactions.LedgerTransaction

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yah, I'm not quite sure how the jar attachment and class loader works here. I was getting errors about not finding the attachment for certain classes. I can try again without this.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tried again without the DummyContract and got:

[ERROR] 2019-05-24T17:45:40,068Z [Node thread-1] transactions.TransactionBuilder.addMissingAttachment - The transaction currently built is missing an attachment for class: com/r3/corda/finance/obligation/client/flows/MakeOffLedgerPayment.
                        Attempted to find a suitable attachment but could not find any in the storage.
                        Please contact the developer of the CorDapp for further instructions. {actor_id=admin, actor_owning_identity=CN=party2, O=Party2, L=Calgary, C=CA, actor_store_id=NODE_CONFIG, fiber-id=10000011, flow-id=659ad3e0-782d-499d-bf94-bcf82c121f1d, invocation_id=c7976465-392d-4ef9-8186-c114975b11a1, invocation_timestamp=2019-05-24T17:45:37.310Z, origin=admin, session_id=e2027977-e1bc-4464-af6a-05547df67cd8, session_timestamp=2019-05-24T17:44:14.174Z, thread-id=474}
[INFO ] 2019-05-24T17:45:40,068Z [Node thread-1] corda.flow.run - Flow raised an error: com/r3/corda/finance/obligation/client/flows/MakeOffLedgerPayment. Sending it to flow hospital to be triaged. {actor_id=admin, actor_owning_identity=CN=party2, O=Party2, L=Calgary, C=CA, actor_store_id=NODE_CONFIG, fiber-id=10000011, flow-id=659ad3e0-782d-499d-bf94-bcf82c121f1d, invocation_id=c7976465-392d-4ef9-8186-c114975b11a1, invocation_timestamp=2019-05-24T17:45:37.310Z, origin=admin, session_id=e2027977-e1bc-4464-af6a-05547df67cd8, session_timestamp=2019-05-24T17:44:14.174Z, thread-id=474}

and

java.lang.NoClassDefFoundError: com/r3/corda/finance/obligation/client/flows/MakeOffLedgerPayment
        at java.lang.ClassLoader.defineClass1(Native Method) ~[?:1.8.0_212]
        at java.lang.ClassLoader.defineClass(ClassLoader.java:763) ~[?:1.8.0_212]
        at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142) ~[?:1.8.0_212]
        at java.net.URLClassLoader.defineClass(URLClassLoader.java:468) ~[?:1.8.0_212]
        at java.net.URLClassLoader.access$100(URLClassLoader.java:74) ~[?:1.8.0_212]
        at java.net.URLClassLoader$1.run(URLClassLoader.java:369) ~[?:1.8.0_212]
        at java.net.URLClassLoader$1.run(URLClassLoader.java:363) ~[?:1.8.0_212]
        at java.security.AccessController.doPrivileged(Native Method) ~[?:1.8.0_212]
        at java.net.URLClassLoader.findClass(URLClassLoader.java:362) ~[?:1.8.0_212]
        at java.lang.ClassLoader.loadClass(ClassLoader.java:424) ~[?:1.8.0_212]
        at java.lang.ClassLoader.loadClass(ClassLoader.java:411) ~[?:1.8.0_212]
        at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ~[?:1.8.0_212]
        at java.lang.Class.forName0(Native Method) ~[?:1.8.0_212]
        at java.lang.Class.forName(Class.java:348) ~[?:1.8.0_212]
        at net.corda.serialization.internal.model.TypeIdentifier$Parameterised.getLocalType(TypeIdentifier.kt:209) ~[corda-serialization-4.1-RC03.jar:?]
        at net.corda.serialization.internal.model.TypeIdentifier$Parameterised.getLocalType(TypeIdentifier.kt:218) ~[corda-serialization-4.1-RC03.jar:?]
        at net.corda.serialization.internal.model.ClassCarpentingTypeLoader$load$noCarpentryRequired$1$1.apply(TypeLoader.kt:38) ~[corda-serialization-4.1-RC03.jar:?]
        at net.corda.serialization.internal.model.ClassCarpentingTypeLoader$load$noCarpentryRequired$1$1.apply(TypeLoader.kt:25) ~[corda-serialization-4.1-RC03.jar:?]
        at java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1660) ~[?:1.8.0_212]
        at net.corda.serialization.internal.model.ClassCarpentingTypeLoader$load$noCarpentryRequired$1.invoke(TypeLoader.kt:38) ~[corda-serialization-4.1-RC03.jar:?]
        at net.corda.serialization.internal.model.ClassCarpentingTypeLoader$load$noCarpentryRequired$1.invoke(TypeLoader.kt:25) ~[corda-serialization-4.1-RC03.jar:?]
        at kotlin.sequences.TransformingSequence$iterator$1.next(Sequences.kt:149) ~[kotlin-stdlib-1.2.71.jar:1.2.71-release-64 (1.2.71)]
        at kotlin.sequences.FilteringSequence$iterator$1.calcNext(Sequences.kt:109) ~[kotlin-stdlib-1.2.71.jar:1.2.71-release-64 (1.2.71)]
        at kotlin.sequences.FilteringSequence$iterator$1.hasNext(Sequences.kt:133) ~[kotlin-stdlib-1.2.71.jar:1.2.71-release-64 (1.2.71)]
        at kotlin.collections.MapsKt__MapsKt.putAll(Maps.kt:339) ~[kotlin-stdlib-1.2.71.jar:1.2.71-release-64 (1.2.71)]
        at kotlin.collections.MapsKt__MapsKt.toMap(Maps.kt:504) ~[kotlin-stdlib-1.2.71.jar:1.2.71-release-64 (1.2.71)]
        at kotlin.collections.MapsKt__MapsKt.toMap(Maps.kt:498) ~[kotlin-stdlib-1.2.71.jar:1.2.71-release-64 (1.2.71)]
        at net.corda.serialization.internal.model.ClassCarpentingTypeLoader.load(TypeLoader.kt:45) ~[corda-serialization-4.1-RC03.jar:?]
        at net.corda.serialization.internal.amqp.DefaultRemoteSerializerFactory.reflect(RemoteSerializerFactory.kt:130) ~[corda-serialization-4.1-RC03.jar:?]
        at net.corda.serialization.internal.amqp.DefaultRemoteSerializerFactory.access$reflect(RemoteSerializerFactory.kt:48) ~[corda-serialization-4.1-RC03.jar:?]
        at net.corda.serialization.internal.amqp.DefaultRemoteSerializerFactory$get$1.invoke(RemoteSerializerFactory.kt:72) ~[corda-serialization-4.1-RC03.jar:?]
        at net.corda.serialization.internal.amqp.DefaultRemoteSerializerFactory$get$1.invoke(RemoteSerializerFactory.kt:48) ~[corda-serialization-4.1-RC03.jar:?]
        at net.corda.serialization.internal.amqp.DefaultDescriptorBasedSerializerRegistry.getOrBuild(DescriptorBasedSerializerRegistry.kt:28) ~[corda-serialization-4.1-RC03.jar:?]
        at net.corda.serialization.internal.amqp.DefaultRemoteSerializerFactory.get(RemoteSerializerFactory.kt:67) ~[corda-serialization-4.1-RC03.jar:?]
        at net.corda.serialization.internal.amqp.ComposedSerializerFactory.get(SerializerFactory.kt) ~[corda-serialization-4.1-RC03.jar:?]
        at net.corda.serialization.internal.amqp.DeserializationInput.readObject$serialization(DeserializationInput.kt:172) ~[corda-serialization-4.1-RC03.jar:?]
        at net.corda.serialization.internal.amqp.DeserializationInput.readObjectOrNull$serialization(DeserializationInput.kt:147) ~[corda-serialization-4.1-RC03.jar:?]
        at net.corda.serialization.internal.amqp.DescribedTypeReadStrategy.readProperty(ComposableTypePropertySerializer.kt:202) ~[corda-serialization-4.1-RC03.jar:?]
        at net.corda.serialization.internal.amqp.ComposableTypePropertySerializer.readProperty(ComposableTypePropertySerializer.kt) ~[corda-serialization-4.1-RC03.jar:?]
        at net.corda.serialization.internal.amqp.ComposableObjectReader$readObject$$inlined$ifThrowsAppend$lambda$1.invoke(ObjectSerializer.kt:140) ~[corda-serialization-4.1-RC03.jar:?]
        at net.corda.serialization.internal.amqp.ComposableObjectReader$readObject$$inlined$ifThrowsAppend$lambda$1.invoke(ObjectSerializer.kt:122) ~[corda-serialization-4.1-RC03.jar:?]
        at kotlin.sequences.TransformingSequence$iterator$1.next(Sequences.kt:149) ~[kotlin-stdlib-1.2.71.jar:1.2.71-release-64 (1.2.71)]
        at net.corda.serialization.internal.amqp.ComposableObjectReader.readObject(ObjectSerializer.kt:219) ~[corda-serialization-4.1-RC03.jar:?]
        at net.corda.serialization.internal.amqp.ComposableObjectSerializer.readObject(ObjectSerializer.kt:91) ~[corda-serialization-4.1-RC03.jar:?]
        at net.corda.serialization.internal.amqp.DeserializationInput.readObject$serialization(DeserializationInput.kt:182) ~[corda-serialization-4.1-RC03.jar:?]
        at net.corda.serialization.internal.amqp.DeserializationInput.readObjectOrNull$serialization(DeserializationInput.kt:147) ~[corda-serialization-4.1-RC03.jar:?]
        at net.corda.serialization.internal.amqp.DescribedTypeReadStrategy.readProperty(ComposableTypePropertySerializer.kt:202) ~[corda-serialization-4.1-RC03.jar:?]
        at net.corda.serialization.internal.amqp.ComposableTypePropertySerializer.readProperty(ComposableTypePropertySerializer.kt) ~[corda-serialization-4.1-RC03.jar:?]
        at net.corda.serialization.internal.amqp.ComposableObjectReader$readObject$$inlined$ifThrowsAppend$lambda$1.invoke(ObjectSerializer.kt:140) ~[corda-serialization-4.1-RC03.jar:?]
        at net.corda.serialization.internal.amqp.ComposableObjectReader$readObject$$inlined$ifThrowsAppend$lambda$1.invoke(ObjectSerializer.kt:122) ~[corda-serialization-4.1-RC03.jar:?]
        at kotlin.sequences.TransformingSequence$iterator$1.next(Sequences.kt:149) ~[kotlin-stdlib-1.2.71.jar:1.2.71-release-64 (1.2.71)]
        at net.corda.serialization.internal.amqp.ComposableObjectReader.readObject(ObjectSerializer.kt:219) ~[corda-serialization-4.1-RC03.jar:?]
        at net.corda.serialization.internal.amqp.ComposableObjectSerializer.readObject(ObjectSerializer.kt:91) ~[corda-serialization-4.1-RC03.jar:?]
        at net.corda.serialization.internal.amqp.DeserializationInput.readObject$serialization(DeserializationInput.kt:182) ~[corda-serialization-4.1-RC03.jar:?]
        at net.corda.serialization.internal.amqp.DeserializationInput.readObjectOrNull$serialization(DeserializationInput.kt:147) ~[corda-serialization-4.1-RC03.jar:?]
        at net.corda.serialization.internal.amqp.DeserializationInput$deserialize$1.invoke(DeserializationInput.kt:124) ~[corda-serialization-4.1-RC03.jar:?]
        at net.corda.serialization.internal.amqp.DeserializationInput.des(DeserializationInput.kt:99) ~[corda-serialization-4.1-RC03.jar:?]
        at net.corda.serialization.internal.amqp.DeserializationInput.deserialize(DeserializationInput.kt:119) ~[corda-serialization-4.1-RC03.jar:?]
        at net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme.deserialize(AMQPSerializationScheme.kt:225) ~[corda-serialization-4.1-RC03.jar:?]
        at net.corda.serialization.internal.SerializationFactoryImpl$deserialize$1$1.invoke(SerializationScheme.kt:105) ~[corda-serialization-4.1-RC03.jar:?]
        at net.corda.core.serialization.SerializationFactory.withCurrentContext(SerializationAPI.kt:71) ~[corda-core-4.1-RC03.jar:?]
        at net.corda.serialization.internal.SerializationFactoryImpl$deserialize$1.invoke(SerializationScheme.kt:105) ~[corda-serialization-4.1-RC03.jar:?]
        at net.corda.serialization.internal.SerializationFactoryImpl$deserialize$1.invoke(SerializationScheme.kt:73) ~[corda-serialization-4.1-RC03.jar:?]
        at net.corda.core.serialization.SerializationFactory.asCurrent(SerializationAPI.kt:85) ~[corda-core-4.1-RC03.jar:?]
        at net.corda.serialization.internal.SerializationFactoryImpl.deserialize(SerializationScheme.kt:105) ~[corda-serialization-4.1-RC03.jar:?]
        at net.corda.core.internal.TransactionUtilsKt$deserialiseComponentGroup$1.invoke(TransactionUtils.kt:78) ~[corda-core-4.1-RC03.jar:?]
        at net.corda.core.internal.TransactionUtilsKt$deserialiseComponentGroup$1.invoke(TransactionUtils.kt) ~[corda-core-4.1-RC03.jar:?]
        at net.corda.core.internal.LazyMappedList.get(InternalUtils.kt:552) ~[corda-core-4.1-RC03.jar:?]
        at java.util.AbstractList$Itr.next(AbstractList.java:358) ~[?:1.8.0_212]
        at java.util.AbstractCollection.toArray(AbstractCollection.java:141) ~[?:1.8.0_212]
        at java.util.ArrayList.addAll(ArrayList.java:581) ~[?:1.8.0_212]
        at kotlin.collections.CollectionsKt___CollectionsKt.plus(_Collections.kt:1970) ~[kotlin-stdlib-1.2.71.jar:1.2.71-release-64 (1.2.71)]
        at net.corda.core.internal.Verifier.<init>(TransactionVerifierServiceInternal.kt:33) ~[corda-core-4.1-RC03.jar:?]
        at net.corda.core.transactions.LedgerTransaction$internalPrepareVerify$1.invoke(LedgerTransaction.kt:148) ~[corda-core-4.1-RC03.jar:?]
        at net.corda.core.transactions.LedgerTransaction$internalPrepareVerify$1.invoke(LedgerTransaction.kt:45) ~[corda-core-4.1-RC03.jar:?]
        at net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder$withAttachmentsClassloaderContext$1.invoke(AttachmentsClassLoader.kt:339) ~[corda-core-4.1-RC03.jar:?]
        at net.corda.core.serialization.SerializationFactory.withCurrentContext(SerializationAPI.kt:71) ~[corda-core-4.1-RC03.jar:?]
        at net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder.withAttachmentsClassloaderContext(AttachmentsClassLoader.kt:338) ~[corda-core-4.1-RC03.jar:?]
        at net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder.withAttachmentsClassloaderContext$default(AttachmentsClassLoader.kt:314) ~[corda-core-4.1-RC03.jar:?]
        at net.corda.core.transactions.LedgerTransaction.internalPrepareVerify$core(LedgerTransaction.kt:144) ~[corda-core-4.1-RC03.jar:?]
        at net.corda.core.transactions.LedgerTransaction.verify(LedgerTransaction.kt:134) ~[corda-core-4.1-RC03.jar:?]
        at net.corda.core.transactions.TransactionBuilder.addMissingDependency(TransactionBuilder.kt:186) ~[corda-core-4.1-RC03.jar:?]
        at net.corda.core.transactions.TransactionBuilder.toWireTransactionWithContext$core(TransactionBuilder.kt:166) ~[corda-core-4.1-RC03.jar:?]
        at net.corda.core.transactions.TransactionBuilder.toWireTransactionWithContext$core(TransactionBuilder.kt:169) ~[corda-core-4.1-RC03.jar:?]
        at net.corda.core.transactions.TransactionBuilder.toWireTransactionWithContext$core(TransactionBuilder.kt:169) ~[corda-core-4.1-RC03.jar:?]
        at net.corda.core.transactions.TransactionBuilder.toWireTransactionWithContext$core(TransactionBuilder.kt:169) ~[corda-core-4.1-RC03.jar:?]
        at net.corda.core.transactions.TransactionBuilder.toWireTransactionWithContext$core$default(TransactionBuilder.kt:134) ~[corda-core-4.1-RC03.jar:?]
        at net.corda.core.transactions.TransactionBuilder.toWireTransaction(TransactionBuilder.kt:131) ~[corda-core-4.1-RC03.jar:?]
        at net.corda.core.transactions.TransactionBuilder.toSignedTransaction(TransactionBuilder.kt:711) ~[corda-core-4.1-RC03.jar:?]
        at net.corda.core.node.ServiceHub$DefaultImpls.signInitialTransaction(ServiceHub.kt:232) ~[corda-core-4.1-RC03.jar:?]
        at net.corda.core.node.ServiceHub$DefaultImpls.signInitialTransaction(ServiceHub.kt:246) ~[corda-core-4.1-RC03.jar:?]
        at net.corda.node.services.api.ServiceHubInternal$DefaultImpls.signInitialTransaction(ServiceHubInternal.kt) ~[corda-node-4.1-RC03.jar:?]
        at net.corda.node.internal.AbstractNode$ServiceHubInternalImpl.signInitialTransaction(AbstractNode.kt:962) ~[corda-node-4.1-RC03.jar:?]
        at net.corda.core.node.ServiceHub$DefaultImpls.signInitialTransaction(ServiceHub.kt:268) ~[corda-core-4.1-RC03.jar:?]
        at net.corda.node.services.api.ServiceHubInternal$DefaultImpls.signInitialTransaction(ServiceHubInternal.kt) ~[corda-node-4.1-RC03.jar:?]
        at net.corda.node.internal.AbstractNode$ServiceHubInternalImpl.signInitialTransaction(AbstractNode.kt:962) ~[corda-node-4.1-RC03.jar:?]
        at com.r3.corda.finance.obligation.client.flows.UpdateSettlementMethod$Initiator.call(UpdateSettlementMethod.kt:73) ~[?:?]
        at com.r3.corda.finance.obligation.client.flows.UpdateSettlementMethod$Initiator.call(UpdateSettlementMethod.kt:22) ~[?:?]
        at net.corda.node.services.statemachine.FlowStateMachineImpl.run(FlowStateMachineImpl.kt:269) ~[corda-node-4.1-RC03.jar:?]
        at net.corda.node.services.statemachine.FlowStateMachineImpl.run(FlowStateMachineImpl.kt:45) ~[corda-node-4.1-RC03.jar:?]
        at co.paralleluniverse.fibers.Fiber.run1(Fiber.java:1092) ~[quasar-core-0.7.10-jdk8.jar:0.7.10]
        at co.paralleluniverse.fibers.Fiber.exec(Fiber.java:788) ~[quasar-core-0.7.10-jdk8.jar:0.7.10]
        at co.paralleluniverse.fibers.RunnableFiberTask.doExec(RunnableFiberTask.java:100) ~[quasar-core-0.7.10-jdk8.jar:0.7.10]
        at co.paralleluniverse.fibers.RunnableFiberTask.run(RunnableFiberTask.java:91) ~[quasar-core-0.7.10-jdk8.jar:0.7.10]
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) ~[?:1.8.0_212]
        at java.util.concurrent.FutureTask.run(FutureTask.java:266) ~[?:1.8.0_212]
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180) ~[?:1.8.0_212]
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) ~[?:1.8.0_212]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) ~[?:1.8.0_212]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) ~[?:1.8.0_212]
        at net.corda.node.utilities.AffinityExecutor$ServiceAffinityExecutor$1$thread$1.run(AffinityExecutor.kt:63) ~[corda-node-4.1-RC03.jar:?]
Caused by: java.lang.ClassNotFoundException: com.r3.corda.finance.obligation.client.flows.MakeOffLedgerPayment
        at java.net.URLClassLoader.findClass(URLClassLoader.java:382) ~[?:1.8.0_212]
        at java.lang.ClassLoader.loadClass(ClassLoader.java:424) ~[?:1.8.0_212]
        at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ~[?:1.8.0_212]
        at net.corda.core.serialization.internal.AttachmentsClassLoader.loadClass(AttachmentsClassLoader.kt:288) ~[corda-core-4.1-RC03.jar:?]
        ... 111 more

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, my bad. I forgot about this... To enable CorDapp dependencies to work, the transaction class loader searches for jars containing the needed classes but it will only search JARs with contract implementations in. Presumably we'll get to fixing this in the next point release. Cheers

@countfloyd
Copy link
Author

Not sure how I request another review. I'll just add a comment here.

@aikkailim
Copy link

Hi @countfloyd , may I ask what are you trying to do with this manual module? Is this a settlement rail you have created for yourself?

Copy link

@roger-that-dev roger-that-dev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for addressing my comments - I added a couple of more for information (no need to action any of them). The only thing I'd ask is that is it possible for you to add a test for the manual settlement mechanism and some unit tests? Other than that, I think this is good to merge. Many thanks for the contribution!

Update: Forgot to mention that the other thing which needs adding is some contract code for cancelling an obligation. Cheers!

/** Represents a manual payment. */
data class ManualPayment(
override val paymentReference: PaymentReference,
override val amount: Amount<FiatCurrency>,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to do anything, more a note for me, is that this could be generalised to enable manual delivery of any TokenType, e.g. shares.

check(ourIdentity == obligee) { "This flow can only be started by the obligee. " }
val obligor = serviceHub.identityService.requireWellKnownPartyFromAnonymous(os.obligor)
val payment = obligationState.payments.find { it.paymentReference == paymentReference }
check(payment != null) { "Could not find payment with reference '$paymentReference'" }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can use if here to get the smart cast, so no need to subsequently use unsafe dereferencing. No need to change anything though!

throw FlowException("Manual payment amount must be in FiatCurrency")
if (obligation.settlementMethod == null || obligation.settlementMethod !is ManualSettlement)
throw FlowException("settlementMethod of ManualSettlement must be provided for manual payment")
sleep(Duration.ofMillis(1))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the sleep for?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure. Left over from my copying MakeSWIFTPayment. Can remove.

if (obligation.settlementMethod == null || obligation.settlementMethod !is ManualSettlement)
throw FlowException("settlementMethod of ManualSettlement must be provided for manual payment")
sleep(Duration.ofMillis(1))
return ManualPayment((obligation.payments.size + 1).toString(), amount as Amount<FiatCurrency>, PaymentStatus.SENT) as Payment<T>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The payment reference is just an int for now ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just used something to make a unique identifier for referencing later when payments are updated. Not sure what it's used for. Didn't see a way to set it with user input in the flow.

@InitiatedBy(Initiator::class)
class Responder(val otherFlow: FlowSession) : FlowLogic<WireTransaction>() {
@Suspendable
override fun call(): WireTransaction {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I usually return the signed transaction here, so you have the signatures, if you need them.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, here i think i returned wire transaction so that it was rendered in the shell (which was a bit of a hack) :)

@roger-that-dev
Copy link

@yikailim93 I think manual settlement is quite useful if you need to settle something off ledger but the rail isn't yet supported. If you trust your counterpart, you can compromise some automation and assurance to handle settlements. Cheers

@countfloyd
Copy link
Author

@yikailim93 Yes we created manual settlement because we need it for our client who do off ledger settlements by writing checks, etc. The obligee can manually verify the payment was received. At the moment, we have no way of automatically verifying a payment was made.

@countfloyd
Copy link
Author

Thanks for addressing my comments - I added a couple of more for information (no need to action any of them). The only thing I'd ask is that is it possible for you to add a test for the manual settlement mechanism and some unit tests? Other than that, I think this is good to merge. Many thanks for the contribution!

Update: Forgot to mention that the other thing which needs adding is some contract code for cancelling an obligation. Cheers!

There's already contract verification code for cancelling an obligation, or did you mean something else?

@aikkailim
Copy link

Thank you both for the reply.

@yikailim93 Yes we created manual settlement because we need it for our client who do off ledger settlements by writing checks, etc. The obligee can manually verify the payment was received. At the moment, we have no way of automatically verifying a payment was made.

@countfloyd is your CorDapp currently up and running? And if I may ask, did you have to make a lot of changes to the cordapp/cordapp-contracts-states/oracle modules (or any other module) in order to integrate your manual settlement module? Also, are the source files in your manual settlement module the only/main files you have created to achieve your objective? Asking all these because I'm currently trying to develop my own settler as well but is facing a lot of roadblocks. Would appreciate if you could shed some light on this corda-settler.

@roger-that-dev
Copy link

There's already contract verification code for cancelling an obligation, or did you mean something else?

Sorry, my bad - I think I even added it. Doh!

@countfloyd
Copy link
Author

@yikailim93 Yes our app is running in private test but not in production. All I really did was add my own ManualSettlement class and ManualPayment as well as a MakeManualPayment flow. I don't use the oracle at all because we have no way to automatically verifying that a payment was made. Instead, I made a UpdatePaymentStatusManually flow so that the obligee could verify that the payment was received. The manual module in Corda-settler was all I added to make obligations work for me, yes.

Curtis Stanford added 2 commits May 29, 2019 14:44
…, and other small changes suggested by Roger
…, and other small changes suggested by Roger
@countfloyd
Copy link
Author

New changes for review:

  1. Added tests for manual module
  2. Fixed checkInvariantProperties in ObligationContract to throw exception. It was returning a Boolean that wasn't being used for anything.
  3. Added new checks in ObligationContract for handleUpdatePayment
  4. Couple of small changes suggested by @roger3cev

@aikkailim
Copy link

aikkailim commented May 30, 2019

@yikailim93 Yes our app is running in private test but not in production. All I really did was add my own ManualSettlement class and ManualPayment as well as a MakeManualPayment flow. I don't use the oracle at all because we have no way to automatically verifying that a payment was made. Instead, I made a UpdatePaymentStatusManually flow so that the obligee could verify that the payment was received. The manual module in Corda-settler was all I added to make obligations work for me, yes.

Thank you @countfloyd! This information is very helpful. May I ask if you can share the commands you ran on the nodes' terminal for the process from creating an obligation up until settlement? This will solely be for demonstration purposes.

Also, if I may add on, I was trying to run the corda-settler repo that you have uploaded on your profile. I encountered the error below when running gradlew clean deployNodes, do you have any idea what went wrong?

C:\Users\aikka\Desktop\corda-settler-manual>gradlew --stacktrace clean deployNodes
> Task :cordapp-contracts-states:clean FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':cordapp-contracts-states:clean'.
> Unable to delete file: C:\Users\aikka\Desktop\corda-settler-manual\cordapp-contracts-states\build\libs\cordapp-contracts-states-0.1.jar

* Try:
Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Exception is:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':cordapp-contracts-states:clean'.
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:110)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:77)
        at org.gradle.api.internal.tasks.execution.OutputDirectoryCreatingTaskExecuter.execute(OutputDirectoryCreatingTaskExecuter.java:51)
        at org.gradle.api.internal.tasks.execution.SkipUpToDateTaskExecuter.execute(SkipUpToDateTaskExecuter.java:59)
        at org.gradle.api.internal.tasks.execution.ResolveTaskOutputCachingStateExecuter.execute(ResolveTaskOutputCachingStateExecuter.java:54)
        at org.gradle.api.internal.tasks.execution.ValidatingTaskExecuter.execute(ValidatingTaskExecuter.java:59)
        at org.gradle.api.internal.tasks.execution.SkipEmptySourceFilesTaskExecuter.execute(SkipEmptySourceFilesTaskExecuter.java:101)
        at org.gradle.api.internal.tasks.execution.FinalizeInputFilePropertiesTaskExecuter.execute(FinalizeInputFilePropertiesTaskExecuter.java:44)
        at org.gradle.api.internal.tasks.execution.CleanupStaleOutputsExecuter.execute(CleanupStaleOutputsExecuter.java:91)
        at org.gradle.api.internal.tasks.execution.ResolveTaskArtifactStateTaskExecuter.execute(ResolveTaskArtifactStateTaskExecuter.java:62)
        at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:59)
        at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:54)
        at org.gradle.api.internal.tasks.execution.ExecuteAtMostOnceTaskExecuter.execute(ExecuteAtMostOnceTaskExecuter.java:43)
        at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:34)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.run(EventFiringTaskExecuter.java:51)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:301)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:293)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:175)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:91)
        at org.gradle.internal.operations.DelegatingBuildOperationExecutor.run(DelegatingBuildOperationExecutor.java:31)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:46)
        at org.gradle.execution.taskgraph.LocalTaskInfoExecutor.execute(LocalTaskInfoExecutor.java:42)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareWorkItemExecutor.execute(DefaultTaskExecutionGraph.java:277)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareWorkItemExecutor.execute(DefaultTaskExecutionGraph.java:262)
        at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$ExecutorWorker$1.execute(DefaultTaskPlanExecutor.java:135)
        at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$ExecutorWorker$1.execute(DefaultTaskPlanExecutor.java:130)
        at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$ExecutorWorker.execute(DefaultTaskPlanExecutor.java:200)
        at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$ExecutorWorker.executeWithWork(DefaultTaskPlanExecutor.java:191)
        at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$ExecutorWorker.run(DefaultTaskPlanExecutor.java:130)
        at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:63)
        at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:46)
        at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:55)
Caused by: org.gradle.api.file.UnableToDeleteFileException: Unable to delete file: C:\Users\aikka\Desktop\corda-settler-manual\cordapp-contracts-states\build\libs\cordapp-contracts-states-0.1.jar
        at org.gradle.api.internal.file.delete.Deleter.handleFailedDelete(Deleter.java:109)
        at org.gradle.api.internal.file.delete.Deleter.doDeleteInternal(Deleter.java:86)
        at org.gradle.api.internal.file.delete.Deleter.doDeleteInternal(Deleter.java:81)
        at org.gradle.api.internal.file.delete.Deleter.doDeleteInternal(Deleter.java:81)
        at org.gradle.api.internal.file.delete.Deleter.delete(Deleter.java:66)
        at org.gradle.api.tasks.Delete.clean(Delete.java:67)
        at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:73)
        at org.gradle.api.internal.project.taskfactory.StandardTaskAction.doExecute(StandardTaskAction.java:46)
        at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:39)
        at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:26)
        at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:801)
        at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:768)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$1.run(ExecuteActionsTaskExecuter.java:131)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:301)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:293)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:175)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:91)
        at org.gradle.internal.operations.DelegatingBuildOperationExecutor.run(DelegatingBuildOperationExecutor.java:31)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeAction(ExecuteActionsTaskExecuter.java:120)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:99)
        ... 31 more

* Get more help at https://help.gradle.org

BUILD FAILED in 2s
3 actionable tasks: 3 executed

PS: I'm really sorry to keep bothering you here, I've been trying to develop my own settler for the past 2 months but kept getting errors and I'm still no where near completing my cordapp. Really hope that you wouldn't mind me asking all the questions.

@roger-that-dev
Copy link

roger-that-dev commented May 30, 2019

@countfloyd I've tried to run the tests locally but none of them pass:

  1. The gradle plugins version was updated but we need to disable sealing for all the cordapps because it doesn't work with the way the packages are currently structured.

cordapp { sealing { enabled false } }

  1. it seems the create obligation flow doesn't work - sends and receives are not matched up. Need to add subFlow(SwapIdentitiesFlow(otherFlow)) in the responder flow as the first sub flow, to match up the sends and receives. The new swap identities flow in Corda 4 is not an initiating flow, so you need to add the responder flow.

  2. Also, the FinalityFlow in SendToSettlementOracle needs a responder. This will be more tricky than you think to implement though.

Cheers

@roger-that-dev
Copy link

@yikailim93 can you please stop asking support questions in this PR? Either go on to slack and ask a question, use stack over flow or submit a github issue. Me or others will be happy to help you in those places. Cheers

@aikkailim
Copy link

@roger3cev okay, sorry for the inconvenience caused.

@roger-that-dev
Copy link

@yikailim93 no worries - is also better for you to ask questions in those places. There's more people to help :)

@countfloyd
Copy link
Author

@roger3cev Hmmm weird. The tests passed for me. Although I only ran them from my IDE. I'll check it out along with your other issues. Won't be able to work on it until next week. Thanks for your review.

@countfloyd
Copy link
Author

@roger3cev, addressed the issues you outlined. Just wondering what I need to run the SWIFT unit tests. README says I need to email you for API key and I need a swift.pem file? Can you email me what I need?

XRP tests all run successfully now.

Thanks.

@roger-that-dev
Copy link

@countfloyd dont worry about the SWIFT tests - I'll sort those out. Cheers!

@roger-that-dev
Copy link

This looks great - good to merge!

@roger-that-dev roger-that-dev merged commit 067d933 into corda:master Jun 4, 2019
@roger-that-dev
Copy link

Thanks for the contribution @countfloyd !

@mikehearn
Copy link
Contributor

Yes, thanks @countfloyd - great work and much appreciated by all.

@countfloyd
Copy link
Author

My pleasure, thanks for the work reviewing this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants