区块链区块的作用是打包链上产生的交易,可以说交易是区块链至关重要的一个组成部分.在区块链中,交易一旦被创建,就没有任何人能够再去修改或是删除它.
关于交易的结构,可以参照以前的这篇:比特币源码研读(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}
复制代码 [h2]Transaction结构[/h2]- type Transaction struct { //1.交易哈希值 TxHAsh []byte //2.交易输入 Vins []*TXInput //3.交易输出 Vouts []*TXOutput}
复制代码 [h2]TXInput[/h2]- type TXInput struct { //交易ID 引用上一笔交易输出作为输入 TxHash []byte //存储TXOutput在Vout里的索引 Vout int //数字签名 暂时可理解为用户名 ScriptSig string}
复制代码 交易输入作为本次交易的消费源,输入来源于之前交易的输出.如上,TxHash是引用的上一笔输出所在的交易的交易哈希;Vout是该输出在相应交易中的输出索引;ScriptSig,可以暂时理解为用户名,表示哪一个用户拥有这一笔输入.ScriptSig的设定是为了保证用户只能话费自己名下的代币.
[h2]TXOutput[/h2]- type TXOutput struct { //面值 Value int64 //暂时理解为用户名 ScriptPubKey string}
复制代码 这里的交易输出就是上面交易输入里引用的输出.Value是该输出的面值,ScriptPubKey暂时理解为用户名,表示谁将拥有这笔输出.
了解比特币的人都知道,交易输出是一个完整的不可分割的结构.什么意思呢?就是我们在引用TXOutput,必须全部引用,不能仅仅使用其一部分.举个简单的:
假如你有一个25btc的TXOutput,你需要花费10btc.这个交易的过程并不是:你花费了25btc中的10btc,你的原有TXOutput依旧有15btc的余额.真正的过程是,你花费了整个原有的TXOutput,由于消费额不匹配,这里会产生一个15btc的找零.消费的结果是:你25btc的TXOutput被话费已不复存在,系统重新为你生成一个15btc面值的TXOutput.这两个TXOutput是完全不同的两个对象!!!
[h2]CoinbaseTransaction[/h2]我们知道,当矿工成功挖到一个区块时会获得一笔奖励.那么这笔奖励是怎么交付到矿工账户的.这就有赖于一笔叫做创币交易的交易.
创币交易是区块内的第一笔交易,它负责将系统产生的奖励给挖出区块的矿工.由于它并不是普通意义上的转账,所以交易输入里并不需要引用任何一笔交易输出.- //创建创币交易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}
复制代码 [h2]HashTransactions[/h2]- //将交易信息转换为字节数组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[:]}
复制代码 [h2]NewBlock改动[/h2]- //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}
复制代码 [h2]CreateBlockchainWithGensisBlock改动[/h2]- //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) }}
复制代码 [h2]POW/prepareData改动[/h2]添加交易后,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[:]}
复制代码 [h2]Printchain添加交易信息打印[/h2]现在,整个交易的数据结构就搭起来了.我们再改动区块链打印方法,将区块的交易信息添加到打印.- //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 } }}
复制代码 [h2]Main_Test[/h2]- package mainimport ( "chaors.com/LearnGo/publicChaorsChain/part7-transaction-Prototype/BLC")func main() { cli := BLC.CLI{} cli.Run()}
复制代码 打印创世区块的结果为:
main_test源代码在这,喜欢的朋友记得给个小star,或者fork.也欢迎大家一起探讨区块链相关知识,一起进步!
.
.
.
.[h2]互联网颠覆世界,区块链颠覆互联网![/h2] ---------------------------------------------20180703 20:40 |
|