bitshares研究系列【bitshares-ui代码分析之api调用流程】

bitshares研究系列【bitshares-ui代码分析之api】已经分析过api websocket调用方式,并用wscat测试了基本调用,本篇从bitshares-ui和bitshares-core看看api的调用流程。

bitsharesjs-ws

安装方法:

npm install bitsharesjs-ws

ApiInstances.js

实现了api实例,instance()创建这样一个对象。

        this.ws_rpc = new ChainWebSocket(cs, this.statusCb, connectTimeout, autoReconnect, function () {
            if (_this2._db) {
                _this2._db.exec('get_objects', [['2.1.0']]).catch(function (e) {});
            }
        });

生成一个ChainWebSocket对象,并调用database的一个命令:get_objects,实际上这条命令会取到什么数据呢?用wscat看一下:

> {"method": "call", "params": [2, "get_objects", [["2.1.0"]]], "id": 4}
< {"id":4,"jsonrpc":"2.0","result":[{"id":"2.1.0","head_block_number":26046408,"head_block_id":"018d6fc89631b618da75fcbf6519bd72a250927d","time":"2018-04-12T07:33:12","current_witness":"1.6.110","next_maintenance_time":"2018-04-12T08:00:00","last_budget_time":"2018-04-12T07:00:00","witness_budget":53900000,"accounts_registered_this_interval":36,"recently_missed_count":0,"current_aslot":26193445,"recent_slots_filled":"340282366920938463463374607431768211455","dynamic_flags":0,"last_irreversible_block_num":26046386}]}

"2.1.0" 取到的是当前区块链数据,特殊帐户对应什么数据参见这:http://docs.bitshares.org/development/blockchain/objects.html

同时也会取到其它api对象,如下:

            _this2._db = new GrapheneApi(_this2.ws_rpc, "database");
            _this2._net = new GrapheneApi(_this2.ws_rpc, "network_broadcast");
            _this2._hist = new GrapheneApi(_this2.ws_rpc, "history");
            if (enableCrypto) _this2._crypt = new GrapheneApi(_this2.ws_rpc, "crypto");

ChainWebSocket.js

文件实现了websocket连接,call函数实现了call调用,如下:

    ChainWebSocket.prototype.call = function call(params) {
        var _this2 = this;

        if (this.ws.readyState !== 1) {
            return Promise.reject(new Error('websocket state error:' + this.ws.readyState));
        }
        var method = params[1];
        if (SOCKET_DEBUG) console.log("[ChainWebSocket] >---- call ----->  \"id\":" + (this.cbId + 1), JSON.stringify(params));

        this.cbId += 1;

        if (method === "set_subscribe_callback" || method === "subscribe_to_market" || method === "broadcast_transaction_with_callback" || method === "set_pending_transaction_callback") {
            // Store callback in subs map
            this.subs[this.cbId] = {
                callback: params[2][0]
            };

            // Replace callback with the callback id
            params[2][0] = this.cbId;
        }

        if (method === "unsubscribe_from_market" || method === "unsubscribe_from_accounts") {
            if (typeof params[2][0] !== "function") {
                throw new Error("First parameter of unsub must be the original callback");
            }

            var unSubCb = params[2].splice(0, 1)[0];

            // Find the corresponding subscription
            for (var id in this.subs) {
                if (this.subs[id].callback === unSubCb) {
                    this.unsub[this.cbId] = id;
                    break;
                }
            }
        }

        var request = {
            method: "call",
            params: params
        };
        request.id = this.cbId;
        this.send_life = max_send_life;

        return new Promise(function (resolve, reject) {
            _this2.cbs[_this2.cbId] = {
                time: new Date(),
                resolve: resolve,
                reject: reject
            };
            _this2.ws.send(JSON.stringify(request));
        });
    };

params[1] 就是method,如"set_subscribe_callback"等,对照ws命令看一下:

 {"method": "call", "params": [2, "set_subscribe_callback", [5, false]], "id": 6}

this.cbId 记录了id,如果是"set_subscribe_callback"等method,会把传进来回调函数保存到this.subs的map中,并用this.cbId替换,这样websocket有订阅消息到达时,可以根据this.cbId找到对应的回调函数。代码如下:

        if (method === "set_subscribe_callback" || method === "subscribe_to_market" || method === "broadcast_transaction_with_callback" || method === "set_pending_transaction_callback") {
            // Store callback in subs map
            this.subs[this.cbId] = {
                callback: params[2][0]
            };

            // Replace callback with the callback id
            params[2][0] = this.cbId;
        }

函数最后就是通过 _this2.ws.send(JSON.stringify(request)); 发送消息了。


listener函数实现了接收消息,如下:

    ChainWebSocket.prototype.listener = function listener(response) {
        if (SOCKET_DEBUG) console.log("[ChainWebSocket] <---- reply ----<", JSON.stringify(response));

        var sub = false,
            callback = null;

        if (response.method === "notice") {
            sub = true;
            response.id = response.params[0];
        }

        if (!sub) {
            callback = this.cbs[response.id];
            this.responseCbId = response.id;
        } else {
            callback = this.subs[response.id].callback;
        }

        if (callback && !sub) {
            if (response.error) {
                callback.reject(response.error);
            } else {
                callback.resolve(response.result);
            }
            delete this.cbs[response.id];

            if (this.unsub[response.id]) {
                delete this.subs[this.unsub[response.id]];
                delete this.unsub[response.id];
            }
        } else if (callback && sub) {
            callback(response.params[1]);
        } else {
            console.log("Warning: unknown websocket response: ", response);
        }
    };

如果response.method === "notice"就是订阅消息,会找到call时保留的回调函数 this.subs[response.id].callback 并调用。


bitsharesjs

安装方法:

npm install bitsharesjs

ChainStore.js

这个文件里有更上一层的调用,封装了很多函数,简单看下init函数中的部分代码:

                        if (delta < 60) {
                            Apis.instance().db_api().exec("set_subscribe_callback", [_this.onUpdate.bind(_this), subscribe_to_new]).then(function () {
                                console.log("synced and subscribed, chainstore ready");
                                _this.subscribed = true;
                                _this.subError = null;
                                _this.notifySubscribers();
                                resolve();
                            }).catch(function (error) {
                                _this.subscribed = false;
                                _this.subError = error;
                                _this.notifySubscribers();
                                reject(error);
                                console.log("Error: ", error);
                            });

在init中自动做了订阅的处理,Apis.instance().db_api().exec 这个函数实现,实际就是调用ChainWebSocket中的call函数,在GrapheneApi.js中,如下:

    GrapheneApi.prototype.exec = function exec(method, params) {
        return this.ws_rpc.call([this.api_id, method, params]).catch(function (error) {
            console.log("!!! GrapheneApi error: ", method, params, error, JSON.stringify(error));
            throw error;
        });
    };

再来看订阅消息到达时的回调函数,也就是Apis.instance().db_api().exec()的参数 _this.onUpdate.bind(_this),实际就是ChainStore.js中的onUpdate()函数,如下:

    ChainStore.prototype.onUpdate = function onUpdate(updated_objects /// map from account id to objects
    ) {
        var cancelledOrders = [];
        var closedCallOrders = [];

        for (var a = 0; a < updated_objects.length; ++a) {
            for (var i = 0; i < updated_objects[a].length; ++i) {
                var _obj = updated_objects[a][i];
                if (_ChainValidation2.default.is_object_id(_obj)) {
                    ...
                } else {
                    this._updateObject(_obj);
                }
            }
        }
        ...
        this.notifySubscribers();
    };

继续看一下上篇中取得的订阅消息格式:

< {"method":"notice","params":[5,[[{"id":"2.1.0","head_block_number":25995300,"head_block_id":"018ca824af5b469d8fd78dfd790b1d248e78f986","time":"2018-04-10T12:46:45","current_witness":"1.6.69","next_maintenance_time":"2018-04-10T13:00:00","last_budget_time":"2018-04-10T12:00:00","witness_budget":26900000,"accounts_registered_this_interval":51,"recently_missed_count":0,"current_aslot":26142245,"recent_slots_filled":"340282366920938463463374607431768211455","dynamic_flags":0,"last_irreversible_block_num":25995281}]]]}

params中[[{"id":"2.1.0"是一个列表的列表,对应以上代码中的两个for循环处理,如果订阅消息到达时,会执行到this._updateObject(_obj),也就是_updateObject()函数,这个函数基本只要是异步消息处理都会调用到,如下:

    ChainStore.prototype._updateObject = function _updateObject(object) {
        /*
        * A lot of objects get spammed by the API that we don't care about, filter these out here
        */
        // Transaction object
        if (object.id.substring(0, transaction_prefix.length) == transaction_prefix) {
            return; // console.log("not interested in transaction:", object);
        } else if (object.id.substring(0, account_transaction_history_prefix.length) == account_transaction_history_prefix) {
            // transaction_history object
        }
        ......
            if (notify_subscribers) {
            this.notifySubscribers();
        }
        return current;
    };

这个函数会用object.id.substring对id进行判断,就是判断"x.x.x"这个id是哪种类型,这个前级定义在文件头,看其中交易数据的定义如下:

var transaction_prefix = "2." + parseInt(impl_object_type.transaction, 10) + ".";

而这个impl_object_type在上篇文章中也已经谈到了,在ChainType.js中也有定义。


onUpdate函数最后调用的this.notifySubscribers();,实现了界面层订阅的消息通知,如下:

    ChainStore.prototype.notifySubscribers = function notifySubscribers() {
        var _this2 = this;

        // Dispatch at most only once every x milliseconds
        if (!this.dispatched) {
            this.dispatched = true;
            this.timeout = setTimeout(function () {
                _this2.dispatched = false;
                _this2.subscribers.forEach(function (callback) {
                    callback();
                });
            }, this.dispatchFrequency);
        }
    };

有一个subscribers列表,subscribe()函数添加订阅,unsubscribe()函数移除订阅,界面层调用方式,看BindToChainState.jsx中的调用:

        componentWillMount() {
            ChainStore.subscribe(this.update);
            this.update();
        }

        componentWillUnmount() {
            ChainStore.unsubscribe(this.update);
        }

这篇文章分析不到bitshares-core的实现了,bitshares-ui这块api调用流程简单分析完成了。


感谢您阅读 @chaimyu 的帖子,期待您能留言交流!

H2
H3
H4
3 columns
2 columns
1 column
1 Comment