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

IF: Implement proposer policy change #2078

Merged
merged 3 commits into from
Jan 12, 2024
Merged

Conversation

heifner
Copy link
Member

@heifner heifner commented Jan 11, 2024

  • Implement proposer policy change design: https://hackmd.io/@9xzIZ7ZaRwegCHgQW02r3Q/BkQ4Oc4Up copied below.
  • Producer schedule is changed after set_proposed_producer in the next, next producer round.
    • For example: Producer A [ b1, b2, .. b12 ], Producer B [ b1, b2, .. b12 ], Producer X [ b1, b2, .. b12 ]
      • If set_proposed_producer is called in trx in any blocks of Producer A [ b1, b2, .. b12], the schedule change happens at Producer X [b1].
  • public controller pending_producers() and proposed_producers renamed to _legacy.
  • Added next_producers() for use by external enties.

Contents of hackmd doc:

namespace unmolested {

struct key_weight {
   public_key_type key;
   uint16_t        weight;
}

struct block_signing_authority {
   uint32_t           threshold;
   vector<key_weight> keys;
}

struct producer_authority {
   name                    producer_name;
   block_signing_authority authority;
}

struct producer_authority_schedule {
   uint32_t                   version; // generation, incremented for each new set
   vector<producer_authority> producers;
}

producer_authority get_scheduled_producer(block_state prev, block_time_stamp t) {
   auto& as = prev.active_schedule;
   return as.at(as.get_slot_index(t.slot));
}

class global_property_object {
  // always !has_value() after IF activation
  std::optional<block_num_type> proposed_schedule_block_num;
  // proposed_schedule.version == 0, .producuers.empty() after IF activation
  shared_producer_authority_schedule  proposed_schedule;
}

} // unmolested

struct block_header_state { 
   // ..
   proposer_policy_ptr                                 active_proposer_policy;
   // timestamp when it will be active
   flat_map<block_timestamp_type, proposer_policy_ptr> proposer_policies;
}

struct proposer_policy {
   constexpr static            current_schema_version = 1;
   const uint8_t               schema_version = current_schema_version;
   // Useful for light clients, not necessary for nodeos
   const block_timestamp_type        active_time; // block when schedule will become active
   const producer_authority_schedule proposer_schedule;
}

producer_authority get_scheduled_proposer(block_state prev, block_time_stamp t) {
   auto& as = prev.active_proposer_policy;
   return as.at(as.get_slot_index(t.slot));
}

// Used for verification, nodeos 6.0 will only propose when scheduled
bool is_allowed_to_propose(block_state prev, block_time_stamp t, name proposer) {
   if (get_scheduled_proposer(prev, t) == proposer) return true;
   return false;
   // future possible implementation
   auto& as = prev.active_proposer_policy;
   if (as.size() > 3)
      // do not allow previous or next proposer in schedule to propose
      if (get_last_round_proposer(t) == proposer) return false;
      if (get_next_round_proposer(t) == proposer) return false;
   }
   return as.contains(proposer);
}

block_timestamp_type get_next_next_round_block_time( block_timestamp_type t) {
   auto index = t.slot % config::producer_repetitions; // current index in current round
   //              (increment to the end of this round  ) + next round
   return t.slot + (config::producer_repetitions - index) + config::producer_repetitions;
}

// called by host function (no changes to host function implemenation)
int64_t controller::set_proposed_producers(vector<producer_authority> producers) {
   if (if_active) return set_proposed_proposers( std::move(producers) );
   // unmolested ...
}

int64_t controller::set_proposed_proposers(vector<producer_authority> proposers) {
   // enforced regardless of builtin_protocol_feature_t::disallow_empty_producer_schedule
   if (proposers.size() == 0) return -1; // -1 returned to contract caller
 
   // always start new proposers at a round boundary, therefore only one change allowed per round
   block_timestamp_type change_block_time = get_next_next_round_block_time(pending->block_timestamp);
   pending->new_proposer_policy = 
      proposer_policy{ 
        .active_time = change_block_time, 
        .proposer_schedule = sch };
}

controller::finalize_block() {
   // ...
   if (bb._pending_block_header_state.new_proposer_policy) {
      // add to block header extension along with new_finalizer_policy if available
      // Note next block could override this by setting a different proposer policy.
      // For example:
      //    block-3-of-12->new_proposer_policy_1, block-11-of-12->new_proposer_policy_2
      //    Since both block-3-of-12 and block-11-of-12 are in the same round, 
      //    block-3-of-12->new_proposer_policy_1 will never be active.
   }
   // ...
}

block_header_state next() {
   // assuming this if-block will be done elsewhere outside of next on first transistion
   if (previous.block_state.is_legacy_block_state()) {
      // maintain 'version' (generation number). Do not restart 'version'.
      result.active_proposer_policy = 
         proposer_policy{
           .active_block_num = block_num; // value doesn't matter
           .proposer_schedule = legacy_block_state->active_schedule };
      db.modify( gpo, [&]( auto& gp ) {
        gp.proposed_schedule_block_num = 0;
        gp.proposed_schedule.version = 0;
        gp.proposed_schedule.producers.clear();
      });
   }
   
   auto it = proposer_policies.less_than_or_equal(current_block_time);
   if (it != proposer_policies.end()) {
      uint32_t generation = result.active_proposer_policy.proposer_schedule.version;
      result.active_proposer_policy = *it;
      result.active_proposer_policy.proposer_schedule.version = generation + 1;
      result.proposer_policies = { ++it, proposer_policies.end() };
      assert(result.proposer_policies.less_than_or_equal(current_block_time) == proposer_policies.end());
   } else {
      result.proposer_policies = proposer_policies;
   }
   
   // replace existing if same change_block_time (only one per round, keep last)
   // result.new_proposer_policy remains empty
   result.proposer_policies.add(new_proposer_policy.active_time, new_proposer_policy);
}

read_only::get_producer_schedule_result read_only::get_producer_schedule() {
   if (if_active) {
      result.active = db.active_producers();
      result.pending; // pending is always empty
      result.proposed = db.finalizer_policies.top(); // the next one in the queue
   }
}

Resolves #1980

@heifner heifner added the OCI Work exclusive to OCI team label Jan 11, 2024
@heifner heifner marked this pull request as draft January 11, 2024 22:26
@heifner heifner changed the base branch from hotstuff_integration to gh_2034_part2 January 12, 2024 14:30
@heifner heifner marked this pull request as ready for review January 12, 2024 15:38
@heifner heifner requested review from greg7mdp and linh2931 January 12, 2024 15:38
@heifner heifner merged commit a3c7628 into gh_2034_part2 Jan 12, 2024
26 checks passed
@heifner heifner deleted the GH-1980-proposer branch January 12, 2024 16:03
@heifner heifner linked an issue Jan 12, 2024 that may be closed by this pull request
result.active_proposer_policy->proposer_schedule.version = result.header.schedule_version;
result.proposer_policies = { ++it, proposer_policies.end() };
} else {
result.proposer_policies = proposer_policies;
Copy link
Contributor

Choose a reason for hiding this comment

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

should we also have here:
result.proposer_policy = proposer_policy;

Copy link
Member Author

@heifner heifner Jan 12, 2024

Choose a reason for hiding this comment

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

I could have swore I had that in there. Yes, we want result.active_proposer_policy = active_proposer_policy. I can push a commit with that fix.

Copy link
Member Author

Choose a reason for hiding this comment

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

pushed

@ericpassmore
Copy link
Contributor

Note:start
group: IF
category: INTERNALS
summary: Implement proposer policy for Faster Finality. Existing proposal policy renamed to reflect they are legacy.
Note: end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
OCI Work exclusive to OCI team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

IF: Implement proposer policy changes
3 participants