├── .gitignore ├── README.md ├── bin ├── import_keys.sh ├── private_blockchain.sh └── private_blockchain_init.sh ├── contracts └── Token.sol ├── genesis.json ├── import_keys.sh ├── private_keys ├── 3ae88fe370c39384fc16da2c9e768cf5d2495b48.key ├── 81063419f13cab5ac090cd8329d8fff9feead4a0.key ├── 9da26fc2e1d6ad9fdd46138906b0104ae68a65d8.key ├── bd2d69e3e68e1ab3944a865b3e566ca5c48740da.key ├── ca9f427df31a1f5862968fad1fe98c0a9ee068c4.key └── import.sh └── screenshots └── private-started.png /.gitignore: -------------------------------------------------------------------------------- 1 | .ethereum 2 | data/ 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ethereum Bootstrap 2 | 3 | 通过本文所述方法和项目中的脚本,我们可以快速的搭建好自己的私链进行开发测试。 4 | 5 | 仓库中包含的工具有: 6 | 7 | * 一个测试账户导入脚本,在首次部署时将五个测试账户私钥导入以太坊节点。 8 | * 一个genesis.json配置文件,为对应的五个测试账户提供初始资金(以太币),方便开发测试。 9 | * 一个快速启动私有链节点并进入交互模式的脚本。 10 | * 一个合约样例:`contracts/Token.sol`。这是一个使用合约语言[Solidity](http://solidity.readthedocs.org/en/latest/)编写的智能合约。Token合约的功能是发行一种token(可以理解为货币,积分等等),只有合约的创建者有发行权,token的拥有者有使用权,并且可以自由转账。 11 | 12 | **测试账户私钥是放在Github上的公开数据,千万不要用于正式环境中或者公有链上。如果在测试环境之外的地方使用这些私钥,你的资金将会被窃取!** 13 | 14 | ## 准备 15 | 16 | 1. 在本地安装好[go-ethereum](https://github.com/ethereum/go-ethereum)和[solc](http://solidity.readthedocs.org/en/latest/), 可以执行`geth`和`solc`命令。如果操作系统是ubuntu, 安装官方的ethereum安装包即可。 17 | 2. 将本仓库通过`git clone`命令下载到本地。 18 | 3. 安装[expect](http://expect.sourceforge.net/),工具脚本用它来自动化一些过程。例如在ubuntu上: `sudo apt-get install expect` 19 | 20 | ## 启动geth 21 | 22 | 1. 进入本仓库目录: `cd ethereum-bootstrap` 23 | 2. 导入测试账户私钥: `./bin/import_keys.sh` 24 | 3. 初始化blockchain: `./bin/private_blockchain_init.sh` 25 | 输出的结果类似如下, $(DIR))代表你的下载路径: 26 | ``` 27 | INFO [09-25|11:07:26] Allocated cache and file handles database=$(DIR)/ethereum-bootstrap/data/geth/chaindata cache=16 handles=16 28 | INFO [09-25|11:07:26] Successfully wrote genesis state database=chaindata hash=194258…f0efa3 29 | INFO [09-25|11:07:26] Allocated cache and file handles database=$(DIR)/ethereum-bootstrap/data/geth/lightchaindata cache=16 handles=16 30 | INFO [09-25|11:07:26] Successfully wrote genesis state database=lightchaindata hash=194258…f0efa3 31 | ``` 32 | 4. 为解决之后操作账户锁定问题,修改本目录下bin/private_blockchain.sh文件,在$geth后添加--unlock 0 --password value. 33 |   其中0表示解锁第0个账户,value为包含你第2步设置的密码的文件的地址路径,需要你建立。这样下面就不需要再解锁账户的操作。 34 | 5. 启动私有链节点: `./bin/private_blockchain.sh`. 启动成功后可以看到类似如下输出: 35 | ![private-started.png](screenshots/private-started.png) 36 | 6. 此时以太坊交互式控制台已经启动,我们可以开始测试和开发了。 37 | 38 | 注意:工具脚本假设你的geth安装在默认位置, 可以直接通过`geth`执行。如果`geth`命令安装在非标准的位置,可以设置`GETH`环境变量指定geth可执行文件的路径。例如: 39 | 40 | `GETH=/some/weird/dir/geth ./bin/import_keys.sh` 41 | 42 | ## 通过挖矿来为account发放ether 43 | 查看账号余额: 44 | ``` 45 | > web3.eth.getBalance(web3.eth.accounts[0]) 46 | 2e+30 47 | ``` 48 | 可以通过挖矿的方式给第一个账号发行ether: 49 | ``` 50 | > miner.start(1) 51 | I0822 17:17:43.496826 miner/miner.go:119] Starting mining operation (CPU=1 TOT=3) 52 | I0822 17:17:43.497379 miner/worker.go:573] commit new work on block 30 with 0 txs & 1 uncles. Took 527.407µs 53 | ``` 54 | 需要调用miner.stop来停止挖矿: 55 | ``` 56 | > miner.stop() 57 | true 58 | > web3.eth.getBalance(web3.eth.accounts[0]) 59 | 2.000000000105e+30 60 | ``` 61 | 使用账号前先解锁: 62 | ``` 63 | > personal.unlockAccount(web3.eth.accounts[0]) 64 | ``` 65 | 66 | ## 使用以太坊控制台编译和部署智能合约 67 | 68 | 在`contracts`目录下有一个智能合约样例文件`Token.sol`, 通过Solidity语言实现了基本的代币功能, 合约持有者可以发行代币, 使用者可以互相转账. 69 | 70 | 我们可以使用以太坊控制台来编译部署这个合约.以太坊控制台是最基本的工具,使用会比较繁琐.社区也提供了其他更加方便的部署工具,此处不做讨论. 71 | 72 | 第一步,我们先编译合约代码.新建一个ssh session, 切换到geth用户环境`su - geth`, 然后输入:``echo "var tokenCompiled=`solc --optimize --combined-json abi,bin,interface contracts/Token.sol`" > token.js``.(此步为编译合约代码,并将生成的内容放入当前路径下的token.js文件) 73 | 74 | 切换到以太坊控制台,把js文件加载进来: 75 | 76 | ```javascript 77 | loadScript("token.js") 78 | ``` 79 | 80 | 通过`tokenCompiled.contracts["contracts/Token.sol:Token"].bin`可以看到编译好的二进制代码,通过`tokenCompiled.contracts["contracts/Token.sol:Token"].abi`可以看到合约的[ABI](https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI). 81 | 82 | 以变量的形式取出合约的二进制代码和ABI。 83 | ```javascript 84 | var tokenContractsBin = "0x" + tokenCompiled.contracts["contracts/Token.sol:Token"].bin; 85 | var tokenContractsAbi = tokenCompiled.contracts["contracts/Token.sol:Token"].abi; 86 | ``` 87 | 88 | 接下来我们要把编译好的合约部署到网络上去. 89 | 90 | 首先我们用ABI来创建一个javascript环境中的合约对象: 91 | 92 | ```javascript 93 | var contract = web3.eth.contract(JSON.parse(tokenContractsAbi)); 94 | ``` 95 | 96 | 我们通过合约对象来部署合约: 97 | 98 | ```javascript 99 | var initializer = {from: web3.eth.accounts[0], data: tokenContractsBin, gas: 300000}; 100 | 101 | var token = contract.new(initializer); 102 | ``` 103 | 104 | `contract.new`方法的第一个参数设置了这个新合约的创建者地址`from`, 这个新合约的代码`data`, 和用于创建新合约的费用`gas`.`gas`是一个估计值,只要比所需要的gas多就可以,合约创建完成后剩下的gas会退还给合约创建者. 105 | 106 | `contract.new`方法的第二个参数设置了一个回调函数,可以告诉我们部署是否成功. 107 | 108 | `contract.new`执行时会提示输入钱包密码.执行成功后,我们的合约Token就已经广播到网络上了.此时只要等待矿工把我们的合约打包保存到以太坊区块链上,部署就完成了. 109 | 110 | 在公有链上,矿工打包平均需要15秒,在私有链上,我们需要自己来做这件事情.首先开启挖矿: 111 | 112 | ```javascript 113 | miner.start(1) 114 | ``` 115 | 116 | 此时需要等待一段时间,以太坊节点会生成挖矿必须的数据,这些数据都会放到内存里面.在数据生成好之后,挖矿就会开始,稍后就能在控制台输出中看到类似: 117 | 118 | ``` 119 | :hammer:mined potential block 120 | ``` 121 | 122 | 的信息,这说明挖到了一个块,合约已经部署到以太坊网络上了!此时我们可以把挖矿关闭: 123 | 124 | ```javascript 125 | miner.stop() 126 | ``` 127 | 128 | 接下来我们就可以调用合约了.先通过`token.address`获得合约部署到的地址, 以后新建合约对象时可以使用.这里我们直接使用原来的contract对象: 129 | 130 | ``` 131 | // 本地钱包的第一个地址所持有的token数量 132 | > web3.eth.getBalance(web3.eth.accounts[0]) 133 | 0 134 | 135 | // 发行100个token给本地钱包的第一个地址 136 | > token.issue.sendTransaction(web3.eth.accounts[0], 100, {from: web3.eth.accounts[0]}); 137 | I1221 11:48:30.512296 11155 xeth.go:1055] Tx(0xc0712460a826bfea67d58a30f584e4bebdbb6138e7e6bc1dbd6880d2fce3a8ef) to: 0x37dc85ae239ec39556ae7cc35a129698152afe3c 138 | "0xc0712460a826bfea67d58a30f584e4bebdbb6138e7e6bc1dbd6880d2fce3a8ef" 139 | 140 | // 发行token是一个transaction, 因此需要挖矿使之生效 141 | > miner.start(1) 142 | :hammer:mined potential block 143 | > miner.stop() 144 | 145 | // 再次查询本地钱包第一个地址的token数量 146 | > token.getBalance(web3.eth.accounts[0]) 147 | 100 148 | 149 | // 从第一个地址转30个token给本地钱包的第二个地址 150 | > token.transfer.sendTransaction(web3.eth.accounts[1], 30, {from: web3.eth.accounts[0]}) 151 | I1221 11:53:31.852541 11155 xeth.go:1055] Tx(0x1d209cef921dea5592d8604ac0da680348987b131235943e372f8df35fd43d1b) to: 0x37dc85ae239ec39556ae7cc35a129698152afe3c 152 | "0x1d209cef921dea5592d8604ac0da680348987b131235943e372f8df35fd43d1b" 153 | > miner.start(1) 154 | :hammer:mined potential block 155 | > miner.stop() 156 | > token.getBalance(web3.eth.accounts[0]) 157 | 70 158 | > token.getBalance(web3.eth.accounts[1]) 159 | 30 160 | ``` 161 | 162 | ## 其他 163 | 164 | 私有链的所有数据都会放在仓库根目录下的`data`目录中,删除这个目录可以清除所有数据,重新启动新环境。 165 | [solidity_compiler_helper](https://github.com/rakeshbs/solidity_compiler_helper),可以使用这个小工具来部署,更方便。 166 | 167 | 168 | 获取关于以太坊的更多信息请访问[EthFans](http://ethfans.org). 169 | -------------------------------------------------------------------------------- /bin/import_keys.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | geth=${GETH:-geth} 4 | echo "***** Using geth at: $geth" 5 | 6 | echo "***** Import all pre-funded private keys" 7 | 8 | read -p "***** Enter password for the new wallet: " password 9 | 10 | for key in `find ./private_keys -name '*.key'` 11 | do 12 | ./private_keys/import.sh $key $password $geth 13 | done 14 | 15 | echo "***** Done." 16 | -------------------------------------------------------------------------------- /bin/private_blockchain.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | geth=${GETH:-geth} 4 | 5 | $geth --datadir data --networkid 31415926 --rpc --rpccorsdomain "*" --nodiscover console 6 | -------------------------------------------------------------------------------- /bin/private_blockchain_init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | geth=${GETH:-geth} 4 | 5 | $geth --datadir data --networkid 31415926 --rpc --rpccorsdomain "*" init ./genesis.json 6 | -------------------------------------------------------------------------------- /contracts/Token.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.4.0 <0.6.0; 2 | 3 | contract Token { 4 | address issuer; 5 | mapping (address => uint) public balances; 6 | 7 | event Issue(address account, uint amount); 8 | event Transfer(address from, address to, uint amount); 9 | 10 | constructor () public { 11 | issuer = msg.sender; 12 | } 13 | 14 | function issue(address account, uint amount) public { 15 | require(msg.sender == issuer); 16 | balances[account] += amount; 17 | } 18 | 19 | function transfer(address to, uint amount) public { 20 | require(balances[msg.sender] >= amount); 21 | 22 | balances[msg.sender] -= amount; 23 | balances[to] += amount; 24 | 25 | emit Transfer(msg.sender, to, amount); 26 | } 27 | 28 | function getBalance(address account) public view returns (uint) { 29 | return balances[account]; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /genesis.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "chainId": 15, 4 | "homesteadBlock": 0, 5 | "eip155Block": 0, 6 | "eip158Block": 0 7 | }, 8 | "alloc": { 9 | "3ae88fe370c39384fc16da2c9e768cf5d2495b48": { 10 | "balance": "1000000000000000000000000000000" 11 | }, 12 | "81063419f13cab5ac090cd8329d8fff9feead4a0": { 13 | "balance": "2000000000000000000000000000000" 14 | }, 15 | "9da26fc2e1d6ad9fdd46138906b0104ae68a65d8": { 16 | "balance": "3000000000000000000000000000000" 17 | }, 18 | "bd2d69e3e68e1ab3944a865b3e566ca5c48740da": { 19 | "balance": "4000000000000000000000000000000" 20 | }, 21 | "ca9f427df31a1f5862968fad1fe98c0a9ee068c4": { 22 | "balance": "5000000000000000000000000000000" 23 | } 24 | }, 25 | 26 | "nonce": "0x0000000000000042", 27 | "difficulty": "0x020000", 28 | "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000", 29 | "coinbase": "0x0000000000000000000000000000000000000000", 30 | "timestamp": "0x00", 31 | "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", 32 | "extraData": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa", 33 | "gasLimit": "0x4c4b40" 34 | } 35 | 36 | -------------------------------------------------------------------------------- /import_keys.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "***** Import all pre-funded private keys" 4 | 5 | read -p "***** Enter password for the new wallet: " password 6 | 7 | for key in `find /home/geth/private_keys -name '*.key'` 8 | do 9 | /home/geth/private_keys/import.sh $key $password 10 | done 11 | 12 | echo "***** Done." 13 | -------------------------------------------------------------------------------- /private_keys/3ae88fe370c39384fc16da2c9e768cf5d2495b48.key: -------------------------------------------------------------------------------- 1 | 095e53c9c20e23fd01eaad953c01da9e9d3ed9bebcfed8e5b2c2fce94037d963 2 | -------------------------------------------------------------------------------- /private_keys/81063419f13cab5ac090cd8329d8fff9feead4a0.key: -------------------------------------------------------------------------------- 1 | 5bc505a123a695176a9688ffe22798cfd40424c5b91c818e985574ea8ebda167 2 | -------------------------------------------------------------------------------- /private_keys/9da26fc2e1d6ad9fdd46138906b0104ae68a65d8.key: -------------------------------------------------------------------------------- 1 | b6a03207128827eaae0d31d97a7a6243de31f2baf99eabd764e33389ecf436fc 2 | -------------------------------------------------------------------------------- /private_keys/bd2d69e3e68e1ab3944a865b3e566ca5c48740da.key: -------------------------------------------------------------------------------- 1 | b35b8064c5c373629a05cc3ef39789ba4dacd404e6e864214ade934c198b636f 2 | -------------------------------------------------------------------------------- /private_keys/ca9f427df31a1f5862968fad1fe98c0a9ee068c4.key: -------------------------------------------------------------------------------- 1 | 9e35c48588711469e13c9a594f9f6d81491ce44ff1e8c5d968fcbd17168088a4 2 | -------------------------------------------------------------------------------- /private_keys/import.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/expect -f 2 | 3 | set key [lindex $argv 0]; 4 | set password [lindex $argv 1]; 5 | set geth [lindex $argv 2] 6 | 7 | spawn $geth --datadir data account import $key 8 | expect "Passphrase:" {send "$password\n"} 9 | expect "Repeat passphrase:" {send "$password\n"} 10 | interact 11 | -------------------------------------------------------------------------------- /screenshots/private-started.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cryptape/ethereum-bootstrap/3d65fd81a3e01f6b84d7edc046620c60ddba5f54/screenshots/private-started.png --------------------------------------------------------------------------------