system合约是EOS区块链最核心的智能合约,分析其源码可以一窥EOS的精妙之处。
native 基础的数据结构和功能
delegate_bandwidth 带宽抵押与内存买卖
exchange_state 内存市场数据结构与bancor算法
eosio.system 关键数据结构
producer_pay和voting DPOS相关
// *** native.hpp ***
newaccount
updateauth
deleteauth
linkauth
unlinkauth
canceldelay
onerror
// 更新区块链参数到链上
// require_auth(eosio)
void setparams(const blockchain_parameters& params);
// 设置用户特权级
// ispriv为bool值
void setpriv(account_name account, uint8_t ispriv);
/* 区块链全局状态 */
struct eosio_global_state : public blockchain_parameters
{
uint64_t max_ram_size = 64 * 1024 * 1024 * 1024; // 64GB
uint64_t total_ram_bytes_reserved;
int64_t total_ram_stake;
block_timestamp last_porducer_schedule_update;
uint64_t last_pervote_bucket_fill;
int64_t pervote_bucket;
int64_t perblock_bucket;
uint32_t total_unpaid_blocks;
int64_t total_activated_stake_time;
uint64_t thresh_activated_stake_time;
uint16_t last_producer_schedule_size;
double total_producer_vote_weight;
block_timestamp last_name_close;
}
// 区块链参数
struct blockchain_parameters
{
uint64_t max_block_net_usage;
uint32_t target_block_net_usage_pct;
uint32_t max_transaction_net_usage;
uint32_t base_per_transaction_net_usage;
uint32_t net_usage_leeway;
uint32_t context_free_discount_net_usage_num;
uint32_t context_free_discount_net_usage_den;
uint32_t max_block_cpu_usage;
uint32_t target_block_cpu_usage_pct;
uint32_t max_transaction_cpu_usage;
uint32_t min_transaction_cpu_usage;
uint64_t context_free_discount_cpu_usage_num;
uint64_t context_free_discount_cpu_usage_den;
uint32_t max_transaction_lifetime;
uint32_t deferred_trx_expiration_window;
uint32_t max_transaction_delay;
uint32_t max_inline_action_size;
uint16_t max_inline_action_depth;
uint16_t max_authority_depth;
};
有一定EOS智能合约开发经验的朋友可以知道,在每次调用合约对外接口时,智能合约对应的类的构造函数和析构函数都是会被调用一遍的,如此使得合约类的构造析构函数会发挥特殊的作用。
构造
1.获取当前区块链参数
2.开启内存市场
析构
更新区块链参数
单例singleton
system合约类定义了两个特殊的成员变量,_gstate和_global。
_global为eosio::singleton类型,_gstate为eosio_global_state类型。
这里涉及到eos内置的singleton,通过观察其定义,可以知道是通过eosio::multi_index存储实例的模板类单例。
eosio::singleton的用法通常会与EOS合约的构造析构函数结合起来,一个是singleton,一个是临时变量。在构造函数中,把singleton的值传到临时变量,其他接口都是通过操作临时变量来完成实现,最后在析构函数中把最新的值更新到singleton中。
// N(userres) user_resources_table
user_resources {
account_name owner // primary key
asset net_weight
asset cpu_weight
int64_t ram_bytes
}
// N(delband) del_bandwidth_table
delegated_bandwidth {
account_name from
account_name to // primary key
asset net_weight
asset cpu_weight
}
// N(refunds) refunds_request
refund_request {
account_name owner // primary key
time request_time
asset net_amount
asset cpu_amount
}
+ void delegatebw(account_name from,
account_name receiver,
asset stake_net_quantity,
asset stake_cpu_quantity,
bool transfer);
+ void undelegatebw(account_name from,
account_name receiver,
asset unstake_net_quantity,
asset unstake_cpu_quantity);
+ void refund(account_name);
- void changebw(account_name from,
account_name receiver,
const asset stake_net_delta,
const asset stake_cpu_delta,
bool transfer)
抵押带宽delegatebw和反抵押带宽undelegatebw内部都是通过调用更改带宽changebw来实现抵押与反抵押。
**changebw的逻辑:
EOS中可以用EOS抵押的带宽有CPU,网络带宽,抵押比例为1:1。
在抵押带宽的过程中,如果含有transfer标志,则资产会转移到接受者(receiver)账下(后面反抵押由receiver发起);否则,资产仍然属于抵押者(from)账下(后面反抵押由from发起)。
在反抵押的过程中,先前抵押以获得的带宽会立刻减少对应的数量,而抵押的EOS会在抵押周期过后返还给抵押者(from)。
在抵押和反抵押的过程中,用户选票的权重会立即根据当前的抵押量而变化。
// 更新最大内存总量
+ void setram(uint64_t max_ram_size);
+ void buyrambytes(account_name from,
account_name receiver,
uint32_t bytes);
+ void buyram(account_name buyer,
account_name receiver,
asset tokens);
+ void sellram(account_name receiver, int64_t bytes);
购买内存逻辑
用户以通货购买内存,根据当前内存价格(bancor算法),以提高内存的使用额度。
出售内存逻辑
在卖出内存时,返还的通货数量是以原来购买内存时购买额度的平均价钱决定的。
/* 账号竞拍信息 */
name_bid
{
account_name newname; // primary key
account_name high_bidder;
int64_t high_bid; // secondary key,负数表示等待揭晓的已关闭的拍卖
uint64_t last_bid_time;
}
+ void bidname(account_name bidder, account_name newname, asset bid)
EOS可以对指定名字的账户进行竞拍,但有特殊要求:长度为0 - 11个字符 或 12字符且包含点(dot .)。
新的竞价也要比当前的最高竞价高出至少10%:bid.amount - current->high_bid > (current->high_bid / 10)。
/* 生产者/超级节点信息 */
producer_info
{
account_name owner; // primary key
double total_votes; // secondary key,
public_key producer_key;
bool is_active;
string url;
uint32_t unpaid_blocks;
uint64_t last_claim_time;
uint16_t location;
}
/* 选民信息
包含选民ID,代理,投票权重等信息
*/
voter_info
{
account_name owner; // primary key
account_name proxy;
vector<account_name> producers;
int64_t staked;
double last_vote_weight;
double proxied_vote_weight;
bool is_proxy;
uint32_t reserved1;
time reserved2;
asset reserved3;
}
+ void regproducer(const account_name producer,
const public_key& producer_key,
const string& url,
uint16_t location);
void rmvproducer(account_name producer)
+ void voteproducer(const account_name voter,
const account_name proxy,
const vector<account_name>& producers);
+ void unregprod(const account_name producer);
+ void regproxy(const account_name proxy, bool isproxy);
// 更新区块生产信息
void onblock(block_timestamp timestamp, account_name producer);
// 派发区块生产者奖励
void claimrewards(const account_name& owner)
投票权重算法
weight = int64_t((now() - (block_timestamp::block_timestamp_epoch / 1000)) / (seconds_per_day * 7)) / double(52)
vote = double(staked) * 2 ** weight
生产者producer:即生产区块的超级节点。
投票人voter:生产者的投票人
代理人proxy:代表其他人来投票,代理人需要有staked的资产。
regproducer,unregprod,regproxy逻辑较为简单,在此略过。
更新投票(update_votes)
前提要求:
逻辑过程:
for (const auto & p : voter->producers)
{
auto& d = producer_deltas[p];
d.first -= voter->last_vote_weight;
d.second = false;
}
for every producer of producer_deltas
producer.total_votes += producer_deltas[produceritr].vote_weight
_gstate.total_producer_vote_weight += producer_deltas[produceritr].vote_weight
voter.last_vote_weight = new_vote_weight
voter.producers = producers
voter.proxy = proxy
在每个新的区块中,区块生产者会生成第一个交易,该交易就是onblock,用于标记未付款的区块数(unpaid block)和更新账户竞标的信息。
而发放奖励的接口claimrewards由外部调用触发。
onblock
宣布奖励claimrewards
发放给指定的生产者以对应的奖励。该接口在区块链内部不会自动调用,由外部调用触发。
前提要求:
EOSIO并非每年只增发一次,一次性增发完全年所有的EOS,而是采用连续增发方式,即任何节点领取(claim)时都会触发系统的增发动作,这也就意味着,增发是根据流动时间长度不定期发生的。
new_tokens为增发的代币量,公式为:
增发代币数量 = 年通货膨胀系数(0.04879 = 4.879%) × 当前代币发行总量 × 上一次增发代币到现在的时长 / 一年的微秒数
年通胀系数为什么不是5%?简单来说,5%是每年只增发一次的年通胀率,但EOS采用的是无限次数增发的方式,因此,在这种增发方式下求出来的年通胀系数为4.879%,可使得EOS的年通胀率近似为5%。
设所有BP全年领取的总次数(即增发次数)n为无穷大,单次通胀率为x。那么在EOS初始总量为10亿,一年增发后数量为10.5亿的情况下,可得等式10乘以(1+x)的n次方等于10.5,求极限可得年通胀系数x乘以n等于ln1.05,即 0.04879。
const auto usecs_since_last_fill = ct - _gstate.last_pervote_bucket_fill;
auto new_tokens = continuous_rate(=0.04879) * double(token_supply.amount) * double(usecs_since_last_fill)) / double(useconds_per_year) );
代币增发的频率没有限制。
增发的代币中的
auto to_producers = new_tokens / 5;
auto to_savings = new_tokens - to_producers;
auto to_per_block_pay = to_producers / 4;
auto to_per_vote_pay = to_producers - to_per_block_pay;
INLINE_ACTION_SENDER(eosio::token, issue)( N(eosio.token), {{N(eosio),N(active)}},
{N(eosio), asset(new_tokens), std::string("issue tokens for producer pay and savings")} );
INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio),N(active)},
{ N(eosio), N(eosio.saving), asset(to_savings), "unallocated inflation" } );
INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio),N(active)},
{ N(eosio), N(eosio.bpay), asset(to_per_block_pay), "fund per-block bucket" } );
INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio),N(active)},
{ N(eosio), N(eosio.vpay), asset(to_per_vote_pay), "fund per-vote bucket" } );
_gstate.pervote_bucket += to_per_vote_pay;
_gstate.perblock_bucket += to_per_block_pay;
_gstate.last_pervote_bucket_fill = ct;
producer_per_block_pay = (_gstate.perblock_bucket * prod.unpaid_blocks) / _gstate.total_unpaid_blocks;
producer_per_vote_pay = int64_t((_gstate.pervote_bucket * prod.total_votes ) / _gstate.total_producer_vote_weight);
INLINE_ACTION_SENDER(eosio::token, issue)( N(eosio.token), {{N(eosio),N(active)}},
{N(eosio), asset(new_tokens), std::string("issue tokens for producer pay and savings")} );
INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio),N(active)},
{ N(eosio), N(eosio.saving), asset(to_savings), "unallocated inflation" } );
INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio),N(active)},
{ N(eosio), N(eosio.bpay), asset(to_per_block_pay), "fund per-block bucket" } );
INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio),N(active)},
{ N(eosio), N(eosio.vpay), asset(to_per_vote_pay), "fund per-vote bucket" } );
_gstate.pervote_bucket -= producer_per_vote_pay;
_gstate.perblock_bucket -= producer_per_block_pay;
_gstate.total_unpaid_blocks -= prod.unpaid_blocks;
区块链內会维护一个数据库,名为context.db,存有信息: