200 行代碼使用 C# 實現區塊鏈


文章原文來自:Code your own blockchain in less than 200 lines of Go!,原始文章是通過 Go 語言來實現自己的區塊鏈的,這里我們參照該文章來使用 C# + Asp.Net Core 實現自己的區塊鏈。在這里我也參考了 這篇譯文

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、側鏈等等。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM