本文是對以太坊文檔 Ethereum Frontier Guide 和 Ethereum Homestead 的整理。Fontier 和 Homestead 分別是以太坊的第 1 和 第 2 個版本。
本文使用 go 語言編寫的命令行客戶端 geth
geth 的命令可以分為 CLI 命令和 JavaScript 控制臺交互式命令,約定如下
-
geth account list
:這是 CLI 命令 -
> eth.accounts
:這是 javaScript 控制臺的命令,前面有一個>
。進入控制臺的方式為geth console
1. 安裝和運行節(jié)點
1.1 安裝客戶端
geth 的安裝教程請參見 Building Ethereum
1.2 同步區(qū)塊
「同步」的意思是把網(wǎng)絡(luò)上的區(qū)塊全部下載到本地,以同步到網(wǎng)絡(luò)的最新狀態(tài)。使用客戶端前必須先同步區(qū)塊。
同步命令如下
-
geth
:全節(jié)點模式同步模式 -
geth --fast --cache=1024
:--fast
快速同步模式,只下載狀態(tài)(state downloads)
更優(yōu)雅的同步方式
-
geth --fast console 2>network_sync.log
:同步時把輸出日志重定向到network_sync.log
中,并進入控制臺。這樣就可以邊同步邊使用控制臺命令。 - 用
tail -f network_sync.log
可以重新瀏覽到日志
數(shù)據(jù)存放目錄
主網(wǎng)絡(luò)區(qū)塊數(shù)據(jù)的默認(rèn)存放目錄是 ~/Library/Ethereum
(Mac OS X)
- 其他系統(tǒng)下,可用該方式找到默認(rèn)路徑:
geth -h
后搜索--datadir
,后面跟著的就是默認(rèn)目錄 - 如果你想將區(qū)塊下載到其他目錄,可以使用命令
geth --fast --datadir "<path>"
不同客戶端是可以共用區(qū)塊數(shù)據(jù)的。用 geth 同步的區(qū)塊數(shù)據(jù),可以在 Ethereum Wallet 或 Mist 客戶端里直接使用。
導(dǎo)入已有的區(qū)塊文件
如果本地已有區(qū)塊文件,可以將其導(dǎo)入
-
geth export filename
:導(dǎo)出區(qū)塊文件 -
geth import filename
:導(dǎo)入?yún)^(qū)塊文件
啟動節(jié)點
- geth 借助啟動節(jié)點(bootstrap nodes)來初始化尋找過程。啟動節(jié)點被寫在源碼里,但可用這些方式修改
-
geth --bootnodes "enode://pubkey1@ip1:port1 enode://pubkey2@ip2:port2 enode://pubkey3@ip3:port3"
,pubkey
、ip
和port
依次為啟動節(jié)點的公鑰地址、ip 和端口號。 > admin.addPeer("enode://pubkey@ip:port")
-
- geth 使用名為 discover protocol 的協(xié)議來尋找其他節(jié)點
1.3 啟動客戶端
啟動客戶端的方式如下
- 主網(wǎng)絡(luò)
- 如果區(qū)塊數(shù)據(jù)在默認(rèn)目錄下:
geth
- 如果區(qū)塊數(shù)據(jù)在其他目錄下:
geth --datadir <path>
- 如果區(qū)塊數(shù)據(jù)在默認(rèn)目錄下:
- 測試網(wǎng)絡(luò):
geth --datadir <path> --networkid 15
。你只會連接與你的協(xié)議版本和 networkid 都相同的節(jié)點。主網(wǎng)絡(luò)的 networkid 是 1,所以 networkid 只要不是 1 就可以
更常用的是啟動客戶端,并進入控制臺模式:geth --datadir <path> console 2>console.log
。同時可以另開窗口,用 tail -f console.log
瀏覽日志。
更復(fù)雜的啟動命令
geth --identity "MyNodeName" --rpc --rpcport "8080" --rpccorsdomain "*" --datadir "./ethdev" --port "30303" --nodiscover --rpcapi "db,eth,net,web3" --networkid 1999
(參考:Command line parameters)
-
identity "MyNodeName"
:為你的節(jié)點設(shè)置身份標(biāo)識,以更容易在節(jié)點列表中便是 -
--rpc
:開啟 RPC 接口 -
--rpcport "8080"
:RPC 端口 -
--rpccorsdomain "*"
:設(shè)置能連接到你的節(jié)點的 URL,用來完成 RPC 任務(wù)。*
指任何 URL 都能連接到你。 -
--datadir "./ethdev"
:區(qū)塊數(shù)據(jù)文件夾 -
--port "30303"
:用來監(jiān)聽其他節(jié)點的端口 -
--nodiscover
:你的節(jié)點不會被其他人發(fā)現(xiàn),除非他們手動添加你 -
--rpcapi "db,eth,net,web3"
:提供給別人使用的 RPC API,默認(rèn)為web3
接口 -
networkid 1999
:相同 networkdid 才會連接到一起
監(jiān)控
在控制臺里,使用這些命令檢查連接狀態(tài)
-
> net.listening
:檢查是否連接 -
> net.peerCount
:連接到的節(jié)點個數(shù) -
> admin.peers
:返回連接到的節(jié)點的詳細(xì)信息 -
> admin.nodeInfo
:返回本地節(jié)點的詳細(xì)信息
此外,還有一些網(wǎng)站供你查看以太坊主網(wǎng)絡(luò)的狀態(tài)
- EthStats.net:查看以太坊網(wǎng)絡(luò)的實時狀態(tài)。本地安裝教程 Eth-Netstats README on Github
- EtherNodes.com:查看節(jié)點數(shù)據(jù)
- etherchain.org:另一個查看實時以太坊狀態(tài)的網(wǎng)站
1.4 測試網(wǎng)絡(luò)
以太坊公有的測試網(wǎng)絡(luò)有 Ropsten 和 Rinkeby。除此之外,你可以搭建自己的私有網(wǎng)絡(luò),即只能本地訪問的私網(wǎng)。
下面介紹 3 種測試網(wǎng)絡(luò)的搭建方式
Ropsten網(wǎng)絡(luò)
- 同步區(qū)塊:
geth --testnet --fast --bootnodes "enode://20c9ad97c081d63397d7b685a412227a40e23c8bdc6688c6f37e97cfbc22d2b4d1db1510d8f61e6a8866ad7f0e17c02b14182d37ea7c3c8b9c2683aeb6b733a1@52.169.14.227:30303,enode://6ce05930c72abc632c58e2e4324f7c7ea478cec0ed4fa2528982cf34483094e9cbc9216e7aa349691242576d552a2a56aaeae426c5303ded677ce455ba1acd9d@13.84.180.240:30303"
,來連接特殊的啟動節(jié)點來同步 Ropsten 網(wǎng)絡(luò)的數(shù)區(qū)塊。或者也可以使用 Mist 進行同步。 - 進入網(wǎng)絡(luò):
geth --testnetwork
Rinkeby網(wǎng)絡(luò)
參見 Rinkeby: Ethereum Testnet - Connect Yourself,有 archive, full, light, embedded 4種模式
私有網(wǎng)絡(luò)
搭建私有網(wǎng)絡(luò),需要先新建創(chuàng)世塊參數(shù)文件 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
初始化測試網(wǎng)絡(luò),并進入控制臺。
該測試網(wǎng)絡(luò)只有你一個人,你需要自己挖坑來記錄交易。
參考
2. 賬戶管理
2.1 創(chuàng)建賬戶
新建賬戶
geth account new
> personal.newAccount("passphrase")
- 非交互方式:
geth --password <passwordfile> account new
,密碼以明文方式寫在文件passwordfile
里
導(dǎo)入賬戶
-
geth account import <keyfile>
,私鑰以明文方式寫在文件keyfile
里,每個一行。-
<keyfile>
是存著賬戶信息的 json 文件,其中私鑰是被賬號密碼(password)加密后,存放在里面的 - 不同平臺的 keyfile 默認(rèn)存儲位置是不同的
- Windows: C:\Users\username%appdata%\Roaming\Ethereum\keystore
- Linux: ~/.ethereum/keystore
- Mac: ~/Library/Ethereum/keystore
-
- 結(jié)合
--password
,可以使用在導(dǎo)入賬戶時設(shè)置密碼:geth --password <passwordfile> account import <keyfile>
修改密碼
geth account update <address>
-
geth account update 2
:2是賬戶的編號,在geth account list
中可以看到
2.2 導(dǎo)入錢包
geth wallet import <etherwallet.json>
創(chuàng)建多簽名錢包,請參見Creating a Multi-Signature Wallet in Mist
2.3 查看賬戶信息
列出所有賬戶
-
geth account list
;對應(yīng)的控制臺命令為> 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>)
導(dǎo)入文件中的函數(shù)。
2.4 發(fā)送交易
以發(fā)起一個 0.01 個 ether 的轉(zhuǎn)賬交易為例
> 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>)
解鎖賬戶,再發(fā)送交易。
3. 挖礦
3.1 介紹
挖礦會有挖礦獎勵
以太坊使用 Ethash 的 PoW 算法
3.2 CPU 挖礦
挖礦
-
geth --mine --minerthreads=4
:--minerthreads
設(shè)置并行挖礦的線程數(shù)量,默認(rèn)為所有的處理器數(shù)量; -
> miner.star(8)
,使用 8 個 minerthreads;>miner.stop()
停止 - 稍微復(fù)雜點的挖礦命令:
geth --mine --minerthreads 4 --datadir /usr/local/share/ethereum/30303 --port 30303
:使用不同數(shù)據(jù)目錄(ethereum/30303)和不同的端口(30303)挖礦
發(fā)放獎勵:eth.etherbase
(也叫 coinbase)是一個地址,挖礦獎勵會發(fā)到這個地址里。改變 etherbase 的方式如下
-
geth --etherbase 1 --mine
:改變 etherbase 為編號1的地址;或geth --etherbase '0xa4d8e9cae4d04b093aac82e6cd355b6b963fb7ff'
- 控制臺:
> miner.setEtherbase(eth.accounts[2])
其他
-
> eth.getBlock(i).miner
:查看塊的挖出者 -
eth.getBlockTransactionCount("pending")
:查看未確認(rèn)交易的數(shù)量 -
eth.getBlock("pending", true).transactions
:查看所有未確認(rèn)交易
下面的函數(shù)可以統(tǒng)計不同地址的出塊數(shù)量
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 是一種無狀態(tài)、輕量級的 RPC 協(xié)議,規(guī)定了通信的數(shù)據(jù)結(jié)構(gòu)和規(guī)則。以太坊客戶端使用 JSON-RPC 和其他客戶端通信。
比如 MetaMask 錢包就是通過 JSON-RPC 和以太坊客戶端進行通信的。
對于不同的以太坊客戶端,默認(rèn) 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)
細(xì)節(jié)請參閱 JSON RPC
4.3 使用 Dapp 的 JavaScript API
你可以使用 web3.js 庫所提供的對象,來搭建 Dapp。
細(xì)節(jié)請參閱 JavaScript API
4.4 JavaScript 控制臺
一般操作都在控制臺模式下進行。
- 進入控制臺的方式:
geth console
- 啟動測試網(wǎng)絡(luò),并進入控制臺:
geth --datadir ./ethdev console
詳細(xì)命令請參閱 JavaScript Console
5. 智能合約
5.1 對智能合約進行基本概念的介紹;后面的小節(jié)依次介紹合約的編寫、編譯和部署。
參考
- Contracts and Transactions - Ethereum Homestead
- Contracts and Transactions - Ethereum Frontier Guide
5.1 介紹
5.1.1 賬戶
以太坊有 2 種賬戶
- 外部賬戶(Externally owned account,EOA)
- 有 ether 余額
- 能發(fā)送交易(轉(zhuǎn)賬或觸發(fā)合約)
- 被私鑰控制,即人類直接掌管的賬戶
- 沒有代碼
- 合約(Contract)
- 有 ether 余額
- 內(nèi)部有代碼
- EOA 或其他交易發(fā)來的消息可以觸發(fā)代碼執(zhí)行
- 圖靈完備,且有持久性的存儲,即它自身有持久性的狀態(tài)。
- 可以調(diào)用其他合約
當(dāng)合約收到交易時,以太坊虛擬機(EVM)會根據(jù)它收到的參數(shù),來執(zhí)行內(nèi)部的代碼。
5.1.2 交易與消息
交易(Transaction):EOA發(fā)送給其他賬戶(EOA或合約)的簽名過的消息
消息(Message):合約發(fā)給其他合約的消息
兩者的不同就在于發(fā)送方的不同
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 |
Gas 被認(rèn)為是網(wǎng)絡(luò)資源的不變花費。我們希望發(fā)送每筆交易的真實成本總是保持不變,所以 Gas 不能被發(fā)行,否則價格會有波動。
反之,我們發(fā)行 ether。當(dāng) ether 價格上升時,Gas 價格就對應(yīng)下降,以保持真實成本不變
參考:Ether
5.1.4 賬戶交互的例子
合約通常為這 4 種目的服務(wù)
- 維護數(shù)據(jù),這些數(shù)據(jù)通常對其他合約有用,或來表示外部世界。典型例子是貨幣和記錄特定組織中的成員身份
- 充當(dāng)一種能執(zhí)行復(fù)雜規(guī)則的 EOA,這被叫作 forwarding contact。該合約收到消息后,在滿足特定情況下,轉(zhuǎn)發(fā)結(jié)果給其他 EOA,比如,直到 3 個私鑰中的 2 個確定,才能發(fā)送消息。
- 管理其他合約和多用戶之間的關(guān)系,典型例子是金融合約或擔(dān)保。一方還可以公開合約,然后供其他人來參與。比如合約自動發(fā)送獎勵給那些解決某些數(shù)學(xué)問題的人
- 為其他合約提供函數(shù),作為庫的作用。
合約能充當(dāng)不同的角色,因此我們希望合約可以多交互。下面舉一個合約交互的例子
- Alice 和 Bob 下了一個賭注,每人出 50 GavCoin,賭明年某個時候 San Francisco 的氣溫是不是會超過 35°,若超過,則 Bob 贏得 100 GavCoin。
- Bob 使用一個 Forwarding Contract。Bob 會先發(fā)送消息給 Forwarding Contract,然后它再轉(zhuǎn)發(fā)給其他地址;接受消息也是一樣。該 Forwarding Contract 需要發(fā)送方不僅提供 ECDSA 簽名,還需要提供 Lamport 簽名,才能轉(zhuǎn)發(fā)消息。
這個例子有 5 個合約
- GavCoin Contract:管理 GavCoin 代幣的合約
- Bet Contract:賭注合約
- Weather Contract:查詢天氣的合約
- Forwarding Contract:Bob 的轉(zhuǎn)發(fā)合約
- Lamport Contract:提供 Lamport 簽名基本操作的合約
Alice 完成賭注,需要這么做
- Alice 發(fā)送消息給 Bet Contract,其消息是「接受賭約,并把自己的 50 個 GavCoin 存到 Bet Contract 的賬戶下」
- Bet Contract 發(fā)送消息給 GavCoin Contract,把 Alice 的 50 個 GavCoin 存到自己的賬戶(即合約地址)里
Bob 想完成賭注,需要這么做
- Bob 的 EOA 發(fā)送消息給 Forwarding Contract,該消息包含了 Bob 的 EOA 的 ECDSA 和 Lamport 簽名,以及消息「接受賭約,并把自己的 50 個 GavCoin 存到 Bet Contract 的賬戶下」。
- Forwarding Contract 發(fā)送消息給 Lamport Contract,要求驗證 Lamport 簽名
- 若驗簽成功,Lamport Contract 返回 1 給 Forwarding Contract。Forwarding Contract 發(fā)送消息給 Bet Contract
- Bet Contract 發(fā)送消息給 GavCoin Contract,把 Bob 的 50 個 GavCoin 存到自己的賬戶(即合約地址)里
Bet Contract 將自動執(zhí)行賭約
- Bet Contract 每隔一定周期發(fā)消息給 Weather Contract,查詢 San Francisco 的當(dāng)前氣溫
- 一旦氣溫超過 35°C,Bet Contract 發(fā)消息給 GavCoin Contract,把自己賬戶里的 100 GavCoin 發(fā)到 Bob 的賬戶里
5.2 編寫合約
學(xué)習(xí) solidity 的資源
- Writing a contract:Solidity 各種文檔和學(xué)習(xí)資源的列表
- 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)字節(jié)碼」的形式存在與區(qū)塊中,因此需要對源文件進行編譯得到字節(jié)碼。
有幾種編譯方式
- 使用
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
:開始編譯
現(xiàn)在敲入命令 solc
就可以調(diào)用編譯器了
注
-
Installing Solidity - npm / Node.js 里的
npm install -g solc
方式安裝的是另一種編譯器solcjs
- 由于 geth 中的
eth.compile
已被移除,因此舊版本的admin.setSolc()
設(shè)置編譯器的命令已無效
5.3.2 編譯
編譯后,我們會得到 2 個東西,用來部署合約:
-
.bin
:二進制字節(jié)碼 -
.abi
(Application Binary Interface):一個 JSON 對象,用來定義接口,用來告訴別人怎么使用這個合約,就好比用戶手冊。
常用編譯命令
-
solc --bin greeter.sol
:編譯得到.bin
二進制字節(jié)碼 -
sloc --abi greeter.sol
:編譯得到.abi
接口(其實這里只做了解析,沒有編譯) -
solc --optimize --bin greeter.sol
:--optimize
可優(yōu)化編譯。優(yōu)化生成的字節(jié)碼更小,部署所需的 gas 也會減少。 -
solc -o outputDirectory --bin --ast --asm greeter.sol
:設(shè)置輸出目錄,目錄 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
文件,看起來會像這個樣子(已經(jīng)過格式化)
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 控制臺里,執(zhí)行下面的命令
> loadScript('greeter_compiled.js') // 導(dǎo)入編譯后生成的文件
true
> var _greeting = "Hello World!!!" // 部署該合約需要的初始化參數(shù)
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);
}
}
);
總結(jié)一下,可以得到部署合約的一般命令(# 開頭的變量是我們需要設(shè)置的)
> var Contract = web3.eth.contract(#abi); // abi 是一個 json 對象
undefined
// 部署一個合約實例
> var deployed = Contract.new(#args, {from: #sender, data: "0x" + #bin, gas: 4700000}, // args:合約的構(gòu)造函數(shù)所需的參數(shù)。如果有多個參數(shù),依次用,隔開 中;sender:合約的部署者地址;bin:二進制字節(jié)碼,前面要加'0x';gas:部署需要花費的 gas 量
function (e, contract) { // 這是個回調(diào)函數(shù),作用是合約部署成功后,通知你一聲
console.log(e, contract);
if (typeof contract.address !== 'undefined') {
console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash);
}
}
);
null [object Object]
undefined
這里只是把你的合約廣播出去,只有被區(qū)塊記錄后,才部署成功。
私有網(wǎng)絡(luò)下,可以自己來挖新區(qū)塊
> miner.start()
null
> null [object Object]
Contract mined! address: 0xdfb2938df1e4c80f309f7f09ceae871175b94a81 transactionHash: 0x49fad3bbb8fe00efa018fa8387d9ee2fef3a53dd5c841f2131b0afb5ba17f349 // 部署返回的合約地址和交易哈希值
更方便(偷懶)的方法:使用在線編譯器里 Remix - Solidity IDE
- 把代碼復(fù)制進去
- 選擇你需要的合約,點擊下方的
Contract details (bytecode, interface etc.)
- 找到
Web3 deploy
,其里面的內(nèi)容就是我們要在 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()
函數(shù)的用法不同,是因為
-
.greet()
不會改變鏈上的狀態(tài),是一個調(diào)用 -
.kill()
會改變鏈上的狀態(tài),是一個交易
5.5.1 實例化
使用合約前,需要對其進行實例化。先前的 gt
就是一個實例。
實例化合約有 2 步
- 第一步,知道合約的 abi:
var myContract = web3.eth.contract(abi);
- 第二步:知道合約地址,間接獲得 bin:
var contractInstance = myContract.at(address);
- 執(zhí)行部署命令時,也順帶會實例化合約:
var contractInstance = myContract.new([constructorParam1] [, constructorParam2], {from: myAccount, data: '0x12345...', gas: 1000000});
- 執(zhí)行部署命令時,也順帶會實例化合約:
5.5.2 調(diào)用合約中的方法
根據(jù)是否會改變網(wǎng)絡(luò)狀態(tài),可將方法分成調(diào)用(call
) 和 交易(sendTransaction
) 兩種類型。它們的調(diào)用方式分別如下
-
contractInstance.method.call(param1 [, param2, ...] [, transactionObject] [, defaultBlock] [, callback]);
:call
類型函數(shù),不會改變網(wǎng)絡(luò)狀態(tài)。 -
contractInstance.method.sendTransaction(param1 [, param2, ...] [, transactionObject] [, callback]);
:sendTransaction
類型函數(shù),會改變網(wǎng)絡(luò)狀態(tài)。
若使用 contractInstance.method(param1 [, param2, ...] [, transactionObject] [, defaultBlock] [, callback]);
,則 EVM 會自動根據(jù)方法類型,來選擇使用 call()
或 sendTransaction()
。
5.5.3 Event
5.X 其他
除了 geth 控制臺,還可以用 JSON-RPC 部署合約。具體參考 Accessing Contracts and Transactions