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

Move important UTxO state transition functions to the top-level and test them #2848

Merged
merged 32 commits into from
Sep 3, 2021

Conversation

sevanspowell
Copy link
Contributor

This work is intended to allow future modifications to the UTxO state transition function (i.e. collateral) to be tested in isolation, at the unit level.

My changes are primarily to the nested function "applyTx". I have tried to keep my changes minimal:

  • Use more verbose variable names in that function to make the purpose of each variable clear.
  • More clearly delineate between transaction outputs that are unspent and transaction outputs "we know about" but may possibly be spent.
  • Make some effort to separate the ideas of "knowledge of a UTxO" from "ownership of a UTxO" to make testing easier.
  • Extract out the core state transition function from the existing "do lots" "applyTx" function and move it to the top-level.
  • Move other useful functions to the top-level for the same reason.
  • Test important functions in wallet model spec.
  • Re-implement hasKnownInput/hasKnownOutput to make their implementations a little more clear and symmetric.

Comments

These changes make testing collateral much easier, we can just test the "applyTxToUTxO" function at the unit level, and adjust existing model tests as necessary.

Issue Number

ADP-1092

@sevanspowell sevanspowell self-assigned this Aug 26, 2021
@sevanspowell sevanspowell force-pushed the sevanspowell/adp-1092/apply-tx-testable branch from 9182740 to dafd813 Compare August 26, 2021 05:12
- Move the function that modifies the state of a UTxO based on a transaction to
  the top level so that it can be tested.
- Move other useful functions to the top-level for the same reason.
- Test important functions in wallet model.
@sevanspowell sevanspowell force-pushed the sevanspowell/adp-1092/apply-tx-testable branch from dafd813 to 88c9886 Compare August 26, 2021 06:14
-- mempty `difference` u = mempty
-- u `difference` u = mempty
difference :: UTxO -> UTxO -> UTxO
difference u1 u2 =
Copy link
Member

@jonathanknowles jonathanknowles Aug 26, 2021

Choose a reason for hiding this comment

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

Suggestion: move this difference function to the UTxO module.

Justification: it's a function that operates purely on UTxO values, and could viewed as analogous to these difference functions:

  • TokenBundle.difference
  • TokenMap.difference
  • Set.difference
  • Map.difference
  • ...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Absolutely 👍

Copy link
Member

Choose a reason for hiding this comment

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

@sevanspowell and I had a discussion about the difference function.

We believe it might make more sense if this function were to have the following signature:

- difference :: UTxO -> UTxO     -> UTxO
+ difference :: UTxO -> Set TxIn -> UTxO

Reasons:

  • The current implementation relies on finding the difference between two TxOut values in the event that a key appears in both UTxO sets.
  • But it's not clear what useful answer we can obtain from differencing a pair of TxOut values. For example, if the pair of TxOut values have different addresses, then which address should we pick, and why?
  • In all situations that we currently deal with, there should only be one possible TxOut for a given TxIn. (We shouldn't ever have a state where there are two possible TxOut values for a given TxIn.)
  • If we assume that there is only one unique mapping for any given TxIn, it makes sense to constrain the type so that we're only subtracting a set of TxIn values (so the function does not need to deal with the odd case where TxOut values differ) from the key set of our UTxO parameter.

However, after making this simplification, we can see there is already a pre-existing function with these semantics:

excluding :: UTxO -> Set TxIn ->  UTxO
excluding (UTxO utxo) =
    UTxO . Map.withoutKeys utxo

Hence, in all places that currently use UTxO.difference, it might make more sense to call UTxO.excluding with just the set of TxIn values from the second map.

- Remove usage of "knownTxO" when calculating "ourNextUTxO", it's not necessary.
- Move definition of knownTxO closer to where it's actually necessary.
- @jonathanknowles pointed out that the difference function on UTxOs is
  problematic to define over the domain of all UTxOs. Ideally, the mapping
  between TxIn and TxOut should be unique, and so we should never need to worry
  about a case where two different TxOuts are associated with the same TxIn, but
  the existing type can't guarantee this property, and so we need to define the
  behaviour when a TxIn has two different TxOuts. The behaviour is tricky to
  define correctly, so we opt to just use the existing "excluding" function
  instead, which can the same intended function as "difference".
- Move the "filterOurUTxOs" function to the UTxO module and rename the function
  to "filterByAddressM".
- Also create a "filterByAddress" function that is the same as
  "filterByAddressM" but runs using the identity functor.
  - Add a test to verify the relationship between the two.
@sevanspowell sevanspowell force-pushed the sevanspowell/adp-1092/apply-tx-testable branch from 8106875 to 78ffacb Compare August 27, 2021 07:51
The updated comments and test names match the generality of the
function (which can take any indicator function on addresses).
This makes it reusable from other locations.

We also move `prop_addressParity_coverage` to `AddressSpec`.
@jonathanknowles jonathanknowles mentioned this pull request Aug 30, 2021
3 tasks
- Using "applyTxToUTxO" then "filterByAddress" filters the entire UTxO on every
  application of a Tx. By presuming that we've already filtered the previous
  UTxO, then only applying parts of the new Tx that we care about, we can get
  better performance characteristics.
  - Add new "spendTx" function to support this. It does part of what
  "applyTxToUTxO" does.
  - Redefine "applyTxToUTxO" in terms of "spendTx"
  - Replace call to "applyTxToUTxO" with the equivalent, and more efficient:
    "spendTx tx u <> filterByAddress (utxoFromTx tx)"
- Add property tests to prove equivalences.
- Re-jig and clean up existing property tests a bit.
@sevanspowell sevanspowell force-pushed the sevanspowell/adp-1092/apply-tx-testable branch from 53b9ec4 to 7717363 Compare August 31, 2021 06:17
Copy link
Member

@jonathanknowles jonathanknowles left a comment

Choose a reason for hiding this comment

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

Hi Sam

I've already made a few comments. But to add to those:

  1. I reckon it would be useful to add some coverage checks to the following properties:

    • prop_applyTxToUTxO_balance
    • prop_applyTxToUTxO_entries
    • prop_applyTxToUTxO_spendTx_utxoFromTx
    • prop_filterByAddress_balance_applyTxToUTxO
    • prop_spendTx_balance_inequality
    • prop_spendTx_balance
    • prop_spendTx_filterByAddress
    • prop_spendTx_isSubset
    • prop_spendTx
    • prop_utxoFromTx_balance
    • prop_utxoFromTx_is_unspent
  2. There are several properties where we use forAll with genX and shrinkX for some type X, where we don't modify the generator in any way.

    I think we could simplify the code a bit by introducing Arbitrary instances (within ModelSpec) for UTxO, Tx, and so on. For example: https://github.com/input-output-hk/cardano-wallet/pull/2848/files#r700738862. Though I reckon it's a good idea to do this only after adding coverage checks, in case we need to alter the size parameter for any of the generators.

sevanspowell and others added 9 commits September 2, 2021 16:54
This allows the definition of a `CoArbitrary` instance for `Address`,
which makes it possible for QuickCheck to generate arbitrary functions
of type `Address -> t`, provided that type `t` has an `Arbitrary`
instance.
Instead of using constant functions, we use arbitrary functions of type
`Address -> Bool`.
…sts.

Instead of using constant functions, we use arbitrary functions of type
`Address -> Bool`.
It's perfectly valid for a transaction to have no outputs if all
incoming value is consumed in some other way (either through the fee, or
by burning tokens).
By conservatively reducing coverage expectations, this change reduces
the overall run time from around 20 seconds to around 1 second.
What we really want here is to capture the notion of "strictly less
than", but `leq` gives us "less than OR equal". We can't use `<`, as
token maps are only partially ordered. So we have to use a combination
of `leq` and `/=`.

In response to review comment:

https://github.com/input-output-hk/cardano-wallet/pull/2867/files#r701595441
…x-testable

Strengthen tests for `applyTx` and `filterByAddress`.
Copy link
Member

@jonathanknowles jonathanknowles left a comment

Choose a reason for hiding this comment

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

LGTM!

I've run the test suite with --qc-max-success 10000, and all the tests pass.

@jonathanknowles
Copy link
Member

bors r+

iohk-bors bot added a commit that referenced this pull request Sep 3, 2021
2848: Move important UTxO state transition functions to the top-level and test them r=jonathanknowles a=sevanspowell

This work is intended to allow future modifications to the UTxO state transition function (i.e. collateral) to be tested in isolation, at the unit level.

My changes are primarily to the nested function "applyTx". I have tried to keep my changes minimal:
  - Use more verbose variable names in that function to make the purpose of each variable clear.
  - More clearly delineate between transaction outputs that are unspent and transaction outputs "we know about" but may possibly be spent.
  - Make some effort to separate the ideas of "knowledge of a UTxO" from "ownership of a UTxO" to make testing easier.
  - Extract out the core state transition function from the existing "do lots" "applyTx" function and move it to the top-level.
  - Move other useful functions to the top-level for the same reason.
  - Test important functions in wallet model spec.
  - Re-implement hasKnownInput/hasKnownOutput to make their implementations a little more clear and symmetric.

### Comments

These changes make testing collateral much easier, we can just test the "applyTxToUTxO" function at the unit level, and adjust existing model tests as necessary.

### Issue Number

ADP-1092


Co-authored-by: Samuel Evans-Powell <mail@sevanspowell.net>
Co-authored-by: Jonathan Knowles <jonathan.knowles@iohk.io>
@iohk-bors
Copy link
Contributor

iohk-bors bot commented Sep 3, 2021

Build failed:

This one looks like a networking issue:

    BYRON_TX_LIST_03 - Shelley CLI does not list Byron wallet transactions (220ms)
    BYRON_TRANS_DELETE - Cannot delete tx on Byron wallet using shelley CLI (200ms)
    BYRON_TRANS_CREATE / BYRON_TRANS_ESTIMATE - Cannot create/estimate tx on Byron wallet using shelley CLI
      create (657ms)
UnexpectedFailure (MissingBlockError (EmptySlot (RealPoint (SlotNo 4724) d189e4debd0a5da70dbf6542a05920234532be37462a6ec2887573dd904714fa)))

cardano-node: The ImmutableDB got corrupted, full validation will be enabled for the next startup      fees (445ms)
  SHELLEY_CLI_WALLETS
retry: expected: Status {statusCode = 201, statusMessage = "Created"}
retry:  but got: Status {statusCode = 500, statusMessage = "Internal Server Error"}
retry:
retry: from the following response: Left (HttpException (ConnectionFailure Network.Socket.connect: <socket: 20>: does not exist (Connection refused)))
retry: Retrying failed test.
retry: Test failed again in the same way.

@sevanspowell
Copy link
Contributor Author

bors retry

iohk-bors bot added a commit that referenced this pull request Sep 3, 2021
2848: Move important UTxO state transition functions to the top-level and test them r=jonathanknowles a=sevanspowell

This work is intended to allow future modifications to the UTxO state transition function (i.e. collateral) to be tested in isolation, at the unit level.

My changes are primarily to the nested function "applyTx". I have tried to keep my changes minimal:
  - Use more verbose variable names in that function to make the purpose of each variable clear.
  - More clearly delineate between transaction outputs that are unspent and transaction outputs "we know about" but may possibly be spent.
  - Make some effort to separate the ideas of "knowledge of a UTxO" from "ownership of a UTxO" to make testing easier.
  - Extract out the core state transition function from the existing "do lots" "applyTx" function and move it to the top-level.
  - Move other useful functions to the top-level for the same reason.
  - Test important functions in wallet model spec.
  - Re-implement hasKnownInput/hasKnownOutput to make their implementations a little more clear and symmetric.

### Comments

These changes make testing collateral much easier, we can just test the "applyTxToUTxO" function at the unit level, and adjust existing model tests as necessary.

### Issue Number

ADP-1092


Co-authored-by: Samuel Evans-Powell <mail@sevanspowell.net>
Co-authored-by: Jonathan Knowles <jonathan.knowles@iohk.io>
@iohk-bors
Copy link
Contributor

iohk-bors bot commented Sep 3, 2021

Build failed:

Test failure:

            , tip = ApiBlockReference
                 { absoluteSlotNumber = ApiT
                     ( SlotNo 4574 )
                 , slotId = ApiSlotId
                     { epochNumber = ApiT
                         ( EpochNo
                             { unEpochNo = 45 }
                         )
                     , slotNumber = ApiT
                         ( SlotInEpoch
                             { unSlotInEpoch = 74 }
                         )
                     }
                 , time = 2021 - 09 - 03 08 : 33 : 40.8 UTC
                 , block = ApiBlockInfo
                     ( Quantity 2201 )
                 }
             }

       Waited longer than 90s to resolve action: "Wallet balance is as expected".
       expected: Quantity 999978
        but got: Quantity 0

  To rerun use: --match "/CLI Specifications/SHELLEY_CLI_HW_WALLETS/HW_WALLETS_01x - Restoration from account public key preserves funds/"

Randomized with seed 2141484065

I tried this locally, and it passes. So will retry.

Looks like:
#2855

@jonathanknowles
Copy link
Member

bors retry

@iohk-bors
Copy link
Contributor

iohk-bors bot commented Sep 3, 2021

Build succeeded:

@iohk-bors iohk-bors bot merged commit e1594b8 into master Sep 3, 2021
@iohk-bors iohk-bors bot deleted the sevanspowell/adp-1092/apply-tx-testable branch September 3, 2021 10:07
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.

2 participants