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

Allow redelegation to the same pool in Conway #4562

Merged

Conversation

paweljakubas
Copy link
Contributor

@paweljakubas paweljakubas commented Apr 23, 2024

  • Change of re-delegation logic.
  • Adjust unit tests
  • Add new unit tests
  • Add integration testing showing the case
  • Babbage: re-delegation to the same pool -> ERROR
  • Conway: re-delegation to the same pool -> ERROR
  • Conway: re-delegation to the same pool + new vote or re-vote other than recent -> OK
  • Conway: re-delegation to the same pool + vote the same again -> ERROR
  • Conway: re-delegation to the different pool + vote the same again -> OK
  • Conway: vote the same again -> ERROR
  • check joinStakePools
  • add handleDelegationVoteRequest

Comments

In order to run integration tests do the following:
(a) Babbage

just integration-tests-cabal-match TRANS_NEW_JOIN_01f

(b) Conway

just conway-integration-tests-cabal-match TRANS_NEW_JOIN_01f

Issue Number

adp 3312

@paweljakubas paweljakubas self-assigned this Apr 23, 2024
@paweljakubas paweljakubas force-pushed the paweljakubas/adp-3312/allow-redelegation-to-the-same-pool branch 3 times, most recently from bc651c6 to 8c31931 Compare April 24, 2024 15:05
Copy link
Contributor

@HeinrichApfelmus HeinrichApfelmus Apr 25, 2024

Choose a reason for hiding this comment

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

Ah, I think that this file should not be modified. 😅 The reason is that allowing or disallowing delegation is the main concern of the function guardJoin — any changes should be localized to that function or its immediate context. (The next larger context for guardJoin is the function joinStakePoolDelegationAction).

That said, we have split the stake pool delegation and the voting parts a little bit — and they are joined only here, which is not a good separation of concerns. Ideally, the logic would be joined at voteAction and handleDelegationRequest. I see two options to choose from:

  1. Make a function handleVoteAndDelegateRequest in the module Cardano.Wallet.IO.Delegation which merges the handleDelegationRequest and voteAction, and returns IO (Tx.DelegationAction, Tx.VotingAction). This function handles the interaction between the two concepts. This moves the concern out of the Server module where it doesn't belong.
  2. Go back to the previous version where we unconditionally allow joining a separate stake pool. This would simplify the implementation tremendously (at the expense of having a future change do 1 anyway.)

I leave the choice up to you. 🤓

(PS: Once the concern has been moved to handleVoteAndDelegateRequest, I will ask you to split off a pure function again, along the lines of how joinStakePoolDelegationAction has been split into a pure variant and an IO variant.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added handleVoteAndDelegateRequest 👍

}]
}|]
rTx2 <- request @(ApiConstructTransaction n) ctx
(Link.createUnsignedTransaction @'Shelley src) Default delegationJoin
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
(Link.createUnsignedTransaction @'Shelley src) Default delegationJoin

Alas, Daedalus still uses the old joinStakePool endpoint

https://cardano-foundation.github.io/cardano-wallet/api/edge/#operation/joinStakePool

Can you write the additional integration tests to be only about this endpoint?

I don't think it makes sense to test Link.createUnsignedTransaction at this level — it's a kitchen-sink function, other tests are checking that it's behavior hasn't changed too much; I think that is sufficient.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added joinStakePool tests showing the case for both Babbage and Conway era. I would argue testing in new tx workflow is also crucial as we want to be consistent here and outlaw some action (those that are redundant and incur fees)

Copy link
Contributor

Choose a reason for hiding this comment

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

I would argue testing in new tx workflow is also crucial as we want to be consistent here and outlaw some action (those that are redundant and incur fees)

Well in this case, I would argue that a user of createUnsignedTransaction is a programmer and knows what they are doing, whereas joinStakePool is a proxy for actions from Daedalus users. So the latter would benefit from not incurring unnecessary fees. But I leave it up to you.

@@ -207,21 +263,21 @@ prop_guardJoinQuit knownPoolsList dlg pid wdrl mRetirementInfo = checkCoverage
pure $ W.currentEpoch info >= W.retirementEpoch info

prop_guardQuitJoin
:: NonEmptyList PoolId
:: (Set PoolId -> WalletDelegation -> PoolId -> Maybe PoolRetirementEpochInfo -> Maybe Bool -> Either ErrCannotJoin ())
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
:: (Set PoolId -> WalletDelegation -> PoolId -> Maybe PoolRetirementEpochInfo -> Maybe Bool -> Either ErrCannotJoin ())
:: GuardQuitJoinFun

A type synonym

type GuardQuitJoinFun = Set PoolId -> WalletDelegation -> PoolId -> Maybe PoolRetirementEpochInfo -> Maybe Bool -> Either ErrCannotJoin ()

helps with understanding.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@@ -117,26 +118,36 @@ joinStakePoolDelegationAction
W.getPoolRetirementCertificate poolStatus

guardJoin
:: Set PoolId
:: Write.IsRecentEra era
=> Write.RecentEra era
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

@paweljakubas paweljakubas force-pushed the paweljakubas/adp-3312/allow-redelegation-to-the-same-pool branch from 6901504 to c61c1e8 Compare April 26, 2024 15:46
Copy link
Contributor

@HeinrichApfelmus HeinrichApfelmus left a comment

Choose a reason for hiding this comment

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

Thanks! 😊 I still have a couple of comments

Importantly, I think that there is still a corner case in joinStakePoolAction where a user may be prevented from delegating to a stake pool if they are voting but not delegating to a pool (a state of affairs which has to be brought about by a different wallet). 🤔 I believe that changing the Maybe Bool type to an enumeration type with more speaking constructors (or additional constructors) may help resolve this issue.

pure (Nothing, Nothing)
optionalDelegationAction <- forM delRequestM $
handleDelegationRequest ctx currentEpochSlotting getKnownPools
getPoolStatus withdrawal votingSameAgain
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
getPoolStatus withdrawal votingSameAgain
getPoolStatus withdrawal votingSameAgain

Indentation for clarity.

Comment on lines 205 to 206
(_,(_, dlg),_) <- W.readWallet ctx
(_, stakeKeyIsRegistered) <- db & \DBLayer{atomically,walletState} ->
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
(_,(_, dlg),_) <- W.readWallet ctx
(_, stakeKeyIsRegistered) <- db & \DBLayer{atomically,walletState} ->
(calculateWalletDelegations, stakeKeyIsRegistered) <- db & \DBLayer{atomically,walletState} ->

Could you remove the call to W.readWallet here, and instead inline the computation of dlg from the result of readDelegation, like this:

    currentEpochSlotting <- getCurrentEpochSlotting nl
    let dlg = calculateWalletDelegations currentEpochSlotting

This improves the separation of concerns — modules that are higher in the hierarchy (Cardano.Wallet) should be assembled from children that are lower in the hierarchy (Cardano.Wallet.*), not the other way round.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, thanks

Comment on lines 402 to 403
(_,(_, dlg),_) <- W.readWallet ctx

Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
(_,(_, dlg),_) <- W.readWallet ctx

As in the other comment — for better separation of concerns, could you compute dlg by inlining the call to W.readWallet here? Compared to the situation above, the value currentEpochSlotting is now already in scope.

properties:
message:
type: string
description: May occur in Conway onwards when the same vote was casted.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
description: May occur in Conway onwards when the same vote was casted.
description: May occur in Conway onwards when the same vote was cast.

The past participle is "cast".

properties:
message:
type: string
description: May occur in Conway onwards when a given poolId matches the current delegation preferences of the wallet's account and also the same vote was casted.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
description: May occur in Conway onwards when a given poolId matches the current delegation preferences of the wallet's account and also the same vote was casted.
description: May occur in Conway onwards when a given poolId matches the current delegation preferences of the wallet's account and also the same vote was cast.

Nothing ->
pure Nothing
(optionalDelegationAction, optionalVoteAction) <- liftIO $
IODeleg.handleDelegationVoteRequest wrk
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

-> WalletDelegation
-> PoolId
-> Maybe PoolRetirementEpochInfo
-> Maybe Bool
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
-> Maybe Bool
-> VoteChange

I find the meaning of this Maybe Bool argument difficult to understand ("boolean blindness"). Given the correctness issue in joinStakePool, I would like to have an type with more speaking names here. How about

data VoteChange = Unimportant | NotVotingToVoting | VotingToAny

Hm, actually, I don't now what to put here. 🤔

I think that joinStakePoolDelegationAction uses the following logic:

  • If the wallet is not delegating, it's ok.
  • If the wallet is delegating, but not voting, it's ok.
  • If the wallet is delegating, and already voting — to abstain or to any other DRep —, then guardJoin should disallow the action.

However, for handleDelegationVoteRequest, the logic is different

  • If the wallet is delegating, and already voting — to abstain or to any other DRep —, then it's ok unless the new vote is exactly the same as the old vote.

If you ask me, the easiest solution is probably to let handleDelegationVoteRequest allow everything, so that constructUnsignedTransaction will always succeed. 😅

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added
data VoteRequest = NotVotedYet | VotedSameLikeBefore | VotedDifferently
to address logic refactoring

}]
}|]
rTx2 <- request @(ApiConstructTransaction n) ctx
(Link.createUnsignedTransaction @'Shelley src) Default delegationJoin
Copy link
Contributor

Choose a reason for hiding this comment

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

I would argue testing in new tx workflow is also crucial as we want to be consistent here and outlaw some action (those that are redundant and incur fees)

Well in this case, I would argue that a user of createUnsignedTransaction is a programmer and knows what they are doing, whereas joinStakePool is a proxy for actions from Daedalus users. So the latter would benefit from not incurring unnecessary fees. But I leave it up to you.

waitForTxStatus ctx w InLedger . getResponse
=<< joinStakePool @n ctx (SpecificPool pool) (w, fixturePassphrase)

eventually "Wallet is delegating to pool and voting abstain" $ do
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

r <- joinStakePool @n ctx (SpecificPool pool) (w, fixturePassphrase)
verify r
[ expectResponseCode HTTP.status403

Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change

(stray newline)

@paweljakubas paweljakubas force-pushed the paweljakubas/adp-3312/allow-redelegation-to-the-same-pool branch from c61c1e8 to 0a3c191 Compare April 30, 2024 10:48
@paweljakubas paweljakubas force-pushed the paweljakubas/adp-3312/allow-redelegation-to-the-same-pool branch from 41c157e to c4f9079 Compare April 30, 2024 12:57
@paweljakubas paweljakubas force-pushed the paweljakubas/adp-3312/allow-redelegation-to-the-same-pool branch from c4f9079 to bd71f61 Compare April 30, 2024 13:01
fix DelegationSpec
Copy link
Contributor

@HeinrichApfelmus HeinrichApfelmus left a comment

Choose a reason for hiding this comment

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

Looking good now, thank you! 😊 One last request on grammar, though.

@@ -75,6 +82,9 @@ data DelegationRequest
-- ^ Stop delegating if the wallet is delegating.
deriving (Eq, Show)

data VoteRequest = NotVotedYet | VotedSameLikeBefore | VotedDifferently
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
data VoteRequest = NotVotedYet | VotedSameLikeBefore | VotedDifferently
data VoteRequest = NotVotedYet | VotedSameAsBefore | VotedDifferently

This is grammatically better. 😅

@paweljakubas paweljakubas added this pull request to the merge queue Apr 30, 2024
Merged via the queue into master with commit cb43316 Apr 30, 2024
3 checks passed
@paweljakubas paweljakubas deleted the paweljakubas/adp-3312/allow-redelegation-to-the-same-pool branch April 30, 2024 15:47
WilliamKingNoel-Bot pushed a commit that referenced this pull request Apr 30, 2024
…il in a few bullet points the work accomplished in this PR. Before you submit, don't forget to: CODE-OF-CONDUCT.md CONTRIBUTING.md LICENSE MAINTAINERS.md README.md cabal.project configs default.nix docker-compose.yml docs flake.lock flake.nix floskell.json fourmolu.yaml hie-direnv.yaml hie.yaml justfile lib nix prototypes reports scripts shell.nix specifications test touch.me.CI weeder.dhall Make sure the GitHub PR fields are correct: ✓ Set a good Title for your PR. ✓ Assign yourself to the PR. ✓ Assign one or more reviewer(s). ✓ Link to a Jira issue, and/or other GitHub issues or PRs. ✓ In the PR description delete any empty sections and all text commented in <!--, so that this text does not appear in merge commit messages. CODE-OF-CONDUCT.md CONTRIBUTING.md LICENSE MAINTAINERS.md README.md cabal.project configs default.nix docker-compose.yml docs flake.lock flake.nix floskell.json fourmolu.yaml hie-direnv.yaml hie.yaml justfile lib nix prototypes reports scripts shell.nix specifications test touch.me.CI weeder.dhall Don't waste reviewers' time: ✓ If it's a draft, select the Create Draft PR option. ✓ Self-review your changes to make sure nothing unexpected slipped through. CODE-OF-CONDUCT.md CONTRIBUTING.md LICENSE MAINTAINERS.md README.md cabal.project configs default.nix docker-compose.yml docs flake.lock flake.nix floskell.json fourmolu.yaml hie-direnv.yaml hie.yaml justfile lib nix prototypes reports scripts shell.nix specifications test touch.me.CI weeder.dhall Try to make your intent clear: ✓ Write a good Description that explains what this PR is meant to do. ✓ Jira will detect and link to this PR once created, but you can also link this PR in the description of the corresponding Jira ticket. ✓ Highlight what Testing you have done. ✓ Acknowledge any changes required to the Documentation. --> - [x] Change of re-delegation logic. - [x] Adjust unit tests - [x] Add new unit tests - [x] Add integration testing showing the case - [x] Babbage: re-delegation to the same pool -> ERROR - [x] Conway: re-delegation to the same pool -> ERROR - [x] Conway: re-delegation to the same pool + new vote or re-vote other than recent -> OK - [x] Conway: re-delegation to the same pool + vote the same again -> ERROR - [x] Conway: re-delegation to the different pool + vote the same again -> OK - [x] Conway: vote the same again -> ERROR - [x] check joinStakePools - [x] add handleDelegationVoteRequest ### Comments In order to run integration tests do the following: (a) Babbage ``` just integration-tests-cabal-match TRANS_NEW_JOIN_01f ``` (b) Conway ``` just conway-integration-tests-cabal-match TRANS_NEW_JOIN_01f ``` <!-- Additional comments, links, or screenshots to attach, if any. --> ### Issue Number adp 3312 <!-- Reference the Jira/GitHub issue that this PR relates to, and which requirements it tackles. Note: Jira issues of the form ADP- will be auto-linked. --> Source commit: cb43316
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.

None yet

2 participants