Golang|区块链Transaction实现源码分析
区块链Transaction实现源码分析
资源
程序运行结果
源码分析
当程序解析到命令行参数时,将调用结构体CLI的createBlockchain方法。这个方法的作用是在本地数据库中创建一个含有创世区块的区块链。(如果在本地数据库中已经含有区块链,则不执行创建)
进入到该方法的源码部分。
// 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值),也就相当于挖出了一个新的区块了。
上一篇: MyBatis之Transaction
下一篇: mybatis源码解析