文章原文来自:Code your own blockchain in less than 200 lines of Go!,原始文章是通过 Go 语言来实现自己的区块链的,这里我们参照该文章来使用 C# + Asp.Net Core 实现自己的区块链。在这里我也参考了 这篇译文 。
区块链网络相关原始文章已经更新,请查看 这里。
1.项目配置
首先新建一个 Asp.Net Core 项目,然后选择 Empty Project(空项目) 类型,建立完成后无需进行任何配置。
2.数据模型
这里我们来创建一个具体的区块数据模型,使用的是 Struct 结构体。
<span class="hljs-keyword">public</span> <span class="hljs-keyword">struct</span> Block
{
<span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag"><summary></span></span>
<span class="hljs-comment"><span class="hljs-doctag">///</span> 区块位置</span>
<span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag"></summary></span></span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> Index { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
<span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag"><summary></span></span>
<span class="hljs-comment"><span class="hljs-doctag">///</span> 区块生成时间戳</span>
<span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag"></summary></span></span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> TimeStamp { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
<span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag"><summary></span></span>
<span class="hljs-comment"><span class="hljs-doctag">///</span> 心率数值</span>
<span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag"></summary></span></span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> BPM { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
<span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag"><summary></span></span>
<span class="hljs-comment"><span class="hljs-doctag">///</span> 区块 SHA-256 散列值</span>
<span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag"></summary></span></span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Hash { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
<span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag"><summary></span></span>
<span class="hljs-comment"><span class="hljs-doctag">///</span> 前一个区块 SHA-256 散列值</span>
<span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag"></summary></span></span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> PrevHash { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
}
这里各个字段的含义已经在注释上方标明了,这里不在过多赘述。
之后我们新建一个 BlockGenerator 静态类用于管理区块链,并且使用一个 List 保存区块链数据。
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">BlockGenerator</span>
{
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> List<Block> _blockChain = <span class="hljs-keyword">new</span> List<Block>();
}
我们使用散列算法(SHA256)来确定和维护链中块和块正确的顺序,确保每一个块的 PrevHash 值等于前一个块中的 Hash 值,这样就以正确的块顺序构建出链:
4.散列与生成区块
使用散列是因为可以使用极少的控件生成每一个区块的唯一标识,而且可以维持整个区块链的完整性,通过每个区块存储的前一个链的散列值,我们就可以确保区块链当中每一个区块的正确性,任何针对区块的无效更改都会导致散列值的改变,也就破坏了区块链。
那么我们就在 BlockGenerator 当中添加一个函数用于计算 Block 的 Hash 值:
<span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag"><summary></span></span>
<span class="hljs-comment"><span class="hljs-doctag">///</span> 计算区块 HASH 值</span>
<span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag"></summary></span></span>
<span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag"><param name="block"></span>区块实例<span class="hljs-doctag"></param></span></span>
<span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag"><returns></span>计算完成的区块散列值<span class="hljs-doctag"></returns></span></span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">string</span> <span class="hljs-title">CalculateHash</span>(<span class="hljs-params">Block block</span>)
</span>{
<span class="hljs-keyword">string</span> calculationStr = $<span class="hljs-string">"{block.Index}{block.TimeStamp}{block.BPM}{block.PrevHash}"</span>;
SHA256 sha256Generator = SHA256.Create();
<span class="hljs-keyword">byte</span>[] sha256HashBytes = sha256Generator.ComputeHash(Encoding.UTF8.GetBytes(calculationStr));
StringBuilder sha256StrBuilder = <span class="hljs-keyword">new</span> StringBuilder();
<span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">byte</span> @<span class="hljs-keyword">byte</span> <span class="hljs-keyword">in</span> sha256HashBytes)
{
sha256StrBuilder.Append(@<span class="hljs-keyword">byte</span>.ToString(<span class="hljs-string">"x2"</span>));
}
<span class="hljs-keyword">return</span> sha256StrBuilder.ToString();
}
这里的 CalculateHash 函数接收一个 Block 实例,通过该实例当中的 Index、TimeStamp、BPM、PrevHash 的值来计算出当前块的 SHA256 Hash 值,之后我们就可以来编写一个生成块的函数:
<span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag"><summary></span></span>
<span class="hljs-comment"><span class="hljs-doctag">///</span> 生成新的区块</span>
<span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag"></summary></span></span>
<span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag"><param name="oldBlock"></span>旧的区块数据<span class="hljs-doctag"></param></span></span>
<span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag"><param name="BPM"></span>心率<span class="hljs-doctag"></param></span></span>
<span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag"><returns></span>新的区块<span class="hljs-doctag"></returns></span></span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Block <span class="hljs-title">GenerateBlock</span>(<span class="hljs-params">Block oldBlock, <span class="hljs-keyword">int</span> BPM</span>)
</span>{
Block newBlock = <span class="hljs-keyword">new</span> Block()
{
Index = oldBlock.Index + <span class="hljs-number">1</span>,
TimeStamp = CalculateCurrentTimeUTC(),
BPM = BPM,
PrevHash = oldBlock.Hash
};
newBlock.Hash = CalculateHash(newBlock);
<span class="hljs-keyword">return</span> newBlock;
}
这个函数需要接收前一个块对象的值,用于新区块的 Index 递增以及 新的 SHA256 Hash 计算。
这里掺入了一个 CalculateCurrentTimeUTC 函数,该函数主要是用于将 DateTime.Now 时间转换为 UTC 时间,如下:
<span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag"><summary></span></span>
<span class="hljs-comment"><span class="hljs-doctag">///</span> 计算当前时间的 UTC 表示格式</span>
<span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag"></summary></span></span>
<span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag"><returns></span>UTC 时间字符串<span class="hljs-doctag"></returns></span></span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">string</span> <span class="hljs-title">CalculateCurrentTimeUTC</span>()
</span>{
DateTime startTime = <span class="hljs-keyword">new</span> DateTime(<span class="hljs-number">1970</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>);
DateTime nowTime = DateTime.Now;
<span class="hljs-keyword">long</span> unixTime = (<span class="hljs-keyword">long</span>)Math.Round((nowTime - startTime).TotalMilliseconds, MidpointRounding.AwayFromZero);
<span class="hljs-keyword">return</span> unixTime.ToString();
}
5.校验区块
每一个区块都是不可信的,所以我们需要在生成新的区块的时候对其进行校验,校验规则如下:
- 校验新区块与旧区块的 Index 是否正确递增
- 校验新区块的 Hash 值是否正确
- 校验新区块的 PrevHash 值是否与旧区块的 Hash 值匹配
有了上述几种条件,我们可以编写一个校验函数如下:
<span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag"><summary></span></span>
<span class="hljs-comment"><span class="hljs-doctag">///</span> 检验区块是否有效</span>
<span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag"></summary></span></span>
<span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag"><param name="newBlock"></span>新生成的区块数据<span class="hljs-doctag"></param></span></span>
<span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag"><param name="oldBlock"></span>旧的区块数据<span class="hljs-doctag"></param></span></span>
<span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag"><returns></span>有效返回 TRUE,无效返回 FALSE<span class="hljs-doctag"></returns></span></span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">bool</span> <span class="hljs-title">IsBlockValid</span>(<span class="hljs-params">Block newBlock, Block oldBlock</span>)
</span>{
<span class="hljs-keyword">if</span> (oldBlock.Index + <span class="hljs-number">1</span> != newBlock.Index) <span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>;
<span class="hljs-keyword">if</span> (oldBlock.Hash != newBlock.PrevHash) <span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>;
<span class="hljs-keyword">if</span> (CalculateHash(newBlock) != newBlock.Hash) <span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>;
<span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>;
}
除开区块校验的问题之外,如果有两个节点被分别添加到各自的区块链上,我们应该始终以最长的那一条为主线,因为最长的那一条意味着他的区块数据始终是最新的。
So,我们还需要一个更新最新区块的函数:
<span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag"><summary></span></span>
<span class="hljs-comment"><span class="hljs-doctag">///</span> 如果新的区块链比当前区块链更新,则切换当前区块链为最新区块链</span>
<span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag"></summary></span></span>
<span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag"><param name="newBlockChain"></span>新的区块链<span class="hljs-doctag"></param></span></span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">SwitchChain</span>(<span class="hljs-params">List<Block> newBlockChain</span>)
</span>{
<span class="hljs-keyword">if</span> (newBlockChain.Count > _blockChain.Count)
{
_blockChain = newBlockChain;
}
}
6.集成到 Web 当中
现在整个区块链的基本操作已经完成,现在我们需要让他运转起来,我们来到 StartUp 当中,添加两个新的路由:
app.Map(<span class="hljs-string">"/BlockChain"</span>, _ =>
{
_.Run(<span class="hljs-keyword">async</span> context =>
{
<span class="hljs-keyword">if</span> (context.Request.Method == <span class="hljs-string">"POST"</span>)
{
<span class="hljs-comment">// 增加区块链</span>
<span class="hljs-keyword">if</span> (BlockGenerator._blockChain.Count == <span class="hljs-number">0</span>)
{
Block firstBlock = <span class="hljs-keyword">new</span> Block()
{
Index = <span class="hljs-number">0</span>,
TimeStamp = BlockGenerator.CalculateCurrentTimeUTC(),
BPM = <span class="hljs-number">0</span>,
Hash = <span class="hljs-keyword">string</span>.Empty,
PrevHash = <span class="hljs-keyword">string</span>.Empty
};
BlockGenerator._blockChain.Add(firstBlock);
<span class="hljs-keyword">await</span> context.Response.WriteAsync(JsonConvert.SerializeObject(firstBlock));
}
<span class="hljs-keyword">else</span>
{
<span class="hljs-keyword">int</span>.TryParse(context.Request.Form[<span class="hljs-string">"BPM"</span>][<span class="hljs-number">0</span>], <span class="hljs-keyword">out</span> <span class="hljs-keyword">int</span> bpm);
Block oldBlock = BlockGenerator._blockChain.Last();
Block newBlock = BlockGenerator.GenerateBlock(oldBlock, bpm);
<span class="hljs-keyword">if</span> (BlockGenerator.IsBlockValid(newBlock, oldBlock))
{
List<Block> newBlockChain = <span class="hljs-keyword">new</span> List<Block>();
<span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">var</span> block <span class="hljs-keyword">in</span> BlockGenerator._blockChain)
{
newBlockChain.Add(block);
}
newBlockChain.Add(newBlock);
BlockGenerator.SwitchChain(newBlockChain);
}
<span class="hljs-keyword">await</span> context.Response.WriteAsync(JsonConvert.SerializeObject(newBlock));
}
}
});
});
app.Map(<span class="hljs-string">"/BlockChains"</span>, _ =>
{
_.Run(<span class="hljs-keyword">async</span> context =>
{
<span class="hljs-keyword">await</span> context.Response.WriteAsync(JsonConvert.SerializeObject(BlockGenerator._blockChain));
});
});
7.最终效果
我们先通过 PostMan 来构建一个创世块:
然后我们尝试多添加几个之后,访问 BlockChain 来查看已经存在的区块链结构:
8.结语
通过以上代码我们完成了一个简陋的区块链,虽然十分简陋,但是已经具备了块生成,散列计算,块校验这些基本能力,你可以参考 GitHub 上面各种成熟的区块链实现来完成工作量证明、权益证明这样的共识算法,或者是智能合约、Dapp、侧链等等。