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

背景描述

我为了自己开发方便,做了一个以太坊工具集,里面提供了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地址初始化全局变量clientweb3

以使用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即可。
  • 保存:将你的代码保存到缓存中。
  • 执行:执行你的代码。

相关资料

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

暂无评论

发送评论 编辑评论


				
上一篇
下一篇