C# 简单的区块链实现

摘要:
设置设置块实例<SHA256sha256Generator=SHA256.Create();StringBuildersha256StrBuilder=newStringBuilder();}returnsha256StrBuilder.ToString();旧块数据&lt///<新建块<

1.项目配置

首先新建一个 Asp.Net Core 项目,然后选择 Empty Project(空项目) 类型,建立完成后无需进行任何配置。

2.数据模型

这里我们来创建一个具体的区块数据模型,使用的是 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; }
}

这里各个字段的含义已经在注释上方标明了,这里不在过多赘述。
之后我们新建一个 BlockGenerator 静态类用于管理区块链,并且使用一个 List 保存区块链数据。

public static class BlockGenerator
{
    public static List<Block> _blockChain = new List<Block>();
}

我们使用散列算法(SHA256)来确定和维护链中块和块正确的顺序,确保每一个块的 PrevHash 值等于前一个块中的 Hash 值,这样就以正确的块顺序构建出链:
img1

4.散列与生成区块

使用散列是因为可以使用极少的控件生成每一个区块的唯一标识,而且可以维持整个区块链的完整性,通过每个区块存储的前一个链的散列值,我们就可以确保区块链当中每一个区块的正确性,任何针对区块的无效更改都会导致散列值的改变,也就破坏了区块链。
那么我们就在 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();
}

这里的 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;
}

这个函数需要接收前一个块对象的值,用于新区块的 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();
}

5.校验区块

每一个区块都是不可信的,所以我们需要在生成新的区块的时候对其进行校验,校验规则如下:

  • 校验新区块与旧区块的 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;
}

除开区块校验的问题之外,如果有两个节点被分别添加到各自的区块链上,我们应该始终以最长的那一条为主线,因为最长的那一条意味着他的区块数据始终是最新的。

img2
So,我们还需要一个更新最新区块的函数:

/// <summary>
/// 如果新的区块链比当前区块链更新,则切换当前区块链为最新区块链
/// </summary>
/// <param name="newBlockChain">新的区块链</param>
public static void SwitchChain(List<Block> newBlockChain)
{
    if (newBlockChain.Count > _blockChain.Count)
    {
        _blockChain = newBlockChain;
    }
}

6.集成到 Web 当中

现在整个区块链的基本操作已经完成,现在我们需要让他运转起来,我们来到 StartUp 当中,添加两个新的路由:

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
            {
                int.TryParse(context.Request.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));
    });
});

7.最终效果

我们先通过 PostMan 来构建一个创世块:


img3
然后我们尝试多添加几个之后,访问 BlockChain 来查看已经存在的区块链结构:
img4

8.结语

通过以上代码我们完成了一个简陋的区块链,虽然十分简陋,但是已经具备了块生成,散列计算,块校验这些基本能力,你可以参考 GitHub 上面各种成熟的区块链实现来完成工作量证明、权益证明这样的共识算法,或者是智能合约、Dapp、侧链等等。

 

原文地址:http://www.cnblogs.com/myzony/p/8478789.html

免责声明:文章转载自《C# 简单的区块链实现》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Redis学习笔记之延时队列数据库系列(五)之 mysql的伸缩性下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Mybatis之批量操作

首先批量操作的优点是:大大的提高查询的效率。 举个简单的例子:如果在程序中遍历来执行sql的话,这种情况就是有多少行数据就要执行多少条sql,这样导致的效率将是非常低。 如下可能需要40s insert into USER (name,age) values ('张三','33'); insert into USER (name,age) values (...

Docker中的Cgroup Driver:Cgroupfs 与 Systemd

在安装kubernetes的过程中,会出现 failed to create kubelet: misconfiguration: kubelet cgroup driver: "cgroupfs" is different from docker cgroup driver: "systemd" 文件驱动默认由systemd改成cgroupfs, 而我们...

2018年十大区块链投资机构盘点-千氪

  2018年,可以说是区块链经历了大起大落的一年,区块链从2017年的一波风口持续烧到了2018年,行业发展如火如荼。在市场大好的背景下,不仅诞生了许多专注于区块链领域的投资机构,同时也吸引了许多互联网的主流风险投资机构加入。   纵观区块链领域的投资机构,不少都是由早期数字货币投资者创立,也有一些从互联网风险投资跨界而来。一个行业的发展势必离不开资金...

freeswitch的拨号规则配置

当一个呼叫在ROUTING状态下达到命中拨号规则解析器时,相应的拨号规则就开始解析了。随着解析的进行,在xml文件中的符合条件的或标签中的指令形成一个指令表,安装到这个通道中。 你可以将拨号规则文件放到conf/dialplan/default下,这个目录下的拨号规则要比enum拨号规则优先处理。这个目录下的文件执行优先级是按其文件名开头的数字排序(由小到...

WPF整理-使用用户选择主题的颜色和字体

“Sometimes it's useful to use one of the selected colors or fonts the user has chosen in theWindows Control Panel Personalization applet (or the older Display Settings in Windows...

WPF实现无刷新动态切换多语言(国际化)

1. 在WPF中国际化使用的是 .xaml文件的格式       如图:Resource Dictionary (WPF)        2. 创建默认的语言文件和其他语言文件        这里以英语为默认语言,新建一个 Resource Dictionary (WPF)文件,并命名为DefaultLanguage.xaml,内容如下:    <R...