前言:创建创世文件genesis.json、初始化创世文件genesis.json 并 搭建私链。
1.创建创世区块配置文件 genesis.json
创建私链文件夹 PrivateChain,在其目录下创建 genesis.json文件,genesis.json文件配置内容如下:
{
"nonce": "0x0000000000000042",
"difficulty": "2000",
"mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"coinbase": "0x0000000000000000000000000000000000000000",
"timestamp": "0x00",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"extraData": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa",
"gasLimit": "2100000",
"alloc": {
"0x6E53739cBbDDAbEfC8B9070a6dEB49937bBD13F2": {
"balance": "1337000000000000000000"
},
"0xB16807bCe0BAe252a9ad684f9bE7F09b215c9018": {
"balance": "229070000000000000000"
}
},
"config": {
"chainId": 717,
"homesteadBlock": 0,
"eip150Block": 0,
"eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0,
"constantinopleBlock": 0,
"petersburgBlock": 0,
"istanbulBlock": 0,
"ethash": {}
}
}
2. 初始化创世文件genesis.json 并 搭建私链。
geth --datadir node1 init genesis.json
geth --datadir node2 init genesis.json
geth --datadir node3 init genesis.json
geth --datadir node1 --networkid 20200101 --http --http.api "db,eth,net,web3,personal" --http.addr 0.0.0.0 --http.port 8545 --http.corsdomain "*" --http.vhosts "*" --nodiscover --port 30303 --allow-insecure-unlock --ipcdisable console
geth --datadir node2 --networkid 20200101 --http --http.api "db,eth,net,web3,personal" --http.addr 0.0.0.0 --http.port 8546 --http.corsdomain "*" --http.vhosts "*" --nodiscover --port 30304 --allow-insecure-unlock --ipcdisable console
geth --datadir node3 --networkid 20200101 --http --http.api "db,eth,net,web3,personal" --http.addr 0.0.0.0 --http.port 8547 --http.corsdomain "*" --http.vhosts "*" --nodiscover --port 30305 --allow-insecure-unlock --ipcdisable console
miner.start(); admin.sleepBlocks(1); miner.stop()
admin.nodeInfo.enode
admin.addPeer(" ")
一、solidty语言相关
二、remix在线创建、编译和部署合约
1. remix编辑代码时打开优化optimization
2. remix编译代码需要注意编译器版本,链上验证合约时需要注意填写正确的编译器版本否则验证不通过
3. 交易的gas费用预估 通过web3调用接口 eth.estimateGas({}) 来实现,示例如 : <br>
eth.estimateGas({ from: eth.accounts[0], to: eth.accounts[1], value: 10 }) //注意:是官方实现的预估实现,需要真实数据。
4. 链上交互 分为两种情况:发送交易 和 调用合约。
4.1 发送交易 传递value, 解析交易数据:
eth.sendTransaction({ from: , to: , value: })
4.2 调用合约 传递DATA内容,解析DATA数据:
DATA位32字节即64位的十六进制数据,其中:
DATA前8位为函数名称sha3()后的前8位十六进制编码,DATA后几位为函数参数的十六进制编码,中间空余的全部填充0。
即:发送DATA数据给合约地址,EVM会解释为函数调用,从payload 里解码出函数名称和参数,调用该函数并传入参数。
注意:
发送给合约的数据有效负载是32字节的十六进制序列化编码,如 :0x2e1a7d4d0000000000000000000000000000000000000000000000000000000000000064
即:调用 简单水龙头合约withdraw(100) 则: 函数:withdraw(uint256), 参数:100, 随后观察签名信息里的DATA会显示如下:
十六进制数据: 36 BYTES
0x2e1a7d4d0000000000000000000000000000000000000000000000000000000000000064
其中:
// 可在geth私链目录下打开geth测试链来测试验证: geth --datadir node_dev --dev console
4.2.1 函数选择器:函数原型 'withdraw(uint256)' 通过Keccak256哈希后的前4个字节前8位作为DATA的前面8位。
即:2e1a7d是函数名称 可通过 web3.sha3()求得:
web3.sha3('withdraw(uint256)') // "0x2e1a7d4d13322e7b96f9a57413e1525c250fb7a9021cf91d1540d5b69f16a49f"
4.2.2 函数参数:函数参数 100 的十六进制表示作为DATA数据的后面几位。 即:64 是参数100的十六进制编码表示。
即:64是函数参数的十六进制表示,可通过 web3.toHex()求得:
web3.toHex(100) //0x64
三、开发环境终端工具
node(js后端运行环境)、npm(node包管理器)、solc(终端命令行编译器)、ganache - cil(测试环境私链)、truffle(框架工具)
3.1 node(js后端运行环境)
参考教程: https://www.runoob.com/nodejs/nodejs-install-setup.html
//windows安装node 官网下载地址:https://nodejs.org/zh-cn
node --version // 验证版本
//ubuntu安装node:
sudo apt install nodejs
sudo apt install npm
node --version
npm --version
//升级node 和 npm
sudo apt install n -g //安装用于安装nodejs的n
sudo n latest //通过n安装指定的nodejs:支持字段latest/stable/lts
sudo npm install npm@latest -g //升级npm为最新版本
sudo node --version //查看版本
sudo npm --version
//启动、变量赋值 和 变量输出
node // 启动node交互式编辑器
//变量声明需要使用 var 关键字,如果没有使用 var 关键字变量会直接打印出来。
var x = 10 // undefined
x // 10
y = 20 // 20 没有使用Var关键字将直接打印
//下划线(_)获取上一个表达式的运算结果
var x = 10
var y = 20
x + y
var sum = _
console.log(sum) // 30
3.2 npm(node包管理器)
npm - v // 查看版本/--version
npm install npm - g // 更新升级npm版本
//升级node 和 npm
sudo apt install n -g //安装用于安装nodejs的n
sudo n latest //通过n安装指定的nodejs:支持字段latest/stable/lts
sudo npm install npm@latest -g //升级npm为最新版本
sudo node --version //查看版本
sudo npm --version
//使用help帮助
npm help show // 查看show命令的用法
//查看已安装的模块
npm list - g // 查看所有全局安装的模块
npm list web3 - g // 查看全局安装的web3模块的版本号,注意:全局安装不代表可以任意目录下require引用。
npm list web3 // 查看当前目录本地安装web3模块的版本号,注意:本地安装可在当前工作目录下require引用。
//查看服务器上注册的模块
npm show web3 // 或者使用 view、info
npm show web3 @0.20.1 dependencies // 显示指定版本 依赖项 ronn版本包
npm view web3 repository.url // 显示最新版本的git 存储库 URL
//查看包安装的目录
npm root
//npm的包安装分为本地安装(local)、全局安装(global)两种,安装命令的差别只是有没有-g而已,但使用方式有区别,一般不推荐全局安装,如果需要两种方式都安装。
npm install < Module Name > //# 本地安装:将安装包放在 ./node_modules 下(运行 npm 命令时所在的目录),可工作目录下require()引入
npm install < Module Name > -g //# 全局安装:将安装包放在 /usr/local 下或者你 node 的安装目录,可在终端命令行使用,不可直接require()引入(需配置环境变量)
npm config set proxy null // 解决安装模块出现错误:npm err! Error: connect ECONNREFUSED 127.0.0.1:8087
//npm淘宝镜像:
npm install - g cnpm --registry = https://registry.npm.taobao.org // 然后就可以使用cnpm来完全代替npm
npm get registry //显示当前镜像地址
npm config set registry http://registry.npm.taobao.org //使用淘宝镜像地址
npm get registry //查看当前镜像地址是否设置成功
//更新、探索、卸载模块
npm update < Module Name > // 更新模块
npm search < Module Name > // 探索模块
npm uninstall < Module Name > // 卸载本地模块
npm uninstall < Module Name > -g // 卸载全局模块
//创建模块:init自动生成必不可少的package.json 文件,内容包括:
npm init // 初始化该创建模块目录,可添加参数 -y 默认初始化生成 package.json 文件,后期可以继续更改。
/*package.json文件内容解读:
Press ^C at any time to quit.
name: (node_modules) runoob # 模块名
version: (1.0.0)
description: Node.js 测试模块(www.runoob.com) # 描述
entry point: (index.js)
test command: make test
git repository: https://github.com/runoob/runoob.git # Github 地址
keywords:
author:
license: (ISC)
About to write to ……/node_modules/package.json: # 生成地址*/
//发布模块:在npm资源库中注册用户、设置密码、然后发布模块。
npm adduser // 设置用户名、密码、email
npm publish // 发布模块
//智能合约工作环境准备:创建并切换合约工作目录contracts,使用 npm init 来初始化新的npm包管理环境,然后使用npm install下载独立的工具包:
//npm init
npm install solc -g //安装后查
npm install ganache-cli
npm install truffle
3.3 solc(终端命令行编译器for solidity)
//全局安装solc模块,用于在命令行编译指定sol文件
npm install - g solc // -g全局安装后可做命令行工具使用
solcjs --version // 查看solcjs版本 注意使用命令 solcjs 而不是solc
//编译sol文件
solcjs --abi Coin.sol // 命令行里编译 Coin.sol文件 到当前目录下,生成abi文件json接口,格式:文件名称_文件格式_合约名称.abi
solcjs --bin Coin.sol // 命令行里编译 Coin.sol文件 到当前目录下,生成bin文件字节码,格式:文件名称_文件格式_合约名称.bin
3.4 ganache-cil(测试环境私链)
//注意:如果是windows的shell运行可能存在策略组的问题,
//查看策略组情况:Get-ExecutionPolicy -List,
//更改策略组情况:Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope CurrentUser
//全局安装ganache-cli
npm install ganache-cli -g
//查看版本
ganache-cli --version
//启动
ganache-cli
//启动选项
-a 或 –accounts: 指定启动时要创建的测试账户数量。
-e 或 –defaultBalanceEther: 分配给每个测试账户的ether数量,默认值为100。
-b 或r –blockTime: 指定自动挖矿的blockTime,以秒为单位。默认值为0,表示不进行自动挖矿。
-d 或 –deterministic: 基于预定的助记词(mnemonic)生成固定的测试账户地址。
-n 或 –secure: 默认锁定所有测试账户,有利于进行第三方交易签名。
-m 或 –mnemonic: 用于生成测试账户地址的助记词。
-p 或 –port: 设置监听端口,默认值为8545。
-h 或 –hostname: 设置监听主机,默认值同NodeJS的server.listen()。
-s 或 –seed: 设置生成助记词的种子。.
-g 或 –gasPrice: 设定Gas价格,默认值为20000000000。
-l 或 –gasLimit: 设定Gas上限,默认值为90000。
-f 或 –fork: 从一个运行中的以太坊节点客户端软件的指定区块分叉。输入值应当是该节点旳HTTP地址和端口,例如http://localhost:8545。 可选使用@标记来指定具体区块,例如:http://localhost:8545@1599200。
-i 或 –networkId:指定网络id。默认值为当前时间,或使用所分叉链的网络id。
–db: 设置保存链数据的目录。如果该路径中已经有链数据,ganache-cli将用它初始化链而不是重新创建。
–debug:输出VM操作码,用于调试。
–mem:输出ganache-cli内存使用统计信息,这将替代标准的输出信息。
–noVMErrorsOnRPCResponse:不把失败的交易作为RCP错误发送。开启这个标志使错误报告方式兼容其他的节点客户端,例如geth和Parity。
特殊选项
–account: 指定账户私钥和账户余额来创建初始测试账户。可多次设置:
ganache-cli --account="<privatekey>,balance" [--account="<privatekey>,balance"]
注意私钥长度为64字符,必须使用0x前缀的16进制字符串。账户余额可以是整数,也可以是0x前缀的17进制字符串,单位为wei。
使用–account选项时,不会自动创建HD钱包。
-u 或 –unlock: 解锁指定账户,或解锁指定序号的账户。可以设置多次。当与–secure选项同时使用时,这个选项将改变指定账户的锁定状态:
ganache-cli --secure --unlock "0x1234..." --unlock "0xabcd..."
也可以指定一个数字,按序号解锁账号:
ganache-cli --secure -u 0 -u 1
将geth链接到ganache-cli,需要在另外一个终端里执行geth的attach子命令,链接到运行中的ganache-cli:
geth attach http://localhost:8545
3.5 mocha(测试工具)
npm install mocha -g //全局安装
npm install mocha --save-dev //开发环境依赖,也可以不指定依赖选项
mocha --version //查看版本信息
mocha ./test.js //执行测试文件test.js
mocha //不带命令,则默认执行测试当前目录下的 test目录 内的所有测试文件
//示例参考 测试脚本 内容
describe('name', () => {
beforeEach(()=>console.log('当前小次测试开始.')) //每一次组内小次测试开始前执行
afterEach(()=>console.log('当前小次测试完成.')) //每一次组内小次测试完成后执行
after(()=>console.log('全部测试完成。')) //全部测试完成后执行
before(()=>console.log('全部测试开始...')) //全部测试开始前执行,不局限代码位置。
it('should has initial brand', async () => {
const brand = await contract.methods.brand().call(); // 注意:await阻塞调用合约方法获取brand
assert.equal(brand, initialBrand); // 断言初始brand = 构造函数参数传入的brand
});
// ...
})
3.6 truffle(框架工具)
待更新
四、web3库
//web3.js_0.2手册:http://cw.hubwiz.com/card/c/web3.js/1/1/1/
//web3.js_1.0手册:http://cw.hubwiz.com/card/c/web3.js-1.0/
在geth客户端里web3库有很多工具包可以使用, 如 HEX十六进制与Decimal十进制的转换:web3.toHex(.) 和 web3.toDecimal(0x..) 等。
//注意:geth客户端里内置的web3类型截止目前(20211010)仍然为0.20.1,目前更新的web3版本为1.6
//创建两个不同版本的web3工作目录,如:testWeb3_0.20.1 和 testWeb3_1.6,然后来分别安装不同版本的web3尝试操作。
//npm init
npm install web3 @0.20.1
npm install web3 //将安装最新版本的web3,如果windows下安装不顺畅则使用: cnpm install web3
npm install solc //当前工作目录中安装solc,用于在当前工作目录内打开的node环境的文件中编译读取到的sol内容,注意版本问题 @0.4.24
npm list solc //查看当前目录安装版本等信息
//注意版本问题:npm uninstall solc; npm install solc@0.4.24
npm install solc - g // 全局安装solc模块,用于在命令行编译sol文件
npm list solc -g // 查看全局安装版本等信息
solcjs--version // 查看solcjs版本 注意使用命令 solcjs 而不是solc
solcjs --abi Coin.sol // 命令行里编译 Coin.sol文件 到当前目录下,生成abi文件json接口,格式:文件名称_文件格式_合约名称.abi
solcjs --bin Coin.sol // 命令行里编译 Coin.sol文件 到当前目录下,生成bin文件字节码,格式:文件名称_文件格式_合约名称.bin
npm install ganache-cli -g //模拟区块链环境
ganache-cli --version //查看版本信息
ganache-cli //打开模拟区块链环境
//web3模块加载
var Web3 = require('web3') // 引入web3模块
var web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545')) // 连接web3的节点,即创建web3对象
//web3环境检查
if (typeof web3 !== 'undefined') {
web3 = new Web3(web3.currentProvider);
} else {
web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
}
//注意:1.0以上版本全部升级为异步版本,直接可以链式调用then()使用。
//网络状态查询
web3.isConnected() //? 同步:查看是否连接到节点; 或者 web3.net.listening
web3.net.getListening((err,res)=>console.log(res)) //? 异步:查看是否连接到节点;
web3.eth.net.isListening().then(console.log) // 1.0版本查看是否连接到节点;
//获取 network id
web3.version.network // ? 同步:
web3.version.getNetwork((err, res)=>{console.log(res)}) //? 异步:
web3.eth.net.getId().then(console.log) //? v1.0.0:
//获取节点的以太坊协议版本
web3.version.ethereum //? 同步: 测试网禁用
web3.version.getEthereum((err, res)=>{console.log(res)}) //? 异步: 测试网禁用
web3.eth.getProtocolVersion().then(console.log) //? v1.0.0: 测试网禁用
//当前连接的peer节点
web3.net.peerCount //? 同步:
web3.net.getPeerCount((err,res)=>console.log(res)) //? 异步:
web3.eth.net.getPeerCount().then(console.log) // ? v1.0.0:
//Provider 节点客户端
//查看当前设置的 web3 provider
web3.currentProvider
//查看浏览器环境设置的 web3 provider(v1.0.0)
web3.givenProvider
//设置更换 provider :web3.setProvider(provider)
web3.setProvider(new web3.providers.HttpProvider('http://localhost:8546')) //更换到8546端口的节点客户端
//web3通用工具方法
//大数处理
var BigNumber = require('bignumber.js') // 引入bignumber.js模块 注意:引入的名称要和node_modules包文件里的名称一致
var balance = new BigNumber("6546546541679746513213203654846541546879684654635135153464") // 定义一个大数
balance // s:代表符号正数为1负数为-1; e:代表科学计数法位数; c:代表所有有效数字的数组[,]每14位切割一次作为数组元素。
balance.toString() // 转换字符串 默认科学记数法显示
balance.toString(10) // 转换字符串 十进制格式 注意:10进制的转换字符串小数位只保留20位。推荐内部总是用 wei 来表示余额(大整数),只有在需要显示给用户看的时候才转换为ether或其它单位
balance.toString(16) // 转换字符串 十六进制格式
//以太单位转换
//1.0版本全部放在web3.utils模块内
web3.fromWei(323452348972923840980920389,'ether')
web3.utils.fromWei('323452348972923840980920389','ether') //? v1.0.0:
web3.toWei(1,'ether')
web3.utils.toWei('1','ether') //? v1.0.0:
//数据类型转换 (对象方法)
web3.toString([10,16])
web3.toDecimal()
web3.toBigNumber()
//字符编码转换(对象方法)
web3.toHex()
web3.toAscii()
web3.toUtf8()
web3.fromUtf8()
//地址相关
web3.isAddress('0x23f511D961955fB78c294D5dEAEB3d16F3e8DB3f')
web3.utils.isAddress('0x23f511D961955fB78c294D5dEAEB3d16F3e8DB3f') //? v1.0.0:
web3.toChecksumAddress('0x23F511D961955Fb78c294D5dEAEB3d16F3e8DB3f') // 转换大小写字母为标准地址字母
web3.utils.toChecksumAddress('0x23F511D961955Fb78c294D5dEAEB3d16F3e8DB3f') // 转换大小写字母为标准地址字母 //? v1.0.0:
//web3.eth – 账户相关
//旷工coinbase 查询
web3.eth.coinbase //? 同步:
web3.eth.getCoinbase( (err, res)=>console.log(res) ) //? 异步:
web3.eth.getCoinbase().then(console.log) //? v1.0.0:
//账户查询
web3.eth.accounts //? 同步:
web3.eth.getAccounts( (err, res)=>console.log(res) ) //? 异步:
web3.eth.getAccounts().then(console.log) //? v1.0.0:
(async () => {const accounts = await web3.eth.getAccounts();console.log(accounts[0])})() //v1.6版本异步获取账户0
//区块相关
//区块高度查询
web3.eth.blockNumber //? 同步:
web3.eth.getBlockNumber( (err, res)=>console.log(res) ) //? 异步:
web3.eth.getBlockNumber().then(console.log); //? v1.0.0:
//gasPrice 查询
web3.eth.gasPrice //? 同步: 默认BN大数格式,可直接后面使用 .toString(10) 转换为十进制
web3.eth.getGasPrice( (err, res)=>console.log(res.toString(10)) ) //? 异步: .toString(10) 转换为十进制
web3.eth.getGasPrice().then(console.log); //? v1.0.0:
//区块查询
//web3.eth.getBlock( hashStringOrBlockNumber[ ,returnTransactionObjects] ) //? 同步: 可选参数当设置为true时,返回块中将包括所有交易详情,否则仅返回交易哈希。
//web3.eth.getBlock( hashStringOrBlockNumber, callback ) //? 异步:
web3.eth.getBlock(25)
web3.eth.getBlock(25,(err, res)=>console.log(res) ) //? 异步:
web3.eth.getBlock(25).then(console.log); //? v1.0.0:
//块中交易数量查询
//web3.eth.getBlockTransactionCount( hashStringOrBlockNumber ) //? 同步:
//web3.eth.getBlockTransactionCount( hashStringOrBlockNumber, callback ) //? 异步:
web3.eth.getBlockTransactionCount(25)
web3.eth.getBlockTransactionCount("0x407d73d8a49eeb85d32cf465507dd71d507100c1").then(console.log); //? v1.0.0:
//交易相关
//余额查询
//web3.eth.getBalance(addressHexString [, defaultBlock]) //? 同步:
//web3.eth.getBalance(addressHexString [, defaultBlock][, callback]) //? 异步:
web3.eth.getBalance(web3.eth.accounts[0], 100) //测试链指定太靠前区块查询余额时有BUG,最近15个区块内可查。
web3.eth.getBalance("0x23f511D961955fB78c294D5dEAEB3d16F3e8DB3f").then(console.log); //? v1.0.0:
//交易查询
web3.eth.getTransaction(transactionHash) //? 同步:
web3.eth.getTransaction(transactionHash [, callback]) //? 异步:
//交易执行相关
//? 交易收据查询(已进块)
web3.eth.getTransactionReceipt(hashString) //? 同步:
web3.eth.getTransactionReceipt(hashString [,callback]) //? 异步:
//? 估计 gas 消耗量
//web3.eth.estimateGas(callObject) //? 同步: 参数:callObject:Object - 交易对象,其from属性可选
//web3.eth.estimateGas(callObject [, callback]) //? 异步: 参数:callObject:Object - 交易对象,其from属性可选
web3.eth.estimateGas({to: "0x11f4d0A3c12e86B4b5F39B213F7E19D048276DAe",
data: "0xc6888fa10000000000000000000000000000000000000000000000000000000000000003"}
).then(console.log); //? v1.0.0: 去掉.then链式则为0.2版本方法
//发送交易
web3.eth.sendTransaction(transactionObject [, callback]) //
/*? 交易对象:
? from:发送地址
? to:接收地址,如果是创建合约交易,可不填
? value:交易金额,以wei为单位,可选
? gas:交易消耗 gas 上限gaslimit,可选
? gasPrice:交易 gas 单价,可选
? data:交易携带的字串数据,可选,如果是合约发送data则注意函数选择器内容十六进制头部为函数名称sha后的前8位,后面几位为合约参数的十六进制。
? nonce:整数 nonce 值,可选
返回:交易哈希*/
web3.eth.getBalance(web3.eth.accounts[0]).toString() //首先查询账户余额情况
web3.eth.getBalance(web3.eth.accounts[1]).toString()
web3.personal.unlockAccount(web3.eth.accounts[0]) //解锁账户才能进行操作
//web3.personal.unlockAccount(web3.eth.accounts[0],"******")
web3.eth.sendTransaction({
from: web3.eth.accounts[0],
to: web3.eth.accounts[1],
value: '1000000000000000'})
web3.eth.getBalance(web3.eth.accounts[1]).toString() //复查账户余额情况,pow需要挖矿
//消息调用
web3.eth.call(callObject [, defaultBlock] [, callback]) //
/*? 参数:
? 调用对象:与交易对象相同,只是from也是可选的
? 默认区块:默认“latest”,可以传入指定的区块高度
? 回调函数,如果没有则为同步调用*/
web3.eth.call({to:"0xc4abd0339eb8d57087278718986382264244252f",
data:"0xc6888fa10000000000000000000000000000000000000000000000000000000000000003" }
).then(console.log); //? v1.0.0: 去掉.then链式则为0.2版本方法
//日志过滤(事件监听)//参考网址:http://cw.hubwiz.com/card/c/web3.js/1/6/29/ 注意日志过滤的方法有两种:字符串(latest/pending)或者 条件过滤。
//web3.eth.filter( filterOptions [ , callback ] ) // 0.20版本
//参数:filterString 可以是 'latest'出块 or 'pending'针对交易的发送状态 ,或者可以填入一个日志过滤 options 即://var filter = web3.eth.filter(options);
//var filter = web3.eth.filter('latest')
var filter = web3.eth.filter('pending') //"pending"字段检测一笔交易的发送状态,即监听开启后可收到交易发送的信息推送
// 事件监听对象返回一个过滤对象,该过滤对象包含以下方法:
/*
Filter.get(callback): 返回满足过滤条件的日志。
Filter.watch(callback): 监听满足条件的状态变化,满足条件时调用回调。
Filter.stopWatching(): 停止监听,清除节点中的过滤。你应该总是在监听完成后,执行这个操作。
*/
// 监听日志变化:返回区块哈希
filter.watch((err, res)=>console.log(res));
//filter.watch(function(error, result){ if (!error) console.log(result);});
// 还可以用传入回调函数的方法,立刻开始监听日志
web3.eth.filter(options, function(error, result){if (!error) console.log(result);});
// 返回满足过滤条件的日志
filter.get((err, res)=>console.log(res));
//filter.get(function(error, result){ if (!error) console.log(result); });
// 停止监听
filter.stopWatching()
//合约相关 —— 创建合约
//参考视频:https://www.bilibili.com/video/BV1NJ411D7rf?p=54&spm_id_from=pageDriver
//web3.eth.contract
// 通过ABI创建交互json接口
//var MyContract = web3.eth.contract(abiArray);
var ABI = [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Sent","type":"event"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balances","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"minter","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"send","outputs":[],"stateMutability":"nonpayable","type":"function"}]
var myContract = web3.eth.contract(ABI)
// 已部署的合约通过地址初始化合约实例
//var contractInstance = MyContract.at(address);
// 未部署的合约通过.new()部署一个新合约
//var contractInstance = MyContract.new([constructorParam1] [, constructorParam2], {data: '0x12345...', from:myAccount, gas: 1000000});
//参数constructorParam: 合约的构造函数中的参数
//参数{}:data内容为byteCode字节码; 注意:通过solcjs --bin File.sol 编译的bin字节码文件的内容格式为十六进制,但是没有0x头指定,需要手动添加如:var bin = '0x'+"3151321*****"
var BIN='0x'+'608060405234801561001057600080fd5b50336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555061057c806100606000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c8063075461721461005157806327e235e31461006f57806340c10f191461009f578063d0679d34146100bb575b600080fd5b6100596100d7565b604051610066919061033d565b60405180910390f35b61008960048036038101906100849190610389565b6100fb565b60405161009691906103cf565b60405180910390f35b6100b960048036038101906100b49190610416565b610113565b005b6100d560048036038101906100d09190610416565b6101c5565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60016020528060005260406000206000915090505481565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461016b57600080fd5b80600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546101ba9190610485565b925050819055505050565b80600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101561021157600080fd5b80600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461026091906104db565b9250508190555080600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546102b69190610485565b925050819055507f3990db2d31862302a685e8086b5755072a6e2b5b780af1ee81ece35ee3cd33453383836040516102f09392919061050f565b60405180910390a15050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610327826102fc565b9050919050565b6103378161031c565b82525050565b6000602082019050610352600083018461032e565b92915050565b600080fd5b6103668161031c565b811461037157600080fd5b50565b6000813590506103838161035d565b92915050565b60006020828403121561039f5761039e610358565b5b60006103ad84828501610374565b91505092915050565b6000819050919050565b6103c9816103b6565b82525050565b60006020820190506103e460008301846103c0565b92915050565b6103f3816103b6565b81146103fe57600080fd5b50565b600081359050610410816103ea565b92915050565b6000806040838503121561042d5761042c610358565b5b600061043b85828601610374565b925050602061044c85828601610401565b9150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610490826103b6565b915061049b836103b6565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156104d0576104cf610456565b5b828201905092915050565b60006104e6826103b6565b91506104f1836103b6565b92508282101561050457610503610456565b5b828203905092915050565b6000606082019050610524600083018661032e565b610531602083018561032e565b61053e60408301846103c0565b94935050505056fea26469706673582212208b41e31e83edf02c534595cebe3107ed49f266d316a53185f4cefacee9be4c5664736f6c63430008090033'
var dataObj={from:web3.eth.accounts[0], data:BIN, gas:1000000}
var myContractInstance=myContract.new(dataObj) //部署Coin合约。返回哈希。 该Coin合约没有构造函数,所以只春如data对象就可以,否则需要首先传入构造函数的参数。
//部署合约后 直接查看myContractInstance 或者 可以查看返回的哈希
myContractInstance //没有挖矿前部署尚未完成地址为空,挖矿后部署完成可查看地址。
web3.eth.getTransaction("0xc1daa95d0af7aeeed095993b39cd13d0107ffe4c0ddefce9429b0d60557fd18d")
myContractInstance.address //挖矿后部署完成可查看地址。 返回PC1地址:'0xbeeb1a5f1decc75d20d2f75fb964199ba4c8e4e9' ;或者 Dell地址:'0xe9dc504a4da9d543dcebf9ab31e21d433c09f221',
//注意:如果没有解锁账户无法交互
web3.personal.unlockAccount(web3.eth.accounts[0]) //解锁账户才能进行操作
//web3.personal.unlockAccount(web3.eth.accounts[0],"******")
//调用合约函数
//特别注意:在web3.js中调用合约函数要在函数参数中添加dataObj对象 {from:0x46546***} 来指定由谁来发送,否则显示错误。
//? 可以通过已创建的合约实例:1、.myMethod()直接调用合约函数(只读方法私链不适用) ;2、.myMethod.call()显示调用只读函数;3、.myMethod.sendTransaction()显示发送交易调用函数
// 1、.myMethod()直接调用,自动按函数类型决定用 sendTransaction 还是 call, 实测私链环境不能自动调用只读函数需要通过.call调用view函数。
//myContractInstance.myMethod(param1 [, param2, ...] [,transactionObject] [, defaultBlock] [, callback]);
// 2、.myMethod.call()显式以消息调用形式 call 该函数(用于只读函数,不改变状态变量)
//myContractInstance.myMethod.call(param1 [, param2, ...] [,transactionObject] [, defaultBlock] [, callback]);
// 3、.myMethod.sendTransaction()显式以发送交易形式调用该函数(用于写入函数,改变状态变量)
//myContractInstance.myMethod.sendTransaction(param1 [,param2, ...] [, transactionObject] [, callback]);
//实例如:
myContractInstance.minter({from:web3.eth.accounts[0]}) //直接调用Coin合约中的minter()方法,如果再web3中不加入dataObj对象的from内容会报错。如果不显式.call调用则只返回哈希。
myContractInstance.minter.call({from:web3.eth.accounts[0]}) //直接调用Coin合约中的minter()方法,如果再web3中不加入dataObj对象的from内容会报错。如果不显式.call调用则只返回哈希。
myContractInstance.balances.call(web3.eth.accounts[0], {from:web3.eth.accounts[0]}) //只读函数注意使用.call,实测私链环境不能自动调用只读函数需要通过.call调用view函数。
myContractInstance.mint(web3.eth.accounts[0], 5555555555,{from:web3.eth.accounts[0]}) //写入函数可以直接调用
myContractInstance.mint(web3.eth.accounts[0], 5555555555,{from:web3.eth.accounts[0]}, (err, res)=>console.log(res)) //异步
myContractInstance.mint.sendTransaction(web3.eth.accounts[0], 5555555555,{from:web3.eth.accounts[0]}, (err, res)=>console.log(res)) //异步
myContractInstance.balances.call(web3.eth.accounts[0], {from:web3.eth.accounts[0]},(err, res)=>console.log(res)) //只读函数注意使用.call,实测私链环境不能自动调用只读函数需要通过.call调用view函数。
myContractInstance.balances.call(web3.eth.accounts[1], {from:web3.eth.accounts[0]},(err, res)=>console.log(res)) //只读函数注意使用.call,实测私链环境不能自动调用只读函数需要通过.call调用view函数。
myContractInstance.send(web3.eth.accounts[1], 12345, {from:web3.eth.accounts[0]})
myContractInstance.balances.call(web3.eth.accounts[1], {from:web3.eth.accounts[0]},(err, res)=>console.log(res)) //只读函数注意使用.call,实测私链环境不能自动调用只读函数需要通过.call调用view函数。
myContractInstance.balances.call(web3.eth.accounts[0], {from:web3.eth.accounts[0]},(err, res)=>console.log(res)) //只读函数注意使用.call,实测私链环境不能自动调用只读函数需要通过.call调用view函数。
//监听合约事件
//? 合约的 event事件 类似于 filter过滤器,可以设置过滤选项来监听
// 创建事件
//var event = myContractInstance.MyEvent({valueA: 23} [, additionalFilterObject])
var event = myContractInstance.Sent("pending")
// 监听事件
event.watch((err, res)=>console.log(res));
//event.watch(function(error, result){ if (!error) console.log(result); });
// 创建事件(如果传入回调函数则)立即监听。
var event1 = myContractInstance.Sent([{valueA: 23}] [, additionalFilterObject] , function(error, result){if (!error) console.log(result);});
五、web3.js脚步编写实现:自动发送交易 、转发代币等交互和监听合约(script)
5.1 自动发送交易脚本
var Web3 = require('web3')
var web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'))
var _from = web3.eth.accounts[0]
var _to = web3.eth.accounts[1]
var _value = 5000000
web3.eth.sendTransaction({from:_from, to:_to, value:_value}, (err,res)=>console.log(res))
5.2 自动转发代币脚本
// autoSendTokenFromArgumentsoS.js
var Web3 = require('web3')
var web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'))
var arguments = process.argv.splice(2) //命令行输入参数切割指定从索引2开始切割。注意索引0为 node,索引1为文件名称
if (!arguments || arguments.length != 2){
console.log('Error: Parameter error:must be 2 ')
} //异常处理
var _from = web3.eth.accounts[0] //发送方指定为第一个账户
var _to = arguments[0] //接收方通过命令行指定
var amount = arguments[1] //发送数量通过命令行指定
if (web3.isAddress(_to) != true) {
console.log('Error: You must give me a Address in The arguments parameter 3')
return
} //接收方地址检查
//var _from = web3.eth.accounts[0]
//var _to = web3.eth.accounts[1]
//var _value = 5000000
var coinAbi = [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Sent","type":"event"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balances","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"minter","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"send","outputs":[],"stateMutability":"nonpayable","type":"function"}]
var coinAddress = '0xe9dc504a4da9d543dcebf9ab31e21d433c09f221'
var coinContract = web3.eth.contract(coinAbi)
var coinInstance = coinContract.at(coinAddress)
//web3.personal.unlockAccount(_from,"******",3600, (err,res)=>{
// if (err){
// console.log('Error:',err)
// }
// else
// coinInstance.send(_to, amount, {from:_from},(err,res)=>console.log(res))
// }
//) //可升级控制台解锁,解锁账户注意:开启控制台需要指定--http.api "web3, eth, personal"
coinInstance.send(_to, amount, {from:_from},(err,res)=>console.log(res))
5.3 自动查询代币余额
// autoGetAmountOfTokenFromArgumentsoS.js
var Web3 = require('web3')
var web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'))
var arguments = process.argv.splice(2) //命令行输入参数切割指定从索引2开始切割。注意索引0为 node,索引1为文件名称
if (!arguments || arguments.length != 1){
console.log('Error: Parameter error:must be 1 ')
} //异常处理
var _addr = arguments[0] //接收方通过命令行指定
if (web3.isAddress(_addr) != true) {
console.log('Error: You must give me a Address in The arguments parameter 3')
return
} //接收方地址检查
var coinAbi = [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Sent","type":"event"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balances","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"minter","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"send","outputs":[],"stateMutability":"nonpayable","type":"function"}]
var coinAddress = '0xe9dc504a4da9d543dcebf9ab31e21d433c09f221'
var coinContract = web3.eth.contract(coinAbi)
var coinInstance = coinContract.at(coinAddress)
coinInstance.balances.call(_addr, (err,res)=>console.log(res))
5.4 监听执行脚本
// scriptForEvent.js //node scriptForEvent.js
var Web3 = require('web3')
var web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'))
var coinAbi = [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Sent","type":"event"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balances","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"minter","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"send","outputs":[],"stateMutability":"nonpayable","type":"function"}]
var coinAddress = '0xe9dc504a4da9d543dcebf9ab31e21d433c09f221'
var coinContract = web3.eth.contract(coinAbi)
var coinInstance = coinContract.at(coinAddress)
coinInstance.Sent('latest', (err,res)=>console.log("Sent Event occurs: ",res)) //传入回调函数 即 立即开始监听。
六、简单投票Dapp_Voting编写+编译+部署+测试+简单前端 的工作流程
6.1 编写合约Voting
// in the workdir: Voting.sol
pragma solidity >=0.4.22;
contract Voting {
mapping(bytes32 => uint8) public votesReceived; //映射字典:候选人映射得票数:{候选人名称: 票数}
bytes32[] public candidateList; //候选人列表
//构造函数:参数传入候选人列表名称
constructor(bytes32[] candidateNames) public {
candidateList = candidateNames;
}
//查询候选人的总得票数
function totalVotesFor(bytes32 candidate) view public returns (uint8) {
//声明参数候选人存在候选人列表为有效
require(validCandidate(candidate));
return votesReceived[candidate];
}
//投票给参数候选人
function voteForCandidate(bytes32 candidate) public {
//声明参数候选人存在候选人列表为有效
require(validCandidate(candidate));
votesReceived[candidate] += 1;
}
//有效候选人检查,是否存在候选人列表
function validCandidate(bytes32 candidate) view public returns (bool) {
for (uint i = 0; i < candidateList.length; i++) {
if (candidateList[i] == candidate) {
return true;
}
}
return false;
}
}
//["0x123b", "0x3454", "0xe324"]
6.2 编译部署交互合约Voting
//in the node
var Web3 = require('web3')
var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
web3.eth.accounts
var code = fs.readFileSync('Voting.sol').toString() //读取合约内容,转换为string类型。 注意:fs为node里的内置模块
//编译读取到的合约内容
var solc = require('solc')
var compiledCode = solc.compile(code) //文件内编译读取到的合约内容string. 注意版本问题:npm uninstall solc; npm install solc@0.4.24;npm install solc@0.4.26;
//部署合约
var abi = JSON.parse(compiledCode.contracts[':Voting'].interface) //提取编译合约后的abi接口
var byteCode = compiledCode.contracts[':Voting'].bytecode //提起编译合约后的byteCode字节码
var votingContract = web3.eth.contract(abi) //合约变量赋值
var deployObj = {from: web3.eth.accounts[0], data: byteCode, gas: 3000000} //合约交易对象赋值
var votingInstance = votingContract.new(['Alice','Bob', 'Cary'], deployObj) //部署合约:传入构造函数参数,传入合约交易对象。
//交互合约测试
votingInstance.voteForCandidate('Alice', {from: web3.eth.accounts[0] }) //执行合约方法:投票给Alice
//votingInstance.voteForCandidate.sendTransaction('Alice', {from: web3.eth.accounts[0] }) //执行合约方法:投票给Alice
votingInstance.totalVotesFor('Alice', {from: web3.eth.accounts[0] }).toString() //执行合约方法:查看总票数
//votingInstance.totalVotesFor.call('Alice', {from: web3.eth.accounts[0] }).toString() //执行合约方法:查看总票数
6.3 网页交互dapp_Voting
6.3.1 创建服务器文件server.js
//server.js
//保存该文件server.js, 然后同级目录下创建一个 index.html 文件 + 合约执行文件 index.js。
//创建完成后 node server.js 执行服务器文件,打开网页 http://127.0.0.1:8080/index.html 来显示网页交互信息。
//参考教程:https://www.runoob.com/nodejs/nodejs-web-module.html
var http = require('http');
var fs = require('fs');
var url = require('url');
// 创建服务器
http.createServer( function (request, response) {
var pathname = url.parse(request.url).pathname; // 解析请求,包括文件名
console.log("Request for " + pathname + " received."); // 输出请求的文件名
// 从文件系统中读取请求的文件内容
fs.readFile(pathname.substr(1), function (err, data) {
if (err) {
console.log(err);
response.writeHead(404, {'Content-Type': 'text/html'});
}else{
response.writeHead(200, {'Content-Type': 'text/html'});
response.write(data.toString()); // 响应文件内容
}
response.end(); // 发送响应数据
});
}).listen(8080);
// 控制台会输出以下信息
console.log('Server running at http://127.0.0.1:8080/');
6.3.2 创建index.html前端文件
<!-- // index.html -->
<!DOCTYPE html>
<html>
<head>
<title>Voting DApp</title>
<link href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css' rel='stylesheet' type='text/css'>
</head>
<body class="container">
<h1>A Simple Voting Application</h1>
<div class="table-responsive">
<table class="table table-bordered">
<thead>
<tr>
<th>Candidate</th>
<th>Votes</th>
</tr>
</thead>
<tbody>
<tr>
<td>Alice</td>
<td id="candidate-1"></td>
</tr>
<tr>
<td>Bob</td>
<td id="candidate-2"></td>
</tr>
<tr>
<td>Cary</td>
<td id="candidate-3"></td>
</tr>
</tbody>
</table>
</div>
<input type="text" id="candidate"/>
<a href="#" onclick="voteForCandidate()" class="btnbtn-primary">Vote</a>
</body>
<!--<script src="https://cdn.jsdelivr.net/npm/web3@latest/dist/web3.min.js"></script> -->
<script src="https://cdn.jsdelivr.net/npm/web3@0.20.1/dist/web3.min.js"></script>
<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js"></script>
<script src="./index.js"></script>
</html>
6.3.3 创建index.js创建js合约交互文件
//index.js
//注意合约的 abi和address 信息变更问题。
web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
abi = JSON.parse('[{"constant":true,"inputs":[{"name":"candidate","type":"bytes32"}],"name":"totalVotesFor","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"candidate","type":"bytes32"}],"name":"validCandidate","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"votesReceived","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"candidateList","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"candidate","type":"bytes32"}],"name":"voteForCandidate","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"candidateNames","type":"bytes32[]"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]' ) //输入编译后得到的abi
VotingContract = web3.eth.contract(abi);
contractInstance =VotingContract.at('0xc4f5bd0ab7740d29f58997a6591971496c659719'); //输入部署后得到的address
candidates = {"Alice": "candidate-1", "Bob": "candidate-2","Cary": "candidate-3"};
//调用合约方法投票
function voteForCandidate(candidate) {
candidateName = $("#candidate").val(); //候选人的名称定义,从html的 candidate 的input输入id里获取.val()值
try {
contractInstance.voteForCandidate(candidateName,{from: web3.eth.accounts[0]},
function() {
let div_id = candidates[candidateName]; // 获取候选人名称,以便接下来查找#ID
$("#"+div_id).html(contractInstance.totalVotesFor.call(candidateName).toString()); //填入得票数,通过id填入交互合约得到的得票数。
}
);
} catch (err) {
}
}
//页面全部加载完成后 调用回调函数 通过合约交互查询候选人得票数 填入进相关html页面中
$(document).ready(function() {
candidateNames = Object.keys(candidates); //提取参数对象里的所有键
for (var i = 0; i < candidateNames.length; i++) {
let name = candidateNames[i]; //赋值定义当前候选人
let val = contractInstance.totalVotesFor.call(name).toString() //获取投票数量:通过合约实例交互
$("#"+candidates[name]).html(val); //将候选人对应的得票数 填入到html页面中
}
});
七、js脚本编写实现合约的自动编译、部署、测试等
其一:
通过脚本构建自动编译、部署、测试
首先需要构建工作目录分别存放合约文件、脚本文件、通过自动脚本实现的已编译文件、测试文件。目录结构如:
mkdir contracts
mkdir scripts
mkdir compiled
mkdir tests
在相应的目录中创建完成脚本文件后通过命令 node *** 来执行脚本文件完成编译、部署、测试等
其二:
也可以通过 npm包管理工具的script脚本机制来run运行相关指令实现自动编译、部署、测试。
操作如:
//npm run *** 的脚本机制实现需要:
//配置node工作目录下的npm包管理文件package.json(通过npm init命令生成)内的scripts键值内容,如下:
"scripts": {
"compile": "node scripts/compile.js",
"deploy": "node scripts/deploy.js",
"test": "mocha tests/",
"start": "node server.js"
},
//然后通过命令实现实现自动化编译/部署/测试工作流程:
npm run compile
npm run test
npm run deploy
7.1 自动编译脚本
// compile.js 自动编译脚本实现
// scripts目录下创建该 compile.js文件,部署脚本node执行命令:node scripts/compile.js
// 引入自动编译需要的包模块
const fs = require('fs-extra'); //fs模块的拓展,查看是否安装:npm list fs-extra,若无则安装:npm isntall fs-extra
const path = require('path');
const solc = require('solc');
// cleanup 健壮性处理:每次编译前清空目录存放已编译文件目录,然后重建该目录
const compiledDir = path.resolve(__dirname, '../compiled'); //解析绝对路径:参数路径拼接为绝对路径。其中参数__dirname为当前运行文件所在的目录
//console.log('当前保存拟编译文件的目录: ',compiledDir); // 当前目录执行命令node compile.js来查看。注意后面代码处理。
fs.removeSync(compiledDir); // 删除参数目录
//console.log('清空保存拟编译文件的目录: ',compiledDir);
fs.ensureDirSync(compiledDir); // 确保参数目录存在。若无则创建。
//console.log('重建保存拟编译文件的目录: ',compiledDir);
// compile 编译指定文件内容 获得编译后的结果result
const contractPath = path.resolve(__dirname,'../contracts', 'Car.sol'); //解析待编译的指定文件地址。其中参数__dirname为当前运行文件所在的目录
const contractSource = fs.readFileSync(contractPath, 'utf8'); //读取待编译的指定文件内容
const result = solc.compile(contractSource, 1); //编译指定文件内容
//console.log(result); //查看指定文件编译后的编译结果
// check errors 健壮性处理:检查编译结果result错误信息,跑出错误信息详细内容
if (Array.isArray(result.errors) && result.errors.length) {
throw new Error(result.errors[0]);
}
// save to disk 编译后二次加工处理:保存编译结果result经过二次加工后的json文件
Object.keys(result.contracts).forEach(name => { // forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数。编译后的内容结构为:{contracts:{":合约名称:{bytecode:'', ...}}}
const contractName = name.replace(/^:/, ''); //将编译后contracts的 ':合约名称'键的名称字符串替换为 "合约名称"
const filePath = path.resolve(compiledDir,`${contractName}.json`); //解析已编译合约的拟保存 json文件地址
fs.outputJsonSync(filePath, result.contracts[name]); //输出保存已编译文件内容的 合约名称键 的 值内容,输出内容为编译结果的result.contracts[name]
console.log(`save compiled contract ${contractName} to ${filePath}`); //输出已保存
}
);
7.2 自动部署脚本
//deploy.js 自动部署合约脚本_web3_1.0以上版本实现
//scripts目录下创建该 deploy.js文件,部署脚本node执行命令:node scripts/deploy.js
//注意web3版本
const path = require('path');
const Web3 = require('web3');
const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545')); //连接rpc,也可以直接连接ganache.provider
// 1. 获取已编译合约的 abi 和 bytecode
const contractPath = path.resolve(__dirname, '../compiled/Car.json'); //解析绝对路径:已编译并二次加工的json文件
const { interface, bytecode } = require(contractPath); //多重复值。注意必须对应一致引入文件的键名称。 引入已编译并二次加工的json文件,直接获取键对应的值内容。
console.log('合约编译ABI :',interface);
// 2. 获取钱包里面的账户
_from = web3.eth.accounts[0];
console.log('部署合约账户:', _from);
// 3. 部署合约 web3_0.20.1版本实现方法
const contract = web3.eth.contract(JSON.parse(interface));
const deployObj = {data:bytecode, from:web3.eth.accounts[0], gas:1000000};
let contractInstance = contract.new('volvo',deployObj, (err, res)=>console.log('合约部署地址:',res.address));
/*
//异步实现部署合约 web3_1.0以上版本方法
//注意:web3_1.0以上版本部署合约时的构造函数参数需要[]括起来,不同于0.20.1版本的本身数据及结构传入。
(async () => {
// 2. 获取钱包里面的账户
const accounts = await web3.eth.getAccounts();
console.log('部署合约账户:', accounts[0]);
// 3. 创建合约实例并且部署。 web3_1.0以上版本实现方法
console.time('合约部署耗时');
var result = await new web3.eth.Contract(JSON.parse(interface)) //部署合约对象
.deploy({ data: bytecode, arguments: ['AUDI'] }) //部署合约:data传入bytecode字节码 和 arguments传入构造函数的参数
.send({ from: accounts[0], gas: '1000000' }); //部署合约:发送交易对象即完成部署。返回部署信息。
console.timeEnd('合约部署耗时');
console.log('合约部署地址:', result.options.address); //输出合约地址
}
)();
*/
7.3 自动测试脚本
npm install mocha -g //全局安装 npm install ganache //注意:需要全局且在当前目录同时安装,用于当前文件Web3实例直接传入ganache.provider(),独立客观。
//tests目录下创建car.sper.js测试文件
// car.sper.js
//tests目录下创建car.sper.js测试文件,测试文件 用于命令行 mocha car.sper.js 来实现单元测试Car.sol合约;或者通过 npm run test 来实现。
const path = require('path');
const assert = require('assert');
const ganache = require('ganache-cli'); //注意:需要全局且在当前目录同时安装,用于当前文件Web3实例直接传入ganache.provider(),独立客观。
const Web3 = require('web3');
// 1. 配置 provider
const web3 = new Web3(ganache.provider()); //Web3实例直接传入ganache.provider(),独立客观。
// 2. 拿到 abi 和 bytecode
const contractPath = path.resolve(__dirname,'../compiled/Car.json'); //解析绝对路径:已编译并二次加工的json文件
const { interface, bytecode } = require(contractPath); //多重复值。注意必须对应一致引入文件的键名称。 引入已编译并二次加工的json文件,直接获取键对应的值内容。
let accounts;
let contract;
const initialBrand = 'BMW';
//注意:describe(,)组测试,可以循环嵌套describe组测试。其中的每个 it就代表一个测试。
describe('contract', () => {
// 3. 注意:beforEach()每一小次测试前都要执行。
//每次跑单测时需要部署全新的合约实例,起到隔离的作用
//异步实现部署合约 web3_1.0以上版本方法
//注意:web3_1.0以上版本部署合约时的构造函数参数需要[]括起来,不同于0.20.1版本的本身数据及结构传入。
beforeEach(async () => {
console.log('当前小次测试开始.');
accounts = await web3.eth.getAccounts(); //异步获取账户列表
console.log('合约部署账户:', accounts[0]);
//部署合约实例web3_1.0以上版本方法,注意异步的await阻塞方法
contract = await new web3.eth.Contract(JSON.parse(interface)) // 合约对象创建
.deploy({ data: bytecode, arguments: [initialBrand] }) //
.send({ from: accounts[0], gas: '1000000' });
console.log('合约部署成功:', contract.options.address); });
// beforeEach(()=>console.log('当前小次测试开始.')) //每一次组内小次测试开始前执行
afterEach(()=>console.log('当前小次测试完成.')) //每一次组内小次测试完成后执行
after(()=>console.log('全部测试完成。')) //全部测试完成后执行
before(()=>console.log('全部测试开始...')) //全部测试开始前执行,不局限代码位置。
// 4. 编写单元测试
// 每个 it就代表一个测试。
it('deployed contract', () => {
assert.ok(contract.options.address); //asser.ok()断言参数内容为真
});
it('should has initial brand', async () => {
const brand = await contract.methods.brand().call(); // 注意:await阻塞调用合约方法获取brand
assert.equal(brand, initialBrand); // 断言初始brand = 构造函数参数传入的brand
});
it('can change the brand', async ()=>{
const newBrand = 'Benz';
await contract.methods.setBrand(newBrand) // 注意:await阻塞调用合约方法设置brand
.send({from: accounts[0]}); // 发送交易对象 实现 调用合约设置Brand
const brand = await contract.methods.brand().call(); // 调用合约查询brand
assert.equal(brand, newBrand); // 断言获取得到的brand = 调用合约设置的brand
});
});
八 完整工作流程(npm run的脚本机制 实现自动化编译/部署/测试)
//npm run *** 的脚本机制实现需要:
//配置node工作目录下的npm包管理文件package.json(通过npm init命令生成)内的scripts键值内容,如下:
"scripts": {
"compile": "node scripts/compile.js",
"deploy": "node scripts/deploy.js",
"test": "mocha tests/",
"start": "node server.js"
},
//然后通过命令实现实现自动化编译/部署/测试工作流程:
npm run compile
npm run test
npm run deploy
|