在windows上安装运行EOS
原文地址:https://steemit.com/eos/@tokenika/installing-and-running-eos-on-windows
如果你是一名windows c++开发者,你最喜欢的ide是ms visual studio,那么你可能就想知道能不能直接在windows上编译eos代码。
我们在windows c++编译器上做了几次试验,结果不行。虽然eos的代码有win32的标签,但主要的问题在于,它前后不一致。而且,windows的clang编译器和unix的clang编译器有些不同,它的限制更多。比如,像std::move(u8"env")这样的表达式在windows下是无效的。
不过,我们找到了一个替代的解决办法: Windows Subsystem for Linux,它捆绑了 Visual Studio Code ide。待会你会看到,用这个方法比直接在windows下编译代码更好。

准备工具
你需要 Windows 10, 版本1703, 也就是 Creators Update。想验证你的windows版本,你可以打开设置面板,然后进入系统>关于 进行查看。
如果你用的是更早的版本,而且不想升级到1703版,你仍然可以试一试,不过你要把你的Windows Subsystem for Linux从ubuntu 14 升级到ubuntu 16.
Windows Subsystem for Linux只能安装到系统盘。如果你的系统盘空间不够(Windows Subsystem for Linux需要3-4G的空间),那你可以使用这里的工具拓展你的系统分区。
动手
首先,我们要启动Windows Subsystem for Linux,然后在visual studio里使用bash 命令行界面。
Windows Subsystem for Linux
官方的Windows Subsystem for Linux安装指南在这里可以找到。不过,我们提供了下面的简化版本:
- 在管理员模式下(右击然后选择以管理员运行/Run as Administrator)打开PowerShell,然后执行以下命令:Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux
- 按照提示重启计算机。
- 打开开发者模式
- 打开设置面板,进入升级&安全>开发者 页面
- 选择开发者模式Developer Mode单选按钮
- 如果有提示,则重启计算机。
- 打开windows 命令提示器,输入bash。接受证书协议,然后会下载一张用户模式图片,并提取到%localappdata%\lxss\。然后会提示你设置linux用户名和密码。当这步骤结束的时候,快捷名称Bash on Ubuntu on Windows会被加入到你的开始菜单。
- 使用下面任一方式启动一个新的ubuntu shell:
- 在命令提示器内输入bash,或者
- 在开始菜单里使用Bash on Ubuntu on Windows。
- 一旦你进入了linux shell,要确保你运行了ubuntu 16:
lsb_release -a - 最后,升级 ubuntu:
sudo apt update sudo apt full-upgrade
重装ubuntu
下面的步骤是可选的,不一定非做不可。只有在你需要从头开始,重装ubuntu的时候才需要做。
- 打开windows 命令行提示器,运行:
lxrun /uninstall /full 进行卸载 lxrun /install 进行重装 - 运行bash,启动新的ubuntu shell,然后在这个新的ubuntu shell里进行更新和升级:
sudo apt update sudo apt full-upgrade
Visual Studio Code
从官网下载并安装visual studio code
https://code.visualstudio.com/Download 要想在visual studio code 内启用ubuntu bash 控制台,你需要修改用户设置:
- 进入 File > Preferences > User Settings ,用户>偏好设置>用户设置。
- 在右边面板里添加如下内容,覆盖掉默认设置:"terminal.integrated.shell.windows": "C:\Windows\sysnative\bash.exe".
- 保存修改,你就可以使用Ctrl + ' 或者 View > Integrated Terminal切换到ubuntu 控制台。
你还可以添加c++拓展到visual studio code,这对c++开发有帮助。使用Ctrl + Shift + X打开拓展面板,然后添加以下拓展:
- C/C++
- C++ Intelisense
- CMakeTools
- CMake Tools Helper
- Code Runner
编译源码
在windows文件系统上为EOS创建一个workspace 文件夹。在这篇文章中,我们使用X:\Workspaces\EOS,不过你可以自己决定你想在哪建这个workspace文件夹。
在linux 文件系统中,上面的地址会被映射为/mnt/x/Workspaces/EOS。它会相应地映射你设置的地址。
注意:使用分区名称的小写字母形式,比如我们使用的是/mnt/x/。
到了这一步,你就可以开始使用visual studio code的ubuntu shell了(View > Integrated Terminal)。它的主要优势是它能方便地复制黏贴。
下面的所有命令都是在ubuntu shell上运行的。
- 定义以下系统变量:
export WORKSPACE_DIR=/mnt/x/Workspaces/EOS export EOSIO_INSTALL_DIR=${WORKSPACE_DIR}/eos export EOS_PROGRAMS=${EOSIO_INSTALL_DIR}/build/programs export TEMP_DIR=/tmp注意:把x/Workspaces/EOS 替换成你设置的workspace地址。
- 把上面设置的系统变量保存到~/.bashrc文件:
echo "export WORKSPACE_DIR=${WORKSPACE_DIR}" >> ~/.bashrc echo "export EOSIO_INSTALL_DIR=${EOSIO_INSTALL_DIR}" >> ~/.bashrc echo "export EOS_PROGRAMS=${EOS_PROGRAMS}" >> ~/.bashrc - 安装 cmake 和git:
sudo apt install cmake sudo apt install git4.从EOS代码仓库clone 源代码:
cd ${WORKSPACE_DIR} git clone https://github.com/eosio/eos --recursive5.现在,你就可以编译源码了。这一步可能需要几个小时时间,取决于你计算机的配置,而且,这期间会要求你确认某些动作,以及输入sudo密码。
cd ${EOSIO_INSTALL_DIR} ./eosio_build.sh注意:由于这一步需要从多个地方下载文件,它有可能会失败。如果失败的话,就重新开始这一步。
运行
如果以上的步骤没有什么错,你就编译好了EOS代码,它生成了多个文件和可执行文件。可执行文件放在$EOS_PROGRAMS文件夹:
- nodeos: 区块链服务器节点生成组建
- cleos: 和区块链交互的接口命令
- keosd: EOS 钱包
- eosio-launcher:节点网络组成和部署的应用
此贴为转载复制占坑,等稍后 重新整理下。
!wast.empty(): no wast file found eosio.token/eosio.token.wast
今天更新EOS到 release 1.1 重新编译后,发现执行eosio.token合约时提示
surou@surou-C-H110M-K-Pro:~/eos/contracts$ cleos set contract eosio.token eosio.token
Reading WAST/WASM from eosio.token/eosio.token.wast...
1595091ms thread-0 main.cpp:2760 main ] Failed with error: Assert Exception (10)
!wast.empty(): no wast file found eosio.token/eosio.token.wast
查看目录下,确实没有生成 wast文件
surou@surou-C-H110M-K-Pro:~/eos/contracts/eosio.token$ ls
CMakeLists.txt eosio.token.abi eosio.token.cpp eosio.token.hpp
手动编译生成wast
surou@surou-C-H110M-K-Pro:~/eos/contracts/eosio.token$ eosiocpp -o eosio.token.wast eosio.token.cpp
surou@surou-C-H110M-K-Pro:~/eos/contracts/eosio.token$ ls
CMakeLists.txt eosio.token.abi eosio.token.cpp eosio.token.hpp eosio.token.wasm eosio.token.wast
再次部署合约,已经OK
surou@surou-C-H110M-K-Pro:~/eos/contracts$ cleos set contract eosio.token eosio.token
Reading WAST/WASM from eosio.token/eosio.token.wasm...
Using already assembled WASM...
Publishing contract...
executed transaction: 45460e0ebf7c0bde0a6802adec4dd7bc457c2d4a76f1d97fc6376c88d9db0f28 8032 bytes 1079 us
# eosio <= eosio::setcode {"account":"eosio.token","vmtype":0,"vmversion":0,"code":"0061736d01000000017e1560037f7e7f0060057f7e...
# eosio <= eosio::setabi {"account":"eosio.token","abi":"0e656f73696f3a3a6162692f312e3000030663726561746500020669737375657204...
warning: transaction executed locally, but may not be confirmed by the network yet
也许是编译有什么警告或者异常没注意,或者是EOS更新什么脚本导致的,暂时未做具体跟进,下次更新版本测试后再做补充。
push_transaction Error 3050003 eosio_assert_message assertion failure
查看nodeos 抛出log
Exception Details: 3050003 eosio_assert_message_exception: eosio_assert_message assertion failure
assertion failure with message: comparison of assets with different symbols is not allowed
是因为push_transaction中的代币符号与所需代币符号不一致
测试代码如下
eos = Eos({
httpEndpoint: 'http://192.168.1.112:8888',
chainId: 'cf057bbfb72640471fd910bcb67639c22df9f92470936cddc1ade0e2f2e7dc4f',
keyProvider: wif,
verbose: true
})
const name = 'bcskillsurou'
const pubkey = 'EOS68mvUMCz73a5Xj2wnJxdTW1aPjmiKCeSAjVdXjTd3D3g9A38EE'
eos.transaction(tr => {
tr.newaccount({
creator: 'dapp.exec1',
name,
owner: pubkey,
active: pubkey
})
tr.buyrambytes({
payer: 'dapp.exec1',
receiver: name,
bytes: 8192
})
tr.delegatebw({
from: 'dapp.exec1',
receiver: name,
stake_net_quantity: '10.0000 EOS',
stake_cpu_quantity: '10.0000 EOS',
transfer: 0
})
}).then(console.log)
.catch(e => {
console.error(e);
})
由于测试网络中内置代币符号为 SYS,所以需要将上面的 EOS修改为 SYS。
还有种就是将测试网内置代币修改为EOS
修改eos_source_dir/CMakeLists.txt
将CORE_SYMBOL_NAME "SYS"修改为CORE_SYMBOL_NAME "EOS"重新编译项目
[EOS源码分析] EOS智能合约开发实践之数据库持久化
以太坊智能合约定义的全局变量的值是持久性的,就相当于智能合约一直在运行着。而EOS的智能合约更加接近我们平时使用的程序,每次执行action都相当于启动智能合约的一个新实例,一旦执行完,代码定义的变量就释放了,不会影响下一次执行环境。但是智能合约肯定需要有持久化存储的需求,比如永久保存智能合约代币的状态,不能代币转账执行完,代币的balance余额信息和转账前一样吧。这个持久化存储就是数据库存储数据。EOS允许智能合约定义自己的私有数据库表。比如下图,Apply Context的内容都是一次性的,一次action执行完成,对象就释放了,只有存储到EOSIO database的才被保存。

这个私有数据表是通过multi_index来访问和交互的。EOS的multi_index类似boost的multi_index,即多索引容器。有了多级索引,智能合约就具备了操作类似数据库模块的功能。eosio::multi_index支持主键,但是必须是唯一的无符号64位整数。eosio::multi_index中的对象容器按主键索引按无符号64位整数主键的升序排序。
申请私有数据表
定义一个multi_index对象即可
纯数据表
multi_index<table_name, record >
> table( code, scope );
这里的code必须是contract的account_name, 因为这个数据表的操作api都有check,比如'modify' 函数.
void modify( const T& obj, uint64_t payer, Lambda&& updater ) {
eosio_assert( _code == current_receiver(), "cannot modify objects in table of another contract" ); // Quick fix for mutating db using multi_index that shouldn't allow mutation. Real fix can come in RC2.
}
scope参数,这个参数的初衷是用来实现交易action并行执行的。我们知道,action之间可能是存在依赖的,比如alice转给bob 5块钱(action1),alice转给james 5块钱(action2), action1和action2就有依赖关系。因为如果Alice只有6块钱,那么这两个action中必然有一个action是失败的。如果这两个action并行执行那这两个都会成功,明显是不合理的。你可能会说可以使用互斥锁解决冲突啊?事实上市不行的,申请互斥锁中的两段代码的执行顺序是随机的,所以导致action1和action2的执行顺序是随机的,这就违背了区块链的基本要求---确定性和可重复性,每个节点按相同顺序执行一个action列表,结果应该是一致的。所以为了解决这个问题就提出了scope的。假设这个balance的数据保存在‘balance’数据表里,scope为'balance_scope', 所以在提交action1时就会传入一个scope参数'balance_scope‘,这样系统根据action1和action的scope很容易知道action1, action2都会操作'balance_scope'对应的'balance'表,就不会让这两个action并行执行。其实,这里的核心问题是action操作的数据表的信息,如果提交的action不告知系统,系统必须要执行完action才能知道操作了哪个数据表,这自然没法实现并行。当然,目前版本scope基本没有起作用,action并行老版本有实现过的框架(但不起作用),最新的版本框架甚至已经删除了相关逻辑,看来action的并行化路漫慢而修远兮。
纯数据表示例
eosio.token合约
struct currency_stats {
asset supply;
asset max_supply;
ccount_name issuer;
//必须有该函数
uint64_t primary_key()const { return supply.symbol.name(); }
};
typedef eosio::multi_index<N(stat), currency_stats> stats;
主键查找数据
纯数据表只能通过table.find函数以主键为参数查找数据
stats statstable( _self, sym.name() );
auto existing = statstable.find( sym.name() );
多索引数据表
基本格式
multi_index<table_name, record,
indexed_by<index_name, index_func>,
indexed_by<index_name_1, index_func_1>,
….>
> table( code, scope );
和纯数据表相比,就多了index_by索引定义
索引的定义格式是:indexed_by<索引名,索引键值函数>,比如这个实例:
eosio::multi_index<N(orders), limit_order,
indexed_by< N(byexp), const_mem_fun<limit_order, uint64_t, &limit_order::get_expiration> >,
indexed_by< N(byprice), const_mem_fun<limit_order, uint128_t, &limit_order::get_price> >
> orders( N(multitest), N(multitest) );
<N(orders), limit_order,这部分和纯数据表是一样的,第一个参数是表名,第二个参数是表行对象。这里有两个索引对象。分别是
indexed_by< N(byexp), const_mem_fun<limit_order, uint64_t, &limit_order::get_expiration>>
indexed_by< N(byprice), const_mem_fun<limit_order, uint128_t, &limit_order::get_price> >
N(byprice):索引名称是'byprice'
在const_mem_fun<limit_order, uint128_t,&limit_order::get_price>中的const_mem_fun定义了一个索引键值函数,这个其实是通过boost::bind函数将对象的const成员函数转化为函数指针.其格式是<ObjectType, indexType(索引类型), &ObjectType::function>

索引查找数据
auto priceidx = orders.get_index<N(byprice)>();
print("Items sorted by price:\n");
for( const auto& item : priceidx ) {
print(" ID=", item.id, ", price=", item.price, ", owner=", name{item.owner}, "\n");
}
二级索引类型限制
- idx64 - 原始的64位无符号整数密钥.
- idx128 - 原始128位无符号整数密钥.
- idx256 - 256位固定大小的字典键.
- idx_double - 双精度浮点键.
- idx_long_double - 四倍精度浮点键.
查看数据表数据
$cleos get table cleos get table [OPTIONS] contract scope table
实例运行
源码
class tabletest : public eosio::contract {
private:
/// @abi table
struct contact {
account_name name;
uint64_t phone;
auto primary_key()const { return name; }
uint64_t get_phone()const { return phone; }
EOSLIB_SERIALIZE( contact, (name)(phone))
};
typedef eosio::multi_index<N(contact), contact, indexed_by< N(byphone), const_mem_fun<contact, uint64_t, &contact::get_phone> >
> contacts;
public:
using contract::contract;
/// @abi action
void add(account_name owner, account_name name, uint64_t phone ) {
//新增数据项
contacts contacttable( _self, owner );//(code, scope)
//(player, item),player域指由谁来支付这个数据存储费用
contacttable.emplace( _self, [&]( auto& o ) {
o.name = name;
o.phone = phone;
});
}
/// @abi action
void remove(account_name owner, account_name contact_name) {
//删除数据项
contacts contacttable( _self, owner );
name n{contact_name};
auto item = contacttable.find( contact_name );
if( item == contacttable.end() ) {
print_f("Not found name:%sn", n.to_string().data());
} else {
contacttable.erase(item);
};
}
/// @abi action
void modify(account_name owner, account_name contact_name, uint64_t phone) {
//修改数据项
contacts contacttable( _self, owner );
name n{contact_name};
auto item = contacttable.find( contact_name );
if( item == contacttable.end() ) {
print_f("Not found name:%sn", n.to_string().data());
} else {
contacttable.modify( item, _self, [&]( auto& o ) {
o.phone = phone;
});
};
}
/// @abi action
void findbyname(account_name owner, account_name contact_name) {
//根据name主键查找数据项
contacts contacttable( _self, owner );
name n{contact_name};
auto item = contacttable.find( contact_name );
if( item == contacttable.end() ) {
print_f("Not found name:%s\n", n.to_string().data());
} else {
n = name{item->name};
print_f("Found phone:%, for %s\n", item->phone, n.to_string().data());
};
}
/// @abi action
void findbyphone(account_name owner, uint64_t phone) {
//根据phone索引查找数据项
contacts contacttable( _self, owner );
auto phoneinx = contacttable.get_index<N(byphone)>();
auto item = phoneinx.find(phone);
if( item == phoneinx.end() ) {
print_f("Not found phone:%\n", phone);
} else {
name n{item->name};
print_f("Found name:%s for phone:%\n", n.to_string().data(), item->phone);
};
}
};
EOSIO_ABI( tabletest, (add) (remove) (modify) (findbyname) (findbyphone))
编译
请参考【EOS编写HelloWorld智能合约】
执行
$cleos create account eosio table.code $KEY_PUB_1 $KEY_PUB_1
$cleos set contract table.code ./table -p table.code
$cleos create account eosio itleaks $KEY_PUB_2 $KEY_PUB_2
$cleos push action table.code add '[ "itleaks", "jackma", "13456" ]' -p itleaks
$cleos get table table.code itleaks contact
源码一键实践
https://github.com/itleaks/eos-contract/tree/master/table-exp