区块链中文技术社区 - EOS源码分析
type: channel
link: https://www.bcskill.com/index.php/tag/EOS源码分析/
items: EOS源码分析之八区块及数据结构
items: EOS源码分析之七钱包和帐户
items: EOS源码分析之六共识
items: EOS源码分析之五虚拟机
items: EOS源码分析之四智能合约
items: EOS源码分析之三交易
items: EOS源码分析之二网络
items: EOS源码分析之一整体介绍

EOS源码分析之八区块及数据结构
type: item
date: 2019-05-15T12:27:46+08:00
description: eos源码分析之八区块及数据结构 做为EOS系列的最后一篇，把区块及相关的数据结构分析一下。虽然在前面的共识中分析过出块这部分，但对EOS的区块结构及一些细节并没有深入进去。 一、区块 EOS的区块设计不同的版本变化很大，这里以4.0的为模板分析，先看一下它的数据结构： struct block_header { block_timestamp_type timestamp; account_name producer;//帐户标识符 13字节 uint16_t confirmed = 1; block_id_type previous;//前一块的HASH checksum256_type transaction_mroot; /// mroot of cycles_summary checksum256_type action_mroot; /// mroot of all delivered action receipts uint32_t schedule_version = 0; optional<producer_schedule_type> new_producers;//新生产者 extensions_type header_extensions; digest_type digest()const;//摘要哈希 block_id_type id() const; //自己的哈希 uint32_t block_num() const { return num_from_id(previous) + 1; } static uint32_t num_from_id(const block_id_type& id);//ID是任意数字，区块号是从零长到现在的排序号 ID=HASH+n }; struct signed_block_header : public block_header { signature_type producer_signature;//生产者签名 }; struct signed_block : public signed_block_header { using signed_block_header::signed_block_header; signed_block() = default; signed_block( const signed_block_header& h ):signed_block_header(h){} vector<transaction_receipt> transactions; /// new or generated transactions交易记录 extensions_type block_extensions;//扩展区 }; using signed_block_ptr = std::shared_ptr<signed_block>;//重定义一个新的数据类型，方便使用 这里感觉最大的不同是把原来的交易ID直接弄成了交易内容，这样有点简单粗暴的感觉，但是确实是容易理解一些。区块的生产在前面选举后分析过，这里不再赘述，看一下产生区块中对交易的处理。 二、交易和上链 正如所有的区块链一样，交易最终打包入区块，才是真正的区块成功能，也就是说，区块生产出来的目的不是单纯生产块，而要把交易数据打包进去，然后再保存到数据库，最终上链。 1、交易 transaction_trace_ptr push_transaction( const transaction_metadata_ptr& trx, fc::time_point deadline, bool implicit, uint32_t billed_cpu_time_us ) { FC_ASSERT(deadline != fc::time_point(), "deadline cannot be uninitialized"); transaction_trace_ptr trace;//交易检索 try { transaction_context trx_context(self, trx->trx, trx->id); //交易管理控制 trx_context.deadline = deadline; trx_context.billed_cpu_time_us = billed_cpu_time_us; trace = trx_context.trace; try { if( implicit ) { trx_context.init_for_implicit_trx(); } else { trx_context.init_for_input_trx( trx->packed_trx.…

EOS源码分析之七钱包和帐户
type: item
date: 2019-05-15T12:26:48+08:00
description: eos源码分析之七钱包和帐户 一、EOS的钱包帐户 EOS的钱包其实主要就是管理密钥对，因为他不负责产生地址，也就是说，不会像以前的以太坊或者比特币，要通过密钥来产生钱包地址。它主要是提供对帐户的签名管理，也就是前面说的签名需要的密钥进行管理。 EOS使用是非UTXO机制，即帐户机制，这点和以太坊相同，但是他们又有不同之处，EOS为了使用安全方便，引入了权限和角色的功能。通过不同的帐户和私钥进行组合，可以达到创建不同的权限的帐户动作。举一个例子，你可以把你自己的帐户处理动作分配给任意的人，那么那个人就拥有了你的所有的帐户动作，但是它仍然是使用自己的密钥对来对你分配的动作进行签名。 要创建帐户，首先要创建钱包，因为创建帐户需要创建钱包时产生的密钥对。 //创建钱包 string wallet_name = "default"; auto createWallet = wallet->add_subcommand("create", localized("Create a new wallet locally"), false); createWallet->add_option("-n,--name", wallet_name, localized("The name of the new wallet"), true); createWallet->set_callback([&wallet_name] { // wait for keosd to come up try_port(uint16_t(std::stoi(parse_url(wallet_url).port)), 2000); const auto& v = call(wallet_url, wallet_create, wallet_name); std::cout << localized("Creating wallet: ${wallet_name}", ("wallet_name", wallet_name)) << std::endl; std::cout << localized("Save password to use in the future to unlock this wallet.") << std::endl; std::cout << localized("Without password imported keys will not be retrievable.") << std::endl; std::cout << fc::json::to_pretty_string(v) << std::endl; }); //因为创建帐户需要创建钱包时产生的密钥对 // create key create->add_subcommand("key", localized("Create a new keypair and print the public and private keys"))->set_callback( [](){ auto pk = private_key_type::generate(); auto privs = string(pk); auto pubs = string(pk.get_public_key()); std::cout << localized("Private key: ${key}", ("key", privs) ) << std::endl; std::cout << localized("Public key: ${key}", ("key", pubs ) ) << std::endl; }); //创建帐户 struct create_account_subcommand { string creator; string account_name; string owner_key_str; string active_key_str; string stake_net; string stake_cpu; uint32_t buy_ram_bytes_in_kbytes = 0; string buy_ram_eos; bool transfer; bool simple; create_account_subcommand(CLI::App* actionRoot, bool s) : simple(s) { auto createAccount = actionRoot->add_subcommand( (simple ? "account" : "newaccount"), localized("Create an account, buy ram, stake for…

EOS源码分析之六共识
type: item
date: 2019-05-15T12:25:41+08:00
description: eos源码分析之六共识 一、EOS使用的共识 EOS使用的是与传统的共识方法不同的DPOS共识机制，而且在最新的版本中已经更改为了BFT-DPOS机制，在网上看到BM说他又找到了一种更新的共识机制，可以解决被超级节点控制的问题，不知道最终会是什么样子，在比特币和以太坊都使用POW的共识的前提下，EOS使用DPOS机制，可以说是解决高并发的一个比较好的方法。但是，DPOS机制很容易由于节点太少被攻击，事实上也是如此。那么什么是DPOS呢？EOS是怎么使用其进行块之间的共识的呢？ 提到dpos,就不得不提到pos,PoS全称Proof of Stake，意为权益证明。说得直白一些就是谁存款多，存款时间长，谁就有权出块（记帐）。这个解决了POW一个痛点，即它不用挖矿，所以也不用耗费老多的电能。但是这这个算法有个致命问题，资本决定了一切，所以很容易被有钱人垄断。 DPOS比POS多了一个D，它的意义是授权，委托。二者的区别是，DPOS需要POS的持有者来通过选举代表，由代表实现出块。而在EOS中则有21个出块者（BP,BlcokProducer），或者叫超级节点。还有101个备份节点。当21个BP的15个确认交易后，交易即不可逆转。 二、共识的过程 1、初始化的共识 EOS初始启动是外在选举的21个超级节点，所以不涉及代码部分。但是一旦启动后会开始新的节点选举，选举成功后，将进行BFT-DPOS共识。 2、选举 主要的代码在contracts/social和eosio.system/voting.cpp中。在cleos的main.cpp中会发现几个数据结构体和相关的应用： auto registerProducer = register_producer_subcommand(system); auto unregisterProducer = unregister_producer_subcommand(system); auto voteProducer = system->add_subcommand("voteproducer", localized("Vote for a producer")); voteProducer->require_subcommand(); auto voteProxy = vote_producer_proxy_subcommand(voteProducer); auto voteProducers = vote_producers_subcommand(voteProducer); auto approveProducer = approve_producer_subcommand(voteProducer); auto unapproveProducer = unapprove_producer_subcommand(voteProducer); auto listProducers = list_producers_subcommand(system); auto delegateBandWidth = delegate_bandwidth_subcommand(system); auto undelegateBandWidth = undelegate_bandwidth_subcommand(system); auto listBandWidth = list_bw_subcommand(system); 这些代码会驱动程序在启动后进行相应的动作。选举在EOS中其实也分成两类，即每人独自发起选举，也可以通过代理人代替自己选举，但结果就是本人就无法再投票了。相应的代码如下： /** * @pre producers must be sorted from lowest to highest and must be registered and active * @pre if proxy is set then no producers can be voted for * @pre if proxy is set then proxy account must exist and be registered as a proxy * @pre every listed producer or proxy must have been previously registered * @pre voter must authorize this action * @pre voter must have previously staked some EOS for voting * @pre voter->staked must be up to date * * @post every …

EOS源码分析之五虚拟机
type: item
date: 2019-05-15T12:24:47+08:00
description: eos源码分析之五虚拟机 因为6月2日，blockone团队发布了上线的源码，所以从这里开始基于最新的1.01版本来分析。 一、虚拟机的模块 虚拟机的代码主要分散在了以下几个目录， 主要在智能合约目录contracts，一些辅助的ABI的源码,区块链目录library/chain，是一些编译的接口，library/wasm-jit目录下，是主要的文件部分，然后在externals/src下也有相当一部分的二进制编译代码。其它一些目录下也有相关的一些文件，但比较分散代码也很少。重点分析编译过程。 虚拟机的模块分成两部分，也就是编译部分和执行部分。智能合约在编译过程中会产生两个文件，一个是.wast,一个是.abi文件。 二、编译过程 1、wast文件的生成 eoscpp是编译智能合约的命令，在tools目录下，eosiocpp.in中： function build_contract { set -e workdir=`mktemp -d` if [[ ${VERBOSE} == "1" ]]; then PRINT_CMDS="set -x" fi ($PRINT_CMDS; mkdir $workdir/built) for file in $@; do name=`basename $file` filePath=`dirname $file` ($PRINT_CMDS; @WASM_CLANG@ -emit-llvm -O3 --std=c++14 --target=wasm32 -nostdinc \ -nostdlib -nostdlibinc -ffreestanding -nostdlib -fno-threadsafe-statics -fno-rtti \ -fno-exceptions -I ${EOSIO_INSTALL_DIR}/include \ -I${EOSIO_INSTALL_DIR}/include/libc++/upstream/include \ -I${EOSIO_INSTALL_DIR}/include/musl/upstream/include \ -I${BOOST_INCLUDE_DIR} \ -I $filePath \ -c $file -o $workdir/built/$name ) done ($PRINT_CMDS; @WASM_LLVM_LINK@ -only-needed -o $workdir/linked.bc $workdir/built/* \ ${EOSIO_INSTALL_DIR}/usr/share/eosio/contractsdk/lib/eosiolib.bc \ ${EOSIO_INSTALL_DIR}/usr/share/eosio/contractsdk/lib/libc++.bc \ ${EOSIO_INSTALL_DIR}/usr/share/eosio/contractsdk/lib/libc.bc ) ($PRINT_CMDS; @WASM_LLC@ -thread-model=single --asm-verbose=false -o $workdir/assembly.s $workdir/linked.bc) ($PRINT_CMDS; ${EOSIO_INSTALL_DIR}/bin/eosio-s2wasm -o $outname -s 16384 $workdir/assembly.s) ($PRINT_CMDS; ${EOSIO_INSTALL_DIR}/bin/eosio-wast2wasm $outname ${outname%.\*}.wasm -n) ($PRINT_CMDS; rm -rf $workdir) set +e } 首先调用了 @WASM_CLANG@ -emit-llvm -O3的编译，这和安装LLVM和CLANG有必然的关系。然后它会调用相关的链接库，关键还是最后几行代码： bin/eosio-s2wasm和bin/eosio-wast2wasm。 从这里基本已经看出LLVM还是要和EOS内部的一些代码一起工作，才能搞定所有的流程。主要的编译工作由LLVM及其相关的模块构成，在这个过程中使用了一种叫做C++ without Emscripten的过程即：直接用 clang 的前端编译到 LLVM 的 bc，然后 llc 编译到汇编文件 s，再用 Binaryen 的工具 s2wasm 从汇编文件编译到 wasm 的 ast 文件 wast，最后用 wasm-as 编译到 wasm。 可能为了数据的通用性和更好的适配性，编译过程中的许多文件都提供了相关工具命令可以来回转换，比如a.ll和a.b…

EOS源码分析之四智能合约
type: item
date: 2019-05-15T12:24:00+08:00
description: eos源码分析之四智能合约 智能合约和虚拟机部分会混合在一起讲，然后在各自的范围内偏向于哪个部分。 一、一个简单智能合约 智能合约的编译使用WASM来编译，也使用了一些自定义的代码用来固定智能合约的格式和入口等。智能合约产生二进制后会放到虚拟机中执行。首先看一个入门的智能合约，helloworld. hello.cpp： #include<eosiolib/eosio.hpp> #include<eosiolib/print.hpp> usingnamespace eosio; class hello :public eosio::contract { public:using contract::contract; /// @abi action void helloworld( account_name user ) { print( "Hello, ", name{user} ); } }; EOSIO_ABI( hello, (hi) ) 在EOS的源码中最EOSIO_ABI被定义成： #define EOSIO_ABI( TYPE, MEMBERS ) \ extern "C" { \ void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \ auto self = receiver; \ if( code == self ) { \ TYPE thiscontract( self ); \ //注意这个变量,后面会引用 switch( action ) { \ EOSIO_API( TYPE, MEMBERS ) \ } \ eosio_exit(0); \ } \ } \ } \ 这时候再对照一下EOS自带的一个空的智能合约的例子： //noop.hpp #pragma once #include <eosiolib/eosio.hpp> #include <eosiolib/dispatcher.hpp> namespace noop { using std::string; /** noop contract All it does is require sender authorization. Actions: anyaction*/ class noop { public: ACTION(N(noop), anyaction) { anyaction() { } anyaction(account_name f, const string& t, const string& d): from(f), type(t), data(d) { } account_name from; string type; string data; EOSLIB_SERIALIZE(anyaction, (from)(type)(data)) }; static void on(const anyaction& act) { require_auth(act.from); } }; } /// noop //noop.cpp #include <noop/noop.hpp> namespace noop { extern "C" { /// The apply method implements the dispatch of events to this contract void apply( uint64_t receiver, uint64_t code, uint64_t action ) { eosio::dispatch<noop, noop::anyaction>(code, action); } } } 通过二者的对比可以发现，其实宏EOSIO_ABI自动完成了对action的映射分发。而EOS自带的则手动实现了静态分发，结果是一样的。它们的核心其实都是apply这个函数，如果有std::bind的使用经验，发现他们还是有些类似的。 继续接着分析EOSIO_ABI的内部代码，里面调用了一个宏： #define EOSIO_API_CALL( r, OP, elem ) \ case ::eosio::string_to_name( BOOST_PP_STRINGIZE(elem) ): \ eosio::execute_action( &thiscontract, &OP::elem ); \ return; #define EOSIO_API( TYPE, MEMBERS ) \ BOOST_PP_SEQ_FOR_EACH( EOSIO_API_CALL, TYPE, MEMBERS ) BOOST_PP_SEQ_FOR_EA…

EOS源码分析之三交易
type: item
date: 2019-05-15T12:23:04+08:00
description: eos源码分析之三交易 一、交易的介绍 说明：最新的1.0及以上代码中已经移除了相关的cycle和shared部分 基本上区块链都是这几板斧，又轮到交易了。一般交易都是最复杂的部分，因为它涉及到网络，帐户，数据库，共识，内存池等等好多部分。EOS的交易数据结构主要有两种： 1、signed_transaction：用户发起的交易 2、deferred_transaction：延期交易，注释的说明这种交易可以返回错误给当事人。 EOS为了应对海量的交易，引入分片（shard）技术，即在区块中维护了一条私有区块链，将一个block分割成多个cycle（循环），每个cycle的生成时间很短，而且不用等待完整的block确认完成（3秒），生成后直接异步广播发送，这样，交易在很快就被确认了。在一个cycle中，如果存在有大量互不想干的交易，但么多核多线程技术将极大的提高交易的处理速度 。 这次仍然从客户端发起一笔交易开始来查看整个代码的流程，基本上是cleos发出Transaction到nodeos的HTTP接口，接口接收到消息，分析打包。 二、交易的具体过程 1、客户端发起交易： 无论是send_actions 还是 send_transaction最终都落到push_transaction fc::variant push_transaction( signed_transaction& trx, int32_t extra_kcpu = 1000, packed_transaction::compression_type compression = packed_transaction::none ) { auto info = get_info(); trx.expiration = info.head_block_time + tx_expiration; trx.set_reference_block(info.head_block_id); if (tx_force_unique) { trx.context_free_actions.emplace_back( generate_nonce() ); } auto required_keys = determine_required_keys(trx); size_t num_keys = required_keys.is_array() ? required_keys.get_array().size() : 1; trx.max_kcpu_usage = (tx_max_cpu_usage + 1023)/1024; trx.max_net_usage_words = (tx_max_net_usage + 7)/8; if (!tx_skip_sign) { //生成交易 sign_transaction(trx, required_keys); } if (!tx_dont_broadcast) { //调用HTTP接口,packed_transaction打包交易 return call(push_txn_func, packed_transaction(trx, compression)); } else { return fc::variant(trx); } } void sign_transaction(signed_transaction& trx, fc::variant& required_keys) { // TODO determine chain id fc::variants sign_args = {fc::variant(trx), required_keys, fc::variant(chain_id_type{})}; const auto& signed_trx = call(wallet_host, wallet_port, wallet_sign_trx, sign_args); trx = signed_trx.as<signed_transaction>(); } 看到调用call，那么进去： template<typename T> fc::variant call( const std::string& server, uint16_t port, const std::string& path, const T& v ) { return eosio::client::http::call( server, port, path, fc::variant(v) ); } template<typename T> fc::variant call( const std::string& path, const T& v…

EOS源码分析之二网络
type: item
date: 2019-05-15T12:22:02+08:00
description: ﻿# eos源码分析之二网络 一、网络的初始化和启动 P2P网络是区块链的运行的基础模块，在EOS中，主要就是net_plugin,http_plugin,net_pai_plugin,当然在这个过程中网络也会引用到其它的一些模块的接口，但为了清晰，重点介绍网络相关部分，其它略过。 首先看一下网络插件生成的时候的代码： net_plugin::net_plugin() :my( new net_plugin_impl ) { my_impl = my.get();//此处写得不是太好，从智能指针又退化回到 普通指针 } 可以看到，它生成了一个net_plugin_impl的实例，真正的网络操作相关的代码其实在这个类中，看名字也可以明白，JAVA接口经常这么干。然后接着按Main函数中的初始化来看： void net_plugin::plugin_initialize( const variables_map& options ) { ......//日志相关忽略 //初始化相关参数，版本，是否发送完整块，交易周期等 my->network_version = static_cast<uint16_t>(app().version()); my->network_version_match = options.at("network-version-match").as<bool>(); my->send_whole_blocks = def_send_whole_blocks; my->sync_master.reset( new sync_manager(options.at("sync-fetch-span").as<uint32_t>() ) ); my->big_msg_master.reset( new big_msg_manager ); my->connector_period = std::chrono::seconds(options.at("connection-cleanup-period").as<int>()); my->txn_exp_period = def_txn_expire_wait; my->resp_expected_period = def_resp_expected_wait; my->big_msg_master->just_send_it_max = def_max_just_send; my->max_client_count = options.at("max-clients").as<int>(); my->num_clients = 0; my->started_sessions = 0; //使用BOOST的resolver来处理与网络相关的数据格式的转换 my->resolver = std::make_shared<tcp::resolver>( std::ref( app().get_io_service() ) ); //根据options设置来设置相关配置 if(options.count("p2p-listen-endpoint")) { my->p2p_address = options.at("p2p-listen-endpoint").as< string >(); auto host = my->p2p_address.substr( 0, my->p2p_address.find(':') ); auto port = my->p2p_address.substr( host.size()+1, my->p2p_address.size() ); idump((host)(port)); tcp::resolver::query query( tcp::v4(), host.c_str(), port.c_str() ); // Note: need to add support for IPv6 too? //得到监听地址 my->listen_endpoint = \*my->resolver->resolve( query); //重置boost socket网络接收器 my->acceptor.reset( new tcp::acceptor( app().get_io_service() ) ); } if(options.count("p2p-server-address")) { my->p2p_address = options.at("p2p-server-address").as< string >(); } else { if(my->listen_endpoint.address().to_v4() == address_v…

EOS源码分析之一整体介绍
type: item
date: 2019-05-15T12:20:22+08:00
description: EOS整体介绍 一、EOS的插件式设计 EOS中，虽然编程的复杂度和设计较比特币大幅提高，但其核心的思想其实并没有多大改变，目前来看，仍然以BOOST的signal,boost::asio的信号消息机制来完成模块间的解耦。相比比特币来言，做得更优雅，封装也更良好。 先看一下插件设计的整体类图： 从上面的类图可以清楚的看到，整个插件的依赖和传导机制。然后在下面的流程分析中会详细说明一下它的具体的应用。 二、EOS的整体流程 EOS的版本做了一次比较大的更迭，至少从形式上看是，它的生成路径下，完成了以下几个目标： cleos:客户端，用来处理和区块链通信。帐户钱包等的管理。 eosio-abigen:二进制ABI的生成程序。 eosio-launcher:简化了eosd节点跨局域网或者跨更宽泛的网络的分布。 keosd:钱包和帐户的实现控制程序 nodeos:核心的节点程序，这个和老版本不一样了，至少名字不一样了。 一般情况下会启动cleos调用keosd来创建帐户和钱包。然后通过启动nodeos来产生节点，进行通信并根据配置生成区块和验证。进入重点，直接看一下 nodeos的创建代码： int main(int argc, char** argv) { try { app().set_version(eosio::nodeos::config::version); auto root = fc::app_path(); app().set_default_data_dir(root / "eosio/nodeos/data" ); app().set_default_config_dir(root / "eosio/nodeos/config" ); //这里直接初始化了四个插件 if(!app().initialize<chain_plugin, http_plugin, net_plugin, producer_plugin>(argc, argv)) return -1; initialize_logging(); ilog("nodeos version ${ver}", ("ver", eosio::nodeos::config::itoh(static_cast<uint32_t>(app().version())))); ilog("eosio root is ${root}", ("root", root.string())); app().startup(); app().exec(); } catch (const fc::exception& e) { elog("${e}", ("e",e.to_detail_string())); } catch (const boost::exception& e) { elog("${e}", ("e",boost::diagnostic_information(e))); } catch (const std::exception& e) { elog("${e}", ("e",e.what())); } catch (...) { elog("unknown exception"); } return 0; } 代码看上去并不多，当然，比之比特币最新中的几行代码来看，还是要稍有复杂的感觉，但是还可以承受，不过，随后可能c++技能的消耗水平会极剧增加。忽略开前几行的相关文件配置直接进行初始化代码看看去。 template<typename... Plugin> bool initialize(int argc, char** argv) { return initialize_impl(argc, argv, {find_plugin<Plugin>()...}); } 没啥，一个向量的初始化。不过有一个变参模板，如果想深入学习的得去看看相关资料。 bool application::initialize_impl(int argc, char** argv, vector<abstract_plugin*> autostart_plugins) { set_program_options();//设置命令选项 bpo::variables_map options;//声明保存结果变量 bpo::store(bpo::parse_command_line(argc, argv, my->_app_options), options);//分析参数并保存 if( options.count( "help" ) ) { cout << my->_app_options << std::endl; return false; } ...... //分析配置文件 bpo:…