/// <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);
                            }
                        }
                    }
                }
            }
        }