區塊鏈簡易公鏈從0到1開發手冊

從0到1簡易區塊鏈開發手冊V0.1

從0到1簡易區塊鏈開發手冊V0.2-創建錢包
https://blog.51cto.com/clovemfong/2161923
從0到1簡易區塊鏈開發手冊V0.3-數據持久化與創世區塊
https://blog.51cto.com/clovemfong/2162169
從0到1簡易區塊鏈開發手冊V0.4-實現轉賬交易的思路分析
https://blog.51cto.com/clovemfong/2163057
從0到1簡易區塊鏈開發手冊V0.5-實現餘額查詢
https://blog.51cto.com/clovemfong/2163109
從0到1簡易區塊鏈開發手冊V0.6-實現打印區塊
https://blog.51cto.com/clovemfong/2163211

前言

這是我這段時間學習區塊鏈開發以來打造的第一個區塊鏈平臺,之所以叫做簡易區塊鏈,是因爲它確實比較簡易,僅僅是實現了底層的一些功能,不足以作爲一個真正的公鏈使用,但通過學習,我們能夠通過代碼更加理解比特幣白皮書中描述的各種比特幣原理,區塊鏈世界,無論是研究理論的,還是實戰開發的,甚至炒幣玩資本的,都離不開比特幣的影子,區塊鏈技術畢竟是從比特幣中剝離抽象而來,所以,作爲一個技術人員,無論是研究以太坊,超級賬本,甚至是各種公鏈,包括某些山寨公鏈,都需要先去理解比特幣原理,而對於開發者而言,理解原理最好的方式就是將其通過代碼實現,當然,我們這裏實現的一些原理只是應用實戰範圍之內可以實現的,比如橢圓加密算法,我們要實現的只是使用橢圓加密去實現某些加密功能,而非用代碼去實現一個完整的橢圓加密代碼庫,這個不再本文的討論範圍內,所以本文面向的羣體是:

  • 對比特幣原理不瞭解,但沒時間看太多的資料文獻的初學者
  • 對比特幣原理有所瞭解,但是停留在理論階段的研究者
  • 沒有對比特幣進行研究,想直接研究以太坊,超級賬本的實戰者(大神除外)
  • 對Golang熟悉,但是不知道如何入手區塊鏈的開發者或者是像我一樣的運維 :-)

本文中,我們先通過命令行的方式演示區塊鏈的工作流程以及相關原理,涉及到比較重要的內容,比如Sha256哈希,橢圓加密,Base58編碼等內容,我會根據時間以及後期的工作情況進行適當調整,這注定是一個短期內沒有結尾的故事。

爲表尊敬,寫在前面,建議先閱讀該文檔

本文的學習資料來自這位liuxhengxu前輩翻譯的資料

能將資料翻譯得如此完美,相比其技術功能也是相當深厚的,感謝分享

建議大家可以先看該資料後再來看我的這系列文章,否則可能會有一些難度,由於該資料是通過循序漸進的方式進行版本迭代,慢慢引導開發者不斷對原有的代碼進行優化,拓展,非常認真並細心,希望大家時間充裕的時候以及對某些本文並未寫清楚的地方,強烈建議閱讀該資料。

本文在此基礎上進行了一些修改(談不上改進),我摒棄一些過於基礎的以及後期需要大量重構的代碼,直接通過該項目的執行流程進行代碼分析,這樣可以稍微節省一些大家的時間,把有限的精力放在對業務有更大提升的技術研究上。

一. 功能描述

Usage:
        createwallet
                        -- 創建錢包
        getaddresslists
                        -- 獲取所有的錢包地址
        createblockchain -address address
                        -- 創建創世區塊
        send -from SourceAddress -to DestAddress -amount Amount
                        -- 轉賬交易
        printchain
                        -- 打印區塊
        getbalance -address address
                        -- 查詢餘額

本文圍繞着幾個功能進行講解

  • 創建錢包

    通過橢圓加密算法創建錢包地址

  • 獲取錢包地址

    獲取區塊鏈中所有的錢包地址

  • 創建創世區塊

    實現創世區塊的創建,並生成區塊鏈

  • 實現轉賬交易

    通過轉賬交易,生成區塊,並存入區塊鏈

  • 打印區塊

    打印出所有的區塊信息,實現轉賬交易的溯源

  • 查詢餘額

    查詢出對應錢包的餘額狀態

隨着代碼的不斷完善,我們將會對以上進行改進,並提供更多的功能點進行分析探討,我們先通過下圖簡單演示一下如上功能

區塊鏈簡易公鏈從0到1開發手冊

關於效果圖,大家先大致看下即可,不需要刻意研究,在後期的課程中都會涉及。

二. 實現命令行功能

區塊鏈簡易公鏈從0到1開發手冊

1.定義結構體

定義一個空結構體

type CLI struct {

}

2.結構體方法

重要!初學者必看

這裏提到的結構體方法並不是真正實現功能的方法,而是命令行對象的方法,這些方法中會調用實際的功能對象方法進行功能實現,在本章節中,創建結構體方法即可,功能代碼可以爲空,如:

例子:
func (cli *CLI) CreateWallet() {
}
func (cli *CLI) GetAddressLists() {
}
.....

其他的可以在後期逐步實現,爲了讓有基礎的同學對項目整體提前有些印象,所以,代碼內容我直接複製粘貼進來,不做刪減,在後期的內容中,會逐步涉及到每個調用的對象方法或者函數的作用。

2.1 創建錢包CreateWallet

func (cli *CLI) CreateWallet() {
    _, wallets := GetWallets() //獲取錢包集合對象
    wallets.CreateNewWallets() //創建錢包集合

}

2.2 獲取錢包地址GetAddressLists

func (cli *CLI) GetAddressLists() {
    fmt.Println("錢包地址列表爲:")
        //獲取錢包的集合,遍歷,依次輸出
    _, wallets := GetWallets() //獲取錢包集合對象
    for address, _ := range wallets.WalletMap { 
        fmt.Printf("\t%s\n", address)
    }
}

2.3 創建創世區塊CreateBlockChain

func (cli *CLI) CreateBlockChain(address string) {
    CreateBlockChainWithGenesisBlock(address)
    bc :=GetBlockChainObject()
    if bc == nil{
        fmt.Println("沒有數據庫")
        os.Exit(1)
    }
    defer bc.DB.Close()
    utxoSet:=&UTXOSet{bc}
    utxoSet.ResetUTXOSet()
}

2.4 創建轉賬交易Send

func (cli *CLI) Send(from, to, amount []string) {
    bc := GetBlockChainObject()
    if bc == nil {
        fmt.Println("沒有BlockChain,無法轉賬。。")
        os.Exit(1)
    }
    defer bc.DB.Close()

    bc.MineNewBlock(from, to, amount)
    //添加更新
    utsoSet :=&UTXOSet{bc}
    utsoSet.Update()
}

2.5 查詢餘額GetBalance

func (cli *CLI) GetBalance(address string) {
    bc := GetBlockChainObject()
    if bc == nil {
        fmt.Println("沒有BlockChain,無法查詢。。")
        os.Exit(1)
    }
    defer bc.DB.Close()
    //total := bc.GetBalance(address,[]*Transaction{})
    utxoSet :=&UTXOSet{bc}
    total:=utxoSet.GetBalance(address)

    fmt.Printf("%s,餘額是:%d\n", address, total)
}

2.6 打印區塊PrintChains

func (cli *CLI) PrintChains() {
    //cli.BlockChain.PrintChains()
    bc := GetBlockChainObject() //bc{Tip,DB}
    if bc == nil {
        fmt.Println("沒有BlockChain,無法打印任何數據。。")
        os.Exit(1)
    }
    defer bc.DB.Close()
    bc.PrintChains()
}

3. 相關函數

3.1 判斷參數是否合法 isValidArgs

func isValidArgs() {
    if len(os.Args) < 2 {
        printUsage()
        os.Exit(1)
    }
}

判斷終端命令是否有參數輸入,如果沒有參數,則提示程序使用說明,並退出程序

3.2 程序使用說明

func printUsage() {
    fmt.Println("Usage:")
    fmt.Println("\tcreatewallet\n\t\t\t-- 創建錢包")
    fmt.Println("\tgetaddresslists\n\t\t\t-- 獲取所有的錢包地址")
    fmt.Println("\tcreateblockchain -address address\n\t\t\t-- 創建創世區塊")
    fmt.Println("\tsend -from SourceAddress -to DestAddress -amount Amount\n\t\t\t-- 轉賬交易")
    fmt.Println("\tprintchain\n\t\t\t-- 打印區塊")
    fmt.Println("\tgetbalance -address address\n\t\t\t-- 查詢餘額")
}

3.3 JSON解析的函數

func JSONToArray(jsonString string) []string {
    var arr [] string
    err := json.Unmarshal([]byte(jsonString), &arr)
    if err != nil {
        log.Panic(err)
    }
    return arr
}

通過該函數將JSON字符串格式轉成字符串數組,用於在多筆轉賬交易中實現同時多個賬戶進行兩兩轉賬的功能。

3.4 校驗地址是否有效

func  IsValidAddress(address []byte) bool {

    //step1:Base58解碼
    //version+pubkeyHash+checksum
    full_payload := Base58Decode(address)

    //step2:獲取地址中攜帶的checkSUm
    checkSumBytes := full_payload[len(full_payload)-addressCheckSumLen:]

    versioned_payload := full_payload[:len(full_payload)-addressCheckSumLen]

    //step3:versioned_payload,生成一次校驗碼
    checkSumBytes2 := CheckSum(versioned_payload)

    //step4:比較checkSumBytes,checkSumBytes2
    return bytes.Compare(checkSumBytes, checkSumBytes2) == 0

}

以下三個功能實現之前需要先調用該函數進行地址校驗

  • 創建創世區塊
  • 轉賬交易
  • 查詢餘額

4.命令行主要方法Run

Usage:
        createwallet
                        -- 創建錢包
        getaddresslists
                        -- 獲取所有的錢包地址
        createblockchain -address address
                        -- 創建創世區塊
        send -from SourceAddress -to DestAddress -amount Amount
                        -- 轉賬交易
        printchain
                        -- 打印區塊
        getbalance -address address
                        -- 查詢餘額

我們將如上功能展示的實現功能寫在Run方法中,實現命令行功能的關鍵是瞭解os.Argsflag

關於這兩個功能,此處不再贅述,否則篇幅會無限臃腫。

代碼塊均在方法體Run中,下文將分步驟對代碼實現進行體現

func (cli *CLI) Run() {
}

4.1 判斷命令行參數是否合法

isValidArgs()

4.2 創建flagset命令對象

    createWalletCmd := flag.NewFlagSet("createwallet", flag.ExitOnError)
    getAddresslistsCmd := flag.NewFlagSet("getaddresslists", flag.ExitOnError)
    CreateBlockChainCmd := flag.NewFlagSet("createblockchain", flag.ExitOnError)
    sendCmd := flag.NewFlagSet("send", flag.ExitOnError)
    printChainCmd := flag.NewFlagSet("printchain", flag.ExitOnError)
    getBalanceCmd := flag.NewFlagSet("getbalance", flag.ExitOnError)
    testMethodCmd := flag.NewFlagSet("test", flag.ExitOnError)

如上,通過flag.NewFlagSet方法創建命令對象,如createwallet,getaddresslists,createblockchain等命令對象

固定用法,掌握即可。

4.3 設置命令後的參數對象

    flagCreateBlockChainData := CreateBlockChainCmd.String("address", "GenesisBlock", "創世區塊的信息")
    flagSendFromData := sendCmd.String("from", "", "轉賬源地址")
    flagSendToData := sendCmd.String("to", "", "轉賬目標地址")
    flagSendAmountData := sendCmd.String("amount", "", "轉賬金額")
    flagGetBalanceData := getBalanceCmd.String("address", "", "要查詢餘額的賬戶")

通過命令對象的String方法爲命令後的參數對象

  • createblockchain命令後的參數對象:address
  • send命令後的參數對象: from | to |amount
  • getbalance命令後的參數對象: address

其中createwallet,getaddresslists,printchain命令沒有參數對象。

4.4 解析命令對象

switch os.Args[1] {
    case "createwallet":
        err := createWalletCmd.Parse(os.Args[2:])
        if err != nil {
            log.Panic(err)
        }
    case "getaddresslists":
        err := getAddresslistsCmd.Parse(os.Args[2:])
        if err != nil {
            log.Panic(err)
        }
    case "createblockchain":
        err := CreateBlockChainCmd.Parse(os.Args[2:])
        if err != nil {
            log.Panic(err)
        }
    case "send":
        err := sendCmd.Parse(os.Args[2:])
        if err != nil {
            log.Panic(err)
        }
    case "getbalance":
        err := getBalanceCmd.Parse(os.Args[2:])
        if err != nil {
            log.Panic(err)
        }
    case "printchain":
        err := printChainCmd.Parse(os.Args[2:])
        if err != nil {
            log.Panic(err)
        }
    case "test":
        err := testMethodCmd.Parse(os.Args[2:])
        if err != nil {
            log.Panic(err)
        }
    default:
        printUsage()
        os.Exit(1)

    }

匹配對應的命令,用命令對象的Parse方法對os.Args[2:]進行解析。

4.5 執行對應功能

    //4.1 創建錢包--->交易地址
    if createWalletCmd.Parsed() {
        cli.CreateWallet()
    }
    //4.2 獲取錢包地址
    if getAddresslistsCmd.Parsed() {
        cli.GetAddressLists()
    }
    //4.3 創建創世區塊
    if CreateBlockChainCmd.Parsed() {
        if !IsValidAddress([]byte(*flagCreateBlockChainData)) {
            fmt.Println("地址無效,無法創建創世前區塊")
            printUsage()
            os.Exit(1)
        }
        cli.CreateBlockChain(*flagCreateBlockChainData)
    }
    //4.4 轉賬交易
    if sendCmd.Parsed() {
        if *flagSendFromData == "" || *flagSendToData == "" || *flagSendAmountData == "" {
            fmt.Println("轉賬信息有誤")
            printUsage()
            os.Exit(1)
        }
        //添加區塊
        from := JSONToArray(*flagSendFromData)     //[]string
        to := JSONToArray(*flagSendToData)         //[]string
        amount := JSONToArray(*flagSendAmountData) //[]string
        for i := 0; i < len(from); i++ {
            if !IsValidAddress([]byte(from[i])) || !IsValidAddress([]byte(to[i])) {
                fmt.Println("地址無效,無法轉賬")
                printUsage()
                os.Exit(1)
            }
        }

        cli.Send(from, to, amount)
    }
    //4.5 查詢餘額
    if getBalanceCmd.Parsed() {
        if !IsValidAddress([]byte(*flagGetBalanceData)) {
            fmt.Println("查詢地址有誤")
            printUsage()
            os.Exit(1)
        }
        cli.GetBalance(*flagGetBalanceData)
    }

    //4.6 打印區塊信息
    if printChainCmd.Parsed() {
        cli.PrintChains()
    }

5. 測試代碼

在main.go中中添加測試代碼

package main
func main() {
    cli:=BLC.CLI{}
    cli.Run()
}

編譯運行

$ go build -o mybtc main.go

測試思路

  1. 查看命令行列表是否可以正常顯示
  2. 輸入非法字符查看是否有錯誤提示

業務功能此處暫未實現,測試時忽略。

從0到1簡易區塊鏈開發手冊V0.2-創建錢包
https://blog.51cto.com/clovemfong/2161923
從0到1簡易區塊鏈開發手冊V0.3-數據持久化與創世區塊
https://blog.51cto.com/clovemfong/2162169
從0到1簡易區塊鏈開發手冊V0.4-實現轉賬交易的思路分析
https://blog.51cto.com/clovemfong/2163057
從0到1簡易區塊鏈開發手冊V0.5-實現餘額查詢
https://blog.51cto.com/clovemfong/2163109
從0到1簡易區塊鏈開發手冊V0.6-實現打印區塊
https://blog.51cto.com/clovemfong/2163211
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章