pid.md 4.1 KB

PID controller rewrite spec

This document describes the spec planning for the PID controller rewrite, needed to simplify the current implementation, along with pseudo code representing each functionality.

Slot sigmas

We need a function to return 2-term target approximation sigma coefficients, corresponding to provided slot consensus state, represented as pallas::Base. To generate the slot sigmas, we have to perform the following:

  1. Calculate the inverse probability f of becoming a block producer (winning the lottery) having all the tokens, represented as Float10.
  2. Calculate network total tokens, deriving from genesis stake, genesis mint, and all block rewarded tokens, represented as Float10.
  3. Calculate the sigmas using previous 2 numbers, represented as pallas::Base.

Each step will be further described in the sub-sections following.

Pseudocode:

/// Return 2-term target approximation sigma coefficients,
/// corresponding to provided slot consensus state.
fn sigmas() -> (pallas::Base, pallas::Base) {
    let f: Float10 = calculate_f();
    let total_tokens: Float10 = total_tokens();
    calculate_sigmas(f, total_tokens)
}

Calculate f

In this step we execute the actual PID controller calculation to calculate f. This calculation asumes we keep track of historic data, like the error feedback and the values themselves. To achieve that, we will store these values in each generated slot, so everyone can validate them in sequence, as those values are based on each slot previous values, therefore showcasing the progression up to that point in time.

Pseudocode:

/// Calculate the inverse probability `f` of becoming a block producer (winning the lottery)
/// having all the tokens, represented as Float10.
fn calculate_f() -> Float10 {
    // PID controller K values based on constants
    let k1 = KP + KI + KD;
    let k2 = FLOAT10_NEG_ONE * KP + FLOAT10_NEG_TWO * KD;
    let k3 = KD;
    
    // Calculate feedback error based on previous block producers.
    // We know how many producers existed in previous slot by
    // the len of its fork hashes.
    let feedback: Float10 = previous_slot.fork_hashes.len();
    let err = FLOAT10_ONE - feedback;
    
    // Calculate f
    let f = previous_slot.f + k1 * err + k2 * previous_slot.err + k3 * previous_previous_slot.err;
    
    // Boundaries control
    if f <= FLOAT10_ZERO {
        f = MIN_F.clone()
    } else if f >= FLOAT10_ONE {
        f = MAX_F
    }
    
    f
}

Calculate total tokens

In this step we calculate network total tokens.

Pseudocode:

/// Network total tokens, assuming constant reward.
/// Only used for fine-tuning. Since a genesis staker
/// is needed to progress the blockchain, we know that
/// total tokens > 0, as genesis_tokens > 0.
fn total_tokens() -> u64 {
    // Retrieve existing blocks count, excluding genesis
    let blocks = (blockchain.len() as u64) - 1;
    // Retrieve longest fork length, to include those
    // proposals in the calculation
    let max_fork_length = longest_chain_length() as u64;
    // Calculate rewarded slots
    let rewarded_slots = blocks + max_fork_length;
    // Calculate rewarded tokens
    let rewarded_tokens = rewarded_slots * REWARD
    
    rewarded_tokens + genesis_tokens
}

Calculate sigmas

Finally we can produce the slot sigmas, based on previous calculations.

Pseudocode:

/// Return 2-term target approximation sigma coefficients,
/// corresponding to provided `f` and `total_tokens` values.
fn calculate_sigmas(f: Float10, total_tokens: Float10) -> (pallas::Base, pallas::Base) {
    // Field `P` value represented as `Float10`
    let field_p: Float10 = P;

    // Calculate `neg_c` value
    let x = FLOAT10_ONE - f;
    let c = x.ln();
    let neg_c = FLOAT10_NEG_ONE * c;

    // Calculate sigma 1
    let sigma1_fbig = neg_c / (total_tokens + FLOAT10_EPSILON) * field_p;
    let sigma1 = fbig2base(sigma1_fbig);

    // Calculate sigma 2
    let sigma2_fbig = (neg_c / (total_tokens + FLOAT10_EPSILON)).powf(FLOAT10_TWO) * (field_p / FLOAT10_TWO);
    let sigma2 = fbig2base(sigma2_fbig);

    (sigma1, sigma2)
}