本文是对以太坊文档 Ethereum Frontier Guide 和 Ethereum Homestead 的整理。Fontier 和 Homestead 分别是以太坊的第 1 和 第 2 个版本。
本文使用 go 语言编写的命令行客户端 geth
geth 的命令可以分为 CLI 命令和 JavaScript 控制台交互式命令,约定如下
-
geth account list
:这是 CLI 命令 -
> eth.accounts
:这是 javaScript 控制台的命令,前面有一个>
。进入控制台的方式为geth console
1. 安装和运行节点
1.1 安装客户端
geth 的安装教程请参见 Building Ethereum
1.2 同步区块
「同步」的意思是把网络上的区块全部下载到本地,以同步到网络的最新状态。使用客户端前必须先同步区块。
同步命令如下
-
geth
:全节点模式同步模式 -
geth --fast --cache=1024
:--fast
快速同步模式,只下载状态(state downloads)
更优雅的同步方式
-
geth --fast console 2>network_sync.log
:同步时把输出日志重定向到network_sync.log
中,并进入控制台。这样就可以边同步边使用控制台命令。 - 用
tail -f network_sync.log
可以重新浏览到日志
数据存放目录
主网络区块数据的默认存放目录是 ~/Library/Ethereum
(Mac OS X)
- 其他系统下,可用该方式找到默认路径:
geth -h
后搜索--datadir
,后面跟着的就是默认目录 - 如果你想将区块下载到其他目录,可以使用命令
geth --fast --datadir "<path>"
不同客户端是可以共用区块数据的。用 geth 同步的区块数据,可以在 Ethereum Wallet 或 Mist 客户端里直接使用。
导入已有的区块文件
如果本地已有区块文件,可以将其导入
-
geth export filename
:导出区块文件 -
geth import filename
:导入区块文件
启动节点
- geth 借助启动节点(bootstrap nodes)来初始化寻找过程。启动节点被写在源码里,但可用这些方式修改
-
geth --bootnodes "enode://pubkey1@ip1:port1 enode://pubkey2@ip2:port2 enode://pubkey3@ip3:port3"
,pubkey
、ip
和port
依次为启动节点的公钥地址、ip 和端口号。 > admin.addPeer("enode://pubkey@ip:port")
-
- geth 使用名为 discover protocol 的协议来寻找其他节点
1.3 启动客户端
启动客户端的方式如下
- 主网络
- 如果区块数据在默认目录下:
geth
- 如果区块数据在其他目录下:
geth --datadir <path>
- 如果区块数据在默认目录下:
- 测试网络:
geth --datadir <path> --networkid 15
。你只会连接与你的协议版本和 networkid 都相同的节点。主网络的 networkid 是 1,所以 networkid 只要不是 1 就可以
更常用的是启动客户端,并进入控制台模式:geth --datadir <path> console 2>console.log
。同时可以另开窗口,用 tail -f console.log
浏览日志。
更复杂的启动命令
geth --identity "MyNodeName" --rpc --rpcport "8080" --rpccorsdomain "*" --datadir "./ethdev" --port "30303" --nodiscover --rpcapi "db,eth,net,web3" --networkid 1999
(参考:Command line parameters)
-
identity "MyNodeName"
:为你的节点设置身份标识,以更容易在节点列表中便是 -
--rpc
:开启 RPC 接口 -
--rpcport "8080"
:RPC 端口 -
--rpccorsdomain "*"
:设置能连接到你的节点的 URL,用来完成 RPC 任务。*
指任何 URL 都能连接到你。 -
--datadir "./ethdev"
:区块数据文件夹 -
--port "30303"
:用来监听其他节点的端口 -
--nodiscover
:你的节点不会被其他人发现,除非他们手动添加你 -
--rpcapi "db,eth,net,web3"
:提供给别人使用的 RPC API,默认为web3
接口 -
networkid 1999
:相同 networkdid 才会连接到一起
监控
在控制台里,使用这些命令检查连接状态
-
> net.listening
:检查是否连接 -
> net.peerCount
:连接到的节点个数 -
> admin.peers
:返回连接到的节点的详细信息 -
> admin.nodeInfo
:返回本地节点的详细信息
此外,还有一些网站供你查看以太坊主网络的状态
- EthStats.net:查看以太坊网络的实时状态。本地安装教程 Eth-Netstats README on Github
- EtherNodes.com:查看节点数据
- etherchain.org:另一个查看实时以太坊状态的网站
1.4 测试网络
以太坊公有的测试网络有 Ropsten 和 Rinkeby。除此之外,你可以搭建自己的私有网络,即只能本地访问的私网。
下面介绍 3 种测试网络的搭建方式
Ropsten网络
- 同步区块:
geth --testnet --fast --bootnodes "enode://20c9ad97c081d63397d7b685a412227a40e23c8bdc6688c6f37e97cfbc22d2b4d1db1510d8f61e6a8866ad7f0e17c02b14182d37ea7c3c8b9c2683aeb6b733a1@52.169.14.227:30303,enode://6ce05930c72abc632c58e2e4324f7c7ea478cec0ed4fa2528982cf34483094e9cbc9216e7aa349691242576d552a2a56aaeae426c5303ded677ce455ba1acd9d@13.84.180.240:30303"
,来连接特殊的启动节点来同步 Ropsten 网络的数区块。或者也可以使用 Mist 进行同步。 - 进入网络:
geth --testnetwork
Rinkeby网络
参见 Rinkeby: Ethereum Testnet - Connect Yourself,有 archive, full, light, embedded 4种模式
私有网络
搭建私有网络,需要先新建创世块参数文件 genesis.json
{
"config": {
"chainId": 15,
"homesteadBlock": 0,
"eip155Block": 0,
"eip158Block": 0
},
"difficulty": "10000",
"gasLimit": "2100000",
"alloc": {
"7df9a875a174b3bc565e6424a0050ebc1b2d1d82": { "balance": "300000" },
"f41c74c9ae680c1aa78f42e5647a62f353b7bdde": { "balance": "400000" }
}
}
接下来使用命令 geth --datadir ./ethdev init <genesis.json> console
初始化测试网络,并进入控制台。
该测试网络只有你一个人,你需要自己挖坑来记录交易。
参考
2. 账户管理
2.1 创建账户
新建账户
geth account new
> personal.newAccount("passphrase")
- 非交互方式:
geth --password <passwordfile> account new
,密码以明文方式写在文件passwordfile
里
导入账户
-
geth account import <keyfile>
,私钥以明文方式写在文件keyfile
里,每个一行。-
<keyfile>
是存着账户信息的 json 文件,其中私钥是被账号密码(password)加密后,存放在里面的 - 不同平台的 keyfile 默认存储位置是不同的
- Windows: C:\Users\username%appdata%\Roaming\Ethereum\keystore
- Linux: ~/.ethereum/keystore
- Mac: ~/Library/Ethereum/keystore
-
- 结合
--password
,可以使用在导入账户时设置密码:geth --password <passwordfile> account import <keyfile>
修改密码
geth account update <address>
-
geth account update 2
:2是账户的编号,在geth account list
中可以看到
2.2 导入钱包
geth wallet import <etherwallet.json>
创建多签名钱包,请参见Creating a Multi-Signature Wallet in Mist
2.3 查看账户信息
列出所有账户
-
geth account list
;对应的控制台命令为> eth.accounts
查看账户余额
-
> eth.getBalance(<address>)
:列出账户余额,单位是 Wei -
> web3.fromWei(eth.getBalance(<address>), "ether")
:列出账户余额,单位是 eth
下面的代码可以打印所有的账户余额
function checkAllBalances() {
var i =0;
eth.accounts.forEach( function(e){
console.log(" eth.accounts["+i+"]: " + e + " \tbalance: " + web3.fromWei(eth.getBalance(e), "ether") + " ether");
i++;
})
};
小技巧:可以把代码存到文件中。进入 geth
的控制台后,用 > loadScript(<loadfile.js>)
导入文件中的函数。
2.4 发送交易
以发起一个 0.01 个 ether 的转账交易为例
> var sender = eth.accounts[0];
> var receiver = eth.accounts[1];
> var amount = web3.toWei(0.01, "ether")
> eth.sendTransaction({from:sender, to:receiver, value: amount, gas: gasAmount}) //`gas` 不是必须的
之后会让你输入密码
或者也可以先用 personal.unlockAccount(sender, <passphrase>)
解锁账户,再发送交易。
3. 挖矿
3.1 介绍
挖矿会有挖矿奖励
以太坊使用 Ethash 的 PoW 算法
3.2 CPU 挖矿
挖矿
-
geth --mine --minerthreads=4
:--minerthreads
设置并行挖矿的线程数量,默认为所有的处理器数量; -
> miner.star(8)
,使用 8 个 minerthreads;>miner.stop()
停止 - 稍微复杂点的挖矿命令:
geth --mine --minerthreads 4 --datadir /usr/local/share/ethereum/30303 --port 30303
:使用不同数据目录(ethereum/30303)和不同的端口(30303)挖矿
发放奖励:eth.etherbase
(也叫 coinbase)是一个地址,挖矿奖励会发到这个地址里。改变 etherbase 的方式如下
-
geth --etherbase 1 --mine
:改变 etherbase 为编号1的地址;或geth --etherbase '0xa4d8e9cae4d04b093aac82e6cd355b6b963fb7ff'
- 控制台:
> miner.setEtherbase(eth.accounts[2])
其他
-
> eth.getBlock(i).miner
:查看块的挖出者 -
eth.getBlockTransactionCount("pending")
:查看未确认交易的数量 -
eth.getBlock("pending", true).transactions
:查看所有未确认交易
下面的函数可以统计不同地址的出块数量
function minedBlocks(lastn, addr) {
addrs = [];
if (!addr) {
addr = eth.coinbase
}
limit = eth.blockNumber - lastn
for (i = eth.blockNumber; i >= limit; i--) {
if (eth.getBlock(i).miner == addr) {
addrs.push(i)
}
}
return addrs
}
// scans the last 1000 blocks and returns the blocknumbers of blocks mined by your coinbase
// (more precisely blocks the mining reward for which is sent to your coinbase).
minedBlocks(1000, eth.coinbase);
//[352708, 352655, 352559]
3.3 其他挖矿方式
4. 接口
4.1 命令行接口
CLI 命令已介绍得差不多了。
可以去 Command Line Options 查阅具体的命令。
4.2 JSON RPC API
JSON-RPC 是一种无状态、轻量级的 RPC 协议,规定了通信的数据结构和规则。以太坊客户端使用 JSON-RPC 和其他客户端通信。
比如 MetaMask 钱包就是通过 JSON-RPC 和以太坊客户端进行通信的。
对于不同的以太坊客户端,默认 JSON-RPC 地址如下
geth 是 go 客户端,因此 JSON-RPC 为 http://localhost:8545。
常用命令
-
geth --rpc
:开启 HTTP JSON-RPC -
geth --rpc --rpcaddr <ip> --rpcport <portnumber>
:改变 JSON-RPC 的 ip 和端口 - 控制台下:
> admin.startRPC(addr, port)
细节请参阅 JSON RPC
4.3 使用 Ðapp 的 JavaScript API
你可以使用 web3.js 库所提供的对象,来搭建 Ðapp。
细节请参阅 JavaScript API
4.4 JavaScript 控制台
一般操作都在控制台模式下进行。
- 进入控制台的方式:
geth console
- 启动测试网络,并进入控制台:
geth --datadir ./ethdev console
详细命令请参阅 JavaScript Console
5. 智能合约
5.1 对智能合约进行基本概念的介绍;后面的小节依次介绍合约的编写、编译和部署。
参考
- Contracts and Transactions - Ethereum Homestead
- Contracts and Transactions - Ethereum Frontier Guide
5.1 介绍
5.1.1 账户
以太坊有 2 种账户
- 外部账户(Externally owned account,EOA)
- 有 ether 余额
- 能发送交易(转账或触发合约)
- 被私钥控制,即人类直接掌管的账户
- 没有代码
- 合约(Contract)
- 有 ether 余额
- 内部有代码
- EOA 或其他交易发来的消息可以触发代码执行
- 图灵完备,且有持久性的存储,即它自身有持久性的状态。
- 可以调用其他合约
当合约收到交易时,以太坊虚拟机(EVM)会根据它收到的参数,来执行内部的代码。
5.1.2 交易与消息
交易(Transaction):EOA发送给其他账户(EOA或合约)的签名过的消息
消息(Message):合约发给其他合约的消息
两者的不同就在于发送方的不同
5.1.3 gas
ether 是以太坊中的货币,用于支付 EVM 的计算。
以太坊中货币最小的单位是 wei。
Unit | Wei Value | Wei |
---|---|---|
wei | 1 wei | 1 |
Kwei (babbage) | 1e3 wei | 1,000 |
Mwei (lovelace) | 1e6 wei | 1,000,000 |
Gwei (shannon) | 1e9 wei | 1,000,000,000 |
microether (szabo) | 1e12 wei | 1,000,000,000,000 |
milliether (finney) | 1e15 wei | 1,000,000,000,000,000 |
ether | 1e18 wei | 1,000,000,000,000,000,000 |
其他:Ether 供应量
Gas 被认为是网络资源的不变花费。我们希望发送每笔交易的真实成本总是保持不变,所以 Gas 不能被发行,否则价格会有波动。
反之,我们发行 ether。当 ether 价格上升时,Gas 价格就对应下降,以保持真实成本不变
参考:Ether
5.1.4 账户交互的例子
合约通常为这 4 种目的服务
- 维护数据,这些数据通常对其他合约有用,或来表示外部世界。典型例子是货币和记录特定组织中的成员身份
- 充当一种能执行复杂规则的 EOA,这被叫作 forwarding contact。该合约收到消息后,在满足特定情况下,转发结果给其他 EOA,比如,直到 3 个私钥中的 2 个确定,才能发送消息。
- 管理其他合约和多用户之间的关系,典型例子是金融合约或担保。一方还可以公开合约,然后供其他人来参与。比如合约自动发送奖励给那些解决某些数学问题的人
- 为其他合约提供函数,作为库的作用。
合约能充当不同的角色,因此我们希望合约可以多交互。下面举一个合约交互的例子
- Alice 和 Bob 下了一个赌注,每人出 50 GavCoin,赌明年某个时候 San Francisco 的气温是不是会超过 35°,若超过,则 Bob 赢得 100 GavCoin。
- Bob 使用一个 Forwarding Contract。Bob 会先发送消息给 Forwarding Contract,然后它再转发给其他地址;接受消息也是一样。该 Forwarding Contract 需要发送方不仅提供 ECDSA 签名,还需要提供 Lamport 签名,才能转发消息。
这个例子有 5 个合约
- GavCoin Contract:管理 GavCoin 代币的合约
- Bet Contract:赌注合约
- Weather Contract:查询天气的合约
- Forwarding Contract:Bob 的转发合约
- Lamport Contract:提供 Lamport 签名基本操作的合约
Alice 完成赌注,需要这么做
- Alice 发送消息给 Bet Contract,其消息是「接受赌约,并把自己的 50 个 GavCoin 存到 Bet Contract 的账户下」
- Bet Contract 发送消息给 GavCoin Contract,把 Alice 的 50 个 GavCoin 存到自己的账户(即合约地址)里
Bob 想完成赌注,需要这么做
- Bob 的 EOA 发送消息给 Forwarding Contract,该消息包含了 Bob 的 EOA 的 ECDSA 和 Lamport 签名,以及消息「接受赌约,并把自己的 50 个 GavCoin 存到 Bet Contract 的账户下」。
- Forwarding Contract 发送消息给 Lamport Contract,要求验证 Lamport 签名
- 若验签成功,Lamport Contract 返回 1 给 Forwarding Contract。Forwarding Contract 发送消息给 Bet Contract
- Bet Contract 发送消息给 GavCoin Contract,把 Bob 的 50 个 GavCoin 存到自己的账户(即合约地址)里
Bet Contract 将自动执行赌约
- Bet Contract 每隔一定周期发消息给 Weather Contract,查询 San Francisco 的当前气温
- 一旦气温超过 35°C,Bet Contract 发消息给 GavCoin Contract,把自己账户里的 100 GavCoin 发到 Bob 的账户里
5.2 编写合约
学习 solidity 的资源
- Writing a contract:Solidity 各种文档和学习资源的列表
- Sodility 文档
- Dapps
接下来以合约 greeter.sol
为例(来自 Contract Tutorial)
pragma solidity ^0.4.10;
contract mortal {
/* Define variable owner of the type address*/
address owner;
/* this function is executed at initialization and sets the owner of the contract */
function mortal() { owner = msg.sender; }
/* Function to recover the funds on the contract */
function kill() { if (msg.sender == owner) suicide(owner); }
}
contract greeter is mortal {
/* define variable greeting of the type string */
string greeting;
/* this runs when the contract is executed */
function greeter(string _greeting) public {
greeting = _greeting;
}
/* main function */
function greet() constant returns (string) {
return greeting;
}
}
5.3 编译合约
合约以「以太坊虚拟机(EVM)字节码」的形式存在与区块中,因此需要对源文件进行编译得到字节码。
有几种编译方式
- 使用
solc
编译器 - 使用 geth 控制台的
> web3.eth.compile.solidity
:这个方法似乎不行了,因为 1.6 版本后的 geth 移除了合约编译器,见 #3793 和 #209 - 使用在线编译器 Remix - Solidity IDE。Remix 还有离线版的:Browser-Only Solidity IDE and Runtime Environment
5.3.1 安装 solc 编译器
下面是 Mac OS 平台上的 solc 编译器安装方式。其他平台请参考 Building from Source
git clone --recursive https://github.com/ethereum/solidity.git
cd solidity
- 需要先安装 Xcode,然后
sudo xcodebuild -license accept
同意 license -
./scripts/install_deps.sh
:安装外部依赖 -
./scripts/build.sh
:开始编译
现在敲入命令 solc
就可以调用编译器了
注
-
Installing Solidity - npm / Node.js 里的
npm install -g solc
方式安装的是另一种编译器solcjs
- 由于 geth 中的
eth.compile
已被移除,因此旧版本的admin.setSolc()
设置编译器的命令已无效
5.3.2 编译
编译后,我们会得到 2 个东西,用来部署合约:
-
.bin
:二进制字节码 -
.abi
(Application Binary Interface):一个 JSON 对象,用来定义接口,用来告诉别人怎么使用这个合约,就好比用户手册。
常用编译命令
-
solc --bin greeter.sol
:编译得到.bin
二进制字节码 -
sloc --abi greeter.sol
:编译得到.abi
接口(其实这里只做了解析,没有编译) -
solc --optimize --bin greeter.sol
:--optimize
可优化编译。优化生成的字节码更小,部署所需的 gas 也会减少。 -
solc -o outputDirectory --bin --ast --asm greeter.sol
:设置输出目录,目录 outputDirectory 会生成greeter.bin
、greeter.ast
和greeter.asm
文件 -
solc --combined-json abi,bin,interface greeter.sol
:输出一个 json 对象,里面包含 bin、abi 和 interface 信息
为了方便部署,我们一般使用这个命令
echo "var compilerOutput=`solc --optimize --combined-json abi,bin greeter.sol`" > greeter_compiled.js
这将生成一个 greeter_compiled.js
文件,看起来会像这个样子(已经过格式化)
var compilerOutput =
{
"contracts": {
"greeter.sol:greeter": {
"abi": "[{\"constant\":false,\"inputs\":[],\"name\":\"kill\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"greet\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"_greeting\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"}]",
"bin": "6060604052341561000f57600080fd5b6040516103173803806103178339810160405280805160008054600160a060020a03191633600160a060020a03161790559190910190506001818051610059929160200190610060565b50506100fb565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106100a157805160ff19168380011785556100ce565b828001600101855582156100ce579182015b828111156100ce5782518255916020019190600101906100b3565b506100da9291506100de565b5090565b6100f891905b808211156100da57600081556001016100e4565b90565b61020d8061010a6000396000f300606060405263ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166341c0e1b58114610047578063cfae32171461005c57600080fd5b341561005257600080fd5b61005a6100e6565b005b341561006757600080fd5b61006f610127565b60405160208082528190810183818151815260200191508051906020019080838360005b838110156100ab578082015183820152602001610093565b50505050905090810190601f1680156100d85780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6000543373ffffffffffffffffffffffffffffffffffffffff908116911614156101255760005473ffffffffffffffffffffffffffffffffffffffff16ff5b565b61012f6101cf565b60018054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156101c55780601f1061019a576101008083540402835291602001916101c5565b820191906000526020600020905b8154815290600101906020018083116101a857829003601f168201915b5050505050905090565b602060405190810160405260008152905600a165627a7a723058202a04be9bc62f62ece115fc346e0b98ea5019ba1d0199402c0883c957096ac9790029"
},
"greeter.sol:mortal": {
"abi": "[{\"constant\":false,\"inputs\":[],\"name\":\"kill\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"}]",
"bin": "6060604052341561000f57600080fd5b60008054600160a060020a033316600160a060020a031990911617905560b98061003a6000396000f300606060405263ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166341c0e1b58114603b57600080fd5b3415604557600080fd5b604b604d565b005b6000543373ffffffffffffffffffffffffffffffffffffffff90811691161415608b5760005473ffffffffffffffffffffffffffffffffffffffff16ff5b5600a165627a7a723058208d5720dc8ecd1ce214bdca4a93dd356c2894a206b14d349dba56a43e49ac2ae80029"
}
},
"version": "0.4.17-develop.2017.8.28+commit.2b3a49f7.Darwin.appleclang"
}
这个文件包含了 bin
和 abi
信息。
接下来要用该文件部署合约
5.4 部署合约
进入 geth 控制台里,执行下面的命令
> loadScript('greeter_compiled.js') // 导入编译后生成的文件
true
> var _greeting = "Hello World!!!" // 部署该合约需要的初始化参数
undefined
> var greeterContract = web3.eth.contract(JSON.parse(compilerOutput.contracts["greeter.sol:greeter"].abi)); // 要解析成 json 对象
undefined
> var gt = greeterContract.new(_greeting, {from: eth.accounts[0], data: "0x" + compilerOutput.contracts["greeter.sol:greeter"].bin, gas: 4700000},
function (e, contract) {
console.log(e, contract);
if (typeof contract.address !== 'undefined') {
console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash);
}
}
);
总结一下,可以得到部署合约的一般命令(# 开头的变量是我们需要设置的)
> var Contract = web3.eth.contract(#abi); // abi 是一个 json 对象
undefined
// 部署一个合约实例
> var deployed = Contract.new(#args, {from: #sender, data: "0x" + #bin, gas: 4700000}, // args:合约的构造函数所需的参数。如果有多个参数,依次用,隔开 中;sender:合约的部署者地址;bin:二进制字节码,前面要加'0x';gas:部署需要花费的 gas 量
function (e, contract) { // 这是个回调函数,作用是合约部署成功后,通知你一声
console.log(e, contract);
if (typeof contract.address !== 'undefined') {
console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash);
}
}
);
null [object Object]
undefined
这里只是把你的合约广播出去,只有被区块记录后,才部署成功。
私有网络下,可以自己来挖新区块
> miner.start()
null
> null [object Object]
Contract mined! address: 0xdfb2938df1e4c80f309f7f09ceae871175b94a81 transactionHash: 0x49fad3bbb8fe00efa018fa8387d9ee2fef3a53dd5c841f2131b0afb5ba17f349 // 部署返回的合约地址和交易哈希值
更方便(偷懒)的方法:使用在线编译器里 Remix - Solidity IDE
- 把代码复制进去
- 选择你需要的合约,点击下方的
Contract details (bytecode, interface etc.)
- 找到
Web3 deploy
,其里面的内容就是我们要在 geth 控制台里输入的 abi 和 bin
由于 1.6 之后的 geth 控制台不能使用 solidity 编译器,而官方教程 Contract Tutorial 还是老版本,没有更新,于是饶了很多弯。最后参考 #14850 和 How to compile Solidity contracts with Geth v1.6? 才弄懂。
5.5 使用合约
先体验一下合约的使用
> gt.greet() // gt 是 5.4 里得到的合约实例
"Hello World!!!"
> gt.address // 合约地址
"0xdfb2938df1e4c80f309f7f09ceae871175b94a81"
> eth.getCode(gt.address)
"0x60606....."
> gt.kill.sendTransaction({from:eth.accounts[0]}) // 销毁合约。销毁后就无法使用了
这里的 .greet()
和 .kill()
函数的用法不同,是因为
-
.greet()
不会改变链上的状态,是一个调用 -
.kill()
会改变链上的状态,是一个交易
5.5.1 实例化
使用合约前,需要对其进行实例化。先前的 gt
就是一个实例。
实例化合约有 2 步
- 第一步,知道合约的 abi:
var myContract = web3.eth.contract(abi);
- 第二步:知道合约地址,间接获得 bin:
var contractInstance = myContract.at(address);
- 执行部署命令时,也顺带会实例化合约:
var contractInstance = myContract.new([constructorParam1] [, constructorParam2], {from: myAccount, data: '0x12345...', gas: 1000000});
- 执行部署命令时,也顺带会实例化合约:
5.5.2 调用合约中的方法
根据是否会改变网络状态,可将方法分成调用(call
) 和 交易(sendTransaction
) 两种类型。它们的调用方式分别如下
-
contractInstance.method.call(param1 [, param2, ...] [, transactionObject] [, defaultBlock] [, callback]);
:call
类型函数,不会改变网络状态。 -
contractInstance.method.sendTransaction(param1 [, param2, ...] [, transactionObject] [, callback]);
:sendTransaction
类型函数,会改变网络状态。
若使用 contractInstance.method(param1 [, param2, ...] [, transactionObject] [, defaultBlock] [, callback]);
,则 EVM 会自动根据方法类型,来选择使用 call()
或 sendTransaction()
。
5.5.3 Event
5.X 其他
除了 geth 控制台,还可以用 JSON-RPC 部署合约。具体参考 Accessing Contracts and Transactions