-
Notifications
You must be signed in to change notification settings - Fork 86
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
Interleaved wallet #384
Interleaved wallet #384
Conversation
Transactions CostsSizes and execution budgets for Hydra protocol transactions. Note that unlisted parameters are currently using
Cost of Init Transaction
Cost of Commit TransactionCurrently only one UTxO per commit allowed (this is about to change soon)
Cost of CollectCom Transaction
Cost of Close Transaction
Cost of Contest Transaction
Cost of Abort Transaction
Cost of FanOut TransactionInvolves spending head output and burning head tokens. Uses ada-only UTxO for better comparability.
|
case coverFee_ pparams systemStart epochInfo lookupUTxO walletUTxO partialTx of | ||
Left e -> | ||
pure (Left e) | ||
Right (walletUTxO', balancedTx) -> do | ||
_ <- swapTMVar utxoVar (walletUTxO', pparams, systemStart, epochInfo) | ||
writeTVar utxoVar (walletUTxO', pparams, systemStart, epochInfo) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we still want to do this? This is optimistically removing used utxos when balancing. But there is no guarantee that transaction will ever be included in the chain?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thinking out loud: the reason we would need this is because we want to prevent trying to double-spend some UTxO. Double-spending would suppose that we try to submit more than one transaction at the time.
- ✔️ We can't commit until after Init has been observed
- ✔️ We don't try to collect-com until after all commits have been observed
- ✔️ We can't close until after collect-com has been observed
- ✔️ We can't contest until after a close has been observed
- ❌ We might try to fanout right after we submit a contest.
Indeed, we allow to contest right up until the deadline; which may lead in situation where the fanout is posted while a contest is pending. This is pretty bad because depending on how transactions gets re-organised due to chain fork, the fanout may end up being processed before / in stead of the contest. If we make sure that the utxo used for fanout depends on the one used for contest, then we at least limit this kind of issues.
As a side-note, we wouldn't be able to fanout either because in normal scenarios, we have only one fuel UTxO. Thus, if a contest is pending, no UTxO is available and fanout becomes impossible. But I highly prefer failing for a "lack of payment input" than later having to handle a rejected transaction from the network because of a double-spending (which is precisely the kind of case we want to treat as adversarial and for which there's no point retrying transactions).
One thing we ought to implement IMO is predicted UTxO. Indeed, at this very moment, we know perfectly well what the transaction is and thus, what would be the resulting UTxO if included. We can thus, instead of removing the consumed UTxO, replaced it with the produced ones as if we had already observed the transaction. This doesn't change much the first 4 cases above, but it allows us to successfully submit a fanout on top of a pending contestation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wait.. what will happen if a transaction fails to be submitted? e.g. the fanout because it was tried before the deadline? Will the UTxO be "made available" again? If not.. that is even a bug and we would NOT want to optimistically consume the UTxO at this location.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup.
That's why we need validity intervals on transactions submitted as part of the Hydra protocol. Say 1h. If after 1h you haven't seen the transaction in a block, then you can legitimately "unlock" the UTxO that was "optimistically consume".
In the end, consuming the UTxO (as in, erasing it completely) is kind of a bad idea. Ideally we would keep two sets. One being the sets of UTxO currently part of pending transactions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't it be a much simpler solution to just not bother and have the ledger sort out potential double spends? i.e. the corner case where two tx were balanced, signed and submitted using the same UTxO because of a race between observing the first tx and constructing the second tx?
After all, our own local cardano-node would not approve a double spend and if our application (or the client in proxy) wants to spam the chain with fanout txs it's our/their fault.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did not follow all the details, doing something else, but I would rather rely on retrying tx and some timeout based on actual observations than maintaining yet another state on-chain. If I understood correctly what's proposed. Or even better: Leave those details to a proper wallet implementation and just define some simpler "protocol" interaction between chain poster and attached wallet.
I will spend time on this PR tomorrow @ch1bo , happy to give it a look. |
25d9a93
to
996878c
Compare
The tiny wallet is not allocating/freeing any resource anymore so we don't need to use the with pattern.
This should make this easier to test and maybe even ditch the MockServer
Craft mock `queryUTxO` handles suitabel for the test at hand instead
This makes it more convincing to not explicitly test this and tests of applyBlock to be sufficient.
We arrived at a point where we would need to be fixing the MockServer because it seems to rely on some behavior not present in the "new" TinyWallet behavior anymore. But the identical test against a real cardano-node works. So we decided we do not want to bother.
613c0d4
to
c5190eb
Compare
@KtorZ worked with @abailly-iohk on this and we think it's ready for your review :) |
Unit Test Results224 tests - 1 222 ✔️ - 1 6m 29s ⏱️ -5s Results for commit 26e1b30. ± Comparison against base commit 9acd79d. This pull request removes 3 and adds 2 tests. Note that renamed tests count towards both.
♻️ This comment has been updated with latest results. |
Note to myself (cc @abailly-iohk): we forgot to actually query from point and maybe there is a somewhat okayisch way to do the queries in a single connection without too much of ouroboros fancyness. |
@ch1bo Do you want to do this here, or in another PR? |
@abailly-iohk Did it in this PR now. Otherwise the semantics wouldn't have been complete. Only thing left (for a follow-up PR) is the fact that it's still using |
The queryXXX functions of the includec CardanoClient do use the local state query protocol, which can acquire some specific point on the chain when passed `Just ChainPoint`, so we do configure these functions to take a `QueryPoint`. We do use this to query at the point to which the chain rolled back in TinyWallet workflow.
f91ecec
to
3326d98
Compare
Following the previous commit we need to specify now explicitly that we are interested in querying the latest point / tip.
d5a74ae
to
26e1b30
Compare
eraHistory <- queryEraHistory networkId socketPath | ||
tip@(ChainPoint slotNo _) <- queryTip networkId socketPath | ||
systemStart <- querySystemStart networkId socketPath (QueryAt tip) | ||
eraHistory <- queryEraHistory networkId socketPath (QueryAt tip) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is probably how we should be querying most things generally speaking -> first get a point, and then run all subsequent queries on that point. It's nothing really crucial though because we explicitly said Hydra heads would need to be terminated in case of protocol parameter updates thus in principle, the case where we get different parameters across several nearby queries does not happen.
Still.. I'd prefer doing things properly from the get-go, so that the day we decide to do something more elaborate regarding hard-forks or protocol updates, we have one less problem to think of.
@@ -189,12 +165,8 @@ finalizeTx TinyWallet{sign, getUTxO, coverFee} headState partialTx = do | |||
Right validatedTx -> do | |||
pure $ sign validatedTx | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that, while we're at it, this CannotCoverFees
error is quite misleading here because it actually wraps several cases, some of which have nothing to do with covering fees so-to-speak:
= ErrNoAvailableUTxO
| ErrNotEnoughFunds ChangeError
| ErrNoPaymentUTxOFound
| ErrScriptExecutionFailed (RdmrPtr, ScriptFailure StandardCrypto)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice clean-up. Glad to see the MockServer & DirectSpec going to oblivion. This is indeed simpler and dictates the kind of interface we would expect from an external wallet 👍
I couldn't help but try out how a synchronous
TinyWallet
would look like & work. This Draft PR tries to drive a discussion whether this is something we want or we should stick with the asynchronous interface for balancing & signing transactions produced by theChain.Direct
layer.TinyWallet
withreset
andupdate
methods to query the latest state and handle individual blocks received via the main ChainSync client.ChainPoint
?hydra-cluster
tests passTests usingMockServer
likeWalletSpec
andDirectSpec
retry
anymoremkChain
to be a simpleSubmitTx
Seems to work and I wonder if it maybe even fixes #367 ?