├── LICENSE ├── README.md ├── 从helloworld到hello红包part1.md ├── 从helloworld到hello红包part2.md ├── 基于区块链的DAPP开发笔记(3)-对比特币共识的理解.md ├── 基于区块链的Dapp开发笔记(1)如何解决以太坊中的nounce冲突问题.md └── 基于区块链的Dapp开发笔记(2)-没来得及映射随机账号,如何创建自己的EOS账号.md /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 rrtoken 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DAPP_Blog 2 | 3 | 记录我们开发过程中的思考,心得和收获。 4 | 5 | 我们的技术栈: 6 | Ethereum, EOS, IPFS 7 | c/C++,node.js,python 8 | reactive, angular,vue.js 9 | 10 | 我们组建了一个DAPP开发者讨论群,加微信号:mindflow2017, 说明自己的技术栈,我拉你入群。 11 | -------------------------------------------------------------------------------- /从helloworld到hello红包part1.md: -------------------------------------------------------------------------------- 1 | # 从helloworld到hello红包之一 2 | 3 | 今天,我们来看看如何在EOS智能合约内部发起转帐(发红包)。在下面第一部分的例子代码里,我们在官方Helloworld标准的打招呼hi()接口的基础上,增加新的参数“金额”,实现在打招呼的同时,通过合约内部转帐给对方账户发红包。第二部分的测试中,合约账号eosonetest1通过新的打招呼接口,给 eosonetest2 发送转帐。 4 | 使用合约内转帐,和标准转帐相比,可以在合约里加上各种条件和控制逻辑,实现丰富多彩的合约玩法。 5 | 6 | ## 从零开始 7 | **创建 helloworld 智能合约** 8 | ``` 9 | $ eosiocpp -n helloworld 10 | created helloworld from skeleton 11 | ``` 12 | 13 | **得到的helloworld.cpp源代码是这个样子的** 14 | ``` 15 | #include 16 | 17 | using namespace eosio; 18 | 19 | class hello : public eosio::contract { 20 | public: 21 | using contract::contract; 22 | 23 | /// @abi action 24 | void hi( account_name user ) { 25 | print( "Hello, ", name{user} ); 26 | } 27 | }; 28 | 29 | EOSIO_ABI( hello, (hi) ) 30 | ``` 31 | 现在我们开始把它改造成“红包合约” 32 | **添加必要的头文件** 33 | ``` 34 | #include 35 | ``` 36 | **给hi() 函数添加通证** 37 | ``` 38 | void hi( account_name to, const asset& quantity ) { 39 | ``` 40 | **加上一些安全检查** 41 | ``` 42 | require_auth( _self ); 43 | eosio_assert( quantity.is_valid(), "invalid token" ); 44 | eosio_assert( quantity.amount > 0, "must be positive quantity" ); 45 | 46 | require_recipient( _self ); 47 | require_recipient( to ); 48 | ``` 49 | **添加合约里转账** 50 | ``` 51 | action( 52 | permission_level{ _self, N(active) }, 53 | N(eosio.token), N(transfer), 54 | std::make_tuple(_self, to, quantity, std::string("hello money")) 55 | ).send(); 56 | ``` 57 | 58 | **改造好的hi()函数变成这个样子:** 59 | ``` 60 | /// @abi action hi 61 | void hi( account_name to, const asset& quantity ) { 62 | require_auth( _self ); 63 | eosio_assert( quantity.is_valid(), "invalid token" ); 64 | eosio_assert( quantity.amount > 0, "must be positive quantity" ); 65 | 66 | require_recipient( _self ); 67 | require_recipient( to ); 68 | 69 | action( 70 | permission_level{ _self, N(active) }, 71 | N(eosio.token), N(transfer), 72 | std::make_tuple(_self, to, quantity, std::string("hello money")) 73 | ).send(); 74 | print( "Hello, here is some money for ", name{to} ); 75 | } 76 | ``` 77 | ## 在 Jungle testnet 上实测 78 | 79 | **给合约账号购买适当的RAM** 80 | ``` 81 | cleos -u http://jungle.cryptolions.io:18888 system buyram -k eosonetest2 eosonetest2 1000 82 | ``` 83 | **把合约布署到合约账号** 84 | ``` 85 | cleos -u http://jungle.cryptolions.io:18888 set contract eosonetest2 ../helloworld -p eosonetest2 86 | ``` 87 | 88 | **合约里转账需要添加的 eosio.code permission** 89 | ``` 90 | cleos -u http://jungle.cryptolions.io:18888 set account permission eosonetest2 active '{"threshold": 1,"keys": [{"key": "EOS8xxxxxxxxxxxxxxx","weight": 1}],"accounts": [{"permission":{"actor":"eosonetest2","permission":"eosio.code"},"weight":1}]}' owner -p eosonetest2@owner 91 | ``` 92 | 93 | ### 测试网上的输出 94 | 95 | **测试前的余额** 96 | ``` 97 | @joel-Lenovo-Y520:helloworld $ cleos -u https://jungle.eosio.cr:443 get currency balance eosio.token eosonetest1 EOS 98 | 7970.3411 EOS 99 | @joel-Lenovo-Y520:helloworld $ cleos -u https://jungle.eosio.cr:443 get currency balance eosio.token eosonetest2 EOS 100 | 6733.2414 EOS 101 | 102 | ``` 103 | **通过合约发红包** 104 | ``` 105 | @joel-Lenovo-Y520:helloworld $ cleos -u http://jungle.cryptolions.io:18888 push action eosonetest2 hi '["eosonetest1", "103.0000 EOS"]' -p eosonetest2 106 | executed transaction: 71fxxxxxxxxxab706fxx3ea1cxxxxx1f70f05cxxxxx 120 bytes 2483 us 107 | # eosonetest2 <= eosonetest2::hi {"to":"eosonetest1","quantity":"103.0000 EOS"} 108 | >> Hello, here is some money for eosonetest1 109 | # eosonetest1 <= eosonetest2::hi {"to":"eosonetest1","quantity":"103.0000 EOS"} 110 | # eosio.token <= eosio.token::transfer {"from":"eosonetest2","to":"eosonetest1","quantity":"103.0000 EOS","memo":"hello money"} 111 | # eosonetest2 <= eosio.token::transfer {"from":"eosonetest2","to":"eosonetest1","quantity":"103.0000 EOS","memo":"hello money"} 112 | # eosonetest1 <= eosio.token::transfer {"from":"eosonetest2","to":"eosonetest1","quantity":"103.0000 EOS","memo":"hello money"} 113 | warning: transaction executed locally, but may not be confirmed by the network yet ] 114 | 115 | ``` 116 | **测试后的余额** 117 | ``` 118 | @joel-Lenovo-Y520:helloworld $ cleos -u https://jungle.eosio.cr:443 get currency balance eosio.token eosonetest1 EOS 119 | 8073.3411 EOS 120 | @joel-Lenovo-Y520:helloworld $ cleos -u https://jungle.eosio.cr:443 get currency balance eosio.token eosonetest2 EOS 121 | 6630.2414 EOS 122 | 123 | ``` 124 | 125 | -------------------------------------------------------------------------------- /从helloworld到hello红包part2.md: -------------------------------------------------------------------------------- 1 |  2 | # 从helloworld到hello红包之二 3 | 4 | 上次我们学会了用EOS智能合约发红包,发红包时需要合约方知道对方的EOS账号,并通过合约账号授权发定向红包(转账送钱)。今天我们要实现的是多人“领红包”的功能,固定金额,见者有份,分完为止,不需要通过合约的EOS账号授权。(有人问,为什么不做拼手气红包呢?因为智能合约的随机数比较特殊,有机会再单独讲。) 5 | 6 | ## 数据存储 7 | 8 | _只要我记得你,你就永远不会消失的啊_ 9 | ----《寻梦幻游记》CoCo 10 | 11 | 12 | 首先,我们需要让合约记住已经领过红包的账号,机会均等,每人只能领一次。在EOS上,最简单的办法就是用table保存合约的状态和数据。 13 | 14 | ### 创建表格 15 | 首先,我们在helloworld.cpp里加上两个表格。其中 primary_key 是主键,bonustable是表名,bonus_t是表结构。 16 | 17 | 红包表: 18 | 19 | ``` 20 | struct bonus_t { 21 | uint64_t id; 22 | asset balance; 23 | uint64_t primary_key() const { return id;} 24 | EOSLIB_SERIALIZE( bonus_t, (id)(balance) ) 25 | }; 26 | multi_index _bonus; 27 | ``` 28 | 领红包的用户表: 29 | 30 | ``` 31 | struct user_t { 32 | uint64_t id; 33 | account_name user; 34 | uint64_t primary_key() const { return user;} 35 | EOSLIB_SERIALIZE( user_t, (id)(user) ) 36 | }; 37 | multi_index _users; 38 | ``` 39 | ### 初始化表格 40 | 加上default constructor,把红包金额初始化为10 EOS。 41 | 42 | ``` 43 | hello( account_name self ) 44 | :contract(self),_bonus( _self, _self),_users( _self, _self) { 45 | asset quantity; 46 | quantity.set_amount(100000); // 10 EOS 47 | if ( _bonus.begin() == _bonus.end() ) 48 | _bonus.emplace( self, [&](auto& a) { 49 | a.id = 1; 50 | a.balance = quantity; 51 | }); 52 | } 53 | } 54 | ``` 55 | ### 检查用户状态 56 | 我们增加一个检查用户的函数checkuser,检查用户是否领过红包。如果用户没领过红包,就把用户的账号加到表格里。 57 | 58 | 59 | ``` 60 | bool checkuser( account_name user ) { 61 | bool found = true; 62 | auto itr = _users.find(user); 63 | if( itr == _users.end() ) { 64 | found = false; 65 | itr = _users.emplace(_self, [&](auto& acnt){ 66 | acnt.id = _users.available_primary_key(); 67 | acnt.user = user; 68 | }); 69 | } 70 | return found; 71 | } 72 | 73 | ``` 74 | ### 更新红包金额 75 | 再把取红包金额的功能放到一个函数里: 76 | 77 | ``` 78 | asset getbonus() { 79 | asset quantity; 80 | auto itr = _bonus.begin(); 81 | auto balance = itr->balance; 82 | eosio_assert( balance.amount > 0, "must be positive quantity" ); 83 | if (balance.amount > 10000){ 84 | quantity.set_amount(10000); // 1 EOS 85 | _bonus.modify(itr, _self, [&](auto& p) { 86 | eosio_assert( p.balance.amount > quantity.amount, "we're out of bonus!" ); 87 | p.balance.amount -= quantity.amount; 88 | }); 89 | } else { 90 | quantity = balance; 91 | itr = _bonus.erase(itr); 92 | } 93 | return quantity; 94 | } 95 | ``` 96 | ### 新的红包接口 97 | 最后,我们把用户检查和取红包金额的功能加到领红包的接口上,改造后的hi()接口不再传入金额。 98 | 99 | ``` 100 | /// @abi action hi 101 | void hi( account_name to ) { 102 | if ( checkuser(to) ) 103 | return; 104 | 105 | asset bonus = getbonus(); 106 | eosio_assert( bonus.amount > 0, "must be positive quantity" ); 107 | require_recipient( _self ); 108 | require_recipient( to ); 109 | 110 | action( 111 | permission_level{ _self, N(active) }, 112 | N(eosio.token), N(transfer), 113 | std::make_tuple(_self, to, bonus, std::string("hello money")) 114 | ).send(); 115 | print( "Hello, here is some money for ", name{to} ); 116 | } 117 | ``` 118 | 具体的测试方法,请参考《从helloworld到hello红包part1》 119 | 120 | ## 知识点总结 121 | 122 | ### 创建表格 123 | 124 | ``` 125 | struct user_t { 126 | uint64_t id; 127 | account_name user; 128 | asset balance; 129 | uint64_t primary_key() const { return user;} 130 | EOSLIB_SERIALIZE( user_t, (id)(user)(balance) ) 131 | }; 132 | multi_index _users; 133 | ``` 134 | ### 查询表格: 135 | ``` 136 | auto itr = _users.find(user); 137 | ``` 138 | ### 给表格添加元素: 139 | ``` 140 | auto itr = _users.find(user); 141 | if( itr == _users.end() ) { 142 | itr = _users.emplace(_self, [&](auto& acnt){ 143 | acnt.id = _users.available_primary_key(); 144 | acnt.user = user; 145 | }); 146 | } 147 | ``` 148 | ### 更新表格: 149 | ``` 150 | auto itr = _users.find(user); 151 | if( itr != _users.end() ) { 152 | _users.modify( itr, _self, [&]( auto& acnt ){ 153 | acnt.user = user; 154 | acnt.balance.amount = new_amount; 155 | }); 156 | } 157 | ``` 158 | ### 清空表格: 159 | ``` 160 | for (auto itr = _users.begin(); itr != _users.end();) 161 | itr = _users.erase(itr); 162 | ``` 163 | -------------------------------------------------------------------------------- /基于区块链的DAPP开发笔记(3)-对比特币共识的理解.md: -------------------------------------------------------------------------------- 1 | ### 1.比特币的共识 2 | 是指由矿工节点组成的比特币网络对所有该产生的合法区块进行确认(同时否认不合法区块,丢弃掉)。由于每个区块又包含了若干交易, 所以也就等于对所有交易进行了确认,达到共识。 3 | 这里的合法,我认为是合乎比特币设计算法规定,而不是法律意义上的合法。 4 | 5 | ### 2.分布式 6 | 由于参与比特币挖矿的节点数量巨大,地理上又分布于不同的地方,网络状况也不尽相同。是一个极端分散的分布式系统。每个矿工节点收到网络上广播的用户交易的顺序,数量都不一样。每个矿工看到的都是不同的view。因此,比特币的共识算法本质上就是要随机选择出一个具体块的矿工。之前在知乎上看到一位大牛的说法,适当的随机数产生算法对公链的设计是最关键的。 7 | 8 | ### 3.随机数 9 | 选择当前的出块节点必须是随机的。如果某个节点知道某一个高度的块一定产生于自己,那么他就可以没有成本的作弊。为了让那些试图伪造比特币交易(例如double spend)的人增加成本,比特币选择了一种需要大计算量来产生随机数的方法。这就是工作量证明,PoW。 10 | 11 | ### 4. PoW 12 | 矿工在把若干交易打包在之后,就需要选择一个随机数c。这个c的值要满足一个条件,就是把c和打包好的交易放在一起进行一个Hash运算,产生了Hash值h。由于采用了Hash256,所以运算结果h是一个256bit的数字。该数字类似于一个随机数,因为你无法根据c来大致猜测这个数字在什么范围。 13 | 比特币网络有一个难度值d,该难度值是动态调整的。每个时间段都会进行调整。 14 | 如果h ) 12 | ``` 13 | 由于infura并没有提供private key的存储,所以默认的HttpProvider并不能发起交易,只能进行查询操作。要进行交易有两个选择,第一是使用一个能保存private key并签名交易的provider,比如 truffle-hdwallet-provider。 14 | 但是这个provider要求我们将助记词传递给它。 15 | ``` 16 | var HDWalletProvider = require("truffle-hdwallet-provider"); 17 | var provider = new HDWalletProvider(mnemonic, "https://mainnet.infura.io/"); 18 | 19 | ``` 20 | 这样做一方面感觉不太放心,另外一方面也不够灵活。所以我们采用了第二个办法,自己签名交易并发送。 21 | 22 | 自己签名交易的时候必须填写每个交易的nonce值。 23 | 由于以太坊设计的技术特点,要求从同一个账号产生的每个交易都有一个不同的nonce,对交易进行区分。这个nonce并不是随意选择的,而是必须从0开始递增。而且每个被以太坊网络记录的交易的nonce都必须比该账号产生的前一个交易大1。 24 | 25 | 听起来很简单,同时,web3也提供了一个接口,getTransactionCount 让我们查询一个特定账号在网络中已经确认了多少笔交易。所以一个最简单的产生nonce的策略就是使用 `getTransactionCount()` 的返回值。 26 | ``` 27 | async function sendTransaction(data, account) { 28 | data.nonce = web.eth.getTransactionCount(account); 29 | await signAndSend(data); 30 | } 31 | ``` 32 | 33 | 不幸的是,以太网是一个弱一致性分布式系统。这里面有太多的不确定性。 34 | 试想,由于以太坊网络确认一笔交易需要数分钟的时间。如果在一个很短的时间内(比如10秒之内)我们产生了两笔交易,我们连续调用了两次 `getTransactionCount()` 来产生两个nonce。**我们会惊奇的发现两个nonce会是完全一样**,因为系统根本还没有来得及确认上一笔交易。那么我们广播出去的这两笔交易,最终只会有一笔得到确认。 35 | 36 | [![P9GK56.md.png](https://s1.ax1x.com/2018/06/24/P9GK56.md.png)](https://imgchr.com/i/P9GK56) 37 | 38 | 39 | 所以我们改进一下策略,如果我们在自己的服务器上记录nonce,每签名一次交易就增加一次怎么样? 40 | ``` 41 | var nonce = 0; 42 | async function initializeNonce() { 43 | nonce = await web3.eth.getTransactionCount(account); 44 | } 45 | 46 | function sendTransaction(data) { 47 | data.nonce = nonce; 48 | nonce += 1; 49 | await signAndSend(data); 50 | } 51 | ``` 52 | 53 | 在一个分布式网络中,即使不考虑本地出错的可能性,网络传输随时都可能产生错误。设想我们签名好一个交易,并且发送出去,然后增加nonce等待下一次签名。在这个时候,如果刚刚送处去的那个交易失败了怎么办?比如transaction fail或者gas太低直接被系统丢弃了怎么办?如果我们继续增加nonce,由于nonce的不连续会导致后面的交易都得不到处理。 54 | 55 | 在刚才改进的策略之上,我们的解决方式是不停的监听所有没有被确认的交易,如果超过一定的时间(比如15分钟)交易都没有得到确认。该交易对应的nonce会被重新使用来发送下一个交易。 56 | 在网络中查询一个交易是否得到确认可以使用`getTransactionReceipt()`方法。 57 | 58 | ![solution](http://wx4.sinaimg.cn/mw690/0060lm7Tly1fsn8oxpdlbj30dt0epmy8.jpg) 59 | ---- 60 | 最后,对我们的小玩具感兴趣的,可以用微信扫描二维码来尝试一下: 61 | 62 | [![P9GQPK.jpg](https://s1.ax1x.com/2018/06/24/P9GQPK.jpg)](https://imgchr.com/i/P9GQPK) 63 | 64 | 65 | -------------------------------------------------------------------------------- /基于区块链的Dapp开发笔记(2)-没来得及映射随机账号,如何创建自己的EOS账号.md: -------------------------------------------------------------------------------- 1 | # 基于区块链的Dapp开发笔记(2)-没来得及映射随机账号,如何创建自己的EOS账号? 2 | 3 | ## 问题1:没账号因此不能创建账号 4 | 5 | 由于懒,我们没来得及在六一之前将EOS提取到钱包,因此也就失去了在EOS全网映射的时候获得账号的机会。 6 | 然而下一步,我们计划基于EOS做DAPP开发,因此必须要实现这个账号,那应该如何操作呢? 7 | 8 | 相信和我们有同样问题的人不在少数,于是我们到各个渠道去搜索了一下,最后在Reddit上找到了答案:[ZEOS](https://zeos.co) 9 | 10 | 要在ZEOS上申请EOS账号,需要准备一个Owner Key和一个Active key。可以用EOS提供的cleos命令行工具来生成,(调用 `cleos wallet create_key`)。不熟悉命令行工具的人可以用EOS各个社区提供的钱包来产生Key,然后拷贝出来。 11 | 12 | 在ZEOS上简单注册之后,就可以生成账号了。 13 | 14 | ![注册账号页面](http://wx2.sinaimg.cn/mw690/0060lm7Tly1fsq5e0197kj31kw0ynwp6.jpeg) 15 | 16 | **注意** 17 | 18 | 账号名称现在只开放了12个字节长度的可以免费注册。更短字节的账号名需要在EOS网络中拍卖获得。账号名称只能使用小写字母和1-5的数字。 19 | 20 | **收费** 21 | 22 | 0.01ETH。 实际上之后我们有了账号,自己再创建账号的时候,因为要分配CPU,带宽,RAM这些,也花了0.4 EOS。 23 | 他们的利润大概在RMB10左右,可以接受。 24 | 25 | ## 问题2: 有了账号了,去哪里获得EOS 来执行操作呢? 26 | 27 | 在EOS主网,智能合约的调用都需要有EOS抵押,而如之前所说,我们的EOS都在交易所(火币/币安),因此目前无法提出来到账号里,进行进一步的操作。 28 | 在问了不少朋友后,最终锁定在**OTCBTC**了。现在OTCBTC提取EOS!!!我们在OTCBTC上提取EOS然后转账到了我们新建的账号上。 29 | 30 | 操作很简单,值得推荐。 31 | 32 | 33 | ## 问题3: 有了账号了,有了EOS 现在能干什么呢? 34 | 恭喜你,你可以开始编写并上传自己的智能合约了。。。 35 | 或者,你不会编写智能合约。没关系,你还可以炒RAM。 36 | 37 | 假设你在上面申请的账号名为 `exampletoken`。 38 | 你可以先用 `cleos get account exampletoken`查看自己有多少RAM, 应该不到4k,这是一个账号存在所必须的最小RAM。 39 | 使用 `cleos system buyram exampletoken exampletoken "1 EOS"`。 你就用你自己的账号为自己购买了1 个EOS对应的RAM。 40 | 通过 `cleos get account exampletoken` 你可以查看现在拥有的RAM。如果以后你不需要这些RAM了(比如RAM涨价了,或者你亏惨了),就可以用 `cleos system sellram` 把它们卖掉,相应的EOS货币就会回到你的账号里面。价格由当时的系统决定。 41 | --------------------------------------------------------------------------------