corda / corda-settler

Corda Off-ledger settlement for all the things
Other
61 stars 36 forks source link

CreateObligation fails with constructor #21

Open TAC911 opened 5 years ago

TAC911 commented 5 years ago

Calling the CreateObligation flow as described in the example leads to an error.

Command: start CreateObligation amount: { quantity: 1000, token: { currencyCode: USD, type: fiat } }, role: OBLIGOR, counterparty: PartyB, dueBy: 1543922400, anonymous: false

Error:

No matching constructor found:
- [net.corda.core.contracts.Amount<T>, com.r3.corda.finance.obligation.client.flows.CreateObligation$InitiatorRole, net.corda.core.identity.Party, java.time.Instant, boolean]: Could not parse as a command: Cannot construct instance of `com.r3.corda.sdk.token.contracts.types.TokenType` (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
 at [Source: UNKNOWN; line: -1, column: -1] (through reference chain: net.corda.core.contracts.Amount["token"])
- [net.corda.core.contracts.Amount, com.r3.corda.finance.obligation.client.flows.CreateObligation$InitiatorRole, net.corda.core.identity.Party, java.time.Instant, boolean, int, kotlin.jvm.internal.DefaultConstructorMarker]: <constructor missing parameter reflection data>

Cheers, TAC

roger-that-dev commented 5 years ago

Thanks for the heads up. It's because I changed the constructor signature (added a type parameter) and the node shell can't deserialise a string to the type. I'll add a helper flow so this will work. Cheers

roger-that-dev commented 5 years ago

Actually I've had a look into this and there's no way to fix it in the short term, it requires some Corda changes or changes to the tokens SDK to provide jackson annotations for sub-classes. In the meantime, I'm going to include a node driver integration test which demonstrates the whole end to end workflow. Cheers

iljadfine commented 5 years ago

I got a similar error for NovateObligation. Command: start NovateObligation linearId: b4436194-5977-4c0b-9581-dc93ee625627, novationCommand: { oldToken: { currencyCode: EUR, type: fiat }, newToken: { currencyCode: XRP, type: digital }, oracle: Oracle, type: token } Error:

No matching constructor found:

  • [net.corda.core.contracts.UniqueIdentifier, com.r3.corda.finance.obligation.commands.ObligationCommands$Novate]: Could not parse as a command: Cannot construct instance of com.r3.corda.sdk.token.contracts.types.TokenType (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information at [Source: UNKNOWN; line: -1, column: -1] (through reference chain: com.r3.corda.finance.obligation.commands.ObligationCommands$Novate$UpdateFaceAmountToken["oldToken"])

Is it the same cause?

roger-that-dev commented 5 years ago

Yes its the same problem. The shell cannot deserialise the types. There is one way around it which is to create a wrapper flow which just takes strings as input and then constructs the objects you need and then invokes the create obligation flow. It's not difficult to do but will get messy, e.g. you'll need to deal with whether the currency code is a fiat or digital currency, for example.

koshikraj commented 5 years ago

@roger3cev, I was just wondering if there is a node driver integration test for an end to end flow for ripple settlement since we are not able to test it using the shell?

roger-that-dev commented 5 years ago

There's not a node driver test at the moment - it's on our TODO To add one though. Cheers

adelrustum commented 5 years ago

Hi guys,

UPDATE: Here's the repo with all the below fixes: https://github.com/adelrustum/corda-settler-fixes

I was able to use Roger's workaround (i.e. instead of passing an Amount, I passed the amount and currency code); below is how I achieved it:

  1. Add cordaCompile "$tokens_release_group:tokens-money:$tokens_release_version" into the gradle file of cordapp module so I can use FiatCurrency class.
  2. Optional: I updated the tokens-sdk version to ext.tokens_release_version = '1.0-RC03', because I noticed when I opened FiatCurrency.kt file and compared it to the latest version of tokens-sdk that they're different. This got me stuck for 2-3 hours lol not an expert on gradle/maven/intellij, but I'm getting there.
  3. Modify CreateObligation.kt and split Amount into longAmount and currencyCode; this will trigger a couple of errors because some functions expect Amount<T> instead of Amount<FiatCurrency> so I'll leave locating those easy fixes to you. Below is the part of the code that I changed:
    class Initiator<T : TokenType>(
            private val longAmount: Long,
            private val currencyCode: String,
            private val role: InitiatorRole,
            private val counterparty: Party,
            private val dueBy: Instant? = null,
            private val anonymous: Boolean = true
    ) : FlowLogic<WireTransaction>() {
        private val token = FiatCurrency(Currency.getInstance(currencyCode))
        private val amount = Amount(longAmount, token)
  4. Use this call in your terminal to create an obligation:
    start CreateObligation longAmount: 1000, currencyCode: USD, role: OBLIGOR, counterparty: PartyB, dueBy: 1650000000, anonymous: false
  5. Below is the updated NovateObligation.kt code: Again, it will trigger some errors in the class code (replace TokenType with FiatCurrencty or DigitalCurrency where applicable).
    class Initiator(
            val linearId: UniqueIdentifier,
            private val fiatIdentifier: String,
            private val digitalIdentifier: String,
            private val oracle: Party
    ) : FlowLogic<WireTransaction>() {
        private val fiatToken = FiatCurrency(Currency.getInstance(fiatIdentifier))
        private val digitalToken = DigitalCurrency(digitalIdentifier)
        private val novationCommand = ObligationCommands.Novate.
                UpdateFaceAmountToken(fiatToken, digitalToken, oracle)
  6. Use this call:
    start NovateObligation linearId: REPLACE_W_OBLIGATION_ID, fiatIdentifier: USD, digitalIdentifier: XRP, oracle: Oracle
  7. To fix UpdateSettlementMethod flow I had to copy the entire ripple library from modules\ripple to cordapp folder, I tried to use it first as a dependency in build.gradle but that created a circular reference error on build because ripple library relies on cordapp.
  8. Update build.gradle under cordapp with compile 'com.ripple:ripple-core:0.0.1-SNAPSHOT'
  9. Update UpdateSettlementMethod:
    class Initiator(
            val linearId: UniqueIdentifier,
            val accountToPay: String,
            val settlementOracle: Party
    ) : FlowLogic<WireTransaction>() {
        private val settlementMethod = XrpSettlement(accountToPay, settlementOracle)
  10. Run this from PartyB terminal (not PartyA): start UpdateSettlementMethod linearId: REPLACE_W_OBLIGATION_UID, accountToPay: REPLACE_W_XRP_ADDRESS, settlementOracle: Oracle
  11. Update OffLedgerSettleObligation flow:
    class Initiator<T : TokenType>(
            private val longAmount: Long,
            private val digitalIdentifier: String,
            private val linearId: UniqueIdentifier
    ) : FlowLogic<WireTransaction>() {
        private val token = DigitalCurrency(digitalIdentifier)
        private val amount = Amount(longAmount, token)
  12. Run this from PartyA terminal: start OffLedgerSettleObligation longAmount: 20000000, digitalIdentifier: XRP, linearId: REPLACE_W_OBLIGATION_UID
koshikraj commented 5 years ago

Hi guys,

UPDATE: Here's the repo with all the below fixes: https://github.com/adelRestom/corda-settler-fixes

I was able to use Roger's workaround (i.e. instead of passing an Amount, I passed the amount and currency code); below is how I achieved it:

  1. Add cordaCompile "$tokens_release_group:tokens-money:$tokens_release_version" into the gradle file of cordapp module so I can use FiatCurrency class.
  2. Optional: I updated the tokens-sdk version to ext.tokens_release_version = '1.0-RC03', because I noticed when I opened FiatCurrency.kt file and compared it to the latest version of tokens-sdk that they're different. This got me stuck for 2-3 hours lol not an expert on gradle/maven/intellij, but I'm getting there.
  3. Modify CreateObligation.kt and split Amount into longAmount and currencyCode; this will trigger a couple of errors because some functions expect Amount<T> instead of Amount<FiatCurrency> so I'll leave locating those easy fixes to you. Below is the part of the code that I changed:
class Initiator<T : TokenType>(
            private val longAmount: Long,
            private val currencyCode: String,
            private val role: InitiatorRole,
            private val counterparty: Party,
            private val dueBy: Instant? = null,
            private val anonymous: Boolean = true
    ) : FlowLogic<WireTransaction>() {
        private val token = FiatCurrency(Currency.getInstance(currencyCode))
        private val amount = Amount(longAmount, token)
  1. Use this call in your terminal to create an obligation:
start CreateObligation longAmount: 1000, currencyCode: USD, role: OBLIGOR, counterparty: PartyB, dueBy: 1650000000, anonymous: false
  1. Below is the updated NovateObligation.kt code: Again, it will trigger some errors in the class code (replace TokenType with FiatCurrencty or DigitalCurrency where applicable).
class Initiator(
            val linearId: UniqueIdentifier,
            private val fiatIdentifier: String,
            private val digitalIdentifier: String,
            private val oracle: Party
    ) : FlowLogic<WireTransaction>() {
        private val fiatToken = FiatCurrency(Currency.getInstance(fiatIdentifier))
        private val digitalToken = DigitalCurrency(digitalIdentifier)
        private val novationCommand = ObligationCommands.Novate.
                UpdateFaceAmountToken(fiatToken, digitalToken, oracle)
  1. Use this call:
start NovateObligation linearId: REPLACE_W_OBLIGATION_ID, fiatIdentifier: USD, digitalIdentifier: XRP, oracle: Oracle
  1. To fix UpdateSettlementMethod flow I had to copy the entire ripple library from modules\ripple to cordapp folder, I tried to use it first as a dependency in build.gradle but that created a circular reference error on build because ripple library relies on cordapp.
  2. Update build.gradle under cordapp with compile 'com.ripple:ripple-core:0.0.1-SNAPSHOT'
  3. Update UpdateSettlementMethod:
class Initiator(
            val linearId: UniqueIdentifier,
            val accountToPay: String,
            val settlementOracle: Party
    ) : FlowLogic<WireTransaction>() {
        private val settlementMethod = XrpSettlement(accountToPay, settlementOracle)
  1. Run this from PartyB terminal (not PartyA): start NovateObligation linearId: REPLACE_W_OBLIGATION_UID, fiatIdentifier: USD, digitalIdentifier: XRP, oracle: Oracle
  2. Update OffLedgerSettleObligation flow:
class Initiator<T : TokenType>(
            private val longAmount: Long,
            private val digitalIdentifier: String,
            private val linearId: UniqueIdentifier
    ) : FlowLogic<WireTransaction>() {
        private val token = DigitalCurrency(digitalIdentifier)
        private val amount = Amount(longAmount, token)
  1. Run this from PartyA terminal: start OffLedgerSettleObligation longAmount: 20000000, digitalIdentifier: XRP, linearId: REPLACE_W_OBLIGATION_UID

Thanks for the fix. I was able to test the end to end flow on the Corda shell.

adelrustum commented 5 years ago

@koshikraj Thanx for your merge request, I also updated the comment here to remove the repeated instruction.