starcoin-framework

Module 0x1::Epoch

The module provide epoch functionality for starcoin.

use 0x1::ConsensusConfig;
use 0x1::CoreAddresses;
use 0x1::Errors;
use 0x1::Event;
use 0x1::Math;
use 0x1::Option;
use 0x1::Timestamp;

Resource Epoch

Current epoch info.

struct Epoch has key
Fields
number: u64
Number of current epoch
start_time: u64
Start time of current epoch
start_block_number: u64
Start block's number of current epoch
end_block_number: u64
End block's number of current epoch
block_time_target: u64
Average target time to calculate a block's difficulty in current epoch
reward_per_block: u128
Rewards per block in current epoch
reward_per_uncle_percent: u64
Percentage of reward_per_block to reward a uncle block in current epoch
block_difficulty_window: u64
How many ancestor blocks which use to calculate next block's difficulty in current epoch
max_uncles_per_block: u64
Maximum number of uncle block per block in current epoch
block_gas_limit: u64
Maximum gases per block in current epoch
strategy: u8
Strategy to calculate difficulty in current epoch
new_epoch_events: Event::EventHandle<Epoch::NewEpochEvent>
Switch Epoch Event

Struct NewEpochEvent

New epoch event.

struct NewEpochEvent has drop, store
Fields
number: u64
Epoch::number
start_time: u64
Epoch::start_time
start_block_number: u64
Epoch::start_block_number
end_block_number: u64
Epoch::end_block_number
block_time_target: u64
Epoch::block_time_target
reward_per_block: u128
Epoch::reward_per_block
previous_epoch_total_reward: u128
Total rewards during previous epoch

Resource EpochData

Epoch data.

struct EpochData has key
Fields
uncles: u64
Up to now, Number of uncle block during current epoch
total_reward: u128
Up to now, Total rewards during current epoch
total_gas: u128
Up to now, Total gases during current epoch

Constants

const EINVALID_UNCLES_COUNT: u64 = 101;

const EUNREACHABLE: u64 = 19;

const HUNDRED: u64 = 100;

const THOUSAND: u64 = 1000;

const THOUSAND_U128: u128 = 1000;

Function initialize

Initialization of the module.

public fun initialize(account: &signer)
Implementation
public fun initialize(
    account: &signer,
) {
    Timestamp::assert_genesis();
    CoreAddresses::assert_genesis_address(account);

    let config = ConsensusConfig::get_config();
    move_to<Epoch>(
        account,
        Epoch {
            number: 0,
            start_time: Timestamp::now_milliseconds(),
            start_block_number: 0,
            end_block_number: ConsensusConfig::epoch_block_count(&config),
            block_time_target: ConsensusConfig::base_block_time_target(&config),
            reward_per_block: ConsensusConfig::base_reward_per_block(&config),
            reward_per_uncle_percent: ConsensusConfig::base_reward_per_uncle_percent(&config),
            block_difficulty_window: ConsensusConfig::base_block_difficulty_window(&config),
            max_uncles_per_block: ConsensusConfig::base_max_uncles_per_block(&config),
            block_gas_limit: ConsensusConfig::base_block_gas_limit(&config),
            strategy: ConsensusConfig::strategy(&config),
            new_epoch_events: Event::new_event_handle<NewEpochEvent>(account),
        },
    );
    move_to<EpochData>(account, EpochData { uncles: 0, total_reward: 0, total_gas: 0 });
}
Specification
aborts_if !Timestamp::is_genesis();
aborts_if Signer::address_of(account) != CoreAddresses::GENESIS_ADDRESS();
aborts_if !exists<Timestamp::CurrentTimeMilliseconds>(CoreAddresses::GENESIS_ADDRESS());
aborts_if !exists<Config::Config<ConsensusConfig>>(CoreAddresses::GENESIS_ADDRESS());
aborts_if exists<Epoch>(Signer::address_of(account));
aborts_if exists<EpochData>(Signer::address_of(account));

Function compute_next_block_time_target

compute next block time_target.

public fun compute_next_block_time_target(config: &ConsensusConfig::ConsensusConfig, last_epoch_time_target: u64, epoch_start_time: u64, now_milli_second: u64, start_block_number: u64, end_block_number: u64, total_uncles: u64): u64
Implementation
public fun compute_next_block_time_target(config: &ConsensusConfig, last_epoch_time_target: u64, epoch_start_time: u64, now_milli_second: u64, start_block_number: u64, end_block_number: u64, total_uncles: u64): u64 {
    let total_time = now_milli_second - epoch_start_time;
    let blocks = end_block_number - start_block_number;
    let avg_block_time = total_time / blocks;
    let uncles_rate = total_uncles * THOUSAND / blocks;
    let new_epoch_block_time_target = (THOUSAND + uncles_rate) * avg_block_time /
            (ConsensusConfig::uncle_rate_target(config) + THOUSAND);
    if (new_epoch_block_time_target > last_epoch_time_target * 2) {
        new_epoch_block_time_target = last_epoch_time_target * 2;
    };
    if (new_epoch_block_time_target < last_epoch_time_target / 2) {
        new_epoch_block_time_target = last_epoch_time_target / 2;
    };
    let min_block_time_target = ConsensusConfig::min_block_time_target(config);
    let max_block_time_target = ConsensusConfig::max_block_time_target(config);
    if (new_epoch_block_time_target < min_block_time_target) {
        new_epoch_block_time_target = min_block_time_target;
    };
    if (new_epoch_block_time_target > max_block_time_target) {
        new_epoch_block_time_target = max_block_time_target;
    };
    new_epoch_block_time_target
}
Specification
pragma verify = false;

Function adjust_epoch

adjust_epoch try to advance to next epoch if current epoch ends.

public fun adjust_epoch(account: &signer, block_number: u64, timestamp: u64, uncles: u64, parent_gas_used: u64): u128
Implementation
public fun adjust_epoch(account: &signer, block_number: u64, timestamp: u64, uncles: u64, parent_gas_used:u64): u128
acquires Epoch, EpochData {
    CoreAddresses::assert_genesis_address(account);

    let epoch_ref = borrow_global_mut<Epoch>(CoreAddresses::GENESIS_ADDRESS());
    assert!(epoch_ref.max_uncles_per_block >= uncles, Errors::invalid_argument(EINVALID_UNCLES_COUNT));

    let epoch_data = borrow_global_mut<EpochData>(CoreAddresses::GENESIS_ADDRESS());
    let (new_epoch, reward_per_block) = if (block_number < epoch_ref.end_block_number) {
        (false, epoch_ref.reward_per_block)
    } else if (block_number == epoch_ref.end_block_number) {
        //start a new epoch
        assert!(uncles == 0, Errors::invalid_argument(EINVALID_UNCLES_COUNT));
        // block time target unit is milli_seconds.
        let now_milli_seconds = timestamp;

        let config = ConsensusConfig::get_config();
        let last_epoch_time_target = epoch_ref.block_time_target;
        let new_epoch_block_time_target = compute_next_block_time_target(&config, last_epoch_time_target, epoch_ref.start_time, now_milli_seconds, epoch_ref.start_block_number, epoch_ref.end_block_number, epoch_data.uncles);
        let new_reward_per_block = ConsensusConfig::do_compute_reward_per_block(&config, new_epoch_block_time_target);

        //update epoch by adjust result or config, because ConsensusConfig may be updated.
        epoch_ref.number = epoch_ref.number + 1;
        epoch_ref.start_time = now_milli_seconds;
        epoch_ref.start_block_number = block_number;
        epoch_ref.end_block_number = block_number + ConsensusConfig::epoch_block_count(&config);
        epoch_ref.block_time_target = new_epoch_block_time_target;
        epoch_ref.reward_per_block = new_reward_per_block;
        epoch_ref.reward_per_uncle_percent = ConsensusConfig::base_reward_per_uncle_percent(&config);
        epoch_ref.block_difficulty_window = ConsensusConfig::base_block_difficulty_window(&config);
        epoch_ref.max_uncles_per_block = ConsensusConfig::base_max_uncles_per_block(&config);
        epoch_ref.strategy = ConsensusConfig::strategy(&config);

        epoch_data.uncles = 0;
        let last_epoch_total_gas = epoch_data.total_gas + (parent_gas_used as u128);
        adjust_gas_limit(&config, epoch_ref, last_epoch_time_target, new_epoch_block_time_target, last_epoch_total_gas);
        emit_epoch_event(epoch_ref, epoch_data.total_reward);
        (true, new_reward_per_block)
    } else {
        //This should never happened.
        abort EUNREACHABLE
    };
    let reward = reward_per_block +
            reward_per_block * (epoch_ref.reward_per_uncle_percent as u128) * (uncles as u128) / (HUNDRED as u128);
    update_epoch_data(epoch_data, new_epoch, reward, uncles, parent_gas_used);
    reward
}
Specification
pragma verify = false;
aborts_if Signer::address_of(account) != CoreAddresses::GENESIS_ADDRESS();
aborts_if !exists<Epoch>(Signer::address_of(account));
aborts_if global<Epoch>(Signer::address_of(account)).max_uncles_per_block < uncles;
aborts_if exists<EpochData>(Signer::address_of(account));
aborts_if block_number == global<Epoch>(Signer::address_of(account)).end_block_number && uncles != 0;

Function adjust_gas_limit

fun adjust_gas_limit(config: &ConsensusConfig::ConsensusConfig, epoch_ref: &mut Epoch::Epoch, last_epoch_time_target: u64, new_epoch_time_target: u64, last_epoch_total_gas: u128)
Implementation
fun adjust_gas_limit(config: &ConsensusConfig, epoch_ref: &mut Epoch, last_epoch_time_target: u64, new_epoch_time_target: u64, last_epoch_total_gas:u128) {
    let new_gas_limit = compute_gas_limit(config, last_epoch_time_target, new_epoch_time_target, epoch_ref.block_gas_limit, last_epoch_total_gas);
    if (Option::is_some(&new_gas_limit)) {
        epoch_ref.block_gas_limit = Option::destroy_some(new_gas_limit);
    }
}
Specification
pragma verify = false;

Function compute_gas_limit

Compute block’s gas limit of next epoch.

public fun compute_gas_limit(config: &ConsensusConfig::ConsensusConfig, last_epoch_time_target: u64, new_epoch_time_target: u64, last_epoch_block_gas_limit: u64, last_epoch_total_gas: u128): Option::Option<u64>
Implementation
public fun compute_gas_limit(config: &ConsensusConfig, last_epoch_time_target: u64, new_epoch_time_target: u64, last_epoch_block_gas_limit: u64, last_epoch_total_gas: u128) : Option::Option<u64> {
    let epoch_block_count = (ConsensusConfig::epoch_block_count(config) as u128);
    let gas_limit_threshold = (last_epoch_total_gas >= Math::mul_div((last_epoch_block_gas_limit as u128) * epoch_block_count, (80 as u128), (HUNDRED as u128)));
    let new_gas_limit = Option::none<u64>();

    let min_block_time_target = ConsensusConfig::min_block_time_target(config);
    let max_block_time_target = ConsensusConfig::max_block_time_target(config);
    let base_block_gas_limit =  ConsensusConfig::base_block_gas_limit(config);
    if (last_epoch_time_target == new_epoch_time_target) {
        if (new_epoch_time_target == min_block_time_target && gas_limit_threshold) {
            let increase_gas_limit = in_or_decrease_gas_limit(last_epoch_block_gas_limit, 110, base_block_gas_limit);
            new_gas_limit = Option::some(increase_gas_limit);
        } else if (new_epoch_time_target == max_block_time_target && !gas_limit_threshold) {
            let decrease_gas_limit = in_or_decrease_gas_limit(last_epoch_block_gas_limit, 90, base_block_gas_limit);
            new_gas_limit = Option::some(decrease_gas_limit);
        }
    };

    new_gas_limit
}
Specification
pragma verify = false;

Function in_or_decrease_gas_limit

fun in_or_decrease_gas_limit(last_epoch_block_gas_limit: u64, percent: u64, min_block_gas_limit: u64): u64
Implementation
fun in_or_decrease_gas_limit(last_epoch_block_gas_limit: u64, percent: u64, min_block_gas_limit: u64): u64 {
    let tmp_gas_limit = Math::mul_div((last_epoch_block_gas_limit as u128), (percent as u128), (HUNDRED as u128));
    let new_gas_limit = if (tmp_gas_limit > (min_block_gas_limit  as u128)) {
        (tmp_gas_limit as u64)
    } else {
        min_block_gas_limit
    };

    new_gas_limit
}
Specification
include Math::MulDivAbortsIf{x: last_epoch_block_gas_limit, y: percent, z: HUNDRED};
aborts_if Math::spec_mul_div() > MAX_U64;

Function update_epoch_data

fun update_epoch_data(epoch_data: &mut Epoch::EpochData, new_epoch: bool, reward: u128, uncles: u64, parent_gas_used: u64)
Implementation
fun update_epoch_data(epoch_data: &mut EpochData, new_epoch: bool, reward: u128, uncles: u64, parent_gas_used:u64) {
    if (new_epoch) {
        epoch_data.total_reward = reward;
        epoch_data.uncles = uncles;
        epoch_data.total_gas = 0;
    } else {
        epoch_data.total_reward = epoch_data.total_reward + reward;
        epoch_data.uncles = epoch_data.uncles + uncles;
        epoch_data.total_gas = epoch_data.total_gas + (parent_gas_used as u128);
    }
}
Specification
aborts_if !new_epoch && epoch_data.total_reward + reward > MAX_U128;
aborts_if !new_epoch && epoch_data.uncles + uncles > MAX_U64;
aborts_if !new_epoch && epoch_data.total_gas + parent_gas_used > MAX_U128;

Function emit_epoch_event

fun emit_epoch_event(epoch_ref: &mut Epoch::Epoch, previous_epoch_total_reward: u128)
Implementation
fun emit_epoch_event(epoch_ref: &mut Epoch, previous_epoch_total_reward: u128) {
    Event::emit_event(
        &mut epoch_ref.new_epoch_events,
        NewEpochEvent {
            number: epoch_ref.number,
            start_time: epoch_ref.start_time,
            start_block_number: epoch_ref.start_block_number,
            end_block_number: epoch_ref.end_block_number,
            block_time_target: epoch_ref.block_time_target,
            reward_per_block: epoch_ref.reward_per_block,
            previous_epoch_total_reward,
        },
    );
}
Specification
aborts_if false;

Function start_time

Get start time of current epoch

public fun start_time(): u64
Implementation
public fun start_time(): u64 acquires Epoch {
    let epoch_ref = borrow_global<Epoch>(CoreAddresses::GENESIS_ADDRESS());
    epoch_ref.start_time
}
Specification
aborts_if !exists<Epoch>(CoreAddresses::GENESIS_ADDRESS());

Function uncles

Get uncles number of current epoch

public fun uncles(): u64
Implementation
public fun uncles(): u64 acquires EpochData {
    let epoch_data = borrow_global<EpochData>(CoreAddresses::GENESIS_ADDRESS());
    epoch_data.uncles
}
Specification
aborts_if !exists<EpochData>(CoreAddresses::GENESIS_ADDRESS());

Function total_gas

Get total gas of current epoch

public fun total_gas(): u128
Implementation
public fun total_gas(): u128 acquires EpochData {
    let epoch_data = borrow_global<EpochData>(CoreAddresses::GENESIS_ADDRESS());
    epoch_data.total_gas
}
Specification
aborts_if !exists<EpochData>(CoreAddresses::GENESIS_ADDRESS());

Function block_gas_limit

Get block’s gas_limit of current epoch

public fun block_gas_limit(): u64
Implementation
public fun block_gas_limit(): u64 acquires Epoch {
    let epoch_ref = borrow_global<Epoch>(CoreAddresses::GENESIS_ADDRESS());
    epoch_ref.block_gas_limit
}
Specification
aborts_if !exists<Epoch>(CoreAddresses::GENESIS_ADDRESS());

Function start_block_number

Get start block’s number of current epoch

public fun start_block_number(): u64
Implementation
public fun start_block_number(): u64 acquires Epoch {
    let epoch_ref = borrow_global<Epoch>(CoreAddresses::GENESIS_ADDRESS());
    epoch_ref.start_block_number
}
Specification
aborts_if !exists<Epoch>(CoreAddresses::GENESIS_ADDRESS());

Function end_block_number

Get end block’s number of current epoch

public fun end_block_number(): u64
Implementation
public fun end_block_number(): u64 acquires Epoch {
    let epoch_ref = borrow_global<Epoch>(CoreAddresses::GENESIS_ADDRESS());
    epoch_ref.end_block_number
}
Specification
aborts_if !exists<Epoch>(CoreAddresses::GENESIS_ADDRESS());

Function number

Get current epoch number

public fun number(): u64
Implementation
public fun number(): u64 acquires Epoch {
    let epoch_ref = borrow_global<Epoch>(CoreAddresses::GENESIS_ADDRESS());
    epoch_ref.number
}
Specification
aborts_if !exists<Epoch>(CoreAddresses::GENESIS_ADDRESS());

Function block_time_target

Get current block time target

public fun block_time_target(): u64
Implementation
public fun block_time_target(): u64 acquires Epoch {
    let epoch_ref = borrow_global<Epoch>(CoreAddresses::GENESIS_ADDRESS());
    epoch_ref.block_time_target
}
Specification
aborts_if !exists<Epoch>(CoreAddresses::GENESIS_ADDRESS());

Module Specification

pragma verify;
pragma aborts_if_is_strict;