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

Change to how SPO votes are counted #4645

Open
lehins opened this issue Sep 23, 2024 · 13 comments
Open

Change to how SPO votes are counted #4645

lehins opened this issue Sep 23, 2024 · 13 comments
Labels
conway intra-era-hardfork A task that requires an intra-era hard fork

Comments

@lehins
Copy link
Collaborator

lehins commented Sep 23, 2024

It has been brought up by a number of community members that the way we currently compute SPO votes is not what everyone expected.
Current implementation is such that for HardForkInitiation behavior is exactly the same as for DReps, however for the other three governance actions, that SPOs can vote on, behavior is different for SPOs that did not vote. In other words for these three governance action types:

  • NoConfidence
  • UpdateCommittee
  • ParameterUpdate (when it includes updates to parameter belonging to the security group)

the default vote counted for SPOs that didn't vote is Abstain, instead of No. The default vote of No is the behavior for DReps, while for SPOs it is default only when HardForkInitiation is proposed.

Current behavior is specified in this portion of the spec and the actual implementation lives here:

in case vote of
Nothing
| HardForkInitiation {} <- pProcGovAction -> (yes, abstain)
| otherwise -> (yes, abstain + stake)

This lead to some concerns about inclusion of SPOs into the future of parameter updates, that is why many have suggested that we should mimic the behavior of DReps exactly. However, that defaulting of SPOs to vote No, has its own concerns. In particular there is a danger if SPOs aren't active enough that they could inadvertently block sensible proposals. Inactivity could be potentially blamed either on indifference or issues with legality for some SPOs.

In order to solve this conundrum we made it the topic of Ledger Working Group Meeting #4 and had a very productive discussion with some community members and came up with an ingenious solution:

Everyone agreed right of the bat that if we had a mechanism for SPOs to choose whether they wanted to default to Abstain instead of No, we would have solved the problem. However making a change to PoolParams at this time is impossible without a new era. Also, whenever this issue of customizing PoolParams has surfaced during Conway development, we knew that making such a change would have been too ambitious of a goal, which would have lead to an extensive delay to the Conway delivery. This has to do with the fact that PoolParams is not a type family yet. Therefore, since adding a custom field to the PoolParams with an optional default vote was not a path that we could choose we almost discarded this idea. It was then that Martin Lang of ATADA Stake Pool proposed an interesting and unexpected solution: we can achieve ability of specifying a default vote for SPOs if we choose to mimic the default delegation of ppRewardAccount that is specified in the PoolParams.

To be more specific the solution to this problem is:

  1. To change the default vote for SPOs to No from Abstain for all of the three proposals in question
  2. Check whether SPO's reward address is delegated to the predefined AlwaysNoConfidence DRep and count default vote as Yes only on NoConfidence proposals for that SPO.
  3. Check whether SPO's reward address is delegated to predefined AlwaysAbstain DRep and count default vote as Abstain for that SPO on all three: NoConfidence, UpdateCommitee and ParameterUpdate proposals.

This is not a terribly complicated logic to implement, which means that we can almost certainly take care of in time for the protocol version 10.0, i.e. the intra-era hard fork, a.k.a Chang+1

@WhatisRT
Copy link
Contributor

I think step 1 is a bad idea, since it still forces participation in governance to return to Abstain. What's wrong with leaving it to default to Abstain? Then SPOs that want to actively participate can change their default and SPOs that don't want to participate don't have to do anything. If you go with the hacky solution for delegation you can just check if there's any DRep delegation in there to default to No.

There's also the issue that this solution means that whatever Ada is associated with that particular stake credential now has to be delegated to a predefined vote. That means that the hacky solution will likely have to be in the code eternally since it affects voting outcomes.

I don't understand the issue with adding a field of type Maybe Vote to PoolParams and just not making it a type family. Is it that it would require CDDL changes?

@gitmachtl
Copy link

gitmachtl commented Sep 24, 2024

The issue is that a single SPO pool voting YES can change the whole SPO acceptance to 100%. That was the reason to change it to default NO, so this cannot happen anymore. Setting all to abstain would lead into the same result.

Here is the link to the recording of the meeting to rewatch it:
https://drive.google.com/file/d/1_Y0K9mzX6k7gMXDGIB7Gbpgzhj3JlitD/view

@WhatisRT
Copy link
Contributor

Yes, I'm aware of that, here's the counterargument I've given before:

I think the whole 'one SPO can single-handedly approve an action' argument is somewhat overblown. It's rather 'one SPO can single-handedly approve an action if all others ignore it while at the same time at least half of the DReps approve it'. Sounds very far-fetched to me. The real danger is that some SPOs don't pay attention in the time frame where the DReps approve the action and it gets just barely in because of that. But you have to weigh that against forcing exchanges to cast votes.

So let's say that most exchanges don't do anything and 20% of the stake defaults to No now. That means that for the remaining SPOs to pass anything, 62.5% of the votes need to be Yes if nobody abstains. And that number just goes up in the presence of abstention. If 20% abstain as well (as some other exchanges would probably do) you now need 83.3% in favour. Good luck passing anything.

I don't know what will actually happen, but hoping for a good scenario here seems way too risky. Having some extreme looking behaviour right at the start of a proposal that's going to stabilize seems like a much smaller issue than permanently skewing the vote to me.

@disassembler
Copy link
Contributor

My opinion is we go forward with this proposal. Yes, there's nothing that forces an exchange to delegate their rewards address to abstain, but this at least gives them the option to do so. Note that to claim rewards, they already will have to delegate their rewards address anyways, and most likely they'll pick abstain, so that feature alone could get large players to opt-out of voting on anything other than hard fork initiation proposals.

@disassembler
Copy link
Contributor

I do agree that a field on the pool registration certificate is a better design; however we ruled that out months ago because of the time it would take to trickle down the CDDL changes to hw wallets and other tools integrating pool registration certificates.

@WhatisRT
Copy link
Contributor

Note that if we let SPOs pick their default, the initial instability is mitigated even more. If, say, 10% of stake defaults to No, you now need 10% of other stake to stealthily vote to overtake them. At that point this doesn't even seem like a problem to me anymore, and defaulting everyone to No only has the downside of forcing everybody who doesn't want to participate to do something.

@kevinhammond
Copy link
Contributor

Agreed, this seems like a sensible solution. Where it differs, we will make sure the CIP text is consistent with it. Thank you to @gitmachtl for making the suggestion, and to @lehins for finding a way to implement it easily.

@lehins
Copy link
Collaborator Author

lehins commented Sep 24, 2024

I don't understand the issue with adding a field of type Maybe Vote to PoolParams and just not making it a type family. Is it that it would require CDDL changes?

Yes, it would require CDDL changes. For this particular case not making it a type family and adding an optional field would work. Yet again, we do need a new era for this, because we can't change serialization for intra-era hardfork. Maybe, if I knew about this issue early enough we could have solved it in Conway by adding that optional field. But, I only learned about it a week ago.

@Cerkoryn
Copy link

I had no idea that SPO votes defaulted to abstain when nothing happened. I just assumed they were treated the same as dReps where inaction means an implicit 'no'. In that case, I have a 4th suggestion @lehins and @disassembler.

Regarding this...

So let's say that most exchanges don't do anything and 20% of the stake defaults to No now. That means that for the remaining SPOs to pass anything, 62.5% of the votes need to be Yes if nobody abstains. And that number just goes up in the presence of abstention. If 20% abstain as well (as some other exchanges would probably do) you now need 83.3% in favour. Good luck passing anything.

I'd previously joined a couple of SPO calls to voice my concern over precisely this issue. My suggestion...

  1. Add a new spoActivity parameter that functions exactly like drepActivity but for SPOs. So if an SPO hasn't voted in that many epochs (currently 20 epochs for dReps), they are switched to abstain and removed from quorum.

That way CEXes or SPOs who don't do anything will simply be removed after a period of time.

Is there any reason why we can't treat both dReps and SPOs the same way? Occam's Razor.

@zhekson1
Copy link

@Cerkoryn The issue I see here is that SPO's will inherently be voting less than dReps, and with a focus on proposals directly relating to infrastructure (as they should be). So if spoActivity countdown is set similarly to drepActivity, it may be the case that most SPOs don't vote for this period, are removed from the quorum, and soon we are back where we started with a very low SPO quorum. If we counteract this by setting spoActivity to much higher than drepActivity, the inverse can quickly become an issue: SPO-relevant parameters progress very slowly due to the high number of (almost always defaulted) "No"s.

IMO giving SPO's more granular optionality in their participation would be more fruitful than trying to arrive at the "right number" for an spoActivity parameter.

@Pdest08
Copy link

Pdest08 commented Sep 25, 2024

Keep it simple: Force SPOs to choose their default when booting up their node

@adamrusch
Copy link

I think the whole 'one SPO can single-handedly approve an action' argument is somewhat overblown. It's rather 'one SPO can single-handedly approve an action if all others ignore it while at the same time at least half of the DReps approve it'. Sounds very far-fetched to me. The real danger is that some SPOs don't pay attention in the time frame where the DReps approve the action and it gets just barely in because of that. But you have to weigh that against forcing exchanges to cast votes.

I made a similar argument in the Working Group meeting because I think the possibility of locking up governance over SPO lack of participation is a far greater danger than underrepresentation of SPOs if they don't cast votes.

However, if we have the ability to offer an auto-abstain option with relatively few trade-offs that is a far more elegant solution that keeps default SPO actions in line with DReps and the CC.

What is incredibly shocking here is that we had such a huge discrepancy come up between the specs/implementation and what I see as clear language in the text of the CIP. We spent months socializing the fact that SPOs must cast votes and cannot auto-abstain. We set the SPO related governance parameters based on the understanding that these thresholds must be met as a percentage of the stake held by all stake pools. These things cannot be taken lightly.

@Hornan7
Copy link

Hornan7 commented Sep 25, 2024

Default Abstain without a 'quorum' requirement presents significant risks to what I view as our last line of defense against bad behavior—the SPOs.

What’s being proposed here, in my opinion, offers the best of both worlds, at least for now. I tested a catastrophic scenario on SanchoNet that effectively worked:

  • I tracked all drepActivity of DReps to time my actions for the right Epoch boundary.

Then, 5 minutes before the Epoch boundary:

  • I submitted an 'Update Committee' governance action to position myself (with 7 cold credentials) and remove all CC-cold credentials that was in place. (In respect of the committeeMinSize protocol parameter value and during a Normal state)
  • I registered an incredibly powerful DRep and voted yes on it, counterbalancing the remaining active DReps.
  • I voted yes only with my own Stake Pool, all within the last 5 minutes of the Epoch to ensure that no other SPOs had time to counterweight that before the said Epoch boundary.

This scripted approach effectively triggered the CC change without anyone noticing before the calculations occurred, leaving no time for inactive DReps to respond as well.

In conclusion. Ultimately, as inconvenient as it might be, SPOs cannot stall governance by themselves if they dont engage; thresholds can always be changed, requiring only DReps and CC votes. The same applies to changing the guardrails within the constitution itself, which is why I strongly prefer getting back to the Default No with the added voting options for SPOs as proposed here.

-- Mike Hornan 😃👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
conway intra-era-hardfork A task that requires an intra-era hard fork
Projects
None yet
Development

No branches or pull requests

10 participants