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

Add WalletState, a pure model for the entire wallet #3099

Merged
merged 3 commits into from
Feb 9, 2022

Conversation

HeinrichApfelmus
Copy link
Contributor

Issue number

ADP-1375

Overview

Previous work in epic ADP-1043 introduced delta encodings, DBVars, and an embedding of the wallet state and its delta encodings into a database table. It's time to integrate these tools with the wallet code. To facilitate code review, the integration proceeds in a sequence of refactorings that do not change functionality and pass all unit tests.

In this step, we introduce a data type WalletState which represents the entire wallet state — not just the most recent checkpoint, but all checkpoints.

data WalletState s = WalletState
    { prologue    :: Prologue s
    , checkpoints :: Checkpoints (BlockHeader, UTxO, Discoveries s)
    } 

The states for the different wallets currently stored in the walletsVar DBVar. Eventually, the data type will become the purely functional in-memory representation of all the data associated with a wallet (though perhaps with a different name). The DBLayer type will eventually be replaced by a Store for values of this type.

The introduction of this type has become possible thanks to the previous separation of the address discovery state s into Prologue s and Discoveries s (PRs #3056, #3068, #3073).

Details

  • As the queries in the DBLayer will more and more become queries on the in-memory cache walletsVar instead of queries on the database table, I have begun to add unit tests for the database tables. Here, I have added a property test for loadPrologue and insertPrologue. Eventually, these separate tests will become a single generic test for a Store of Table.

Comments

  • One of the next steps will be to replace UTxO in the above type by its delta encoding DeltaUTxO. This will reduce the memory footprint of the in-memory representation.
  • The Cardano.Wallet.DB.Model module actually implements a pure model for the entire wallet state, including TxHistory etc. When extending the type, we can scavenge from there.

@HeinrichApfelmus HeinrichApfelmus changed the title Heinrich apfelmus/adp 1043/wallet state Add WalletState, a pure model for the entire wallet Jan 27, 2022
@HeinrichApfelmus HeinrichApfelmus force-pushed the HeinrichApfelmus/ADP-1043/WalletState branch 2 times, most recently from 222cbe0 to c649f57 Compare February 1, 2022 13:57
let (prologue, wcp) = fromWallet cp
slot = getSlot wcp
delta = Just $ Adjust wid
[ UpdateCheckpoints $ PutCheckpoint slot wcp
Copy link
Contributor

Choose a reason for hiding this comment

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

so basically this is a moment we do persistence, ie. store what is in cp and write to the DB, right? And thanks to construction of Adjust we will only write "difference" data, right? not all data from scratch?

Copy link
Contributor Author

@HeinrichApfelmus HeinrichApfelmus Feb 3, 2022

Choose a reason for hiding this comment

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

Essentially yes. More specifically, at this point in the code, we specify the delta that should be applied to the Store.

The actual update is performed by the updateS function of the Store here:

https://github.com/input-output-hk/cardano-wallet/blob/c649f5730632fa4498750dc6b111a43b380e42c2/lib/core/src/Cardano/Wallet/DB/Sqlite/CheckpointsOld.hs#L258-L259

Currently, this function still writes the entire UTxO and discovered addresses for the given checkpoint. In order to change that, we need to a) change the constructor PutCheckpoint to something like PutDifferenceToPreviousCheckpoint and b) change the database schema to accommodate this. One of the next few pull requests will do a), and one of the last commits in this series will be about b).

That said, it is true that thanks to Adjust wid, only the checkpoint for one wallet will be written — not all of them.

Copy link
Contributor

Choose a reason for hiding this comment

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

ok, thanks for clarification

:: forall s. PersistAddressBook s
=> W.WalletId
-> Store (SqlPersistT IO) (DeltaWalletState s)
mkStoreWallet wid =
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if we can introduce reasonable invariant to make sure DeltaWalletState s is legal/not corrupt (especially in updateS). It could be not the case now (as we have prologue and checkpoints only), but when new things like tx history, pending txs, etc. will be added maybe it is going to be crucial this is the case. Do you have opinion on that?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's a good question: As deltas are now first-class values, how can we ensure that deltas da are always paired with the Base da value from which they were derived? 🤔

Unfortunately, I don't have a good answer to that yet, but a couple of thoughts:

  1. The status quo has the same problem, so at least we are not doing worse. For example, random calls to putTxHistory could also mess up the database, it's the responsibility of the caller of putTxHistory to make sure that the call is appropriate for the current state of the database.

  2. Maybe first-class deltas that can be applied to different values is something we actually want?

    • For example, the DeltaSet1 type from Data.Delta satisfies interesting algebraic properties such as apply [Insert a, Delete a] = apply (Insert a).
    • In this light, it is sensible to design the delta type so that applying it will never fail. For example, Data.Set.delete never fails to return a result, even if the element that is to be deleted is not in the set. An alternative design would have been Data.Set.delete :: Set a -> Maybe (Set a), but that would have been more cumbersome to use.
    • Given a partial function changeP :: b -> Maybe b, we can always turn it into a total function changeT :: b -> b by defining changeT b = fromMaybe b $ changeP b. So, the idea would be to always use the second definition when designing a delta type and implementing apply.
  3. In the other direction, perhaps it is possible to use the type system to ensure that each delta is paired with the value? In a dependently typed language, one could make the value part of the delta type. That would be very cumbersome, though. But perhaps it is possible to simulate this with some type "tags" that keep track of which delta belongs to which value?

    newtype Delta da x y
    newtype Base da x
    
    apply :: Delta da x y -> Base da x -> Base da y
    

    Here, the tag x ensures that the base value and the delta belong together. Chances are that managing these tags will involve existential quantification and be somewhat cumbersome as well, but maybe there is some low-hanging fruit here.

For now, I'm leaning towards 1 and 2 — making sure that deltas can always be applied and use property tests to make that sequences of deltas are correctly generated.

Copy link
Contributor

Choose a reason for hiding this comment

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

indeed, 1 and 2 sounds reasonable

Copy link
Contributor

@paweljakubas paweljakubas left a comment

Choose a reason for hiding this comment

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

lgtm! Another step progressing towards new DB layer. It would be good if @rvl also give it another look before merging. This PR seems to have a tiny risk of destroying anything, we still write whole utxos and discovered addresses as before, schema is intact. I am very fond of how new storage abstractions eat out bite by bite the legacy solution:-)

@HeinrichApfelmus HeinrichApfelmus force-pushed the HeinrichApfelmus/ADP-1043/WalletState branch from 0ee8753 to 2691be8 Compare February 4, 2022 16:08
@HeinrichApfelmus
Copy link
Contributor Author

bors try

iohk-bors bot added a commit that referenced this pull request Feb 7, 2022
@piotr-iohk
Copy link
Contributor

@HeinrichApfelmus I've run e2e suite also on top of this branch and all passed. So lgtm.

@iohk-bors
Copy link
Contributor

iohk-bors bot commented Feb 7, 2022

try

Timed out.

@HeinrichApfelmus
Copy link
Contributor Author

bors merge

iohk-bors bot added a commit that referenced this pull request Feb 7, 2022
3099: Add `WalletState`, a pure model for the entire wallet r=HeinrichApfelmus a=HeinrichApfelmus

### Issue number

ADP-1375

### Overview

Previous work in epic ADP-1043 introduced delta encodings, DBVars, and an embedding of the wallet state and its delta encodings into a database table. It's time to integrate these tools with the wallet code. To facilitate code review, the integration proceeds in a sequence of refactorings that do not change functionality and pass all unit tests.

In this step, we introduce a data type `WalletState` which represents the entire wallet state — not just the most recent checkpoint, but _all_ checkpoints.
```
data WalletState s = WalletState
    { prologue    :: Prologue s
    , checkpoints :: Checkpoints (BlockHeader, UTxO, Discoveries s)
    } 
```
The states for the different wallets currently stored in the `walletsVar` DBVar. Eventually, the data type will become the purely functional in-memory representation of all the data associated with a wallet (though perhaps with a different name). The `DBLayer` type will eventually be replaced by a `Store` for values of this type.

The introduction of this type has become possible thanks to the previous separation of the address discovery state `s` into `Prologue s` and `Discoveries s` (PRs #3056, #3068, #3073).

### Details

* As the queries in the `DBLayer` will more and more become queries on the in-memory cache `walletsVar` instead of queries on the database table, I have begun to add unit tests for the database tables. Here, I have added a property test for `loadPrologue` and `insertPrologue`. Eventually, these separate tests will become a single generic test for a `Store` of `Table`.

### Comments

* One of the next steps will be to replace `UTxO` in the above type by its delta encoding `DeltaUTxO`. This will reduce the memory footprint of the in-memory representation.
* The `Cardano.Wallet.DB.Model` module actually implements a pure model for the entire wallet state, including TxHistory etc. When extending the type, we can scavenge from there.

Co-authored-by: Heinrich Apfelmus <[email protected]>
@iohk-bors
Copy link
Contributor

iohk-bors bot commented Feb 7, 2022

Build failed:

 src/Test/Integration/Framework/DSL.hs:1019:51:
  7) API Specifications, SHELLEY_TRANSACTIONS, SHELLEY_TX_REDEEM_01 - Can redeem rewards from self
       not yet
       Waited longer than 90s to resolve action: "waitForNextEpoch: goes to next epoch".

  To rerun use: --match "/API Specifications/SHELLEY_TRANSACTIONS/SHELLEY_TX_REDEEM_01 - Can redeem rewards from self/"

Over 100 errors. Seems the connection or cluster completely broke at some point.

#3123

@HeinrichApfelmus
Copy link
Contributor Author

bors retry

iohk-bors bot added a commit that referenced this pull request Feb 8, 2022
3099: Add `WalletState`, a pure model for the entire wallet r=HeinrichApfelmus a=HeinrichApfelmus

### Issue number

ADP-1375

### Overview

Previous work in epic ADP-1043 introduced delta encodings, DBVars, and an embedding of the wallet state and its delta encodings into a database table. It's time to integrate these tools with the wallet code. To facilitate code review, the integration proceeds in a sequence of refactorings that do not change functionality and pass all unit tests.

In this step, we introduce a data type `WalletState` which represents the entire wallet state — not just the most recent checkpoint, but _all_ checkpoints.
```
data WalletState s = WalletState
    { prologue    :: Prologue s
    , checkpoints :: Checkpoints (BlockHeader, UTxO, Discoveries s)
    } 
```
The states for the different wallets currently stored in the `walletsVar` DBVar. Eventually, the data type will become the purely functional in-memory representation of all the data associated with a wallet (though perhaps with a different name). The `DBLayer` type will eventually be replaced by a `Store` for values of this type.

The introduction of this type has become possible thanks to the previous separation of the address discovery state `s` into `Prologue s` and `Discoveries s` (PRs #3056, #3068, #3073).

### Details

* As the queries in the `DBLayer` will more and more become queries on the in-memory cache `walletsVar` instead of queries on the database table, I have begun to add unit tests for the database tables. Here, I have added a property test for `loadPrologue` and `insertPrologue`. Eventually, these separate tests will become a single generic test for a `Store` of `Table`.

### Comments

* One of the next steps will be to replace `UTxO` in the above type by its delta encoding `DeltaUTxO`. This will reduce the memory footprint of the in-memory representation.
* The `Cardano.Wallet.DB.Model` module actually implements a pure model for the entire wallet state, including TxHistory etc. When extending the type, we can scavenge from there.

Co-authored-by: Heinrich Apfelmus <[email protected]>
@iohk-bors
Copy link
Contributor

iohk-bors bot commented Feb 8, 2022

Build failed:

  src/Test/Integration/Scenario/API/Shelley/TransactionsNew.hs:2118:54:
  1) API Specifications, NEW_SHELLEY_TRANSACTIONS, Plutus scenarios, withdrawal
       uncaught exception: ProcessHasExited
       ProcessHasExited "cardano-cli failed: Command failed: transaction submit  Error: Error while submitting tx: ShelleyTxValidationError ShelleyBasedEraAlonzo (ApplyTxError [DelegsFailure (DelplFailure (DelegFailure (StakeKeyAlreadyRegisteredDELEG (ScriptHashObj (ScriptHash \"1b8374d0b294a4a3399ff25f06a0707b16214f1e9b67cf89ba2454a6\")))))])\n" (ExitFailure 1)

  To rerun use: --match "/API Specifications/NEW_SHELLEY_TRANSACTIONS/Plutus scenarios/withdrawal/"

#3057

* Separates the `Prologue` from the `Discoveries` and only stores the latter in the `Checkpoints`.
* Intended to include pending transactions, TxHistory, … in the future
@HeinrichApfelmus HeinrichApfelmus force-pushed the HeinrichApfelmus/ADP-1043/WalletState branch from 2691be8 to cededf8 Compare February 8, 2022 11:37
@HeinrichApfelmus
Copy link
Contributor Author

bors r+

iohk-bors bot added a commit that referenced this pull request Feb 8, 2022
3099: Add `WalletState`, a pure model for the entire wallet r=HeinrichApfelmus a=HeinrichApfelmus

### Issue number

ADP-1375

### Overview

Previous work in epic ADP-1043 introduced delta encodings, DBVars, and an embedding of the wallet state and its delta encodings into a database table. It's time to integrate these tools with the wallet code. To facilitate code review, the integration proceeds in a sequence of refactorings that do not change functionality and pass all unit tests.

In this step, we introduce a data type `WalletState` which represents the entire wallet state — not just the most recent checkpoint, but _all_ checkpoints.
```
data WalletState s = WalletState
    { prologue    :: Prologue s
    , checkpoints :: Checkpoints (BlockHeader, UTxO, Discoveries s)
    } 
```
The states for the different wallets currently stored in the `walletsVar` DBVar. Eventually, the data type will become the purely functional in-memory representation of all the data associated with a wallet (though perhaps with a different name). The `DBLayer` type will eventually be replaced by a `Store` for values of this type.

The introduction of this type has become possible thanks to the previous separation of the address discovery state `s` into `Prologue s` and `Discoveries s` (PRs #3056, #3068, #3073).

### Details

* As the queries in the `DBLayer` will more and more become queries on the in-memory cache `walletsVar` instead of queries on the database table, I have begun to add unit tests for the database tables. Here, I have added a property test for `loadPrologue` and `insertPrologue`. Eventually, these separate tests will become a single generic test for a `Store` of `Table`.

### Comments

* One of the next steps will be to replace `UTxO` in the above type by its delta encoding `DeltaUTxO`. This will reduce the memory footprint of the in-memory representation.
* The `Cardano.Wallet.DB.Model` module actually implements a pure model for the entire wallet state, including TxHistory etc. When extending the type, we can scavenge from there.

Co-authored-by: Heinrich Apfelmus <[email protected]>
Co-authored-by: IOHK <[email protected]>
@iohk-bors
Copy link
Contributor

iohk-bors bot commented Feb 8, 2022

Build failed:

Encountered error while migrating Pantry database:
--
  | SQLite3 returned ErrorNotFound while attempting to perform prepare "SELECT sql FROM sqlite_master WHERE type='table' AND name=?": database disk image is malformed
  | Please report this on https://github.com/commercialhaskell/stack/issues
  | As a workaround you may delete Pantry database in /build/cardano-wallet.stack/pantry/pantry.sqlite3 triggering its recreation.
  | error: Command exited with code 1!

#3120

@HeinrichApfelmus
Copy link
Contributor Author

bors retry

iohk-bors bot added a commit that referenced this pull request Feb 9, 2022
3099: Add `WalletState`, a pure model for the entire wallet r=HeinrichApfelmus a=HeinrichApfelmus

### Issue number

ADP-1375

### Overview

Previous work in epic ADP-1043 introduced delta encodings, DBVars, and an embedding of the wallet state and its delta encodings into a database table. It's time to integrate these tools with the wallet code. To facilitate code review, the integration proceeds in a sequence of refactorings that do not change functionality and pass all unit tests.

In this step, we introduce a data type `WalletState` which represents the entire wallet state — not just the most recent checkpoint, but _all_ checkpoints.
```
data WalletState s = WalletState
    { prologue    :: Prologue s
    , checkpoints :: Checkpoints (BlockHeader, UTxO, Discoveries s)
    } 
```
The states for the different wallets currently stored in the `walletsVar` DBVar. Eventually, the data type will become the purely functional in-memory representation of all the data associated with a wallet (though perhaps with a different name). The `DBLayer` type will eventually be replaced by a `Store` for values of this type.

The introduction of this type has become possible thanks to the previous separation of the address discovery state `s` into `Prologue s` and `Discoveries s` (PRs #3056, #3068, #3073).

### Details

* As the queries in the `DBLayer` will more and more become queries on the in-memory cache `walletsVar` instead of queries on the database table, I have begun to add unit tests for the database tables. Here, I have added a property test for `loadPrologue` and `insertPrologue`. Eventually, these separate tests will become a single generic test for a `Store` of `Table`.

### Comments

* One of the next steps will be to replace `UTxO` in the above type by its delta encoding `DeltaUTxO`. This will reduce the memory footprint of the in-memory representation.
* The `Cardano.Wallet.DB.Model` module actually implements a pure model for the entire wallet state, including TxHistory etc. When extending the type, we can scavenge from there.

Co-authored-by: Heinrich Apfelmus <[email protected]>
Co-authored-by: IOHK <[email protected]>
@iohk-bors
Copy link
Contributor

iohk-bors bot commented Feb 9, 2022

Build failed:

  test/unit/Cardano/Wallet/Api/ServerSpec.hs:141:5: 
  1) Cardano.Wallet.Api.Server, API Server, handles bad host name
       uncaught exception: IOException of type UserError
       user error (Network.Socket.gai_strerror not supported: 11002)

  To rerun use: --match "/Cardano.Wallet.Api.Server/API Server/handles bad host name/"

#3121

@HeinrichApfelmus
Copy link
Contributor Author

bors retry

@iohk-bors
Copy link
Contributor

iohk-bors bot commented Feb 9, 2022

Build succeeded:

@iohk-bors iohk-bors bot merged commit 2423a01 into master Feb 9, 2022
@iohk-bors iohk-bors bot deleted the HeinrichApfelmus/ADP-1043/WalletState branch February 9, 2022 16:03
WilliamKingNoel-Bot pushed a commit that referenced this pull request Feb 9, 2022
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.

3 participants