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
开发者的专业学习平台
打开

1 回复

Rust区块链选举模块pallet-elections-phragmen使用指南

模块介绍

pallet-elections-phragmen是Substrate框架中实现Phragmén选举算法的模块,用于区块链网络中的理事会/议会成员选举。该算法源自瑞典数学家Lars Edvard Phragmén提出的比例代表制选举方法,特点是能够公平地按得票比例分配席位。

主要功能

  1. 实现基于质押权重的代币投票选举
  2. 支持多席位选举(如议会、理事会等)
  3. 提供选举后的成员轮换机制
  4. 包含防作弊和激励机制

使用方法

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) - 移除成员

选举算法特点

  1. 公平性:确保得票与席位分配比例尽可能接近
  2. 抗串谋:防止少数大持币者垄断所有席位
  3. 效率:算法复杂度优化,适合区块链环境

完整示例代码

// 测试环境设置
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));
    });
}

注意事项

  1. 选举周期需要在runtime中合理配置
  2. 候选人需要质押保证金防止垃圾注册
  3. 投票者也需要小额质押防止女巫攻击
  4. 选举计算可能有较高gas消耗,建议在非高峰期执行

该模块是Substrate治理体系的核心组件之一,常用于理事会、技术委员会等治理机构的成员选举。

回到顶部