/// <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());
                }
            }
        }
        void OnRecv_Response_Tx(IModulePipeline from, MessagePackObjectDictionary dict)
        {
            var tx = dict["tx"].AsBinary();

            if (tx != null)
            {
                var trans = SerializeHelper.DeserializeWithBinary <Transaction>(tx);
                //验证交易合法性,合法就收
                bool sign = Helper_NEO.VerifySignature(trans.message, trans.signdata.IScript, trans.signdata.VScript);
                if (!sign)
                {
                    return;
                }
                this.txpool.AddTx(trans);
            }
        }
        void OnRecv_BoardCast_Tx(IModulePipeline from, MessagePackObjectDictionary dict)
        {
            var  signData = SerializeHelper.DeserializeWithBinary <TransactionSign>(dict["signData"].AsBinary());
            bool sign     = Helper_NEO.VerifySignature(dict["message"].AsBinary(), signData.IScript, signData.VScript);

            if (!sign)
            {
                return;
            }
            Transaction trans = new Transaction();

            trans.Index    = this.txpool.MaxTransactionID;
            trans.message  = dict["message"].AsBinary();
            trans.signdata = signData;
            this.txpool.AddTx(trans);
        }
        void OnRecv_RequestProvePeer(IModulePipeline from, MessagePackObjectDictionary dict)
        {
            var  link     = this.linkNodes[from.system.PeerID];
            var  addinfo  = dict["addinfo"].AsBinary();
            var  pubkey   = dict["pubkey"].AsBinary();
            var  signdata = dict["signdata"].AsBinary();
            var  message  = addinfo.Concat(link.CheckInfo).ToArray();
            bool sign     = Helper_NEO.VerifySignature(message, signdata, pubkey);

            if (sign)
            {
                link.PublicKey = pubkey;
                logger.Info("had a proved peer:" + Helper.Bytes2HexString(pubkey));
            }
            else
            {
                logger.Info("had a error proved peer:" + Helper.Bytes2HexString(pubkey));
            }
        }