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