Blockchain bằng ngôn ngữ Go - 1st Step: Block và Blockchain sơ khai
Trong các bài viết trước trên blog của Vietnam Lab Center, đã có khá nhiều bài viết giới thiệu về blockchain và tiền ảo. Lần này với mục đích hiểu thêm về blockchain và đồng thời học thêm một ngôn ngữ mới là Go, người viết quyết định dịch (có biến tấu) một series bài viết về chủ đề lập trình Blockchain bằng ngôn ngữ Go và giới thiệu trên blog Vietnam Lab. Phần đầu tiêu của loạt bài viết sẽ giới thiệu qua về ngôn ngữ Go, đồng thời khởi tạo code ban đầu cho dự án. Link cho các phần sau cũng sẽ được update ở đầu mỗi bài viết
Các khái niệm cơ bản về blockchain
Để có thể hiểu nội dung bài viết một cách tốt nhất, người đọc nên có những hiểu biết cơ bản về blockchain. Điều đấy có thể dễ dàng thu được qua các bài viết trên chính blog của Vietnam Lab:
- Vì sao blockchain được tin tưởng hay lý thuyết trò chơi và blockchain? (Phần 1)
- Vì sao blockchain được tin tưởng hay lý thuyết trò chơi và blockchain? (Phần 2)
- ...
Giới thiệu về ngôn ngữ Go
Go là một ngôn ngữ lập trình được ra đời tại Google vào năm 2009 với các tác giả là những cây đa, cây đề trong giới lập trình như Rob Pike hay Ken Thompson.
Go ra đời với mục đích trước nhất là đáp ứng yêu cầu của Google, một ngôn ngữ có tốc độ cao, dễ sử dụng, không quá phức tạp, thích hợp cho việc lập trình hệ thống ở mức không quá low-level (Java, C++ quá phức tạp, Python dễ dùng nhưng lại chậm, C tuy syntax đơn giản, nhưng khó dùng và quá low-level).
Với tác giả là những người làm ở Bell Labs và có đóng góp rất lớn cho hệ điều hành Unix, nên Go chịu ảnh hưởng rất lớn từ C về syntax với những cải tiến để khiến code dễ đọc hơn. Tuy nhiên khác với C, Go sử dụng garbage collection thay vì việc quản lý memory thủ công và do vậy dễ sử dụng hơn nhưng tốc độ cũng chậm hơn. Go cũng là một ngôn ngữ static giống như Java, C++ nhưng đơn giản hơn nhiều (không hỗ trợ class, inheritance hay generic). Go cũng hỗ trợ rất tốt cho việc lập trình đa luồng hay networking.
Bạn đọc có thể tìm hiểu về ngôn ngữ Go tại trang chủ của ngôn ngữ này: https://golang.org/. Đồng thời sử dụng thử ngay trên web tại trang: https://tour.golang.org
Blockchain bằng Go - 1st step
1. Khởi tạo dự án Go
Sau khi đã cài đặt Go và thiết lập GOPATH
như hướng dẫn cài đặt trên trang chủ của Go, ta cài đặt công cụ dep, công cụ quản lý dependencies của Go.
$ curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
Sau đó ta sử dụng dep
để tạo mới một dự án Go:
$ mkdir $GOPATH/src/gochain
$ cd $GOPATH/src/gochain
$ dep init
Trong folder gochain
, ta sẽ có các file and folders như sau:
gochain
|- vendor/
|- Gopkg.lock
|- Gopkg.toml
Những file và folder trên sẽ phục vụ cho việc quản lý dependencies về sau. Ta sẽ không dùng đến chúng trong bài viết này
2. Những dòng code đầu tiên cho Block
Rõ ràng với blockchain, block đóng vai trò quan trọng nhất và cũng là thứ đầu tiên ta cần code. Trước hết, ta tạo file block.go
và taọ cấu trúc dữ liệu cho block như sau
// block.go
package main
import (
"bytes"
"time"
"strconv"
"crypto/sha256"
)
// A Block in the chain
type Block struct {
Timestamp int64
Data []byte
PrevBlockHash []byte
Hash []byte
}
Các bạn nào đã quen thuộc với C, thì struct
trong Go cũng có ý nghĩa tương tự như struct
trong C vậy. Nó là một cấu trúc dữ liệu với các trường được định nghĩa sẵn. Ở đây, block là một struct
với các trường timestamp, data của block này, hash của block trước và hash của block này. Tất nhiên, block trong blockchain của bitcoin hay các đồng tiền ảo khác sẽ phức tạp hơn và chứa nhiều thông tin hơn rất nhiều.
Tiếp theo, ta sẽ add thêm hàm để tính hash cho data của block hiện tại:
// block.go
func (b *Block) SetHash() {
timestamp := []byte(strconv.FormatInt(b.Timestamp, 10))
headers := bytes.Join([][]byte{b.PrevBlockHash, b.Data, timestamp}, []byte{})
hash := sha256.Sum256(headers)
b.Hash = hash[:]
}
- Đây là một method với tên
SetHash
được gọi trên một pointer đến một structBlock
. Pointer trong Go cũng giống như trong C là một con trỏ chỉ đến địa chỉ memory chứa giá trị của biến số. Vì hàm trong Go cũng như C mang tính chất copy by value nên khi truyền tham số vào trong hàm, biến số được sử dụng sẽ là bản copy có cùng giá trị của tham số chứ không phải là bản thân tham số đấy. Dù ở đâySetHash
là một method được gọi trên structBlock
nhưng nguyên tắc này vẫn không thay đổi. Nếu muốn thay đổi gía trị củastruct
này, ta cần gọi method trên một pointer đếnstruct
đó. - Nội dung code của method này thì cũng khá dễ hiểu, ai nếu đọc qua về cách tạo hash 1 block của blockchain đều sẽ có thể hiểu được: Lấy timestamp hiện tại, join lần lượt với hash của block trước, data của block này, sau đó dùng hàm
sha256
để tính hash.
Đã có method để tính hash cho block, tiếp theo sẽ là function để tạo một block mới:
// block.go
func NewBlock(data string, prevBlockHash []byte) *Block {
block := &Block{time.Now().Unix(), []byte(data), prevBlockHash, []byte{}}
block.SetHash()
return block
}
- Function
SetHash
ở trên được gọi là method vì nó được gọi trên một đối tượng khác (struct
), cònNewBlock
ở đây là một hàm bình thường trong Go với 2 tham sốdata
vàprevBlockHash
và giá trị trả về là một pointer đếnBlock
. - Nội dung hàm cũng khá đơn giản: Khởi tạo một block mới, lấy address trên memory của nó gán cho một biến pointer rồi dùng hàm
SetHash
trên biến này để tính hash của block mới.
3. Blockchain sơ khai
Blockchain nếu hiểu đơn giản là chain của nhưng block, và ta cũng sẽ lập trình ban đầu nó đơn giản như vậy. Tạo file blockchain.go
với nội dung như sau:
// blockchain.go
package main
type Blockchain struct {
blocks []*Block
}
Blockchain
ở đây chỉ đơn giản là mộtstruct
với một trườngblocks
là mộtslice
các pointer Block.slice
trong Go tương tự nhưarray
, nhưng không cần phải cố định độ lớn khi khai báo (slice
vàarray
có mối liên hệ mật thiết, bạn đọc có thể tìm hiểu thêm)
Tiếp đến là hàm khởi tạo blockchain và hàm tạo block Genesis (block đầu tiên trong blockchain):
// blockchain.go
func NewGenesisBlock() *Block {
return NewBlock("Genesis Block", []byte{})
}
func NewBlockchain() *Blockchain {
return &Blockchain{[]*Block{NewGenesisBlock()}}
}
-
Hàm
NewGenesisBlock
để tạo block đầu tiên khá đơn giản, chỉ là gọi đến hàmNewBlock
trong fileblock.go
với tham số: data của block đầu tiên và hash của block trước là rỗng. Vì 2 file code đều sử dụng chung packagemain
nên có thể gọi thoải mái hàm và biến số public giữa các file -
Hàm
NewBlockchain
để tạo 1 blockchain mới thì chỉ là tạo mộtstruct
Blockchain
với trườngblocks
chỉ có Genesis block. Sau đó thì trả về địa chỉ trên memory củastruct
này.
Cuối cùng chỉ cần thêm hàm với tính năng add một block mới vào blockchain là ta đã có một blockchain tối giản:
// blockchain.go
// AddBlock add new block
func (bc *Blockchain) AddBlock(data string) {
prevBlock := bc.blocks[len(bc.blocks)-1]
newBlock := NewBlock(data, prevBlock.Hash)
bc.blocks = append(bc.blocks, newBlock)
}
- Đây cũng là một method gọi trên pointer của một
struct
Blockchain
. Nội dung hàm thì sẽ lấy hash của block cuối cùng trong blockchain hiện tại; dùng hàmNewBlock
để khởi tạo block mới với data được truyền vào từ tham số. Sau đó thêm block mới vào cuối chuỗi các block hiện tại.
4.Test thử một blockchain tối giản.
Vậy là ta bước đầu đã code được prototype của một blockchain tối giản. Và đây là đoạn code để ta test thử thành quả:
// main.go
package main
import (
"fmt"
)
func main() {
bc := NewBlockchain()
bc.AddBlock("Linh Dep Trai")
bc.AddBlock("Linh Dep Trai x2")
for _, block := range bc.blocks {
fmt.Printf("Prev block's hash: %x\n", block.PrevBlockHash)
fmt.Printf("Current block's hash: %x\n", block.Hash)
fmt.Printf("Current block's data: %x\n", block.Data)
fmt.Println()
}
}
Trong đoạn code trên, ta tạo một blockchain, thêm 1 vài block mới sau đó loop qua từng block trong chain và in các thống tin của block đó.
Build và chạy thử code test:
$ cd $GOPATH/src/gochain
$ go build
$ ./gochain
Prev block's hash:
Current block's hash: b8f50cc397ff08c3417df8408db3ee9073c7a17652fd4c0ba4868b3c3eb7b228
Current block's data: Genesis Block
Prev block's hash: b8f50cc397ff08c3417df8408db3ee9073c7a17652fd4c0ba4868b3c3eb7b228
Current block's hash: 6f3e44b7a8f203513828e2a49119186a0cd66f922290a65f56c0def228a6b28b
Current block's data: Linh Dep Trai
Prev block's hash: 6f3e44b7a8f203513828e2a49119186a0cd66f922290a65f56c0def228a6b28b
Current block's hash: 66719538f9e1ed99d44ac4a3f31a0fff95452a4519de4f9ab615155f3c515193
Current block's data: Linh Dep Trai x2
Kết bài
Vậy là sử dụng những tính năng cơ bản của ngôn ngữ Go, ta đã có thể code được một blockchain với tính năng tối giản nhất có thể. Phần tiếp theo ta sẽ thêm một tính năng quan trọng tương tự như blockchain của Bitcoin: Proof of Work