带你玩转区块链--以太坊框架truffle及私有链的搭建-第二章-第三节【以太坊篇】

一、意义:

          在之前内容的学习中,我们发现compile、deploy等文件在不同的项目中是可以互用的,根据低聚合高耦合的代码设计规则,我们可以将此部分封装成一个框架,从而更好的完成开发。所以ETH官方推出了Truffle框架。那么,Truffle到底为我们带来了什么呢?让我们来一探究竟。

二、准备工作:

特殊要求:请将node版本降低到11.15.X版本,node12、13版本会因为不兼容报错,博主在这上面花了2个小时才解决。

1.安装truffle

npm install truffle -g

2.官方文档地址

github:
https://github.com/trufflesuite/truffle

官方文档:
https://www.trufflesuite.com/docs/truffle/overview

三、truffle的学习:

1.创建一个工程

$ truffle
Truffle v3.4.11 - a development framework for Ethereum
$ cd myproject
$ truffle init

ps:

1.由于某些原因在安装过程中,可能会卡着不动,这时候您需要科学上网或者使用淘宝镜像cnpm安装

truffle-config.js是win端的设置,truffle.js是mac或linux的设置。在创建工程后,windows端需要删除truffle.js,不然可能会报错,linux或mac端不用管。

2.使用truffle部署一个合约

1.在contracts中创建Simple.sol文件,并写入。文件名需和合约名一致

pragma solidity ^0.4.24;
contract Simple{
    uint256 tmp;
    function setValue(uint256 value)public {
        tmp=value;
    }
    
    function getValue()public view returns(uint256){
        return tmp;
    }
}

2.执行编译命令

truffle compile

3.在migrations文件夹中创建2_Simple_migrations.js文件并写入

const Migrations = artifacts.require("Migrations");
module.exports = function(deployer) {
  deployer.deploy(Migrations);
};

4.设置truffle-config.js、修改默认文件为:

module.exports = {
  networks: {
    ganacheNetwork: {
      host: "127.0.0.1",
      port: 8545,
      network_id: "*" // Match any network id
    }
  }
};



//修改编译器
module.exports = {
  compilers: {
    solc: {
      version: <string>, // A version or constraint - Ex. "^0.5.0"
                         // Can also be set to "native" to use a native solc
      docker: <boolean>, // Use a version obtained through docker
      parser: "solcjs",  // Leverages solc-js purely for speedy parsing
      settings: {
        optimizer: {
          enabled: <boolean>,
          runs: <number>   // Optimize for how many times you intend to run the code
        },
        evmVersion: <string> // Default: "petersburg"
      }
    }
  }
}

4.连接ganache后执行部署命令

truffle migrate --network ganacheNetwork

从上述例子中,我们可以发现truffle的第一个优势:1.深度集成。开发,测试,部署一行命令都可以搞定。

 

3.Truffle自带控制台develop

     在truffle中默认提供了一个与以太坊节点仿真器工具develop,将数据存储在了内存中,它可以更加快速的测试,但看不到数据。默认端口为9545,启动命令为truffle develop ,在启动后直接执行compile进行编译,直接执行migrate进行部署。

1.develop与合约交互

var deployinstance;
Test01.deployed().then(instance=>{deployinstance=instance})

//输入deployinstance  ,这时返回值不是未定义,而是有合约实例了
deployinstance  

 //与合约进行交互。格式如:deployinstance.getValue.call({from:xxx}).then()
deployinstance.getValue.call()
  
//交互,格式如:deployinstance.setValue(500,{from:xxx}).then(),from可省略,默认为第一个账户
deployinstance.setValue(500) 

2.合约测试Test

       由于博主truffle版本问题,在本地运行报错,错误原因为:"before all" hook: prepare suite。这是由于truffle版本的问题,在truffle4中不会遇见此问题,测试代码如下:

import 'truffle/Assert.sol';
import 'truffle/DeployedAddresses.sol';
import '../contracts/Test01.sol';
contract TestTest01{
    //合约大写,函数名小写
    function testSet() public {
        //Test01 ss=Test01(DeployedAddresses.Test01());
        Test01 ss=new Test01();
        ss.setValue(500);
        uint256 res=ss.getValue();
        Assert.equal(res,500,"res should be 500,error");
    }
}

3.重新部署合约

migrate --reset

若还是失败,则手动删除build文件夹后,重新compile

从上述例子中,我们可以发现truffle的第二个优势:2.提供了控制台,使用框架构建后,可以直接在命令行调用输出结果,可极大方便开发调试

4.truffle react框架

1.安装

新建一个文件夹,进入该文件夹内使用命令 truffle unbox react命令下载该框架。

安装过程错误处理

1.1由于该框架有230多M,所以需要耐心等待,如果下载过程出错了,请删掉原文件夹后重新下载。

1.2若安装过程中,出现node-gyp错误,请将本地python版本降低至2.7.11,再将本地vs装visual studio 2015,博主在这里搞了一下午。若您需要python2和python3共存,请将环境变量重新设置。

2.启动项目

进入项目文件后,cd client后 输入npm start,即可启动项目

3.运行合约实例、以及源码解析

    由于原项目本身附带了一个SimpileStorage.sol的合约,所以我只需对这个合约进行研究。首先进入develop模式后进行compile和migrate,这时web页面会出现界面,并访问metamask将value修改为5,若有界面显示value却没有修改,则需要在metamask中设置一个随机的ChainID。项目部分源码解析如下:(App.js)

import React, { Component } from "react";
import SimpleStorageContract from "./contracts/SimpleStorage.json";  //第一步引入合约
import getWeb3 from "./getWeb3";

import "./App.css";

class App extends Component {
  state = { storageValue: 7, web3: null, accounts: null, contract: null };

  componentDidMount = async () => {
    try {
      // Get network provider and web3 instance.
      const web3 = await getWeb3();                   //第二步创建web3对象

      // Use web3 to get the user's accounts.
      const accounts = await web3.eth.getAccounts();

      // Get the contract instance.
      const networkId = await web3.eth.net.getId();  //第三步返回当前网络ID
      //第四步,拿到网络对象
      const deployedNetwork = SimpleStorageContract.networks[networkId]; 
      //第五步获取实例
      const instance = new web3.eth.Contract( 
        SimpleStorageContract.abi,//ABI
        deployedNetwork && deployedNetwork.address,//合约地址
      );

      // Set web3, accounts, and contract to the state, and then proceed with an
      // example of interacting with the contract's methods.
      this.setState({ web3, accounts, contract: instance }, this.runExample);
    } catch (error) {
      // Catch any errors for any of the above operations.
      alert(
        `Failed to load web3, accounts, or contract. Check console for details.`,
      );
      console.error(error);
    }
  };

  runExample = async () => {
    const { accounts, contract } = this.state;

    // Stores a given value, 5 by default.
    await contract.methods.set(5).send({ from: accounts[0] });

    // Get the value from the contract to prove it worked.
    const response = await contract.methods.get().call();
    console.log(response,"oooooooooooooo")

    // Update state with the result.
    this.setState({ storageValue: response });
  };

  render() {
    if (!this.state.web3) {
      return <div>Loading Web3, accounts, and contract...</div>;
    }
    return (
      <div className="App">
        <h1>Good to Go!</h1>
        <p>Your Truffle Box is installed and ready.</p>
        <h2>Smart Contract Example</h2>
        <p>
          If your contracts compiled and migrated successfully, below will show
          a stored value of 5 (by default).
        </p>
        <p>
          Try changing the value stored on <strong>line 40</strong> of App.js.
        </p>
        <div>The stored value is: {this.state.storageValue}</div>
      </div>
    );
  }
}

export default App;

5.Web3知识补充

let Web3 = require('web3')
let web3 = new Web3('http://127.0.0.1:7545')
console.log('version :', web3.version)
//获取账户
let accounts
//error first callback style
//旧版本web3只支持方式一形式,不支持方式二方式三
web3.eth.getAccounts((err, res) => {
    console.log('获取账户方式一:回调函数形式')
    if (err) {
        console.log('err:', err)
    }

    // console.log(res)
})
web3.eth.getAccounts().then(res => {
    console.log('获取账户方式二:then形式')
    // console.log(res)
}).catch(e => {
    console.log(e)
})

let f = async () => {
    try {
        let accounts = await web3.eth.getAccounts()
        console.log('获取账户方式三:async/await形式')
        // console.log(accounts)

        let balance1 = await web3.eth.getBalance(accounts[0])
        console.log('balance1:', balance1)

        //balance1: Promise { <pending> }
        // let balance1 = web3.eth.getBalance(accounts[0])

        let defaultAccount = web3.eth.defaultAccount
        console.log('default:', defaultAccount)
        //设置默认账号
        web3.eth.defaultAccount = accounts[2]
        console.log('new default:', web3.eth.defaultAccount)

        let defaultBlock = web3.eth.defaultBlock
        console.log('defaultBlock:', defaultBlock)


        //由账户0向账户1转10eth
        let res = await web3.eth.sendTransaction({
            // from: accounts[0], //如果不指定from,那么会使用defaultAccount的值
            to: accounts[1],
            gas: '6000000',
            value: web3.utils.toWei('10', 'ether'),
        })
        //修改defaultBlock
        // web3.eth.defaultBlock = 3
        web3.eth.defaultBlock = 'latest'
        console.log('defaultBlock:', web3.eth.defaultBlock)

        let balance2 = await web3.eth.getBalance(accounts[2])
        console.log('balance2:', balance2)

    } catch (e) {
        console.log(e)
    }
}

f()

 6.Web3-Utils

由于js处理大数据会导致精度缺失,我们可以在区块链计算中引入Bignumber模块,或使用web3.utils中的toBN模块。在两个方法后结果都是采用的科学技术法的数值,其中符号: s    指数: e   尾数: c,为了更好的观察数据,这里我们只需用toString()转换一下即可。

6.1Bignumber

 安装

npm i bignumber.js

处理加法

let BigNumber=require('bignumber.js');
let x=new BigNumber('10000000000000000000000000000000000000000050');
let y=new BigNumber('10');
let res=x.plus(y);
console.log(res.toString());
//- 详细示例
var BigNumber = require('bignumber.js');

console.log('====相等?====')
x = new BigNumber(123.4567);
y = new BigNumber(123456.7e-3);
z = new BigNumber(x);
console.log(x.eq(y))

console.log('====加法====')
m = new BigNumber(10101, 2);
n = new BigNumber("ABCD", 16);  //16代表进制
console.log(m.plus(n))
console.log(m.plus(n).toString())

console.log('====减法====')
x = new BigNumber(0.5)
y = new BigNumber(0.4)
console.log(0.5 - 0.4)
console.log(x.minus(y).toString())

console.log('====乘法====')
x = new BigNumber('2222222222222222222222222222222222')
y = new BigNumber('7777777777777777777777777777777777', 16)
console.log(x.times(y).toString())

console.log('====除法====')
console.log(x.div(y).toString())
console.log(x.div(y).toFixed(6).toString())  //6代表小数位

console.log('==== x = -123.456====')
x = new BigNumber(-123.456)

console.log(x)

console.log("尾数x.c:",x.c)
console.log("指数x.e:",x.e)
console.log("符号x.s",x.s)

6.2web3.utils.toBN()

let Web3=require('web3');

let web3=new Web3();
let x=web3.utils.toBN('100000000000000000000000000050');
let y=web3.utils.toBN('10');

let res=x.add(y);
console.log(res.toString());

6.3 单位转换

var Web3 = require('Web3')
var web3 = new Web3()

console.log('\n将wei转换为ether, Gwei, Mwei')
console.log(web3.utils.fromWei('12345567890876433', 'ether'))
console.log(web3.utils.fromWei('12345567890876433', 'Gwei'))
console.log(web3.utils.fromWei('12345567890876433', 'Mwei'))

console.log('\n转换为Wei')
console.log(web3.utils.toWei('1', 'ether'))
console.log(web3.utils.toWei('1', 'Gwei'))
console.log(web3.utils.toWei('1', 'Mwei'))

 

6.4转换为十六进制

var Web3 = require('Web3')
var web3 = new Web3()

console.log(web3.utils.toHex('a'))
console.log(web3.utils.toHex(1234))
console.log(web3.utils.toHex({name:'Duke'}))

//将所有传入的数据都当做字符串进行处理,然后按照ASCII的16进制返回
//如果内部有单引号,则自动转化成双引号,再在外部用单引号括起来
console.log(JSON.stringify({name:'Duke'}))
console.log(web3.utils.toHex('{"name":"Duke"}'))

console.log(web3.utils.toHex(JSON.stringify({name:'Duke'})))

console.log(web3.utils.toHex([1,2,3,4]))
console.log(web3.utils.toHex('[1,2,3,4]'))

//执行结果
0x61
0x4d2
0x7b226e616d65223a2244756b65227d
{"name":"Duke"}
0x7b226e616d65223a2244756b65227d
0x7b226e616d65223a2244756b65227d
0x5b312c322c332c345d
0x5b312c322c332c345d

十六进制与ASCII转换

var Web3 = require('Web3')
var web3 = new Web3()

console.log("先将字符串'xyz'转换为ascii,然后转化为十六进制")
var str = web3.utils.fromAscii('xyz')
console.log(str)

console.log("先将十六进制转换为ascii,然后转化为字符串")
str = web3.utils.toAscii('0x78797a')
console.log(str)

生成hash字符串

var Web3 = require('Web3')
var web3 = new Web3()

var hash0 = web3.utils.sha3('abc')
console.log(hash0)

//对结果0x4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45进行hash
var hash1 = (web3.utils.sha3(hash0))
console.log(hash1)

四、私有网络搭建

1. 准备创世文件 genesis.json

{
 "alloc": {},
 "config": {
   "chainID": 72,
   "homesteadBlock": 0,
   "eip155Block": 0,
   "eip158Block": 0
 },
 "nonce": "0x0000000000000000",
 "difficulty": "0x4000",
 "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
 "coinbase": "0x0000000000000000000000000000000000000000",
 "timestamp": "0x00",
 "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
 "extraData": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa",
 "gasLimit": "0xffffffff"
}

2. 创建一个文件夹,指定为私有网络数据存储位置

mkdir myPrivateNet
cd myPrivateNet
mv genesis.json ./      //<--这个genesis.json放到这里是便于管理,位置不限,能访问即可

3. 创建私有链

==//init 务必要加上,否则可以创建成功,但是后面有问题==

geth --datadir "./node1" init genesis.json

执行效果

[duke ~/ethStudy/myPrivateNet]$ geth --datadir "./node1" init genesis.json
INFO [07-16|07:49:26] Maximum peer count                       ETH=25 LES=0 total=25
INFO [07-16|07:49:26] Allocated cache and file handles         database=/Users/duke/ethStudy/myPrivateNet/node1/geth/chaindata cache=16 handles=16
INFO [07-16|07:49:26] Writing custom genesis block 
INFO [07-16|07:49:26] Persisted trie from memory database      nodes=0 size=0.00B time=8.302µs gcnodes=0 gcsize=0.00B gctime=0s livenodes=1 livesize=0.00B
INFO [07-16|07:49:26] Successfully wrote genesis state         database=chaindata                                              hash=942f59…a2588a
INFO [07-16|07:49:26] Allocated cache and file handles         database=/Users/duke/ethStudy/myPrivateNet/node1/geth/lightchaindata cache=16 handles=16
INFO [07-16|07:49:26] Writing custom genesis block 
INFO [07-16|07:49:26] Persisted trie from memory database      nodes=0 size=0.00B time=1.506µs gcnodes=0 gcsize=0.00B gctime=0s livenodes=1 livesize=0.00B
INFO [07-16|07:49:26] Successfully wrote genesis state         database=lightchaindata                                              hash=942f59…a2588a

4. 启动私有链节点

geth --datadir "./node1" --networkid 72 --port 30301 console
  • --datadir: 指定节点数据存储路径,此处会自动创建 node1,之所以这样命名,是因为后面会创建node2,便于模拟多个节点交互

  • --networkid: 当前网络的id,写在genesis.json中的

  • --port 端口

  • console: 表明同时启动一个交互的终端(非必要)

这个关键字可以省略,省略的话表明只启动节点服务,不创建交互终端,后面如果想接入这个节点的话,可以通过attach命令接入,后面讲。​
效果:

[duke ~/ethStudy/myPrivateNet]$ geth --datadir "./node1" --networkid 72 --port 30301 console
INFO [07-16|07:49:59] Maximum peer count                       ETH=25 LES=0 total=25
INFO [07-16|07:49:59] Starting peer-to-peer node               instance=Geth/v1.8.11-stable-dea1ce05/darwin-amd64/go1.10.3
INFO [07-16|07:49:59] Allocated cache and file handles         database=/Users/duke/ethStudy/myPrivateNet/node1/geth/chaindata cache=768 handles=128
INFO [07-16|07:49:59] Initialised chain configuration          config="{ChainID: 72 Homestead: 0 DAO: <nil> DAOSupport: false EIP150: <nil> EIP155: 0 EIP158: 0 Byzantium: <nil> Constantinople: <nil> Engine: unknown}"
INFO [07-16|07:49:59] Disk storage enabled for ethash caches   dir=/Users/duke/ethStudy/myPrivateNet/node1/geth/ethash count=3
INFO [07-16|07:49:59] Disk storage enabled for ethash DAGs     dir=/Users/duke/.ethash                                 count=2
INFO [07-16|07:49:59] Initialising Ethereum protocol           versions="[63 62]" network=72
INFO [07-16|07:49:59] Loaded most recent local header          number=0 hash=942f59…a2588a td=16384
INFO [07-16|07:49:59] Loaded most recent local full block      number=0 hash=942f59…a2588a td=16384
INFO [07-16|07:49:59] Loaded most recent local fast block      number=0 hash=942f59…a2588a td=16384
INFO [07-16|07:49:59] Regenerated local transaction journal    transactions=0 accounts=0
INFO [07-16|07:49:59] Starting P2P networking 
INFO [07-16|07:50:02] UDP listener up                          self=enode://58e6eec1f00e0b2c221053c580c5f9c5b482bac142c97e711200e1709a736a5c4b9751d5637de524858b208bad7f3ecc6d17c36065801be438ee3b00fe931e35@192.168.1.65:30301
INFO [07-16|07:50:02] RLPx listener up                         self=enode://58e6eec1f00e0b2c221053c580c5f9c5b482bac142c97e711200e1709a736a5c4b9751d5637de524858b208bad7f3ecc6d17c36065801be438ee3b00fe931e35@192.168.1.65:30301
INFO [07-16|07:50:02] IPC endpoint opened                      url=/Users/duke/ethStudy/myPrivateNet/node1/geth.ipc
Welcome to the Geth JavaScript console!

instance: Geth/v1.8.11-stable-dea1ce05/darwin-amd64/go1.10.3
 modules: admin:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0

> INFO [07-16|07:50:02] Mapped network port                      proto=tcp extport=30301 intport=30301 interface="UPNP IGDv1-IP1"

> 
>
  1. 另起一个终端,查看node1下面的内容
[duke ~/ethStudy/myPrivateNet]$ tree node1 -d
node1
├── geth
│   ├── chaindata
│   ├── lightchaindata
│   └── nodes
└── keystore

5 directories

至此,私有网络已搭建完成,下面开始执行各项命令

五、私有链基本命令

instance: Geth/v1.8.11-stable-dea1ce05/darwin-amd64/go1.10.3
 modules: admin:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0

1. 查看当前网络账户

> eth.accounts
[]  <--当前网络账户为空
>

注,,我们在创世块中可以预先指定一些账户,在alloc字段

"alloc": {
      "7df9a875a174b3bc565e6424a0050ebc1b2d1d82": {
        "balance": "300000"
      },
      "f41c74c9ae680c1aa78f42e5647a62f353b7bdde": {
        "balance": "400000"
      }
}

2. 创建账户

> 
> personal.newAccount("1111")
"0xe5c9766404eb3633163c85ef13e7a1f79cdee922"
> 
> 
> eth.accounts
["0xe5c9766404eb3633163c85ef13e7a1f79cdee922"]
>

其中,"1111"是这个账户的密码

3. 多创建几个账户,并查看当前节点矿工

> personal.newAccount("111")
"0x5032641c3f09bc8995a60c485b9ca4ac3d073fd8"
> personal.newAccount("111")
"0x864ced8aff4b883c99ab1c5156f2d51c47b25f8f"
> personal.newAccount("111")
"0xf3b1e1712a311443db772c55e12c9634912199ab"
> personal.newAccount("111")
"0xef89f85b7e276dcf738276df88888298cd9272ee"
>
> eth.coinbase
INFO [07-16|07:54:10] Etherbase automatically configured       address=0x5032641c3f09bc8995a60C485B9Ca4ac3d073fD8
"0x5032641c3f09bc8995a60c485b9ca4ac3d073fd8"
>

可以看到,默认的矿工是第一个账户,可以通过eth.accounts来查看当前节点的所有账户,为了方便记忆,我们把所有的账户密码都设置为”1111“。​

查看一下keystore目录,可以看到创建了四个账户文件

[duke ~/ethStudy/myPrivateNet]$ ls node1/keystore/
UTC--2018-07-15T23-52-31.825281356Z--5032641c3f09bc8995a60c485b9ca4ac3d073fd8
UTC--2018-07-15T23-53-42.431125900Z--864ced8aff4b883c99ab1c5156f2d51c47b25f8f
UTC--2018-07-15T23-53-44.166684858Z--f3b1e1712a311443db772c55e12c9634912199ab
UTC--2018-07-15T23-53-45.567447952Z--ef89f85b7e276dcf738276df88888298cd9272ee
[duke ~/ethStudy/myPrivateNet]$

4. 手动修改挖矿的账户

> miner.setEtherbase(eth.accounts[1])
true
> eth.coinbase
"0x864ced8aff4b883c99ab1c5156f2d51c47b25f8f"
>

可以看到coinbase的值已经改变,挖矿的时候,奖励会发给指定的账户。​
==注,每次节点重新启动时,会自动将coinbase设置为第一个账户。==

私链重启后,挖矿人重新自动恢复为第一个主账户

5. 查看账户余额

> eth.getBalance(eth.accounts[0])
0
> eth.getBalance(eth.accounts[1])
0

没有挖矿,也没有转账,当前余额均为0。​

6. 挖矿

> miner.start()

执行效果:

> miner.start()
INFO [07-16|07:56:48] Updated mining threads                   threads=0
INFO [07-16|07:56:48] Transaction pool price threshold updated price=18000000000
INFO [07-16|07:56:48] Starting mining operation 
null
> INFO [07-16|07:56:48] Commit new mining work                   number=1 txs=0 uncles=0 elapsed=166.057µs
INFO [07-16|07:56:50] Successfully sealed new block            number=1 hash=2a840c…6e5118
INFO [07-16|07:56:50] 🔨 mined potential block                  number=1 hash=2a840c…6e5118
INFO [07-16|07:56:50] Commit new mining work                   number=2 txs=0 uncles=0 elapsed=165.72µs
INFO [07-16|07:56:50] Successfully sealed new block            number=2 hash=f220e8…ffef15
INFO [07-16|07:56:50] 🔨 mined potential block                  number=2 hash=f220e8…ffef15
INFO [07-16|07:56:50] Commit new mining work                   number=3 txs=0 uncles=0 elapsed=133.196µs

执行后开始刷屏,停止挖矿

> miner.stop()

再次查看余额​

> eth.getBalance(eth.coinbase)
65000000000000000000
> 
> eth.getBalance(eth.accounts[0])
0
>

如果挖矿不成功,请更换目录,重新创建区块链

单位是wei,矿工得到奖励,其他账户为零。

可以使用web3接口,将金额进行转换成ether单位,每个块奖励5个ether,共13个块,所以是5 * 13 = 65个奖励

> web3.fromWei(eth.getBalance(eth.coinbase), "ether")
65
> eth.blockNumber
13
>

7. 检查节点

> admin.peers
[]
>

当前网络仅一个节点,所以返回空​

8. 创建一个新节点

步骤同上​

geth --datadir "./node2" init genesis.json
geth --datadir "./node2" --networkid 72 --port 30302

注意:

  • ==init操作千万不要忘记执行,直接执行启动也会成功运行,但是后续添加节点等总是失败,一定要注意!==

  • ==此处我们创建node2,port修改为30302,但是没有指定 console,这是为了演示attach命令==

  • ==两个节点想互连的话,必须都指定--networkid 72==

9. 使用attach参数,创建接入节点的控制台

geth attach ipc:note2/geth.ipc

执行效果:

[duke ~/ethStudy/myPrivateNet]$ geth attach ipc:note2/geth.ipc 
Welcome to the Geth JavaScript console!

instance: Geth/v1.8.11-stable-dea1ce05/darwin-amd64/go1.10.3
 modules: admin:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0

>

此处geth.ipc在node2下面,这个节点启动后,自动生成,节点停止后自动删除。

在node2中,创建新账户

> personal.newAccount("111")
"0x6cf4d0785589497d3b5fca83571097f5cd6df352"
>
> personal.newAccount("111")
"0x1757a2db3fd67f0724bffffc33428faa7e9552f7"
>

10. 在node2中,查看当前节点的信息

> admin.nodeInfo

执行结果:

> admin.nodeInfo
{
  enode: "enode://3e707df543403f724144c8d51656b1399c1b9d5ae1d50f5739e02ef06130f1a11fd02baa320552ce1e6f90a7f45f4e37b1caa4d0f45eefcc97606cc16c150197@192.168.1.65:30302",
  id: "3e707df543403f724144c8d51656b1399c1b9d5ae1d50f5739e02ef06130f1a11fd02baa320552ce1e6f90a7f45f4e37b1caa4d0f45eefcc97606cc16c150197",
  ip: "192.168.1.65",
  listenAddr: "[::]:30302",
  name: "Geth/v1.8.11-stable-dea1ce05/darwin-amd64/go1.10.3",
  ports: {
    discovery: 30302,
    listener: 30302
  },
  protocols: {
    eth: {
      config: {
        chainId: 72,
        eip150Hash: "0x0000000000000000000000000000000000000000000000000000000000000000",
        eip155Block: 0,
        eip158Block: 0,
        homesteadBlock: 0
      },
      difficulty: 1725312,
      genesis: "0x942f596f99dc8879b426b59080824662e1f97587353d087487fea0a0e2a2588a",
      head: "0x17fa69432921727241ea299ab7f12611e09bb5a8473e71f1250f89832661ed86",
      network: 72
    }
  }
}
>

encode字段能唯一标识这个节点,我们将它添加到node1中。

也可以直接获取enode字段

> admin.nodeInfo.enode
"enode://3e707df543403f724144c8d51656b1399c1b9d5ae1d50f5739e02ef06130f1a11fd02baa320552ce1e6f90a7f45f4e37b1caa4d0f45eefcc97606cc16c150197@192.168.1.65:30302"
>

11. 在node1中添加node2节点

切换到node1终端,执行下面的命令:

> admin.addPeer("enode://3e707df543403f724144c8d51656b1399c1b9d5ae1d50f5739e02ef06130f1a11fd02baa320552ce1e6f90a7f45f4e37b1caa4d0f45eefcc97606cc16c150197@192.168.1.65:30302")

添加后,查看节点信息,可以看到,已经添加进来

> admin.peers

[{
    caps: ["eth/63"],
    id: "3e707df543403f724144c8d51656b1399c1b9d5ae1d50f5739e02ef06130f1a11fd02baa320552ce1e6f90a7f45f4e37b1caa4d0f45eefcc97606cc16c150197",
    name: "Geth/v1.8.11-stable-dea1ce05/darwin-amd64/go1.10.3",
    network: {
      inbound: false,
      localAddress: "192.168.0.107:57154",
      remoteAddress: "192.168.1.65:30302",
      static: true,
      trusted: false
    },
    protocols: {
      eth: {
        difficulty: 16384,
        head: "0x942f596f99dc8879b426b59080824662e1f97587353d087487fea0a0e2a2588a",
        version: 63
      }
    }
}]
>

12. 发送交易

由node1账户向node2账户发送一笔交易

在node2中获取第一个账户的地址,该账户当前余额为零

> eth.accounts[0]
"0x6cf4d0785589497d3b5fca83571097f5cd6df352"
> eth.getBalance(eth.accounts[0])
0

切换回node1,执行如下命令:

查看node1中账户余额状态:

> eth.getBalance(eth.accounts[0])
0
> eth.getBalance(eth.accounts[1])
65000000000000000000

我们使用accounts[1]向node2的accounts[0]转账15个ether

> eth.sendTransaction({from : eth.accounts[1], to: "0x6cf4d0785589497d3b5fca83571097f5cd6df352", value: web3.toWei(15, "ether")})
>

此时会报错如下

Error: authentication needed: password or unlock
    at web3.js:3143:20
    at web3.js:6347:15
    at web3.js:5081:36
    at <anonymous>:1:2
>

这说明当前账户被锁定,需要解锁后才能发起交易。

13. 解锁这个from指定的账户

> personal.unlockAccount(eth.accounts[1])

效果:

> personal.unlockAccount(eth.accounts[1])
Unlock account 0x864ced8aff4b883c99ab1c5156f2d51c47b25f8f
Passphrase: 
true
>

重新发送交易,可以看到如下效果,交易创建成功,同时返回交易hash

> eth.sendTransaction({from : eth.accounts[1], to: "0x6cf4d0785589497d3b5fca83571097f5cd6df352", value: web3.toWei(15, "ether")})

INFO [07-16|08:38:58] Submitted transaction                    fullhash=0x1cd7f14436a088586d9bb75db6cadec08f4293d270b21c72b237605d4b921bea recipient=0x6cf4D0785589497D3B5fca83571097F5cD6dF352
"0x1cd7f14436a088586d9bb75db6cadec08f4293d270b21c72b237605d4b921bea"
>

14. 查看交易状态

交易发送到网络中后,需要矿工打包才能写入区块,查看当前该交易的状态

>txpool.status

效果

> txpool.status
{
  pending: 1,  //<--- 等待被打包
  queued: 0
}
>

在node2中启动挖矿,然后停止挖矿,在node1中重新查看

node2:

> miner.start()
null
> miner.stop()
true
>

 node1:

> INFO [07-16|08:41:44] Block synchronisation started 
INFO [07-16|08:41:44] Imported new chain segment               blocks=2 txs=1 mgas=0.021 elapsed=565.360ms mgasps=0.037 number=15 hash=b2be14…d19460 cache=3.33kB
INFO [07-16|08:41:44] Imported new chain segment               blocks=1 txs=0 mgas=0.000 elapsed=4.209ms   mgasps=0.000 number=16 hash=752b6a…2164c9 cache=3.73kB
INFO [07-16|08:41:44] Mining too far in the future             wait=2s
INFO [07-16|08:41:47] Imported new chain segment               blocks=1 txs=0 mgas=0.000 elapsed=4.799ms   mgasps=0.000 number=17 hash=68b112…82e15e cache=4.12kB
INFO [07-16|08:41:47] Imported new chain segment               blocks=1 txs=0 mgas=0.000 elapsed=4.190ms   mgasps=0.000 number=18 hash=9ca0f2…2685c9 cache=4.51kB
INFO [07-16|08:41:47] Imported new chain segment               blocks=1 txs=0 mgas=0.000 elapsed=2.166ms   mgasps=0.000 number=19 hash=7376ea…8c9461 cache=4.90kB
INFO [07-16|08:41:47] Mining too far in the future             wait=2s
INFO [07-16|08:41:51] Imported new chain segment               blocks=1 txs=0 mgas=0.000 elapsed=4.604ms   mgasps=0.000 number=20 hash=35227e…9c2aa3 cache=5.29kB

> 
> txpool.status

{
  pending: 0,
  queued: 0
}
>

可以看到,pending数目变为0,说明交易已经被打包到区块内。

查看交易后的金额,

node1中:

> eth.getBalance(eth.accounts[1])
49999622000000000000
>

node1 的 accounts[1] 账户变为 65 - 15 - 手续费 = 49.99,正确

node2中:

> eth.getBalance(eth.accounts[0])
50000378000000000000

> web3.fromWei(eth.getBalance(eth.accounts[0]), "ether")
50.000378
>

我们发现,accounts[0]的值并非15,为何?

原因是这个账户也是coinbase账户,刚刚参与了挖矿,得到了挖矿的奖励

> eth.blockNumber
20

当前区块数为20,有13个块是从node1节点同步过来的,刚刚我们运行miner.start()一共产生了7个区块,

每个区块奖励5, 共得到奖励 35ether,加上转账获得的15个,为50,再加上手续费,所以总数为50.000378。

六、Ethereum连接私有链

Ethereum默认安装目录

  • Mac: ~/Library/Ethereum
  • Linux: ~/.ethereum
  • Windows: %APPDATA%\Ethereum

我们启动Ethereum客户端,此时连接的是测试网络:

 

我们通过命令行,查看它起的节点命令如下:

Ethereum启动geth命令:

Mac查看方式 :

ps -ef | grep geth

返回如下信息:

/Users/duke/Library/Application Support/Mist/binaries/Geth/unpacked/geth --testnet --syncmode light --cache 1024 --ipcpath /Users/duke/Library/Ethereum/geth.ipc

Windows查看方式:

wmic process where caption="geth.exe" get caption,commandline /value

请自行查看。

Ethereum客户端每次都要指定特定的geth.ipc,指定--testnet 来和测试网络连接,--testnet参数就是Ropsten网络。

我们可以事先拉起一个geth服务,其中将存储数据参数指定为我们的私有网络,这样Mist在启动时,就会自动连接到我们的服务,而不会重新起服务。

Mac:

geth  --datadir ./node1  --ipcpath /Users/duke/Library/Ethereum/geth.ipc

Windows的ipc是固定的:

geth  --datadir .\node1  --ipcpath \\.\pipe\geth.ipc

重新打开Etherueum钱包,界面如下:

 

在命令行,我们通过attach建立一个终端,查看账户信息,我们发现与图形界面一致。

Mac:

geth attach ipc:/Users/duke/Library/Ethereum/geth.ipc

Windows :

geth attach ipc:\\.\pipe\geth.ipc

==注意,此时的ipc应该使用上面的geth.ipc,注意路径,这个钱包自动生成的,并不在我们的node1目录下面。==

执行

> eth.accounts
["0x5032641c3f09bc8995a60c485b9ca4ac3d073fd8", "0x864ced8aff4b883c99ab1c5156f2d51c47b25f8f", "0xf3b1e1712a311443db772c55e12c9634912199ab", "0xef89f85b7e276dcf738276df88888298cd9272ee"]
>

至此,我们完成了私有链的搭建

我们学会如下知识点:

  • 了解geth搭建私有链
  • 熟悉基本的web3接口命令
  • 使用Ethereum客户端连接到私有网络
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章