GO实现BTC-V4-交易
package main //定义交易结构 //定义input //定义output //设置交易ID type TXInput struct { TXID []byte //交易ID Index int64 //output的索引 Address string //解锁脚本,先使用地址模拟 } type TXOutput struct { Value float64 //转账金额 Address string //锁定脚本 } type Transaction struct { TXid []byte //交易id TXInputs []TXInput //所有的inputs TXOutputs []TXOutput //所有的outputs }
setTXID函数
func (tx *Transaction) setTXID() { var buffer bytes.Buffer encoder := gob.NewEncoder(&buffer) err := encoder.Encode(tx) if err != nil{ log.Panic(err) } hash := sha256.Sum256(buffer.Bytes()) tx.TXid = hash[:] }
实现挖矿交易
//特点:只有输出,没有有效的输入(不需要ID索引签名) func NewCoinbaseTx(miner string) *Transaction { //挖矿交易没输入 inputs := []TXInput{TXInput{nil,-1,"coinbase"}} //输出给矿工奖励 outputs := []TXOutput{TXOutput{12.5,miner}} tx := Transaction{nil,inputs,outputs} tx.setTXID() return &tx }
使用Transaction改写程序
1、改写block结构
2、逐个文件根据错误提示逐个处理,把程序编译成功
3、使用strings db查看
模拟梅克尔根
更正:比特币做哈希值,并不是对整个区块做哈希,而是对区块头做哈希
//模拟梅克尔根,简单处理 func (block *Block) HashTransactions() { //将交易的ID拼接起来 var hashes []byte for _,tx := range block.Transactions{ txid := tx.TXid hashes = append(hashes,txid...) } hash := sha256.Sum256(hashes) block.MarKleRoot = hash[:] }
我有多少可用的比特币
1、遍历账本,找到能够支配的UTXO
2、剔除已经花费过的output
实现思路:
1、遍历账本
2、遍历交易
3、遍历output
4、找到属于我所有的output
此时会找到所有历史上属于我的output,这个是不准确的,需要把消耗过的剔除
在添加Output之前,遍历input,过滤掉已经消耗的output
UTXO理解参照 http://c.biancheng.net/view/1895.html
通过UTXO获取余额框架搭建
//实现思路 //1、遍历账本 //2、遍历交易 //3、遍历output //4、找到属于我的所有output func (bc *BlockChain) FindMyUtxos(address string) []TXOutput { //TODO fmt.Println("FUNC FindMyUtxos") return []TXOutput{} } func (bc *BlockChain) GetBalance(address string) { utxos := bc.FindMyUtxos(address) var total = 0.0 for _,tx := range utxos{ total += tx.Value } fmt.Printf("%s 的余额为%f \n",address,total) }
在cli.go中添加getBalance命令,调用GetBalance函数
cli.bc.GetBalance(cmds[2])
遍历交易输出TXOutputs
func (bc *BlockChain) FindMyUtxos(address string) []TXOutput { fmt.Println("FUNC FindMyUtxos") it := bc.NewIterator() var myOutput []TXOutput for{ //遍历账本 block := it.Next() for _,tx := range block.Transactions{ //遍历交易 for i,output := range tx.TXOutputs{ //遍历output if output.Address == address{ //找到属于我的所有output fmt.Printf("找到了属于 %s 的output,i:%d\n",address,i) myOutput = append(myOutput, output) } } } //这里一定要跳出 if len(block.PrevHash) == 0{ fmt.Printf("遍历区块链结束\n") break } } return myOutput } func (bc *BlockChain) GetBalance(address string) { utxos := bc.FindMyUtxos(address) var total = 0.0 for _,tx := range utxos{ total += tx.Value } fmt.Printf("%s 的余额为%f \n",address,total) }
创建普通交易
参数
1、付款人 2、收款人 3、转账金额 4、bc
内部逻辑
1、遍历账本,找到属于付款人的合适的金额,把这个outputs找到
2、如果找到的钱不足以转账,创建交易失败
3、将outputs转成inputs
4、创建输出,创建一个属于收款人的output
5、如果有找零,创建属于付款人的output
6、设置交易ID
7、返回交易结构
//创建普通交易 func NewTransaction(from,to string,amount float64,bc *BlockChain) *Transaction{ utxos := make(map[string][]int64) //标识能用的utxo var resValue float64 //这些utxo存储的金额 //例如李四转赵六4,返回的信息为 //utxos[0x333] = int64{0,1} //resValue = 5 //1、遍历账本,找到属于付款人的合适金额,把这个outputs找到 utxos,resValue = bc.FindNeedUtxos(from,amount) //2、如果找到钱不足以转账,创建交易失败 if resValue < amount{ fmt.Printf("余额不足,交易失败\n") return nil } var inputs []TXInput var outputs []TXOutput //3.将Outputs转成inputs for txid,indexes := range utxos{ for _,i := range indexes{ input := TXInput{[]byte(txid),i,from} inputs = append(inputs,input) } } //4、创建输出,创建一个属于收款人的output output := TXOutput{amount,to} outputs = append(outputs,output) //5、如果有找零,创建属于付款人output if resValue > amount{ output1 := TXOutput{resValue-amount,from} outputs = append(outputs,output1) } tx := Transaction{nil,inputs,outputs} //6、设置交易ID tx.setTXID() //7、返回交易结构 return &tx }
FindNeedUtxos 空实现
//1、遍历账本,找到属于付款人的合适金额,把这个outputs找到 //utxos,resValue = bc.FindNeedUtxos(from,amount) func (bc *BlockChain)FindNeedUtxos(from string,amount float64) (map[string][]int64,float64) { //TODO 找到合理的utxos集合 utxos := make(map[string][]int64) //标识能用的utxo return utxos,0.0 }
FindNeedUtxos第一版本实现
func (bc *BlockChain)FindNeedUtxos(from string,amount float64) (map[string][]int64,float64) { needUtxos := make(map[string][]int64) //标识能用的utxo var resValue float64 //返回统计的金额 it := bc.NewIterator() //这是标识已经消耗过的utxo结构key是交易ID,value是这个id里的output索引的数组 spentUTXOs := make(map[string][]int64) for{ //遍历账本 block := it.Next() for _,tx := range block.Transactions{ //遍历交易 //遍历交易输入inputs for _,input := range tx.TXInputs{ if input.Address == from{ fmt.Printf("找到了消耗过的output! index:%d\n",input.Index) key := string(input.TXID) spentUTXOs[key] = append(spentUTXOs[key],input.Index) } } //遍历output OUTPUT: for i,output := range tx.TXOutputs{ key := string(tx.TXid) indexes /*[]int64{0,1}*/ := spentUTXOs[key] if len(indexes) != 0{ fmt.Printf("当前这币交易中又被消耗过的output\n") for _,j /*0,1*/ := range indexes{ if int64(i) == j{ fmt.Printf("i == j ,当前的output已经被消耗过了,跳过不统计\n") continue OUTPUT //跳出两层循环 } } } //找到属于我的所有output if output.Address == from{ fmt.Printf("找到了属于 %s 的output,i:%d\n",from,i) //myOutput = append(myOutput, output) //在这里实现二次修改的控制逻辑 找到符合条件的output //1、添加到返回结构中needUtxos needUtxos[key] = append(needUtxos[key],int64(i)) resValue += output.Value //2、判断一下金额是否足够 if resValue >= amount{ //a,足够,直接返回 return needUtxos,resValue } //b,不足,继续遍历,无需处理 } } } //这里一定要跳出 if len(block.PrevHash) == 0{ fmt.Printf("遍历区块链结束\n") break } } return needUtxos,resValue }
添加Send命令
case "send": fmt.Printf("转账命令被调用\n") //./blockchain send FROM TO AMOUNT MINER DATA 转账命令 if len(cmds) != 7{ fmt.Printf("send命令发现无效参数,请检查\n") fmt.Printf(Usage) os.Exit(1) } from := cmds[2] to := cmds[3] amount,_ := strconv.ParseFloat(cmds[4],64) miner := cmds[5] data := cmds[6] cli.Send(from,to,amount,miner,data)
Send命令实现
//发送交易cli.Send(from,to,amount,miner,data) func (cli *CLI) Send (from,to string,amount float64,miner string,data string) { //1、创建挖矿交易 coinbase := NewCoinbaseTx(miner,data) txs := []*Transaction{coinbase} //2、创建普通交易 tx := NewTransaction(from,to,amount,cli.bc) if tx != nil{ txs = append(txs, tx) }else{ fmt.Printf("发现无效交易,过滤!\n") } //3、添加到区块 cli.bc.addBlock(txs) fmt.Printf("挖矿成功!") }
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
注意:以下主要是对代码的优化和改写,主要运用代码知识,无区块链知识 不作为学习区块链原理的重点
补充:改写和优化
我们想把`FindMyUtoxs`和`FindNeedUTXO`进行整合
1. FindMyUtoxs: 找到所有utxo(只要output就可以了)
2. FindNeedUTXO:找到需要的utxo(要output的定位)
我们可以定义一个结构,同时包含output已经定位信息
UTXOInfo
1. TXID
2. index
3. output
FindMyUtoxs()[]UTXOInfo
//实现思路: func (bc *BlockChain) FindMyUtoxs(address string) []UTXOInfo { fmt.Printf("FindMyUtoxs\n") //var UTXOs []TXOutput //返回的结构 var UTXOInfos []UTXOInfo //新的返回结构 it := bc.NewIterator() //这是标识已经消耗过的utxo的结构,key是交易id,value是这个id里面的output索引的数组 spentUTXOs := make(map[string][]int64) //1. 遍历账本 for { block := it.Next() //2. 遍历交易 for _, tx := range block.Transactions { //遍历交易输入:inputs for _, input := range tx.TXInputs { if input.Address == address { fmt.Printf("找到了消耗过的output! index : %d\n", input.Index) key := string(input.TXID) spentUTXOs[key] = append(spentUTXOs[key], input.Index) //spentUTXOs[0x222] = []int64{0} //spentUTXOs[0x333] = []int64{0} //中间状态 //spentUTXOs[0x333] = []int64{0, 1} } } key := string(tx.TXid) indexes /*[]int64{0,1}*/ := spentUTXOs[key] OUTPUT: //3. 遍历output for i, output := range tx.TXOutputs { if len(indexes) != 0 { fmt.Printf("当前这笔交易中有被消耗过的output!\n") for _, j /*0, 1*/ := range indexes { if int64(i) == j { fmt.Printf("i == j, 当前的output已经被消耗过了,跳过不统计!\n") continue OUTPUT } } } //4. 找到属于我的所有output if address == output.Address { fmt.Printf("找到了属于 %s 的output, i : %d\n", address, i) //UTXOs = append(UTXOs, output) utxoinfo := UTXOInfo{tx.TXid, int64(i), output} UTXOInfos = append(UTXOInfos, utxoinfo) } } } if len(block.PrevBlockHash) == 0 { fmt.Printf("遍历区块链结束!\n") break } } return UTXOInfos }
改写GetBalance
func (bc *BlockChain) GetBalance(address string) { utxoinfos := bc.FindMyUtoxs(address) var total = 0.0 //所有的output都在utxoinfos内部 //获取余额时,遍历utxoinfos获取output即可 for _, utxoinfo := range utxoinfos { total += utxoinfo.Output.Value //10, 3, 1 } fmt.Printf("%s 的余额为: %f\n", address, total) }
改写FindNeedUTXO
//1. 遍历账本,找到属于付款人的合适的金额,把这个outputs找到 //utxos, resValue = bc.FindNeedUtxos(from, amount) func (bc *BlockChain)FindNeedUtxos(from string,amount float64) (map[string][]int64,float64) { needUtxos := make(map[string][]int64) //标识能用的utxo var resValue float64 //返回统计的金额 //复用FindMyUtxo函数,这个函数已经包含了所有信息 utxoinfos := bc.FindMyUtxos(from) for _,utxoinfo := range utxoinfos{ key := string(utxoinfo.TXID) needUtxos[key] = append(needUtxos[key],int64(utxoinfo.Index)) //这里定位了UTXO->TXID和索引 resValue += utxoinfo.Output.Value //判断一下金额是否足够 if resValue >= amount{ //足够就跳出 break } } return needUtxos,resValue }
收尾工作
1. 写一个函数IsCoinbase,判断一个交易是否为挖矿交易
2. 把创建区块链这个命令单独实现一下
./blockchain createBlockChain 班花
IsCoinbase实现
func (tx *Transaction) IsCoinbase() bool { //特点:1. 只有一个input 2. 引用的id是nil 3. 引用的索引是-1 inputs := tx.TXInputs if len(inputs) == 1 && inputs[0].TXID == nil && inputs[0].Index == -1 { return true } return false }
在遍历inputs时使用
if tx.IsCoinbase() == false { //如果不是coinbase,说明是普通交易,才有必要进行遍历 for _, input := range tx.TXInputs { if input.Address == address { fmt.Printf("找到了消耗过的output! index : %d\n", input.Index) key := string(input.TXID) spentUTXOs[key] = append(spentUTXOs[key], input.Index) //spentUTXOs[0x222] = []int64{0} //spentUTXOs[0x333] = []int64{0} //中间状态 //spentUTXOs[0x333] = []int64{0, 1} } } }
添加创建区块链命令
1. 当前情况
在NewBlockCHain中实现了两个功能
– 没有区块链时创建区块链,并且返回区块链实例
– 如果区块链存在,直接返回区块链实例
2. 我们要做的事情
* 添加CreateBlockChain命令,只创建区块链(只能调用一次)
* 把返回区块链的实例使用一个新的函数实现(并不单独使用,而是在每次操作区块链之前调用一下)
创建区块链函数
func CreateBlockChain(miner string) *BlockChain { //功能分析: //1. 获得数据库的句柄,打开数据库,读写数据 db, err := bolt.Open(blockChainName, 0600, nil) //向数据库中写入数据 //从数据库中读取数据 if err != nil { log.Panic(err) } //defer db.Close() var tail []byte db.Update(func(tx *bolt.Tx) error { b, err := tx.CreateBucket([]byte(blockBucketName)) if err != nil { log.Panic(err) } //抽屉准备完毕,开始添加创世块 //创世块中只有一个挖矿交易,只有Coinbase coinbase := NewCoinbaseTx(miner, genesisInfo) genesisBlock := NewBlock([]*Transaction{coinbase}, []byte{}) b.Put(genesisBlock.Hash, genesisBlock.Serialize() /*将区块序列化,转成字节流*/) b.Put([]byte(lastHashKey), genesisBlock.Hash) //为了测试,我们把写入的数据读取出来,如果没问题,注释掉这段代码 //blockInfo := b.Get(genesisBlock.Hash) //block := Deserialize(blockInfo) //fmt.Printf("解码后的block数据:%s\n", block) tail = genesisBlock.Hash return nil }) return &BlockChain{db, tail} }
获取区块链实例函数
//返回区块链实例 func NewBlockChain() *BlockChain { //功能分析: //1. 获得数据库的句柄,打开数据库,读写数据 db, err := bolt.Open(blockChainName, 0600, nil) if err != nil { log.Panic(err) } //defer db.Close() var tail []byte db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blockBucketName)) if b == nil { fmt.Printf("区块链bucket为空,请检查!\n") os.Exit(1) } tail = b.Get([]byte(lastHashKey)) return nil }) return &BlockChain{db, tail} }
添加CreateBlockChain命令 在cli.go中添加命令
switch cmds[1] { case "createBlockChain": if len(cmds) != 3 { fmt.Printf(Usage) os.Exit(1) } fmt.Printf("创建区块链命令被调用!\n") addr := cmds[2] cli.CreateBlockChain(addr) case "printChain":
在commands.go中实现
func (cli *CLI) CreateBlockChain(addr string) { bc := CreateBlockChain(addr) bc.db.Close() fmt.Printf("创建区块链成功!\n") }
主函数
package main func main() { cli := CLI{} cli.Run() }
在commands.go中用NewBlockChain函数
例如,获取余额:
func (cli *CLI)GetBalance(addr string) { bc := NewBlockChain() defer bc.db.Close() bc.GetBalance(addr) }
判断区块链文件是否存在
1. 如果存在,不允许再次创建
2. 如果没有,做所有操作之前,必须先创建区块链
判断文件`blockChain.db`是否存在??
在utils.go中添加IsFileExist函数,代码如下:
//判断文件是否存在 func IsFileExist(fileName string) bool { //使用os.Stat来判断 //func Stat(name string) (FileInfo, error) { _, err := os.Stat(fileName) if os.IsNotExist(err) { return false } return true }
在NewBlockChain中调用
func NewBlockChain() *BlockChain { if !IsFileExist(blockChainName) { fmt.Printf("区块链不存在,请先创建!\n") return nil } // xxxxx }
在CreateBlockChain中调用
func CreateBlockChain(miner string) *BlockChain { if IsFileExist(blockChainName) { fmt.Printf("区块链已经存在,不需要重复创建!\n") return nil } //xxxx }
在commands.go进行有效性判断
func (cli *CLI) Send(from, to string, amount float64, miner string, data string) { bc := NewBlockChain() if bc == nil { return } defer bc.db.Close() //xxxxx }