文章原文來自: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 值,這樣就以正確的塊順序構建出鏈:
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;
}
除開區塊校驗的問題之外,如果有兩個節點被分別添加到各自的區塊鏈上,我們應該始終以最長的那一條為主線,因為最長的那一條意味着他的區塊數據始終是最新的。
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 來構建一個創世塊:
然后我們嘗試多添加幾個之后,訪問 BlockChain 來查看已經存在的區塊鏈結構:
8.結語
通過以上代碼我們完成了一個簡陋的區塊鏈,雖然十分簡陋,但是已經具備了塊生成,散列計算,塊校驗這些基本能力,你可以參考 GitHub 上面各種成熟的區塊鏈實現來完成工作量證明、權益證明這樣的共識算法,或者是智能合約、Dapp、側鏈等等。