第十七篇 墨客区块链(MOAC BlockChain) JSON RPC API的使用

版权声明:Copyright Reserved © 2018-2020 https://blog.csdn.net/lyq13573221675/article/details/82107371

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

  • none

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:交易对象

  • from:20字节,交易发送账号地址;
  • to:20字节,交易目的账号地址;
  • gas:integer,交易执行需要提供的gas费数量,没有用完的将返还;
  • gasPrice:integer,交易执行时,每一个支付的gas的价格;
  • value:integer,交易的moac数量;
  • data:附加数据;
  • nounce:integer,发送账号的交易序号。

Return

  • 返回交易的hash值;

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:交易对象

  • from:20字节,交易发送账号地址;
  • to:20字节,交易目的账号地址;
  • gas:integer,交易执行需要提供的gas费数量,没有用完的将返还;
  • gasPrice:integer,交易执行时,每一个支付的gas的价格;
  • value:integer,交易的moac数量;
  • data:附加数据;
  • nounce:integer,发送账号的交易序号。

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

  • Data:交易用私钥签名后的数据;

Return

  • Data:32字节。成功,则返回交易hash;无效,返回zero hash;

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="")。

猜你喜欢

转载自blog.csdn.net/lyq13573221675/article/details/82107371
今日推荐