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
}



