以太坊介绍
以太坊(Ethereum)是一个建立在区块链技术之上,去中心化应用平台。它允许任何人在平台中建立和使用通过区块链技术运行的去中心化应用。
智能合约
以太坊是一个运行着智能合约的分布式平台,智能合约本质就是脚本,这些脚本可以处理各种业务逻辑来利用以太坊区块链的能力,比如彩票、拍卖、物流跟踪等等。这也正是以太坊区别于比特币的最重要的属性,定位来说以太坊旨在成为一个平台,而比特币则是一个货币体系。智能合约赋能给以太坊无限想象力和更强大的生命力。
一个简单的例子,就是A转账给B,在比特币和以太坊中大概都怎么实现的:
以太坊智能合约实现的方式貌似能看懂,比较易读。事实也是这样的,智能合约使得区块链的扩展性更强,且实现上更简洁,从而让以太坊发展成为目前最大的一个区块链开发平台。
随着智能合约的发展,智能合约构建的组织如同现实商业社会一样的运行,这样形成的去中心化组织网络会变得极其复杂和自治,会出现各种形态:DAPP(去中心化应用)DAO(去中心化自治组织)DAC(去中心化自治公司)DAS(去中心化自治社会).
DAPP
DAPP是去中心化的APP,DAPP和普通的App原理一样,除了他们是完全去中心化的,在以太坊网络本身自己的节点来运作的DAPP,不依赖于任何中心化的服务器,DAPP是去中心化的,可以完全自动地运行。
以太坊中一般会认为智能合约就是DAPP,当然更准确的可以认为智能合约相当于服务器后台,另外要实现用户体验,还需要UI交互界面,通过RPC与后台对接,那么DAPP就是包含完整的智能合约+用户UI交互界面。
智能合约开发
搭建开发环境
智能合约的开发环境需要:
· Truffle:是以太坊的开发环境、测试框架和资产通道。换句话说,它可以帮助你开发、发布和测试智能合约等等。
· Ganache:以前叫作 TestRPC,如果你读过几个月前的教程的话,有可能他们在使用TestRPC 的情境下配合使用了 Truffle,它在 TestRPC 和Truffle 的集成后被重新命名为 Ganache。Ganache 的工作很简单:创建一个虚拟的以太坊区块链,并生成一些我们将在开发过程中用到的虚拟账号。
· Mist:Mist 是一个分布式网络 apps 的浏览器,相当于是只针对 Dapps 的 Chrome 或Firefox。目前来说,它仍然是不安全的,所以你还不能在不受信任的 Dapp 中使用它。
· 以太坊钱包:它是 Mist 的一个版本,但只启动一个 Dapp ——以太坊钱包。Mist 和以太坊钱包只是 UI(用户界面)前端,我们还需要一个将我们连接到以太坊区块链的核心程序(它可以是一个真正的以太坊区块链,也可以是一个测试版的)。
· Geth:Geth 是把你连接到区块链的核心应用程序,它也可以启动一个新的区块链(在我们这个示例中,我们将创建一个本地测试网区块链),创建合约,挖掘以太币等。
Truffle和Ganache都是基于NodeJS,所以首先得安装Node和NPM。本文是CentOS7环境,
Node版本如下:
$ node -v v7.0.0 $ npm -v 3.10.8 |
安装Truffle:
安装成功查询:
$ truffle version Truffle v4.1.11 (core: 4.1.11) Solidity v0.4.24 (solc-js) |
安装Ganache命令行工具:
$ npm install -g ganache-cli |
创建Truffle工程
使用Truffle来创建一个工程:
$ mkdir my-dapp $ cd my-dapp/ $ truffle init Downloading... Unpacking... Setting up... Unbox successful. Sweet!
Commands:
Compile: truffle compile Migrate: truffle migrate Test contracts: truffle test |
Truffle会初始化一个项目,项目的文件结构如下:
. ├── contracts │ └── Migrations.sol ├── migrations │ └── 1_initial_migration.js ├── test ├── truffle-config.js └── truffle.js |
第一步在工程contracts 目录下创建一个Solidity 脚本:
Wrestling.sol
pragma solidity ^0.4.18; /** * Example script for the Ethereum development walkthrough */ contract Wrestling { /** * Our wrestlers */ address public wrestler1; address public wrestler2; bool public wrestler1Played; bool public wrestler2Played; uint private wrestler1Deposit; uint private wrestler2Deposit; bool public gameFinished; address public theWinner; uint gains; /** * The logs that will be emitted in every step of the contract's life cycle */ event WrestlingStartsEvent(address wrestler1, address wrestler2); event EndOfRoundEvent(uint wrestler1Deposit, uint wrestler2Deposit); event EndOfWrestlingEvent(address winner, uint gains); /** * The contract constructor */ constructor() public { wrestler1 = msg.sender; } /** * A second wrestler can register as an opponent */ function registerAsAnOpponent() public { require(wrestler2 == address(0)); wrestler2 = msg.sender; emit WrestlingStartsEvent(wrestler1, wrestler2); } /** * Every round a player can put a sum of ether, if one of the player put in twice or * more the money (in total) than the other did, the first wins */ function wrestle() public payable { require(!gameFinished && (msg.sender == wrestler1 || msg.sender == wrestler2)); if(msg.sender == wrestler1) { require(wrestler1Played == false); wrestler1Played = true; wrestler1Deposit = wrestler1Deposit + msg.value; } else { require(wrestler2Played == false); wrestler2Played = true; wrestler2Deposit = wrestler2Deposit + msg.value; } if(wrestler1Played && wrestler2Played) { if(wrestler1Deposit >= wrestler2Deposit * 2) { endOfGame(wrestler1); } else if (wrestler2Deposit >= wrestler1Deposit * 2) { endOfGame(wrestler2); } else { endOfRound(); } } } function endOfRound() internal { wrestler1Played = false; wrestler2Played = false; emit EndOfRoundEvent(wrestler1Deposit, wrestler2Deposit); } function endOfGame(address winner) internal { gameFinished = true; theWinner = winner; gains = wrestler1Deposit + wrestler2Deposit; emit EndOfWrestlingEvent(winner, gains); } /** * The withdraw function, following the withdraw pattern shown and explained here: * http://solidity.readthedocs.io/en/develop/common-patterns.html#withdrawal-from-contracts */ function withdraw() public { require(gameFinished && theWinner == msg.sender); uint amount = gains; gains = 0; msg.sender.transfer(amount); } } |
Wrestling实现的是一个角力的游戏,在这个游戏中,每一轮的比赛,参与者都可以投入一笔钱,如果一个人投入的钱大于等于另一个人的两倍(总计),那他就赢了。
关于代码的解读参考http://www.cocoachina.com/blockchain/20180312/22550.html
第二步在工程目录migrations创建一个部署脚本:
2_deploy_contracts.js
const Wrestling = artifacts.require("./Wrestling.sol")
module.exports = function(deployer) { deployer.deploy(Wrestling); }; |
这个部署脚本的作用是导入Wrestling.sol然后部署到区块链中。
启动Ganache
Truffle工程创建完成后,现在通过Ganache创建一个虚拟的以太坊区块链。
新开一个窗口执行:
Ganache CLI v6.1.0 (ganache-core: 2.1.0)
Available Accounts ================== (0) 0x67f0c56f2ed6b635e7a4d4285615ae01b4b6d834 (1) 0xdde20e330d5747253aa2041532b486fb425951a8 (2) 0xecc3a4c50bce3014d11a3840d0d1dd3011cf65e8 (3) 0x3db9dcb607a15b744affb1453ce1d36121f56a9a (4) 0x0f1c36775edba34f9239f6fbe3e1d071a35a80bf (5) 0x14156195c281babf28200aec756ce16e8794e724 (6) 0xdb89069d044cb1f8246fad2a805b97102b05c6a5 (7) 0xa3dfb369603aa5788626371ab022e42f98001607 (8) 0x455bffe5cce17a3a71bfb1ef1fb74c3640f77b97 (9) 0x82512876028923943d5e937441710d6675d573b0
Private Keys ================== (0) 5d5d0d038bbd85c1ca25768a0da88b49805dd74bbffa628cd4b0968877eb84af (1) ba616ed800cf1e0306ec1eaea7416d52bab5ad0dd7216a997194784461da7efe (2) 0f80b88a4ad72850003a8aca809932e3e27f29e2a931d900ac0cb06fc22247bd (3) c0cb6aecff075d9527eebb69c5389f6cfaea2a3e7120a2fb6fb4122e299dfdad (4) 7ea0bd30edcbe05b89553a1c7e4eb52c4ebf87f1a664202ccad7eb26e589403c (5) 5ff14162882f900a4c2241042b1e51c866adeb089c28e6643787ef26b43293c8 (6) 24da6f19bfaa3de81ab1fd9b62636064282604e4fa26d6697eacfc3c9d3d9de8 (7) 944c9ede3956b7dc55b2ece7f9506358b1d7c5e5f7275c6b11dacd5e38414c0f (8) 43e8c352eaafd39e7116b43aed86006141daa18c1cf7c06806c6c19cbb7be29a (9) bf43907fd082a64b987c00ceb9b7ae45eee08fcf96ae07d6b0260d4ec370f736
HD Wallet ================== Mnemonic: video march rail weapon lunch holiday organ vague food strategy gown artist Base HD Path: m/44'/60'/0'/0/{account_index}
Listening on localhost:7545 |
Ganache启动成功后,会生成10个测试账号,默认情况下,每个账户都有未锁定的100个以太币并,所以我们可以*地从那里发送以太币。最后Ganache服务监听在localhost:7545。
编译和部署
Ganache启动后,回到Truffle工程目录下修改配置文件truffle.js和truffle-config.js:
module.exports = { // See // for more about customizing your Truffle configuration! networks: { development: { host: "127.0.0.1", port: 7545, network_id: "*" // Match any network id } } }; |
简单说就是配置Truffle工程的一个开发网络,地址是127.0.0.1:7545,也就是Ganache服务的地址。也就是说可以把工程部署到Ganache。
然后开始编译Truffle工程:
Compiling ./contracts/Migrations.sol... Compiling ./contracts/Wrestling.sol... Writing artifacts to ./build/contracts |
编程成功后部署到Ganache:
Using network 'development'.
Running migration: 1_initial_migration.js Deploying Migrations... ... 0xc278ec5575aa24a03c6c84e17c32d890ba6e748034e3d93a83c77f5082d856d7 Migrations: 0xd4ec84fa50eef802535fce494a187f17beb2a642 Saving successful migration to network... ... 0xa1ec2490cdb988259b7762bf483184f7226a4af3a57d11f660b5d1848ef9aeb0 Saving artifacts... Running migration: 2_initial_migration.js Deploying Wrestling... ... 0x802eaf5257108cdf3615b9c75903826a6223cb7ec9201d23c75f36085439d4f3 Wrestling: 0xb386d031d04d3cb623d6f45872203a9cb99b3e4a Saving successful migration to network... ... 0x57325073308f757f7788920795b7f3cff59c7483b71ff5319de251505b0d6b13 Saving artifacts... |
可以看到Wrestling合约的地址是0xb386d031d04d3cb623d6f45872203a9cb99b3e4a
另外在Ganache的日志可以看到:
Transaction: 0x802eaf5257108cdf3615b9c75903826a6223cb7ec9201d23c75f36085439d4f3 Contract created: 0xb386d031d04d3cb623d6f45872203a9cb99b3e4a Gas usage: 708985 Block Number: 3 Block Time: Fri May 25 2018 17:35:22 GMT+0800 (CST) |
这里创建Wrestling合约花费了708985Gas
执行合约
启动 Truffle 控制台,这会帮助我们与ganache的区块链进行交互:
$ truffle console --network development truffle(development)> |
然后在控制台中进行编码来实现合约的执行
首先选择2个账户作为游戏的2个参与者
> account0 = web3.eth.accounts[0] '0xe2be5aa059b3891a519f7f1e04666d05e92b1365' > account1 = web3.eth.accounts[1] '0x726f35aa2eabc41b6cb934fb5be7719263f4fe13' |
接着初始化一个Wrestling合约实例WrestlingInstance
> Wrestling.deployed().then(inst => { WrestlingInstance = inst }) |
可以查看WrestlingInstance的变量wrestler1,也就是选手1的地址,因为我们没有指定,所以默认就是Ganache 生成的第一个账户,也就是account0
> WrestlingInstance.wrestler1.call() '0xe2be5aa059b3891a519f7f1e04666d05e92b1365' |
然后我们把account1注册成Wrestling合约的第二个选手2
> WrestlingInstance.registerAsAnOpponent({from: account1}) { tx: '0x1144b68e8d19c4288ef0d20a558fd178066ec1cd38cc6610a9e16150e013d3ea', receipt: { transactionHash: '0x1144b68e8d19c4288ef0d20a558fd178066ec1cd38cc6610a9e16150e013d3ea', transactionIndex: 0, blockHash: '0x7b36144a86ff06c56fb702a376af0bbc300e077ee09451a154b7743fa2f6e052', blockNumber: 5, gasUsed: 43900, cumulativeGasUsed: 43900, contractAddress: null, logs: [ [Object] ], status: '0x01', logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000' }, logs: [ { logIndex: 0, transactionIndex: 0, transactionHash: '0x1144b68e8d19c4288ef0d20a558fd178066ec1cd38cc6610a9e16150e013d3ea', blockHash: '0x7b36144a86ff06c56fb702a376af0bbc300e077ee09451a154b7743fa2f6e052', blockNumber: 5, address: '0xb386d031d04d3cb623d6f45872203a9cb99b3e4a', type: 'mined', event: 'WrestlingStartsEvent', args: [Object] } ] } |
从返回的结果可以看出该项交易使用了Gas,并且触发了“WrestlingStartsEvent” 事件。
现在2个选手准备就绪,就可以开始游戏: 每一轮的比赛,参与者都可以投入一笔钱,如果一个人投入的钱大于等于另一个人的两倍(总计),那他就赢了。
第一轮,选手1出价2 ether,选手2出价3 ether
> WrestlingInstance.wrestle({from: account0, value: web3.toWei(2, "ether")}) > WrestlingInstance.wrestle({from: account1, value: web3.toWei(3, "ether")}) |
第二轮,选手1出价5 ether,选手2出价20 ether
> WrestlingInstance.wrestle({from: account0, value: web3.toWei(5, "ether")}) > WrestlingInstance.wrestle({from: account1, value: web3.toWei(20, "ether")}) |
两轮下来,选手1出价7ether,选手2出价23ether,所以选手2获胜,这时候获胜者可以提现:
> WrestlingInstance.withdraw({from: account1}) |
最后查询账户余额,因为在合约执行过程中要支付Gas,所以账户1的余额是小于107ether的。
> web3.eth.getBalance(account1) { [String: '106974788700000000000'] s: 1, e: 20, c: [ 1069747, 88700000000000 ] |
参考
- https://blog.csdn.net/m0_37722557/article/details/79899847
- http://xinwen.eastday.com/a/180314214918603.html?xx=1&recommendtype=d
- http://www.cocoachina.com/blockchain/20180314/22592.html
- http://www.cocoachina.com/blockchain/20180312/22550.html
作者简介
吴龙辉,《Kubernetes实战》作者,活跃于技术开源社区,贡献代码和撰写技术文档。邮箱: wlh6666@qq.com