以太坊4

一、什么是web3.js
最好的教程:官方文档

web3.js是开发以太坊去中心化应用(DApp)必备的JavaScript库,提供了用于与geth通讯的JavaScript API,web3.js使用了JSON-RPC协议与geth进行通信。
JSON-RPC是一个无状态,轻量级的远程调用协议(RPC),允许使用http,socket等协议进行通讯,使用JSON作为数据格式。
Web3.js可以与所有支持JSON-RPC的节点进行通信,包括以太坊生态中的其他节点,如Whisper(一个集成以太坊的非实时性消息系统)和Swarm(一个去中心化的存储系统)
Web3.js无法直接连接到以太坊网络, 只能连接到以太坊节点,如:geth
二、为什么要使用web3.js
以太坊只提供JSON-RPC接口,Web.js是JavaScript的一个封装了以太坊JSON-RPC的API的库。

JSON-RPC API
Web.js API
使用原生JSON-RPC API调用不方便,两种方式对比如下

目的:获取当前区块链上的区块数量。

  1. JSON-RPC调用方式
    curl -X POST --data ‘{“jsonrpc”:“2.0”,“method”:“eth_accounts”,“params”:[],“id”:1}’ http://localhost:8545 -H ‘content-type: application/json’
    启动ganache,执行效果如下:

{“id”:1,“jsonrpc”:“2.0”,“result”:[“0xd5957914c31e1d785ccc58237d065dd25c61c4d0”,“0x18cec83129c0a766012a26863419640cd5f89400”,“0xdd2c462a89c8ca5921f2d69d4171acc3384c9d4c”,“0x7ff040debdc47b931fe2b49b5a2be4a6df60e0db”,“0xd2e37f99c1f4034108ac5fce4734dec307a95a2c”,“0x700881f1ee835c9ad319832f473d714168ce1c20”,“0x436ec2513d3ba38ff8d0ee172b6052e035d66616”,“0xea17d75d85300523947dc363376fd04c72e394a4”,“0x93f270195eb32d454f5a08add05b42a32af57420”,“0x4d6a14505808b29e416106cd7f2029463920c8d3”]}
图示:

image-20181024094328652

  1. web.js调用方式
    let Web3 = require(‘web3’);
    var web3 = new Web3() //手机,仅能玩贪吃蛇,没有电话卡
    web3.setProvider(new Web3.providers.HttpProvider(“http://localhost:8545”)) //选定了服务商
    web3.eth.getAccounts((err, accounts) => {
    console.log(accounts)
    })
    执行结果:

image-20181024095359726

我们可以看出,使用web3.js库更方便。

三、web3与以太坊关系图示

四、安装web3

  1. 命令
    npm install web3 --save
  2. 版本
  • 旧版本(0.xx,如0.2.1)
    官方文档:https://github.com/ethereum/wiki/wiki/JavaScript-API#web3ethaccounts
    特点:支持同步调用,返回值不是promise,需要使用回调函数方式调用
    举例:使用MetaMask查看同步异步
    旧版本的返回值不是promise,无法使用.then或者await方式获取,只能使用回调方式。
    image-20181024104357602

  • 新版本(1.0,授课版本)
    官方文档:https://web3js.readthedocs.io/en/1.0/web3-eth-accounts.html#eth-accounts

特点:不支持同步调用,返回值都是promise,也支持回调方式调用

image-20181024114241639

五、we3包含模块

  1. 图示
    image-20181024102849309

  2. 讲授
    eth
    utils
    六、web3工作模式

  3. 概述
    image-20181127140741893

  4. 细节
    Web3 大写的Web3是构造函数。(直接看函数原型)
    通过构造函数生成web3的实例。
    web3的实例需要装入provider电话卡(联通卡,移动卡,电信卡) 才可以去以太坊网络进行交互。
    web3实例可以不填写provider,那么只能测试web3的基本信息,如version,以及一些工具函数,但是只有指定了provider(可以通过setProvider方法),才可以和特定的区块链进行交互,这就像是电话服务商一样。
    metamask本身就是web3实例,所以直接在浏览器就可以输入web3.version,版本比较老,因为metamask对老版本的修改比较多,依赖较多,不敢轻易修改,通过设置它的url可以连接到不同的网络。
    image-20181024134704392

七、eth相关函数

  1. 获取账户(三种调用方式)
    // import Web3 from ‘web3’;
    let Web3 = require(‘web3’);

// var web3 = new Web3(new Web3.providers.HttpProvider(“http://localhost:8545”));
var web3 = new Web3()
web3.setProvider(new Web3.providers.HttpProvider(“http://localhost:8545”))
//
let accounts;
web3.eth.getAccounts((err, res) => {
accounts = res
console.log(“方式一: 回调函数”)
console.log(res)
})

web3.eth.getAccounts().then(accounts => {
console.log(“方式二: then”)
console.log(accounts)
})

f1 = async () => {
accounts = await web3.eth.getAccounts()
console.log(“方式三: promise”)
console.log(accounts)
}
f1()
image-20181024155035877

  • 获取账户余额

  • 默认账户(defaultAccount)
    见上述案例

这个属性会影响下面两个函数(from字段如果不填写,会使用默认账户)

web3.eth.sendTransaction()
web3.eth.call()

  • 设置默认区块(defaultBlock)
    见上述案例

默认区块可以通过下面的属性设置,默认情况下是最新区块,可以设置的几个值:

‘latest’:最新区块
‘pending’:当前挖出的块
‘earliest’:创世区块
自定义数字:指定区块
注意,不要设置上述之外的字符串,否则会卡死。

修改该属性会影响下面几个函数:

web3.eth.getBalance()
web3.eth.getCode()
web3.eth.getTransactionCount()
web3.eth.getStorageAt()
web3.eth.call()
contract.myMethod.call()
contract.myMethod.estimateGas()
这几个函数其实都有默认参数的,拿web3.eth.getBalance()为例,函数原型为

web3.eth.getBalance(addressHexString [, defaultBlock] [, callback])
第一个参数是请求的地址
第二个参数就是默认区块,如果指定为0,那么返回值为该账户在0区块时的余额
回调函数,用于异步调用

  • 返回指定区块信息(getBlock)
    var Web3 = require(‘Web3’)
    var web3 = new Web3(new Web3.providers.HttpProvider(‘http://localhost:8545’))

//返回指定区块的信息
web3.eth.getBlock(48, function(error, result){
if(!error)
console.log(JSON.stringify(“no error”, result));
else
console.error(“error”, error);
})
执行结果

no error
指定回调函数时,属于异步调用。如不指定,则为同步调用,返回null

回调函数都是这种格式:

function(error, result){
//这里的result是调用函数的返回值
}
3. 发送交易(sendTransaction)
这个很有意思,函数原型如下:

web3.eth.sendTransaction(transactionObject [, callback])
向网络发送一笔交易,包含两种交易:转账交易或发布智能合约。

第一个参数时交易对象,包含如下字段:

from:这个是可选的,未填写使用默认账户(但是要注意,默认账户的默认值是undefined)

to:如果是转账交易,则必须填写。如果是创建合约,则不必填写

value:转账值,单位是wei,创建合约时转移的金额

gas

gasPrice

data: 这个比较重要,只在两个时候使用:

调用合约函数时(eth.call函数会使用这个字段),这个值是函数的参数, 16进制字节码
创建合约时,是合约的二进制码

  • sendTransaction与send异同
    两者都能用来调用合约方法,都能转账(相同)。
    sendTransaction是专门用来转账的,无需合约,eth直接能调用,send必须有合约的方法(不同)。
    sendTransaction 比send多一个回调函数。
    let to = ‘0x18CeC83129C0a766012a26863419640cD5F89400’;
    trasferMoney = async () => {
    const accounts = await web3.eth.getAccounts();
    //部署到特定的网络中时,accounts只返回一个账号,不会返回10个
    console.log("====== accounts : “, accounts);
    console.log(”====== accounts[0] : “, accounts[0]);
    //accounts[1]是undefined,metamask也一样
    console.log(”====== accounts[1] : ", accounts[1]);
    const res = web3.eth.sendTransaction(
    {
    from : accounts[0],
    to : to,
    value: 10 * (10 ** 18),
    gas : 1000000
    });

    console.log("res : ", res);
    }

trasferMoney();

  • 数据上链
    长生疫苗事件数据上链:点击查看

在data字段添加欲上链数据的十六进制数据,请部署到ropsten上,ganache有bug,

data生成方式如下:

let data = Buffer.from(data).toString('hex);
4. 综合示例
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 {  }
    // 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()
八、Utils模块
因为Javascript不能正确的处理BigNumber,所以以太坊提供了BigNumber相关的api:如下面的例子:

duke ~/gitee/solidity_syntax/Inbox/web介绍$ node

a = 101010100324325345346456456456456456456
1.0101010032432535e+38
a + 1
1.0101010032432535e+38 //不准确

所以web3.js依赖,BigNmber链接:http://mikemcl.github.io/bignumber.js/

  • 安装
    npm i --save bignumber.js //注意是.js,你没有看错,我们要使用这个
  • 注意,注意!
    npm i --save bignumber //注意,没有.js,这个也能安装成功,但是我们不用这个!!!

同样是上面的例子,

let BigNumber = require(‘bignumber.js’)

let v1 = 101010100324325345346456456456456456456
let v2 = 10
console.log('v1 + v2 = ', v1 + v2)
console.log('v1 * v2 = ', v1 * v2)

//bignumber.js 库中提供一个 BigNumber 的构造函数,参数必须是长度在15位以内的数字
//数字可以长,但是不准确
//输入字符串可以任意长度
let v3 = new BigNumber(‘101010100324325345346456456456456456456’)
let v4 = 10

console.log('v3 + v4 = ', v3.plus(v4).toString())
console.log('v3 * v4 = ', v3.times(v4).toString())
科学计数法:https://www.cnblogs.com/chaoguo1234/p/6268385.html

1.2345 * 10^(2)

image-20181127071016290

尾数: c

指数: e

符号: s

以太坊BN:

let BigNumber = require(‘bignumber.js’)
let Web3 = require(‘web3’)
let web3 = new Web3()

let v1 = 101010100324325345346456456456456456456
let v2 = 10

console.log(‘v1 + v2:’, v1 + v2)

let v3 = new BigNumber(‘101010100324325345346456456456456456456’)
console.log(‘v3 + v2 :’, v3.plus(10).toString())

//创建BN的时候,输入的值如果是字符,长度任意任意,如果是十进制数字,最多20位,后面的会无效
let v4 = web3.utils.toBN(‘101010100324325345346456456456456456456’)
let v5 = web3.utils.toBN(‘10’)

//运算的时候,只接受BN类型
console.log(‘v4 + v2 :’, v4.add(v5).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);
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())

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)
执行结果:

相等?
true
加法
BigNumber { s: 1, e: 4, c: [ 44002 ] }
44002
减法
0.09999999999999998
0.1
乘法
9.0338666892195811337239599484107936881562199669307755164928940478094297346e+73
除法
5.466398580833e-8
0.000000
==== x = -123.456====
BigNumber { s: -1, e: 2, c: [ 123, 45600000000000 ] }
尾数x.c: [ 123, 45600000000000 ]
指数x.e: 2
符号x.s: -1
3.utils相关

  • 单位转换
    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’))
执行结果:

[duke ~/ethStudy/web3/basicApi]$ node weiEtherConvert.js

将wei转换为ether, Gwei, Mwei
0.012345567890876433
12345567.890876433
12345567890.876433

转换为Wei
1000000000000000000
1000000000
1000000

  • 转换为十六进制
    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]’))
执行结果:

[duke ~/ethStudy/web3/basicApi]$ node toHex.js
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)
执行结果:

[duke ~/ethStudy/web3/basicApi]$ node hexAndASCII.js
先将字符串’xyz’转换为ascii,然后转化为十六进制
0x78797a
先将十六进制转换为ascii,然后转化为字符串
xyz

  • 其他转换
    web3.utils.toDecimal()
    web3.utils.toBigNumber()
  • 检测地址有效性
    该方法只检查16进制长度,地址是40位16进制字符串,共160位

web3.utils.isAddress(add1)

  • 生成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)
执行结果:

[duke ~/ethStudy/web3/basicApi]$ node toHash.js
0x4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45
0xb8e12eedbb60e5321db47f5a3bfeb8ec0ff6ae9af10020cc61bb8c82ae0b7b66
九、合约模块
image-20181024173112096

  1. 部署合约
    ABI

BYTECODE

// import Web3 from ‘web3’;
let Web3 = require(‘web3’);

//1. 获取账户
// var web3 = new Web3(new Web3.providers.HttpProvider(“http://localhost:8545”));
var web3 = new Web3()
web3.setProvider(new Web3.providers.HttpProvider(“http://localhost:8545”))

const abi = [{
“constant”: false,
“inputs”: [],
“name”: “getMessage”,
“outputs”: [{“name”: “”, “type”: “string”}],
“payable”: false,
“stateMutability”: “nonpayable”,
“type”: “function”
}, {
“constant”: false,
“inputs”: [{“name”: “message”, “type”: “string”}],
“name”: “setMessage”,
“outputs”: [],
“payable”: false,
“stateMutability”: “nonpayable”,
“type”: “function”
}, {
“inputs”: [{“name”: “src”, “type”: “string”}],
“payable”: false,
“stateMutability”: “nonpayable”,
“type”: “constructor”
}];
const bytecode = ‘608060405234801561001057600080fd5b506040516103db3803806103db833981018060405281019080805182019291905050508060009080519060200190610049929190610050565b50506100f5565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061009157805160ff19168380011785556100bf565b828001600101855582156100bf579182015b828111156100be5782518255916020019190600101906100a3565b5b5090506100cc91906100d0565b5090565b6100f291905b808211156100ee5760008160009055506001016100d6565b5090565b90565b6102d7806101046000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063368b877214610051578063ce6d41de146100ba575b600080fd5b34801561005d57600080fd5b506100b8600480360381019080803590602001908201803590602001908080601f016020809104026020016040519081016040528093929190818152602001838380828437820191505050505050919291929050505061014a565b005b3480156100c657600080fd5b506100cf610164565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561010f5780820151818401526020810190506100f4565b50505050905090810190601f16801561013c5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b8060009080519060200190610160929190610206565b5050565b606060008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156101fc5780601f106101d1576101008083540402835291602001916101fc565b820191906000526020600020905b8154815290600101906020018083116101df57829003601f168201915b5050505050905090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061024757805160ff1916838001178555610275565b82800160010185558215610275579182015b82811115610274578251825591602001919060010190610259565b5b5090506102829190610286565b5090565b6102a891905b808211156102a457600081600090555060010161028c565b5090565b905600a165627a7a72305820f1e87be02dd6e7f710dbf54444a8ae7f74d50b8e48f13501e36f55edb4e630f70029’

deploy = async () => {
try {
accounts = await web3.eth.getAccounts()
let contract = await new web3.eth.Contract(abi)
let mycontract = await contract.deploy({
data: bytecode,
arguments: [‘Hi’],
}).send({
from: accounts[0],
gas: ‘1000000’, //注意要加上单引号,否则可能会报错
})

    //res就是合约实例,填充了address字段
    console.log("mycontract : ", mycontract.options.address) //这个是填好的合约实例
    console.log("contract : ", contract.options.address) //这个是空的
} catch (e) {
    console.error(e)
}

}
deploy()
创建完合约以后,所有合约相关的数据在mycontract.option中,方法在mycontract.methods中

  1. 调用合约方法
    调用合约有两种类型,具体如下:
  • send
  • call
  1. 代码
    let mycontract
    deploy = async () => {
    try {
    accounts = await web3.eth.getAccounts()
    let contract = await new web3.eth.Contract(abi)
    mycontract = await contract.deploy({
    data: bytecode,
    arguments: [‘Hi’],
    }).send({
    from: accounts[0],
    gas: ‘1000000’, //注意要加上单引号,否则可能会报错
    })

     //res就是合约实例,填充了address字段
     console.log("mycontract : ", mycontract.options.address)
     console.log("contract : ", contract.options.address)
    
     //++++++++++++++++++++++++ 从这里开始 +++++++++++++++++++++++++++++
    
     let getMsg = await mycontract.methods.getMessage().call({
         from : accounts[0],
     })
     console.log('getMessage111 : ', getMsg)
    
     res1 = await mycontract.methods.setMessage('Hello World!').send({
         from : accounts[0],
     }, console.log)
     console.log('res1 : ', res1)
    
     getMsg = await mycontract.methods.getMessage().call({
         from : accounts[0],
     })
     console.log('getMessage222 : ', getMsg)
    

    } catch (e) {
    console.error(e)
    }
    }
    deploy()
    十、使用的API总结

  • 基础api
    api 含义 备注
    web3.utils.fromWei(1000, ‘ether’) 由1000wei转成ether
    web3.utils.toWei(1, ‘ether’) 由1ether转成wei
    bignumber 能够处理大的数据
    web3.utils.toHex 转成16进制
    web3.utils.fromAscii 由ascii转成16进制
    web3.utils.isAddress 判断是否为地址
    web3.utils.sha3 sha3哈希函数
    web3.utils.toDecimal 转成10进制
  • 与eth相关
    api 含义 备注
    web3.eth.getAccounts() 获取当前的账户 一般只返回当前助记词的主账户
    web3.eth.getBalance() 获取指定地址的余额
    web3.eth.defaultAccount() 默认区块
    web3.eth.sendTransaction() 转账
  • 与合约相关
    api 含义 备注
    web3.eth.Contract(JSON.parse(interface)).deploy() 部署合约
    web3.eth.方法.send() 调用函数向合约写数据
    web3.eth.方法.call() 调用函数向合约读数据
    Web3.providers.HttpProvider(“http://localhost:8545”) 创建provider
    window.web3.currentProvider 获取当前的provider

你可能感兴趣的:(以太坊)