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

Custody punishments for late reveals & deterministic staggering #872

Closed
wants to merge 5 commits into from

Conversation

CarlBeek
Copy link
Contributor

@CarlBeek CarlBeek commented Apr 1, 2019

This PR implements the following:

  • Forced timely key reveals Validators who have not revealed by the end of their reveal period are slashed.
  • Staggers reveal periods Reveal periods are deterministically staggered by taking the validator_index%EPOCHS_PER_CUSTODY_PERIOD. This introduces modulo-bias and the earlier epochs in a period could have one more reveal each. The staggering strategy from Stagger reveal slots #869 is not used for two reasons: it requires storing when each validator's latest reveal period started (as it cannot be calculated later as it is dependent on the RANDAO state) and secondly, this mechanism allows validators to know when (approximately) their reveal period will begin.
  • Removes max_reveal_lateness I think all the functionality that was provided by max_reveal_lateness has been replaced, but please check my logic.

Hereby items 6, 8, and 9 from #864 are addressed.

@dankrad
Copy link
Contributor

dankrad commented Apr 1, 2019

Cool! One question: Theoretically, we do not need latest_custody_reveal_period to be an integer, right? It could be a boolean that stores whether a reveal was published in this period.
This might be annoying as it would require one additional validator update per period, so we could just make this number mod 2? I think that information would be sufficient?

@vbuterin
Copy link
Contributor

vbuterin commented Apr 2, 2019

The staggering strategy from #869 is not used for two reasons: it requires storing when each validator's latest reveal period started (as it cannot be calculated later as it is dependent on the RANDAO state) and secondly, this mechanism allows validators to know when (approximately) their reveal period will begin.

Wait, why do you need to store when each validator's latest reveal period started? In my proposal, the only thing that gets staggered is the minimum reveal block, custody periods are still aligned.

If custody periods are all aligned, then I would argue that my staggering strategy is superior because relying on validator index means that there's persistent unfairness where some validators get always earlier reveal times than others. If custody periods are not aligned, I agree your approach is best. What do you see as being the benefits and costs either way?

Cool! One question: Theoretically, we do not need latest_custody_reveal_period to be an integer, right? It could be a boolean that stores whether a reveal was published in this period.

No because the +1 lateness bound is too tight to be safe (see my other comment), it needs to be +8.

@hwwhww hwwhww added the phase1 label Apr 2, 2019
@CarlBeek
Copy link
Contributor Author

CarlBeek commented Apr 2, 2019

Wait, why do you need to store when each validator's latest reveal period started? In my proposal, the only thing that gets staggered is the minimum reveal block, custody periods are still aligned.

This comes down to the stringency of my slashing mechanism. As everyone who has not revealed is slashed by the start of their next period, it would be unfair to allow certain validators only 1 block to reveal their key.

If custody periods are not aligned, I agree your approach is best.

I was staggering the periods of each validator (up to 1 custody_period phase difference between any two validators). I was thus targeting non-aligned custody periods hence the ridiculous naming of get_validators_current_custody_reveal_period(). By increasing the reveal time (which I agree should be done), the phase difference of your stochastic minimum_reveal_block has less of an impact on the available reveal time for a validator.

What do you see as being the benefits and costs either way?

I don't think it really matters much either way. The downsides of doing it your way are increased hashing (minor) and lack of predictability of when a particular validator's reveal period begins. On the other hand, my system has constant bias (as described by me as "modulo-bias" in my initial comment) in the sense that blocks at the start of a reveal period could contain 1 more reveal every time.

Co-Authored-By: CarlBeek <carl.beek@gmail.com>
@dankrad
Copy link
Contributor

dankrad commented Apr 2, 2019

What do you see as being the benefits and costs either way?

I don't think it really matters much either way. The downsides of doing it your way are increased hashing (minor) and lack of predictability of when a particular validator's reveal period begins. On the other hand, my system has constant bias (as described by me as "modulo-bias" in my initial comment) in the sense that blocks at the start of a reveal period could contain 1 more reveal every time.

I like the approach that Carl is suggesting here, because it minimises the time from when a custody secret is used to its corresponding reveal period. The expected rational behaviour is to reveal a custody secret as soon as possible after using it, so this approach has two advantages:

  • The time to check a custody secret is minimised (less load on validators checking secrets)
  • Validators can reveal as soon as possible, minimising the time when they can get slashed

Also the logic does seem to have very little overhead. But overall, the difference is fairly minor, so I don't feel very strongly about it.

* Limits bit challenges to maximally ~73 + 9 days in the future.
* Prevents exists if ∃ open custody bit challenges
* Updates bit-challenge processing to correctly check whether the challenger is slashable
# Cannot exit if you have not revealed all of your custody keys
elif epoch_to_custody_period(revealer.activation_epoch) + validator.custody_reveal_index <= epoch_to_custody_period(validator.exit_epoch):
elif validator.latest_custody_reveal_period <= epoch_to_custody_period(validator.exit_epoch):
Copy link
Contributor

Choose a reason for hiding this comment

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

I think if it's latest reveal rather than next reveal, the inequality should be <, not <=. The rationale is that if you exit during epoch N, that means that you used the subkey for epoch N and previous epochs, so you need to reveal all the subkeys up to and including N, so the "latest reveal" should be N for an exit to be valid.

@vbuterin
Copy link
Contributor

vbuterin commented Apr 2, 2019

This comes down to the stringency of my slashing mechanism. As everyone who has not revealed is slashed by the start of their next period, it would be unfair to allow certain validators only 1 block to reveal their key.

So if the slashing mechanism only works after 8 epochs, this seems like it matters less.

because it minimises the time from when a custody secret is used to its corresponding reveal period

I like how it stabilizes the time between use and reveal, but I actually think minimizing the time could be a bad idea. Revealing data literally a block after the period ends opens you up to slashing if the chain reorgs. That said, I suppose this is something that we could leave open as a tradeoff to the user - if they wait longer, then they get more safety, but then they would have to store more data in the meantime.

# Verify the attestation
assert verify_standalone_attestation(state, convert_to_standalone(state, challenge.attestation))
# Verify the attestation is eligible for challenging
responder = state.validator_registry[challenge.responder_index]
min_challengeable_epoch = responder.exit_epoch - EPOCHS_PER_CUSTODY_PERIOD * (1 + responder.max_reveal_lateness)
Copy link
Contributor

Choose a reason for hiding this comment

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

We can't get rid of the max delay because then the validator would be liable for an unboundedly growing period of data. We also can't set max delay to equal the custody response deadline because that would just be too much data to store (2 MB per epoch * 2**14 epochs = 32 GB); we want a validator to only be responsible for the data in a single period (unless the validator screws up eg. by delaying subkey reveals). So we need a max delay of 1 in the normal case, but if the validator does delay subkey reveals we need to increase the max delay to make sure that when the subkey is revealed the data can still be challenges. This was the rationale for max_reveal_lateness.

Copy link
Contributor

Choose a reason for hiding this comment

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

@JustinDrake and I thought about this and couldn't come up with a way to solve this issue without having that variable, despite its ugliness, though what we can do is ameliorate the problem where if you're late once you're stuck with a longer period forever, by having max_reveal_lateness be measured in a finer-grained time interval (eg. epochs) and have it slowly decrease over time if a validator shows good behavior (another isomorphic approach is to have a min_challengeable_epoch that, if it is more than a full period behind the current epoch, moves forward eg. 2 epochs per epoch.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, now I see the rationale for max_reveal_lateness. I like the idea of having it decrease over time.

(of the particular validator) *AFTER* which the validator was activated.
= get_validators_current_custody_reveal_period(...) + 1
'''
'latest_custody_reveal_period': 'uint64',
Copy link
Contributor

Choose a reason for hiding this comment

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

So the first subkey that the validator will be revealing is from the period that is two periods ahead of the current one? Not sure I see the logic here. You sure we don't want - 1?

And if it is -1, then it seems better to avoid negative numbers and keep track of next_custody_reveal_period instead of latest_custody_reveal_period.

@dankrad
Copy link
Contributor

dankrad commented Apr 2, 2019

Revealing data literally a block after the period ends opens you up to slashing if the chain reorgs.

Good point. Would it make sense to only allow slashing while the custody secret is actually in use? Then you can never get slashed for revealing your secret, which makes sense, and if it is accidentally disclosed after the custody period it is presumably not really an offense worth slashing.

@vbuterin
Copy link
Contributor

vbuterin commented Apr 2, 2019

Would it make sense to only allow slashing while the custody secret is actually in use?

Aren't we doing that already? At least we were when I wrote the original code; that's definitely how it should work IMO.

proposer_index = get_beacon_proposer_index(state, state.slot)
increase_balance(state, proposer_index, base_reward(state, index) // MINOR_REWARD_QUOTIENT)

# Case 2: masked punitive early reveal
else:
assert reveal.period > current_custody_period
assert revealer.slashed is False
assert reveal.period >= validators_next_custody_reveal_period
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
assert reveal.period >= validators_next_custody_reveal_period
assert reveal.period >= get_validators_current_custody_reveal_period(state, reveal.masker_index)

Only allow slashing for secrets that are still in use or will be used in the future

@dankrad
Copy link
Contributor

dankrad commented Apr 3, 2019

Aren't we doing that already? At least we were when I wrote the original code; that's definitely how it should work IMO.

Yup, probably my bad, I accidentally removed that logic in a suggestion earlier

@CarlBeek
Copy link
Contributor Author

This is being deprecated due to the improved rewrite that is #934 :)

@CarlBeek CarlBeek closed this Apr 16, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants