/// <summary> /// 交易业务说明 /// SendRaw是将交易数据从 任意节点(任意节点包括共识节点) 流转到 一个共识节点的过程 /// /// 1.流转说明 /// 如果SendRaw发起者就是共识节点,则不需要再流转了,直接处理交易 /// 如果不是,只流转给自己连接的节点中plevel 小于等于自己的,这个没见到 /// 2.sendraw参数 /// SendRaw(byte[] message,SignData) /// 发送交易只需要两个数据,一个 bytearray,一个signdata,一定是byte[],不要整什么MessagePackObjectList /// signdata参考neo的最简形态,我们只支持最简的 txpool.TransactionSign 写在那里的 /// 3.流转验证 /// 每一次流转,都要检查sendraw 数据对不对 /// signdata里面的vscript 包含公钥,iscript包含签名数据,执行ecc验签,不通过不转发,还记录本地黑名单,第二次收到,都不需要验证 /// /// 4.txid /// 交易的id ,就是交易message 的 hash256,保持和neo兼容 /// /// 5.共识节点收到交易的处理 /// 交易不是块,和块没有关系 /// 先忽略共识过程,系统就一个共识节点,自己就是议长。议长干的第一件事是构造一个统一的交易内存池 /// 收到交易,只需要存在内存里 /// 我们先假设所有共识节点有同样的交易index,交易的index是由议长分配的,议长分配完id,告知所有的共识节点 /// /// 当前共识节点收到交易,给他分配一个index,就存在自己的内存池里,不需要存数据库。 /// 分配了index的交易就可以全网广播,>=plevel /// 然后开一个定时器,定时从自己的内存池里挑一些交易,组装成块,广播 /// /// block 的header 包括当前块所包含的交易的hash /// block 的body 就是 所有的包含的交易的 message 和 signdata /// 只需要广播block的header,block的body 各个节点自己就可以组装 /// /// 6.错过广播 /// 错过广播是很正常的,所以每个节点有一个高度设计,就是block的index /// 可以找任意高度大于自己的节点索要指定的block header(by block index or block id) 和 指定的hash(by txid) /// /// 7.数据存储 /// 对共识节点,仅当组装成块的时候,一次性写入 block header、block涉及的交易、当前高度,用db 的 writebatch 方式 /// 刚收到的交易不写数据库,仅有随块一起写入的,并且已写入的交易,内存池里就不必保持了。 /// 但是所有的txid->block index 的映射,内存池里要保持 /// </summary> /// <param name="from"></param> /// <param name="dict"></param> //static DateTime start; //static int recvcount = 0; void OnRecv_Post_SendRaw(IModulePipeline from, MessagePackObjectDictionary dict) { //if(recvcount == 0 ) //{ // start = DateTime.Now; //} //recvcount++; logger.Info($"------OnRecv_Post_SendRaw From:{from?.system?.Remote?.ToString()??"Local"} -------"); //验证交易合法性,合法就收 var signData = SerializeHelper.DeserializeWithBinary <TransactionSign>(dict["signData"].AsBinary()); bool sign = Helper_NEO.VerifySignature(dict["message"].AsBinary(), signData.IScript, signData.VScript); if (!sign) { logger.Info($"------OnRecv_Post_SendRaw sign error -------"); return; } //if (recvcount % 20 == 0) //{ // var end = DateTime.Now; // logger.Info("recv count:" + recvcount + " span=" + (end - start).TotalMilliseconds + " txpool.discard"+ TXPool.txpoolcount); //} //收到消息后要么转发,要么保存 if (this.isProved) { Transaction trans = new Transaction(); trans.Index = this.txpool.MaxTransactionID; trans.message = dict["message"].AsBinary(); trans.signdata = signData; lock (blockTimerLock) { this.txpool.AddTx(trans); } //向内存池保存完,向全网广播这个交易 foreach (var item in this.linkNodes) { if (item.Value.hadJoin) { Tell_BoardCast_Tx(item.Value.remoteNode, dict["message"].AsBinary(), dict["signData"].AsBinary()); } } } else { //只流转给非记账节点,按照优先级,小于等于自己的其中一个 LinkObj minLink = null; foreach (var item in this.linkNodes) { if ((minLink == null || item.Value.pLevel < minLink.pLevel) && (item.Value.hadJoin)) { minLink = item.Value; } } if (minLink != null) { Tell_SendRaw(minLink.remoteNode, dict["message"].AsBinary(), dict["signData"].AsBinary()); } } }
/// <summary> /// linkobj的plevel无效了 /// </summary> /// <param name="obj"></param> /// <returns>本节点是否被影响</returns> bool losePlevelFromLinkObj(LinkObj obj) { if (obj.pLevel != -1 && obj.pLevel < this.pLevel) //判断是否优先级比本节点高 { obj.pLevel = -1; //重置 if (!this.checklinkNodesPlevelUpThis()) { this.losePlevel(); return(true); } } return(false); }
/// <summary> /// linkobj告诉我他的plevel /// </summary> /// <param name="link"></param> /// <param name="linkPlevel"></param> void getPlevelFromLinkObj(LinkObj link, int linkPlevel) { if (this.beObserver) { return; } if (linkPlevel >= 0)//当libobj 的plevel=-1的时候,本节点不受它影响 { //linkobj的初值为-1,对方可能发多次消息过来告知plevel变更。 //进入这个函数方式:1.OnRecv_ResponseAcceptJoin 2.接到广播OnRecv_BoradCast_PeerState(向周边发广播原因:1.本节点优先级变大了。2.收到OnRecv_BoardCast_LosePlevel 周边节点需要重新传递plevel) //gaoxiqing //这种假设应该是错的,没办法保证只往小的变,如果你真想区分多个命令的先后的话,建议加上时间戳,根据时间戳判断肯定不会错 // # 回应 #原因见进入方式,所以不需要时间戳 if (link.pLevel >= linkPlevel || link.pLevel == -1) { link.pLevel = linkPlevel; if (this.pLevel > linkPlevel + 1 || this.pLevel == -1)//linkobj告知本机刷新plevel { this.pLevel = linkPlevel + 1; foreach (var item in this.linkNodes) { //不管linkobj是否hasjoin,都给它发消息.如果linknode最终还是hasjoin=false,就当做发一次无效消息。 //判断linkobj hasjoin=ture才发的话,有可能本节点已告知过该节点plevel(request_joinpeer),response消息还没返回,该节点在本节点向下广播的时候没有收到消息,之后也不会被告知本节点刷新plevel了, //gaoxiqing //不判断hasjoin=ture的话,有时是会出异常的。假设那个连接它真就没有连上,这时你给它发信,肯定出异常。 //我觉得对应这种情况,应该在ResponseAcceptJoin中加上刷新level回执消息,无论什么时候收到,都去刷下对方的level //# 回应# 1.onpeerlink之后才会开始发消息,所以连接上了 2.actor发消息不关心有没有连接上,如果异常了,底层没处理好,修就行了。 //# 回应# 在ResponseAcceptJoin中加上刷新level回执消息,这种方式也行,但是增加了发消息的频度,这个消息是可以不发的。 if ((item.Value.pLevel > this.pLevel || item.Value.pLevel == -1)) { Tell_BoradCast_PeerState(item.Value.remoteNode); } } } } } }