从0到1简易区块链开发手册V0.1
从0到1简易区块链开发手册V0.2-创建钱包
http://blog.51cto.com/clovemfong/2161923
从0到1简易区块链开发手册V0.3-数据持久化与创世区块
http://blog.51cto.com/clovemfong/2162169
从0到1简易区块链开发手册V0.4-实现转账交易的思路分析
http://blog.51cto.com/clovemfong/2163057
从0到1简易区块链开发手册V0.5-实现余额查询
http://blog.51cto.com/clovemfong/2163109
从0到1简易区块链开发手册V0.6-实现打印区块
http://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.Args与flag
关于这两个功能,此处不再赘述,否则篇幅会无限臃肿。
代码块均在方法体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
测试思路
查看命令行列表是否可以正常显示
输入非法字符查看是否有错误提示
业务功能此处暂未实现,测试时忽略。
从0到1简易区块链开发手册V0.2-创建钱包
http://blog.51cto.com/clovemfong/2161923
从0到1简易区块链开发手册V0.3-数据持久化与创世区块
http://blog.51cto.com/clovemfong/2162169
从0到1简易区块链开发手册V0.4-实现转账交易的思路分析
http://blog.51cto.com/clovemfong/2163057
从0到1简易区块链开发手册V0.5-实现余额查询
http://blog.51cto.com/clovemfong/2163109
从0到1简易区块链开发手册V0.6-实现打印区块
http://blog.51cto.com/clovemfong/2163211
©著作权归作者所有:来自51CTO博客作者暗黑魔君的原创作品,如需转载,请注明出处,否则将追究法律责任