参考文章:http://www.cnblogs.com/myzony/p/8478789.html
使用 C# + Asp.Net Core 实现自己的区块链项目。
1.项目配置
首先新建一个 Asp.Net Core 项目,然后选择 Empty Project(空项目) 类型,建立完成后无需进行任何配置。(本文以Microsoft Visual Studio2017企业版创建项目)
过程如下:
(1).打开文件→新建→项目
(2) 选择Visual C# 菜单栏下的Web →选择.NET.Core→选择ASP.NET.Core Web应用程序进行创建项目。
(3)选择空类型进行创建,勾选:为HTPS配置,创建完成后不需要进行任何配置。
2.Program.cs中需要的引用声明
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
3.建立区块链数据模型
(1)创建一个具体的区块数据模型,使用Struct 结构体。
代码如下:
public struct Block
{
/// <summary>
/// 区块位置
/// </summary>
public int Index { get; set; }
/// <summary>
/// 区块生成时间戳
/// </summary>
public string TimeStamp { get; set; }
/// <summary>
/// 心率数值
/// </summary>
public int BPM { get; set; }
/// <summary>
/// 区块 SHA-256 散列值
/// </summary>
public string Hash { get; set; }
/// <summary>
/// 前一个区块 SHA-256 散列值
/// </summary>
public string PrevHash { get; set; }
}
(2)之后我们新建一个 BlockGenerator 静态类用于管理区块链,并且使用一个 List 保存区块链数据。
代码如下:
public static class BlockGenerator
{
public static List<Block> _blockChain = new List<Block>();
internal static string CalculateCurrentTimeUTC()
{
throw new NotImplementedException();
}
internal static void SwitchChain(List<Block> newBlockChain)
{
throw new NotImplementedException();
}
internal static Block GenerateBlock(Block oldBlock, int bpm)
{
throw new NotImplementedException();
}
internal static bool IsBlockValid(Block newBlock, Block oldBlock)
{
throw new NotImplementedException();
}
}
(3)在 BlockGenerator 当中添加一个函数用于计算 Block 的 Hash 值:
代码如下:
/// <summary>
/// 计算区块 HASH 值
/// </summary>
/// <param name="block">区块实例</param>
/// <returns>计算完成的区块散列值</returns>
public static string CalculateHash(Block block)
{
string calculationStr = $"{block.Index}{block.TimeStamp}{block.BPM}{block.PrevHash}";
SHA256 sha256Generator = SHA256.Create();
byte[] sha256HashBytes = sha256Generator.ComputeHash(Encoding.UTF8.GetBytes(calculationStr));
StringBuilder sha256StrBuilder = new StringBuilder();
foreach (byte @byte in sha256HashBytes)
{
sha256StrBuilder.Append(@byte.ToString("x2"));
}
return sha256StrBuilder.ToString();
}
(4)这里的 CalculateHash 函数接收一个 Block 实例,通过该实例当中的 Index、TimeStamp、BPM、PrevHash 的值来计算出当前块的 SHA256 Hash 值,之后我们就可以来编写一个生成块的函数:
代码如下:
/// <summary>
/// 生成新的区块
/// </summary>
/// <param name="oldBlock">旧的区块数据</param>
/// <param name="BPM">心率</param>
/// <returns>新的区块</returns>
public static Block GenerateBlock(Block oldBlock, int BPM)
{
Block newBlock = new Block()
{
Index = oldBlock.Index + 1,
TimeStamp = CalculateCurrentTimeUTC(),
BPM = BPM,
PrevHash = oldBlock.Hash
};
newBlock.Hash = CalculateHash(newBlock);
return newBlock;
}
(5)这个函数需要接收前一个块对象的值,用于新区块的 Index 递增以及 新的 SHA256 Hash 计算。
这里掺入了一个 CalculateCurrentTimeUTC 函数,该函数主要是用于将 DateTime.Now 时间转换为 UTC 时间,如下:
/// <summary>
/// 计算当前时间的 UTC 表示格式
/// </summary>
/// <returns>UTC 时间字符串</returns>
public static string CalculateCurrentTimeUTC()
{
DateTime startTime = new DateTime(1970, 1, 1, 0, 0, 0, 0);
DateTime nowTime = DateTime.Now;
long unixTime = (long)Math.Round((nowTime - startTime).TotalMilliseconds, MidpointRounding.AwayFromZero);
return unixTime.ToString();
}
(6)每一个区块都是不可信的,所以我们需要在生成新的区块的时候对其进行校验,校验规则如下:
- 校验新区块与旧区块的 Index 是否正确递增
- 校验新区块的 Hash 值是否正确
- 校验新区块的 PrevHash 值是否与旧区块的 Hash 值匹配
有了上述几种条件,我们可以编写一个校验函数如下:
/// <summary>
/// 检验区块是否有效
/// </summary>
/// <param name="newBlock">新生成的区块数据</param>
/// <param name="oldBlock">旧的区块数据</param>
/// <returns>有效返回 TRUE,无效返回 FALSE</returns>
public static bool IsBlockValid(Block newBlock, Block oldBlock)
{
if (oldBlock.Index + 1 != newBlock.Index) return false;
if (oldBlock.Hash != newBlock.PrevHash) return false;
if (CalculateHash(newBlock) != newBlock.Hash) return false;
return true;
}
(7)除开区块校验的问题之外,如果有两个节点被分别添加到各自的区块链上,我们应该始终以最长的那一条为主线,因为最长的那一条意味着他的区块数据始终是最新的。所以,我们还需要一个更新最新区块的函数(之前有声明)。
/// <summary>
/// 如果新的区块链比当前区块链更新,则切换当前区块链为最新区块链
/// </summary>
/// <param name="newBlockChain">新的区块链</param>
public static void SwitchChain(List<Block> newBlockChain)
{
if (newBlockChain.Count > _blockChain.Count)
{
_blockChain = newBlockChain;
}
}
3.集成到Web
在StartUp 当中,添加两个新的路由
Startup.cs源码如下(包含引用及路由)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using static WebApplication1.Program;
namespace WebApplication1
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.Map("/BlockChain", _ =>
{
_.Run(async context =>
{
if (context.Request.Method == "POST")
{
// 增加区块链
if (BlockGenerator._blockChain.Count == 0)
{
Block firstBlock = new Block()
{
Index = 0,
TimeStamp = BlockGenerator.CalculateCurrentTimeUTC(),
BPM = 0,
Hash = string.Empty,
PrevHash = string.Empty
};
BlockGenerator._blockChain.Add(firstBlock);
await context.Response.WriteAsync(JsonConvert.SerializeObject(firstBlock));
}
else
{
var form = await context.Request.ReadFormAsync();
int.TryParse(form["BPM"][0], out int bpm);
Block oldBlock = BlockGenerator._blockChain.Last();
Block newBlock = BlockGenerator.GenerateBlock(oldBlock, bpm);
if (BlockGenerator.IsBlockValid(newBlock, oldBlock))
{
List<Block> newBlockChain = new List<Block>();
foreach (var block in BlockGenerator._blockChain)
{
newBlockChain.Add(block);
}
newBlockChain.Add(newBlock);
BlockGenerator.SwitchChain(newBlockChain);
}
await context.Response.WriteAsync(JsonConvert.SerializeObject(newBlock));
}
}
});
});
app.Map("/BlockChains", _ =>
{
_.Run(async context =>
{
await context.Response.WriteAsync(JsonConvert.SerializeObject(BlockGenerator._blockChain));
});
});
}
}
}
4.暂行调试结果:
5.最终效果
(1)先通过PostMan创建一个创世块
PostMan下载链接:https://www.getpostman.com/products,选择适用版本安装后创建一个创世块。
(2)然后尝试多添加几个之后,访问 BlockChain 来查看已经存在的区块链结构。
附c#项目代码:
https://gitee.com/DiJun1/QuKuaiLianShiLi