/// <summary>
        /// Response to recording request from side chain node.
        /// One request to many responses.
        /// </summary>
        /// <param name="request"></param>
        /// <param name="responseStream"></param>
        /// <param name="context"></param>
        /// <returns></returns>
        public override async Task RecordServerStreaming(RequestBlockInfo request, IServerStreamWriter <ResponseParentChainBlockInfo> responseStream, ServerCallContext context)
        {
            _logger?.Trace("Parent Chain Server received IndexedInfo message.");

            try
            {
                var height      = request.NextHeight;
                var sideChainId = request.ChainId;
                while (height <= await BlockChain.GetCurrentBlockHeightAsync())
                {
                    IBlock block = await BlockChain.GetBlockByHeightAsync(height);

                    BlockHeader header = block?.Header;
                    BlockBody   body   = block?.Body;

                    var res = new ResponseParentChainBlockInfo
                    {
                        Success = block != null
                    };

                    if (res.Success)
                    {
                        res.BlockInfo = new ParentChainBlockInfo
                        {
                            Root = new ParentChainBlockRootInfo
                            {
                                Height = height,
                                SideChainBlockHeadersRoot = header?.SideChainBlockHeadersRoot,
                                SideChainTransactionsRoot = header?.SideChainTransactionsRoot,
                                ChainId = header?.ChainId
                            }
                        };

                        var tree = await _binaryMerkleTreeManager
                                   .GetSideChainTransactionRootsMerkleTreeByHeightAsync(header?.ChainId, height);

                        //Todo: this is to tell side chain the height of side chain block in this main chain block, which could be removed with subsequent improvement.
                        body?.IndexedInfo.Where(predicate: i => i.ChainId.Equals(sideChainId))
                        .Select((info, index) =>
                                new KeyValuePair <ulong, MerklePath>(info.Height, tree.GenerateMerklePath(index)))
                        .ForEach(kv => res.BlockInfo.IndexedBlockInfo.Add(kv.Key, kv.Value));
                    }

                    //_logger?.Log(LogLevel.Trace, $"Parent Chain Server responsed IndexedInfo message of height {height}");
                    await responseStream.WriteAsync(res);

                    height++;
                }
            }
            catch (Exception e)
            {
                _logger?.Error(e, "Miner server RecordDuplexStreaming failed.");
            }
        }
        /// <summary>
        /// Response to recording request from side chain node.
        /// Many requests to many responses.
        /// </summary>
        /// <param name="requestStream"></param>
        /// <param name="responseStream"></param>
        /// <param name="context"></param>
        /// <returns></returns>
        public override async Task RecordDuplexStreaming(IAsyncStreamReader <RequestBlockInfo> requestStream,
                                                         IServerStreamWriter <ResponseParentChainBlockInfo> responseStream, ServerCallContext context)
        {
            _logger?.Debug("Parent Chain Server received IndexedInfo message.");

            try
            {
                while (await requestStream.MoveNext())
                {
                    var requestInfo     = requestStream.Current;
                    var requestedHeight = requestInfo.NextHeight;
                    var sideChainId     = requestInfo.ChainId;
                    var currentHeight   = await BlockChain.GetCurrentBlockHeightAsync();

                    if (currentHeight - requestedHeight < (ulong)GlobalConfig.InvertibleChainHeight)
                    {
                        await responseStream.WriteAsync(new ResponseParentChainBlockInfo
                        {
                            Success = false
                        });

                        continue;
                    }
                    IBlock block = await BlockChain.GetBlockByHeightAsync(requestedHeight);

                    BlockHeader header = block?.Header;
                    BlockBody   body   = block?.Body;

                    var res = new ResponseParentChainBlockInfo
                    {
                        Success = block != null
                    };

                    if (res.Success)
                    {
                        res.BlockInfo = new ParentChainBlockInfo
                        {
                            Root = new ParentChainBlockRootInfo
                            {
                                Height = requestedHeight,
                                SideChainBlockHeadersRoot = header?.SideChainBlockHeadersRoot,
                                SideChainTransactionsRoot = header?.SideChainTransactionsRoot,
                                ChainId = header?.ChainId
                            }
                        };
                        var tree = await _binaryMerkleTreeManager
                                   .GetSideChainTransactionRootsMerkleTreeByHeightAsync(header?.ChainId, requestedHeight);

                        if (tree != null)
                        {
                            //Todo: this is to tell side chain the merkle path for one side chain block, which could be removed with subsequent improvement.

                            /*body?.IndexedInfo.Where(predicate: i => i.ChainId.Equals(sideChainId))
                             *  .Select((info, index) =>
                             *      new KeyValuePair<ulong, MerklePath>(info.Height, tree.GenerateMerklePath(index)))
                             *  .ForEach(kv => res.BlockInfo.IndexedBlockInfo.Add(kv.Key, kv.Value));*/
                            for (int i = 0; i < body?.IndexedInfo.Count; i++)
                            {
                                var info = body.IndexedInfo[i];
                                if (!info.ChainId.Equals(sideChainId))
                                {
                                    continue;
                                }
                                var merklePath = tree.GenerateMerklePath(i);
                                if (merklePath == null)
                                {
                                    _logger?.Trace($"tree.Root == null: {tree.Root == null}");
                                    _logger?.Trace($"tree.LeafCount = {tree.LeafCount}, index = {i}");
                                }
                                res.BlockInfo.IndexedBlockInfo.Add(info.Height, merklePath);
                            }
                        }
                    }

                    await responseStream.WriteAsync(res);
                }
            }
            catch (Exception e)
            {
                _logger?.Error(e, "Miner server RecordDuplexStreaming failed.");
            }
        }