写在前面的话
看EOS源码,就像穿越千座大山,感觉已经搞懂了一个地方,但是随之而来的是更大的谜团。
公司要求我了解智能合约的相关原理,我专心用EOS的合约作为调研对象,但是到现在为止,还是有许多很大的谜团在等待着我:
1.EOS合约的结构是怎样的,有什么是必须的,什么是可选的,为什么必须的就是的必须?
2.我们知道,合约通过eosio-cpp 编译成wasm格式和可读模式abi文件,但是编译的细节又是怎样?
3.合约是怎么部署的,部署后的文件是什么类型,存储在哪里,存储的内容又是什么?是怎么生成一个hash码的?
4.怎么调用合约,合约之间的接口又是什么形式存储。
5.更改合约内容后如何重新部署
6.如何快速升级合约,是否需要升级合约?
等等。
所以,不得不去看源码,但是如何找到线头可以更快的去了解这些东西,这是非常重要的,本文旨在尝试通过笔者特有的角度试着解答这些谜题。
EOS关联的技术确实很多很多,多到不可能一个人能够知道所有,谁敢说他精通LLVM的同时,对CMAKE也非常了解,即便对CMAKE也很精通,但是C++11,C++14甚至C++17呢?
或许更大的谜题是Boost,是wasm。我没法解决这些问题,因此当我看到有这些相关的知识点时,我只能试着避过它们,对他们实现的细节,性能等我不去深究,我只在我看到的地方大概知道是干嘛的就可以了,我相信,别人也是这样干的。好了,我们一起来深究智能合约的谜团吧。
从什么地方开始?我试着从链和块的生成开始
链和块是如何生成的
通过我之前的文档,我们已经知道EOS是如何部署的,有关细节请参考:
eosio_build.sh 执行过程,eosio_build_centos.sh执行过程,eosio_install.sh执行过程
这里面已经完全安装了工具包和eos软件,也就是说,当执行完eosio_build.sh 和 eosio_install.sh后,一切都已经准备好了,包括LLVM,包括Boost,也许还包括MongoDB。但是还有些东西没有准备好,比如wasm 虚拟机,比如block和chain,看过官方文档的人知道,要把这一切准备好,只需要执行这样的命令:
nodeos -e -p eosio \ --plugin eosio::producer_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是管理节点的,在${WORKSPACE}\programs\nodeos目录下,这里面有关于nodeos的所有内容,先从CMakeLists.txt文件入手,第一行就是这个
add_executable( ${NODE_EXECUTABLE_NAME} main.cpp )
其中${NODE_EXECUTABLE_NAME}就是nodeos,这个只要ctrl+r就知道了,透露下,这里的定义是在主CMakeLists.txt里面的,cleos和keosd都是在主CMakeLists.txt定义,内容如下
set( CLI_CLIENT_EXECUTABLE_NAME cleos )
set( NODE_EXECUTABLE_NAME nodeos )
set( KEY_STORE_EXECUTABLE_NAME keosd )
ps:
add_executable:引入一个名为< name>的可执行目标,该目标会由调用该命令时在源文件列表中指定的源文件来构建
set:用来显式的定义变量
也就是说,当使用nodeos命令时,会执行main.cpp里面的方法,也就是执行main方法,现在开始分析main方法,关键代码如下
app().set_version(eosio::nodeos::config::version); auto root = fc::app_path(); app().set_default_data_dir(root / "eosio" / nodeos::config::node_executable_name / "data" ); app().set_default_config_dir(root / "eosio" / nodeos::config::node_executable_name / "config" ); http_plugin::set_defaults({ .default_unix_socket_path = "", .default_http_port = 8888 }); if(!app().initialize<chain_plugin, net_plugin, producer_plugin>(argc, argv)) return INITIALIZE_FAIL; initialize_logging(); ilog("${name} version ${ver}", ("name", nodeos::config::node_executable_name)("ver", app().version_string())); ilog("${name} using configuration file ${c}", ("name", nodeos::config::node_executable_name)("c", app().full_config_file_path().string())); ilog("${name} data directory is ${d}", ("name", nodeos::config::node_executable_name)("d", app().data_dir().string())); app().startup(); app().exec();