- vừa được xem lúc

Xây dựng Blockchain từ đầu với Go (Phần 3) - Monolithic Event vs Transaction

0 0 27

Người đăng: Lê Quốc Bình

Theo Viblo Asia

Giới thiệu

Ở phần trước, chúng ta đã tìm hiểu cơ bản về Transaction & DB State. Bạn nào chưa tìm hiểu thì có thể xem lại nhé.

Github: https://github.com/lequocbinh04/the-simple-blockchain

Tiếp tục câu chuyện

Tuesday evening, March 26.

Vào một buổi tối lạnh lẽo khi John đang vui vẻ xã stress với tựa game yêu thích của mình, John vô tình đã gõ nhầm rm -rf /., mọi thứ trên máy tính đã biến mất sạch sẽ, kể cả genesis.jsonstate.json cũng đã bị bay màu.

John, một senior developer, anh ấy vô cùng bực tức và chửi phụ huynh của chiếc máy tính, nhưng anh ấy ko hoảng sợ khi không có backup cho máy tính của mình, vì anh ấy đã có một thứ tốt hơn: mảnh giấy ghi lại toàn bộ giao dịch. Bây giờ việc của anh ấy cần làm là thực hiện lại những transactions và db của anh ấy sẽ được khôi phục.

Anh ấy vô cùng thích thú với phương án này, nên anh ấy quyết định sẽ phát triển MVP database của mình: Với mỗi hoạt động của quán bar (VD: thanh toán tiền nước), nó phải được ghi lại bên trong cơ sở dữ liệu của blockchain.

Mỗi khách hàng được ghi trong DB là một kiểu dữ liệu Account:

type Account string

Mỗi Transaction (TX - sự thay đổi dữ liệu trong DB) sẽ có 4 attributes: from, to, value và data. Thuộc tính data là một giá trị là data của tx đó (vd: "reward" biểu thị cho transaction đó là phần thưởng cho John vì đã phát minh ra TBB và làm tăng tổng cung ban đầu của TBB).

type Tx struct { From Account `json:"from"` To Account `json:"to"` Value uint `json:"value"` Data string `json:"data"`
}
func (t Tx) IsReward() bool { return t.Data == "reward"
}

Genesis DB vẫn là một file json:

{ "genesis_time": "2019-03-18T00:00:00.000000000Z", "chain_id": "the-blockchain-bar-ledger", "balances": { "john": 1000000 }
}

Và mọi giao dịch, trước kia được viết vào giấy, giờ anh ấy đã viết vào file tx.db (mỗi dòng 1 file json là chi tiết của tx)

{"from":"john","to":"john","value":3,"data":""}
{"from":"john","to":"john","value":700,"data":"reward"}
{"from":"john","to":"jack","value":2000,"data":""}
{"from":"john","to":"john","value":100,"data":"reward"}
{"from":"jack","to":"john","value":1,"data":""}

Và phần quan trọng nhất, chứa toàn bộ business login sẽ là State

type State struct { Balances map[Account]uint txMempool []Tx dbFile *os.File
}

State sẽ lưu lại toàn bộ số dư của người dùng, lịch sử giao dịch, ai chuyển TBB token cho ai. State bắt đầu bằng cách đọc dữ liệu init từ genesis.json

func NewStateFromDisk() (*State, error) { // get current working directory cwd, err := os.Getwd() if err != nil { return nil, err } genFilePath := filepath.Join(cwd, "database", "genesis.json") gen, err := loadGenesis(genFilePath) if err != nil { return nil, err } balances := make(map[Account]uint) for account, balance := range gen.Balances { balances[account] = balance }

Sau khi load thành công số dư ban đầu của người dùng từ genesis.json chúng ta sẽ tuần tự thực hiện lại toàn bộ giao dịch trong tx.db

 txDbFilePath := filepath.Join(cwd, "database", "tx.db") f, err := os.OpenFile(txDbFilePath, os.O_APPEND|os.O_RDWR, 0600) if err != nil { return nil, err } scanner := bufio.NewScanner(f) state := &State{balances, make([]Tx, 0), f} // Iterate over each the tx.db file's line for scanner.Scan() { if err := scanner.Err(); err != nil { return nil, err } // Convert JSON encoded TX into an object (struct) var tx Tx json.Unmarshal(scanner.Bytes(), &tx) // Rebuild the state (user balances), // as a series of events if err := state.apply(tx); err != nil { return nil, err } } return state, nil
}

State component chịu trách nhiệm:

  • Thêm mới một transaction vào Mempool
  • Xác thực transaction (check số dư người dùng)
  • Thay đổi state
  • Persist state vào file
  • Tính toán lại toàn bộ giao dịch bắt đầu từ genesis

Thêm mới một transaction vào Mempool:

func (s *State) Add(tx Tx) error { if err := s.apply(tx); err != nil { return err } s.txMempool = append(s.txMempool, tx) return nil
}

Persist state vào file

func (s *State) Persist() error { // Make a copy of mempool because the s.txMempool will be modified // in the loop below mempool := make([]Tx, len(s.txMempool)) copy(mempool, s.txMempool) for i := 0; i < len(mempool); i++ { txJson, err := json.Marshal(mempool[i]) if err != nil { return err } if _, err = s.dbFile.Write(append(txJson, '\n')); err != nil { return err } // Remove the TX written to a file from the mempool s.txMempool = s.txMempool[1:] } return nil
}

Xác thực transaction (check số dư người dùng)

func (s *State) apply(tx Tx) error { if tx.IsReward() { s.Balances[tx.To] += tx.Value return nil } if tx.Value > s.Balances[tx.From] { return fmt.Errorf("insufficient balance") } s.Balances[tx.From] -= tx.Value s.Balances[tx.To] += tx.Value return nil
}

Build một Command-Line-Interface (CLI)

Tuesday evening, March 26

John muốn có một cách để thêm transaction vào DB dễ dàng hơn và dễ dàng lấy ra số dư của user trên hệ thống. Vì go có thể build ra binary file nên John quyết định dựng một CLI để dễ dàng thao tác.

Cách dễ dàng để build 1 CLI với go là sử dụng thư viện github.com/spf13/cobra. Tiếp theo anh ấy tạo mới file cmd/tbb/main.go

package main
import ( "github.com/spf13/cobra" "os" "fmt"
)
func main() { var tbbCmd = &cobra.Command{ Use: "tbb", Short: "The Blockchain Bar CLI", Run: func(cmd *cobra.Command, args []string) { }, } err := tbbCmd.Execute() if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) }
}

Sau đó anh ấy tiến hành cài đặt CLI

go install ./cmd/tbb/...
go: finding github.com/spf13/cobra v1.0.0
go: downloading github.com/spf13/cobra v1.0.0
go: extracting github.com/spf13/cobra v1.0.0

Go sẽ tự động cài nó vào $GOPATH/bin và bạn có thể chạy trực tiếp lệnh tbb ở terminal, nhưng tất nhiên là hiện tại sẽ chưa có gì cả do hàm run ở file main.go đang rỗng.

Đầu tiên John sẽ thực hiện tạo version cho nó, cùng thư mục với main.go ta tạo version.go

package main
import ( "fmt" "github.com/spf13/cobra"
)
const Major = "0"
const Minor = "1"
const Fix = "0"
const Verbal = "TX Add && Balances List"
var versionCmd = &cobra.Command{ Use: "version", Short: "Describes version.", Run: func(cmd *cobra.Command, args []string) { fmt.Printf("Version: %s.%s.%s-beta %s", Major, Minor, Fix, Verbal) },
}

Compile và run

go install ./cmd/tbb/...
tbb version
> Version: 0.1.0-beta TX Add && Balances List

Nuột!

Tương tự anh ấy tạo file balances.go

func balancesCmd() *cobra.Command { var balancesCmd = &cobra.Command{ Use: "balances", Short: "Interact with balances (list...).", PreRunE: func(cmd *cobra.Command, args []string) error { return incorrectUsageErr() }, Run: func(cmd *cobra.Command, args []string) { }, } balancesCmd.AddCommand(balancesListCmd) return balancesCmd
}

Lệnh balance sẽ in số dư của user ra màn hình

var balancesListCmd = &cobra.Command{ Use: "list", Short: "Lists all balances.", Run: func(cmd *cobra.Command, args []string) { state, err := database.NewStateFromDisk() if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } defer state.Close() fmt.Println("Accounts balances:") fmt.Println("__________________") fmt.Println("") for account, balance := range state.Balances { fmt.Println(fmt.Sprintf("%s: %d", account, balance)) } },
}

Tiếp theo John test lệnh balance

go install ./cmd/tbb/... tbb balances list
> john: 1000000

Ngon lành! tiếp theo John sẽ làm lệnh để tương tác với transaction, anh ấy tạo tiếp file ./cmd/tbb/tx.go

func txCmd() *cobra.Command { var txsCmd = &cobra.Command{ Use: "tx", Short: "Interact with txs (add...).", PreRunE: func(cmd *cobra.Command, args []string) error { return incorrectUsageErr() }, Run: func(cmd *cobra.Command, args []string) { }, } txsCmd.AddCommand(txAddCmd()) return txsCmd
}

tbb tx add để thêm mới một tx vào db

func txAddCmd() *cobra.Command { var cmd = &cobra.Command{ Use: "add", Short: "Adds new TX to database.", Run: func(cmd *cobra.Command, args []string) { from, _ := cmd.Flags().GetString(flagFrom) to, _ := cmd.Flags().GetString(flagTo) value, _ := cmd.Flags().GetUint(flagValue) data, _ := cmd.Flags().GetString(flagData) fromAcc := database.NewAccount(from) toAcc := database.NewAccount(to) tx := database.NewTx(fromAcc, toAcc, value, data) state, err := database.NewStateFromDisk() if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } // defer means, at the end of this function execution, // execute the following statement // (close DB file with all TXs) defer state.Close() // Add the TX to an in-memory array (pool) err = state.Add(tx) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } // Flush the mempool TXs to disk err = state.Persist() if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } fmt.Println("TX successfully added to the ledger.") }, }

Lệnh tbb tx add sẽ có 3 flag bắt buộc--from, --to--value. CLI đã hoàn thiện!

John lần lượt thực hiện add những tx anh ấy đã ghi trên tờ giấy

tbb tx add --from=john --to=john --value=3
tbb tx add --from=john --to=john --value=700 --data=reward
tbb tx add --from=john --to=jack --value=2000
tbb tx add --from=john --to=john --value=100 --data=reward
tbb tx add --from=jack --to=john --value=1

Get balance

tbb balances list
> Accounts balances:
__________________
john: 998801
jack: 1999

Dữ liệu đã được ghi thành công!

Mempool

Mempool trong series này được thiết kế tác giả một cách rất rất đơn giản, nhằm mục đích giáo dục. Để biết rõ hơn về mempool trong các dự án thực tế các bạn có thể tự mình tìm hiểu tại code của go-ethereum

Tổng kết

[🔑] Block chain là một cơ sở dữ liệu.

Tổng cung của token, số dư ban đầu của user, và các setting của blockchain sẽ được đặt ở Genesis file. Số dư, trạng thái ban đầu của blockchain được ghi ở genesis file là không bao giờ thay đổi.

Các thay đổi lên cơ sở dữ liệu gọi là giao dịch (Transaction | TX). Giao dịch là các sự kiện cho các hành động trong hệ thống

Bình luận

Bài viết tương tự

- vừa được xem lúc

[Blockchain] Road to Bitcoin

. Chắc mọi người hẳn đã không còn xa lạ gì với anh chàng tỷ phú đã ném vỡ cửa kính ô tô nhà mình cùng với siêu năng lực điều khiển vật giá chỉ bằng lời nói, người đã đẩy định giá Bitcoin trên thị trường vượt ngưỡng 50K dolar/coin với những bài twitter để đời . .

0 0 64

- vừa được xem lúc

Khi Ethereum có chi phí giao dịch quá đắt đỏ - Tương lai cho layer2 ?

Với sự phát triển như vũ bão của Blockchain, ETH dường như đang quá tải và hệ quả là chi phí Gas đã lên đến 1000Gwei, phí để tạo những transaction phức tạp đã xấp xỉ 500$ . Và một giải pháp cứu cánh cho các sản phẩm Defi trên ETH chính là Layer2, và trong nhiệm vụ lần này Matic đang thể hiện khả năn

0 0 90

- vừa được xem lúc

Blockchain với Java - Tại sao không?

Cuộc cách mạng công nghiệp 4.0 ra đời kéo theo nhiều sự thay đổi và xu hướng mới được hình thành. Riêng đối với lĩnh vực CNTT cũng không nằm ngoài vùng ảnh hưởng mạnh mẽ. Chính làn sóng 4.

0 0 95

- vừa được xem lúc

Phân loại và tầm quan trọng của các node trong mạng blockchain

Trước khi đi vào phân loại và nêu rõ được tầm quan trọng của các node trọng mạng blockchain thì mình xin được trích dẫn khái niệm về blockchain từ Wikipedia như sau:. .

0 1 66

- vừa được xem lúc

Code Smart Contract bằng Assembly ?

Introduction. Hồi còn học trong ghế nhà trường bộ môn lập trình tốn nhiều não nhất của mình là code assembly. Nôm na thì bất cứ ngôn ngữ bậc cao nào như C , Go, Java,... được sinh ra để người dễ hiểu và dễ code , tuy nhiên chúng đều sẽ được compiled down xuống assembly một ngôn ngữ bậc thấp để máy h

0 0 60

- vừa được xem lúc

Dextool - Công cụ phân tích Decentralized Exchange tuyệt vời

. Trend Defi mặc dù đã bớt nhiệt nhưng những sản phẩm nổi bật của làn sóng này mang lại thì vẫn rất được người dùng ưa chuộng. Đặc biệt là các nền tảng Decentralized Exchange, tiêu biểu là Uniswap, SushiSwap, 1inch Exchange, FalconSwap,... Nhưng khi đã sử dụng các nền tảng DEx này mà không biết đến

0 0 107