tweag / cooked-validators

MIT License
39 stars 11 forks source link

Better UTxO search #252

Closed carlhammann closed 1 year ago

carlhammann commented 1 year ago

Edit: Look at this comment to see where the PR ended up. I'll leave the original comment here in order to keep the conversation logical.

This PR proposes a small improvement to how we retrieve UTxOs from the blockchain. It will close #239. (I did not implement all ideas I proposed there, because some of them did not seem worth the trouble, at least not until we actually store more type information in the mock chain, see issue #235.)

I removed all of the filteredUtxos... and the ...withDatums functions, in favour of the two functions liftFilter and liftLookup. This enables us to write something like

  utxos <-
    utxosAt (Pl.validatorAddress A.auctionValidator)
      >>= liftFilter (isOutputWithValueSuchThat (`Value.geq` theNft))
      >>= liftLookup resolveDatum
      >>= liftFilter (isOutputWithInlineDatumOfType @A.AuctionState)

instead of

utxos <-
    filteredUtxosWithDatums $
      isScriptOutputFrom' A.auctionValidator
        >=> isOutputWithValueSuchThat (`Value.geq` theNft)

which I think is much clearer.

I also added a few "lookup" functions to use with liftLookup, namely resolveDatum resolveValidator and resolveReferenceScript. For the last of these three, I also changed the implementation of the direct monad a bit so that it keeps track of reference scripts, (i.e. validatorFromHash returns Just the full script, even for scripts that sit in the reference script field of some UTxO, independently of whether they own any outputs). This is tested here.

carlhammann commented 1 year ago

my only concern is with the naming of liftFilter and liftLookup

That's also my concern... Why should we call a function b -> m (Maybe c) a "lookup" and a function b -> Maybe c a "filter", and in what sense do we "lift"? Both functions apply a possibly failing "updating" operation to a list of outputs (represented as pairs (TxOutRef, o) for some o), and keep only the successfully updated outputs. The difference is that, for liftLookup, the updating operation can depend on the state of the mock chain.

Here's a more fundamental solution that just occurred to me: We could in principle go around the explicit applications of liftFilter and liftLookup (or whatever we want to call them) with a similar trick as we use for tweaks, defining a custom monad for retrieving and filtering of UTxOs. This would allow idioms like

utxos <-
  getUtxos $
    utxosAt (Pl.validatorAddress A.auctionValidator)
      >>= isOutputWithValueSuchThat (`Value.geq` theNft)
      >>= resolveDatum
      >>= isOutputWithInlineDatumOfType @A.AuctionState

I think it'd be nice to have something like this, but let's discuss the options first.

carlhammann commented 1 year ago

I took another swing at this, and I'm now (relatively) satisfied. I have written a small module with a type UtxoSearch m a and a function

runUtxoSearch :: Monad m => UtxoSearch m a -> m [(TxOutRef, a)]

The module also contains various functions like filterWith :: UtxoSearch m a -> (a -> m (Maybe b)) -> UtxoSearch m b, so that the running example in this discussion finally becomes:

  utxos <-  runUtxoSearch $
      utxosAtSearch (Pl.validatorAddress A.auctionValidator)
        `filterWithBool` ((`Value.geq` theNft) . outputValue)
        `filterWith` resolveDatum
        `filterWithPure` isOutputWithInlineDatumOfType @A.AuctionState
mmontin commented 1 year ago

As a additional feature, we could have an additional search module with helpers for searching with a certain reference script, as certain staking credential, etc.

carlhammann commented 1 year ago

As a additional feature, we could have an additional search module with helpers for searching with a certain reference script, as certain staking credential, etc.

I think that's a question that will be answered by our usage patterns. For now, I think that idioms like allUtxos `filterWith` resolveReferenceScript `filterWithBool` ((== theReferenceScript) . outputReferenceScriptL) are enough.