Bitshares订单撮合详解
第1章 【绪言】
1.1 编写目的
bitshares作为一个去中心化的交易所,使用DPOS共识机制,用户可以在bitshares这条链上交易订单,链上具有自动撮合功能,撮合多笔订单,本文的目的是深入解析bitshares的源代码,对订单撮合部分功能进行讲解。
1.2 术语定义
transaction:字面意思是交易,在bitshares里面,transaction意义既包含通用意义上的交易:比如充值、提现、转账、发布限价单,还包含其它一些意义,如:创建账户,升级账户,创建见证人结点,等一系列操作。
signed_transaction:当见证人对transaction进行签名之后,这个transaction具有一定的效力,可以认为是被确认过的,所以是signed_transaction,signed_transaction才可以被存入区块中。
processed_transaction:被执行过的transaction,当一个transaction被签名后,才可以被执行,执行转账订单撮合等操作,只有被执行过的processed_transaction,才可以被放入区块中。
signed_block:区块结构,一个区块包含一个或多个processed_transaction。
第2章 【订单撮合原理及技术分析】
2.1 工作原理
bitshares是一个去中心化的交易所,用户可以在交易所上进行虚拟资产的交易,例如用比特币兑换以太坊等。当用户在客户端发布了一种类型的资产交易,例如以1个比特币兑换10个以太坊,客户端会将交易数据发布给所连接的见证人节点。见证人节点接收到后,生成一条transaction,将具体信息封装进这条transaction中。因为这类交易并不是实时完成,所以会以订单(order)的方式,保存进bitshares的数据库中。每一个order都有自己的过期时间,如果到达过期时间仍未匹配成功将被从数据库订单列表中删除。transaction会被全网广播,并被打包进区块里面。假如另外有用户a发布一个订单,以9个以太坊兑换1个比特币,而用户b也发布一个订单,以11个以太坊兑换1个比特币。显而易见,用户a的订单不满足需求,而用户b的订单满足需求,此时bitshares就会将用户b的订单与其进行匹配,匹配成功之后,从订单列表中删除,然后修改两个用户余额,进行相应资产的加减操作,这就是bitshares订单撮合的大致原理。
2.2 流程分析
当用户发布了一条限价单后,假如此条限价单是卖出1000个BTS,买回1个BTC,此限价单就是一个交易,即transaction。因为用户使用bitshares-ui这一层进行发布限价单,而每一个用户都是连接bitshares底层graphene网络中的见证人结点进行操作,所以用户发布的限价单会首先由该用户所连接的witness_node接收到命令。
witness_node接收到命令(本质上是一段json)后,会进行解析处理,此处对底层解析不作赘述,因为发布限价单的sell_asset,所以最终会调用到wallet.cpp中的sell_asset函数(1930行)。
创建一个signed_transaction,将限价单相关数据放入到limit_order_create_operation中去,然后sign_transaction。
下面详看sign_transaction这个函数,在sign_transaction函数中,主要对transaction进行一系列签名处理,这里不作详细说明,在签名完成之后,调用
_remote_net_broadcast->broadcast_transaction( tx )这个函数,将transaction广播出去给其它结点。
在broadcast_transaction函数中,主要进行两个操作,如下:_app.chain_database()->push_transaction(trx);
_app.p2p_node()->broadcast_transaction(trx);
第一个函数push_transaction,将transaction缓存起来,会调用db_block.cpp中的_push_transaction函数(229行),在这里执行_apply_transaction函数,_apply_transaction函数,_apply_transaction函数执行这条transaction,取出transaction里面的operation,进行一系列执行过程,并将执行结果附加到这个transaction的后面,一个signed_transaction执行过后便是一个processed_transaction了,而processed_transaction会被放入到区块里面
在apply_transaction里面,进行一系列调用,此处层次比较多,暂时不作详细说明,经过limit_order_create_evaluator的do_apply函数(market_evaluator.cpp 73行):
此处创建一个limit_order_object对象,将此limit_order_object存进数据库(object_database),然后调用apply_order函数(db_market.cpp 239行),其中关键代码在此处
根据limit_order_object对象中的price,从数据库中查询所有price满足要求的limit_order_object,然后在while循环中,进行match操作,match函数在301行,
在if判断中,判断生成的limit_order_object(简称order1)和查询到的limit_order_object(简称order2),在if为true时,order2满足order1,但order1不一定满足order2,比如order1卖出1000BTS买入1BTC,order2卖出2BTC买入2000BTS,这种情况就是order2满足order1而order1不满足order2,此时可以进行匹配,运行两次fill_order函数,第一次fill_order修改order1,第二次fill_order修改order2,下面是fill_order相关代码
首先执行pay_order函数,如下
这个函数先修改order对应用户的total_core_in_orders值,减去卖出值,再修改余额,加上买入值。
然后在if( pays == order.amount_for_sale() )判断中,判断一下,如果这次匹配的卖出等于这个order的卖出,那么代表这个order匹配成功了,执行remove函数,从数据库中删除这个order,如果小于的话,说明这个order没有完全匹配成功,进行修改操作,还是用上面的例子,order2原来卖出2BTC,但和order1匹配,order2其中1个BTC会给order1,所以此处将order2的卖出改成1BTC,仍然保留在数据库中。
在match函数最后result,如果order1被满足,返回1,如果order2被满足,返回2,如果order1和order2同时被满足,返回3。
然后回来apply_order中的while循环中,每次match,如果返回值等于2,继续匹配,如果返回值不等于2,结束循环,即优先匹配order1,尽量保证order1被匹配成功,只要order1被匹配成功立即跳出循环,order2就算部分被匹配也无所谓,只进行修改。
现在push_transaction已经执行完成,下一步是broadcast_transaction这个操作,broadcast_transaction函数最终会调用node.cpp 4938行broadcast函数
判断broadcast的消息类型是transaction,将transaction相关信息缓存进_message_cache和_new_inventory里面,并执行trigger_advertise_inventory_loop。在node.cpp的1234行advertise_inventory_loop函数中,可以看到,从_new_inventory里面取出缓存的message信息,向每个结点发送一条item_ids_inventory_message消息。一个item_ids_inventory_message包含两种信息,一个是消息类型,即transaction类型,一个是消息id,是signed_transaction的哈希。
当一个signed_transaction被广播出去之后,根据grahpene网络结构,其它见证人结点也会接收到这个transaction,见node.cpp 1758行on_message函数,on_message是专门用于处理网络上消息的函数,根据不同的消息类型,进行对应的处理,当其它结点接收到transaction后,执行process_ordinary_message(node.cpp 3864行)。在这里调用handle_transaction函数,如下图所示
在这里调用database的push_transaction函数,push_transaction函数上面有讲解,执行apply_transaction函数,进行一系列处理。因为当一个见证人结点接收到transaction并广播出去了,其它的见证人结点进行同步处理,所以所有的结点都将transaction处理了一遍,保证各个结点上数据的同步。
第3章 【总结】
每一个订单对象在数据库中都是一个limit_order_object实例,此对象包含3个关键因素:卖出的资产、买入的资产、两种资产的比价(价格)。bitshares在生成一个limit_order_object后,会从数据库中按价格进行查找,找到价格可以匹配的订单,匹配成功的订单从数据库中删除,匹配部分成功的订单会修改一下留着继续匹配,匹配后会修改两个订单发布者的对应资产余额,这就是订单撮合的流程。