注于Java領(lǐng)域優(yōu)質(zhì)技術(shù),歡迎關(guān)注
首先了解什么叫RPC,為什么要RPC,RPC是指遠(yuǎn)程過程調(diào)用,也就是說(shuō)兩臺(tái)服務(wù)器A,B,一個(gè)應(yīng)用部署在A服務(wù)器上,想要調(diào)用B服務(wù)器上應(yīng)用提供的函數(shù)/方法,由于不在一個(gè)內(nèi)存空間,不能直接調(diào)用,需要通過網(wǎng)絡(luò)來(lái)表達(dá)調(diào)用的語(yǔ)義和傳達(dá)調(diào)用的數(shù)據(jù)。
比如說(shuō),一個(gè)方法可能是這樣定義的:
Employee getEmployeeByName(String fullName)
那么:
(圖片來(lái)源:https://www.cs.rutgers.edu/~pxk/417/notes/03-rpc.html)
為什么RPC呢?就是無(wú)法在一個(gè)進(jìn)程內(nèi),甚至一個(gè)計(jì)算機(jī)內(nèi)通過本地調(diào)用的方式完成的需求,比如比如不同的系統(tǒng)間的通訊,甚至不同的組織間的通訊。由于計(jì)算能力需要橫向擴(kuò)展,需要在多臺(tái)機(jī)器組成的集群上部署應(yīng)用,
RPC的協(xié)議有很多,比如最早的CORBA,Java RMI,Web Service的RPC風(fēng)格,Hessian,Thrift,甚至Rest API。
而Netty框架不局限于RPC,更多的是作為一種網(wǎng)絡(luò)協(xié)議的實(shí)現(xiàn)框架,比如HTTP,由于RPC需要高效的網(wǎng)絡(luò)通信,就可能選擇以Netty作為基礎(chǔ)。除了網(wǎng)絡(luò)通信,RPC還需要有比較高效的序列化框架,以及一種尋址方式。如果是帶會(huì)話(狀態(tài))的RPC調(diào)用,還需要有會(huì)話和狀態(tài)保持的功能。
大體上來(lái)說(shuō),Netty就是提供一種事件驅(qū)動(dòng)的,責(zé)任鏈?zhǔn)剑ㄒ部梢哉f(shuō)是流水線)的網(wǎng)絡(luò)協(xié)議實(shí)現(xiàn)方式。網(wǎng)絡(luò)協(xié)議包含很多層次,很多部分組成,如傳輸層協(xié)議,編碼解碼,壓縮解壓,身份認(rèn)證,加密解密,請(qǐng)求的處理邏輯,怎么能夠更好的復(fù)用,擴(kuò)展,業(yè)界通用的方法就是責(zé)任鏈,
一個(gè)請(qǐng)求應(yīng)答網(wǎng)絡(luò)交互通常包含兩條鏈,一條鏈(Upstream)是從傳輸層,經(jīng)過一系列步驟,如身份認(rèn)證,解密,日志,流控,最后到達(dá)業(yè)務(wù)層,一條鏈(DownStream)是業(yè)務(wù)層返回后,又經(jīng)過一系列步驟,如加密等,又回到傳輸層。
(圖片來(lái)源:ChannelPipeline (The Netty Project API Reference (3.2.6.Final)))
這樣每一層都有一個(gè)處理接口,都可以進(jìn)行不同的操作,比如身份認(rèn)證,加解密,日志,流控,將不同的處理實(shí)現(xiàn)像拼積木那樣插接起來(lái)就可以實(shí)現(xiàn)一個(gè)網(wǎng)絡(luò)協(xié)議了(快速開發(fā))。每一層都有自己的實(shí)現(xiàn),上層不需要關(guān)注面向網(wǎng)絡(luò)的操作(可維護(hù))。Netty已經(jīng)提供了很多實(shí)現(xiàn)。
(圖片來(lái)源:http://docs.jboss.org/netty/3.1/guide/html/architecture.html)
當(dāng)然Netty還有許多好處,比如對(duì)非阻塞IO(NIO)的支持,比如在鏈上傳遞時(shí)最大程度的減少buffer的copy(高性能)。
面試的時(shí)候經(jīng)常被問到RPC相關(guān)的問題,例如:你說(shuō)說(shuō)RPC實(shí)現(xiàn)原理、讓你實(shí)現(xiàn)一個(gè)RPC框架應(yīng)該考慮哪些地方、RPC框架基礎(chǔ)上發(fā)起一個(gè)請(qǐng)求是怎樣一個(gè)流程等等。所以這次我就總結(jié)一波RPC的相關(guān)知識(shí)點(diǎn),提前說(shuō)明一下,本篇文章只是為了回答一些面試問題,所以只是解釋原理,并不會(huì)深入挖掘細(xì)節(jié)。
RPC(Remote Procedure Call)翻譯成中文就是$\color{red}{遠(yuǎn)程過程調(diào)用}$。RPC框架起到的作用就是為了實(shí)現(xiàn),調(diào)用遠(yuǎn)程方法時(shí),能夠做到和調(diào)用本地方法一樣,讓開發(fā)人員更專注于業(yè)務(wù)開發(fā),不用去考慮網(wǎng)絡(luò)編程等細(xì)節(jié)。
首先我們區(qū)分兩個(gè)角色一個(gè)服務(wù)提供方,一個(gè)是服務(wù)調(diào)用方。服務(wù)調(diào)用方其實(shí)是通過動(dòng)態(tài)代理、負(fù)載均衡、網(wǎng)絡(luò)調(diào)用等機(jī)制去服務(wù)提供方的機(jī)器上去執(zhí)行對(duì)應(yīng)的方法。服務(wù)提供方將方法執(zhí)行完成后,將執(zhí)行結(jié)果再通過網(wǎng)絡(luò)傳輸返回到服務(wù)提供方。
大致過程如下:
但是現(xiàn)在的服務(wù)都是集群部署,那么服務(wù)調(diào)用方怎么應(yīng)該實(shí)時(shí)的知道服務(wù)提供方的集群中的變化,例如服務(wù)提供方的IP地址變了,或者是服務(wù)重啟時(shí)怎么能夠及時(shí)的切換流量呢?
這就需要$\color{red}{注冊(cè)中心}$起作用了,我們可以把注冊(cè)中心看作服務(wù)端,然后每個(gè)服務(wù)都看成客戶端,每個(gè)客戶端都需要將自己注冊(cè)到注冊(cè)中心,然后一個(gè)服務(wù)調(diào)用方要調(diào)用另一個(gè)服務(wù)時(shí),需要從注冊(cè)中心獲取服務(wù)提供方的信息,主要是獲取服務(wù)提供方的服務(wù)器IP地址列表和端口信息。
服務(wù)調(diào)用方獲取到這些信息后緩存到自己本地,并且跟注冊(cè)中心保持一個(gè)長(zhǎng)連接當(dāng)服務(wù)提供方有任何變化時(shí),注冊(cè)中心能夠?qū)崟r(shí)的通知給服務(wù)調(diào)用方,調(diào)用方能夠及時(shí)更新自己本地緩存的信息(也可以采用定時(shí)輪詢的方式)。
服務(wù)調(diào)用方獲取到服務(wù)器IP地址信息后,根據(jù)自己的負(fù)載均衡策略選擇一個(gè)IP地址然后發(fā)起網(wǎng)絡(luò)調(diào)用的請(qǐng)求。
可以自己使用JDK原生的BIO活NIO來(lái)實(shí)現(xiàn)一套網(wǎng)絡(luò)通信模塊,但是這里我們建議直接使用強(qiáng)大的網(wǎng)絡(luò)通信框架Netty。它是基于NIO的網(wǎng)絡(luò)通信框架,支持高并發(fā),封裝完善,而且性能好傳輸快。
Netty不是我們本文的主要內(nèi)容,這里就不展開說(shuō)了。
因?yàn)槲覀冎罃?shù)據(jù)在網(wǎng)絡(luò)中傳輸?shù)臅r(shí)候都是以二進(jìn)制的形式的,所以在調(diào)用方將調(diào)用的參數(shù)進(jìn)行傳遞的時(shí)候是需要進(jìn)行序列化的。服務(wù)提供方在接收到參數(shù)時(shí)也是需要進(jìn)行反序列化的。
調(diào)用方既然需要序列化,服務(wù)提供方又要進(jìn)行反序列化,這樣雙方就要確定好一個(gè)協(xié)議,調(diào)用方傳輸什么參數(shù),服務(wù)提供方就按照這個(gè)協(xié)議去進(jìn)行解析,而且在返回結(jié)果的時(shí)候也是按照這個(gè)協(xié)議進(jìn)行結(jié)果解析。
那么這個(gè)協(xié)議應(yīng)該是怎么樣的結(jié)構(gòu),都是什么樣子的呢?
因?yàn)檫@個(gè)協(xié)議可以自定義,我們?yōu)榱朔奖憔鸵訨SON的形式給舉個(gè)例子:
{
"interfaces": "interface=com.jimoer.rpc.test.producer.TestService;method=printTest;parameter=com.jiomer.rpc.test.producer.TestArgs",
"requestId": "3",
"parameter": {
"com.jiomer.rpc.test.producer.TestArgs": {
"age": 20,
"name": "Jimoer"
}
}
}
首先第一個(gè)參數(shù)interfaces是,我們要讓服務(wù)提供方知道調(diào)用方要調(diào)用哪個(gè)接口,以及接口中的哪個(gè)方法,并且方法的參數(shù)是什么類型的。
第二個(gè)參數(shù)是當(dāng)前一次請(qǐng)求的一個(gè)唯一標(biāo)識(shí),在多個(gè)線程同時(shí)請(qǐng)求一個(gè)方法時(shí),用這個(gè)id來(lái)進(jìn)行區(qū)分,以后無(wú)論是做鏈路追蹤還是日志管理都可以以此id為依據(jù)。
第三個(gè)參數(shù)就是 實(shí)際的調(diào)用方法中的參數(shù)值。具體是什么類型的,每個(gè)屬性值都是什么。
下面也是舉一個(gè)簡(jiǎn)單的例子來(lái)說(shuō)明一下調(diào)用的過程。我們一部分采用代碼的形式一部分采用文字的形式來(lái)將整個(gè)調(diào)用過程串起來(lái)。
// 定義請(qǐng)求的URL
String tcpURL = "tcp://testProducer/TestServiceImpl";
// 定義接口請(qǐng)求
TestService testService = ProxyFactory.create(TestService.class, tcpURL);
// 組裝請(qǐng)求參數(shù)
TestArgs testArgs = new TestArgs(20,"Jimoer");
// 通過動(dòng)態(tài)代理執(zhí)行請(qǐng)求
String result = testService.printTest(testArgs);
通過查看上面的代碼我們可以看到整個(gè)調(diào)用過程最核心的地方在ProxyFactory.create()方法里,這個(gè)方法里面主要的過程是,動(dòng)態(tài)代理生成接口的實(shí)際代理對(duì)象,然后使用Netty的接口發(fā)起網(wǎng)絡(luò)請(qǐng)求。
Proxy.newProxyInstance(getClass().getClassLoader(), interfaces.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 第一步:獲取調(diào)用服務(wù)的地址列表
ListregistryInfos = interfacesMethodRegistryList.get(clazz);
if (registryInfos == null) {
throw new RuntimeException("無(wú)法找到服務(wù)提供者");
}
// 第二步: 通過自身的負(fù)載均衡策略選擇一個(gè)地址
RegistryInfo registryInfo = loadBalancer.choose(registryInfos);
// 第三步:Netty的網(wǎng)絡(luò)請(qǐng)求處理
ChannelHandlerContext ctx = channels.get(registryInfo);
// 第四步:根據(jù)接口類的全路徑名和方法生成唯一標(biāo)識(shí)
String identify = InvokeUtils.buildInterfaceMethodIdentify(clazz, method);
String requestId;
// 第五步:通過加鎖的方式保證生成的requestId的唯一性
synchronized (ApplicationContext.this) {
requestIdWorker.increment();
requestId = String.valueOf(requestIdWorker.longValue());
}
// 第六步: 組織參數(shù)
JSONObject jsonObject = new JSONObject();
jsonObject.put("interfaces", identify);
jsonObject.put("parameter", param);
jsonObject.put("requestId", requestId);
System.out.println("發(fā)送給服務(wù)端JSON為:" + jsonObject.toJSONString());
// $$ 多條消息之間的分隔符
String msg = jsonObject.toJSONString() + "$$";
ByteBuf byteBuf = Unpooled.buffer(msg.getBytes().length);
byteBuf.writeBytes(msg.getBytes());
// 第七步:這里發(fā)起調(diào)用
ctx.writeAndFlush(byteBuf);
// 這里會(huì)將線程進(jìn)行阻塞,知道服務(wù)提供方將請(qǐng)求處理好之后返回結(jié)果,再喚醒。
waitForResult();
return result;
}
});
執(zhí)行過程大致分為這幾步:
上面也說(shuō)了,服務(wù)調(diào)用方發(fā)起網(wǎng)絡(luò)請(qǐng)求后,會(huì)阻塞住,直到服務(wù)提供方返回?cái)?shù)據(jù),所以服務(wù)提供方處理完調(diào)用方法的邏輯后,還是要喚醒阻塞的調(diào)用線程的。
服務(wù)提供方在處理請(qǐng)求時(shí)也是先通過Netty獲取到數(shù)據(jù),然后再進(jìn)行反序列化,然后再根據(jù)協(xié)議獲取到需要調(diào)用的方法,然后通過反射去進(jìn)行調(diào)用。
Netty的返回入口在下面這部分邏輯里
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
try {
String message = (String) msg;
if (messageCallback != null) {
// 將接收到的消息放到回調(diào)方法中
messageCallback.onMessage(message);
}
} finally {
ReferenceCountUtil.release(msg);
}
}
Netty的client接收到響應(yīng)的消息后,先將結(jié)果返回到調(diào)用方,處理完成之后再去釋放之前的阻塞調(diào)用線程。
client.setMessageCallback(message -> {
// 這里收單服務(wù)端返回的消息,先壓入隊(duì)列
RpcResponse response = JSONObject.parseObject(message, RpcResponse.class);
System.out.println("收到一個(gè)響應(yīng):" + response);
String interfaceMethodIdentify = response.getInterfaceMethodIdentify();
String requestId = response.getRequestId();
// 設(shè)定唯一標(biāo)識(shí)
String key = interfaceMethodIdentify + "#" + requestId;
Invoker invoker = inProgressInvoker.remove(key);
// 將結(jié)果設(shè)置到代理對(duì)象中
invoker.setResult(response.getResult());
// 加鎖再釋放之前的阻塞線程。
synchronized (ApplicationContext.this) {
ApplicationContext.this.notifyAll();
}
});
setResult()方法
@Override
public void setResult(String result) {
synchronized (this) {
this.result = JSONObject.parseObject(result, returnType);
notifyAll();
}
}
上面的步驟就是這樣,按照之前請(qǐng)求的唯一標(biāo)識(shí)放入到返回的信息中,然后將結(jié)果設(shè)置到代理對(duì)象中,再通過返回結(jié)果,然后喚醒之前的調(diào)用阻塞線程。
其實(shí)整個(gè)RPC的請(qǐng)求過程就是如下(不含異步調(diào)用):
做一個(gè)總結(jié),用大白話把一個(gè)RPC請(qǐng)求流程描述出來(lái):
首先無(wú)論是調(diào)用方還是服務(wù)提供方都要注冊(cè)到注冊(cè)中心;
這樣就完成了一次RPC網(wǎng)絡(luò)調(diào)用,其實(shí)后面框架擴(kuò)展后,還要考慮限流、熔斷、服務(wù)降級(jí)、序列化多樣性擴(kuò)展,服務(wù)監(jiān)控、鏈路追蹤等等功能。這些就要后面再擴(kuò)展的講了,這次就先到這了。
原文鏈接:https://www.cnblogs.com/jimoer/p/15511954.html
景:今年3月開放聯(lián)盟鏈“中移鏈”在區(qū)塊鏈服務(wù)網(wǎng)絡(luò)(BSN[1])中完成適配并上線發(fā)布,吸引了大批開發(fā)者,部分開發(fā)者提出了一些共性問題
目的:本篇文章是為了讓讀者了解如何在中移鏈(基于EOS)上調(diào)用RPC接口組裝交易、簽名、上鏈以及查詢上鏈結(jié)果
適用對(duì)象:適用于BSN開放聯(lián)盟鏈--中移鏈(基于EOS)開發(fā)者
?需要現(xiàn)在bsn[2]創(chuàng)建項(xiàng)目和創(chuàng)建一個(gè)賬戶,可以參考:https://bsnbase.com/static/tmpFile/bzsc/openper/7-3-6.html
?可以獲取到eos 端點(diǎn):https://opbningxia.bsngate.com:18602/api/aecb28acfd154cfeb90d0b6a8ecab1e7/rpc ?eos賬戶/合約地址:helailiang14
?為賬戶購(gòu)買足夠的資源(cpu、net、ram)
?安裝EOSIO開發(fā)環(huán)境:需要安裝cleos和keosd, 可以參考:https://developers.eos.io/welcome/latest/getting-started-guide/local-development-environment/installing-eosio-binaries ?部署eos合約: hello.cpp
#include <eosio/eosio.hpp>
#include <eosio/transaction.hpp>
using namespace eosio;
// 通過[[eosio::contract]]標(biāo)注這個(gè)類是一個(gè)合約
class [[eosio::contract]] hello : public contract
{
public:
using contract::contract;
// 在構(gòu)造函數(shù)進(jìn)行表對(duì)象的實(shí)例化, 標(biāo)準(zhǔn)合約構(gòu)造函數(shù),receiver也就是我們的合約賬號(hào)(一般情況下),code就是我們的action名稱,ds就是數(shù)據(jù)流
// get_self() 合約所在的賬號(hào) get_code() 當(dāng)前交易請(qǐng)求的action方法名 get_datastream() 當(dāng)前數(shù)據(jù)流
hello(name receiver, name code, datastream<const char *> ds) : contract(receiver, code, ds), friend_table(get_self(), get_self().value)
{
}
// 用[[eosio::action]]標(biāo)注這個(gè)方法是一個(gè)合約action就行
// 注意:action的名稱要求符合name類型的規(guī)則
[[eosio::action]] void hi(name user)
{
print("Hello, ", user);
print("get_self,", get_self().value);
// print("get_code,", get_code().value);
uint32_t now = current_time_point().sec_since_epoch();
auto friend_itr = friend_table.find(user.value);
// 數(shù)據(jù)不存在
if (friend_itr == friend_table.end())
{
// 第一個(gè)參數(shù)就是內(nèi)存使用的對(duì)象,第二個(gè)參數(shù)就是添加表對(duì)象時(shí)的委托方法。
friend_table.emplace(get_self(), [&](auto &f) {
f.friend_name = user;
f.visit_time = now;
});
}
else
{
// 第一個(gè)參數(shù)是傳遞需要修改的數(shù)據(jù)指針,第二個(gè)參數(shù)是內(nèi)存使用的對(duì)象,第二個(gè)參數(shù)就是表對(duì)象修改時(shí)的委托方法
friend_table.modify(friend_itr, get_self(), [&](auto &f) {
f.visit_time = now;
});
}
}
[[eosio::action]] void nevermeet(name user)
{
print("Never see you again, ", user);
auto friend_itr = friend_table.find(user.value);
check(friend_itr != friend_table.end(), "I don't know who you are.");
// 只有一個(gè)參數(shù),就是要?jiǎng)h除的對(duì)象指針
friend_table.erase(friend_itr);
}
[[eosio::action]] void meetagain()
{
uint32_t now = current_time_point().sec_since_epoch();
auto time_idx = friend_table.get_index<"time"_n>();
auto last_meet_itr = time_idx.begin();
check(last_meet_itr != time_idx.end(), "I don't have a friend.");
time_idx.modify(last_meet_itr, get_self(), [&](auto &f) {
f.visit_time = now;
});
}
private:
// 定義一個(gè)結(jié)構(gòu)體,然后用[[eosio::table]]標(biāo)注這個(gè)結(jié)構(gòu)體是一個(gè)合約表。在結(jié)構(gòu)體里定義一個(gè)函數(shù)名primary_key,返回uint64_t類型,作為主鍵的定義
struct [[eosio::table]] my_friend
{
name friend_name;
uint64_t visit_time;
uint64_t primary_key() const { return friend_name.value; }
double by_secondary() const { return -visit_time; }
};
// 定義表名和查詢索引 "friends"_n就是定義表名,所以使用了name類型,之后my_friend是表的結(jié)構(gòu)類
typedef eosio::multi_index<"friends"_n, my_friend> friends;
friends friend_table;
};
curl -X POST 'https://opbningxia.bsngate.com:18602/api/{您的開放聯(lián)盟鏈項(xiàng)目ID/rpc/v1/chain/abi_json_to_bin' \
-d '{
"code": "helailiang14",
"action": "hi",
"args": {
"user": "helloworld"
}
}'
------------------
return
{"binargs":"00408a97721aa36a"}
curl GET 'https://opbningxia.bsngate.com:18602/api/{您的開放聯(lián)盟鏈項(xiàng)目ID/rpc/v1/chain/get_info'
------------------
return
{
"server_version":"11d35f0f",
"chain_id":"9b4c6015f8b73b2d7ee3ebd92d249a1aba06a614e9990dcf54f7cf2e3d5172e1",
"head_block_num":15134328,
"last_irreversible_block_num":15134262,
"last_irreversible_block_id":"00e6ee360b5e7680a526ddea45db1be15c4be2cd2389020688218fe765be6db7",
"head_block_id":"00e6ee7889523875a28284effecdd1199cc960adb14c14c36cd1bd52afed6824",
"head_block_time":"2022-04-27T09:08:08.500",
"head_block_producer":"prod.b",
"virtual_block_cpu_limit":200000000,
"virtual_block_net_limit":1048576000,
"block_cpu_limit":199900,
"block_net_limit":1048576,
"server_version_string":"v3af0a20",
"fork_db_head_block_num":15134328,
"fork_db_head_block_id":"00e6ee7889523875a28284effecdd1199cc960adb14c14c36cd1bd52afed6824",
"server_full_version_string":"v3af0a20",
"last_irreversible_block_time":"2022-04-27T09:07:35.500"
}
獲取到head_block_num : 15134328
獲取 head_block_id:00e6ee7889523875a28284effecdd1199cc960adb14c14c36cd1bd52afed6824
獲取 chain_id: 9b4c6015f8b73b2d7ee3ebd92d249a1aba06a614e9990dcf54f7cf2e3d5172e1
curl -X POST 'https://opbningxia.bsngate.com:18602/api/{您的開放聯(lián)盟鏈項(xiàng)目ID/rpc/v1/chain/get_block' \
-d '{
"block_num_or_id": "15130610"
}'
------------------
return
{
"timestamp": "2022-04-27T09:08:08.500",
"producer": "prod.b",
"confirmed": 0,
"previous": "00e6ee77f2655528739622d2c9235026d4f10138b9821e46ea35165cb086d12d",
"transaction_mroot": "0000000000000000000000000000000000000000000000000000000000000000",
"action_mroot": "665584b582b234bf58d3708b31da20e14d266713e3bc6ce79ea3187cc2ffa5a4",
"schedule_version": 2,
"new_producers": null,
"producer_signature": "SIG_K1_KiYCDLMgE6gE1nNqQQL2jEEF3VVd6iaspAePvvJMjKwgg2Yf6GiTYcznrkymAdtZUAUFh28N8r9RzX936cASKDB6JW6ga3",
"transactions": [],
"id": "00e6ee7889523875a28284effecdd1199cc960adb14c14c36cd1bd52afed6824",
"block_num": 15134328,
"ref_block_prefix": 4018438818
}
獲取到timestamp : 2022-04-27T09:08:08
使用EOSIO提供的簽名工具實(shí)現(xiàn)簽名:啟動(dòng)keosd后,才會(huì)提供簽名服務(wù)
curl -X POST POST 'http://192.168.1.46:8800/v1/wallet/sign_transaction' \
-d '[
{
"expiration": "2022-04-27T10:08:08",
"ref_block_num": 61048,
"ref_block_prefix": 4018438818,
"max_net_usage_words": 0,
"max_cpu_usage_ms": 0,
"delay_sec": 0,
"context_free_actions": [],
"actions": [
{
"account": "helailiang14",
"name": "hi",
"authorization": [
{
"actor": "helailiang14",
"permission": "active"
}
],
"data": "00408a97721aa36a"
}
],
"transaction_extensions": [],
"signatures": [],
"context_free_data": []
},
[
"EOS6F6PRkSaPyijTDBYskFbsxpGz53JMTFbEhua94fQEyf7pAMc7Y"
],
"9b4c6015f8b73b2d7ee3ebd92d249a1aba06a614e9990dcf54f7cf2e3d5172e1"
]'
------------------
return :
{
"expiration":"2022-04-27T10:08:08",
"ref_block_num":61048,
"ref_block_prefix":4018438818,
"max_net_usage_words":0,
"max_cpu_usage_ms":0,
"delay_sec":0,
"context_free_actions":[
],
"actions":[
{
"account":"helailiang14",
"name":"hi",
"authorization":[
{
"actor":"helailiang14",
"permission":"active"
}
],
"data":"00408a97721aa36a"
}
],
"transaction_extensions":[
],
"signatures":[
"SIG_K1_K2AzV2Pk4SP3PQhcdQ1bYgGZgr7PUUcJkAGowvncFV1ngrZufeCQpveAUBRYvNA5uyxFk2hKiot3Mu7FCW5rqqeoU5SVTo"
],
"context_free_data":[
]
}
?expiration 過期時(shí)間。這里將timestamp加上了60分鐘.?ref_block_num: 由第二步返回的head_block_id 16進(jìn)制的 然后字段截取前八位低4位 轉(zhuǎn)10進(jìn)制, ee78 ==> 61048?ref_block_prefix:由第二步返回的head_block_id 16進(jìn)制的 然后字段截取16到24 然后兩個(gè)字節(jié)反轉(zhuǎn)(a28284ef反轉(zhuǎn)為ef8482a2) 轉(zhuǎn)10進(jìn)制, ef8482a2 ==> 4018438818?account 合約名稱,即部署合約的賬戶?name 調(diào)用的合約方法。?actor 調(diào)用者。簽名者?data :第一步生成的bin字符串?permission 使用的權(quán)限類型?EOS6F6PRkSaPyijTDBYskFbsxpGz53JMTFbEhua94fQEyf7pAMc7Y :簽署此交易的公鑰。實(shí)際上是由錢包中對(duì)應(yīng)的私鑰來(lái)簽?9b4c6015f8b73b2d7ee3ebd92d249a1aba06a614e9990dcf54f7cf2e3d5172e1: 是第二步獲取的chain_id??signatures: 簽名結(jié)果 SIG_K1_K2AzV2Pk4SP3PQhcdQ1bYgGZgr7PUUcJkAGowvncFV1ngrZufeCQpveAUBRYvNA5uyxFk2hKiot3Mu7FCW5rqqeoU5SVTo
使用 cleos convert pack_transaction將交易報(bào)文轉(zhuǎn)換成 packed 格式
cleos convert pack_transaction '{
"expiration":"2022-04-27T10:08:08",
"ref_block_num":61048,
"ref_block_prefix":4018438818,
"max_net_usage_words": 0,
"max_cpu_usage_ms": 0,
"delay_sec": 0,
"context_free_actions": [],
"actions": [
{
"account": "helailiang14",
"name": "hi",
"authorization": [
{
"actor": "helailiang14",
"permission": "active"
}
],
"data": "00408a97721aa36a"
}
],
"transaction_extensions": []
}'
------------------
return:
{
"signatures": [],
"compression": "none",
"packed_context_free_data": "",
"packed_trx": "0816696278eea28284ef000000000140029bc64567a26a000000000000806b0140029bc64567a26a00000000a8ed32320800408a97721aa36a00"
}
packed_trx: 為交易報(bào)文的 packed 格式
curl -X POST 'https://opbningxia.bsngate.com:18602/api/{您的開放聯(lián)盟鏈項(xiàng)目ID/rpc/v1/chain/push_transaction' \
-d '{
"signatures": [
"SIG_K1_K2AzV2Pk4SP3PQhcdQ1bYgGZgr7PUUcJkAGowvncFV1ngrZufeCQpveAUBRYvNA5uyxFk2hKiot3Mu7FCW5rqqeoU5SVTo"
],
"compression": "none",
"packed_context_free_data": "",
"packed_trx": "0816696278eea28284ef000000000140029bc64567a26a000000000000806b0140029bc64567a26a00000000a8ed32320800408a97721aa36a00"
}'
------------------
return:
{
"transaction_id":"a69d03f6b1bab4bd8908124eef5e59d3e47df4063e697a07487308cde63a9f79",
"processed":{
"id":"a69d03f6b1bab4bd8908124eef5e59d3e47df4063e697a07487308cde63a9f79",
"block_num":15136664,
"block_time":"2022-04-27T09:27:36.500",
"producer_block_id":null,
"receipt":{
"status":"executed",
"cpu_usage_us":272,
"net_usage_words":13
},
"elapsed":272,
"net_usage":104,
"scheduled":false,
"action_traces":[
{
"action_ordinal":1,
"creator_action_ordinal":0,
"closest_unnotified_ancestor_action_ordinal":0,
"receipt":{
"receiver":"helailiang14",
"act_digest":"31ff1ecb2b0b0c89911b74c7930f08ecfefbd24ba59ef30a905d44068d2d8910",
"global_sequence":15199315,
"recv_sequence":2,
"auth_sequence":[
[
"helailiang14",
4
]
],
"code_sequence":1,
"abi_sequence":1
},
"receiver":"helailiang14",
"act":{
"account":"helailiang14",
"name":"hi",
"authorization":[
{
"actor":"helailiang14",
"permission":"active"
}
],
"data":{
"user":"helloworld"
},
"hex_data":"00408a97721aa36a"
},
"context_free":false,
"elapsed":63,
"console":"Hello, helloworldget_self,7683817463629939264",
"trx_id":"a69d03f6b1bab4bd8908124eef5e59d3e47df4063e697a07487308cde63a9f79",
"block_num":15136664,
"block_time":"2022-04-27T09:27:36.500",
"producer_block_id":null,
"account_ram_deltas":[
],
"account_disk_deltas":[
],
"except":null,
"error_code":null,
"return_value_hex_data":"",
"inline_traces":[
]
}
],
"account_ram_delta":null,
"except":null,
"error_code":null
}
}
signatures: 為第4步簽名的結(jié)果
packed_trx: 為第5步的報(bào)文轉(zhuǎn)換結(jié)果
curl -X POST 'https://opbningxia.bsngate.com:18602/api/{您的開放聯(lián)盟鏈項(xiàng)目ID/rpc/v1/chain/get_table_rows' \
-d'{
"code": "helailiang14",
"table": "friends",
"scope": "helailiang14",
"json": true
}'
------------------
return:
{
"rows":[
{
"friend_name":"helloworld",
"visit_time":1651051656
}
],
"more":false,
"next_key":"",
"next_key_bytes":""
}
數(shù)據(jù)[helloworld]已經(jīng)寫到table中
[1] BSN: https://bsnbase.com/
[2] bsn: https://bsnbase.com/p/home/Openalliance/projectManagement
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。