25个版本 (12个破坏性)
0.17.1 | 2023年1月26日 |
---|---|
0.16.0 | 2022年12月16日 |
0.15.1 | 2022年9月22日 |
0.13.0 | 2022年7月26日 |
0.8.1 | 2022年3月29日 |
#397 in 魔法豆
每月57次下载
305KB
6K SLoC
Tgrade验证者集
这使用了Tgrade特定绑定,允许特权合约将可信cw4合约映射到运行链的Tendermint验证者集。指向cw4-group
合约将实现PoA,指向cw4-stake
合约将创建一个纯(未委托)PoS链。
(惩罚和奖励分配是其他合约的后续工作)
奖励计算
在Tgrade::EndBlock
sudo消息中,此合约执行通过epoch的活跃验证者的奖励计算和分配。
累积奖励值包含
- 每个epoch的奖励 - 每个epoch新铸造的代币
- 验证的块中的交易费用
每个epoch的奖励在实例化消息中可配置,在epoch_reward
字段中。费用累积在合约本身上。
epoch奖励不是固定的 - epoch_reward
是其基本值,但它根据累积的费用数量而修改。最终的奖励公式是
cumulative_reward = max(0, epoch_rewards - fee_percentage * fees) + fees
该想法是,在早期时期,预计交易不会太多,因此会铸造奖励以使验证有利可图。然而,在后期,当交易增多时,费用足以作为验证的奖励,因此不需要再铸造新代币,所以实际上没有必要引入代币通货膨胀。
可以通过将 fee_percentage
设置为 0
(这实际上使 fee_percentage * fees
始终等于 0
)来轻松禁用奖励减少功能。将其设置为大于 1
(或 100%
)会导致随着费用的增长,累计奖励会减少,直到 fees
达到 epoch_reward / fee_percentage
阈值(从这一点开始,不再铸造新代币,只有费用在验证者之间分配)。在 (0; 1]
范围内设置 fee_percentage
会导致累计奖励增长减少 - 基本上,当 fees
达到 epoch_reward / fee_percentage
时,所有费用都值得 (1 - fee_percentage) * fees
(它们被削减)。
下一步是将 cumulative_reward
分成几个部分。对于每个 分配合约,接受一个地址和一个比例。使用 distribute_funds
消息将 distribution_contract_ratio * cumulative_reward
发送到每个此类合约,其目的是根据非验证者的参与度将这部分奖励分配给他们。剩余的奖励代币作为 validators_reward
发送到上一个周期的验证者。需要在 InstantiateMsg
中配置的 distribution_contracts
向量中包含分配比率。这些比率的总和需要适合 [0, 1] 范围。该向量可以为空,在这种情况下,所有奖励都将归验证者所有。
当计算 validators_reward
时,它将在活跃验证者之间分配。活跃验证者是前 max_validators
个得分最高的验证者,但至少有 min_points
。scaling
是一个可选字段,它允许根据 Tendermint 的目的缩放积分(它不应影响奖励分配)。选择验证者后,cumulative_reward
将按验证者 points
的比例在他们之间分配。所有 max_validators
、min_points
和 scaling
都可以在实例化期间进行配置。通过外部合约实现 validators_reward
的分配。
fee_percentage
的默认值为 0
(因此,当在消息中未指定时,奖励减少将被禁用)。在 Tgrade 创世时,fee_percentage
预计将被设置为 0.5
。
奖励分配合约
如前文所述,奖励分配是通过由 tgrade-valset
管理的外部合约实现的。它假定是 tg4-engagement
合约,但在现实中它只需支持适当的 API,然后 tgrade-valset
(它只是 tg4-engagement
的一个子集)就可以使用。
在 valset 实例化过程中,奖励分配合约,称为 validator_group
,使用以下消息进行实例化:
{
"admin": "tgrade_valset_addr",
"denom": "epoch_reward_denom",
"members": []
}
存储的验证者组合约的代码 ID 通过其实例化消息发送到 valset(validator_group_code_id
字段)。验证者组合约分配的地址将以 wasm
事件的形式发出。
{
"_contract_addr": "valset_addr",
"action": "tgrade-valset_instantiation",
"validator_group": "validator_group_addr"
}
在每一个纪末,将使用执行消息将奖励发送到验证者组合约。
{
"distribute_funds": {}
}
之后,将发送另一个消息来更新验证者和他们的积分。
{
"update_members": {
"remove": ["validator_to_be_removed"],
"add": [{
"addr": "validator_with_points_updated",
"points": 10
}]
}
}
监禁
监禁是一种暂时禁止操作者验证区块的机制。
只有一个地址允许监禁成员,并且它在 InstantiateMsg
中配置为 admin
。想法是,管理员是某种投票合约,它将通过投票共识来决定禁止。
监禁成员将禁止他在未来的纪中作为验证者,除非他被解禁。有三种解禁成员的方法:
- 管理员可以始终通过投票解禁被监禁的成员。
- 任何成员都可以在监禁期届满时自行解禁。
- 成员可以在监禁期届满后自动解禁(这可以通过
InstantiateMsg::auto_unjail
标志启用)。
可以通过正常验证者查询查询监禁状态 - 如果验证者被监禁,则响应将包含一个 jailed_until
字段,该字段包含一个单独的 forever
字段(如果此成员永远不允许自行解禁),或者一个包含时间戳的 until
字段,指示成员何时可以解禁。
减薪
该合约实现了减薪语义,但实际上并没有实现完整的减薪接口。它对减薪消息做出适当的反应。
{
"slash": {
"addr": "contract_to_slash",
"portion": portion_to_slash
}
}
减薪通过只是将 Slash
消息转发到 membership
合约(在实例化时设置 - 这被假定为混合合约,但从技术上讲,可以是任何实现 tg4
和 Slashing
接口的合约)。显然,要能够在 membership
合约上减薪,tgrade-valset
必须将其自身注册为减薪者,因此在实例化时,它会向 membership
合约发送 AddSlasher
消息,整个实例化必须成功。因此,必须在 membership
合约上设置适当的 slashing_preauths
。
tgrade-valset
对 AddSlasher
和 RemoveSlasher
消息没有反应,因为它不支持多个减薪者。只有 tgrade-valset
的管理员才能永远在该合约上减薪(他也可以永远这样做)。
由于只有 membership
合约通过此 Slash
实现被减薪,因此该 membership
合约本身负责确保验证者和参与合约上的积分保持一致。然而,由于奖励分配直到下一个纪才会重新计算,减薪不会影响当前纪。
初始化
pub struct InstantiateMsg {
/// Address allowed to jail, meant to be a OC voting contract. If `None`, then jailing is
/// impossible in this contract.
pub admin: Option<String>,
/// Address of a cw4 contract with the raw membership used to feed the validator set
pub membership: String,
/// Minimum points needed by an address in `membership` to be considered for the validator set.
/// 0-points members are always filtered out.
/// TODO: if we allow sub-1 scaling factors, determine if this is pre-/post- scaling
/// (use points for cw4, power for Tendermint)
pub min_points: u64,
/// The maximum number of validators that can be included in the Tendermint validator set.
/// If there are more validators than slots, we select the top N by membership points
/// descending. (In case of ties at the last slot, select by "first" Tendermint pubkey,
/// lexicographically sorted).
pub max_validators: u32,
/// Number of seconds in one epoch. We update the Tendermint validator set only once per epoch.
/// Epoch # is env.block.time/epoch_length (round down). The first block with a new epoch number
/// will trigger a new validator calculation.
pub epoch_length: u64,
/// Total reward paid out at each epoch. This will be split among all validators during the last
/// epoch.
/// (epoch_reward.amount * 86_400 * 30 / epoch_length) is the amount of reward tokens to mint
/// each month.
/// Ensure this is sensible in relation to the total token supply.
pub epoch_reward: Coin,
/// Initial operators and validator keys registered.
/// If you do not set this, the validators need to register themselves before
/// making this privileged/calling the EndBlockers, so that we have a non-empty validator set
pub initial_keys: Vec<OperatorInitInfo>,
/// A scaling factor to multiply cw4-group points to produce the Tendermint validator power
/// (TODO: should we allow this to reduce points? Like 1/1000?)
pub scaling: Option<u32>,
/// Percentage of total accumulated fees that is subtracted from tokens minted as rewards.
/// 50% by default. To disable this feature just set it to 0 (which effectively means that fees
/// don't affect the per-epoch reward).
#[serde(default = "default_fee_percentage")]
pub fee_percentage: Decimal,
/// Flag determining if validators should be automatically unjailed after the jailing period;
/// false by default.
#[serde(default)]
pub auto_unjail: bool,
/// Validators who are caught double signing are jailed forever and their bonded tokens are
/// slashed based on this value.
#[serde(default = "default_double_sign_slash")]
pub double_sign_slash_ratio: Decimal,
/// Addresses where part of the reward for non-validators is sent for further distribution. These are
/// required to handle the `Distribute {}` message (eg. tg4-engagement contract) which would
/// distribute the funds sent with this message.
///
/// The sum of ratios here has to be in the [0, 1] range. The remainder is sent to validators via the
/// rewards contract.
///
/// Note that the particular algorithm this contract uses calculates token rewards for distribution
/// contracts by applying decimal division to the pool of reward tokens, and then passes the remainder
/// to validators via the contract instantiated from `rewards_code_is`. This will cause edge cases where
/// indivisible tokens end up with the validators. For example if the reward pool for an epoch is 1 token
/// and there are two distribution contracts with 50% ratio each, that token will end up with the
/// validators.
pub distribution_contracts: UnvalidatedDistributionContracts,
/// Code id of the contract which would be used to distribute the rewards of this token, assuming
/// `tg4-engagement`. The contract will be initialized with the message:
/// ```json
/// {
/// "admin": "valset_addr",
/// "denom": "reward_denom",
/// }
/// ```
///
/// This contract has to support all the `RewardsDistribution` messages
pub validator_group_id: u64,
}
消息
pub enum ExecuteMsg {
/// Change the admin
UpdateAdmin {
admin: Option<String>,
},
/// Links info.sender (operator) to this Tendermint consensus key.
/// The operator cannot re-register another key.
/// No two operators may have the same consensus_key.
RegisterValidatorKey {
pubkey: Pubkey,
/// Additional metadata assigned to this validator
metadata: ValidatorMetadata,
},
UpdateMetadata(ValidatorMetadata),
/// Jails validator. Can be executed only by the admin.
Jail {
/// Operator which should be jailed
operator: String,
/// Duration for how long validator is jailed, `None` for jailing forever
duration: Option<Duration>,
},
/// Unjails validator. Admin can unjail anyone anytime, others can unjail only themselves and
/// only if the jail period passed.
Unjail {
/// Address to unjail. Optional, as if not provided it is assumed to be the sender of the
/// message (for convenience when unjailing self after the jail period).
operator: Option<String>,
},
/// To be called by admin only. Slashes a given address (by forwarding slash to both rewards
/// contract and engagement contract)
Slash {
addr: String,
portion: Decimal,
},
}
pub struct ValidatorMetadata {
/// The validator's name (required)
pub moniker: String,
/// The optional identity signature (ex. UPort or Keybase)
pub identity: Option<String>,
/// The validator's (optional) website
pub website: Option<String>,
/// The validator's (optional) security contact email
pub security_contact: Option<String>,
/// The validator's (optional) details
pub details: Option<String>,
}
查询
pub enum QueryMsg {
/// Returns ConfigResponse - static contract data
Config {},
/// Returns EpochResponse - get info on current and next epochs
Epoch {},
/// Returns the validator key and associated metadata (if present) for the given operator.
/// Returns ValidatorResponse
Validator { operator: String },
/// Paginate over all operators, using operator address as pagination.
/// Returns ListValidatorsResponse
ListValidators {
start_after: Option<String>,
limit: Option<u32>,
},
/// List the current validator set, sorted by power descending
/// (no pagination - reasonable limit from max_validators)
ListActiveValidators {},
/// This will calculate who the new validators would be if
/// we recalculated end block right now.
/// Also returns ListActiveValidatorsResponse
SimulateActiveValidators {},
/// Returns a list of validator slashing events.
/// Returns ListValidatorSlashingResponse
ListValidatorSlashing { operator: String },
}
依赖
~4–16MB
~232K SLoC