比特币、以太坊、EOS的对比

简介

平台和工具链

EOSIO Development Lifecycle

Nodeos

  • EOSIO节点守护进程的核心
  • 处理区块链数据持久层、点对点网络和智能合约调度
  • 在开发环境中,可以使用nodeos建立单节点区块链网络

Cleos

  • 命令行工具,调用nodeos暴露的接口API
  • 部署和测试EOSIO智能合约

Keosd

  • 密钥管理器守护进程,用于存储私钥和对消息签名
  • keosd为要在相关钱包文件中加密的密钥提供安全密钥存储介质
  • keosd守护进程还定义了一个安全的enclave,用于对cleos或第三方库创建的交易进行签名

EOSIO.CDT

  • WASM工具链
  • 对EOSIO智能合约的编译进行EOSIO-specfic优化

EOSJS

  • JavaScript API SDK,集成基于EOSIO的区块链

核心概念

账户

  • 存储在区块链上的人类可读的名字
  • 用于转移或提交有效的交易到区块链上

钱包

  • 存储密钥的客户端
  • 理想情况下,钱包有一个加密状态和一个解密状态

授权和权限

  • 定义交易的需求,通过linking authorization或linkauth来为特殊合约分配权限

智能合约

  • 在链上执行,一旦执行就永久留存在链上且不可变

DPOS共识

  • Delegated Proof of Stake 委托权益证明
  • 若在链上持有token,可以通过投票系统选择block producer
  • 任何人都可以参与block producer的竞选

RAM

  • 被视为永久存储,用于存放账户名、权限、代币余额等需要快速访问的链上数据
  • RAM需要购买,并且不是基于权益分配,因为RAM是一个有限的持久性资源

CPU

  • 代表区块链上一个动作的执行时间
  • 在cleos的 get account 命令的输出中,CPU被称为 cpu bandwidth,代表一个账户在将动作提交到合约时可以支配的处理时间
  • CPU是一个瞬态系统资源,属于EOSIO的权益机制

NET

  • NET是交易的网络带宽,单位为byte
  • 在cleos的 get account 命令的输出中,NET被称为 net bandwidth
  • NET是一个瞬态系统资源,属于EOSIO的权益机制

技术特征

WebAssembly C++编译

  • EOSIO使用C++编写智能合约
  • 在EOSIO核心层的顶端是一个Wasm虚拟机:EOS VM ,用于执行智能合约(它是为区块链应用的高要求而设计的,对WebAssembly引擎的要求远远高于为web浏览器标准开发而设计的引擎)
  • 使用Wasm标准有利于将其他语言移植到EOSIO平台

高吞吐量、更快的确认、低延迟

通过智能合约分配和管理资源

权益机制

  • 通过权益机制来获取系统资源(CPU和NET)
  • 用户使用权益token换取CPU和NET的使用,按照占所有用户的token比例分配

商业模型灵活性

综合的权限架构

可升级

高效能源消耗

环境配置

安装EOSIO和CDT

根据官网指示安装EOSIOCDT,在Ubuntu20.04上,下载github上对应的Pre-release包

创建开发钱包

创建钱包

注意保存密码

1
cleos wallet create --to-console

打开钱包

1
cleos wallet open

查看钱包列表

1
cleos wallet list

解锁钱包

1
cleos wallet unlock

向钱包导入密钥

1
cleos wallet create_key

导入开发密钥

1
cleos wallet import

输入如下私钥(所有新的EOSIO链都有相同的开发密钥)

1
5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3

对应的开发公钥为:

1
EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV

开启keosd和nodeos

开启keosd

1
keosd &

若遇到报错:

1
"3120000 wallet_exception: Wallet exception Failed to lock access to wallet directory; is another keosd running?"

先使用命令 pkill keosd 再重新开启

开启nodeos

运行Nodeos,加载所有基本插件,设置服务器地址,开启CORS,添加合约debug和日志

1
2
3
4
5
6
7
8
9
10
11
12
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 &

查看日志

查看Nodeos是否正在出块

1
tail -f nodeos.log

如果报错 Database dirty flag set (likely due to unclean shutdown): replay required ,先使用命令 pkill nodeos 安全关闭nodeos,再重新开启

确保RPC API正常运行

1
curl http://localhost:8888/v1/chain/get_info

创建开发账户

  • 账户是授权的集合,保存在区块链上
  • 账户可以标识一个用户,也可以标识智能合约(一个账户只能有一个智能合约)
  • 账户可以由一个用户或一组用户控制

创建测试账户

每个EOSIO账户都和一个公钥绑定

1
2
3
cleos create account eosio bob EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV

cleos create account eosio alice EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV

查看账户信息

1
cleos get account alice

得到如下结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
permissions: 
owner 1: 1 EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV
active 1: 1 EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV
memory:
quota: unlimited used: 2.66 KiB

net bandwidth:
used: unlimited
available: unlimited
limit: unlimited

cpu bandwidth:
used: unlimited
available: unlimited
limit: unlimited

用户可以暴露active公钥,自己保存owner公钥,这样,如果active公钥被泄露,用户可以使用owner公钥取回账户控制权(所以建议使用不同的active公钥和owner公钥)

智能合约编写

Hello World

  1. 创建hello目录并编写hello.cpp

    1
    2
    3
    4
    5
    6
    7
    8
    #include <eosio/eosio.hpp>
    class [[eosio::contract]] hello : public eosio::contract {
    public:
    using eosio::contract::contract;
    [[eosio::action]] void hi(eosio::name user) {
    print("Hello, ", user);
    }
    };
  2. 编译cpp,会产生两个文件:hello.wasm和hello.abi

    1
    eosio-cpp -abigen -o hello.wasm hello.cpp
  3. 部署智能合约

    • 首先创建一个新的hello账户,在hello目录下生成hello账户的公私钥

      1
      cleos create key --file hello_key
    • 使用生成的公钥创建hello账户

      1
      cleos create account eosio hello <public key>
    • 向钱包导入hello私钥

      1
      cleos wallet import
    • 部署hello智能合约到hello账户(先cd到contracts目录)

      1
      cleos set contract hello ./hello -p hello@active
  4. 调用智能合约

    1
    cleos push action hello hi '["bob"]' -p bob@active
  5. 设置智能合约调用权限

    • 修改hello.cpp并重新编译以及部署

      1
      2
      3
      4
      5
      6
      7
      8
      9
      #include <eosio/eosio.hpp>
      class [[eosio::contract]] hello : public eosio::contract {
      public:
      using eosio::contract::contract;
      [[eosio::action]] void hi(eosio::name user) {
      require_auth(user);
      print("Hello, ", eosio::name{ user });
      }
      };
    • 测试:bob说hi,但交易由alice签名(失败)

      1
      2
      3
      4
      cleos push action hello hi '["bob"]' -p alice@active

      Error 3090004: Missing required authority
      Ensure that you have the related authority inside your transaction!;
    • 测试:bob说hi,交易由bob签名(成功)

      1
      2
      3
      4
      5
      cleos push action hello hi '["bob"]' -p bob@active

      executed transaction: 033b54bd4a79a9bc868910087bd769e108ffe6f0db29b2b51c61007b03880f51 104 bytes 116 us
      # hello <= hello::hi {"user":"bob"}
      >> Hello, bob

Tokens

  1. 获取源代码,在contracts目录下:

    1
    2
    3
    git clone https://github.com/EOSIO/eosio.contracts --branch v1.7.0 --single-branch

    cd eosio.contracts/contracts/eosio.token
  2. 创建一个账户来部署token合约(公钥是EOSIO开发公钥)

    1
    cleos create account eosio eosio.token EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV
  3. 编译智能合约

    1
    eosio-cpp -I include -o eosio.token.wasm src/eosio.token.cpp --abigen
  4. 部署智能合约(cd到 eosio.token/…/ 目录下)

    1
    cleos set contract eosio.token ./eosio.token --abi eosio.token.abi -p eosio.token@active
  5. 创建token:需要指定一个颁发者和token名称以及最大供应量;同时为了创建token,需要eosio.token账户的权限

    1
    cleos push action eosio.token create '[ "alice", "1000000000.0000 SYS"]' -p eosio.token@active
  6. 颁发token:拥有颁发权的alice颁发100个token(可以加上 -d -j 参数来debug)

    1
    cleos push action eosio.token issue '[ "alice", "100.0000 SYS", "memo" ]' -p alice@active
  7. 交易token:alice转了25个token给bob

    1
    cleos push action eosio.token transfer '[ "alice", "bob", "25.0000 SYS", "m" ]' -p alice@active
    • 查看bob以及alice的账户余额

      1
      2
      3
      cleos get currency balance eosio.token bob SYS

      cleos get currency balance eosio.token alice SYS

ABI文件

Application Binary Interface(ABI)用来描述如何将用户操作在JSON格式和二进制格式之间转换

ABI文件结构

1
2
3
4
5
6
7
8
9
10
{
"version": "eosio::abi/1.0",
"types": [],
"structs": [],
"actions": [],
"tables": [],
"ricardian_clauses": [],
"abi_extensions": [],
"___comment" : ""
}

types

自定义数据类型需要在ABI文件中进行描述,但是对于EOS.IO的内建类型,无需在ABI文件中说明,之前的helloeos.token合约的ABI文件中,types都是[],因为没有自定义类型

如下,给account_name建立一个笔名,在ABI文件里用name可以代替account_name

1
2
3
4
"types": [{
"new_type_name": "account_name",
"type": "name"
}]

structs

声明各个action需要传入的参数,系统根据actions部分中声明的type,在structs部分寻找对应的数据结构,也就是说,函数声明部分并没包含具体内容,具体数据需要到structs中寻找

1
2
3
4
5
6
7
{
"name": "issue", // 数据结构名
"base": "", // 继承的父结构名
"fields": [ // 参数数组,包含参数名和类型
{"name":"", "type":""},
]
}

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
"structs": [{
"name": "transfer",
"base": "",
"fields": [
{"name":"from", "type":"account_name"},
{"name":"to", "type":"account_name"},
{"name":"quantity", "type":"asset"},
{"name":"memo", "type":"string"}
]
},{
"name": "create",
"base": "",
"fields": [
{"name":"issuer", "type":"account_name"},
{"name":"maximum_supply", "type":"asset"},
{"name":"can_freeze", "type":"uint8"},
{"name":"can_recall", "type":"uint8"},
{"name":"can_whitelist", "type":"uint8"}
]
},{
"name": "issue",
"base": "",
"fields": [
{"name":"to", "type":"account_name"},
{"name":"quantity", "type":"asset"},
{"name":"memo", "type":"string"}
]
}
]

actions

用于声明智能合约有哪些可以调用的action

1
2
3
4
5
{
"name": "transfer", // 在智能合约中定义的函数名
"type": "transfer", // 在structs中声明的数据结构名
"ricardian_contract": "" // 可选参数,开发中
}

tables

列出智能合约需要建立的数据表的名称以及数据表中存储的结构体名称

1
2
3
4
5
6
7
{
"name": "", // 数据表的名称
"type": "", // 数据表对应的struct
"index_type": "", // 主键类型
"key_names" : [], // 键名列表,长度与key_types相同
"key_types" : [] // 键的类型列表,长度与key_names相同
}

例子:

1
2
3
4
5
6
7
{
"name": "accounts",
"type": "account",
"index_type": "i64",
"key_names" : ["primary_key"],
"key_types" : ["uint64"]
}