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

hub: Add an endpoint to create a scheduled payment #3231

Closed
wants to merge 7 commits into from

Conversation

jurgenwerk
Copy link
Contributor

@jurgenwerk jurgenwerk commented Sep 16, 2022

Ticket: CS-4373

It goes like this:

  1. Client will generate the signature for creating the transaction to add a scheduled payment on the blockchain
  2. Client will send the scheduled payment params to the endpoint, along with the signature
  3. Endpoint will save the scheduled payment row in the db, and submit a blockchain transaction. The request will wait until we are able to get the transaction hash. If there are problems submitting the transaction, the request will fail, and the scheduled payment row will be discarded
  4. At this point, the transaction is not mined yet. This could take many minutes to finish, so we do it async in a background worker which will update the scheduled payment row once done (it will write the creation_block_number). This way the client can poll the scheduled payment and when creation_block_number gets written by the background job, it means the scheduled payment has been added in both blockchain and the crank - the client can then show it as "added successfully", otherwise it will be "pending".

TODO:

  • Test using a real chain
  • More thorough validator checks + validator tests
  • Add a route test for a recurring scheduled payment (important: see if payAt gets set correctly)
  • Prevent double save (at the beginning of the request, check if there was a very similar scheduled payment created very recently)

@github-actions
Copy link

ssr-web test results

    1 files  ±0      1 suites  ±0   11s ⏱️ -1s
172 tests ±0  172 ✔️ ±0  0 💤 ±0  0 ±0 

Results for commit 9ae745a. ± Comparison against base commit 166cfb6.

@github-actions
Copy link

web-client test results

    1 files  ±0      1 suites  ±0   1m 21s ⏱️ +2s
525 tests ±0  525 ✔️ ±0  0 💤 ±0  0 ±0 

Results for commit 9ae745a. ± Comparison against base commit 166cfb6.

@jurgenwerk
Copy link
Contributor Author

This is ready for a draft review, I'm currently not planning any significant changes, only the things listed in TODO.

});

it('does not persist a scheduled payment when something goes wrong with on chain call', async function () {
forceSchedulePaymentOnChainTimeout = true;
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe this should be reset to false in a beforeEach in case any tests get added after this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds good. Will change

// can poll the record to see when it's mined (creation_block_number attribute will be set).
await this.workerClient.addJob('scheduled-payment-on-chain-creation-waiter', {
scheduledPaymentId: scheduledPayment.id,
});
Copy link
Contributor

Choose a reason for hiding this comment

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

The profile purchase code uses a “job ticket” pattern for a slow/chain operation; the endpoint returns a newly-created ticket that the consumer can poll at /api/job-tickets/:id to track progress. Would it make sense to use that pattern here?

Copy link
Contributor Author

@jurgenwerk jurgenwerk Sep 19, 2022

Choose a reason for hiding this comment

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

I thought about it. I think it's easier to just query for the scheduled payments. If creation_block_number is present on the record, it will mean the job finished successfully. In this case job ticket pattern sounds a bit roundabout

});
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it worth having a couple of tests for this task?

@habdelra
Copy link
Contributor

habdelra commented Sep 16, 2022

Love your approach, especially #4 🙏 I imagine that @FadhlanR will need to adjust the work he's done in the SDK to poll the hub for the txn instead of using the SDK waitUntilTransactionMined()?

@@ -435,3 +435,15 @@ function sortSignaturesAsBytes(
return [contractSignature, ownerSignature];
}
}

export function signatureToVRS(rawSignature: string) {
Copy link
Contributor

Choose a reason for hiding this comment

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

is this essentially the same as ethSignSignatureToRSVForSafe() in this module?

Copy link
Contributor Author

@jurgenwerk jurgenwerk Sep 19, 2022

Choose a reason for hiding this comment

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

It looks like it. However, there is one diference, in the recovery identifier (v):

signatureToVRS uses parseInt(value) while ethSignSignatureToRSVForSafe uses parseInt(value, 16)

So for example, let's have:
signature = 0x21fbf0696d5e0aa2ef41a2b4ffb623bcaf070461d61cf7251c74161f82fec3a4370854bc0a34b3ab487c1bc021cd318c734c51ae29374f2beb0e6f2dd49b4bf41c

v is the last 2 bytes and that is 1c

signatureToVRS:

parseInt("1c")
=> 1

ethSignSignatureToRSVForSafe:

parseInt("1c", 16)
=> 28

Judging by this article it looks like the v can be either either 27 (0x1b) or 28 (0x1c). That makes me think ethSignSignatureToRSVForSafe is the correct implementation and we should use it instead of signatureToVRS (and get rid of this one).

However I'm not sure how this even worked then, given the parsing might be wrong. 🙇‍♂️ Judging by this comment ethereum/go-ethereum#19751 (comment), v can also be 0 or 1 and this is the reason both implementations that we have are working.

}

export function strip0x(s: string) {
return Web3.utils.isHexStrict(s) ? s.substr(2) : s;
Copy link
Contributor

@habdelra habdelra Sep 16, 2022

Choose a reason for hiding this comment

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

using s.replace(/^0x/, '') is simpler and it's idempotent--you can run it as many times as you want and it will always return the correct thing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agree! Will change it

@jurgenwerk
Copy link
Contributor Author

@habdelra I think @FadhlanR used waitUntilTransactionMined for the act of enabling the scheduled payment module on the safe. I believe we discussed that doesn't concern the hub and that we want to delegate the waiting to the client. @FadhlanR can you confirm?

@FadhlanR
Copy link
Contributor

From what I understand for now the development in the hub is only related to the crank work which is regarding scheduling the payment and triggering the execution of the scheduled payment. I don't think we plan to use the hub to pull any type of transactions, such as creating safe and enabling modules that will not be handled by the hub/crank. CMIIW

@habdelra
Copy link
Contributor

habdelra commented Sep 19, 2022

@jurgenwerk @FadhlanR so i'm confused them about about this function in the SDK: https://github.com/cardstack/cardstack/blob/main/packages/cardpay-sdk/sdk/scheduled-payment-module.ts#L672. This function uses waitUntilTransactionMined() https://github.com/cardstack/cardstack/blob/main/packages/cardpay-sdk/sdk/scheduled-payment-module.ts#L739. Shouldn't this function be responsible for calling the endpoint in this PR to setup the scheduled payments, and then based on the comments in #4 of this PR also poll the hub to confirm that the sceduled payments have been setup?

To be clear is this PR about establishing the schedule for the payments or to execute a specific payment that is part of the schedule?

@jurgenwerk
Copy link
Contributor Author

Closing because the approach changed, and it will be split into 2 PRs - one for the SDK/CLI and one for the hub.

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