Skip to content
This repository has been archived by the owner on Aug 2, 2022. It is now read-only.

Commit

Permalink
Ref #19: Implement producer scheduling
Browse files Browse the repository at this point in the history
Calculating the new producer schedule based on votes is now implemented;
however, the voting itself is not. :D But the code builds, and the tests
pass. It's good progress, it's been a good day.

Note that "the tests pass" means little at this stage, since I haven't
written any new tests to exercise the new code. In other words, NEEDS
TESTING!
  • Loading branch information
nathanielhourt committed Jun 15, 2017
1 parent cb13ff0 commit 566c1da
Show file tree
Hide file tree
Showing 12 changed files with 251 additions and 69 deletions.
1 change: 1 addition & 0 deletions libraries/chain/chain_controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -859,6 +859,7 @@ chain_controller::chain_controller(database& database, fork_database& fork_db, b
}();

initialize_indexes();
starter.register_types(*this, _db);
initialize_chain(starter);
spinup_db();
spinup_fork_db();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ class chain_administration_interface {
public:
virtual ~chain_administration_interface();

virtual ProducerRound get_next_round(const chainbase::database& db) = 0;
/**
* @brief Calculate the next round of block producers and return it
* @param db The current blockchain database state. Private state or block producer scheduling may be modiied.
* @return The next round of block producers, sorted by owner name
*/
virtual ProducerRound get_next_round(chainbase::database& db) = 0;
virtual BlockchainConfiguration get_blockchain_configuration(const chainbase::database& db,
const ProducerRound& round) = 0;
};
Expand Down
6 changes: 2 additions & 4 deletions libraries/chain/include/eos/chain/chain_controller.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -252,10 +252,6 @@ namespace eos { namespace chain {
void apply_debug_updates();
void debug_update(const fc::variant_object& update);

// these were formerly private, but they have a fairly well-defined API, so let's make them public
void apply_block(const signed_block& next_block, uint32_t skip = skip_nothing);
void apply_transaction(const SignedTransaction& trx, uint32_t skip = skip_nothing);

protected:
const chainbase::database& get_database() const { return _db; }

Expand All @@ -266,6 +262,8 @@ namespace eos { namespace chain {

void replay();

void apply_block(const signed_block& next_block, uint32_t skip = skip_nothing);
void apply_transaction(const SignedTransaction& trx, uint32_t skip = skip_nothing);
void _apply_block(const signed_block& next_block);
void _apply_transaction(const SignedTransaction& trx);

Expand Down
29 changes: 19 additions & 10 deletions libraries/chain/include/eos/chain/chain_initializer_interface.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,27 +24,36 @@ class chain_initializer_interface {
/// Retrieve the first round of block producers
virtual std::array<AccountName, config::BlocksPerRound> get_chain_start_producers() = 0;

/**
* @brief Install necessary indices and message handlers that chain_controller doesn't know about
*
* This method is called every time the chain_controller is initialized, before any chain state is read or written,
* regardless of whether the chain is new or not.
*
* This method may perform any necessary initializations on the chain and/or database, such as installing indices
* and message handlers that should be defined before the first block is processed. This may be necessary in order
* for the list of messages returned by @ref initialize_database to be processed successfully.
*/
virtual void register_types(chain_controller& chain, chainbase::database& db) = 0;
/**
* @brief Prepare the database, creating objects and defining state which should exist before the first block
* @param chain A reference to the @ref chain_controller
* @param db A reference to the @ref chainbase::database
* @return A list of @ref Message "Messages" to be applied before the first block
*
* This method is only called when starting a new blockchain. It is called at the end of chain initialization, after
* setting the state used in core chain operations.
*
* This method creates the @ref account_object "account_objects" and @ref producer_object "producer_objects" for
* at least the initial block producers returned by @ref get_chain_start_producers
*
* This method also provides an opportunity to create objects and setup the database to the state it should be in
* prior to the first block. This method should only initialize state that the @ref chain_controller itself does
* not understand. The other methods on @ref chain_initializer are called to retrieve the data necessary to
* initialize chain state the controller does understand, including the initial round of block producers and the
* initial @ref BlockchainConfiguration.
*
* Finally, this method may perform any necessary initializations on the chain and/or database, such as
* installing indexes and message handlers that should be defined before the first block is processed. This may
* be necessary in order for the returned list of messages to be processed successfully.
* prior to the first block, including registering any message types unknown to @ref chain_controller. This method
* should only initialize state that the @ref chain_controller itself does not understand.
*
* This method is called at the end of chain initialization, after setting the state used in core chain
* operations.
* The other methods on @ref chain_initializer_interface are called to retrieve the data necessary to initialize
* chain state the controller does understand, including the initial round of block producers, the initial chain
* time, and the initial @ref BlockchainConfiguration.
*/
virtual vector<Message> prepare_database(chain_controller& chain, chainbase::database& db) = 0;
};
Expand Down
2 changes: 2 additions & 0 deletions libraries/chain/include/eos/chain/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ namespace eos { namespace chain {
balance_object_type, ///< Defined by native_system_contract_plugin
staked_balance_object_type, ///< Defined by native_system_contract_plugin
producer_votes_object_type, ///< Defined by native_system_contract_plugin
producer_schedule_object_type, ///< Defined by native_system_contract_plugin
OBJECT_TYPE_COUNT ///< Sentry value which contains the number of different object types
};

Expand Down Expand Up @@ -201,6 +202,7 @@ FC_REFLECT_ENUM(eos::chain::object_type,
(balance_object_type)
(staked_balance_object_type)
(producer_votes_object_type)
(producer_schedule_object_type)
(OBJECT_TYPE_COUNT)
)
FC_REFLECT( eos::chain::void_t, )
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace eos { namespace native_contract {
using chain::ProducerRound;

class native_contract_chain_administrator : public chain::chain_administration_interface {
ProducerRound get_next_round(const chainbase::database& db);
ProducerRound get_next_round(chainbase::database& db);
chain::BlockchainConfiguration get_blockchain_configuration(const chainbase::database& db,
const ProducerRound& round);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@ class native_contract_chain_initializer : public chain::chain_initializer_interf
native_contract_chain_initializer(const genesis_state_type& genesis) : genesis(genesis) {}
virtual ~native_contract_chain_initializer() {}

virtual std::vector<chain::Message> prepare_database(chain::chain_controller& chain, chainbase::database& db);
virtual types::Time get_chain_start_time();
virtual chain::BlockchainConfiguration get_chain_start_configuration();
virtual std::array<types::AccountName, config::BlocksPerRound> get_chain_start_producers();
virtual types::Time get_chain_start_time() override;
virtual chain::BlockchainConfiguration get_chain_start_configuration() override;
virtual std::array<types::AccountName, config::BlocksPerRound> get_chain_start_producers() override;

virtual void register_types(chain::chain_controller& chain, chainbase::database& db) override;
virtual std::vector<chain::Message> prepare_database(chain::chain_controller& chain,
chainbase::database& db) override;
};

} } // namespace eos::native_contract
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@

#include <eos/types/types.hpp>

#include <eos/utilities/exception_macros.hpp>

#include <chainbase/chainbase.hpp>

#include <boost/multi_index/mem_fun.hpp>

namespace eos {

FC_DECLARE_EXCEPTION(ProducerRaceOverflowException, 10000000, "Producer Virtual Race time has overflowed");

/**
* @brief The ProducerVotesObject class tracks all votes for and by the block producers
*
Expand Down Expand Up @@ -65,18 +69,67 @@ class ProducerVotesObject : public chainbase::object<chain::producer_votes_objec
*/
struct {
/// The current speed for this producer (which is actually the total votes for the producer)
types::ShareType speed;
types::ShareType speed = 0;
/// The position of this producer when we last updated the records
types::UInt128 position;
types::UInt128 position = 0;
/// The "race time" when we last updated the records
types::UInt128 positionUpdateTime;
types::UInt128 positionUpdateTime = 0;
/// The projected "race time" at which this producer will finish the race
types::UInt128 projectedFinishTime = std::numeric_limits<types::UInt128>::max();

/// Set all fields on race, given the current speed, position, and time
void update(types::ShareType currentSpeed, types::UInt128 currentPosition, types::UInt128 currentRaceTime) {
speed = currentSpeed;
position = currentPosition;
positionUpdateTime = currentRaceTime;
auto distanceRemaining = config::ProducerRaceLapLength - position;
auto projectedTimeToFinish = speed > 0? distanceRemaining / speed
: std::numeric_limits<types::UInt128>::max();
EOS_ASSERT(currentRaceTime <= std::numeric_limits<types::UInt128>::max() - projectedTimeToFinish,
ProducerRaceOverflowException, "Producer race time has overflowed",
("currentTime", currentRaceTime)("timeToFinish", projectedTimeToFinish)("limit", std::numeric_limits<types::UInt128>::max()));
projectedFinishTime = currentRaceTime + projectedTimeToFinish;
}
} race;

void startNewRaceLap(types::UInt128 currentRaceTime) { race.update(race.speed, 0, currentRaceTime); }
types::UInt128 projectedRaceFinishTime() const { return race.projectedFinishTime; }
};

/**
* @brief The ProducerScheduleObject class schedules producers into rounds
*
* This class stores the state of the virtual race to select runner-up producers, and provides the logic for selecting
* a round of producers.
*
* This is a singleton object within the database; there will only be one stored.
*/
class ProducerScheduleObject : public chainbase::object<chain::producer_schedule_object_type, ProducerScheduleObject> {
OBJECT_CTOR(ProducerScheduleObject)

id_type id;
types::UInt128 currentRaceTime = 0;

/// Retrieve a reference to the ProducerScheduleObject stored in the provided database
static const ProducerScheduleObject& get(const chainbase::database& db) { return db.get(id_type()); }

/**
* @brief Calculate the next round of block producers
* @param db The blockchain database
* @return The next round of block producers, sorted by owner name
*
* This method calculates the next round of block producers according to votes and the virtual race for runner-up
* producers. Although it is a const method, it will use its non-const db parameter to update its own records, as
* well as the race records stored in the @ref ProducerVotesObjects
*/
chain::ProducerRound calculateNextRound(chainbase::database& db) const;

/**
* @brief Reset all producers in the virtual race to the starting line, and reset virtual time to zero
*/
void resetProducerRace(chainbase::database& db) const;
};

using boost::multi_index::const_mem_fun;
/// Index producers by their owner's name
struct byOwnerName;
Expand Down Expand Up @@ -104,6 +157,16 @@ using ProducerVotesMultiIndex = chainbase::shared_multi_index_container<
>
>;

using ProducerScheduleMultiIndex = chainbase::shared_multi_index_container<
ProducerScheduleObject,
indexed_by<
ordered_unique<tag<by_id>,
member<ProducerScheduleObject, ProducerScheduleObject::id_type, &ProducerScheduleObject::id>
>
>
>;

} // namespace eos

CHAINBASE_SET_INDEX_TYPE(eos::ProducerVotesObject, eos::ProducerVotesMultiIndex)
CHAINBASE_SET_INDEX_TYPE(eos::ProducerScheduleObject, eos::ProducerScheduleMultiIndex)
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#include <eos/native_contract/native_contract_chain_administrator.hpp>
#include <eos/native_contract/staked_balance_objects.hpp>
#include <eos/native_contract/producer_objects.hpp>

#include <eos/chain/global_property_object.hpp>
#include <eos/chain/producer_object.hpp>
Expand All @@ -11,9 +11,8 @@ namespace eos { namespace native_contract {

using administrator = native_contract_chain_administrator;

ProducerRound administrator::get_next_round(const chainbase::database& db) {
#warning TODO: Implement me
return db.get(chain::global_property_object::id_type()).active_producers;
ProducerRound administrator::get_next_round(chainbase::database& db) {
return ProducerScheduleObject::get(db).calculateNextRound(db);
}

chain::BlockchainConfiguration administrator::get_blockchain_configuration(const chainbase::database& db,
Expand All @@ -22,11 +21,11 @@ chain::BlockchainConfiguration administrator::get_blockchain_configuration(const
using types::AccountName;
using chain::producer_object;

auto get_producer_votes = transformed([&db](const AccountName& owner) {
auto ProducerNameToConfiguration = transformed([&db](const AccountName& owner) {
return db.get<producer_object, chain::by_owner>(owner).configuration;
});

auto votes_range = round | get_producer_votes;
auto votes_range = round | ProducerNameToConfiguration;

return chain::BlockchainConfiguration::get_median_values({votes_range.begin(), votes_range.end()});
}
Expand Down
74 changes: 40 additions & 34 deletions libraries/native_contract/native_contract_chain_initializer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,30 +12,27 @@
namespace eos { namespace native_contract {
using namespace chain;

std::vector<chain::Message> native_contract_chain_initializer::prepare_database(chain_controller& chain,
chainbase::database& db) {
std::vector<chain::Message> messages_to_process;
types::Time native_contract_chain_initializer::get_chain_start_time() {
return genesis.initial_timestamp;
}

chain::BlockchainConfiguration native_contract_chain_initializer::get_chain_start_configuration() {
return genesis.initial_configuration;
}

std::array<types::AccountName, config::BlocksPerRound> native_contract_chain_initializer::get_chain_start_producers() {
std::array<types::AccountName, config::BlocksPerRound> result;
std::transform(genesis.initial_producers.begin(), genesis.initial_producers.end(), result.begin(),
[](const auto& p) { return p.owner_name; });
return result;
}

void native_contract_chain_initializer::register_types(chain_controller& chain, chainbase::database& db) {
// Install the native contract's indexes; we can't do anything until our objects are recognized
db.add_index<StakedBalanceMultiIndex>();
db.add_index<BalanceMultiIndex>();
db.add_index<ProducerVotesMultiIndex>();

/// Create the native contract accounts manually; sadly, we can't run their contracts to make them create themselves
auto CreateNativeAccount = [this, &db](auto name, auto liquidBalance) {
db.create<account_object>([this, &name](account_object& a) {
a.name = name;
a.creation_date = genesis.initial_timestamp;
});
db.create<BalanceObject>([&name, liquidBalance](BalanceObject& b) {
b.ownerName = name;
b.balance = liquidBalance;
});
db.create<StakedBalanceObject>([&name](StakedBalanceObject& sb) { sb.ownerName = name; });
};
CreateNativeAccount(config::SystemContractName, config::InitialTokenSupply);
CreateNativeAccount(config::EosContractName, 0);
CreateNativeAccount(config::StakedBalanceContractName, 0);
db.add_index<ProducerScheduleMultiIndex>();

// Install the native contract's message handlers
// First, set message handlers
Expand Down Expand Up @@ -63,6 +60,11 @@ std::vector<chain::Message> native_contract_chain_initializer::prepare_database(
&CreateAccount_Notify_Eos::validate_preconditions, &CreateAccount_Notify_Eos::apply);
SetNotifyHandlers(config::SystemContractName, config::StakedBalanceContractName, "CreateAccount",
&CreateAccount_Notify_Staked::validate_preconditions, &CreateAccount_Notify_Staked::apply);
}

std::vector<chain::Message> native_contract_chain_initializer::prepare_database(chain_controller& chain,
chainbase::database& db) {
std::vector<chain::Message> messages_to_process;

// Register native contract message types
#define MACRO(r, data, elem) chain.register_type<types::elem>(data);
Expand All @@ -71,6 +73,25 @@ std::vector<chain::Message> native_contract_chain_initializer::prepare_database(
BOOST_PP_SEQ_FOR_EACH(MACRO, config::StakedBalanceContractName, EOS_STAKED_BALANCE_CONTRACT_FUNCTIONS)
#undef MACRO

// Create the singleton object, ProducerScheduleObject
db.create<ProducerScheduleObject>([](const auto&){});

/// Create the native contract accounts manually; sadly, we can't run their contracts to make them create themselves
auto CreateNativeAccount = [this, &db](auto name, auto liquidBalance) {
db.create<account_object>([this, &name](account_object& a) {
a.name = name;
a.creation_date = genesis.initial_timestamp;
});
db.create<BalanceObject>([&name, liquidBalance](BalanceObject& b) {
b.ownerName = name;
b.balance = liquidBalance;
});
db.create<StakedBalanceObject>([&name](StakedBalanceObject& sb) { sb.ownerName = name; });
};
CreateNativeAccount(config::SystemContractName, config::InitialTokenSupply);
CreateNativeAccount(config::EosContractName, 0);
CreateNativeAccount(config::StakedBalanceContractName, 0);

// Queue up messages which will run contracts to create the initial accounts
auto KeyAuthority = [](PublicKey k) {
return types::Authority(1, {{k, 1}}, {});
Expand Down Expand Up @@ -102,19 +123,4 @@ std::vector<chain::Message> native_contract_chain_initializer::prepare_database(
return messages_to_process;
}

types::Time native_contract_chain_initializer::get_chain_start_time() {
return genesis.initial_timestamp;
}

chain::BlockchainConfiguration native_contract_chain_initializer::get_chain_start_configuration() {
return genesis.initial_configuration;
}

std::array<types::AccountName, config::BlocksPerRound> native_contract_chain_initializer::get_chain_start_producers() {
std::array<types::AccountName, config::BlocksPerRound> result;
std::transform(genesis.initial_producers.begin(), genesis.initial_producers.end(), result.begin(),
[](const auto& p) { return p.owner_name; });
return result;
}

} } // namespace eos::native_contract
Loading

0 comments on commit 566c1da

Please sign in to comment.