Algorand 系列三: Algorand 的账户和奖励

YOUChain Research

YOUChain 研究团队,成员毕业于国内外顶级名校,有长期的工业界经验。我们持续跟踪的区块链学界和业界的前沿发展,致力于深入区块链本质,推动学术和技术发展。团队诚邀加密、算法、区块链、工程人才加入!

本文来自 junqiang@YOUChainResearch

本文主要介绍Algorand系统中账户系统和奖励相关的概念,并结合Algorand的源码,对相关逻辑进行了分析。

前置概念

账户状态 账户状态有三种,OnlineOfflineNotParticipating

系统奖励 除了创世区块中的声明的持币地址,系统出块时会给与OnlineOffline的持 币地址奖励

rewardUnit 奖励单位,每rewardUnit个 algos 被看做一个奖励的计量单位

rewardLevel 奖励累计自创世以来,每个奖励单位获得的 algos 总数

rewardsPerUnit 单位奖励值当前高度每个奖励单位能得到的 algos 数量

RewardsRateRefreshInterval 奖励周期,每隔多少个块重新计算出块奖励

出块奖励 = 奖励池余额 / 奖励周期

比如基金会给奖励池账户转账 1000,并在代码中指定RewardsRateRefreshInterval=10, 那么从前十个块每个块就可以奖励 100 algos,完成这一轮后,系统重新计算新的周期的出块奖励。

账户

存储每个钱包地址的状态、余额、激励等信息

账户在代码中的数据结构如下:

1
2
3
4
5
6
7
8
9
10
type AccountData struct {
// 状态,在线,离线,非参与者
Status Status `codec:"onl"`
// 账号总额
MicroAlgos MicroAlgos `codec:"algo"`
// 最后一次资金归集时的系统rewardLevel
RewardsBase uint64 `codec:"ebase"`
// 记录累计得到的奖励
RewardedMicroAlgos MicroAlgos `codec:"ern"`
}

系统账户

系统中存在一类特殊用途的账户,比如交易费账户、激励池账户等,它们是与用户账户相同 的数据,只是用途比较特殊。比如下面的系统账户是在创世区块中声明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
奖励池 RewardsPool
地址:737777777777777777777777777777777777777777777777777UFEJ2CI
初始值:10,000,000

交易费池 FeeSink
地址:Y76M3MSY6DKBRHBL7C3NNDXGS5IIMQVQVUAB6MP4XEMMGVF2QWNPL226CA
初始值:1

Algorand社区公告 AlgorandCommunityAnnouncement
地址:XQJEJECPWUOXSKMIC5TCSARPVGHQJIIOKHO7WTKEPPLJMKG3D7VWWID66E
初始值:10

拍卖管理员 AuctionsMaster
地址:VCINCVUX2DBKQ6WP63NOGPEAQAYGHGSGQX7TSH4M5LI5NBPVAGIHJPMIPM
初始值:1000

A BIT DOES E NOT BUT E STARTS...AS NOT MADE NOT
地址:ALGORANDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIN5DNAU
初始值:1

系统总账

系统总账是将账号按状态分组求和的结果。

1
2
3
4
5
6
type AccountTotals struct {
Online AlgoCount
Offline AlgoCount
NotParticipating AlgoCount
RewardsLevel uint64
}

通过accountTotals可以得到几个比较重要的数值:

计算
状态为 Online 的量 Online
总参与量 Participating = Online + Offline
总发行量 All = NotParticipating + Participating
总奖励量 Reward = Online.Rewards + Offline.Rewareds

激励方式

激励的来源

Algorand中,奖励通过一个特定的钱包(即RewardsPool地址)来发放。

奖励池的资金主要来自两个方面:

  1. 基金会的转账。这是主要的部分,基金会定期将对应的激励发放到RewardsPool的钱包

  2. 全网的交易手续费。即FeeSink钱包,这部分会定期转账到奖励池。

激励的分配方式

每个区块分配一定数量的 algos 给每个参与帐户(Online+Offline)。

持币账户的奖励计算方式 balancerewardUnitrewardLevelpreRewardLeveltotalRewardUnit`\frac{balance}{rewardUnit}*\frac{rewardLevel-preRewardLevel}{totalRewardUnit}`

由于性能原因,系统不会遍历每个帐户来将这些奖励归集到MicroAlgos。相反,系统会推迟奖励的归集,直到某些交易关联到了此帐户。此时,才会将所有奖励应用到帐户的MicroAlgos

另外,系统为了有效地确定系统总的发行量,在奖励计算中没有使用复利的方式,而是等到直到它们被归集到MicroAlgos,之后才会改变一个账户所持有的奖励单位数量。

这种机制会促使持有代币的用户,经常性的产生交易,以实现分红复利

账户的真是余额 balance=MicroAlgos+`balance=MicroAlgos+未归集的奖励`

AccountData.Money()所计算的那样。调用AccountData.WithUpdatedRewards()时, 会将奖励归集到MicroAlgos

奖励归集

由于系统采用了延迟奖励的方式,所以只有在发生相关交易时,才会将账户奖励进行归集。 流程如下:

为了缩减篇幅,这里省略掉了异常处理的逻辑,完整代码在 data/basics/userBalance.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func (u AccountData) WithUpdatedRewards(proto config.ConsensusParams, rewardsLevel uint64) AccountData {
if u.Status != NotParticipating {
var ot OverflowTracker
// 计算持有的奖励单位数
rewardsUnits := u.MicroAlgos.RewardUnits(proto)
// 当前 rewardLevel - 上一个块的 rewardLevel 得到 rewardLevel 的增量
rewardsDelta := ot.Sub(rewardsLevel, u.RewardsBase)
// 相乘 得到的奖励的algos数量
rewards := MicroAlgos{Raw: ot.Mul(rewardsUnits, rewardsDelta)}
// 将奖励归集到余额
u.MicroAlgos = ot.AddA(u.MicroAlgos, rewards)
// 只是最后一次资金变化时的系统rewardLevel,以便下次资金变动时计算奖励
u.RewardsBase = rewardsLevel
// 更新账号累计得到的奖励, 如果超过64位无符号数的最大值,可能会回滚。
u.RewardedMicroAlgos = MicroAlgos{Raw: (u.RewardedMicroAlgos.Raw + rewards.Raw)}
}
return u
}

从以上代码中可以看到,在对账户奖励进行归集时,需要对比前后的rewardLevel得出增量, 并且方法的第三个参数rewardLevel uint64来自于系统总账中的rewardLevel,这是一个 随着区块高度增加而不断增长的数字,那么激励的分配就跟这个rewardLevel的更新规则息 息相关。

RewardLevel 更新

RewardsState 是控制激励发放的全局参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
RewardsState struct {
// 这个地址用来接收交易手续费,收到的交易手续费只能转给奖励池地址
FeeSink basics.Address `codec:"fees"`

// FeeSink定期将手续费收入转给奖金池,然后作为奖励重新分配给各个参与账户
RewardsPool basics.Address `codec:"rwd"`

// 表示从创世区块到现在,累计分配给每个奖励单位的奖励数量。
RewardsLevel uint64 `codec:"earn"`

// 下一轮作为奖励发放给参与者的algos数量,相当于每个块的总奖励algos数
RewardsRate uint64 `codec:"rate"`

// 按照 RewardsRate%totalRewardUnits 对总奖励单位取模,剩余的algos数量,
// 留给下个区块
RewardsResidue uint64 `codec:"frac"`

// 每多少个区块重新计算RewardsRate
RewardsRecalculationRound basics.Round `codec:"rwcalr"`
}

RewardRate的计算方法:

RewardRate=incentivePoolBalancetotalRewardUnits`RewardRate=\frac{incentivePoolBalance}{totalRewardUnits}`

当前高度的奖励总量 rewardsWithResidue=rewardRate+rewardResidue`rewardsWithResidue = rewardRate + rewardResidue`

将出块奖励平摊到每个奖励单位上,更新下一个块的RewardLevel

nextRewardLevel=rewardLevel+rewardsWithResiduetotalRewardUnit`nextRewardLevel = rewardLevel + \frac{rewardsWithResidue}{totalRewardUnit}`

本高度执行完成后剩余的奖励 nextResidue=rewardsWithResidue`nextResidue = rewardsWithResidue % totalRewardUnit`

从下面的代码可以看到rewardLevel的更新逻辑

为了缩减篇幅,这里省略掉了异常处理的逻辑,完整代码在 data/bookkeeping/block.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func (s RewardsState) NextRewardsState(nextRound basics.Round, nextProto config.ConsensusParams, incentivePoolBalance basics.MicroAlgos, totalRewardUnits uint64) (res RewardsState) {
res = s

// 重新计算出块奖励
if nextRound == s.RewardsRecalculationRound {
newRate, overflowed := basics.OSub(incentivePoolBalance.Raw, nextProto.MinBalance)
// 计算下个周期的区块奖励
res.RewardsRate = newRate / nextProto.RewardsRateRefreshInterval
// 下次更新奖励奖励的块高度
res.RewardsRecalculationRound = nextRound + basics.Round(nextProto.RewardsRateRefreshInterval)
}

// nextLevel = preLevel + ((增量 + 上次结余) / 总单位数)
// (rate + residue) / totalUnit
var ot basics.OverflowTracker
rewardsWithResidue := ot.Add(s.RewardsRate, s.RewardsResidue) //把取模剩下的加上
nextRewardLevel := ot.Add(s.RewardsLevel, rewardsWithResidue/totalRewardUnits)
nextResidue := rewardsWithResidue % totalRewardUnits // 剩余的留作下一个区块的奖励

res.RewardsLevel = nextRewardLevel
res.RewardsResidue = nextResidue

return
}

以上就是AlgorandrewardLevel更新逻辑,系统全局的rewardLevel将随着出块不断的上涨,每个持币账户也就得到了相应比例的奖励。

如上所述,基金会可以通过向奖池地址转账的方式来增发algos,相比其他方式,可以更有效的控制发行量。

交流群

受限于笔者的学识和能力,文章内容难免有纰漏之处。技术探讨,请添加加微信 kvdoth 备注「YOUChain 技术交流」,进群交流。

公众号

欢迎添加「有令」公众号,了解项目最新进展和技术分享。

wxmp
wxmp
相关文章推荐