/ test_libs / pyspec / eth2spec / test / block_processing / test_process_voluntary_exit.py
test_process_voluntary_exit.py
  1  import eth2spec.phase0.spec as spec
  2  from eth2spec.phase0.spec import (
  3      get_active_validator_indices,
  4      get_churn_limit,
  5      get_current_epoch,
  6      process_voluntary_exit,
  7  )
  8  from eth2spec.test.context import spec_state_test, expect_assertion_error, always_bls
  9  from eth2spec.test.helpers.keys import pubkey_to_privkey
 10  from eth2spec.test.helpers.voluntary_exits import build_voluntary_exit, sign_voluntary_exit
 11  
 12  
 13  def run_voluntary_exit_processing(state, voluntary_exit, valid=True):
 14      """
 15      Run ``process_voluntary_exit``, yielding:
 16        - pre-state ('pre')
 17        - voluntary_exit ('voluntary_exit')
 18        - post-state ('post').
 19      If ``valid == False``, run expecting ``AssertionError``
 20      """
 21      validator_index = voluntary_exit.validator_index
 22  
 23      yield 'pre', state
 24      yield 'voluntary_exit', voluntary_exit
 25  
 26      if not valid:
 27          expect_assertion_error(lambda: process_voluntary_exit(state, voluntary_exit))
 28          yield 'post', None
 29          return
 30  
 31      pre_exit_epoch = state.validator_registry[validator_index].exit_epoch
 32  
 33      process_voluntary_exit(state, voluntary_exit)
 34  
 35      yield 'post', state
 36  
 37      assert pre_exit_epoch == spec.FAR_FUTURE_EPOCH
 38      assert state.validator_registry[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH
 39  
 40  
 41  @spec_state_test
 42  def test_success(state):
 43      # move state forward PERSISTENT_COMMITTEE_PERIOD epochs to allow for exit
 44      state.slot += spec.PERSISTENT_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
 45  
 46      current_epoch = get_current_epoch(state)
 47      validator_index = get_active_validator_indices(state, current_epoch)[0]
 48      privkey = pubkey_to_privkey[state.validator_registry[validator_index].pubkey]
 49  
 50      voluntary_exit = build_voluntary_exit(state, current_epoch, validator_index, privkey, signed=True)
 51  
 52      yield from run_voluntary_exit_processing(state, voluntary_exit)
 53  
 54  
 55  @always_bls
 56  @spec_state_test
 57  def test_invalid_signature(state):
 58      # move state forward PERSISTENT_COMMITTEE_PERIOD epochs to allow for exit
 59      state.slot += spec.PERSISTENT_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
 60  
 61      current_epoch = get_current_epoch(state)
 62      validator_index = get_active_validator_indices(state, current_epoch)[0]
 63      privkey = pubkey_to_privkey[state.validator_registry[validator_index].pubkey]
 64  
 65      voluntary_exit = build_voluntary_exit(state, current_epoch, validator_index, privkey)
 66  
 67      yield from run_voluntary_exit_processing(state, voluntary_exit, False)
 68  
 69  
 70  @spec_state_test
 71  def test_success_exit_queue(state):
 72      # move state forward PERSISTENT_COMMITTEE_PERIOD epochs to allow for exit
 73      state.slot += spec.PERSISTENT_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
 74  
 75      current_epoch = get_current_epoch(state)
 76  
 77      # exit `MAX_EXITS_PER_EPOCH`
 78      initial_indices = get_active_validator_indices(state, current_epoch)[:get_churn_limit(state)]
 79  
 80      # Prepare a bunch of exits, based on the current state
 81      exit_queue = []
 82      for index in initial_indices:
 83          privkey = pubkey_to_privkey[state.validator_registry[index].pubkey]
 84          exit_queue.append(build_voluntary_exit(
 85              state,
 86              current_epoch,
 87              index,
 88              privkey,
 89              signed=True,
 90          ))
 91  
 92      # Now run all the exits
 93      for voluntary_exit in exit_queue:
 94          # the function yields data, but we are just interested in running it here, ignore yields.
 95          for _ in run_voluntary_exit_processing(state, voluntary_exit):
 96              continue
 97  
 98      # exit an additional validator
 99      validator_index = get_active_validator_indices(state, current_epoch)[-1]
100      privkey = pubkey_to_privkey[state.validator_registry[validator_index].pubkey]
101      voluntary_exit = build_voluntary_exit(
102          state,
103          current_epoch,
104          validator_index,
105          privkey,
106          signed=True,
107      )
108  
109      # This is the interesting part of the test: on a pre-state with a full exit queue,
110      #  when processing an additional exit, it results in an exit in a later epoch
111      yield from run_voluntary_exit_processing(state, voluntary_exit)
112  
113      assert (
114          state.validator_registry[validator_index].exit_epoch ==
115          state.validator_registry[initial_indices[0]].exit_epoch + 1
116      )
117  
118  
119  @spec_state_test
120  def test_validator_exit_in_future(state):
121      # move state forward PERSISTENT_COMMITTEE_PERIOD epochs to allow for exit
122      state.slot += spec.PERSISTENT_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
123  
124      current_epoch = get_current_epoch(state)
125      validator_index = get_active_validator_indices(state, current_epoch)[0]
126      privkey = pubkey_to_privkey[state.validator_registry[validator_index].pubkey]
127  
128      voluntary_exit = build_voluntary_exit(
129          state,
130          current_epoch,
131          validator_index,
132          privkey,
133          signed=False,
134      )
135      voluntary_exit.epoch += 1
136      sign_voluntary_exit(state, voluntary_exit, privkey)
137  
138      yield from run_voluntary_exit_processing(state, voluntary_exit, False)
139  
140  
141  @spec_state_test
142  def test_validator_invalid_validator_index(state):
143      # move state forward PERSISTENT_COMMITTEE_PERIOD epochs to allow for exit
144      state.slot += spec.PERSISTENT_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
145  
146      current_epoch = get_current_epoch(state)
147      validator_index = get_active_validator_indices(state, current_epoch)[0]
148      privkey = pubkey_to_privkey[state.validator_registry[validator_index].pubkey]
149  
150      voluntary_exit = build_voluntary_exit(
151          state,
152          current_epoch,
153          validator_index,
154          privkey,
155          signed=False,
156      )
157      voluntary_exit.validator_index = len(state.validator_registry)
158      sign_voluntary_exit(state, voluntary_exit, privkey)
159  
160      yield from run_voluntary_exit_processing(state, voluntary_exit, False)
161  
162  
163  @spec_state_test
164  def test_validator_not_active(state):
165      current_epoch = get_current_epoch(state)
166      validator_index = get_active_validator_indices(state, current_epoch)[0]
167      privkey = pubkey_to_privkey[state.validator_registry[validator_index].pubkey]
168  
169      state.validator_registry[validator_index].activation_epoch = spec.FAR_FUTURE_EPOCH
170  
171      # build and test voluntary exit
172      voluntary_exit = build_voluntary_exit(
173          state,
174          current_epoch,
175          validator_index,
176          privkey,
177          signed=True,
178      )
179  
180      yield from run_voluntary_exit_processing(state, voluntary_exit, False)
181  
182  
183  @spec_state_test
184  def test_validator_already_exited(state):
185      # move state forward PERSISTENT_COMMITTEE_PERIOD epochs to allow validator able to exit
186      state.slot += spec.PERSISTENT_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
187  
188      current_epoch = get_current_epoch(state)
189      validator_index = get_active_validator_indices(state, current_epoch)[0]
190      privkey = pubkey_to_privkey[state.validator_registry[validator_index].pubkey]
191  
192      # but validator already has exited
193      state.validator_registry[validator_index].exit_epoch = current_epoch + 2
194  
195      voluntary_exit = build_voluntary_exit(
196          state,
197          current_epoch,
198          validator_index,
199          privkey,
200          signed=True,
201      )
202  
203      yield from run_voluntary_exit_processing(state, voluntary_exit, False)
204  
205  
206  @spec_state_test
207  def test_validator_not_active_long_enough(state):
208      current_epoch = get_current_epoch(state)
209      validator_index = get_active_validator_indices(state, current_epoch)[0]
210      privkey = pubkey_to_privkey[state.validator_registry[validator_index].pubkey]
211  
212      voluntary_exit = build_voluntary_exit(
213          state,
214          current_epoch,
215          validator_index,
216          privkey,
217          signed=True,
218      )
219  
220      assert (
221          current_epoch - state.validator_registry[validator_index].activation_epoch <
222          spec.PERSISTENT_COMMITTEE_PERIOD
223      )
224  
225      yield from run_voluntary_exit_processing(state, voluntary_exit, False)