EOS智能合约的HelloWorld
EOS主网已经如火如荼开始上线了,作为区块链从业者,如何开始在EOS上开发自己的应用呢?SIC(Smart Insurance Chain)开发团队从EOS的Dawn2.0,到Dawn3.0、4.0,再到现在的EOS1.0,见证了EOS一路的风雨历程,来跟大家分享EOS的技术知识,希望能推动EOS生态更健康的发展。本文可以是要拥抱EOS的有心人,应该阅读的第一篇入门文档。
摘要:本文介绍区块链架构EOS上面智能合约,从智能合约的概念、EOS的智能合约的机制、以及编写和上传一个简单的智能合约介绍。
1、简介
目前EOSIO1.0版本已经正式发布,主网启动也在如火如荼的进行当中,当然,主网的启动,是重中之重,但是这也是基础层面,如何安全、稳定的运行EOS,是作为超级节点以及各个备选节点们重点关注的事情。作为更广大的Dapp开发群体,更关心的还是如何在启动后的主网上开发我们的Dapp(即区块链应用)。
何为智能合约?
智能合约尼克萨博在1996年首次提出的概念:“一个智能合约是一套以数字形式定义的约定,包括合约参与方可以在上面执行这些约定的协议。智能合约的基本思想是,各种各样的合约条款可以嵌入到我们使用的硬件和软件中从而使得攻击者需要很大的代价去攻击。”
而这种自动执行且攻击代价昂贵的智能合约的概念非常适合在区块链上实现。
区块链上的智能合约是一套软件程序,是基于区块链的,并且会在区块链检测到某些特定数据条件下时会触发。我们知道智能合约的概念,一般都是从以太坊上得知的,但是,其实作为区块链“始祖”的比特币,也有智能合约的概念,只不过,比特币的“智能合约”功能比较单一,就是集中于认证新币产生以及旧币在账户间转移。而作为“区块链2.0”的以太坊,将这个只是认证交易的功能,扩大为一个图灵完备的虚拟机,让开发者可以根据功能需求,开发对应的应用程序。不得不说,以太坊能有现在的规模是实至名归的。
2、 关于Web Assembly
Web Assembly,大部分的web开发都应该知道它。WebAssembly 是一种接近机器语言的跨平台二进制格式,它被定义为“精简、加载时间短的格式和执行模型”。本质上来说,它是一种中间代码(字节码),是浏览器都支持的一种代码,编译之后是后缀为“.wasm”的文件。所有其他语言(c, c++, java)编写的程序都可以编程成wasm字节码的程序。也就是说,EOS的智能合约从可能上来说的话,支持C、C++、java等,虽然现在我们能看到的都是C/C++的代码。而且,Web Assembly拥有体积小、解析速度快等特点,单单速度和存储这两点对于区块链来说就是非常重要。
既然是一种执行程序,必然需要编译工具链、和执行环境。EOSIO使用eosiocpp作为编译工具,以及WAVM或者Binaryen作为运行环境。具体过程如下图所示:
- 开发者编写完成C++的智能合约之后, 使用eosiocpp工具将C++编译为wasm的字节码,即.wasm格式的文件(其实,还要生成一个名为abi的文件,之后介绍);
- 开发者将 wasm文件上传至合约账户,即将合约的二进制数据,使用发起交易的方式,发送给eosio节点;
- 节点接收到上传交易,将数据添加至合约账户,并加载到节点的WAVM虚拟机当中;
- 然后,用户在获得合约的调用方法之后,可以发起action交易,调用智能合约相应的函数;
- 节点接收到action交易之后,将调用WAVM中,对应智能合约的功能;
3、HelloWorld的智能合约
上一章提到了编译工具eosiocpp,该工具可以将C/C++的文件,编译为我们需要的wasm文件,本节就来介绍这个工具,编写一个简单的智能合约,以及其生成的文件。
3.1 eosiocpp工具
提到编译工具,你可能会有疑问:eosiocpp和gcc之类的工具有何不同?
其实eosiocpp是一个针对EOSIO平台定制版的,编译和链接std-c和std-c++的编译工具。它剔除了那些对于智能合约没有意义的标准库的某一些部分(比如,套接字和文件系统访问等)。也就是说,它是一个定制的gcc。
3.2 获取eosiocpp
获取eosiocpp,需要从eos的GitHub下载源码:
./git clone https://github.com/EOSIO/eos –recursive
./git submodule update --init --recursive
在源码目录,执行编译脚本:
./ eosio_build.sh
编译通过之后,进入目录build,执行make install
安装完成后,就可以使用eosiocpp了。
(PS: 编译完成之后,还编译得到节点程序nodeos、命令行客户端程序cleos。关于如何使用nodeos启动节点,以及如何使用cleos,在本文章暂时不做介绍,会在其他文章介绍)
3.3 写一个HelloWorld
我们先写一个“经典”的HelloWorld:
/**
* @file hello.cpp
* @author hanfei
*/
#include <eosiolib/eosio.hpp>
#include <eosiolib/print.hpp>
using std::string;
namespace hello {
class hello : public eosio::contract {
public:
using contract::contract;
/// 执行eosiocpp 时生成action
/// @abi action
void printinfo( account_name sender, std::string info ) {
eosio::print( "Print : ", eosio::name{sender}, "->", info );
}
private:
/// 执行eosiocpp 时生成table
/// @abi table
struct message {
account_name sender;
std::string info;
/// 序列化该结构,用于table时候查询
EOSLIB_SERIALIZE( message, (sender)(info) )
};
};
EOSIO_ABI( hello, (printinfo) )
}
从基本上来看,是一个标准的使用C++编写的对象,当然,也有比较特殊的地方:
- 首先,定义继承自contract的智能合约结构体,在public下实现智能合约的action接口,然后在private下实现table等结构(在注释中用@abi,可以使用eosiocpp -g生成abi);
- 其次,在定义结构体的时候,需要使用EOSLIB_SERIALIZE( structname, (param)...),将结构体进行序列化,table存储的时候,才能正常序列化;
- 最后,使用EOSIO_ABI(classname, (action)...),同样序列化操作,在abi生成的时候,正确生成abi;
3.4 生成合约文件
通过执行eosiocpp,我们看到帮助信息:
Usage: /usr/local/bin/eosiocpp -o output.wast contract.cpp [other.cpp ...]
OR
/usr/local/bin/eosiocpp -n mycontract
OR
/usr/local/bin/eosiocpp -g contract.abi types.hpp
Options:
-n | --newcontract [name]
Create a new contract in the [name] folder, based on the example contract
OR
-o | --outname [output.wast] [input.cpp ...]
Generate the wast output file based on input cpp files
The wasm output will also be created as output.wasm
OR
-g | --genabi contract.abi types.hpp
Generate the ABI specification file [EXPERIMENTAL]
- eosiocpp -g:生成后缀为abi智能合约的描述文件,该文件本质是一个json文件,用于描述智能合约的数据结构、action信息、tables;
- eosiocpp -o:生成合约的wast文件;
(关于wast,上面介绍Web Assembly的字节码文件是wasm,其实WebAssembly 除了定义了二进制格式以外,还定义了一份对等的文本描述。官方给出的是线性表示的例子,而 wast 是用 S-表达式(s-expressions) 描述的另一种文本格式。也就是说,wast是非字节码的,人类易读的格式,当然,易读的代价就是比字节码占用空间大。能够生成wasm,还是首推wasm。)
然后,我们就使用工具,生成我们的hello.abi和hello.wast文件:
eosiocpp -g hello.abi hello.cpp //生成abi文件
eosiocpp -o hello.wast hello.cpp //生成wast文件
3.5 部署执行合约
(PS:由于eos网络的启动是个比较复杂的过程,所以,此处的测试只用docker启动创世,并且不加载一些经济模型方面的系统合约,只进行合约测试)
(1) 用GitHub上的教程启动eosio,出现如下打印,即为已经正常启动节点:
nodeosd_1 | 3166632ms thread-0 http_plugin.cpp:369 add_handler ] add api url: /v1/chain/get_producers
keosd_1 | 3166699ms thread-0 http_plugin.cpp:369 add_handler ] add api url: /v1/wallet/set_timeout
keosd_1 | 3166699ms thread-0 http_plugin.cpp:369 add_handler ] add api url: /v1/wallet/sign_digest
nodeosd_1 | 3166632ms thread-0 http_plugin.cpp:369 add_handler ] add api url: /v1/chain/get_required_keys
keosd_1 | 3166699ms thread-0 http_plugin.cpp:369 add_handler ] add api url: /v1/wallet/sign_transaction
nodeosd_1 | 3166632ms thread-0 http_plugin.cpp:369 add_handler ] add api url: /v1/chain/get_table_rows
keosd_1 | 3166699ms thread-0 http_plugin.cpp:369 add_handler ] add api url: /v1/wallet/unlock
nodeosd_1 | 3166632ms thread-0 http_plugin.cpp:369 add_handler ] add api url: /v1/chain/push_block
nodeosd_1 | 3166632ms thread-0 http_plugin.cpp:369 add_handler ] add api url: /v1/chain/push_transaction
nodeosd_1 | 3166632ms thread-0 http_plugin.cpp:369 add_handler ] add api url: /v1/chain/push_transactions
nodeosd_1 | 3166632ms thread-0 producer_plugin.cpp:577 plugin_startup ] producer plugin: plugin_startup() begin
nodeosd_1 | 3166632ms thread-0 producer_plugin.cpp:592 plugin_startup ] Launching block production for 1 producers at 2018-06-06T09:52:46.632.
nodeosd_1 | 3166632ms thread-0 producer_plugin.cpp:604 plugin_startup ] producer plugin: plugin_startup() end
nodeosd_1 | 3166632ms thread-0 history_api_plugin.cpp:38 plugin_startup ] starting history_api_plugin
nodeosd_1 | 3166632ms thread-0 http_plugin.cpp:369 add_handler ] add api url: /v1/history/get_actions
nodeosd_1 | 3166632ms thread-0 http_plugin.cpp:369 add_handler ] add api url: /v1/history/get_controlled_accounts
nodeosd_1 | 3166632ms thread-0 http_plugin.cpp:369 add_handler ] add api url: /v1/history/get_key_accounts
nodeosd_1 | 3166632ms thread-0 http_plugin.cpp:369 add_handler ] add api url: /v1/history/get_transaction
nodeosd_1 | 3166632ms thread-0 net_plugin.cpp:2920 plugin_startup ] starting listener, max clients is 13
nodeosd_1 | 3167000ms thread-0 producer_plugin.cpp:1073 produce_block ] Produced block 00000002937bd333... #2 @ 2018-06-06T09:52:47.000 signed by eosio [trxs: 0, lib: 0, confirmed: 0]
nodeosd_1 | 3167502ms thread-0 producer_plugin.cpp:1073 produce_block ] Produced block 000000033564eb2b... #3 @ 2018-06-06T09:52:47.500 signed by eosio [trxs: 0, lib: 2, confirmed: 0]
nodeosd_1 | 3168001ms thread-0 producer_plugin.cpp:1073 produce_block ] Produced block 00000004b924c0d7... #4 @ 2018-06-06T09:52:48.000 signed by eosio [trxs: 0, lib: 3, confirmed: 0]
nodeosd_1 | 3168500ms thread-0 producer_plugin.cpp:1073 produce_block ] Produced block 00000005a81c0f54... #5 @ 2018-06-06T09:52:48.500 signed by eosio [trxs: 0, lib: 4, confirmed: 0]
(2) 我们先用官方提供的命令行程序cleos,创建钱包,下面输出是钱包密码:
$ cleos wallet create
Creating wallet:
Save password to use in the future to unlock
Without password imported keys will
"PW5J4kT9mug7W9G5qpLrKYyenEpLEQ7AQeg5j9T4czXfJxjzWMpZ8"
(3) 用create key 创建的公私钥对,然后将私钥导入钱包;
$cleos wallet import 个人私钥
imported private key for: EOS66Cweq7FQK3SeAXScSVo6nCx3qo5buRWpW2Y5EUJmGBkQRwgkf
(4) 之后,就可以使用eosio的创世账户,创建新账户“hello”,并将公钥与其绑定;
$ cleos create account eosio hello EOS66Cweq7FQK3SeAXScSVo6nCx3qo5buRWpW2Y5EUJmGBkQRwgkf EOS66Cweq7FQK3SeAXScSVo6nCx3qo5buRWpW2Y5EUJmGBkQRwgkf
executed transaction: d691f531b89accf5f58d47412345c985a73f1390463b211a67eb561c74ad803e 200 bytes 178 us
# eosio <= eosio::newaccount {"creator":"eosio","name":"hello","owner":{"threshold":1,"keys":[{"key":"EOS66Cweq7FQK3SeAXScSVo6nCx...
warning: transaction executed locally, but may not be confirmed by the network yet
(5) 如下命令,就是上传智能合约的命令行,参数格式为:cleos set contract “合约账户” “合约文件路径” -p 上传者的权限
$ cleos set contract hello /opt/eosio/bin/data-dir/contracts/hello -p hello
Reading WAST/WASM from /opt/eosio/bin/data-dir/contracts/hello/hello.wasm...
Using already assembled WASM...
Publishing contract...
executed transaction: f303f9650ef8aec87a8df5f0298d95736863ba0544247bc540a03f0deae3c50d 2824 bytes 458 us
# eosio <= eosio::setcode {"account":"hello","vmtype":0,"vmversion":0,"code":"0061736d01000000013c0c60037f7e7f006000006000017e...
# eosio <= eosio::setabi {"account":"hello","abi":"0e656f73696f3a3a6162692f312e300002076d65737361676500020673656e646572046e61...
warning: transaction executed locally, but may not be confirmed by the network yet
(6) 执行合约的命令行,其中,hello就是合约账户,printinfo是需要调用的方法,单引号之内的,就是方法需要的参数,最后是提交合约需要的签名的账户;
$cleos push action hello printinfo '["hello", "Hello World!"]' -p hello
executed transaction: 7afb3c20c1b038a93cda8d536fbe17b480a8ecdb18e451aff251d140d96c91a1 120 bytes 262 us
# hello <= hello::printinfo {"sender":"hello","info":"Hello World!"}
>> Print : hello->Hello World!
我们可以看到,成功执行合约,打印了“hello->Hello World!”。
4、总结
以上就是对EOS的智能合约的一个简单介绍,关于智能合约还有很多东西没有介绍,比如abi文件的详细信息、合约的数据存储、合约间调用等等。
除了合约以外,eos节点启动、运行模式、资源控制等等,还有太多需要研究的东西,在之后的文章,我们一起研究。
SIC官网:http://sichain.io
SIC高级工程师 zero
Congratulations @sichain! You received a personal award!
You can view your badges on your Steem Board and compare to others on the Steem Ranking
Vote for @Steemitboard as a witness to get one more award and increased upvotes!