本文接上一篇文单《200行go代码实现区块链》,在上文中我们使用go语言实现了一个简单的区块链,本文我们在其基础上添加挖矿功能。
原文出处《Code your own blockchain mining algorithm in Go!》
加密货币必须保持其稀缺性才能保证其价值 ,假如任何人都可以在任何时间产生任意多的比特币,那么比特币将一文不值。比特币算法每隔10分种就会向网络中的发现下一个区块的成员(节点)发送一定量的比特币。
节点需要与其他节点竟争共同做一项“工作”,这个工作就是挖矿。
如果你在google中搜索“比特币挖矿是如何工作的”,你将会得到很多结果,大部分都会告诉你是“解一个数学难题”,从技术角度将这个回答是没问题的,但是却是非常无用且平庸,为了理解比特币是如何挖矿的我们需要了解一点加密算法和哈希算法。
单向加密算法是使用一个加密函数将可读明文如:“Hello World” 转换为不可读的密文。这个加密函数广泛存在且复杂。越复杂的算法对密码逆向工程来说就越难破解(PS:这个不一定对)。如加密算法广泛的应用于存储用户密码。
我们以SHA-256为例,这个网站hashing website可以很方便的将数据进行SHA-256计算。如我们对”Hello world”进行SHA-256计算,可以得到如下结果
无论我们计算多少次”Hello world”的SHA-256值,你得到的结果都是同一个,这种特性叫做 幂等性
加密算法的一个基础特性是很容易由明文产生密文,但是很难由密文推断出明文,就以上边”Hello world“ 为例,我们很容易的就能计算出Hello world 的SHA-256值 ,但你从其SHA-256值 很难得到”Hello world”,这就叫做单向加密。
比特币使用双层SHA-256加密算法,也就是将第一次SHA-256计算得到的值再进行一次SHA-256计算得到最终结果。
现在我们了解了加密算法,我们可以开始聊数字货币挖矿了。比特币需要找到一种方法让参与者为了挣得比特币而工作,所以其发行的速度不能太快。比特币使参与者尝试不同的组合直到产生的哈希值变成由指定个数的0开头的值。
如,我们使用 hash website 计算 886 其哈希值是由3个0开头的。
但是我们怎么知道886的哈希值是由3个0开头的呢?这就是重点,在写这篇博客时我们是不知道的。我们需要尝试不同的数字、字符计算并检测其哈希值直到找到一个组合使得其哈希值是以3个0开头的。
实际上任何人可以很容易的检验886的哈希值是否是以3个0开头的证明我们是做了很多次尝试使用了很多不同组合才得到这样的结果。因此假如我是第一个发现这个结果,我们将得到比特币奖励,因为其他节点很容易就能验证886的哈希值是以3个0开头的,这就是为什么说比特币的一致性算法是Proof-of-work (POW:工作量证明)
比特币难度更大一些(需要以更多的0开头),比特币网络动态的调整开头0的个数以保证每10分钟左右出一个块,这就是动态调整出块难度。
在具备充足的背景知识后我们可以开始实现挖矿代码了。
我们基于上一篇文章的代码,在其中添加挖矿相关的方法。
修改main.go中的相关代码
const difficulty = 1
type Block struct {
Index int
Timestamp string
BPM int
Hash string
PrevHash string
Difficulty int
Nonce string
}
var mutex = &sync.Mutex{}
difficulty 表示我们要求生成的哈希值开头的0的个数。起始状态下我们仅仅要求以一个0开头
在Block 结构中我们添加了一些字段,Difficulty 和Noncy
我们需要修改一下写入新块的方法如下:
func handleWriteBlock(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
var m Message
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&m); err != nil {
respondWithJSON(w, r, http.StatusBadRequest, r.Body)
return
}
defer r.Body.Close()
//ensure atomicity when creating new block
mutex.Lock()
newBlock := generateBlock(Blockchain[len(Blockchain)-1], m.BPM)
mutex.Unlock()
if isBlockValid(newBlock, Blockchain[len(Blockchain)-1]) {
Blockchain = append(Blockchain, newBlock)
spew.Dump(Blockchain)
}
respondWithJSON(w, r, http.StatusCreated, newBlock)
}
注意上述的mutex锁,这里加个锁以保证只有一个线程在产生下一个块(比特币实际上每时每刻都有很多节点在试图产生新的块,但本文里仅用于演示算法,所以只由一个纯生来产生下一个符合难度要求的块)
计算哈希值的方法也需要修改
func calculateHash(block Block) string {
record := strconv.Itoa(block.Index) + block.Timestamp + strconv.Itoa(block.BPM) + block.PrevHash + block.Nonce
h := sha256.New()
h.Write([]byte(record))
hashed := h.Sum(nil)
return hex.EncodeToString(hashed)
}
我们添加一个方法以验证哈希是否以指difficulty个0开头的
func isHashValid(hash string, difficulty int) bool {
prefix := strings.Repeat("0", difficulty)
return strings.HasPrefix(hash, prefix)
}
然后我们修改generateBlock函数
func generateBlock(oldBlock Block, BPM int) Block {
var newBlock Block
t := time.Now()
newBlock.Index = oldBlock.Index + 1
newBlock.Timestamp = t.String()
newBlock.BPM = BPM
newBlock.PrevHash = oldBlock.Hash
newBlock.Difficulty = difficulty
for i := 0; ; i++ {
hex := fmt.Sprintf("%x", i)
newBlock.Nonce = hex
if !isHashValid(calculateHash(newBlock), newBlock.Difficulty) {
fmt.Println(calculateHash(newBlock), " do more work!")
time.Sleep(time.Second)
continue
} else {
fmt.Println(calculateHash(newBlock), " work done!")
newBlock.Hash = calculateHash(newBlock)
break
}
}
return newBlock
}
最后我们也需要对main方法进行修改
func main() {
err := godotenv.Load()
if err != nil {
log.Fatal(err)
}
go func() {
t := time.Now()
genesisBlock := Block{}
genesisBlock = Block{0, t.String(), 0, calculateHash(genesisBlock), "", difficulty, ""}
spew.Dump(genesisBlock)
mutex.Lock()
Blockchain = append(Blockchain, genesisBlock)
mutex.Unlock()
}()
log.Fatal(run())
}
以上就完成了所有代码,大家可以使用上一篇文章的测试方法进行测试了,下面我就不多写了,
附上英文原文的PDF版链接以方便一些用户。
《Code your own blockchain mining algorithm in Go!》