Per period committee snapshot

Solution 3 - maintaining vote counting and head collation on-chain

If we can move vote counting in-chain, the committee size problem could be solved by maintain period_notary_pool_len in storage:

  • period_notary_pool_len represent notary_pool_len that will use for sampling current period
  • notary_offset represent the variation of notary_pool_len during the current period. (Might be negative number)

p.s. there’s still a notary_pool_len which represents the real length.

The period_notary_pool_len will be updated when the first time SMC is called with transaction per period, includes:

  1. register_notary
  2. deregister_notary
  3. add_header

Then we can apply @NicLin 's voting checking.

  1. When the notaries try to do lookahead right after the period starts, they use notary_pool_len = period_notary_pool_len + notary_offset.
  2. For the on-chain voting, using period_notary_pool_len in contract.
  • Pros:
    1. Less on-chain gas spending for the proposer than Solution 2.
  • Cons:
    1. When the case that index out of range, this seat will be given up.
    2. If add_header becomes off-chain in the future scheme, it will be tricky again.
    3. Only the notary pool being snapshotted, so it’s possible that one notary_1 deregisters and another notary_2 registers and takes the empty slot from notary_1 during one period. If there’s slashing condition for do-nothing notary, the innocent notary_2 will be penalized?

Summary of Solution 3 + @vbuterin 's bitfield counting:

  1. Add one storage in SMC: last_period: public(int128) records the period of the most recent success add_header message.

  2. Add a storage variable current_vote: public(bytes32):

    • First bytes31: bitfield of who has voted and who has not. Each bit represent the iterating number in the get_committee function.
    • Last byte: a counter of the total number of eligible notaries that voted
  3. Add one period_notary_pool_len in storage to represent the notary_pool_len at the time of that the period just starts.

  4. Add a SMC.settle_current_notary_len() function to settle period_notary_pool_len:

    def settle_current_notary_len() -> int128:
        self.period_notary_pool_len += self.notary_offset
        self.notary_offset = 0
        self.last_period = current_period
        return self.period_notary_pool_len
    
  5. Modify SMC.register_notary function:

    • If self.last_period < current_period, call self.settle_current_notary_len().
    • self.natary_offset += 1
  6. Modify SMC.deregister_notary function:

    • If self.last_period < current_period, call self.settle_notary_len().
    • natary_offset -= 1
  7. Modify SMC.add_header function. When the-first-proposer-per-period calls it:

    • If self.last_period < current_period, call self.settle_current_notary_len().
    • Resets current_vote
  8. Add check_eligible_notary(shard_id, period, index) checks if the notary index is eligible:

    def check_eligible_notary(shard_id, period, index) -> bool:
        return msg.sender == self.notary_pool[sha3(h + index) % self.period_notary_pool_len]
    
    • Another version for the notary to do lookahead message call only and it returns index if result >= 0:
      def lookahead_eligible_notary(shard_id, period, notary_pool_len) -> int128:
          for index in range(self.COMMITTEE_SIZE):
             if msg.sender == self.notary_pool[sha3(h + index) % notary_pool_len]
                 return index
          return -1
      
  9. Modify SMC.submit_vote function:

    • Checks the eligibility with SMC.check_eligible_notary(shard_id, period, index)
    • Updates current_vote:
      • Set the bit of index to 1
      • Increase last byte by 1
    • If current_vote >= 2/3 COMMITTEE_SIZE, update head collation info on-chain.