背景描述
我为了自己开发方便,做了一个以太坊工具集,里面提供了RPC的调用,Web3.js调用合约等。但是单纯使用rpc调用,无法满足某些测试需求,比如我想在一个块里面包含3比交易,那么我需要连续发3笔交易,我不可能手动连续点三次eth_sendTransaction。于是我使用JavaScript的函数eval()
实现了自己的需求。总体界面大概如下:
实现概要
我主要使用了库axios做的网络库。首先在添加主机的时候,我导出了一个全局的变量client
,client初始化的代码如下所示:
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。代码如下:
let i = document.createElement("iframe");
i.style.display = "none";
document.body.appendChild(i);
console.log = i.contentWindow.console.log;
具体使用
首先,你需要点击左上角的IP地址或者点击设置->更换链接使用正确的IP地址初始化全局变量client
跟web3
。
以使用clinet
组包rpc与后台ethereum为例子,用web3
相似。
因为client.dispatch
是一个async函数,如果你需要使用await的方式调用它, 需要在async函数里面执行。所以你的代码总体应该是一个自执行的async函数。如下所示:
(async function() {
// you code 你的代码
})();
具体一个实例,假设有如下一个智能合约MonitorTest.sol
:
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代码如下:
(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即可。
- 保存:将你的代码保存到缓存中。
- 执行:执行你的代码。