-
Notifications
You must be signed in to change notification settings - Fork 217
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 pool DB operation listRetiredPools
.
#2024
Conversation
be4151a
to
ee71e8a
Compare
cd555c8
to
fcbc5f2
Compare
listRetiredPools
.listRetiredPools
.
let safeCast (Single poolId, Single retirementEpoch) = | ||
PoolRetirementCertificate | ||
<$> fromPersistValue poolId | ||
<*> fromPersistValue retirementEpoch |
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.
Hmmm. Why is the unsafe query needed here? We should stick with the persistent DSL as much as possible and only resort to unsafe queries when the DSL is too limiting or becomes a real performance impediment.
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.
@KtorZ wrote:
Why is the unsafe query needed here?
When determining the current set of effective retirements, we need to combine results from two different tables:
pool_registrations
pool_retirements
This is because registration and retirement certificates can cancel each other out.
The only way to find out whether a retirement certificate is still in effect is to verify that there are no other retirement certificates or registration certificates that cancel it out.
we should only resort to unsafe queries when the DSL is too limiting
I do agree with this.
However, the implementation in this PR determines the set of currently-active retirement certificates with a single query that uses:
- the SQL
UNION
function; - the SQL
row_number
window function.
As far as I know, persistent
supports neither of the above functions (at least out of the box).
Furthermore, defining this query in terms of an SQL view can help us if we need to debug a real pool database. By opening the database and fetching the active_pool_retirements
view, we can immediately see which retirement certificates the wallet currently considers to be active.
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.
fcbc5f2
to
999db87
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Extend `prop_pool{Registration,Retirement}` to call `listRetiredPools`.
Extend `prop_rollbackRetirement` to call `listRetiredPools`.
Extend `prop_readPoolLifeCycleStatus` to call `listRetiredPools`.
15814ae
to
65b143d
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Use different serialization orders when serializing certificate sequences from multiple pools in property `prop_listRetiredPools_multiplePools_multipleCerts`. This gives us extra confidence that the `activeRetirementCertificates` view is handling the `(slot, slot_internal_index)` order correctly.
65b143d
to
c59e8e8
Compare
bors try |
tryBuild succeeded |
assertWith "entriesIn == entriesOut" | ||
$ entriesIn == entriesOut | ||
assertWith "no pools are marked to retire" | ||
$ null poolsMarkedToRetire |
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.
👍
assertWith "all pools are marked to retire" | ||
$ (==) | ||
(Set.fromList $ snd <$> entriesIn) | ||
(Set.fromList poolsMarkedToRetire) |
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.
👍
mRetrievedCertificatePublication | ||
mExpectedCertificatePublication | ||
assertWith "pool is not marked to retire" $ | ||
null poolsMarkedToRetire |
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.
👍
lifeCycleStatuses <- run $ atomically $ do | ||
mapM readPoolLifeCycleStatus allPoolIds | ||
let poolsMarkedToRetire = catMaybes $ | ||
getPoolRetirementCertificate <$> lifeCycleStatuses |
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'd be better not to rely on lifeCycleStatuses
to determine which pools are supposed to be retired. The risk is to have a similar logic bug in both lifeCycleStatuses
and listRetiredPools
that remain uncaught, especially if the same approach is used behind the scene.
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'd be better not to rely on
lifeCycleStatuses
to determine which pools are supposed to be retired. The risk is to have a similar logic bug in bothlifeCycleStatuses
andlistRetiredPools
that remain uncaught, especially if the same approach is used behind the scene.
I definitely agree, this is indeed a risk.
To counteract this risk:
-
We do have a suite of tests specifically aimed at
determinePoolLifeCycleStatus
:prop_determinePoolLifeCycleStatus_orderCorrect
prop_determinePoolLifeCycleStatus_neverRegistered
prop_determinePoolLifeCycleStatus_differentPools
-
The definition of
listRetiredPools
uses a completely different approach in the form of a handwritten query. In the property test for this query, we check it against thedeterminePoolLifeCycleStatus
function, which we consider to be the source of truth.
bors r+ |
2024: Add pool DB operation `listRetiredPools`. r=jonathanknowles a=jonathanknowles # Issue Number #2016 # Overview This PR: - [x] Adds a `listRetiredPools` operation to the pool DB layer:<br><br> ```hs listRetiredPools :: EpochNo -> stm [PoolId] -- ^ List all pools with an active retirement epoch that is -- earlier than or equal to the specified epoch. ```` - [x] Provides the following implementations of `listRetiredPools`. - [x] `Pool.DB.MVar` - [x] `Pool.DB.SQLite`. The SQLite implementation is based on an **SQL view** ([see definition](https://github.com/input-output-hk/cardano-wallet/pull/2024/files#diff-08afa2852f50fa31f5757942d004ce8aR513)) which determines the currently-active set of retirement certificates. <details><summary>Click to view sample data</summary> ![active_pool_retirements](https://user-images.githubusercontent.com/206319/90096831-13b1a280-dd67-11ea-9612-fd1c4a825fb6.png) </details> # Property Testing This PR also adds property tests to check that: - [x] `listRetiredPools` **does not** return: - [x] pools that have never had retirement certificates associated with them. - [x] pools that have had their most-recently-published retirement certificates revoked by subsequent re-registration certificates. - [x] pools with effective retirement epochs that are later than the specified epoch. - [x] `listRetiredPools` returns the correct set of pools in the following contexts: - [x] where there are **multiple pools**. - [x] where there are **multiple certificates per pool** (registrations, retirements, re-registrations, re-retirements). - [x] where the publications of certificates affecting different pools are **interleaved together in time**. # Mutation Testing To increase confidence that the `activePoolRetirements` view is correctly defined, I have manually performed the following mutations and verified that the `prop_listRetiredPools_multiplePools_multipleCerts` property fails appropriately. ### Mutation 1 ```patch - WINDOW w AS (ORDER BY pool_id, slot desc, slot_internal_index desc) + WINDOW w AS (ORDER BY pool_id, slot , slot_internal_index desc) ) GROUP BY pool_id ``` ### Mutation 2 ```patch - WINDOW w AS (ORDER BY pool_id, slot desc, slot_internal_index desc) + WINDOW w AS (ORDER BY pool_id, slot desc, slot_internal_index ) ) GROUP BY pool_id ``` ### Mutation 3 ```patch - WINDOW w AS (ORDER BY pool_id, slot desc, slot_internal_index desc) + WINDOW w AS (ORDER BY pool_id, slot , slot_internal_index ) ) GROUP BY pool_id ``` ### Mutation 4 ```patch - WINDOW w AS (ORDER BY pool_id, slot desc, slot_internal_index desc) + WINDOW w AS (ORDER BY pool_id, slot desc, ) ) GROUP BY pool_id ``` Co-authored-by: Jonathan Knowles <[email protected]>
Build failed |
bors retry |
Build succeeded |
2047: Add pool DB operation `removeRetiredPools`. r=jonathanknowles a=jonathanknowles # Issue Number #2018 # Overview Building on the work added in PRs #2024 and #2038, this PR: - [x] Adds a `removeRetiredPools` operation to the pool DB layer:<br><br> ```hs removeRetiredPools :: EpochNo -> stm [PoolRetirementCertificate] -- ^ Remove all pools with an active retirement epoch that is earlier -- than or equal to the specified epoch. -- -- Returns the retirement certificates of the pools that were removed. ``` - [x] Writes a bracketed `MsgRemovingRetiredPools` message to the log whenever the `removeRetiredPools` operation is called:<br><br> ``` Looking for pools that retired in or before epoch 1000. Removing the following retired pools: Pool 4355a46b with retirement epoch 999 Pool 53c234e5 with retirement epoch 999 Pool 121cfccd with retirement epoch 1000 Pool 917df332 with retirement epoch 1000 Finished removing retired pools. ``` # Further Work Before garbage collection can actually happen, the wallet needs to call `removeRetiredPools` at appropriate intervals. This work is deferred to a future PR. See issue #2019. Co-authored-by: Jonathan Knowles <[email protected]>
Issue Number
#2016
Overview
This PR:
listRetiredPools
operation to the pool DB layer:listRetiredPools
.Pool.DB.MVar
Pool.DB.SQLite
.The SQLite implementation is based on an SQL view (see definition) which determines the currently-active set of retirement certificates.
Click to view sample data
Property Testing
This PR also adds property tests to check that:
listRetiredPools
does not return:listRetiredPools
returns the correct set of pools in the following contexts:(registrations, retirements, re-registrations, re-retirements).
Mutation Testing
To increase confidence that the
activePoolRetirements
view is correctly defined, I have manually performed the following mutations and verified that theprop_listRetiredPools_multiplePools_multipleCerts
property fails appropriately.Mutation 1
Mutation 2
Mutation 3
Mutation 4