Cosmos SDK governance模块定制指导:自定义投票权重计算逻辑
定制Cosmos SDK Governance模块:混合投票权重实现指南
一、前置准备
- 确保本地安装Go 1.20+(对应Cosmos SDK v0.47.x版本,可根据你使用的SDK版本调整)
- 基于Starport或Cosmos SDK模板初始化链项目,示例:
starport init mychain --cosmos-sdk-version v0.47.5 - 熟悉
x/gov和x/staking模块的核心逻辑,尤其是默认投票权重的计算流程
二、原有投票权重逻辑梳理
Cosmos SDK默认治理投票权重直接绑定用户的质押金额,核心入口在x/gov/keeper/vote.go的AddVote方法中,通过调用stakingKeeper.GetDelegatorTotalVotePower获取用户总质押权重。我们需要在这个基础上,注入质押时长的权重占比逻辑。
三、核心实现步骤
1. 添加可配置权重比例参数(推荐)
为了让权重比例支持链上治理调整,在x/gov模块中新增参数配置:
- 修改
x/gov/types/params.proto,添加字段:message Params { // ... 原有字段保留 string deposit_weight_ratio = 10; // 存款权重占比,默认"0.8" string staking_duration_weight_ratio = 11; // 质押时长权重占比,默认"0.2" } - 运行代码生成命令更新Go代码:
make proto-gen - 在
x/gov/types/params.go中设置默认值:func DefaultParams() Params { return Params{ // ... 原有默认值保留 DepositWeightRatio: sdk.MustNewDecFromStr("0.8"), StakingDurationWeightRatio: sdk.MustNewDecFromStr("0.2"), } }
2. 实现质押时长权重计算
从x/staking模块获取用户委托信息,计算加权平均质押时长并转换为权重:
- 在
x/gov/keeper/keeper.go中,确保Keeper持有staking.Keeper引用(项目默认已包含) - 添加加权平均时长计算方法:
// CalculateWeightedStakingDuration 计算用户有效委托的加权平均质押时长(秒) func (k Keeper) CalculateWeightedStakingDuration(ctx sdk.Context, delegator sdk.AccAddress) (sdk.Dec, error) { totalDuration := sdk.ZeroDec() totalStaked := sdk.ZeroInt() // 遍历用户所有有效委托 delegations := k.stakingKeeper.GetDelegatorDelegations(ctx, delegator, math.MaxUint16) for _, del := range delegations { // 跳过解绑中的委托 if k.stakingKeeper.HasUnbondingDelegation(ctx, delegator, del.GetValidatorAddr()) { continue } // 计算单份委托的时长(当前区块时间 - 委托创建时间) creationTime := del.GetCreationTime() duration := ctx.BlockTime().Sub(creationTime).Seconds() // 加权累加:委托金额 * 时长 delAmount := sdk.NewDecFromInt(del.GetShares().TruncateInt()) totalDuration = totalDuration.Add(delAmount.Mul(sdk.NewDec(duration))) totalStaked = totalStaked.Add(del.GetShares().TruncateInt()) } if totalStaked.IsZero() { return sdk.ZeroDec(), nil } // 返回加权平均时长 return totalDuration.Quo(sdk.NewDecFromInt(totalStaked)), nil } - 将时长转换为可与质押金额匹配的权重(示例:按年归一化):
// CalculateStakingDurationWeight 将加权平均时长转换为权重值 func (k Keeper) CalculateStakingDurationWeight(ctx sdk.Context, delegator sdk.AccAddress) (sdk.Dec, error) { avgDuration, err := k.CalculateWeightedStakingDuration(ctx, delegator) if err != nil { return sdk.ZeroDec(), err } // 归一化:时长转年,最大权重倍数限制为1(质押满1年以上按1倍计算) years := avgDuration.Quo(sdk.NewDec(365 * 24 * 3600)) if years.GT(sdk.OneDec()) { years = sdk.OneDec() } // 时长权重 = 总质押金额 * 归一化倍数 totalStaked := sdk.NewDecFromInt(k.stakingKeeper.GetDelegatorTotalVotePower(ctx, delegator)) return totalStaked.Mul(years), nil }
3. 实现混合投票权重计算
修改x/gov/keeper/vote.go中的权重获取逻辑:
func (k Keeper) AddVote(ctx sdk.Context, proposalID uint64, vote types.Vote) error { // ... 原有校验逻辑保留 // 计算混合投票权重 params := k.GetParams(ctx) depositWeight := sdk.NewDecFromInt(k.stakingKeeper.GetDelegatorTotalVotePower(ctx, vote.Voter)) durationWeight, err := k.CalculateStakingDurationWeight(ctx, vote.Voter) if err != nil { return err } mixedVotePower := depositWeight.Mul(params.DepositWeightRatio).Add(durationWeight.Mul(params.StakingDurationWeightRatio)) votePower := mixedVotePower.TruncateInt() // 转换为整数权重(根据链精度调整) // ... 后续投票存储逻辑,使用votePower替代原有默认权重 }
4. 同步修改计票逻辑
在x/gov/keeper/tally.go的Tally方法中,确保遍历投票时使用新的混合权重统计结果,替换原有的质押权重统计逻辑。
四、测试验证
1. 单元测试
编写测试用例覆盖核心场景:
- 单用户质押100Token,质押30天,验证混合权重是否符合
100*0.8 + (100*(30/365))*0.2 - 多委托、不同时长场景,验证加权平均时长计算正确性
- 修改权重比例参数,验证权重计算同步变化
2. 本地链测试
- 编译链:
make install - 启动节点:
mychaind start - 创建测试账户并质押:
mychaind tx staking delegate <validator-addr> 1000000mycoin --from alice - 创建治理提案并投票:
mychaind tx gov submit-proposal text-proposal --title "Test Mixed Vote" --description "Verify mixed vote power" --deposit 1000000mycoin --from alice mychaind tx gov vote 1 yes --from alice - 查询提案结果:
mychaind query gov proposal 1,验证投票权重是否符合预期
五、注意事项
- 质押时长逻辑可按需调整(如非线性权重、阶梯式权重)
- 需处理 redelegation、解绑中委托的情况,确保仅统计有效质押时长
- 注意委托遍历的性能,避免大量委托场景下的节点卡顿
- 使用Cosmos SDK的
Dec类型处理浮点运算,避免精度误差
内容的提问来源于stack exchange,提问作者Eric0123
相关产品推荐
相关产品推荐

