Rust区块链选举模块pallet-elections-phragmen的使用,Substrate框架下的Phragmén选举算法实现
Phragmén Election Module
基于顺序Phragmén算法的选举模块。
Term和Round
选举以轮次(rounds)进行:每N个区块,所有前成员退休并选举新成员(可能与前一轮有交集)。每轮持续由TermDuration
存储项定义的区块数量。术语"term"和"round"在此上下文中可以互换使用。
TermDuration
可能在轮次中改变。这会缩短或延长轮次的长度。下一轮选举的区块号从不存储,而是实时计算。基于当前区块号和TermDuration
,当满足条件BlockNumber % TermDuration == 0
时总会触发新选举轮次。
投票
投票者可以投票给任何候选人集合,提供账户ID列表。无效投票(投票给非候选人)在选举中被忽略。投票者可能投票给未来候选人。投票时需预留保证金。每个投票定义value
值,该金额从投票者账户锁定并指示投票权重。投票者可以随时通过再次调用vote()
更新投票。这会保持保证金不变但可选地改变锁定value
。轮次后,投票保留并可能对后续轮次仍然有效。投票者完成后应调用remove_voter
取回保证金并移除锁定。
投票者也可以报告其他投票者为无效以获取其保证金。当投票者投票的所有候选人既不是有效候选人也非成员时,该投票者为无效。报告时,如果目标投票者确实无效,报告者将获得目标投票者的保证金。目标将失去保证金并被移除。如果目标有效,报告者将被惩罚并移除。为防止被报告,投票者应在无效状态时手动提交remove_voter()
。
候选人和成员
候选人提交候选资格时也需预留保证金。候选人不能撤回候选资格。候选人可能处于以下情况之一:
- 胜者(Winner):作为成员保留。必须仍有保证金预留,并自动作为下一轮选举的候选人。
- 候补(Runner-up):紧随胜者的最佳候选人。保留的候补数量可配置。候补用于替换被
remove_member
踢出的候选人或主动成员放弃候选资格的情况。候补自动作为下一轮选举的候选人。 - 败者(Loser):任何非胜者的候选人。败者可能是离任成员或候补,意味着他们是未能保住位置的活跃成员。离任者总会失去保证金。
放弃候选资格
所有候选人(无论是否当选)可以放弃候选资格。调用Module::renounce_candidacy
总会退还候选资格保证金。
注意,由于成员是下一轮默认候选人且投票保留在存储中,若无进一步输入,选举系统完全稳定。这意味着如果系统有特定候选人集合C和投票者V导致成员集合M当选,只要V和C不删除其候选资格和投票,M将在每轮结束时继续当选。
模块信息
election_sp_phragmen::Config
Call
Module
许可证:Apache-2.0
完整示例代码
// 导入必要的模块
use frame_support::{decl_module, decl_storage, decl_event, dispatch};
use frame_system::{self as system, ensure_signed};
use sp_runtime::traits::{Zero, StaticLookup};
use sp_std::prelude::*;
// 定义模块配置trait
pub trait Config: system::Config {
type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;
// 投票保证金
type VotingBond: Get<BalanceOf<Self>>;
// 候选资格保证金
type CandidacyBond: Get<BalanceOf<Self>>;
// 每轮任期长度(区块数)
type TermDuration: Get<Self::BlockNumber>;
// 要保留的候补数量
type DesiredRunnersUp: Get<u32>;
}
// 定义存储项
decl_storage! {
trait Store for Module<T: Config> as Elections {
// 当前成员列表
pub Members get(fn members): Vec<T::AccountId>;
// 当前候补列表
pub RunnersUp get(fn runners_up): Vec<T::AccountId>;
// 所有候选人
pub Candidates get(fn candidates): Vec<T::AccountId>;
// 投票信息
pub Voting get(fn voting): map hasher(blake2_128_concat) T::AccountId => (BalanceOf<T>, Vec<T::AccountId>);
}
}
// 定义事件
decl_event!(
pub enum Event<T> where AccountId = <T as system::Config>::AccountId {
// 候选人提交
CandidateAdded(AccountId),
// 投票提交
VoterAdded(AccountId, BalanceOf<T>, Vec<AccountId>),
// 选举完成
ElectionCompleted(Vec<AccountId>, Vec极客时间App
开发者的专业学习平台
打开
Rust区块链选举模块pallet-elections-phragmen使用指南
模块介绍
pallet-elections-phragmen
是Substrate框架中实现Phragmén选举算法的模块,用于区块链网络中的理事会/议会成员选举。该算法源自瑞典数学家Lars Edvard Phragmén提出的比例代表制选举方法,特点是能够公平地按得票比例分配席位。
主要功能
- 实现基于质押权重的代币投票选举
- 支持多席位选举(如议会、理事会等)
- 提供选举后的成员轮换机制
- 包含防作弊和激励机制
使用方法
1. 在runtime中集成
// runtime/src/lib.rs
parameter_types! {
pub const ElectionsPhragmenModuleId: LockIdentifier = *b"phrelect";
pub const CandidacyBond: Balance = 100 * DOLLARS;
pub const VotingBound: Balance = 1 * DOLLARS;
pub const DesiredMembers: u32 = 13;
pub const DesiredRunnersUp: u32 = 7;
pub const TermDuration: BlockNumber = 7 * DAYS;
}
impl pallet_elections_phragmen::Config for Runtime {
type Event = Event;
type ModuleId = ElectionsPhragmenModuleId;
type Currency = Balances;
type ChangeMembers = Council; // 选举结果更新到理事会
type InitializeMembers = Council;
type CurrencyToVote = U128CurrencyToVote;
type CandidacyBond = CandidacyBond;
type VotingBond = VotingBond;
type LoserCandidate = ();
type KickedMember = ();
type DesiredMembers = DesiredMembers;
type DesiredRunnersUp = DesiredRunnersUp;
type TermDuration = TermDuration;
type WeightInfo = ();
}
2. 基本操作示例
提交候选人
// 提交自己作为候选人(需要质押保证金)
let bond = 100 * DOLLARS;
ElectionsPhragmen::submit_candidacy(Origin::signed(1), 0);
投票
// 选民投票给候选人(1和2)
let votes = vec![1, 2];
let stake = 500 * DOLLARS;
ElectionsPhragmen::vote(Origin::signed(3), votes, stake);
执行选举
// 当选举周期结束时,执行选举计算
ElectionsPhragmen::do_phragmen();
3. 常用API
vote(origin, votes, value)
- 为候选人投票remove_voter(origin)
- 移除自己的投票submit_candidacy(origin, candidate_count)
- 注册为候选人renounce_candidacy(origin, renouncing)
- 退出候选remove_member(origin, who, has_replacement)
- 移除成员
选举算法特点
- 公平性:确保得票与席位分配比例尽可能接近
- 抗串谋:防止少数大持币者垄断所有席位
- 效率:算法复杂度优化,适合区块链环境
完整示例代码
// 测试环境设置
use frame_support::{assert_ok, parameter_types};
use sp_core::H256;
use sp_runtime::{
testing::Header,
traits::{BlakeTwo256, IdentityLookup},
};
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
type Block = frame_system::mocking::MockBlock<Test>;
frame_support::construct_runtime!(
pub enum Test where
Block = Block,
NodeBlock = Block,
UncheckedExtrinsic = UncheckedExtrinsic,
{
System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
ElectionsPhragmen: pallet_elections_phragmen::{Pallet, Call, Storage, Event<T>, Config<T>},
}
);
parameter_types! {
pub const BlockHashCount: u64 = 250;
pub const SS58Prefix: u8 = 42;
}
impl frame_system::Config for Test {
type BaseCallFilter = frame_support::traits::Everything;
type BlockWeights = ();
type BlockLength = ();
type DbWeight = ();
type Origin = Origin;
type Call = Call;
type Index = u64;
type BlockNumber = u64;
type Hash = H256;
type Hashing = BlakeTwo256;
type AccountId = u64;
type Lookup = IdentityLookup<Self::AccountId>;
type Header = Header;
type Event = Event;
type BlockHashCount = BlockHashCount;
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = pallet_balances::AccountData<u64>;
type OnNewAccount = ();
type OnKilledAccount = ();
type SystemWeightInfo = ();
type SS58Prefix = SS58Prefix;
type OnSetCode = ();
}
parameter_types! {
pub const ExistentialDeposit: u64 = 1;
pub const MaxLocks: u32 = 10;
}
impl pallet_balances::Config for Test {
type MaxLocks = MaxLocks;
type Balance = u64;
type Event = Event;
type DustRemoval = ();
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = System;
type WeightInfo = ();
}
parameter_types! {
pub const ElectionsPhragmenModuleId: LockIdentifier = *b"phrelect";
pub const CandidacyBond: u64 = 100;
pub const VotingBond: u64 = 1;
pub const DesiredMembers: u32 = 2;
pub const DesiredRunnersUp: u32 = 1;
pub const TermDuration: u64 = 5;
}
impl pallet_elections_phragmen::Config for Test {
type Event = Event;
type ModuleId = ElectionsPhragmenModuleId;
type Currency = Balances;
type ChangeMembers = ();
type InitializeMembers = ();
type CurrencyToVote = frame_support::traits::U128CurrencyToVote;
type CandidacyBond = CandidacyBond;
type VotingBond = VotingBond;
type LoserCandidate = ();
type KickedMember = ();
type DesiredMembers = DesiredMembers;
type DesiredRunnersUp = DesiredRunnersUp;
type TermDuration = TermDuration;
type WeightInfo = ();
}
// 创建测试环境
pub fn new_test_ext() -> sp_io::TestExternalities {
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
pallet_balances::GenesisConfig::<Test> {
balances: vec![(1, 1000), (2, 1000), (3, 1000)],
}
.assimilate_storage(&mut t)
.unwrap();
t.into()
}
// 测试用例
#[test]
fn complete_election_flow() {
new_test_ext().execute_with(|| {
System::set_block_number(1);
// 1. 提交候选人
assert_ok!(ElectionsPhragmen::submit_candidacy(Origin::signed(1), 0));
assert_ok!(ElectionsPhragmen::submit_candidacy(Origin::signed(2), 1));
assert_ok!(ElectionsPhragmen::submit_candidacy(Origin::signed(3), 2));
// 验证候选人已注册
assert_eq!(ElectionsPhragmen::candidates(), vec![1, 2, 3]);
// 2. 投票阶段
assert_ok!(ElectionsPhragmen::vote(Origin::signed(1), vec![2, 3], 600));
assert_ok!(ElectionsPhragmen::vote(Origin::signed(2), vec![1, 3], 400));
assert_ok!(ElectionsPhragmen::vote(Origin::signed(3), vec![1, 2], 200));
// 3. 执行选举
System::set_block_number(TermDuration::get());
assert_ok!(ElectionsPhragmen::do_phragmen(Origin::root()));
// 4. 验证选举结果
let members = ElectionsPhragmen::members();
assert_eq!(members.len(), 2);
assert!(members.iter().any(|(who, _)| *who == 1));
assert!(members.iter().any(|(who, _)| *who == 2));
// 5. 验证候补成员
let runners_up = ElectionsPhragmen::runners_up();
assert_eq!(runners_up.len(), 1);
assert!(runners_up.iter().any(|(who, _)| *who == 3));
});
}
注意事项
- 选举周期需要在runtime中合理配置
- 候选人需要质押保证金防止垃圾注册
- 投票者也需要小额质押防止女巫攻击
- 选举计算可能有较高gas消耗,建议在非高峰期执行
该模块是Substrate治理体系的核心组件之一,常用于理事会、技术委员会等治理机构的成员选举。