介绍
区块链是一项突破性的技术,它已经超越了最初在加密货币中的使用,并已成为安全、去中心化系统的重要组成部分。在本文中,我们将探讨区块链的基础知识,并使用 Go 编程语言动手演示其内部工作原理。
为什么区块链对开发人员很重要
作为这个时代的开发人员,了解区块链不再是可有可无的,而是一种战略优势。除了与数字货币的关联之外,区块链还是一种强大的工具,用于在各种应用程序中建立信任、透明度和安全性。无论您是经验丰富的开发人员还是好奇的新手,掌握区块链的基础知识都会为创新解决方案和新机会打开大门。
区块链技术的核心
从本质上讲,区块链是一种分布式账本,用于记录计算机网络上的交易。它的分散性质使它与众不同,消除了对中央权威的需要。想象一下一连串的区块,每个区块都包含一个交易列表,通过加密哈希链接在一起。这确保了数据的完整性,并使其几乎防篡改。
区块链的构成要素
了解关键组件至关重要:
块:数据存储的基本单位,包含事务列表。
交易:有关事件或操作的信息,例如资产转移。
加密哈希:一种为每个区块创建唯一标识符的安全方法,确保数据的完整性。
分散:没有中央管理机构,加强了安全性并促进了参与者的信任。
共识算法的作用
共识算法,如工作量证明或权益证明,在维护分布式账本的完整性方面发挥着至关重要的作用。这些机制确保网络中的所有节点都同意区块链的状态,从而降低欺诈或操纵的风险。
本文内容
以下部分将探讨如何使用 Go 编程语言实现区块链系统。Go 的简单性和效率使其成为理解区块链复杂性的绝佳选择,而不会造成不必要的复杂性。我们将介绍代码结构,讨论关键功能,并提供区块链的分步演示。
免责声明:本文帮助您了解区块、区块哈希、区块验证和区块完整性的基本概念。它不会教你成为一名区块链开发人员,但肯定是帮助你入门的好信息!
也就是说,让我们开始吧!
首先,你要设置你的环境,你应该在你的计算机上安装 GO,这样你就可以通过运行以下命令来为项目创建一个目录:
mkdir go-blockchain
这将为项目创建一个文件夹。接下来,我们进入该文件夹;
cd go-blockchain
并通过运行以下命令在包中启动一个新的 GO 项目:
go mod init github.com/{your-username}/go-blockchain
上面的命令将创建一个在项目目录中调用的文件。go.mod
完成后,在项目目录中创建一个文件。我们将在这个文件中完成所有的烹饪。main.go
我们将创造什么
我们将创建一个简单而强大的区块链系统。该区块链将作为跟踪图书结账交易的强大账本,展示去中心化和防篡改系统的运作方式。我们的创作将包含以下关键要素:
区块链结构: - 我们将定义一个结构化的区块链,由单独的区块组成,每个区块都包含有关图书结账的基本信息。这些区块将链接在一起,形成一个不可变的交易链。
区块组件: - 区块链中的每个区块都将封装结账数据、位置、时间戳、哈希值和前一个区块的哈希值。此结构可确保数据完整性、可追溯性和安全性,如下图所示:
Representation of Blocks Within a Blockchain
哈希机制: - 区块链将利用加密哈希,特别是 SHA-256,为每个区块生成唯一标识符。这种哈希机制保证了过去交易的不变性,并保护整个区块链免受未经授权的更改。
创世区块: - 我们将建立创世区块的概念,标志着我们区块链的开始。这个初始区块是后续交易的基础,并构成了整个账本的基础。
HTTP 处理程序: - 为了与我们的区块链进行交互,我们将实现 HTTP 处理程序以执行基本操作,例如创建新书籍、检索整个区块链以及向链中添加新区块。这种实用的方法使我们能够通过HTTP请求见证区块链。
实时显示: - 作为奖励,我们将实时显示区块链中现有区块,展示存储在每个区块中的信息,包括之前的哈希值、数据详细信息和区块的唯一哈希值。
让我们看看所有这些是如何在代码中组合在一起的。我们将从设置我们的区块和区块链结构开始。打开文件并创建以下结构:main.go
// Book represents the item to be checked outtype Book struct {
ID string `json:"id"`
Title string `json:"title"`
Author string `json:"author"`
PublishDate string `json:"publish_date"`
ISBN string `json:"isbn"`}// BookCheckout represents the data of a Book to be checked outtype BookCheckout struct {
BookID string `json:"book_id"`
User string `json:"user"`
CheckoutDate string `json:"checkout_date"`
IsGenesis bool `json:"is_genesis"`}// Block represents the individual block on the blockchain// it holds the checkout data, position, timestamp, hash, and prevhashtype Block struct {
Pos int
Data BookCheckout
TimeStamp string
Hash string
PrevHash string}// BlockChain represents the core blockchain implementationtype BlockChain struct {
blocks []*Block
}
Book是表示要签出的项的结构。
它包含诸如 、 、 、 和 等字段,每个字段都提供有关特定书籍的信息。IDTitleAuthorPublishDateISBN
标记指示用于正确序列化/反序列化的 JSON 字段名称。json:"..."
BookCheckout是一个结构体,表示要签出的书籍的数据。
它包括(图书的 ID)、(借出图书的人)、(借出日期)和(指示它是否是创世块的标志)等字段。BookIDUserCheckoutDateIsGenesis
Block是表示区块链中单个区块的结构。如上图所示,它包含仓位、交易的基础数据(在本例中,我们使用的是信息)、区块创建的时间戳、新区块的唯一哈希值,这也是基于前一个区块的哈希值BookCheckoutBlock
BlockChain是表示区块链核心实现的结构。它由指针 () 的切片组成,形成整个链。Blockblocks
太好了,现在我们有了我们的结构!让我们继续添加相关功能,我们将从创建新书开始,因为这是我们将要购买的项目。
但在此之前,我们需要设置我们的 HTTP 处理程序,因为我们将通过 HTTP 发送创建书籍的请求。您可以使用您选择的任何 HTTP 工具。对我来说,我会在 GO 中使用一个名为 BARF 的出色、易于使用的 HTTP 包。
若要安装 BARF,请导航到项目根目录,然后运行以下命令:
go get github.com/opensaucerer/barf
这会将包安装到您的项目中,您现在可以在文件的 main 函数中创建一个简单的 HTTP 服务器,如下所示:barf
func main() { // start barf server
if err := barf.Beck(); err != nil {
barf.Logger().Error(err.Error())
}
}
现在,如果您再次转到终端并运行,您应该会看到一些如下日志:go run main.go
现在我们的服务器正在运行,我们可以编写函数来创建一个新的 book 函数作为处理程序,并将其连接到服务器中的路由。
func newBook(w http.ResponseWriter, r *http.Request) { var book Book
err := barf.Request(r).Body().Format(&book) if err != nil {
barf.Logger().Error(err.Error())
barf.Response(w).Status(http.StatusInternalServerError).JSON(barf.Res{
Status: false,
Data: nil,
Message: "Error creating book",
})
}
h := md5.New()
io.WriteString(h, book.ISBN+book.PublishDate)
book.ID = hex.EncodeToString(h.Sum(nil))
barf.Response(w).Status(http.StatusOK).JSON(barf.Res{
Status: true,
Data: book,
Message: "New Book Created",
})
}
该函数在我们的区块链模拟中处理要交易的订单或项目。它通过网络收集要购买的书籍的信息,并将传入的数据解析到结构中。newBookBook
barf已经帮助转换了以下行: .
如果解析 JSON 时出现错误,我们会向用户返回错误响应。
也可以快速解决这个问题(看看 barf 有多快速和易于使用? err := barf.Request(r).Body().Format(&book)barf
否则,如果一切顺利,我们就会使用哈希算法生成书籍的 ISBN 和发布日期组合的哈希值,以生成 .md5book.ID
然后,我们只需将创建的书籍的解析返回给 JSON,并将结果作为对请求的响应返回。毋庸置疑,它也可以优雅地处理这个问题。barf
将其连接到路由,在启动服务器之前指定首选路由:barf
func main() {
barf.Post("/new", newBook) // start barf server
if err := barf.Beck(); err != nil {
barf.Logger().Error(err.Error())
}
}
完成此操作后,我们可以继续创建另一个将区块写入区块链的函数。
为了完成此过程,我们将再创建两个 HTTP 处理程序和一些帮助程序函数。
考虑到区块链的工作原理,我们需要创建单独的区块来保存系统中的敏感信息。在这个系统中,敏感数据是图书结账信息。
因此,让我们编写函数来创建一个新块:
func CreateBlock(prevBlock *Block, data BookCheckout) *Block { // function to create a new block
block := &Block{} if data.IsGenesis {
block.Pos = 0
} else {
block.Pos = prevBlock.Pos + 1
}
block.TimeStamp = time.Now().String()
block.Data = data
block.PrevHash = prevBlock.Hash
block.generateHash() return block
}
此函数接收先前的区块信息和我们打算为其创建新区块的新数据。请记住,在我们对区块链项目的分解中,我们强调我们将考虑创建创世区块。CreateBlock
函数中的条件检查检出数据是否为创世。如果是这样,它会分配初始位置 0,否则,它只会增加位置计数。
Timestamp根据区块链技术的规则,在创建区块方面非常重要。我们必须知道每个块的创建时间。
每个区块还保留前一个哈希值,即创建“链”的点,并且包含前一个哈希值(或父哈希值)也会在区块链上强制执行完整性。
这是让我对整个区块链技术着迷的部分。
每个区块都指向它前面的区块的哈希值,形成一系列相互连接的区块。它在块之间创建依赖关系,因此更改一个块中的数据需要更改哈希值,从而影响后续块。
这种相互依赖性使得在不改变所有后续区块的情况下篡改单个区块在计算上不可行,从而提供了一种维护整个区块链完整性的机制。是不是很漂亮?
好的,紧随其后的下一行是新创建的区块哈希值的生成。
是块结构体的方法,让我们在块结构体下声明该方法:generateHash()
type Block struct {
Pos int
Data BookCheckout
TimeStamp string
Hash string
PrevHash string}func (b *Block) generateHash() { // method to generate block hash based on the checkout data sttacched to the block
bytes, _ := json.Marshal(b.Data)
data := string(rune(b.Pos)) + b.TimeStamp + string(bytes) + b.PrevHash
hash := sha256.New()
b.Hash = hex.EncodeToString(hash.Sum([]byte(data)))
}
该方法获取关联的块并生成其哈希值。
它将整个区块结账数据转换为字节,并将其字符串等效值与位置、时间戳和前一个哈希连接起来,以生成哈希算法的输入。
在区块链中,标准哈希算法是SHA256。这就是为什么我们有这样的行:启动 sha256 算法,最后计算数据的哈希值,将结果转换为字符串,并将其分配给 .generateHash()hash := sha256.New()b.Hash
在创建新区块的过程中,许多哈希过程都依赖于结账数据。那么,让我们看看我们如何创建结帐数据。
为此,我们需要第二个 HTTP 处理程序来收集结帐数据:
func writeBlock(w http.ResponseWriter, r *http.Request) { var bookCheckout BookCheckout
err := barf.Request(r).Body().Format(&bookCheckout) if err != nil {
barf.Logger().Error(err.Error())
barf.Response(w).Status(http.StatusInternalServerError).JSON(barf.Res{
Status: false,
Data: nil,
Message: "Error creating book checkout",
})
}
resp, err := json.MarshalIndent(bookCheckout, "", " ") if err != nil {
barf.Logger().Error(err.Error())
barf.Response(w).Status(http.StatusInternalServerError).JSON(barf.Res{
Status: false,
Data: nil,
Message: "Error creating book checkout",
})
}
barf.Response(w).Status(http.StatusOK).JSON(barf.Res{
Status: true,
Data: string(resp),
Message: "New Block Created",
})
}
这个函数非常简单,我们只是收集图书结账信息,将其解析为结构,然后返回结果作为对请求的响应。
将其添加到如下所示的路由中:
func main() {
barf.Post("/", writeBlock)
barf.Post("/new", newBook) // start barf server
if err := barf.Beck(); err != nil {
barf.Logger().Error(err.Error())
}
}
擎!该函数已命名,但我们在这里没有看到任何正在创建或写入的块。这是故意的。
我们已经编写了收集要存储在单个区块中的数据的函数,但是,在我们验证区块并将其添加到区块链中的区块列表中之前,将区块写入区块链是不完整的。writeBlock()
由于我们有一个带有块数组的结构体,让我们实现一个函数,将一个新块作为结构体上的方法添加到链中:BlockChainBlockChain
type BlockChain struct {
blocks []*Block
}func (bc *BlockChain) AddBlock(data BookCheckout) { // method to add a block to the array of block chains
lastBlock := bc.blocks[len(bc.blocks)-1]
block := CreateBlock(lastBlock, data) if validBlock(block, lastBlock) {
bc.blocks = append(bc.blocks, block)
}
}
该方法使用该函数根据提供的(实例)和最后一个块的信息生成一个新块 ()。
然后,它使用该函数来检查新块是否对前一个块 () 有效。我们稍后会写出来。AddBlock()CreateBlockblockdataBookCheckoutvalidBlocklastBlock
最后,如果新区块有效,则将其附加到区块链中的区块数组中()。bc.blocks = append(bc.blocks, block)bc.blocks
我们如何知道一个区块是否有效?此验证通常包括检查哈希值是否匹配以及位置是否正确递增:
func validBlock(block, prevBlock *Block) bool { if prevBlock.Hash != block.PrevHash { return false
} if !block.validateHash(block.Hash) { return false
} if prevBlock.Pos+1 != block.Pos { return false
} return true}
如上面的代码片段所示,我们需要在块结构上使用另一种方法来验证生成的 Hash:
func (b *Block) validateHash(hash string) bool {
b.generateHash() return b.Hash == hash
}
此方法再次重新计算给定块的哈希值,并将其与提供的 has 进行比较,并返回相等作为其结果。简单而时尚!
伟大!我们现在可以创建、验证和添加新区块到我们的区块链中。但是,请注意,我们尚未在任何地方调用该函数。我们需要仔细考虑我们打算如何管理区块链阵列的状态。AddBlock
我们什么时候启动一个新的区块链?它将在哪里更新?
新的区块链应该在main功能之前启动,原因有两个。首先,我们需要在文件的某个地方提供基本的区块链“对象”,使其全局可用于每个区块/函数。
广告 我们还需要确保每次我们向服务器发起另一个请求时,这个区块链都不会被重新实例化。我们只需要每个服务器实例一个链!
所以,让我们把它放在结构定义的正下方,如下所示:
type BlockChain struct {
blocks []*Block
}func (bc *BlockChain) AddBlock(data BookCheckout) { // method to add a block to the array of block chains
lastBlock := bc.blocks[len(bc.blocks)-1]
block := CreateBlock(lastBlock, data) if validBlock(block, lastBlock) {
bc.blocks = append(bc.blocks, block)
}
}var MyBlockChain *BlockChain
好!现在我们可以在这个已经初始化的区块链上调用该方法。我们将在处理程序中调用 AddBlock 方法,在我们成功解析检出数据后,我们可以将函数更新为:AddBLockwriteBlockwriteBlock
func writeBlock(w http.ResponseWriter, r *http.Request) { var bookCheckout BookCheckout
err := barf.Request(r).Body().Format(&bookCheckout) if err != nil {
barf.Logger().Error(err.Error())
barf.Response(w).Status(http.StatusInternalServerError).JSON(barf.Res{
Status: false,
Data: nil,
Message: "Error creating book checkout",
})
} // Add block here
MyBlockChain.AddBlock(bookCheckout)
resp, err := json.MarshalIndent(bookCheckout, "", " ") if err != nil {
barf.Logger().Error(err.Error())
barf.Response(w).Status(http.StatusInternalServerError).JSON(barf.Res{
Status: false,
Data: nil,
Message: "Error creating book checkout",
})
}
barf.Response(w).Status(http.StatusOK).JSON(barf.Res{
Status: true,
Data: string(resp),
Message: "New Block Created",
})
}
现在,我们已经完成了发起交易、创建区块、验证区块以及最终将该区块添加到链中的过程。
To verify that our results are accurate and valid, we should write the last HTTP handler that traverses the blocks within the blockchain and returns their content as a JSON response:
COPY
COPY
func getBlockChain(w http.ResponseWriter, r *http.Request) {
jbytes, err := json.MarshalIndent(MyBlockChain.blocks, "", " ") if err != nil {
barf.Logger().Error(err.Error())
barf.Response(w).Status(http.StatusInternalServerError).JSON(barf.Res{
Status: false,
Data: nil,
Message: "Error getting blocks from chain",
}) return
}
barf.Response(w).Status(http.StatusOK).JSON(barf.Res{
Status: true,
Data: string(jbytes),
Message: "Error getting blocks from chain",
})
}
并将其连接到函数中的路由:main()
func main() {
barf.Get("/", getBlockChain)
barf.Post("/", writeBlock)
barf.Post("/new", newBook) // start barf server
if err := barf.Beck(); err != nil {
barf.Logger().Error(err.Error())
}
}
就是这样!我们刚刚按照标准的区块链程序构建了一个完整的功能区块链实现,以强制交易或敏感对象的信任和完整性。
为了测试一切检查是否正确,这里有一个测试文件来验证我们程序的功能和行为:
package mainimport ( "net/http"
"net/http/httptest"
"strings"
"testing")func TestCreateBlock(t *testing.T) {
prevBlock := &Block{
Pos: 1,
TimeStamp: "2022-01-01",
Hash: "prevHash",
}
data := BookCheckout{
BookID: "123",
User: "John Doe",
CheckoutDate: "2022-01-02",
IsGenesis: false,
}
block := CreateBlock(prevBlock, data) // Add your assertions based on the expected behavior of CreateBlock
if block.Pos != prevBlock.Pos+1 {
t.Errorf("Expected Pos to be %d, got %d", prevBlock.Pos+1, block.Pos)
}
}func TestAddBlock(t *testing.T) { // Initialize a sample blockchain
blockchain := NewBlockChain() // Create a sample book checkout data
bookData := BookCheckout{
BookID: "456",
User: "Jane Doe",
CheckoutDate: "2022-01-03",
IsGenesis: false,
} // Add a block to the blockchain
blockchain.AddBlock(bookData) // Add your assertions based on the expected behavior of AddBlock
if len(blockchain.blocks) != 2 {
t.Errorf("Expected blockchain length to be 2, got %d", len(blockchain.blocks))
}
}func TestHTTPHandlers(t *testing.T) { // Create a sample HTTP request for the newBook handler
reqBody := `{"isbn": "123456789", "publish_date": "2022-01-01", "title": "Sample Book", "author": "John Doe"}`
req, err := http.NewRequest("POST", "/new", strings.NewReader(reqBody)) if err != nil {
t.Fatal(err)
} // Create a sample response recorder
rr := httptest.NewRecorder() // Call the newBook handler
newBook(rr, req) // Check the response status code
if status := rr.Code; status != http.StatusOK {
t.Errorf("Handler returned wrong status code: got %v want %v", status, http.StatusOK)
}
}func TestGenesisBlock(t *testing.T) { // Create a Genesis block
genesisBlock := GenesisBlock() // Ensure that the Genesis block has the expected properties
if genesisBlock.Pos != 0 {
t.Errorf("Expected Genesis block Pos to be 1, got %d", genesisBlock.Pos)
}
}func TestHashGeneration(t *testing.T) { // Create a sample block
block := &Block{
Pos: 1,
TimeStamp: "2022-01-01",
PrevHash: "prevHash",
Data: BookCheckout{
BookID: "123",
User: "John Doe",
CheckoutDate: "2022-01-02",
IsGenesis: false,
},
} // Generate the hash for the block
block.generateHash() // Validate that the block's hash is not empty
if block.Hash == "" {
t.Error("Expected non-empty hash, got an empty string")
}
}func TestBlockchainStartsWithGenesisBlock(t *testing.T) { // Create a new blockchain
blockchain := NewBlockChain() // Ensure that the blockchain starts with the Genesis block
if len(blockchain.blocks) != 1 || blockchain.blocks[0].Pos != 0 {
t.Error("Blockchain should start with the Genesis block")
}
}
我们已经展示了区块链作为一种技术如何应用于简单的图书购买商店。这个项目可能无法部署为一个全新的区块链,它将改变世界,但有了这个,你现在可以亲身了解该技术是如何在引擎盖下工作的!