EOS 基础
EOS,可以理解为 Enterprise Operation System,即为商用分布式应用设计的一款区块链。EOS 是一个新的高性能区块链平台,可以快速部署高性能和高安全性的区块链应用。有以下特色:
- 支持免费交易
- 极快出块速度(0.5s)
- 短的区块确认(1.5s)
- WASM 智能合约
- 账户权限管理
- 上下文无关交易并行验证
一、EOS 主网启动
初始超级节点和创世节点选取、众筹活动代币冻结
- 在 EOS 主网上线之前,超级节点社区 将从成员里面挑选一批志愿参与 上线候选池(Go-Live Pool)的技术精英。这个上线候选池中应该有大概 50 人。
- 在上线之前,从候选池随机选出 22 个人组建一个 上线小队(Go-Live team)。然后,他们开始建立高度加密互通的 VPN 连接,以阻挡 DDOS 攻击及其他黑客攻击。
- EOS Token 的众筹活动(以太坊 ERC-20 代币合约)截止在 2018 年的 6 月 2 日 22:00,在这一刻,代币将被冻结。最后一刻会生成一个包含所有 EOS Token 数量的快照。
- 快照需要被 上线小队 22 名成员中的 15 人确认,随机选择 22 人中的 1 人作为 创世节点 ,其余为初始 超级节点 。创世节点随机一个 EOS 密钥(创世密钥)。
创世节点引导创建 EOS 区块链
- 根据之前的包含 EOS Token 数量的快照和创世密钥建立 EOS 区块链上第一个区块(创世区块)。
- 利用创世密钥和创世区块,创世节点启动,构建 初始 EOS 区块链网络 ,自动根据创世密钥创建 eosio 系统账号。
- 创世节点安装 21 个指定的初始超级节点,这些节点将负责进行首次超级节点投票选举。
- 创世节点将系统的算力分配给这 21 个超级节点。
- 将系统账号的权限从创世节点交给 21 个超级节点。接下来,创世节点将发布自己刚才使用的创世密钥(现在已无用)。
超级节点选举、初始超级节点替换
- 初始超级节点连接初始 EOS 区块链网络,并且对系统账户以及余额进行验证,然后开始生成区块。一旦 21 个指定节点都已经上线,EOS 区块链网络初步启动成功,后面参与进来的人就可以相互连接了。
- 上线小队 中的剩余成员可以连接 EOS 区块链,并将自己置于超级节点候选人的位置上。
- EOS Token 持币人也可以接入 EOS 区块链,并且用代币来换选票,用选票来进行超级节点的投票。
- 初始超级节点的职责只有一个:组织选举,直到 21 个新超级节点产生。21 个当选超级节点将进行几轮的区块生产来组织整个 EOS 主网,而上线小队中剩余的超级节点以候选者身份接入 EOS 主网,他们同样也可以参与 EOS 主网运行。
就此主网已经启动就绪,已当选的 21 个超级节点对主网的普通交易进行处理,开始无尽的候选者选举、当选超级节点等流程。启动过程可以总结为两步:
- 创建,配置和启动创世节点
- 从单一超级节点过渡到 21 个超级节点
二、账户模型
相比其它的区块链简单的公私钥账户模型,EOS 提供了完善的账户体系,包括自定义账户标识和权限管理等等。
1、钱包
- 钱包是一个用来存储私钥的地方
- 使用钱包之前,需要先打开解锁钱包
- 打开之后的钱包,有两种状态:解锁状态 和锁定状态
- 钱包,是一个超高强度加密的文件。失去钱包密码的话,意味着你丢失了钱包中的私钥
2、账户
类似各个 APP 上用户名,代表你身份的唯一标识,有以下几个特点:
- 是一个可读的字符串,创建之后,存储在区块链中。
- 长度为 1 到 12 个字符,字符可以包括小写字母 a -z,数字 1 - 5 和点(.)(最后一个字符除外)。
- 账户创建者自己选择命名。
- 由排列组合得出共有 31×(32^n 从 0 到 11 的和)=2^60−1=1,152,921,504,606,846,975 个账户,10^18 级别
- 私钥控制,且可以更换,所以可以更换私钥来确保账户安全
3、权限
当一个账户创建的时候,具备了两种基本权限。每个权限绑定到一个公钥上(单签名账户)或多个公钥上(多签名账户),除了绑定公钥也可以绑定到另一个有效的账户上:
- owner:账号主权限, 声明的这个账号的归属。拥有这个权限的私钥需要严格保存,可以用来设置其他权限和其它所有操作。
- active:活动权限,顾名思议,这个权限一般用来做一些转账、投票、发起事务等常规操作。
除了以上两种默认权限,还可以对账户自定义新的权限。在 EOS 节点上,账户权限使用数组的方式存储,每一个元素包括三个字段:权限名称、父权限名称、允许的权限,权限可以链接到其父权限,形成以 owner 为根的权限树,这就是在 EOSIO 中允许分层权限的原因。
4、权限的阈值公钥的权重
一个权限的许可需要公钥的权重不小于权限的阈值
- 单签名账户:就是权限的阈值和钥匙的权重都为 1 的一种账户类型。使用某个权限,只需要一把对应的私钥就行了
- 多签名账户:一个权限绑定了多个公钥,权限的阈值和每一个公钥的权重可以自定义,使用某个权限,需要多个公钥的许可且权重和不小于阈值
注:这里的许可就是公钥对应的私钥签名
三、资源模型
EOS 网络有 3 种资源可用,即 RAM、CPU 和 NET,这些资源由 21 个超级节点决定和提供。
- RAM(内存):RAM 是区块链账户和智能合约消耗的重要系统资源之一。RAM 充当永久性存储,用于存储帐户数据,它是有限的持久资源。
- CPU(中央处理器):代表交易的处理时间,以微秒(μs)为单位。CPU 是一种临时系统资源,一段时间后可恢复
- NET(带宽):代表传输交易所用带宽,以字节(byte)为单位。NET 也是一种临时系统资源,一段时间后可恢复
RAM
一般被称为内存,用于在 EOS 网络上存储账户相关数据(包括账户名、授权信息、合约代码、合约 abi 和智能合约数据)。RAM 是固定资源,买多少就拥有多少,有多少就可以用多少,没有分配问题,也比较容易计量。RAM 的买卖,实质上花 EOS 从系统账户处购买,而不是买方和卖方直接的交易。不论是购买 RAM,还是卖出 RAM,都是参与者与系统账户之间的交互,该过程将会收取手续费。RAM 的价格是基于 Bancor 算法,可简单理解为市场的供需模型,如果 RAM 供不应求,则买入 RAM 时就需要锁定更多的 EOS,同时,卖出 RAM 也能获得更多的 EOS。如果一笔交易使用的内存超过限制则会被拒绝,另外,对于不再使用的 RAM,可以销毁并卖出。EOS 采用全内存方案,将账户信息和账户数据直接放到超级节点计算机的 RAM 中,加快了合约的处理速度。
CPU 和 NET
通过抵押 EOS 获得,属于可恢复资源。账户每发起一笔交易时,就会减掉一部分 CPU 和 NET,当 CPU 与宽带资源耗尽之后,完全恢复需要 24 小时,这样就限制了账户一段时间内使用的资源总量。CPU 和 NET 是按照质押的 EOS 比例来分配的,每个账户所能获得的资源为:系统总资源 * 抵押代币 / 总的抵押代币。虽然说 CPU 和 NET 的完全恢复周期为 24h,但不代表需要等 24 小时才能使用,其实 EOS 的资源是每时每刻都在恢复的。对 CPU 的计量主要是交易消耗的时间,对 NET 的计量主要是交易的大小,EOS 有 21 个超级节点,每个节点的性能是不一样的,同样的交易,在不同的出块节点执行,消耗的 CPU 时间是不一样的,在这里以出块节点为准,节点将交易打包到区块时,会开一张收据,这张收据记录交易消耗的 CPU 和 NET,其它节点都认这个收据。
对于 EOS 区块链,可以看做一台电脑,所有的账户都可以使用它(执行交易),但是它资源是有限的,所以要限制。内存是静态的(账户一旦写入数据就一直存在,除非数据被销毁了),所以需要花钱购买,不用了可以卖掉获得一部分钱;而处理器和带宽是动态的(现在用掉了一会儿不用就会自动恢复),可以采用抵押的方式,按照抵押的比例获得对应比例的处理器和带宽。通过买卖内存和租用处理器带宽的机制,可以让开发应用的人购买和抵押 EOS 提供服务,供普通用户免费使用。
四、智能合约入门
使用 debian10 搭建环境,项目是 C ++ 构建的,依赖复杂,其它 Linux 平台使用需要安装好依赖包,不同的系统包名可能有所不同。为了方便和主机隔离,使用 docker 作为演示,需要先安装 docker。
1、环境构建
创建并运行 debian:10 镜像
docker run --name eos --restart always -p 8888:8888 -itd debian:10
连接 debian 终端
docker exec -it eos /bin/bash
换源(加速下载包)和更新 ,自带的实在是太慢了
/bin/bash -c 'echo -e "\
deb http://mirrors.163.com/debian/ buster main non-free contrib\n\
deb http://mirrors.163.com/debian/ buster-updates main non-free contrib\n\
deb http://mirrors.163.com/debian/ buster-backports main non-free contrib\n\
deb http://mirrors.163.com/debian-security/ buster/updates main non-free contrib" > /etc/apt/sources.list;\
apt update;\
apt upgrade -y;'
安装所需的包 ,还包括一些必备工具
apt install -y clang cmake git g++ nano curl pkg-config libusb-1.0-0-dev libgmp-dev doxygen libssl-dev libcurl4-openssl-dev libboost-all-dev zlib1g-dev graphviz
编译安装区块链 eos 项目 ,使用 2.0.6 版本
git clone https://github.com/EOSIO/eos.git -b v2.0.6 --depth 1 --recursive --shallow-submodules ~/eosio/eos
mkdir ~/eosio/eos/build && cd ~/eosio/eos/build && cmake .. && make -j$(nproc) && make install
编译安装智能合约 cdt 项目 ,使用 1.7.0 版本
git clone https://github.com/EOSIO/eosio.cdt.git -b v1.7.0 --depth 1 --recursive --shallow-submodules ~/eosio/eosio.cdt
mkdir ~/eosio/eosio.cdt/build && cd ~/eosio/eosio.cdt/build && cmake .. && make -j$(nproc) && make install
安装检查
程序会安装到目录 /usr/local/bin 下,主要包括 nodeos、keosd、cleos、eosio-cpp 等命令, 使用以下命令查看它们
ls -l /usr/local/bin
清理编译缓存 ,缓存很大
rm -r ~/eosio/eos/build ~/eosio/eosio.cdt/build
删除源代码 ,如果编译安装成功,可以删除代码
rm -r ~/eosio
2、钱包和相关秘钥生成
创建钱包,保存密码到~/eosio-wallet/passwd,记住钱包密码,后面需要用来解锁钱包
cleos wallet create -f ~/eosio-wallet/passwd
cat ~/eosio-wallet/passwd && echo
打开钱包 ,查看和解锁钱包
cleos wallet open
cleos wallet list //带有*号为已解锁钱包
cleos wallet unlock //密码为上一步创建时得到的密码
生成开发者密钥 ,创建一个密钥对,私钥用来签名交易,部署合约,公钥用作身份标识
cleos wallet create_key
导入系统密钥 (每一个 eos 链都有一个相同的系统密钥,用于链的治理和共识,它是众所周知的)
cleos wallet import
为了方便,使用这个私钥:5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3
密钥查看 ,命令:cleos wallet private_keys
,确保钱包打开并解锁,并输入钱包密码,可以看到创建的开发者秘钥和导入的系统秘钥
password: [[
"EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV",
"5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3"
],[
"EOS7X2xejGqzG5uPVfgz85oks9G4YKDsE5W4Va7rBzyPbrRzpxHqj",
"5KGvAGENQzPNjaZJ82ofdvMPZU2U6RhQA5PrSobF1v6FwHKvGfM"
]
]
3、单节点启动与测试账户生成
启动钱包服务,使用ps -aux
命令查看钱包服务是否运行,如果没有,使用以下命令启动服务
keosd &
启动区块链节点服务 ,加载所有基本插件,更多参数信息查看官方文档
nodeos -e -p eosio \
--plugin eosio::producer_plugin \
--plugin eosio::producer_api_plugin \
--plugin eosio::chain_api_plugin \
--plugin eosio::http_plugin \
--plugin eosio::history_plugin \
--plugin eosio::history_api_plugin \
--filter-on="*" \
--access-control-allow-origin='*' \
--contracts-console \
--http-validate-host=false \
--verbose-http-errors >> nodeos.log 2>&1 &
环境检查 :tail -f nodeos.log
跟踪区块生产日志,按下ctrl + c
以关闭日志;cleos get info
输出节点信息;cleos get account eosio
,查看 eosio 系统账户,初始创建的默认系统账户,可以看到,公钥是之前导入的系统秘钥的公钥
# tail -f nodeos.log
info 2020-06-11T07:09:29.903 nodeos producer_plugin.cpp:2093 produce_block ] Produced block 4db76dfa2ec0d5be... #5 @ 2020-06-11T07:09:30.000 signed by eosio [trxs: 0, lib: 4, confirmed: 0]
info 2020-06-11T07:09:30.402 nodeos producer_plugin.cpp:2093 produce_block ] Produced block b004a1a219686344... #6 @ 2020-06-11T07:09:30.500 signed by eosio [trxs: 0, lib: 5, confirmed: 0]
^C
# cleos get info
{
"server_version": "c6a7ec0d",
"chain_id": "cf057bbfb72640471fd910bcb67639c22df9f92470936cddc1ade0e2f2e7dc4f",
"head_block_num": 38,
"last_irreversible_block_num": 37,
"last_irreversible_block_id": "000000252e6b2af2fe7a512f21df20bfc5dd1e2ed74f69a5c64d0a9539544fbf",
"head_block_id": "00000026e8d449cb0057ab3f5a17bb9b2ceb6fc8baf7e0c5b672a6b698d79816",
"head_block_time": "2020-06-11T07:39:40.000",
"head_block_producer": "eosio",
"virtual_block_cpu_limit": 207526,
"virtual_block_net_limit": 1088100,
"block_cpu_limit": 199900,
"block_net_limit": 1048576,
"server_version_string": "v2.0.6",
"fork_db_head_block_num": 38,
"fork_db_head_block_id": "00000026e8d449cb0057ab3f5a17bb9b2ceb6fc8baf7e0c5b672a6b698d79816",
"server_full_version_string": "v2.0.6-c6a7ec0dd816f98a6840f59dca9fed04efd9f7a5"
}
# cleos get account eosio
created: 2018-06-01T12:00:00.000
privileged: true
permissions:
owner 1: 1 EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV
active 1: 1 EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV
memory:
quota: unlimited used: 2.66 KiB
创建测试账户 bob 和 alice,权限公钥使用之前获得的开发者公钥
cleos create account eosio bob EOS7X2xejGqzG5uPVfgz85oks9G4YKDsE5W4Va7rBzyPbrRzpxHqj
cleos create account eosio alice EOS7X2xejGqzG5uPVfgz85oks9G4YKDsE5W4Va7rBzyPbrRzpxHqj
如果不能创建需要先解锁钱包
4、hello 合约示例
创建 hello 合约
mkdir ~/hello && nano ~/hello/hello.cpp
使用下面的示例代码,使用ctrl + c
和ctrl + c
保存并退出
#include <eosio/eosio.hpp>
using namespace eosio;
class [[eosio::contract]] hello : public contract {
public:
using contract::contract;
[[eosio::action]]
void hi( name user ) {
print( "Hello, ", user);
}
};
编译 hello 合约 ,会在~/hello 目录下生成同名的 wasm 和 abi 文件
cd ~/hello && eosio-cpp -abigen -o hello.wasm hello.cpp
部署 hello 合约
首先为合约创建 hello 帐户,公钥使用之前获得的开发者秘钥
cleos create account eosio hello EOS7X2xejGqzG5uPVfgz85oks9G4YKDsE5W4Va7rBzyPbrRzpxHqj -p eosio@active
使用set contract
子命令部署合约, 合约使用绝对路径
cleos set contract hello /root/hello -p hello@active
调用 hello 合约
cleos push action hello hi '["bob"]' -p bob@active
如果成功,将会输出 Hello, bob
# cleos push action hello hi '["bob"]' -p bob@active
executed transaction: 8e862b47e6cdfb78a0b941ce450a3bfd196fd1bf63351ebc376c1b7cf3d1b989 104 bytes 1331 us
# hello <= hello::hi {"user":"bob"}
>> Hello, bob
5、token 合约示例
获取 token 合约,使用官方提供合约
git clone https://github.com/EOSIO/eosio.contracts -b v1.9.1 --depth 1 ~/eosio.contracts
编译 token 合约 ,注意查看是否生成 abi 和 wasm 文件
cd ~/eosio.contracts/contracts/eosio.token && eosio-cpp -I include -o eosio.token.wasm src/eosio.token.cpp --abigen
部署 token 合约
先创建 eosio.token 账户,公钥使用之前获得的系统秘钥,可能需要先解锁钱包
cleos create account eosio eosio.token EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV
部署合约
cleos set contract eosio.token /root/eosio.contracts/contracts/eosio.token --abi eosio.token.abi -p eosio.token@active
调用 token 合约
创建代币,指定了代币发行者,合约所有者才能创建
cleos push action eosio.token create '[ "alice", "1000000000.0000 SYS"]' -p eosio.token@active
发行代币,代币所有者才能发行
cleos push action eosio.token issue '[ "alice", "100.0000 SYS", "memo" ]' -p alice@active
代币转账
cleos push action eosio.token transfer '[ "alice", "bob", "25.0000 SYS", "m" ]' -p alice@active
代币余额查看与验证
cleos get currency balance eosio.token bob SYS
cleos get currency balance eosio.token alice SYS
最终 alice 和 bob 的 SYS 分别是 75 和 25
root@2554169d285c:~/eosio.contracts/contracts/eosio.token# cleos push action eosio.token issue '[ "alice", "100.0000 SYS", "memo" ]' -p alice@active
executed transaction: 58214acda6e872c5003228132e0995107f9678d864bd48c9116cb8367cec8984 128 bytes 766 us
# eosio.token <= eosio.token::issue {"to":"alice","quantity":"100.0000 SYS","memo":"memo"}
warning: transaction executed locally, but may not be confirmed by the network yet ]
root@2554169d285c:~/eosio.contracts/contracts/eosio.token# cleos push action eosio.token transfer '[ "alice", "bob", "25.0000 SYS", "m" ]' -p alice@active
executed transaction: 34c9daf8587f53e778c19374b3b1dd40a106ad9dd345e5c6388c1c5819fd3a45 128 bytes 1131 us
# eosio.token <= eosio.token::transfer {"from":"alice","to":"bob","quantity":"25.0000 SYS","memo":"m"}
# alice <= eosio.token::transfer {"from":"alice","to":"bob","quantity":"25.0000 SYS","memo":"m"}
# bob <= eosio.token::transfer {"from":"alice","to":"bob","quantity":"25.0000 SYS","memo":"m"}
warning: transaction executed locally, but may not be confirmed by the network yet ]
root@2554169d285c:~/eosio.contracts/contracts/eosio.token# cleos get currency balance eosio.token bob SYS
25.0000 SYS
root@2554169d285c:~/eosio.contracts/contracts/eosio.token# cleos get currency balance eosio.token alice SYS
75.0000 SYS
6、注意事项:
- 钱包保存在
~/eosio-wallet
目录中,节点数据数据保存在~/.local/share/eosio
目录中,区块生成日志保存在~/nodeos.log
文件中 - 上面示例中将钱包密码保存到~/eosio-wallet/passwd 文件中,钱包密码非常重要,需要妥善保存
- 如果数据和环境弄乱了,密码忘记了,可以清除相关数据重新开始
- EOS 的账户是需要创建的,光在钱包生成秘钥是不行的
- 交易不能执行,可能是钱包锁定了,重新解锁即可