JSON 指的是 JavaScript 对象表示法(JavaScript Object Notation),是轻量级的文本数据交换格式。
JSON 使用 Javascript语法来描述数据对象,但是 JSON 仍然独立于语言和平台。JSON 解析器和 JSON 库支持许多不同的编程语言。 目前非常多的动态(PHP,JSP,.NET)编程语言都支持JSON。
JSON RPC就是基于JSON数据格式的RPC协议。墨客区块链在开放 Chain3 JavaScript API 的基础上,也开放了 JSON RPC API ,以适应用户不同编程语言的开发环境。
1.环境安装
本文平台:MOAC Nuwa v1.0.2;
操作系统:64位 Windows 10 中文版;
开发环境:node.js v8.11.1 + npm v5.6.0;
本文代码需要引入node.js的模块“request”,在项目目录安装该模块:
d:\myProject>npm install request
2.启动节点
以HTTP JSON-RPC方式启动节点:
D:\nuwa1.0.2.win>moac --rpc
启动节点时修改默认端口(8545)和地址(localhost):
D:\nuwa1.0.2.win>moac --rpc --rpcaddr <ip> --rpcport <portnumber>
如果浏览器访问RPC,启动节点需要定义CORS域(“browser地址”):
D:\nuwa1.0.2.win>moac --rpc --rpccorsdomain "http://localhost:3000"
3.无参数接口实例(chain3_clientVersion)
Parameters
Return
Example:保存文件getClientVersion.js
var request = require('request');
var url = "http://127.0.0.1:8545";
var requestData = {"jsonrpc":"2.0","method":"chain3_clientVersion","params":[],"id":101};
httprequest(url,requestData);
function httprequest(url,data){
request({
url: url,
method: "POST",
json: true,
body: data
}, function(error, response, result) {
if (!error && response.statusCode == 200) {
console.log(result) // 请求成功的处理逻辑
}
});
};
node getClientVersion.js,返回结果:
{ jsonrpc: '2.0',
id: 101,
result: 'Moac/v1.0.2-stable-632a21f0/windows-amd64/go1.9.5' }
4.带参数接口实例(mc_sendTransaction)
Parameters
Object:交易对象
Return
Example:保存文件sendTransaction.js
var request = require('request');
var url = "http://127.0.0.1:8545";
//moac转账
var fromAddr = "0x745c57ca5318093115d61bbca368XXXXXXXXXXXX";
var toAddr = "0x68986c1bcd54ae5dae69310fc64eXXXXXXXXXXXX";
var dict = {
"from": fromAddr,
"to": toAddr,
"gas": "0x76c0", // 30400
"gasPrice": "0x4a817c800", // 20000000000
"value": "0x9184e72a", // 2441406250
"data": "0x",
};
var requestData2 = {"jsonrpc":"2.0","method":"mc_sendTransaction","params":[dict],"id":99};
httprequest(url,requestData2);
//token转账
var fromAddr = "0x745c57ca5318093115d61bbca368XXXXXXXXXXXX";
var toAddr = "68986c1bcd54ae5dae69310fc64eXXXXXXXXXXXX"; //没有0x
var contractAddr = "0xa2580d58a58998ca06e6f5b2a96aXXXXXXXXXXXX";
var valueString = "000000000000000000000000000000000000000000000000000000003b9aca00"; //发送token数量1000000000,含8位Decimals
var dict = {
"from": fromAddr,
"to": contractAddr,
"gas": "0x76c0", // 30400,
"gasPrice": "0x4a817c800", // 20000000000
"value": "0x0",
"data": "0xa9059cbb000000000000000000000000" + toAddr + valueString,
};
var requestData3 = {"jsonrpc":"2.0","method":"mc_sendTransaction","params":[dict],"id":99};
httprequest(url,requestData3);
function httprequest(url,data){
request({
url: url,
method: "POST",
json: true,
body: data
}, function(error, response, result) {
if (!error && response.statusCode == 200) {
console.log(result) // 请求成功的处理逻辑
}
});
};
该步骤需要消耗gas费,因此运行以上代码需要解锁发送账号,在moac节点使用命令:
>personal.unlockAccount(mc.accounts[0], <passwd>, 300)
node sendTransaction.js,返回结果:
{ jsonrpc: '2.0',
id: 99,
result: '0x015dc3d64e6f0d218ded0309563e6f2fb2126fa4f6f1XXXXXXXXXXXXXXXXXXXX' }
5.非交易调用接口实例(mc_call)
Parameters
1.Object:交易对象
2.quantity | tag:integer,区块号;或者string,“latest”,“earliest” or "pending"。
Return
Example:保存文件mcCall.js。
var request = require('request');
var url = "http://127.0.0.1:8545";
//token查询
var fromAddr = "0x745c57ca5318093115d61bbca368XXXXXXXXXXXX";
var toAddr = "745c57ca5318093115d61bbca368XXXXXXXXXXXX";
var contractAddr = "0xc89d49950bcf72d58cc203538e4eXXXXXXXXXXXX";
var dict = {
"from": fromAddr,
"to": contractAddr,
"data": "0x8462151c000000000000000000000000" + toAddr, // 根据合约函数确定数据格式和数据值
};
var requestData5 = {"jsonrpc":"2.0","method":"mc_call","params":[dict,"latest"],"id":99};
httprequest(url,requestData5);
function httprequest(url,data){
request({
url: url,
method: "POST",
json: true,
body: data
}, function(error, response, result) {
if (!error && response.statusCode == 200) {
console.log(result) // 请求成功的处理逻辑
}
});
};
对合约的查询无需消耗gas费,node mcCall.js,返回结果:
{ jsonrpc: '2.0',
id: 99,
result: '0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000' }
6.签名交易调用接口实例(mc_sendRawTransaction)
Parameters
Return
Example:保存文件sendRawTransaction.js。
var request = require('request');
var secp256k1 = require('secp256k1');
var Hash = require("eth-lib/lib/hash");
var RLP = require("eth-lib/lib/rlp");
var Nat = require("eth-lib/lib/nat");
var Bytes = require("eth-lib/lib/bytes");
var Buffer = require('safe-buffer').Buffer;
var url = "http://127.0.0.1:8545";
// 私钥签名交易
var privateKey = "9a863cb325ba30b5f41bd285e80c14c2d96f86b21e90XXXXXXXXXXXXXXXXXXXX";
var toAddr = "0x745c57ca5318093115d61bbca368XXXXXXXXXXXX";
var tx = {
"nonce": "0x2e8",
"to": toAddr,
"gas": "0x76c0", // 30400
"gasPrice": "0x4a817c800", // 20000000000
"value": "0x29e8d60800",// 180000000000
"data": "0x",
"chainId": "0x63", // mianNet:99
"shardingFlag": "0x0",
"systemContract": "0x0",
"via": "0x",
};
// start, 代码来自 chain3\lib\utils\account.js
// To fix an error of 2 leading 0s
var trimLeadingZero = function (hex) {
while (hex && hex.startsWith('0x00')) {
hex = '0x' + hex.slice(4);
}
return hex;
};
var makeEven = function (hex) {
if(hex.length % 2 === 1) {
hex = hex.replace('0x', '0x0');
}
return hex;
};
function isHexString(value, length) {
if (typeof value !== 'string' || !value.match(/^0x[0-9A-Fa-f]*$/)) {
return false;
}
if (length && value.length !== 2 + 2 * length) {
return false;
}
return true;
}
function isHexPrefixed (str) {
return str.slice(0, 2) === '0x';
}
// Removes 0x from a given String
function stripHexPrefix (str) {
if (typeof str !== 'string') {
return str;
}
return isHexPrefixed(str) ? str.slice(2) : str;
}
function toBuffer (v) {
if (!Buffer.isBuffer(v)) {
if (Array.isArray(v)) {
v = Buffer.from(v)
} else if (typeof v === 'string') {
if (isHexString(v)) {
v = Buffer.from(padToEven(stripHexPrefix(v)), 'hex')
} else {
v = Buffer.from(v)
}
} else if (typeof v === 'number') {
v = intToBuffer(v)
} else if (v === null || v === undefined) {
v = Buffer.allocUnsafe(0)
} else if (v.toArray) {
// converts a BN to a Buffer
v = Buffer.from(v.toArray())
} else {
throw new Error('invalid type')
}
}
return v
}
function bufferToHex (buf) {
buf = toBuffer(buf)
return '0x' + buf.toString('hex')
}
function intToHex (i) {
var hex = i.toString(16)
if (hex.length % 2) {
hex = '0' + hex
}
return hex
}
function intToBuffer (i) {
var hex = intToHex(i)
return new Buffer(hex, 'hex')
}
function ecsign (msgHash, privateKeyStr) {
//Convert the input string to Buffer
if (typeof msgHash === 'string') {
if (isHexString(msgHash)) {
msgHash = Buffer.from(makeEven(stripHexPrefix(msgHash)), 'hex')
}
}
var privateKey = new Buffer(privateKeyStr, 'hex');
var sig = secp256k1.sign(msgHash, privateKey)
var ret = {}
ret.r = sig.signature.slice(0, 32)
ret.s = sig.signature.slice(32, 64)
ret.v = sig.recovery + 27
return ret
}
var signTransaction = function (tx, privateKey) {
//Check the input fiels of the tx
if (tx.chainId < 1) {
return new Error('"Chain ID" is invalid');
}
if (!tx.gas && !tx.gasLimit) {
return new Error('"gas" is missing');
}
if (tx.nonce < 0 ||
tx.gasLimit < 0 ||
tx.gasPrice < 0 ||
tx.chainId < 0) {
return new Error('Gas, gasPrice, nonce or chainId is lower than 0');
}
//Sharding Flag only accept the
//If input has not sharding flag, set it to 0 as global TX.
if (tx.shardingFlag == undefined){
// console.log("Set default sharding to 0");
tx.shardingFlag = 0;
}
try {
//Make sure all the number fields are in HEX format
var transaction = tx;
transaction.nonce = tx.nonce;
transaction.to = tx.to || '0x';//Can be zero, for contract creation
transaction.gasLimit = tx.gas;
transaction.gasPrice = tx.gasPrice;
transaction.data = tx.data || '0x';//can be zero for general TXs
transaction.value = tx.value || '0x';//can be zero for contract call
transaction.chainId = tx.chainId;
transaction.shardingFlag = tx.shardingFlag;
transaction.systemContract = '0x0';//System contract flag, always = 0
transaction.via = tx.via || '0x'; //Sharding subchain address
var rlpEncoded = RLP.encode([
Bytes.fromNat(transaction.nonce),
Bytes.fromNat(transaction.systemContract),
Bytes.fromNat(transaction.gasPrice),
Bytes.fromNat(transaction.gasLimit),
transaction.to.toLowerCase(),
Bytes.fromNat(transaction.value),
transaction.data,
Bytes.fromNat(transaction.shardingFlag),
transaction.via.toLowerCase(),
Bytes.fromNat(transaction.chainId),
"0x",
"0x"]);
var hash = Hash.keccak256(rlpEncoded);
// for MOAC, keep 9 fields instead of 6
var vPos = 9;
//Sign the hash with the private key to produce the
//V, R, S
var newsign = ecsign(hash, stripHexPrefix(privateKey));
var rawTx = RLP.decode(rlpEncoded).slice(0,vPos+3);
//Replace the V field with chainID info
var newV = newsign.v + 8 + transaction.chainId *2;
// Add trimLeadingZero to avoid '0x00' after makeEven
// dont allow uneven r,s,v values
rawTx[vPos] = trimLeadingZero(makeEven(bufferToHex(newV)));
rawTx[vPos+1] = trimLeadingZero(makeEven(bufferToHex(newsign.r)));
rawTx[vPos+2] = trimLeadingZero(makeEven(bufferToHex(newsign.s)));
var rawTransaction = RLP.encode(rawTx);
} catch(e) {
return e;
}
return rawTransaction;
};
// end, 代码来自 chain3\lib\utils\account.js
var signTx = signTransaction(tx, privateKey)
console.log("signTx:\n" , signTx);
var requestData6 = {"jsonrpc":"2.0","method":"mc_sendRawTransaction","params":[signTx],"id":99};
httprequest(url,requestData6);
function httprequest(url,data){
request({
url: url,
method: "POST",
json: true,
body: data
}, function(error, response, result) {
if (!error && response.statusCode == 200) {
console.log(result) // 请求成功的处理逻辑
}
});
};
node sendRawTransaction.js,返回结果:
signTx:0xf86f8202ec808504a817c8008276c094745c57ca5318093115d61bbca3687ca02c2984278529e8d6080080808081eaa0df6856287f3b4ab31c4cc050a7c43b2e1b4d06e2a62ba974f2fe79fc90d023a7a04f68ddd218c2a584ab74568824f98534c7b7b30a9af5b18fb8df37c1e6b76d64
{ jsonrpc: '2.0',
id: 99,
result: '0x4ef6096614f7c018a2681138443e528c462c0d889f7cd1f2d61db6861b135f5b' }
代码中需要手动设置nonce。
如果nonce设置太高,会正常返回hash,但是交易一直会在pending状态;
如果nonce设置太低,会报错:
{ jsonrpc: '2.0',
id: 99,
error: { code: -32000, message: 'nonce too low' } }
备注:1.本例中使用私钥(privateKey)对交易(tx)进行签名,签名后的数据作为参数传进JSON RPC 接口;
2.本例中签名的实现方法signTransaction,源码参考:
https://github.com/MOACChain/chain3/blob/master/lib/utils/account.js;
3.本例中的交易(tx)使用墨客主网格式(chainId=99;shardingFlag=0;systemContract=0;via="")。