变更记录
- 2020-01-08:初始版本
- 2020-01-09:为处理 vesting 账户所做的调整
- 2020-01-14:根据评审反馈更新
- 2020-01-30:根据实现情况更新
术语表
denom/ 面额键:唯一的代币标识符。
背景
在无许可 IBC 的场景下,任何人都可以向任何其他账户发送任意面额的代币。当前,所有非零余额都与账户一起存储在sdk.Coins 结构中,这会带来潜在的拒绝服务风险,因为每次修改账户时,如果面额数量过多,加载和存储的成本都会变得很高。更多背景可参见议题 5467 和 4982。
仅仅在面额数量达到上限后拒绝新的入账并不可行,因为这会引入一种恶意骚扰路径:有人可以通过 IBC 向某个用户发送大量无意义的代币,从而阻止该用户接收真实且有价值的面额(例如质押奖励)。
决策
余额应按账户和面额分别存储在一个对面额和账户均唯一的键下,从而可以对特定账户在特定面额下的余额实现 O(1) 的读写访问。账户接口(x/auth)
由于代币余额现在将由 bank 模块存储和管理,GetCoins() 和 SetCoins() 将从账户接口中移除。
vesting 账户接口将以 LockedCoins 替代 SpendableCoins,因为前者不再需要账户余额。此外,TrackDelegation() 现在将接收 vesting 余额中所有代币的账户余额,而不是加载整个账户余额。
vesting 账户仍将继续存储 original vesting、delegated free 和 delegated vesting 代币(这是安全的,因为这些字段不会包含任意面额)。
Bank keeper(x/bank)
以下 API 将被加入x/bank keeper:
GetAllBalances(ctx Context, addr AccAddress) CoinsGetBalance(ctx Context, addr AccAddress, denom string) CoinSetBalance(ctx Context, addr AccAddress, coin Coin)LockedCoins(ctx Context, addr AccAddress) CoinsSpendableCoins(ctx Context, addr AccAddress) Coins
balances/{address}/{denom} 的字节表示形式建立索引。
DelegateCoins() 和 UndelegateCoins() 将被调整为:仅按(反)委托金额中出现的面额,分别加载对应的账户余额。因此,对账户余额的任何变更都将按面额进行。
SubtractCoins() 和 AddCoins() 将被调整为直接读写余额,而不是调用 GetCoins() / SetCoins()(这两个接口将不再存在)。
trackDelegation() 和 trackUndelegation() 将被调整为不再更新账户余额。
为了保持向后兼容,外部 API 需要扫描某个账户下的全部余额。建议这些 API 在可能的情况下使用 GetBalance 和 SetBalance,而不是 GetAllBalances,以避免加载整个账户余额。
Supply 模块
为了实现总供应量不变量,supply 模块现在需要扫描所有账户,并通过x/bank Keeper 调用 GetAllBalances,随后汇总这些余额并检查其是否与预期的总供应量一致。
状态
已接受。影响
正面
- 余额读写可实现 O(1) 复杂度(相对于账户具有非零余额的面额数量而言)。注意,这里并不是指实际 I/O 成本,而是所需直接读取次数的总量。
负面
- 在一次交易中读取和写入单个账户的全部余额时,效率会略低一些。
中性
无特别影响。参考
Changelog
- 2020-01-08: Initial version
- 2020-01-09: Alterations to handle vesting accounts
- 2020-01-14: Updates from review feedback
- 2020-01-30: Updates from implementation
Glossary
- denom / denomination key — unique token identifier.
Context
With permissionless IBC, anyone will be able to send arbitrary denominations to any other account. Currently, all non-zero balances are stored along with the account in ansdk.Coins struct, which creates a potential denial-of-service concern, as too many denominations will become expensive to load & store each time the account is modified. See issues 5467 and 4982 for additional context.
Simply rejecting incoming deposits after a denomination count limit doesn’t work, since it opens up a griefing vector: someone could send a user lots of nonsensical coins over IBC, and then prevent the user from receiving real denominations (such as staking rewards).
Decision
Balances shall be stored per-account & per-denomination under a denomination- and account-unique key, thus enabling O(1) read & write access to the balance of a particular account in a particular denomination.Account interface (x/auth)
GetCoins() and SetCoins() will be removed from the account interface, since coin balances will
now be stored in & managed by the bank module.
The vesting account interface will replace SpendableCoins in favor of LockedCoins which does
not require the account balance anymore. In addition, TrackDelegation() will now accept the
account balance of all tokens denominated in the vesting balance instead of loading the entire
account balance.
Vesting accounts will continue to store original vesting, delegated free, and delegated
vesting coins (which is safe since these cannot contain arbitrary denominations).
Bank keeper (x/bank)
The following APIs will be added to thex/bank keeper:
GetAllBalances(ctx Context, addr AccAddress) CoinsGetBalance(ctx Context, addr AccAddress, denom string) CoinSetBalance(ctx Context, addr AccAddress, coin Coin)LockedCoins(ctx Context, addr AccAddress) CoinsSpendableCoins(ctx Context, addr AccAddress) Coins
balances/{address}/{denom}.
DelegateCoins() and UndelegateCoins() will be altered to only load each individual
account balance by denomination found in the (un)delegation amount. As a result,
any mutations to the account balance by will made by denomination.
SubtractCoins() and AddCoins() will be altered to read & write the balances
directly instead of calling GetCoins() / SetCoins() (which no longer exist).
trackDelegation() and trackUndelegation() will be altered to no longer update
account balances.
External APIs will need to scan all balances under an account to retain backwards-compatibility. It
is advised that these APIs use GetBalance and SetBalance instead of GetAllBalances when
possible as to not load the entire account balance.
Supply module
The supply module, in order to implement the total supply invariant, will now need to scan all accounts & callGetAllBalances using the x/bank Keeper, then sum
the balances and check that they match the expected total supply.
Status
Accepted.Consequences
Positive
- O(1) reads & writes of balances (with respect to the number of denominations for which an account has non-zero balances). Note, this does not relate to the actual I/O cost, rather the total number of direct reads needed.
Negative
- Slightly less efficient reads/writes when reading & writing all balances of a single account in a transaction.