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

API Proposal for Connecting Managed Player to FuelTank #12

Open
gormaaen opened this issue Oct 8, 2023 · 11 comments
Open

API Proposal for Connecting Managed Player to FuelTank #12

gormaaen opened this issue Oct 8, 2023 · 11 comments

Comments

@gormaaen
Copy link

gormaaen commented Oct 8, 2023

The issue with the current implementation is that SendCreateWallet will return true even if the resulting player wallet as no Address assigned. This means that expected workflow for creating a managed player and immediately assigning them to a FuelTank will fail (SendCreateWallet .success -> GetWallet.success -> AddAccount using returned wallet.Account.Address) If you add a wait 3secs or so between succesfull SendCreateWallet and GetWallet you might get lucky and have the Address field field filled out. This not ideal.

Solution 1. SendCreateWallet only returns true when Wallet has been completely formed (with Address and Public Key).
Solution 2. SendCreateWallet returns a Transaction for the Wallet creation (with Address and Public Key).

Since creating a managed player and adding a FuelTank ref is expected to be the most common scenario why not do both in one call
Solution 3. SendCreateWallet.SetTankId().SetExternalId() for a combined create managed wallet + add fueltank. Returning a Transaction.

@leonardocustodio
Copy link
Member

Hello @gormaaen, there is no transaction associated to "CreateWallet" as that's not something that happens in the blockchain itself but rather outside of it and thus there is no way to return a transaction as suggested on solution 2.

Personally I don't see much difference between the things of what you proposed and simply check if the address was created already or not. Here is a full case example:

  1. Send the mutation CreateWallet with the externalId
  2. Keep polling GetWallet with the externalId checking the field address to see if it was already created.
  3. When the field address appears, go to the next step.

About solution 1, if your proposal was to hold the query until it was created that could lead to a timeout. If your solution is supposed to return false/false/false until it gets create and return true... That's exactly the same thing as the previous suggestion.

@gormaaen
Copy link
Author

gormaaen commented Oct 9, 2023

Hi Leonardo,

I get the polling requirements, but it is still not intuitive and I am sure alot of devs will fall into the trap of believing the Wallet is fully formed when the system returns true. Two reasons why returning a Transaction is preferred is that 1. it force devs to either deploy a polling sequence and/or 2. allows for the use of the PusherClient to catch the Transaction there.

If you deside to stay with the current implementation then maybe just add a comment to the inline comments for SendCreateWallet?

Cheers,

@leonardocustodio
Copy link
Member

Returning a transaction is not an option. The transactions are on-chain transactions, they have transactionId, hash, events, encoded data, etc....

None of these are available in Creating a Wallet as it is not a transaction.

We can't just include something that is not a transaction in there. There are workers/processors/parsers lots of stuff that expect a transaction in the transactions table.

But we can try to think about something else. I will raise your concern and check what we can do

@CliffCawley
Copy link
Contributor

CliffCawley commented Oct 26, 2023

@leonardocustodio Isn't it possible to use an idempotency key for this? It's one of the uses they can be used for. That way you can issue the same transaction again, with the same key, and if it wasn't processed, it'll be processed, but otherwise it'll return the result that wasn't returned last time it was called.

It's going to be an issue anyway if the call fails due to some network issue and you can't get any details to query anyway, so providing the unique idempotency key ourselves, allows us to recover in the case that the results were never returned too.

@leonardocustodio
Copy link
Member

I see so your suggestion would be:

Call Create Wallet the first time, get the idempotency key and keep calling Create Wallet with the idempotency key until it returns the "Wallet" (address), is that correct? Or obviously, you guys provide the idempotency key the first time and just keep calling it until it returns the "wallet".

@gormaaen do you think that would be a good solution for your use-case? If so @CliffCawley is right and we can easily implement that pretty quick.

@gormaaen
Copy link
Author

gormaaen commented Oct 26, 2023 via email

@CliffCawley
Copy link
Contributor

CliffCawley commented Oct 27, 2023

@leonardocustodio @gormaaen I'm not sure if it's good practice to call it an idempotency key or not, but the functionality is similar.

So just quickly, before it's called this, just double check what you think. The definition of idempotency is An API call or operation is idempotent if it has the same result no matter how many times it's applied. That is, an idempotent operation provides protection against accidental duplicate calls causing unintended consequences.

Additionally When a dangerous or difficult-to-undo operation must be performed, but it cannot be made idempotent without more context, use an Idempotency Key. Common examples are when creating data in a third-party where that data has a cost, or cannot be easily removed or changed to handle intermittent failures.

So really, if the result changes over time, it's probably not the true meaning (I.e. if it's changing from first call 'In progress' to successive calls eventually changing to 'Success') then it's possible it might cause confusion for some developers if it's called that name. Or maybe this is fine.

But the mechanic is similar. A client of the API calls with a unique key (Could even be called request key?). Either way, it must be unique, in that it's never been used for another transaction. And so (I copied below from square dev documentation):

  1. If you make the same request with the same idempotency key again, the endpoint knows it's a duplicate request. It doesn't perform the full activity again. Instead of getting an error, the endpoint returns the response it would have with the very first time it was called.
  2. If you use the same idempotency key but change the request (for example, specify a different value), you get an error indicating that you used the idempotency key previously.

Idempotency keys can be anything, but they need to be unique. Virtually all popular programming languages provide a function for generating unique strings. You should use one of these language calls.

image

Anyway, up to you, but if we supply a unique key, and you keep track of those keys to requests, double checking the params and the key are the same and following the rules above, then I think it should work well. If storage of keys/requests is too much, they could potentially expire over time and be deleted, but that wouldn't be standard practice for idempotency keys I don't think.

@leonardocustodio
Copy link
Member

If we consider the whole concept I guess it cannot be called a idempotency key. But the alternatives to that would be:

jobId which would be generated by us and you guys would use to query the status

Or something like this https://restfulapi.net/rest-api-design-for-long-running-tasks/ that is not possible on GraphQL as we don't make use of the status codes.

Either way, I don't see how that would change anything from the current behavior (getting a status and then querying another place until it returns the data). It is pretty much the samething.

What I can suggest in here so we don't make a confusion for other developers is we keep using externalId and we will return the wallet already, while it doesn't have an address without it and if you keep calling it will have the address after a few seconds and we add this to the websockets as well.

@leonardocustodio
Copy link
Member

Also just to note this query is already "idempotent" in a way that a externalId will always generate the same wallet. You can call the endpoint multiple times and the address will not change.

That happens because the externalId is used as the derivation path. So, if you don't change the derivation path, the address does not change.

@CliffCawley
Copy link
Contributor

CliffCawley commented Oct 28, 2023

jobId which would be generated by us and you guys would use to query the status

No, that's where the issue is, it needs to be generated by the client, not the server, so that if something happens to the call on the way to the server (network issue, whatever), we can call the same thing again, with the same id, and you'll either process it the first time, or just return the results of the previous call.

That's what changes it from the current behaviour.

It's kind of like and idempotency key, but we're then also using it to get the progress of a long running process. It's kind of like combining an idempotency key together with a returned jobid, except generated by the caller.

But yes, if passing externalId multiple times to this, results in the same result, without creating duplicate entries of anything, then yes it's already idempotent.

@leonardocustodio
Copy link
Member

Just to let you guys know this change needs to be done first on core, I will check if we can push it for our November 7th release. After that we can update the SDK.

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

No branches or pull requests

3 participants