Update by reference for two state objects affect each other
Consider the following test:
@with_electra_and_later
@spec_state_test
def test_state_changes_overwritten(spec, state):
validator_0 = state.validators[0]
validator_1 = state.validators[1]
validator_0.exit_epoch = spec.Epoch(0)
assert state.validators[0].exit_epoch == spec.Epoch(0)
validator_1.exit_epoch = spec.Epoch(1)
# Check that both changes have been applied to the state
assert state.validators[1].exit_epoch == spec.Epoch(1)
assert state.validators[0].exit_epoch == spec.Epoch(0)
The second assert fails, as validator_1.exit_epoch = spec.Epoch(1) reverts the change made to validator_0 in the line above. If we change the order to 1) update validator_1 2) update validator_0 then the first assert would fail.
The cause of the bug is in the following remerkleable behaviour:
state.validators[0]expression creates the following chain of view backings:state.validators_a.validator_0and keeps it in thevalidator_0view, similarlyvalidator_1denotesstate.validators_b.validator_1validator_0.exit_epoch = spec.Epoch(0)assignment updatesvalidator_0chain of backings tostate_a.validators_a.validator_0_updated, note that thestateview is now backed by thestate_abacking and the first assert passes. Also, note thatvalidator_1denotesstate_a.validators_b.validator_1: thevalidatorsview diverged from thestateviewvalidator_1.exit_epoch = spec.Epoch(1)assignment is applied tovalidator_1, then it updatesvalidatorsbacking and is finally propagated to thestatebacking, resulting in the following chain of backings:state_b.validators_b.validator_1and thestatenow backed bystate_bmaking the last assert fail
Potential solution would be to cache a child view inside of a parent view instance if a child view is a ComplexView that can be further updated by reference; and on every subsequence calls obtaining the same child view return the cached value.
With this approach the above case would have: validator_0 <- state.validators.validator_0, and validator_1 <- state.validators.validator_1. So, both validator views would refer to the same instance of state.validators. And an update to validator_0 would be propagated to validator_1 backings
Another case with similar cause:
@with_electra_and_later
@spec_state_test
def test_state_changes_overwritten_2(spec, state):
validator_0 = state.validators[0]
state.validators[0].exit_epoch = spec.Epoch(0)
assert validator_0.exit_epoch == spec.Epoch(0)
The assert above fails because the update is not applied to the validator_0 view
I wonder if @protolambda has anything to add here
I am closing this issue because it seems stale. Please, do not hesitate to reopen it if this is a mistake