搭建以太坊测试链的简易教程 the-beginners-guide-to-using-an-ethereum-test-network
在以太坊项目实际开发中,需要一个测试环境,因为产品环境是需要消耗GAS的。因此我们希望在测试环境测试无误之后再发布到产品环境以太坊链上去。可以搭建一个本地的测试链。
本篇的主要参考内容来自于: https://medium.com/compound-finance/the-beginners-guide-to-using-an-ethereum-test-network-95bbbc85fc1d 以及 https://karl.tech/intro-guide-to-ethereum-testnets/
Testnets
以太坊的测试网络环境被称为Testnets
,拥有几乎和以太坊等效的功能。因此可以在Testnets上开发和测试自己的智能合约,测试发币等等,作为上链前的评估环境。
目前Testnets环境有不少,受到开发者喜爱的主要有以下三种:
- Ropsten: 一个POW的区块链,非常类似于以太坊,你可以很轻松的进行挖矿操作。
- Kovan: 一个POA的区块链,不能挖矿,需要申请。
- Rinkeby: 一个POA的区块链,不能挖矿,需要申请。
以上项目属于公共的Testnets
,可以无需本地搭建直接使用,参考对应的文档接入即可,也可以使用Geth
命令行工具连接到这些公共Testnets网络中。
下面我们要介绍的是需要自己DIY的Testnets项目,适用于本地小规模测试,或者没有外网访问的测试环境中,就是ganache-cli项目。
ganache-cli 用于测试和开发的快速以太坊RPC客户端
曾经这个项目被称为testrpc
,现在已经增强了功能,并且重命名为ganache-cli
,也由etherenum项目组迁移到了truffle项目组的名下,作为truffle框架下的一个子项目。
安装它需要使用npm
:
**
$ npm install -g ganache-cli
快速启动只需要空参运行ganache-cli
即可:
**
$ ganache-cli
Ganache CLI v6.1.6 (ganache-core: 2.1.5)
Available Accounts
==================
(0) 0x721e4416f6290230274be32f3d68e8194403f5de (~100 ETH)
(1) 0xefbd7fd54126b3a755cce16ab80b6f018eb2cc4a (~100 ETH)
(2) 0x3af60c2747c737b37a49cbdaf6d4e3f80794cbcf (~100 ETH)
(3) 0x35456a4255a0e8d2525ffd2eccd16e88b8242de7 (~100 ETH)
(4) 0xba6b44714329f4578d785b6132e942b8f920ca07 (~100 ETH)
(5) 0xfbb8c6da1c3bcfb7e285210f2404364112877a1b (~100 ETH)
(6) 0x33e90bf0bc85d18f1fd104aaae22722ab046cfc4 (~100 ETH)
(7) 0x88a7e5c3ebd8c36f84dded2b7e15a59473153cf3 (~100 ETH)
(8) 0xe025cdf4066b6d8079438dd9eb9d8a30f82a7f1d (~100 ETH)
(9) 0x5da32eb8ca2c8fd14c745776633924e4d87cfbe4 (~100 ETH)
Private Keys
==================
(0) 0x7a11088361c7cad64acadce8deaae666a6721d02837523663cb46683b4ed7cda
(1) 0x3619c935a7f965449a0104935e4e631cba9077c0783cbf77fe0c5f92fd8bb763
(2) 0x9ac7a2914d651ef3afba681c1c2035909ae6ca93b86ee8fa367b0373c63f9379
(3) 0x7e4651e35c7e9956e7a18eb4f721fad81a0e608736cda6bdfee6a5ff9ee8b99d
(4) 0x11f3144b6f041601e80bfeb6fab4b74caff28fd9fb1cdbf3435967deee308592
(5) 0xf747fbd7e8e85fbf76d6f7c8eb4244d3b72655d5e86648daab6d020f98a0a251
(6) 0x065d754b60b856a42a6418e13fb625632122433d9b3263b686265fb67cfc550b
(7) 0x2857ecc4b378de190265245825dc42443370262c119b894dc61f88564d491252
(8) 0xa86da3e1e8fc33bbd4fcfab2461af10e7d6eaa16002fb9db582aedf8ff92881c
(9) 0x4a50bb4a4d4d29df2e894ad9d18086e079b7a3e8b8e4960649c5043268e73ae5
HD Wallet
==================
Mnemonic: hip achieve device country identify gun drive nest danger run outside window
Base HD Path: m/44'/60'/0'/0/{account_index}
Gas Price
==================
20000000000
Gas Limit
==================
6721975
Listening on 127.0.0.1:8545
可以看到默参运行会直接创建10个钱包账户,初始化一些以太币,并且有非常多的Gas和足够的Gas Limit,通常来说也足够运行智能合约的测试了。
更多可用参数可以参考ganache-cli --help
的输出,可能比较有用的是持久化钱包(--db
参数)和监听0.0.0.0
(--host
参数),以便组建以太坊Testnets。比如这样:
**
# 在某一台主机(我这里是192.168.31.100)上运行
$ mkdir -p ganache_db # 数据库目录必须事先创建,否则程序运行报错
$ ganache-cli -h 0.0.0.0 --db ganache_db/
....
HD Wallet
==================
Mnemonic: crucial peanut biology duck casino safe laugh crowd over enrich crane turkey
Base HD Path: m/44'/60'/0'/0/{account_index}
Gas Price
==================
20000000000
Gas Limit
==================
6721975
Listening on 0.0.0.0:8545
我们以某一台主机为master,监听0.0.0.0
,运行ganache-cli
,其他参数全都默认。在另一台主机再启动一个ganache-cli
,指明从master节点上进行fork:
**
$ mkdir -p ganache_db
$ ganache-cli -h 0.0.0.0 -f http://192.168.31.100:8545 --db ganache_db/
...
Gas Price
==================
20000000000
Gas Limit
==================
6721975
Forked Chain
==================
Location: http://192.168.31.100:8545
Block: 0
Network ID: 1531668183776
Time: Mon Jul 16 2018 00:25:09 GMT+0900
Listening on 0.0.0.0:8546
最后出现了Forked Chain
字样,这样就可以在多个节点上部署区块链,模拟区块链的网络环境。
启动模拟环境之后,使用metamask就可以连接到测试网络上,测试交易了,可以参考官方文档进行操作: https://truffleframework.com/docs/advanced/truffle-with-metamask
大概的步骤如下:
启动ganache-cli之后,在终端输出可以看到类似于这样的字样:
**
HD Wallet
==================
Mnemonic: hard verb crunch rare direct universe mean gold claw need neutral carry
Base HD Path: m/44'/60'/0'/0/{account_index}
按照metamask网站的链接,安装chrome或firefox插件之后,打开插件图标,经过一系列的Accept操作之后,进入页面,选择Localhost 8545
(如果修改过默认监听端口,那么请使用最后的Custom RPC):
image.png
然后选择Import Existing DEN
(因为ganache-cli已经帮我们初始化好了账户,不用自己重新创建了)。在弹出界面中钱包种子填入终端上的Menmonic
,初始化密码自己填一个,满足8位即可:
image.png
之后就可以看到简单的钱包账户界面了:
image.png
通过Import Account
功能就可以导入其他创建好的账户了:
image.png
同样账户的私钥已经打印到终端上了,直接用就可以了。
Ganache CLI是以太坊开发工具Truffle(https://truffle.tryblockchain.org/)套件的一部分,是以太坊开发私有区块链的Ganache命令行版本。可以方便开发者快速进行以太坊DApp的开发与测试。 本文将详细介绍ganache-cli的安装及命令行参数的作用。
Ganache CLI使用ethereumjs来模拟完整的客户端行为,使开发以太坊应用程序更快,更轻松,更安全。它还包括所有主流的RPC函数和功能(如event),并可以准确地运行以使开发变得容易。
以太坊客户端可以选择。我们推荐在开发和部署时使用不同客户端。
适用开发的客户端
- EtherumJS TestRPC ---Ganache CLI
当开发基于Truffle的应用时,我们推荐使用EthereumJS TestRPC。它是一个完整的在内存中的区块链仅仅存在于你开发的设备上。它在执行交易时是实时返回,而不等待默认的出块时间,这样你可以快速验证你新写的代码,当出现错误时,也能即时反馈给你。它同时还是一个支持自动化测试的功能强大的客户端。Truffle充分利用它的特性,能将测试运行时间提速近90%。
适用正式发布的客户端
对此有许多官方和非官方的以太坊客户端可供选择。最好使用TestRPC客户端充分测试后,再使用这些客户端。这些是完整的客户端实现,包括挖矿,网络,块及交易的处理,Truffle可以在不需要额外配置的情况下发布到这些客户端。
当发布到私有网络中
私人网络中使用了相同的技术,但却有不同的配置。所以你可以将上面提及的客户端来运行一个私有的网络,部署到这样的网络也是使用同样的方式。
安装
npm install -g ganache-cli
启动
~$ ganache-cli
启动选项
- -a 或 –accounts: 指定启动时要创建的测试账户数量。
- -e 或 –defaultBalanceEther: 分配给每个测试账户的ether数量,默认值为100。
- -b 或r –blockTime: 指定自动挖矿的blockTime,以秒为单位。默认值为0,表示不进行自动挖矿。
- -d 或 –deterministic: 基于预定的助记词(
mnemonic
)生成固定的测试账户地址。 - -n 或 –secure: 默认锁定所有测试账户,有利于进行第三方交易签名。
- -m 或 –mnemonic: 用于生成测试账户地址的助记词。
- -p 或 –port: 设置监听端口,默认值为8545。
- -h 或 –hostname: 设置监听主机,默认值同NodeJS的
server.listen()
。 - -s 或 –seed: 设置生成助记词的种子。.
- -g 或 –gasPrice: 设定Gas价格,默认值为20000000000。
- -l 或 –gasLimit: 设定Gas上限,默认值为90000。
- -f 或 –fork: 从一个运行中的以太坊节点客户端软件的指定区块分叉。输入值应当是该节点旳HTTP地址和端口,例如
http://localhost:8545
。 可选使用@标记来指定具体区块,例如:http://localhost:8545@1599200
。 - -i 或 –networkId:指定网络id。默认值为当前时间,或使用所分叉链的网络id。
- –db: 设置保存链数据的目录。如果该路径中已经有链数据,ganache-cli将用它初始化链而不是重新创建。
- –debug:输出VM操作码,用于调试。
- –mem:输出ganache-cli内存使用统计信息,这将替代标准的输出信息。
- –noVMErrorsOnRPCResponse:不把失败的交易作为RCP错误发送。开启这个标志使错误报告方式兼容其他的节点客户端,例如geth和Parity。
特殊选项
-
–account: 指定账户私钥和账户余额来创建初始测试账户。可多次设置:
$ ganache-cli --account="<privatekey>,balance" [--account="<privatekey>,balance"]
注意私钥长度为64字符,必须使用0x前缀的16进制字符串。账户余额可以是整数,也可以是0x前缀的17进制字符串,单位为wei。
使用–account选项时,不会自动创建HD钱包。
- -u 或 –unlock: 解锁指定账户,或解锁指定序号的账户。可以设置多次。当与–secure选项同时使用时,这个选项将改变指定账户的锁定状态:
$ ganache-cli --secure --unlock "0x1234..." --unlock "0xabcd..."
也可以指定一个数字,按序号解锁账号:
$ ganache-cli --secure -u 0 -u 1
此功能也可用于模拟帐户来解锁您无法访问的地址。当与-fork功能一起使用时,可以使用ganache-cli将交易作为区块链上的任何地址进行处理,这对于测试和动态分析非常有用。
用于测试和开发的快速以太坊RPC客户端。 [http://truffleframework.com/ganache](http://truffleframework.com/ganache)
Library 作为Web3提供者(provider):
var ganache = require("ganache-cli");
web3.setProvider(ganache.provider());
作为一般的http服务器(server):
var ganache = require("ganache-cli");
var server = ganache.server(); server.listen(port, function(err, blockchain) {...});
.provider()和.server()都有一个对象,它允许你指定ganache-cli的行为。该参数是可选的。可用的选项有:
· "accounts": Array of Object's. 每个对象都应该有一个十六进制值的平衡键。还可以指定密钥secretKey,它代表帐户的私钥。如果没有secretKey,则地址是使用给定余额自动生成的。如果指定,则该密钥用于确定帐户的地址。
· "debug": boolean - 用于调试的输出VM操作码
· "logger": Object - 实现log()函数的对象,如控制台。
· "mnemonic": 使用特定的HD钱包助记符来生成初始地址。
· "port": 作为服务器运行时要侦听的端口号。
· "seed": 使用任意数据生成要使用的HD钱包助记符。
· "total_accounts": number - 在启动时生成的帐户数量。
· "fork": string - 与上面的--fork选项相同
· "network_id": integer - 与上面的--networkId选项相同
· "time": Date - 第一个块应该开始的日期。使用此功能以及evm_increaseTime方法来测试时间相关的代码。
· "locked": boolean - 帐户是否默认锁定。
· "db_path": String - 指定目录的路径以保存链式数据库。如果数据库已经存在,ganache-cli会初始化该链,而不是创建一个新链。
· "account_keys_path": String - 指定用于保存帐户和私钥的文件,以进行测试。
· "vmErrorsOnRPCResponse": boolean - 是否将事务故障作为RPC错误传送。对于与其他客户端(如geth和Parity)兼容的错误报告行为,设置为false。
### Implemented Methods 目前实现的RPC方法是: * bzz_hive (stub) * bzz_info (stub) * debug_traceTransaction * eth_accounts * eth_blockNumber * eth_call * eth_coinbase * eth_estimateGas * eth_gasPrice * eth_getBalance * eth_getBlockByNumber * eth_getBlockByHash * eth_getBlockTransactionCountByHash * eth_getBlockTransactionCountByNumber * eth_getCode (only supports block number “latest”) * eth_getCompilers * eth_getFilterChanges * eth_getFilterLogs * eth_getLogs * eth_getStorageAt * eth_getTransactionByHash * eth_getTransactionByBlockHashAndIndex * eth_getTransactionByBlockNumberAndIndex * eth_getTransactionCount * eth_getTransactionReceipt * eth_hashrate * eth_mining * eth_newBlockFilter * eth_newFilter (includes log/event filters) * eth_protocolVersion * eth_sendTransaction * eth_sendRawTransaction * eth_sign * eth_syncing * eth_uninstallFilter * net_listening * net_peerCount * net_version * miner_start * miner_stop * personal_listAccounts * personal_lockAccount * personal_newAccount * personal_unlockAccount * personal_sendTransaction * shh_version * rpc_modules * web3_clientVersion * web3_sha3 还有一些特殊的非标准方法不包含在原始RPC规范中: * evm_snapshot : 快照当前块的区块链状态。没有参数。返回创建的快照的整数ID。 * evm_revert : 将区块链状态恢复为上一个快照。采用一个参数,即要恢复的快照ID。如果没有传递快照ID,它将恢复到最新的快照。返回true。 * evm_increaseTime : 及时向前跳。取一个参数,即以秒为单位增加的时间量。返回总时间调整,以秒为单位。 * evm_mine : 强制挖矿。没有参数。开采矿块与是否采矿开始或停止无关。 ### Unsupported Methods * eth_compileSolidity: 如果你想用Javascript编译Solidity,请参阅solc-js项目。
以太坊节点
为什么我需要连接到一个节点?
以太坊协议定义了一种方法,用于人们通过网络与智能合约相互作用。为了获得关于合约、账户余额和新交易状态等最新的信息,协议需要与网络上的节点进行连接。这些节点不断地共享最新的数据。
Web3.Py是用于连接这些节点的Python库。它不在内部运行它自己的节点。
如何选择使用哪个节点?
由于以太坊的特点,这在很大程度上由个人的偏好来决定,但它会对安全性和可用性有重大影响。此外,节点软件正在快速发展,所以请需要对当前可选项进行研究。我们不推荐任何特定的节点,但列出一些流行的选项和一些基本细节。
最关键的决定之一是使用本地节点还是托管节点。来看看本地VS托管节点的简单含义:
- 本地节点:本地节点由你自己启动并控制。它的安全和你的环境安全性一样,在于你自身。当你在机器上运行
geth
或parity
时,你就是在运行本地节点。 - 托管节点:托管节点由其他人控制。当连接到
Infura
时,连接到托管节点。
本地节点比托管节点有更好的安全性。恶意托管节点可以给你提供不正确的信息,用IP地址记录你发送的交易,或者只是脱机运行。不正确的信息会引起各种各样的问题,甚至包括资产流失。
另一方面,在本地节点上,你的机器正在逐个验证网络上的所有交易,并提供最新状态。不幸的是,这意味着使用大量的磁盘空间,有时显著的带宽和计算。另外,下载完整的区块链历史记录有很大的前期成本。
如果希望让节点管理密钥(流行的选项),则必须使用本地节点。注意,即使在自己的机器上运行一个节点,你仍然要信任节点软件,并在该节点上创建的任何帐户。
最流行的自运行节点选项是:
你可以在ethdocs.org中找到一个更完整的节点软件列表。
有些人认为,从零开始同步本地节点所需的时间太久,尤其是如果它们只是刚开始学习以太坊的话。解决这个问题的一种方法就是使用托管节点。
最流行的托管节点选项是Infura。你可以连接它,就像它是一个本地节点,有几个注意事项。它不能(也不应该)为你提供私钥,这意味着一些常见的方法,如w3.eth.sendTransaction()
不可直接使用。要向托管节点发送交易,请阅读有关本地私钥的工作。
一旦决定要选择什么节点选项,就需要选择连接哪个网络。通常,你在公有链和测试链之间进行选择。
我可以用MetaMask作为节点吗?
MetaMask
不是一个节点。它是一个与节点交互的接口。大致上,如果你把Web3.Py变成浏览器扩展,MetaMask就和web3.py是差不多的。
默认情况下,MetaMask连接到一个Infura节点。也还可以设置MetaMask使用本地运行的节点。
如果你试图使用已在MetaMask中创建的帐户,请参阅如何使用Web3.Py中的MetaMask帐户?
我应该连接哪个网络?
一旦你回答了我该如何选择使用哪一个节点?你必须选择连接哪个网络。对于某些场景来说,这是很容易的:如果你有以太ether,你想消费它,或者你想与任何生产网络上的智能合约交互,那么你连接到以太坊公有链网络上。
如果你想测试这些东西而不使用真正的以太ether,那么你需要连接到测试链。有几个测试链可供选择。一个测试网络Ropsten
,它是最类似于生产网络的。然而,当你想测试一个智能合约的时候,已经有垃圾邮件和攻击发生,这是有破坏性的。
有一些替代网络限制了垃圾邮件和攻击的破坏,但是它们对节点软件没有完全标准化。geth运行自己的(Rinkeby),Parity运行自己(Kovan)。在这个Stackexchange Q&A.中看到一个完整的比较。
所以我们简略的讲,选择这样的方式:
- 如果使用
Parity
,连接到Kovan
- 如果使用
Geth
,连接到Rinkeby
- 如果使用不同的节点,或测试挖掘,连接到
Ropsten
他们的每一个网络都有自己版本的ether。生产网络即公有链以太ether必须购买,自然,测试链上的以太ether通常是免费的。看看测试网是如何获得以太?
一旦确定了连接哪个网络,并为该网络设置节点,就需要决定如何连接它。在大多数节点中有一些选项。请参见选择如何连接到节点。
连接到Testnets
Metamask
Metamask完全支持Ropsten,Kovan和Rinkeby测试网,但也可以连接到其他测试网和本地网。在Metamask中,只需单击“main network”下拉菜单,即可切换网络。MetaMask还提供了一个“buy”测试ether的选项,该选项将你引导至你可以请求免费测试以太网的faucet。如果使用Ropsten测试网,则可以从Ropsten测试faucet服务中获取ether。你可以从此页面访问此faucet。它需要Metamask扩展才能工作。https://faucet.metamask.io/
Infura
当MetaMask连接到测试网络时,它使用Infura服务提供商来访问JSON-RPC接口。Infura诞生的目的是为ConsenSys内部项目提供稳定可靠的RPC访问。除了JSON-RPC API之外,Infura还提供REST(表述性状态转移)API,IPFS(星际文件系统,即去中心化存储)API和Websockets(即流式传输)API。
Infura为Ethereum主网,Ropsten,Kovan,Rinkeby和INFURAnet(用于Infura的定制测试网络)提供网关API。
要通过MetaMask使用Infura进行较低级别的活动,你不需要账户。要直接使用API,你需要注册一个账户并使用Infura提供的API密钥。
有关Infura的更多信息,请访问:
Remix集成开发环境(IDE)
Remix IDE可用于在主网和测试网上部署和交互智能合约,包括Ropsten,Rinkeby和Kovan(Web3提供者使用Infura地址和API密钥或通过Injected Web3使用MetaMask中选择的网络)和Ganache( Web3提供端点http://localhost:8545)
https://github.com/ethereum/remix/blob/master/docs/run_tab.rst https://medium.com/swlh/deploy-smart-contracts-on-ropsten-testnet-through-ethereum-remix-233cd1494b4b
Geth
Geth本身支持Ropsten和Rinkeby网络。要连接到Ropsten网络,请使用命令行参数:
geth --testnet
这将开始同步Ropsten区块链。名为 testnet 的新目录将在你的主Ethereum数据目录中创建。一个 keystore 目录将在 testnet 内部创建,并将存储你的testnet帐户的私钥。在撰写本文时,Ropsten区块链比以太坊主区块链小得多:大约14GB的数据。由于测试网需要的资源较少,因此首先在测试网上设置并测试你的代码会更简单。
与testnet的交互与mainnet类似。你可以使用控制台启动Geth testnet,方法是运行:
geth --testnet console
这使得执行操作成为可能,例如开设新账户,检查余额,检查其他以太坊地址的余额等。 在Geth控制台之外运行时,只需将--testnet
参数添加到命令行指令中,就可以执行类似于在主网上执行的操作。作为列举所有可用的testnet帐户及其地址的示例,请运行:
geth --testnet account list
Tip | 虽然小得多,但测试网仍需要一些时间才能完全同步。 |
---|
你可以通过在geth交互式控制台中运行以下命令来检查geth是否已完成同步测试网络:
eth.getBlock("latest").number
一旦你的testnet节点完全同步,这应该返回一个非0的数字。你可以将该编号与已知的testnet区块浏览器中的最新块进行比较,例如https://ropsten.etherscan.io/
同样,要连接到Rinkeby测试网络,请使用命令行参数:
geth --rinkeby
Parity
Parity客户端支持Ropsten和Kovan测试网络。你可以用+chain+参数选择你要连接的网络。例如,要同步Ropsten测试网络:
parity --chain ropsten
同样,要同步Kovan测试网络,请使用:
parity --chain kovan
如何使用truffle + Atom
进行以太坊公开透明拍卖智能合约的编写,以及如何使用ganache-cli
进行智能合约的交互测试。
1 Trueffle框架编写代码
1.1 建立项目
新建项目文件夹SimpleAuction
开启另一个终端窗口,输入以下命令建立项目:
**
PS H:\TestContract> mkdir SimpleAuction
目录: H:\TestContract
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 2018/7/12 12:01 SimpleAuction
PS H:\TestContract> cd SimpleAuction
PS H:\TestContract\SimpleAuction> truffle init
Downloading...
Unpacking...
Setting up...
Unbox successful. Sweet!
Commands:
Compile: truffle compile
Migrate: truffle migrate
Test contracts: truffle test
PS H:\TestContract\SimpleAuction>
目录结构:
目录结构.PNG
打开contracts
文件夹,再通过truffle命令创建SimpleAuction
合约,必须通过命令行创建。
**
PS H:\TestContract\SimpleAuction> cd contracts
PS H:\TestContract\SimpleAuction\contracts> truffle create contract SimpleAuction
\contracts
:存放智能合约源代码的地方,可以看到里面已经有一个sol
文件,我们开发的BlindAuction.sol
文件就存放在这个文件夹。\migrations
:这是Truffle
用来部署智能合约的功能,待会儿我们会新建一个类似1_initial_migration.js
的文件来部署BlindAuction.sol
。\test
:测试智能合约的代码放在这里,支持js
与sol
测试。truffle-config.js
和truffle.js
:Truffle
的配置文件,需要配置要连接的以太坊网络。
1.2 创建合约
在Atom中打开项目文件夹SimpleAuction
,开始编辑contracts
文件夹下的SimpleAuction.sol
文件。
整个流程如下:
- 我们首先要记录拍卖的基本数据:谁是受益人,什么时候结束
- 我们开启拍卖,一个出价更高的人会替代之前出价最高的人
- 当出现替代时,还要退还之前出价高的人的代币
- 出于安全的考虑,退还过程将由之前用户主动发起
**
pragma solidity ^0.4.22;
contract SimpleAuction {
// 定义参数:受益人、拍卖结束时间
address public beneficiary;
uint public auctionEnd;
uint public biddingTime;
uint public auctionStart;
// 最高出价者
address public highestBidder;
// 最高出价
uint public highestBid;
mapping (address => uint) pendingReturns; // 用于取回之前的出价
// 拍卖是否结束,不允许被修改
bool ended;
// 最高出价变动时调用事件
event HighestBidIncreased(address _bidder, uint _amount);
// 拍卖结束时调用事件
event AuctionEnded(address _winner, uint _amount);
// The following is a so-called natspec comment,
// recognizable by the three slashes.
// It will be shown when the user is asked to
// confirm a transaction.
// 构造函数
// 创建一个拍卖对象,初始化参数值:受益人、拍卖持续时间
constructor(uint _biddingTime, address _beneficiary) public {
beneficiary = _beneficiary;
auctionStart = now;
biddingTime = _biddingTime;
auctionEnd = now + _biddingTime; // now: current block's timestamp
}
// 使用代币进行拍卖,当拍卖失败时,会退回代币
// 出价功能:包括交易参数
// 当出价不是最高,资金会被自动退回
function bid() public payable{
// 不需要参数,因为都被自动处理了
// 当一个函数要处理Ether时,需要包含payable的修饰符
// 如果超过了截止期,交易撤回
if(now > auctionStart + biddingTime){
revert();
}
// 如果出价不够,交易撤回
if (msg.value <= highestBid){
revert();
}
// 如果出价最高,当前出价者作为最高出价人
if (highestBidder != 0){
//highestBidder.send(highestBid); // send ether(in wei)to the address
// 调用highestBidder.send(highestBid)的方式是危险的
// 因为会执行不知道的协议
// 因此最好让用户自己取回自己的代币
pendingReturns[highestBidder] += highestBid;
}
highestBidder = msg.sender;
highestBid = msg.value;
emit HighestBidIncreased(msg.sender, msg.value);
}
// 取回被超出的拍卖前的出资
function withdraw() public returns (bool){
uint amount = pendingReturns[msg.sender];
if (amount > 0){
// 需要提前设置为0,因为接收者可以在这个函数结束前再次调用它
pendingReturns[msg.sender] = 0;
if (!msg.sender.send(amount)){
// 不需要throw,直接重置代币数量即可
pendingReturns[msg.sender] = amount;
return false;
}
}
return true;
}
// 结束拍卖,将金额给予受益人
function auctionEnd() public {
// 与其他协议交互的最好遵循以下顺序的三个步骤:
// 1.检查状况
// 2.修改状态
// 3.合约交互
// 如果这三个步骤混在一起,那么攻击者可能通过多次调用这个函数来进行攻击
// 1.检查状况
if (now <= auctionEnd) {
revert();
}
if(ended){
revert();
}
// require (now >= auctionEnd, "Auction not yet ended.");
// require (!ended, "auctionEnd has already called.");
// 2.修改状态
ended = true;
emit AuctionEnded(highestBidder, highestBid);
// 3.合约交互
beneficiary.transfer(highestBid);
}
function () public{
revert();
}
}
**Note:**一定要有最后的function()
不然调用bid()
总会报错。
1.3 编译合约
在项目根目录SimpleAuction
的powershell中执行truffle compile
命令:
**
PS H:\TestContract\SimpleAuction> truffle compile
Compiling .\contracts\Migrations.sol...
Compiling .\contracts\SimpleAuction.sol...
Compilation warnings encountered:
/H/TestContract/SimpleAuction/contracts/SimpleAuction.sol:32:5: Warning: Defining constructors as functions with the same name as the contract is deprecated. Use "constructor(...) { ... }" instead.
function SimpleAuction(uint _biddingTime, address _beneficiary) public {
^ (Relevant source part starts here and spans across multiple lines).
Writing artifacts to .\build\contracts
PS H:\TestContract\SimpleAuction>
2 Ganache-cli 部署测试智能合约
2.1 启动ganache-cli
打开powershell
终端,可以看到ganache-cli
启动后自动建立了10
个账号(Accounts),与每个账号对应的私钥(Private Key)。每个账号中都有100
个测试用的以太币(Ether)。
Note. ganache-cli仅运行在内存中,因此每次重开时都会回到全新的状态。
**
C:\Users\aby>ganache-cli
Ganache CLI v6.1.6 (ganache-core: 2.1.5)
Available Accounts
==================
(0) 0x553cc75f3099ec4f6ba57e2580f65f982dfcbc67 (~100 ETH)
(1) 0xed700e53205af0b9daa4548cc48465cab55d376c (~100 ETH)
(2) 0x3fdcbf82e2343f3d88a5ec967800ee188b7bc440 (~100 ETH)
(3) 0x76757998260e9d7bb01e2761a4abacffd73162e2 (~100 ETH)
(4) 0x0e53c9c2cb04a7b46acd3e94703434c6e0b1e1c9 (~100 ETH)
(5) 0xfb7db3e68877e08df576d0071d1f68b1ad185d50 (~100 ETH)
(6) 0x0ff87726fac78f3675751fc67c3b2b174aa7ec68 (~100 ETH)
(7) 0x8bee12196d694c864b00c4b19295b0c0a067bb1a (~100 ETH)
(8) 0x14e498d9ca25b050e17fb702e1325135da21e9e7 (~100 ETH)
(9) 0x1aed5d4a441d50716313426bdc519b8263c37bfc (~100 ETH)
Private Keys
==================
(0) 0x260ed471ee7c4897c3b478a3ae36fb47f7b5dc4e2bfaeea2062e53edbc664f8e
(1) 0xbbaaf6be4797da3598ca72969b46a04569448a7d67185f96b55b2618327176b6
(2) 0xca9fd89f5c5ada96b35afd2b5782c14b2286894454178e9225df2aa3bed133d4
(3) 0x5e282a3ddb12f4f77318a30f2e653b1764b3fac52f43d575e5b1cd9564e6c6f6
(4) 0x17f336fbcc161a20f38979d787eb9f1ae33fea870acdd37142dc3be3bcf127e7
(5) 0x9521e42f1d3a03079883032a47a7146cd76275bf0bf206b5f0467c8a2249e553
(6) 0x4fd49f71649d35d9d93d5c9be03f554844ee68a7c45ae9c899f08ead8d2e0405
(7) 0x0221b4004e5b70684a0d19926739d5ed2e420d9e7c908a734763fbbf51d13133
(8) 0xd54daf5bfda3d3491ff5b3cefbba6a9e88b68d8ae322135337e9d530523308a6
(9) 0x73420d353ed74fd5939590dc30c2e7a966727f6561c84526ce71f04e0c668bec
HD Wallet
==================
Mnemonic: give sick brand tail farm mechanic fence flock submit boost fiction magnet
Base HD Path: m/44'/60'/0'/0/{account_index}
Gas Price
==================
20000000000
Gas Limit
==================
6721975
Listening on 127.0.0.1:8545
2.2 部署合约
(1)migrations
目录下创建一个名字叫做2_deploy_contracts.js
的文件。文件中的内容为:
**
var SimpleAuction = artifacts.require('./SimpleAuction.sol');
module.exports = function(deployer) {
deployer.deploy(SimpleAuction);
}
(2)修改truffle.js
文件,连接本地ganache-cli
环境。参数在最开初始化ganache-cli
环境的窗口可以看到。
**
module.exports = {
// See <http://truffleframework.com/docs/advanced/configuration>
// to customize your Truffle configuration!
networks: {
development:{
host: "127.0.0.1",
port: 8545,
network_id: "*" // match any network id
}
}
};
(3)现在执行truffle migrate
命令,我们可以将SimpleAuction.sol
原始码编译成Ethereum bytecode
。
**
PS H:\TestContract\SimpleAuction> truffle migrate
Using network 'development'.
Running migration: 2_deploy_contracts.js
Deploying SimpleAuction...
Error encountered, bailing. Network state unknown. Review successful transactions manually.
Error: SimpleAuction contract constructor expected 2 arguments, received 0
at C:\Users\aby\AppData\Roaming\npm\node_modules\truffle\build\webpack:\packages\truffle-contract\contract.js:390:1
at new Promise (<anonymous>)
at C:\Users\aby\AppData\Roaming\npm\node_modules\truffle\build\webpack:\packages\truffle-contract\contract.js:374:1
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:188:7)
PS H:\TestContract\SimpleAuction>
发现报错,Error: SimpleAuction contract constructor expected 2 arguments, received 0
,可以在部署时进行构造函数的赋值,不必修改智能合约内容:在2_deploy_contracts.js
中,修改deploy脚本,deployer.deploy(SimpleAuction, 20, "0x553cc75f3099ec4f6ba57e2580f65f982dfcbc67");
即可。
**
PS H:\TestContract\SimpleAuction> truffle migrate
Using network 'development'.
Running migration: 2_deploy_contracts.js
Deploying SimpleAuction...
... 0x3b611b5e12c85ba20b59c66b7a2b997a580fab7dbe1431be323fcdd3f9d07f62
SimpleAuction: 0x7307036020a33ae1996ff74a5044123eda03302c
Saving successful migration to network...
... 0xb646aa61fefa4933f6102d04e7af232c10c0c43f9384df49ab250f54a083c2c6
Saving artifacts...
PS H:\TestContract\SimpleAuction>
2.3 与合约交互
truffle
提供命令行工具,执行truffle console
命令后,可用Javascript
来和刚刚部署的合约互动。
**
PS H:\TestContract\SimpleAuction> truffle console
truffle(development)>
使用web3.eth.accounts
会输出ganache-cli
网络上的所有账户。
**
truffle(development)> web3.eth.accounts
[ '0x553cc75f3099ec4f6ba57e2580f65f982dfcbc67',
'0xed700e53205af0b9daa4548cc48465cab55d376c',
'0x3fdcbf82e2343f3d88a5ec967800ee188b7bc440',
'0x76757998260e9d7bb01e2761a4abacffd73162e2',
'0x0e53c9c2cb04a7b46acd3e94703434c6e0b1e1c9',
'0xfb7db3e68877e08df576d0071d1f68b1ad185d50',
'0x0ff87726fac78f3675751fc67c3b2b174aa7ec68',
'0x8bee12196d694c864b00c4b19295b0c0a067bb1a',
'0x14e498d9ca25b050e17fb702e1325135da21e9e7',
'0x1aed5d4a441d50716313426bdc519b8263c37bfc' ]
我们需要准备一些测试账户。
它会把第一个帐户的地址分配给变量account0
,第二个帐户分配给变量account1
。Web3
是一个JavaScript API
,它将RPC
调用包装起来以方便我们与区块链进行交互。
**
truffle(development)> acc1 = web3.eth.accounts[1]
'0xed700e53205af0b9daa4548cc48465cab55d376c'
我们可以看一下拍卖发起人以及第一个账户的余额:
**
truffle(development)> web3.eth.getBalance(address)
BigNumber { s: 1, e: 19, c: [ 999060, 70900000000000 ] }
truffle(development)> web3.eth.getBalance(acc1)
BigNumber { s: 1, e: 20, c: [ 1000000 ] }
此时我们用acc1
调用bid()
,发送2 ether
。
**
truffle(development)> contract.bid({from:acc1, value:web3.toWei(2,"ether")})
{ tx: '0xfad44c38347ee56220bf3810c4e6904b6c1e10a3cbd6495da9008533ffc5a1ee',
receipt:
{ transactionHash: '0xfad44c38347ee56220bf3810c4e6904b6c1e10a3cbd6495da9008533ffc5a1ee',
transactionIndex: 0,
blockHash: '0xd9657a80fd84cb9a6582f560f3ce91dcc7f571932772d7f4ce4dd60ecd0f25c9',
blockNumber: 5,
gasUsed: 63946,
cumulativeGasUsed: 63946,
contractAddress: null,
logs: [ [Object] ],
status: '0x1',
logsBloom: '0x00004000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000' },
logs:
[ { logIndex: 0,
transactionIndex: 0,
transactionHash: '0xfad44c38347ee56220bf3810c4e6904b6c1e10a3cbd6495da9008533ffc5a1ee',
blockHash: '0xd9657a80fd84cb9a6582f560f3ce91dcc7f571932772d7f4ce4dd60ecd0f25c9',
blockNumber: 5,
address: '0x4faf92d14a7ac059b4fb97975376f52c32e1abe1',
type: 'mined',
event: 'HighestBidIncreased',
args: [Object] } ] }
并且查看此时acc1
余额,以及highestBid
。
**
truffle(development)> contract.highestBid.call()
BigNumber { s: 1, e: 18, c: [ 20000 ] }
truffle(development)> web3.eth.getBalance(acc1)
BigNumber { s: 1, e: 19, c: [ 979936, 5400000000000 ] }
由于拍卖时间设置较短,所以结束拍卖。
**
truffle(development)> contract.auctionEnd({from:address})
{ tx: '0x8701d85470423c67608d14eaa96e807a156981bf9d47826aa0bfcd69b4711fcc',
receipt:
{ transactionHash: '0x8701d85470423c67608d14eaa96e807a156981bf9d47826aa0bfcd69b4711fcc',
transactionIndex: 0,
blockHash: '0x5dc6e9b00d495f19b2e3427b128c5b70544191a2bf8c78d9deabf6a79deaf097',
blockNumber: 8,
gasUsed: 51912,
cumulativeGasUsed: 51912,
contractAddress: null,
logs: [ [Object] ],
status: '0x1',
logsBloom: '0x00004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000001000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000' },
logs:
[ { logIndex: 0,
transactionIndex: 0,
transactionHash: '0x8701d85470423c67608d14eaa96e807a156981bf9d47826aa0bfcd69b4711fcc',
blockHash: '0x5dc6e9b00d495f19b2e3427b128c5b70544191a2bf8c78d9deabf6a79deaf097',
blockNumber: 8,
address: '0x4faf92d14a7ac059b4fb97975376f52c32e1abe1',
type: 'mined',
event: 'AuctionEnded',
args: [Object] } ] }
2.4 拍卖过程
由于上面结束拍卖,我们现在重新设置拍卖时长,并连贯顺序地进行一次拍卖过程。由于ganache-cli
每次都重新启动,都会随机创建10个账户,所以这里地址不同。
同样创建其他账户
**
PS H:\TestContract\SimpleAuction> truffle console
truffle(development)> address = web3.eth.accounts[0]
'0x0bf5cd9a0313121dda0f91e2da6ff9479be4ec5c'
truffle(development)> acc1 = web3.eth.accounts[1]
'0xdfa9eca806498bcf1c083b14ab20c53b2a3815a5'
truffle(development)> acc2 = web3.eth.accounts[2]
'0xc8dd1ff47b6e7067f1b4c2ec18fc60f36da97c46'
truffle(development)> acc3 = web3.eth.accounts[3]
'0xf758b41d6bb008e6f64bea810ecc38be46299ae6'
truffle(development)> acc4 = web3.eth.accounts[4]
'0x0830f17d845b90d8dc965ddb73b43e0f46e06bb8'
address
调用创建一个拍卖:
**
truffle(development)> let contract
undefined
truffle(development)> SimpleAuction.deployed().then(instance => contract = instance)
我们可以看一下,当前创建拍卖的收益人,最高价,以及各个账户的余额等。
**
truffle(development)> contract.beneficiary.call()
'0x0bf5cd9a0313121dda0f91e2da6ff9479be4ec5c'
truffle(development)> contract.auctionStart.call()
BigNumber { s: 1, e: 9, c: [ 1531385059 ] }
truffle(development)> contract.highestBid.call()
BigNumber { s: 1, e: 0, c: [ 0 ] }
truffle(development)> contract.highestBidder.call()
'0x0000000000000000000000000000000000000000'
truffle(development)> web3.eth.getBalance(address)
BigNumber { s: 1, e: 19, c: [ 999060, 70900000000000 ] }
truffle(development)> web3.eth.getBalance(acc1)
BigNumber { s: 1, e: 20, c: [ 1000000 ] }
truffle(development)> web3.eth.getBalance(acc2)
BigNumber { s: 1, e: 20, c: [ 1000000 ] }
使用acc1
进行一次bid()
:
**
truffle(development)> contract.bid({from:acc1, value:web3.toWei(2,"ether")})
{ tx: '0x68d752b6afc9b1bbb5c987bf4c4faad3907ea312f552f9280573a22dca662f05',
receipt:
{ transactionHash: '0x68d752b6afc9b1bbb5c987bf4c4faad3907ea312f552f9280573a22dca662f05',
transactionIndex: 0,
blockHash: '0x30f1edf3689d8af7639c2bf2475e932d34155891d333512e3b97dffac25f5220',
blockNumber: 5,
gasUsed: 63946,
cumulativeGasUsed: 63946,
contractAddress: null,
logs: [ [Object] ],
status: '0x1',
logsBloom: '0x00000000000000000000000000800080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' },
logs:
[ { logIndex: 0,
transactionIndex: 0,
transactionHash: '0x68d752b6afc9b1bbb5c987bf4c4faad3907ea312f552f9280573a22dca662f05',
blockHash: '0x30f1edf3689d8af7639c2bf2475e932d34155891d333512e3b97dffac25f5220',
blockNumber: 5,
address: '0x55ac96c388568a6d2e233a8dbb9c1e5be1c3e4c8',
type: 'mined',
event: 'HighestBidIncreased',
args: [Object] } ] }
查看当前acc1
的余额,以及highestBid
,可以看到acc1
减少了,highestBid
变成了2000
:
**
truffle(development)> web3.eth.getBalance(acc1)
BigNumber { s: 1, e: 19, c: [ 979936, 5400000000000 ] }
truffle(development)> contract.highestBid.call()
BigNumber { s: 1, e: 18, c: [ 20000 ] }
同样的,使用acc2
,acc3
,acc4
分别进行bid
:
Account | Value |
---|---|
acc1 | 2 |
acc2 | 4 |
acc3 | 1 |
acc4 | 6 |
可以看到当bid
价格比highestBid
高时会出现上面的结果,如果低,则会被revert()
抛出异常。
**
truffle(development)> contract.bid({from:acc3, value:web3.toWei(1,"ether")})
Error: VM Exception while processing transaction: revert
at XMLHttpRequest._onHttpResponseEnd (C:\Users\aby\AppData\Roaming\npm\node_modules\truffle\build\webpack:\~\xhr2\lib\xhr2.js:509:1)
at XMLHttpRequest._setReadyState (C:\Users\aby\AppData\Roaming\npm\node_modules\truffle\build\webpack:\~\xhr2\lib\xhr2.js:354:1)
at XMLHttpRequestEventTarget.dispatchEvent (C:\Users\aby\AppData\Roaming\npm\node_modules\truffle\build\webpack:\~\xhr2\lib\xhr2.js:64:1)
at XMLHttpRequest.request.onreadystatechange (C:\Users\aby\AppData\Roaming\npm\node_modules\truffle\build\webpack:\~\web3\lib\web3\httpprovider.js:128:1)
at C:\Users\aby\AppData\Roaming\npm\node_modules\truffle\build\webpack:\packages\truffle-provider\wrapper.js:134:1
at C:\Users\aby\AppData\Roaming\npm\node_modules\truffle\build\webpack:\~\web3\lib\web3\requestmanager.js:86:1
at Object.InvalidResponse (C:\Users\aby\AppData\Roaming\npm\node_modules\truffle\build\webpack:\~\web3\lib\web3\errors.js:38:1)
此时,调用结束拍卖函数auctionEnd()
,也会被revert()
抛出异常,因为当前时间小于结束时间auctionEnd
。
过一段时间,再次调用auctionEnd()
:
**
truffle(development)> contract.auctionEnd.sendTransaction({from:address})
'0xfe1ed3d6fb181d2f0bb122edcadb99c735abee31f4d620f25a7e8ca7c8677014'
再看看此时受益者,也就是address
地址的余额:
可以明显看到他增加了6 ether
,得到收益。
**
truffle(development)> web3.eth.getBalance(address)
BigNumber { s: 1, e: 20, c: [ 1058987, 14600000000000 ] }
最后每个报价者取回自己的余额,竞拍失败者可以取回钱。
**
truffle(development)> contract.withdraw({from:acc1})
{ tx: '0x44d9f709c52d2e2b27e0d075b2afbd3fb9f065a1d66b70026a6657bd48a3c1ef',
receipt:
{ transactionHash: '0x44d9f709c52d2e2b27e0d075b2afbd3fb9f065a1d66b70026a6657bd48a3c1ef',
transactionIndex: 0,
blockHash: '0xa5b1f3a0e2670e88462bd1c6338fe4eaa1afc6496a81fc508899fedc86d38e8a',
blockNumber: 11,
gasUsed: 19482,
cumulativeGasUsed: 19482,
contractAddress: null,
logs: [],
status: '0x1',
logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' },
logs: [] }
acc1
查看自己当前账户余额:
**
truffle(development)> web3.eth.getBalance(acc1)
BigNumber { s: 1, e: 19, c: [ 999916, 57200000000000 ] }
其他账户做同样操作。
2.5 账户变化
最后可以总结一下,每个参与拍卖的账户余额变化:
账户 | 1.初始化 | 2.创建合约 | 3.bid | 4.拍卖结束及取回 |
---|---|---|---|---|
address | 1000000 | 999060, 70900000000000 | 999060, 70900000000000 | 1058987, 14600000000000 |
acc1 | 1000000 | 1000000 | 979936, 5400000000000(2) | 999916, 57200000000000 |
acc2 | 1000000 | 1000000 | 4 | 999925, 81300000000000 |
acc3 | 1000000 | 1000000 | 1 | 999956, 8300000000000 |
acc4 | 1000000 | 1000000 | 6 | 939923, 41400000000000 |
由于发起交易需要消耗gas
所以,账户1,2,3余额会比最开始少一点。而acc4
则因为拍卖成功,相对较少的较多。
如何安装以太坊钱包Parity
编程狂魔关注
**0.5242018.08.26 21:56:45字数 1,565阅读 6,206
Parity内置易用的以太坊钱包和Ðapp环境,并可通过Web浏览器访问。Parity被认为是与以太坊区块链交互的最快速,最安全的方式,并为公共以太网网络的大部分基础设施提供支持,很受欢迎。
对于开发者使用Parity或通过构建扩展协议来开发智能合约和去中心化应用程序。
Parity是一个很活跃的开源项目。
二进制
这里有几种可行的方法。你可以通过源码构建Parity Ethereum
;你可以使用为Linux,Mac/Homebrew和Windows操作系统提供的最新版本的二进制文件启动Parity Ethereum
,或者如果你使用的是Ubuntu Snappy
平台,则只需使用Snappy App
;其他类Unix环境应该可以工作,但我们不会花费太多精力来支持他们,尽管这永远是受欢迎的。
一行代码就可以通过二进制文件安装程序
这种方法比从源代码构建方法快,但它只适用于安装了Homebrew的Ubuntu和Mac。它将为你安装和配置Parity Ethereum
客户端。要使用该脚本,只需运行:
**
$ bash <(curl https://get.parity.io -L)
使用适合你平台的二进制文件
提供了Parity Ethereum
的二进制文件。如果你想尝试这些,可以在Releases页面上下载。
- Linux
- 从上面的链接下载最新版本。
- 通过运行
chmod u+x parity
使parity文件可执行。 - 使用你最喜爱的方式启动Parity:
./parity --chain dev --jsonrpc-apis personal
- Windows和Mac
- 下载二进制文件并双击它。
依赖
对于Linux系统:
- Ubuntu,Debian
**
$ apt-get install build-essential openssl libssl-dev libudev-dev
- CentOS
**
$ yum install openssl libssl-devel libudev-devel openssl-devel
$ yum group install "Development Tools"
从源代码构建
要从源代码构建Parity Ethereum
,请遵循README中的说明。
对于以太坊密钥管理,可以使用Ethstore。要编译一下才能使用。
**
$ cargo build --release -p ethstore-cli
在Parity Ethereum根目录中使用,看看帮助:
**
$ ./target/release/ethstore --help
使用Docker构建
Docker非常适合兼容操作系统,发行版,安装和构建环境之间的差异而不会妨碍编码和开发的乐趣。为了快速入门,我们将使用docker设置最小的Ubuntu安装并从那里开始。但是,你不需要这样,你可以使用类似的指令来完成任何Linux安装或Mac Homebrew系统的构建工作,当然如果它在某些小众发行版中不起作用也不要抱怨或者花费很长的时间非要来搞定它。相反,我们应该让docker镜像继续工作,然后找出差异是什么。
注意:确保你已在使用docker。
**
$ docker run -it ubuntu bash
这将为你提供临时的docker环境。
Grab Rust
注意:如果你的环境中已经有Rust,则无需为此烦恼。
下面的命令将在Linux和OS X上下载并安装Rust:
**
$ curl https://sh.rustup.rs -sSf | SH
如果你使用的是Windows,请确保已安装带有C++支持的Visual Studio 2015。确保在VS2015 x64 Native Tools命令提示符
中运行所有后续内容。
注意:我们不支持Windows上的GNU工具集。不要安装它,也不要安装任何的Rust版本并使用它。如果你在任何地方看到x86_64-pc-windows-gnu
,那你就是错了!
下载并运行rustup,使用以下命令设置MSVC工具链:
**
$ rustup default stable-x86_64-pc-windows-msvc
安装和构建Parity
接下来,克隆Parity Ethereum存储库:
**
$ git clone https://github.com/paritytech/parity
$ cd parity
为了测试方便,还要更新子模块:
**
$ git submodule init
$ git submodule update
你可以建立:
**
$ cargo build
你可以使用以下命令运行单元测试:
**
$ ./test.sh
你可以使用以下方式运行共识测试:
**
$ cargo test --release --features ethcore/json-tests -p ethcore
要在Linux和Mac OS上安装Parity Ethereum,只需构建它并将其复制到/usr/local/bin
:
**
$ cargo build --release && cp target/release/parity /usr/local/bin
对于Windows,使用copy
将其复制到C:/Windows:
**
$ cargo build --release
$ copy target/release/parity C:/Windows
你可以启动客户端并与网络同步:
**
$ cargo run --release
要获得有关parity
客户端的命令行选项的帮助,请使用--help
:
**
$ cargo run --release --help
关于使用Docker备份datadir的注意事项
如果你需要保留区块链文件,密钥等,你应该使用--base-path
选项运行映像然后安装它,例如:
**
$ docker run --name parity -v /srv/parity:/mnt ethcore/parity:beta --base-path /mnt
Raspberry Pi上的Ubuntu Snappy
RasPi有Ubuntu Snappy可以用来构建,可在Parity Ethereum Snappy存储库中找到。
======================================================================
如果你希望通过熟悉的开发语言来开发以太坊dapp,可以学习下面的教程:
- C#以太坊,主要讲解如何使用C#开发基于.Net的以太坊应用,包括账户管理、状态与交易、智能合约开发与交互、过滤器和事件等。
- php以太坊,主要是介绍使用php进行智能合约开发交互,进行账号创建、交易、转账、代币开发以及过滤器和事件等内容。
- web3j教程,主要是针对java和android程序员进行区块链以太坊开发的web3j详解。
- 以太坊教程,主要介绍智能合约与dapp应用开发,适合入门。
- 以太坊开发,主要是介绍使用node.js、mongodb、区块链、ipfs实现去中心化电商DApp实战,适合进阶。
- python以太坊,主要是针对python工程师使用web3.py进行区块链以太坊开发的详解。
- EOS智能合约与DApp开发入门教程,内容涵盖EOS工具链、账户与钱包、发行代币、智能合约开发与部署、使用代码与智能合约交互等核心知识点,最后运用react和各知识点完成一个便签DApp的开发。
网络
以太坊网络
这里补充下以太坊网络,当前以太坊在使用的网络有5个:
-
Mainnet :主网,真正有价值的网络,当前Pow共识;
-
Ropsten :测试网网络, 使用Pow,和当前的公有链环境一致;
-
Kovan :测试网网络, 使用PoA共识,仅parity钱包支持;
-
Rinkeby:测试网网络,使用PoA共识 仅geth钱包支持;
-
Goerli:测试网网络,为Eth2.0 作准备启动的一个跨客户端的网络。
-
Morden(已退役)
Morden是以太坊官方提供的测试网络,自2015年7月开始运行。到2016年11月时,由于难度炸弹已经严重影响出块速度,不得不退役,重新开启一条新的区块链。Morden的共识机制为PoW。
- Ropsten(区块链浏览器)
Ropsten也是以太坊官方提供的测试网络,是为了解决Morden难度炸弹问题而重新启动的一条区块链,目前仍在运行,共识机制为PoW。测试网络上的以太币并无实际价值,因此Ropsten的挖矿难度很低,目前在755M左右,仅仅只有主网络的0.07%。这样低的难度一方面使一台普通笔记本电脑的CPU也可以挖出区块,获得测试网络上的以太币,方便开发人员测试软件,但是却不能阻止攻击。
PoW共识机制要求有足够强大的算力保证没有人可以随意生成区块,这种共识机制只有在具有实际价值的主网络中才会有效。测试网络上的以太币没有价值,也就不会有强大的算力投入来维护测试网络的安全,这就导致了测试网络的挖矿难度很低,即使几块普通的显卡,也足以进行一次51%攻击,或者用垃圾交易阻塞区块链,攻击的成本及其低廉。
2017年2月,Ropsten便遭到了一次利用测试网络的低难度进行的攻击,攻击者发送了千万级的垃圾交易,并逐渐把区块Gas上限从正常的4,700,000提高到了90,000,000,000,在一段时间内,影响了测试网络的运行。攻击者发动这些攻击,并不能获得利益,仅仅是为了测试、炫耀、或者单纯觉得好玩儿。
- Kovan(区块链浏览器)
为了解决测试网络中PoW共识机制的问题,以太坊钱包Parity的开发团队发起了一个新的测试网络Kovan。Kovan使用了权威证明(Proof-of-Authority)的共识机制,简称PoA。
PoW是用工作量来获得生成区块的权利,必须完成一定次数的计算后,发现一个满足条件的谜题答案,才能够生成有效的区块。
PoA是由若干个权威节点来生成区块,其他节点无权生成,这样也就不再需要挖矿。由于测试网络上的以太币无价值,权威节点仅仅是用来防止区块被随意生成,造成测试网络拥堵,完全是义务劳动,不存在作恶的动机,因此这种机制在测试网络上是可行的。
Kovan与主网络使用不同的共识机制,影响的仅仅是谁有权来生成区块,以及验证区块是否有效的方式,权威节点可以根据开发人员的申请生成以太币,并不影响开发者测试智能合约和其他功能。
Kovan目前仍在运行,但仅有Parity钱包客户端可以使用这个测试网络。
- Rinkeby(区块链浏览器)
Rinkeby也是以太坊官方提供的测试网络,使用PoA共识机制。与Kovan不同,以太坊团队提供了Rinkeby的PoA共识机制说明文档,理论上任何以太坊钱包都可以根据这个说明文档,支持Rinkeby测试网络,目前Rinkeby已经开始运行。
除此之外,登链钱包还支持本地开发网络。我们可以选择自己搭建节点(使用Geth、Ganache 等工具),或使用第三方的服务。
测试网络
如果是测试网络,就必须得自己搭建节点,如使用geth启动一个网络:
geth --datadir my_datadir --dev --rpc --rpcaddr "0.0.0.0" console
特别要注意,需要对--rpcaddr
进行设置,表示哪一个地址能接受RPC请求,因为默认情况下,geth只接受来自 localhost 的请求,这样就无法接受到来自手机的客户端的请求。
如果是Ganache,可以点击Ganache右上角的设置,进行配置。
确定当前网络
在钱包有一个设置项,会把用户选中的网络的name
保存到 SharedPreference
, 如图:
确定网络的代码逻辑就简单了: 从SharedPreference
读取到选中的网络名再对NETWORKS
做一个匹配,代码在EthereumNetworkRepository
中,大家可对照查看。
Coin 还是 Token
Coin 指的是以太币,Token 是大家通常所说的代币 或 通证,以太余额何Token余额,他们的获取方式是不一样的,明白这一点很重要,有必要先介绍下以太坊账户模型。
以太坊账户模型
以太币Eth是以太坊的原生代币,在以太坊的账户模型中,有一个字段balance
存储着余额,例如账号的定义像下面:
class Account {
nonce: '0x01',
balance: '0x03e7', // wei
stateRoot: '0x56abc....',
codeHash: '0x56abc....',
}
获取以太币的余额只需要调用web3j提供的RPC接口eth_getBalance
。
而一个地址的Token余额,他记录在Token合约上,注意合约其实也是一个账户(合约账户),Token是指
符合ERC20标准的合约, 每个地址的余额通常存储在一个Mapping
类型的balanceOf
变量中,获取地址的余额需要调用合约的balanceOf方法,并给他传递地址作为参数。
如果在合约地址上调用
eth_getBalance
, 获取的是合约上所存的 eth余额。
Token & TokenInfo
在登链代码里,每一种币及余额封装成了一个Token
类,不论是以太币还是Token 都处理是一个Token
实例。
这里Token 命名不是很严谨,以太币一般称为Coin,为了方便,Coin和Token 都统一作为Token处理,Coin 作为一个特殊的Token,了解这一点对后文阅读很重要。
Token的定义如下:
public class Token {
public final TokenInfo tokenInfo;
public final String balance; // 币余额
public String value; // 币对应的法币价值
}
public class TokenInfo {
public final String address; // 合约地址
public final String name;
public final String symbol; // 代币符号
public final int decimals;
}
账号所有资产
资产包括以太币资产及Token资产。
关联 Token
在获取账号余额之前,我们需要先知道有多少 Token 种类,然后再获取每种Token余额。在登链钱包中,每一账号在某个网络下所关联 Token种类,保存为一个 Realm文件,相关逻辑在RealmTokenSource
类中。
Realm 是一个移动端数据库,是替代sqlite的一种解决方案。
在用户通过以下界面添加新资产,会调用RealmTokenSource
类的put
方法保存到.realm
文件。
现在来看看如何获取账号所关联的 Token, 逻辑上比较简单,不过涉及了多个类,我把调用序列图梳理一下:
{% mermaid sequenceDiagram %}
Title: 获取账号Token种类
TokensViewModel->FetchTokensInteract: fetch
FetchTokensInteract->TokenRepository: fetch
TokenRepository->TokenLocalSource: fetch
TokenLocalSource-->>TokensViewModel: OnTokens
{% endmermaid %}
通过这个调用过程,最终通过TokensViewModel类的onTokens获取到Token种类。
private void onTokens(Token[] tokens) {
this.tokens.postValue(tokens);
}
在PropertyFragmeng界面中订阅收到数据之后,把它设置到界面的Adapter里,完成Token列表的显示。
Ethplorer-API 服务
TokenRepository在执行fetch方法时,如果是在主网下,会调用代码中
EthplorerTokenService
类,从第三方服务Ethplorer-API获取到获取到某一个地址所关联的所有的Token种类。
Ethplorer-API提供的API更多,不过我们只需要getAddressInfo接口,请求接口如下:
/getAddressInfo/0xaccount?apiKey=freekey
Ethplorer-API 的免费接口是有请求限额,每2秒才能发起一个请求,需要注意访问频度。
余额 balance
获取以太余额
分为两步:
- 先构造出web3j 对象
- web3j 调用 ethGetBalance 获取以太余额
web3j对象的构造方法如下:
web3j = Web3j.build(new HttpService(networkInfo.rpcServerUrl, httpClient, false));
web3j对象在TokenRepository初始化的时候完成,在TokenRepository获取到Token列表之后,如果是以太币会随即会调用getEthBalance
方法:
private BigDecimal getEthBalance(String walletAddress) throws Exception {
return new BigDecimal(web3j
.ethGetBalance(walletAddress, DefaultBlockParameterName.LATEST)
.send()
.getBalance());
}
获取 Token 数量
在TokenRepository获取到Token列表之后,如果是ERC20代币会随即会调用getBalance
方法。
根据前面的介绍获取代币的余额需要调用合约的balanceOf方法,在以太坊上对合约方法的调用实际上会合约地址发起一个调用,调用的附加数据是函数及参数的ABI编码数据。
之前写过一篇文章:如何理解以太坊ABI, 大家可以读一下。
用以下方法构造出balanceOf
的ABI函数类型:
private static org.web3j.abi.datatypes.Function balanceOf(String owner) {
return new org.web3j.abi.datatypes.Function(
"balanceOf",
Collections.singletonList(new Address(owner)),
Collections.singletonList(new TypeReference<Uint256>() {}));
}
获取到balanceOf的ABI 之后,经过编码之后,使用 createEthCallTransaction来构造这样一个交易:交易的发起者是当前的账号,交易的目标地址是合约地址,附加数据是编码之后的数据,getBalance方法如下:
private BigDecimal getBalance(String walletAddress, TokenInfo tokenInfo) throws Exception {
org.web3j.abi.datatypes.Function function = balanceOf(walletAddress);
String responseValue = callSmartContractFunction(function, tokenInfo.address, walletAddress);
List<Type> response = FunctionReturnDecoder.decode(
responseValue, function.getOutputParameters());
if (response.size() == 1) {
return new BigDecimal(((Uint256) response.get(0)).getValue());
} else {
return null;
}
}
private String callSmartContractFunction(
org.web3j.abi.datatypes.Function function, String contractAddress, String walletAddress) throws Exception {
String encodedFunction = FunctionEncoder.encode(function);
EthCall response = web3j.ethCall(
Transaction.createEthCallTransaction(walletAddress, contractAddress, encodedFunction),
DefaultBlockParameterName.LATEST)
.sendAsync().get();
return response.getValue();
}
余额格式化
上面获取到的余额,是以最小单位表示的一个数,如以太币余额用wei表示,而现示给用户的数据是ether,即大家说的以太。
注: 1 eth = 10^18 wei , 更多单位转换
转换方法如下:
BigDecimal decimalDivisor = new BigDecimal(Math.pow(10, decimals));
BigDecimal ethbalance = balance.divide(decimalDivisor);
对以太币而言 decimals 为 18,之后 ethbalance 会转化为一个保留4位小数点数的字符串保存到Token类型的balance
变量,转换方法如下:
ethBalance.setScale(4, RoundingMode.CEILING).toPlainString()
UI界面最终通过订阅 tokens 数组获取Token种类及余额,代码查阅 PropertyFragment.java
。
参考文档
二、连接测试网络
目前开发人员最常用的测试网络是Rinkeby,我将演示一种最简单的连接和使用Rinkeby的方法。
1、下载以太坊钱包:ethereum/mist
根据使用的操作系统不同,在下图红框中选择合适的版本,下载解压。
MIST其实只是以太坊钱包的一个图形界面,后端还是官方的Geth,只是可以使用图形化的方式操作,减少了出错的几率,降低使用门槛。MIST是使用Electron开发的,具有跨平台的能力,所以在各个系统上的界面和操作应该是基本一致的。
2、运行mist,选择测试网络。
可以看到我们是在测试网络Rinkeby上。
在测试网络上创建一个钱包地址,并给钱包加个密码。
提醒你保存好密码和keyfiles
一路“next”后,进入下面的界面。
等待测试网络的数据同步完成。
之后点击下面的“LAUNCH APPLICATION!”就可以进入主界面了。
注意:一定要确保左下角有个红色的Rinkeby字样,这表明你正在Rinkeby测试网络中。
点击右侧红框,就可以看到你的钱包地址的余额了,现在应该是0以太币。
查看交易或者余额
https://rinkeby.etherscan.io/address/0xF67c8d7233a262cE020F1AFE18a37D95F8d2731d
https://blockscan.com/address/0xA54BF5C1059D24ac1943F332851a2F0779EDD026
3、获取测试网络上的以太币
Rinkeby测试网络使用的是PoA共识机制,我们不能通过挖矿来获取以太币。
想获取Rinkeby测试网络中的以太币,需要去申请,这个申请Rinkeby以太币的功能被称为水龙头(Faucet)。还真是挺形象的,水龙头会源源不断的产生以太币,并且受到权威节点控制,以确保不会被滥用。
进入这个水龙头的网站:Rinkeby: GitHub Faucet