搭建以太坊测试链的简易教程 the-beginners-guide-to-using-an-ethereum-test-network(zz)

资料仓库  收藏
0 / 1156

搭建以太坊测试链的简易教程 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),并可以准确地运行以使开发变得容易。

以太坊客户端可以选择。我们推荐在开发和部署时使用不同客户端。

适用开发的客户端

当开发基于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托管节点的简单含义:

  • 本地节点:本地节点由你自己启动并控制。它的安全和你的环境安全性一样,在于你自身。当你在机器上运行gethparity时,你就是在运行本地节点。
  • 托管节点:托管节点由其他人控制。当连接到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的更多信息,请访问:

https://infura.io/

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:测试智能合约的代码放在这里,支持jssol测试。
  • truffle-config.jstruffle.jsTruffle的配置文件,需要配置要连接的以太坊网络。

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,第二个帐户分配给变量account1Web3是一个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: