在浏览器上使用JavaScript代码与以太坊RPC通讯

背景描述

我为了自己开发方便,做了一个以太坊工具集,里面提供了RPC的调用,Web3.js调用合约等。但是单纯使用rpc调用,无法满足某些测试需求,比如我想在一个块里面包含3比交易,那么我需要连续发3笔交易,我不可能手动连续点三次eth_sendTransaction。于是我使用JavaScript的函数eval()实现了自己的需求。总体界面大概如下:

实现概要

我主要使用了库axios做的网络库。首先在添加主机的时候,我导出了一个全局的变量client,client初始化的代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
async function(protocol, ip, port) {
let host = `${protocol}://${ip}:${port}`;

Vue.prototype.$web3 = new window.Web3(host);
protocol.indexOf("ws") > 0 && Vue.prototype.$web3.setProvider(new window.Web3.providers.WebsocketProvider(host));
window.$web3 = Vue.prototype.$web3;
window.web3 = Vue.prototype.$web3;
let client = {};
if (protocol === "ws") {
client = new WebSocket(host);
client.dispatch = data => {
return new Promise((resolve, reject) => {
client.send(JSON.stringify(data));
let tId = setTimeout(() => {
reject("Time out");
}, 10000);
client.onmessage = function(evt) {
var reply = JSON.parse(evt.data);
if (reply.id == data.id) {
resolve(reply);
clearTimeout(tId);
}
};
});
};
} else {
client = axios.create({
baseURL: host
});
client.dispatch = async (data, async = true) => {
if (async) {
let replay = await client.post("", data);
return new Promise((resolve, reject) => {
if (replay.status === 200) {
resolve(replay.data);
} else {
reject("request error");
}
});
} else {
return client.post("", data);
}
};
}
Vue.prototype.$client = client;
window.$client = client;
window.client = client;
};

主要是client.dispatch,初始化之后,就可在任意地方调用该函数于以太坊底层服务器进行rpc交互。第二个参数async主要是为了等待还是不等待结果返回。如果设为true,那么等待底层的给的数据返回,如果不设置,那么直接返回一个promise。

从上面代码可以看到,我也导出了一个全局的变量web3,所以也可以在其他地方使用web3进行合约等一系列的调用交互。

从上面的界面图可以看出后面调用完成之后有些日志,因为写的脚本有些使用console.log输出日志,我为了实现这个需求,使用我自己写的函数console.log = this.evalLog;直接拦截了系统打印的日志然后显示到界面上。但是,当你切到其他界面的时候,需要恢复console.log。代码如下:

1
2
3
4
let i = document.createElement("iframe");
i.style.display = "none";
document.body.appendChild(i);
console.log = i.contentWindow.console.log;

具体使用

首先,你需要点击左上角的IP地址或者点击设置->更换链接使用正确的IP地址初始化全局变量clientweb3

以使用clinet组包rpc与后台ethereum为例子,用web3相似。

因为client.dispatch是一个async函数,如果你需要使用await的方式调用它, 需要在async函数里面执行。所以你的代码总体应该是一个自执行的async函数。如下所示:

1
2
3
4
5
(async function() {

// you code 你的代码

})();

具体一个实例,假设有如下一个智能合约MonitorTest.sol

1
2
3
4
5
6
7
8
9
10
11
12
13
pragma solidity ^0.4.2;
contract MonitorTest {
uint value;

function set(uint amount) public returns(uint) {
value = amount;
return value;
}

function get() public constant returns(uint) {
return value;
}
}

我需要连续三次调用set(uint)设置合约的值。那么,你的JavaScript代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
(async function() {
const pwd = "12345678"; // 解锁密码
const monitorTestAddress = "0x0d2bf7651722048b3b055d63b8acdcd4f2a385cd"; // 合约 MonitorTest 的地址
let from = null; // 调用者地址

function getId() {
let id = parseInt(new Date().Format("hhmmss"));
return id;
}

// 获取所有账号
let getAccounts = async () => {
let data = {
method: "eth_accounts",
jsonrpc: "2.0",
id: parseInt(getId()),
params: []
};
try {
let replay = await client.dispatch(data);
return Promise.resolve(replay.result);
} catch (error) {
return Promise.reject(error);
}
};

// 解锁所有账号
let unlockAccounts = async accounts => {
for (const account of accounts) {
let data = {
method: "personal_unlockAccount",
jsonrpc: "2.0",
id: parseInt(getId()),
params: [account, pwd, 24 * 60 * 60]
};
try {
await client.dispatch(data);
} catch (error) {
return Promise.reject(error);
}
}
return Promise.resolve(true);
};

// 发交易形式。asyncCall 是否等确认上链。
let sendTransactionMonitorData = async (asyncCall = true, gas, num) => {
// 第一次解锁一次
if (!from) {
let accounts = await getAccounts();
await unlockAccounts([accounts[0]]);
from = accounts[0];
}

if (!from) {
console.log("no account!");
return null;
}

// 组rpc包
num = num || parseInt(Math.random() * 100000);
let funHash = "60fe47b1";
let numHex = ("0000000000000000000000000000000000000000000000000000000000000000" + Number(num).toString(16)).slice(-64);
var sendTransaction = {
jsonrpc: "2.0",
id: getId(),
method: "eth_sendTransaction",
params: [{
gas: gas || "0xe8d4a50fff",
gasPrice: "0x174876e800",
from: from,
data: "0x" + funHash + numHex,
to: monitorTestAddress
}]
};

// 调用
let reply = null;
if (asyncCall) {
reply = await client.dispatch(sendTransaction); // 等待交易hash返回
} else {
reply = client.dispatch(sendTransaction, false); // 不等待交易hash返回
}

return reply;
};

var loop = 3;
while (loop--) {
let reply = await sendTransactionMonitorData(true); // 等待交易hash返回

// 不等待交易hash返回,直接返回一个promise,注意调用前面没有 await
//let reply = sendTransactionMonitorData(false);

console.log(reply);
}
})();

一些按钮功能描述:

  • send.js:这列显示的是你调用并保存的文件,切换文件会跟随切换你保存的代码。
  • 执行自动保存代码:你点击执行按钮,会自动保存你的代码到缓存中。
  • 删除:删除当前保存在缓存中的代码。
  • 格式化:在代码编辑框右键点Format Document即可。
  • 保存:将你的代码保存到缓存中。
  • 执行:执行你的代码。

相关资料

web3.js 1.0中文手册
以太坊JSON RPC手册
Restoring console.log()

您的支持将鼓励我继续创作!
0%