go公链实战0x05交易(1)

作者: WallisW | 来源:发表于2018-07-03 20:41 被阅读42次

    区块链区块的作用是打包链上产生的交易,可以说交易是区块链至关重要的一个组成部分.在区块链中,交易一旦被创建,就没有任何人能够再去修改或是删除它.

    关于交易的结构,可以参照以前的这篇:比特币源码研读(3)数据结构-交易Transaction

    我们之前定义的区块结构简单地用一个字符串描述交易内容,今后将正式用新的结构来表示交易:

    type Block struct {
        //1.区块高度
        Height int64
        //2.上一个区块HAsh
        PrevBlockHash []byte
        //3.交易数据
        //Data []byte    //只前简单地对交易的描述
            Txs [] *Transaction   //Transaction结构数组表示交易
        //4.时间戳
        Timestamp int64
        //5.Hash
        Hash []byte
        //6.Nonce  符合工作量证明的随机数
        Nonce int64
    }
    

    Transaction结构

    type Transaction struct {
        //1.交易哈希值
        TxHAsh []byte
        //2.交易输入
        Vins []*TXInput
        //3.交易输出
        Vouts []*TXOutput
    }
    

    TXInput

    type TXInput struct {
        //交易ID 引用上一笔交易输出作为输入
        TxHash []byte
        //存储TXOutput在Vout里的索引
        Vout int
        //数字签名  暂时可理解为用户名
        ScriptSig string
    }
    

    交易输入作为本次交易的消费源,输入来源于之前交易的输出.如上,TxHash是引用的上一笔输出所在的交易的交易哈希;Vout是该输出在相应交易中的输出索引;ScriptSig,可以暂时理解为用户名,表示哪一个用户拥有这一笔输入.ScriptSig的设定是为了保证用户只能话费自己名下的代币.

    TXOutput

    type TXOutput struct {
        //面值
        Value int64
        //暂时理解为用户名
        ScriptPubKey string
    }
    

    这里的交易输出就是上面交易输入里引用的输出.Value是该输出的面值,ScriptPubKey暂时理解为用户名,表示谁将拥有这笔输出.

    了解比特币的人都知道,交易输出是一个完整的不可分割的结构.什么意思呢?就是我们在引用TXOutput,必须全部引用,不能仅仅使用其一部分.举个简单的🌰:

    假如你有一个25btc的TXOutput,你需要花费10btc.这个交易的过程并不是:你花费了25btc中的10btc,你的原有TXOutput依旧有15btc的余额.真正的过程是,你花费了整个原有的TXOutput,由于消费额不匹配,这里会产生一个15btc的找零.消费的结果是:你25btc的TXOutput被话费已不复存在,系统重新为你生成一个15btc面值的TXOutput.这两个TXOutput是完全不同的两个对象!!!

    CoinbaseTransaction

    我们知道,当矿工成功挖到一个区块时会获得一笔奖励.那么这笔奖励是怎么交付到矿工账户的.这就有赖于一笔叫做创币交易的交易.

    创币交易是区块内的第一笔交易,它负责将系统产生的奖励给挖出区块的矿工.由于它并不是普通意义上的转账,所以交易输入里并不需要引用任何一笔交易输出.

    //创建创币交易
    func NewCoinbaseTransaction(address string) *Transaction {
    
        //交易输入  由于区块第一笔交易其实没有输入,所以交易哈希传空,TXOutput索引传-1,签名随你
        txInput := &TXInput{
            []byte{},
            -1,
            "CoinbaseTransaction",
        }
    
        //交易输出  产生一笔奖励给挖矿者address
        txOutput := &TXOutput{25, address}
        txCoinbase := &Transaction{
            []byte{},   //暂时将交易哈希置空
            []*TXInput{txInput},
            []*TXOutput{txOutput},
        }
            
        //交易哈希的计算
        txCoinbase.HashTransactions()
    
        return txCoinbase
    }
    

    HashTransactions

    //将交易信息转换为字节数组
    func (tx *Transaction) HashTransactions() {
    
        //交易信息序列化
        var result bytes.Buffer
        encoder := gob.NewEncoder(&result)
    
        err := encoder.Encode(tx)
        if err != nil {
    
            log.Panic(err)
        }
    
        //设置hash
        txHash := sha256.Sum256(result.Bytes())
        tx.TxHAsh = txHash[:]
    }
    

    NewBlock改动

    //1.创建新的区块
    func NewBlock(txs []*Transaction, height int64, prevBlockHash []byte) *Block {
    
        //创建区块
        block := &Block{
            Height:        height,
            PrevBlockHash: prevBlockHash,
            Txs:           txs,
            Timestamp:     time.Now().Unix(),
            Hash:          nil,
            Nonce:         0}
    
        //调用工作量证明返回有效的Hash
        pow := NewProofOfWork(block)
        hash, nonce := pow.Run()
        block.Hash = hash[:]
        block.Nonce = nonce
    
        fmt.Printf("\r######%d-%x\n", nonce, hash)
    
        return block
    }
    

    CreateBlockchainWithGensisBlock改动

    //1.创建创世区块
    func CreateBlockchainWithGensisBlock(address string) {
    
        //判断数据库是否存在
        if IsDBExists(dbName) {
    
            fmt.Println("创世区块已存在...")
            os.Exit(1)
    
            //创建并打开数据库
            db, err := bolt.Open(dbName, 0600, nil)
            if err != nil {
                log.Fatal(err)
            }
    
            var block *Block
            err = db.View(func(tx *bolt.Tx) error {
    
                b := tx.Bucket([]byte(blockTableName))
                if b != nil {
    
                    hash := b.Get([]byte(newestBlockKey))
                    block = DeSerializeBlock(b.Get(hash))
                    fmt.Printf("\r######%d-%x\n", block.Nonce, hash)
                }
    
                return nil
            })
            if err != nil {
    
                log.Panic(err)
            }
    
            os.Exit(1)
        }
    
        fmt.Println("正在创建创世区块...")
    
        //创建并打开数据库
        db, err := bolt.Open(dbName, 0600, nil)
        if err != nil {
            log.Fatal(err)
        }
        err = db.Update(func(tx *bolt.Tx) error {
    
            b, err := tx.CreateBucket([]byte(blockTableName))
            if err != nil {
    
                log.Panic(err)
            }
    
            if b != nil {
    
                //创币交易
                txCoinbase := NewCoinbaseTransaction(address)
                //创世区块
                gensisBlock := CreateGenesisBlock([]*Transaction{txCoinbase})
                //存入数据库
                err := b.Put(gensisBlock.Hash, gensisBlock.Serialize())
                if err != nil {
                    log.Panic(err)
                }
    
                //存储最新区块hash
                err = b.Put([]byte(newestBlockKey), gensisBlock.Hash)
                if err != nil {
                    log.Panic(err)
                }
            }
    
            return nil
        })
        //更新数据库失败
        if err != nil {
            log.Fatal(err)
        }
    }
    

    POW/prepareData改动

    添加交易后,POW挖矿时也必须相应地把交易信息考虑进去.这里需要改动prepareData方法

    //拼接区块属性,返回字节数组
    func (pow *ProofOfWork) prepareData(nonce int) []byte {
    
        data := bytes.Join(
            [][]byte{
                pow.Block.PrevBlockHash,
                pow.Block.HashTransactions(),
                IntToHex(pow.Block.Timestamp),
                IntToHex(int64(targetBits)),
                IntToHex(int64(nonce)),
                IntToHex(int64(pow.Block.Height)),
            },
            []byte{},
        )
    
        return data
    }
    

    这里POW计算目标哈希并不需要将所有的交易信息拼接,我们只需要将每一个交易的交易哈希拼接起来即可.因为,交易哈希是交易所有信息的哈希值.这样做也能保证交易信息的完整性.所以,我们需要在Block新增一个方法:

    //将交易信息转换为字节数组
    func (block *Block) HashTransactions() []byte  {
    
        var txHashes [][]byte
        var txHash [32]byte
    
        for _, tx := range block.Txs {
    
            txHashes = append(txHashes, tx.TxHAsh)
        }
    
        txHash = sha256.Sum256(bytes.Join(txHashes, []byte{}))
    
        return txHash[:]
    }
    

    Printchain添加交易信息打印

    现在,整个交易的数据结构就搭起来了.我们再改动区块链打印方法,将区块的交易信息添加到打印.

    //3.X 优化区块链遍历方法
    func (blc *Blockchain) Printchain() {
        //迭代器
        blcIterator := blc.Iterator()
        for {
    
            block := blcIterator.Next()
    
            fmt.Println("------------------------------")
            fmt.Printf("Height:%d\n", block.Height)
            fmt.Printf("PrevBlockHash:%x\n", block.PrevBlockHash)
            fmt.Printf("Timestamp:%s\n", time.Unix(block.Timestamp, 0).Format("2006-01-02 03:04:05 PM"))
            fmt.Printf("Hash:%x\n", block.Hash)
            fmt.Printf("Nonce:%d\n", block.Nonce)
            fmt.Println("Txs:")
            for _,tx := range block.Txs {
    
                fmt.Printf("%x\n", tx.TxHAsh)
                fmt.Println("Vins:")
                for _,in := range tx.Vins  {
                    fmt.Printf("txHash:%x\n", in.TxHash)
                    fmt.Printf("Vout:%d\n", in.Vout)
                    fmt.Printf("ScriptSig:%s\n\n", in.ScriptSig)
                }
    
                fmt.Println("Vouts:")
                for _,out := range tx.Vouts  {
                    fmt.Printf("Value:%x\n", out.Value)
                    fmt.Printf("ScriptPubKey:%x\n\n", out.ScriptPubKey)
                }
            }
            fmt.Println("------------------------------")
    
            var hashInt big.Int
            hashInt.SetBytes(block.PrevBlockHash)
    
            if big.NewInt(0).Cmp(&hashInt) == 0 {
    
                break
            }
        }
    }
    

    Main_Test

    package main
    
    import (
        "chaors.com/LearnGo/publicChaorsChain/part7-transaction-Prototype/BLC"
    )
    
    func main() {
    
        cli := BLC.CLI{}
        cli.Run()
    }
    

    打印创世区块的结果为:

    main_test

    源代码在这,喜欢的朋友记得给个小star,或者fork.也欢迎大家一起探讨区块链相关知识,一起进步!

    .
    .
    .
    .

    互联网颠覆世界,区块链颠覆互联网!

    ---------------------------------------------20180703 20:40

    相关文章

      网友评论

        本文标题:go公链实战0x05交易(1)

        本文链接:https://www.haomeiwen.com/subject/zkzduftx.html