本文是对以太坊文档 Ethereum Frontier Guide 和 Ethereum Homestead 的整理。Fontier 和 Homestead 分别是以太坊的第 1 和 第 2 个版本。
本文使用 go 语言编写的命令行客户端 geth
geth 的命令可以分为 CLI 命令和 JavaScript 控制台交互式命令,约定如下
geth account list:这是 CLI 命令> eth.accounts:这是 javaScript 控制台的命令,前面有一个
>。进入控制台的方式为
geth consolegeth 的安装教程请参见 Building Ethereum
「同步」的意思是把网络上的区块全部下载到本地,以同步到网络的最新状态。使用客户端前必须先同步区块。
同步命令如下
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 --bootnodes "enode://pubkey1@ip1:port1 enode://pubkey2@ip2:port2 enode://pubkey3@ip3:port3",
pubkey、
ip 和
port 依次为启动节点的公钥地址、ip 和端口号。> admin.addPeer("enode://pubkey@ip:port")启动客户端的方式如下
gethgeth --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:返回本地节点的详细信息此外,还有一些网站供你查看以太坊主网络的状态
以太坊公有的测试网络有 Ropsten 和 Rinkeby。除此之外,你可以搭建自己的私有网络,即只能本地访问的私网。
下面介绍 3 种测试网络的搭建方式
geth --testnet --fast --bootnodes "enode://20c9ad97c081d63397d7b685a412227a40e23c8bdc6688c6f37e97cfbc22d2b4d1db1510d8f61e6a8866ad7f0e17c02b14182d37ea7c3c8b9c2683aeb6b733a1@52.169.14.227:30303,enode://6ce05930c72abc632c58e2e4324f7c7ea478cec0ed4fa2528982cf34483094e9cbc9216e7aa349691242576d552a2a56aaeae426c5303ded677ce455ba1acd9d@13.84.180.240:30303",来连接特殊的启动节点来同步 Ropsten 网络的数区块。或者也可以使用 Mist 进行同步。geth --testnetwork参考: ethereum/ropsten
参见 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 初始化测试网络,并进入控制台。
该测试网络只有你一个人,你需要自己挖坑来记录交易。
参考
新建账户
geth account new> personal.newAccount("passphrase")geth --password <passwordfile> account new,密码以明文方式写在文件
passwordfile 里导入账户
geth account import <keyfile>,私钥以明文方式写在文件
keyfile 里,每个一行。
<keyfile> 是存着账户信息的 json 文件,其中私钥是被账号密码(password)加密后,存放在里面的--password,可以使用在导入账户时设置密码:
geth --password <passwordfile> account import <keyfile>修改密码
geth account update <address>geth account update 2:2是账户的编号,在
geth account list 中可以看到geth wallet import <etherwallet.json>创建多签名钱包,请参见 Creating a Multi-Signature Wallet in Mist
列出所有账户
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>) 导入文件中的函数。
以发起一个 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>) 解锁账户,再发送交易。
挖矿会有挖矿奖励
以太坊使用 Ethash 的 PoW 算法
挖矿
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]CLI 命令已介绍得差不多了。
可以去 Command Line Options 查阅具体的命令。
JSON-RPC 是一种无状态、轻量级的 RPC 协议,规定了通信的数据结构和规则。以太坊客户端使用 JSON-RPC 和其他客户端通信。
比如 MetaMask 钱包就是通过 JSON-RPC 和以太坊客户端进行通信的。
对于不同的以太坊客户端,默认 JSON-RPC 地址如下
geth 是 go 客户端,因此 JSON-RPC 为 http://localhost:8545。
常用命令
geth --rpc:开启 HTTP JSON-RPCgeth --rpc --rpcaddr <ip> --rpcport <portnumber>:改变 JSON-RPC 的 ip 和端口> admin.startRPC(addr, port)细节请参阅 JSON RPC
你可以使用 web3.js 库所提供的对象,来搭建 Ðapp。
细节请参阅 JavaScript API
一般操作都在控制台模式下进行。
geth consolegeth --datadir ./ethdev console详细命令请参阅 JavaScript Console
5.1 对智能合约进行基本概念的介绍;后面的小节依次介绍合约的编写、编译和部署。
参考
以太坊有 2 种账户
当合约收到交易时,以太坊虚拟机(EVM)会根据它收到的参数,来执行内部的代码。
交易(Transaction): EOA发送给其他账户(EOA或合约)的签名过的消息
消息(Message):合约发给其他合约的消息
两者的不同就在于发送方的不同
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
合约通常为这 4 种目的服务
合约能充当不同的角色,因此我们希望合约可以多交互。下面举一个合约交互的例子
这个例子有 5 个合约
Alice 完成赌注,需要这么做
Bob 想完成赌注,需要这么做
Bet Contract 将自动执行赌约
学习 solidity 的资源
接下来以合约
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;
}}合约以「以太坊虚拟机(EVM)字节码」的形式存在与区块中,因此需要对源文件进行编译得到字节码。
有几种编译方式
solc 编译器> web3.eth.compile.solidity:这个方法似乎不行了,因为 1.6 版本后的 geth 移除了合约编译器,见
#3793 和
#209下面是 Mac OS 平台上的 solc 编译器安装方式。其他平台请参考 Building from Source
git clone --recursive https://github.com/ethereum/solidity.gitcd soliditysudo xcodebuild -license accept 同意 license./scripts/install_deps.sh:安装外部依赖./scripts/build.sh:开始编译现在敲入命令
solc 就可以调用编译器了
注
npm install -g solc 方式安装的是另一种编译器
solcjseth.compile 已被移除,因此旧版本的
admin.setSolc() 设置编译器的命令已无效编译后,我们会得到 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 信息。
接下来要用该文件部署合约
进入 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? 才弄懂。
先体验一下合约的使用
> 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() 会改变链上的状态,是一个
交易使用合约前,需要对其进行实例化。先前的
gt 就是一个实例。
实例化合约有 2 步
var myContract = web3.eth.contract(abi);var contractInstance = myContract.at(address);
var contractInstance = myContract.new([constructorParam1] [, constructorParam2], {from: myAccount, data: '0x12345...', gas: 1000000});根据是否会改变网络状态,可将方法分成调用(
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()。
参考: Contract Methods
参考: Contract Event
除了 geth 控制台,还可以用 JSON-RPC 部署合约。具体参考 Accessing Contracts and Transactions
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/69966803/viewspace-2681023/ ,如需转载,请注明出处,否则将追究法律责任。