-
Notifications
You must be signed in to change notification settings - Fork 658
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
Routine for processing deposits #1640
Conversation
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.
great! comments below
validator_registry=(), | ||
) | ||
|
||
privkey_1 = privkeys[0] |
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.
maybe should do a loop
for index in range(3):
privkey = privkeys[index]
pubkey = pubkeys[index]
....
result_state, validator_index = process_deposit()
...
check_results()
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.
Oh but I want to make an empty slot between the tests. ("# Force the first validator exited")
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.
Oh I see! Maybe update comment
# Add the third validator.
# Should overwrite previously exited validator
or something. Would be good to make it more explicit than just the assert len(result_state.validator_registry) == 2
(2) seems okay to me. |
eth/beacon/deposit_helpers.py
Outdated
current_slot | ||
): | ||
return index | ||
return None |
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.
We tend to try and stay away from return value checking. I'd convert this to raise an IndexError
or a custom exception (probably the better option) here to ensure that call-sites cannot accidentally treat the return value incorrectly.
eth/beacon/deposit_helpers.py
Outdated
"BLS signature verification error" | ||
) | ||
|
||
return True |
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.
Our convention for validation functions is to have them return None
(implicitly with no return statement).
eth/beacon/deposit_helpers.py
Outdated
# Update validator's balance and state | ||
with validator.build_changeset() as validator_changeset: | ||
validator_changeset.balance = validator.balance + deposit | ||
validator = validator_changeset.commit() |
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.
This can be simplified to: validator = validator.copy(balance=validator.balance + deposit)
. The changeset
API is better purposed for when you need to do multiple updates, potentially with some conditional logic.
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.
Oh, you're right.
And I just benchmarked between using copy()
and changeset()
- I thought using copy
would be slower since it's deepcopy
, but, it turns out using copy
is 30-40% faster(!) (with a simple RLP data with 13 fields)
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.
p.s. I haven't tried a hugh RLP data like BeaconState
. But the performance for ValidatorRecord
should be better with using copy()
.
Changelog
|
validator_registry=(), | ||
) | ||
|
||
privkey_1 = privkeys[0] |
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.
Oh I see! Maybe update comment
# Add the third validator.
# Should overwrite previously exited validator
or something. Would be good to make it more explicit than just the assert len(result_state.validator_registry) == 2
|
||
validator_pubkeys = tuple([v.pubkey for v in state.validator_registry]) | ||
if pubkey not in validator_pubkeys: | ||
validator = ValidatorRecord.get_pending_validator( |
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.
Would this be clearer as create_pending_validator
rather than get_
?
get
implies to me that it is trying to retrieve something that at least might already exist.
Not sure what the codebase naming conventions are in general. Will defer to @pipermerriam
eth/beacon/deposit_helpers.py
Outdated
current_slot: int, | ||
zero_balance_validator_ttl: int) -> int: | ||
for index, validator in enumerate(validators): | ||
if ( |
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.
small formatting nitpick. I'd capture the contidional in a variable and then use it in a single line if
is_empty = (validator.balance == 0 and ...)
if is_empty:
return index
eth/beacon/deposit_helpers.py
Outdated
except MinEmptyValidatorIndexNotFound: | ||
index = None | ||
|
||
if index is None: |
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 think this would all be a little better readability if you had all of this in the try/except/else
try:
index = ...
except MinEmptyValidatorIndexNotFound:
... # logic from the `if index is None`
else:
... # other logic.
return state, index
eth/beacon/deposit_helpers.py
Outdated
randao_commitment, | ||
) | ||
|
||
validator_pubkeys = tuple([v.pubkey for v in state.validator_registry]) |
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.
The inner [...]
brackets can be removed as tuple(generator)
works just fine.
eth/beacon/deposit_helpers.py
Outdated
index = validator_pubkeys.index(pubkey) | ||
validator = state.validator_registry[index] | ||
|
||
if validator.withdrawal_credentials != validator.withdrawal_credentials: |
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.
This conditional looks broken. Seems like you are comparing the same value with itself. Is one of these supposed to be just withdrawal_credentials
.
Maybe add a test that covers this code path since this code path probably isn't being exercised in the tests right now?
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.
Yeah, should be checking that the existing validator with the pubkey pubkey
has the same withdrawal_credentials
that were passed in with the deposit
eth/beacon/types/states.py
Outdated
validator: ValidatorRecord) -> 'BeaconState': | ||
validator_registry = list(self.validator_registry) | ||
validator_registry[validator_index] = validator | ||
self = self.copy( |
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'm 👎 on overriding self
in a method context. Seems like an antipattern and easily able to lead to confusion. Maybe just inline the copy
in the return
statement or call the variable something like updated_state
eth/utils/bls.py
Outdated
pairing( | ||
FQP_point_to_FQ2_point(hash_to_G2(message, domain)), | ||
neg(decompress_G1(pubkey)), | ||
False |
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 find positional booleans to be confusing since they give no context on what is being False
'd.. Maybe convert to either a keyword argument, or just use a comment to denote what argument this is for if performance is an issue.
eth/utils/bls.py
Outdated
for m_pubs in set(messages): | ||
# aggregate the pubs | ||
group_pub = Z1 | ||
for i in range(len_msgs): |
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.
This looks like you could do for pubkey, message in zip(pubkeys, messages):
which removes the indexing.
Alternatively, I think the following is a functional equivalent implementation.
groups = (
pairing(hash_to_G2(m_pubs, domain), reduce(add, (decompress_G1(pubkey), Z1)), False)
for (pubkey, message)
in zip(pubkeys, messages) if message == m_pubs
for m_pubs in set(messages)
)
final_exponentiation = final_exponentiate(
reduce(
operator.mul,
groups,
FQ12([1] + [0] * 11),
) * pairing(decompress_G2(signature), neg(G1), False)
)
return final_exponentiation == FQ12.one()
Also, there are a few constants in there that could be lifted out such as:
FQ12.one()
neg(G1)
The above is just hand-written code without being tested so you should verify if you choose to switch. I think the above code would be more performant... but that's just intuition.
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.
Thanks for the feedback! 👍 That's looks great!
I'm inclined to leave this refactoring to #1592 because the current spec is still a draft, we may seek for review from academic cryptographers to help review in 1-2 months; in the meantime, the Python codes are the reference for the other implementers.
What was wrong?
Implement routine for processing deposits
How was it fixed?
BeaconState.update_validator(validator_index, validator)
DepositInput
get_new_validator_registry_delta_chain_tip
eth/beacon/deposits_helpers.py
<-- might be put to somewhere under state machinedef min_empty_validator_index
as specdef process_deposit
as specQuestion to the reviewers:
state: BeaconState
RLP/SSZ object frequently. Currently, I'm usingchangeset()
for each snippet, but I'm not sure if that's the best practice.BeaconState.update_validator(validator_index, validator)
for updatingBeaconState.validator_registry: Sequence[ValidatorRecord]
- immutably. Do you think it's okay for readability or any other concerns?Cute Animal Picture