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

Voting WRITE side and api logic #4438

Merged
merged 22 commits into from
Feb 16, 2024

Conversation

paweljakubas
Copy link
Contributor

  • write side of certs (from wallet to ledger)
  • logic - is conway era
  • logic for withdrawals in conway
  • vote to abstain logic

Comments

Issue Number

adp-3212

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.

Thank you for the pull request!

First round of comments.

The most important request is that I do not wish the "delegate to abstain" logic be part of the constructTransaction endpoint — I want it to be part of the joinStakePool endpoint.

lib/wallet/src/Cardano/Wallet.hs Outdated Show resolved Hide resolved
Comment on lines 2642 to 2682
checkingIfVoted
:: DBLayer IO s
-> NetworkLayer IO block
-> ExceptT ErrConstructTx IO ()
checkingIfVoted DBLayer{..} nw = do
era <- liftIO $ currentNodeEra nw
case era of
Cardano.AnyCardanoEra Cardano.ConwayEra -> do
voted <- liftIO $ atomically (alreadyVoted walletState)
if voted then
pure ()
else
throwE ErrConstructTxWithdrawalWithoutVoting
_ -> pure ()

alreadyVoted
:: Functor stm
=> DBVar stm (DeltaWalletState s)
-> stm Bool
alreadyVoted walletState =
Dlgs.isVoting . view #delegations
<$> readDBVar 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
checkingIfVoted
:: DBLayer IO s
-> NetworkLayer IO block
-> ExceptT ErrConstructTx IO ()
checkingIfVoted DBLayer{..} nw = do
era <- liftIO $ currentNodeEra nw
case era of
Cardano.AnyCardanoEra Cardano.ConwayEra -> do
voted <- liftIO $ atomically (alreadyVoted walletState)
if voted then
pure ()
else
throwE ErrConstructTxWithdrawalWithoutVoting
_ -> pure ()
alreadyVoted
:: Functor stm
=> DBVar stm (DeltaWalletState s)
-> stm Bool
alreadyVoted walletState =
Dlgs.isVoting . view #delegations
<$> readDBVar walletState
assertIsVoting
:: DBLayer IO s
-> RecentEra era
-> ExceptT ErrConstructTx IO ()
assertIsVoting DBLayer{..} = \case
RecentEraConway -> do
voting <- liftIO . atomically . fmap isVoting $ readDBVar walletState
if voting then
pure ()
else
throwE ErrConstructTxWithdrawalWithoutVoting
_ -> pure ()
isVoting :: WalletState -> Bool
isVoting = Dlgs.isVoting . view #delegations

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

Comment on lines 2787 to 2814
Nothing ->
liftIO $ W.handleVotingWhenMissingInConway nl db
(isJust (body ^. #delegations))
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
Nothing ->
liftIO $ W.handleVotingWhenMissingInConway nl db
(isJust (body ^. #delegations))
Nothing -> pure Nothing

⚠️

I have come to the conclusion that the "Vote to abstain" feature is best implemented as part of the joinStakePool endpoint. The reason is that

  • The intent of constructTransaction is to construct a transaction from the given JSON data. It performs a bit of validation, but the JSON input is essentially a one-to-one representation of a Cardano transaction — no "magic".
  • The intent of joinStakePool function is to perform the necessary steps to join a stake pool — this includes "magic" such as automatically voting to a DRep.

In general, the Cardano.Wallet.Api.Http.Shelley.Server module is not a good place for business logic such as handleVotingWhenMissingInConway. The main concern of this module is to parse and partially validate JSON data. We need to separate concerns properly in order to keep our code manageable. I'm afraid, but this module has grown way past manageable — and this slows down development of new features and dependency updates.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

When it comes to constructTransaction endpoint this is part of NEW transaction flow where we have construct/sign/decode/submit that contains all new features that were are in the last two years. joinStakePool like postTransaction are legacy endpoints (they have construct/sign/submit in one step). Of course I understand that we might ALSO support joinStakePool having conway voting feature as daedalus might use it (although not sure if they have not adopted new tx workflow). Nevertheless, I do think voting should be primarly supported in constructTransaction....

Copy link
Contributor

Choose a reason for hiding this comment

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

Nevertheless, I do think voting should be primarly supported in constructTransaction....

Yes, I do agree with that — what I'm saying is that in constructTransaction, voting should be explicit rather than automatic/implicit. In other words, constructTransaction should not implicitly vote Abstain if the JSON data does not explicitly contain a vote to abstain.

Of course I understand that we might ALSO support joinStakePool having conway voting feature as daedalus might use it (although not sure if they have not adopted new tx workflow).

Yes. I believe that Daedalus is still using joinStakePool, but worth checking.

lib/wallet/src/Cardano/Wallet.hs Show resolved Hide resolved
Comment on lines 69 to 72
-> Maybe DelegationAction
-- ^ Delegation action that we plan to take
-> Maybe VotingAction
-- ^ Optional vote action in Conway era onwards
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 DelegationAction
-- ^ Delegation action that we plan to take
-> Maybe VotingAction
-- ^ Optional vote action in Conway era onwards
-> DelegationAction
-- ^ Delegation action that we plan to take

Hm, this function is becoming a mess, I am afraid. 😵‍💫 @paweljakubas, could you:

  • remove the logic for Maybe VotingAction from this function again
  • and instead add a new module Cardano.Wallet.Transaction.Voting with an analogous function certificateFromVotingAction. Then, you can use this function to create certificates that are specific to voting and join them with the certificates for delegation — see other comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah, my bad 🥲 I have added Delegation.Voting module

@@ -711,11 +713,14 @@ mkUnsignedTransaction networkId stakeCred ctx selection = do
inpsScripts
Nothing refScriptM
Just action -> do
let deposit = view #txDeposit ctx
let certs = case stakeCred of
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
let certs = case stakeCred of
let delegationCerts = case stakeCred of
            let deduplicateRegister (JoinRegisteringKey k) (VotingRegisteringKey v) = Voting v
                deduplicateRegister _ v = v
            let votingCerts = case deduplicateRegister <$> (#txVotingAction ctx) of   certificateFromVotingAction 
            let certs = delegationCerts ++ votingCerts 

Here, the idea is to separate two concerns: Adding delegation certificates and adding voting certificates. It's ok to use two certificates in a single transaction; the code becomes too much of a mess otherwise.

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. I do think nub $ delegCerts <> votingCerts should be enough as we use the same staking key during registration

Copy link
Contributor

Choose a reason for hiding this comment

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

I do think nub $ delegCerts <> votingCerts should be enough as we use the same staking key during registration

Ah, nub is a good idea.

One small hitch: We have to careful whether the certificates are applied left-to-right, or right-to-left. In the situation

  nub ["Delegate", "Register", "Vote", "Register"]
=
  nub ["Delegate", "Register", "Vote"] 

applying right-to-left would be a mistake, because nub takes the leftmost element when deduplicating.

specifications/Cardano/Wallet/delegation.md Outdated Show resolved Hide resolved
@paweljakubas
Copy link
Contributor Author

@HeinrichApfelmus joinStakePool and quitStakePool logic of vote to abstain added and removed from constructTransaction -> e2627af

Comment on lines 2691 to 2699
-> Bool
-> IO (Maybe VotingAction)
handleVotingWhenMissingInConway era db delegationsPresent = do
areWeInConway <- case votingEnabledInEra era of
Left _ -> pure False
Right _ -> pure True
voting <- haveWeVoted db
stakingKeyRegistered <- isStakeKeyInDb db
if (areWeInConway && not voting && delegationsPresent) then
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
-> Bool
-> IO (Maybe VotingAction)
handleVotingWhenMissingInConway era db delegationsPresent = do
areWeInConway <- case votingEnabledInEra era of
Left _ -> pure False
Right _ -> pure True
voting <- haveWeVoted db
stakingKeyRegistered <- isStakeKeyInDb db
if (areWeInConway && not voting && delegationsPresent) then
-> IO (Maybe VotingAction)
handleVotingWhenMissingInConway era db = do
areWeInConway <- case votingEnabledInEra era of
Left _ -> pure False
Right _ -> pure True
voting <- haveWeVoted db
stakingKeyRegistered <- isStakeKeyInDb db
if (areWeInConway && not voting) then

After removing the handleVotingWhenMissingInConway from constructTransaction and making it part of selectCoinsForJoin, the delegationsPresent parameter is now always True — and we can remove it. 🤓

Comment on lines 2222 to 2229

optionalVoteAction <-
liftIO $ W.handleVotingWhenMissingInConway era db True

let txCtx = defaultTransactionCtx
{ txDelegationAction = Just action
, txWithdrawal = withdrawal
, txVotingAction = optionalVoteAction
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
optionalVoteAction <-
liftIO $ W.handleVotingWhenMissingInConway era db True
let txCtx = defaultTransactionCtx
{ txDelegationAction = Just action
, txWithdrawal = withdrawal
, txVotingAction = optionalVoteAction
let txCtx = defaultTransactionCtx
{ txDelegationAction = Just action
, txWithdrawal = withdrawal

We do not need to supply a txVotingAction ≠ Nothing here, because the result of WD.quitStakePoolDelegationAction can only be Quit. There is no point in adding a vote to Abstain if we want to quit anyway. 🤔

We do need the txDeposit, however — the code that constructs the certificate corresponding to the Quit action will refuse to do so without a deposit size. 😲

Copy link
Contributor Author

@paweljakubas paweljakubas Feb 16, 2024

Choose a reason for hiding this comment

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

indeed, txDeposit is needed when registration and derigistration happens. no optionalVoteAction during quitting stake pool!

Copy link
Contributor

@HeinrichApfelmus HeinrichApfelmus Feb 16, 2024

Choose a reason for hiding this comment

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

I do think when quitPools happens and there is voting then we need to emit user error ....

As the "Quit" endpoint goes, I think it's better to not emit an error when the wallet is voting, because this is a legacy endpoint and doesn't expect anything about voting anyway.

As far as this function goes, I think this would not happen anyway? 🤔

  • handleVotingWhenMissingInConway doesn't emit an error.
  • The only reason why there may be an error is when txDeposit = Nothing and txDelegationAction = Just Quit and the function certificateFromDelegationAction will hit the following case:
       (Quit, Nothing) ->
           error "certificateFromDelegationAction: deposit value required in \
                 \Conway era when registration is carried out (quitting)"

I would like to not hit that case, because that's a bug — we didn't provide the deposit value.

Copy link
Member

@Anviking Anviking Feb 16, 2024

Choose a reason for hiding this comment

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

txDeposit is needed when registration and derigistration happens

Related: https://cardanofoundation.atlassian.net/browse/ADP-3270

To properly handle:

-- | De-Register the staking credential. Deposit, if present, must match the amount
-- that was left as a deposit upon stake credential registration.

https://github.com/IntersectMBO/cardano-ledger/blob/53a64c898408966940c66fa4b29d42f2656240f4/eras/conway/impl/src/Cardano/Ledger/Conway/TxCert.hs#L391-L392

i.e., the deposit not necessarily being currentPP ^. ppKeyDepositL, I believe we'd need to either get it over LSQ if possible or track it in our DB.

Copy link
Contributor

Choose a reason for hiding this comment

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

track it in our DB.

😵‍💫 I think that's what we have to do — but perhaps not as part of the Conway hardfork.

The ledger still recognizes the old way of unregistering certificates, it's just that Cardano.Api does not expose it as part of makeStakeAddressUnregistrationCertificate. It's possible to use the constructor ConwayCertificate directly, though.

Comment on lines +66 to +67
-> Maybe Coin
-- ^ Optional deposit value
Copy link
Contributor

Choose a reason for hiding this comment

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

    -> Coin
        -- ^ Deposit value

Ideally, we want this to be Coin rather than Maybe Coin — the deposit is always known from the protocol parameters. However, fixing this in the current codebase is hard — the best approach would be to carry around the protocol parameters around all the way down mkUnsignedTransaction or even further.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will leave it for separate PR

Comment on lines 51 to 53
( CardanoApiEra
, RecentEra (RecentEraBabbage, RecentEraConway)
)
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
( CardanoApiEra
, RecentEra (RecentEraBabbage, RecentEraConway)
)

(🤷 I see no value in adding explicit exports to qualified imports.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yep, the same was in Transaction.Delegation module and I automatically copy pasted it. Now removed that strange way of importing in both modules

Right Nothing ->
error $ unwords
[ "stakeCred in mkUnsignedTransaction must be"
, "either xpub or script when there is delegation"
, "action"
]
let certs = L.nub $ votingCerts <> delegCerts
Copy link
Contributor

Choose a reason for hiding this comment

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

👍 I have checked. In Conway, the rule handling the certificates is called CERTS. As far as I can tell, it goes left to right, so nub is appropriate here.

@paweljakubas paweljakubas added this pull request to the merge queue Feb 16, 2024
Merged via the queue into master with commit 8a3a1f7 Feb 16, 2024
3 checks passed
@paweljakubas paweljakubas deleted the paweljakubas/adp-3212/voting-write-side branch February 16, 2024 16:01
WilliamKingNoel-Bot pushed a commit that referenced this pull request Feb 16, 2024
…llet 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] write side of certs (from wallet to ledger) - [x] logic - is conway era - [x] logic for withdrawals in conway - [x] vote to abstain logic ### Comments <!-- Additional comments, links, or screenshots to attach, if any. --> ### Issue Number adp-3212 <!-- 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: 8a3a1f7
github-merge-queue bot pushed a commit that referenced this pull request Feb 23, 2024
- [x] Enable delegation tests for Conway
- [x] Ensure Conway delegation works by adding missing txDeposit-related
glue

### Comments

```
LOCAL_CLUSTER_CONFIGS=../local-cluster/test/data/cluster-configs CARDANO_WALLET_TEST_DATA=test/data LOCAL_CLUSTER_ERA=conway cabal test cardano-wallet:integration --test-options '--match JOIN_01 -j 4'
```

### Issue Number

Related to ADP-3212, #4438
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