欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Golang|区块链Transaction实现源码分析

程序员文章站 2022-04-21 23:51:46
...

区块链Transaction实现源码分析

资源

go实现区块链

程序运行结果

Golang|区块链Transaction实现源码分析

源码分析

当程序解析到命令行参数时,将调用结构体CLI的createBlockchain方法。这个方法的作用是在本地数据库中创建一个含有创世区块的区块链。(如果在本地数据库中已经含有区块链,则不执行创建)

Golang|区块链Transaction实现源码分析

进入到该方法的源码部分。

// CreateBlockchain creates a new blockchain DB
func CreateBlockchain(address string) *Blockchain {
	if dbExists() {
		fmt.Println("Blockchain already exists.")
		os.Exit(1)
	}

	var tip []byte
	db, err := bolt.Open(dbFile, 0600, nil) // 打开数据库
	// 打开一个数据库读写事务
	err = db.Update(func(tx *bolt.Tx) error {
		// 尝试获取存储区块的bucket
		b := tx.Bucket([]byte(blocksBucket))
			// 产生一个挖出创世区块的奖励事务
			cbtx := NewCoinbaseTX(address,genesisCoinbaseData)
			// 构造新区块来存储记录该事务
			genesis := NewGenesisBlock(cbtx)
			// 在数据库中创建一个bucket
			b, err := tx.CreateBucket([]byte(blocksBucket))
			// 将传世区块存储到bucket中
			err = b.Put(genesis.Hash, genesis.Serialize())
			// 更新bucket中存储的区块链最后一个区块的哈希值
			err = b.Put([]byte{1}, genesis.Hash)
			tip = genesis.Hash
			return err
		return nil
	})
	if err != nil {
		log.Panic(err.Error())
	}
	bc := Blockchain{tip, db}
	return &bc
}

观察上面的源码,其中比较关键的有两个方法。

一个方法是NewCoinbaseTX,另一个方法是NewGenesisBlock。在这段代码中,程序将产生一个挖出创世区块的Transaction,然后将Transaction存储在创世区块上面,最后将创世区块添加到区块链中(存到本地数据库)。

先进入NewCoinbaseTX方法,观察它做了什么操作。

func NewCoinbaseTX(to,data string)*Transaction {
	if data == "" {
		data = fmt.Sprintf("Reward to %s", to)
	}
	// 任意交易输入
	txin := TXInput{[]byte{}, -1, data}
	// 交易输出:奖励旷工虚拟货币
	txout := TXOutput{subsidy, to}
	// 构造交易
	tx := Transaction{nil, []TXInput{txin}, []TXOutput{txout}}
	tx.SetID() // 设置交易ID
	return &tx
}

在NewCoinbaseTX中,它构造了一组输入交易、一组输出交易并且设置了Transaction的ID,完成Transaction的构造后,该方法将Transaction指针返回。

以下是SetID的源码。

func (tx *Transaction)SetID(){
	var result bytes.Buffer
	encoder := gob.NewEncoder(&result)
	encoder.Encode(tx)
	// 对tx的序列化结果进行sha256运算
	hash := sha256.Sum256(result.Bytes())
	// 将运算的32位结果作为tx的ID
	tx.ID = hash[:]
}

可以看到,该方法将Transaction进行了序列化,然后将序列化的结果进行了哈希运算,并且将哈希运算的值作为了Transaction的ID。这样做的目的是,我们可以通过Transaction的ID值来校验Transaction的内容(一组输入交易以及一组输出交易)是否发生了篡改。

NewCoinbaseTX方法执行完毕后,将进入到NewGenesisBlock方法中。

// 产生一个挖出创世区块的奖励事务
	cbtx := NewCoinbaseTX(address,genesisCoinbaseData)
// 构造新区块来存储记录该事务
	genesis := NewGenesisBlock(cbtx)

现在进入到NewGenesisBlock中,观察它做了什么操作。

func NewGenesisBlock(coinbase *Transaction) *Block {
	return NewBlock([]*Transaction{coinbase}, []byte{})
}

func NewBlock(transactions []*Transaction, prevBlockHash []byte) *Block {
	block := Block{}
	block.Transactions = transactions
	block.Timestamp = time.Now().Unix()
	block.PrevBlockHash = prevBlockHash
	// 工作量证明
	pow := NewProofOfWork(&block)
	nonce, hash := pow.Run()
	block.Hash = hash[:]
	block.Nonce = nonce
	return &block
}

可以看到,在NewGenesisBlock方法中,调用NewBlock构造了一个Block,在NewBlock中,比较重要的是工作量证明部分。现在进入到pow.Run()方法中,观察这个方法做了什么操作。

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)),
		},
		[]byte{},
	)

	return data
}

// Run performs a proof-of-work
func (pow *ProofOfWork) Run() (int, []byte) {
	var hashInt big.Int
	var hash [32]byte
	nonce := 0

	for nonce < maxNonce {
		data := pow.prepareData(nonce)

		hash = sha256.Sum256(data)
		fmt.Printf("\r%x", hash)
		hashInt.SetBytes(hash[:])

		if hashInt.Cmp(pow.target) == -1 {
			break
		} else {
			nonce++
		}
	}
	fmt.Print("\n\n")

	return nonce, hash[:]
}

工作量证明部分,程序通过枚举nonce来寻找符合系统难度的一串哈希值。当工作量证明完成后(得到nonce与当前区块的hash值),也就相当于挖出了一个新的区块了。

相关标签: Go