Compare commits

...

2 Commits

Author SHA1 Message Date
coopops 0349c25ccf docs(contracts): setfinalizer — bootstrap этапа 1, не конечная модель
Typecheck / desktop (pull_request) Successful in 13m27s
Typecheck / controller (pull_request) Successful in 13m36s
single-producer — текущее состояние сети, не архитектура. Этап 2 —
выборная машинерия финализаторов под кооперативную модель (1 пайщик =
1 голос); upstream regfinkey/switchtosvnn (top-21 по stake) не подходит.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 13:21:44 +00:00
coopops e2de5957ac feat(contracts): setfinalizer в eosio.system — политика финализаторов Savanna
Typecheck / desktop (pull_request) Has been cancelled
Typecheck / controller (pull_request) Has been cancelled
Порт self-contained action из upstream eosio.bios (reference-contracts):
проверка BLS-ключей и proof of possession, вызов host-функции
set_finalizers. Машинерия regfinkey/switchtosvnn голосующего DPoS
не переносится — цепь single-producer, политику задаёт оператор.

Требует CDT >= 4.1 (crypto_bls_ext); собрано CDT savanna 4.3.0 из
образа dicoop/blockchain:latest, setfinalizer присутствует в ABI.
До активации протокол-фичи SAVANNA поведение цепи не меняется.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 13:09:37 +00:00
3 changed files with 118 additions and 1 deletions
@@ -10,7 +10,8 @@ add_contract(
${CMAKE_CURRENT_SOURCE_DIR}/src/powerup.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/voting.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/limit_auth_changes.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/block_info.cpp)
${CMAKE_CURRENT_SOURCE_DIR}/src/block_info.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/finalizer_key.cpp)
if(IS_TESTNET)
target_compile_definitions(eosio.system PUBLIC IS_TESTNET=1)
@@ -511,6 +511,32 @@ namespace eosiosystem {
indexed_by<"byexpires"_n, const_mem_fun<powerup_order, uint64_t, &powerup_order::by_expires>>
> powerup_order_table;
/**
* Параметры финализаторов Savanna (порт setfinalizer из upstream eosio.bios).
* Этап 1 (bootstrap): политику финализаторов задаёт оператор напрямую.
* Этап 2 — выборная машинерия финализаторов под кооперативную модель
* голосования (1 пайщик = 1 голос); upstream-вариант regfinkey/switchtosvnn
* (top-21 по stake-весу) нашей модели не соответствует и потому не переносится как есть.
*/
constexpr size_t max_finalizers = 64*1024;
constexpr size_t max_finalizer_description_size = 256;
struct finalizer_authority {
std::string description; // человеко-читаемое описание финализатора
uint64_t weight = 0; // вес голоса финализатора для достижения threshold
std::string public_key; // BLS-ключ финализатора в формате base64 (PUB_BLS...)
std::string pop; // proof of possession ключа (SIG_BLS...)
EOSLIB_SERIALIZE(finalizer_authority, (description)(weight)(public_key)(pop))
};
struct finalizer_policy {
uint64_t threshold = 0; // порог суммарного веса для финализации
std::vector<finalizer_authority> finalizers;
EOSLIB_SERIALIZE(finalizer_policy, (threshold)(finalizers));
};
/**
* The `eosio.system` smart contract is provided by `block.one` as a sample system contract, and it defines the structures and actions needed for blockchain's core functionality.
*
@@ -616,6 +642,21 @@ namespace eosiosystem {
/**
* @brief Предложить новую политику финализаторов Savanna.
* Проверяет BLS-ключи и proof of possession каждого финализатора и
* передаёт политику в хост-функцию set_finalizers. Вступает в силу,
* когда не вытеснена более поздней политикой. Первый вызов после
* активации протокол-фичи SAVANNA запускает переход цепи на
* instant finality.
*
* @param finalizer_policy - предлагаемая политика финализаторов
*
* @note Авторизация требуется от аккаунта: @p get_self()
*/
[[eosio::action]]
void setfinalizer( const finalizer_policy& finalizer_policy );
[[eosio::action]]
void onblock( ignore<block_header> header );
@@ -0,0 +1,75 @@
#include <eosio.system/eosio.system.hpp>
#include <eosio/crypto_bls_ext.hpp>
#include <eosio/instant_finality.hpp>
#include <cstring>
#include <unordered_set>
namespace eosiosystem {
// Порт setfinalizer из upstream eosio.bios (reference-contracts):
// все дорогие проверки выполняются здесь, чтобы хост-функция
// set_finalizers гарантированно не упала.
// Это bootstrap-механизм этапа 1 (политика от оператора); выборная
// машинерия финализаторов под кооперативное голосование — этап 2.
void system_contract::setfinalizer( const finalizer_policy& finalizer_policy ) {
require_auth( get_self() );
check(finalizer_policy.finalizers.size() <= max_finalizers, "number of finalizers exceeds the maximum allowed");
check(finalizer_policy.finalizers.size() > 0, "require at least one finalizer");
eosio::finalizer_policy fin_policy;
fin_policy.threshold = finalizer_policy.threshold;
fin_policy.finalizers.reserve(finalizer_policy.finalizers.size());
const std::string pk_prefix = "PUB_BLS";
const std::string sig_prefix = "SIG_BLS";
// сырой affine-формат (bls_g1 = std::array<char, 96>) для проверки уникальности
struct g1_hash {
std::size_t operator()(const eosio::bls_g1& g1) const {
std::hash<const char*> hash_func;
return hash_func(g1.data());
}
};
struct g1_equal {
bool operator()(const eosio::bls_g1& lhs, const eosio::bls_g1& rhs) const {
return std::memcmp(lhs.data(), rhs.data(), lhs.size()) == 0;
}
};
std::unordered_set<eosio::bls_g1, g1_hash, g1_equal> unique_finalizer_keys;
uint64_t weight_sum = 0;
for (const auto& f: finalizer_policy.finalizers) {
check(f.description.size() <= max_finalizer_description_size, "Finalizer description greater than max allowed size");
// базовые проверки формата ключа
check(f.public_key.substr(0, pk_prefix.length()) == pk_prefix, "public key shoud start with PUB_BLS");
check(f.pop.substr(0, sig_prefix.length()) == sig_prefix, "proof of possession signature should start with SIG_BLS");
// защита от переполнения суммы весов
check(std::numeric_limits<uint64_t>::max() - weight_sum >= f.weight, "sum of weights causes uint64_t overflow");
weight_sum += f.weight;
// decode_bls_public_key_to_g1 сам падает (check) на невалидном ключе
const auto pk = eosio::decode_bls_public_key_to_g1(f.public_key);
check(unique_finalizer_keys.insert(pk).second, "duplicate public key");
const auto signature = eosio::decode_bls_signature_to_g2(f.pop);
// проверка владения приватным ключом
check(eosio::bls_pop_verify(pk, signature), "proof of possession failed");
std::vector<char> pk_vector(pk.begin(), pk.end());
fin_policy.finalizers.emplace_back(eosio::finalizer_authority{f.description, f.weight, std::move(pk_vector)});
}
check( weight_sum >= finalizer_policy.threshold && finalizer_policy.threshold > weight_sum / 2,
"Finalizer policy threshold must be greater than half of the sum of the weights, and less than or equal to the sum of the weights");
set_finalizers(std::move(fin_policy));
}
} /// namespace eosiosystem