#2 Proof-of-Work
Blockchain의 꽃, PoW를 벌써 코딩해봅시다.
blockchain/proof.go
이전 코드에서 AddBlock함수를 부르면 무한으로 블록을 추가할 수 있었습니다. 이제 작업증명 방식을 도입해서 AddBlock함수가 불렸을 때 작업을 진행하고 작업이 완료되어야 블록을 추가할 수 있도록 바꾸겠습니다. 이 부분에 해당하는 이론적 지식은 blockchain structure 혹은 PoW 에서 찾아볼 수 있습니다.
blockchain/proof.go 파일을 생성하고 아래 내용을 붙혀넣습니다. 자세한 내용은 주석을 참고하세요.
package blockchain
import (
"bytes"
"crypto/sha256"
"encoding/binary"
"fmt"
"log"
"math"
"math/big"
)
// 우리가 찾고자하는 정답을 정의하겠습니다.
// 우리는 256bit중 왼쪽 {Difficulty}만큼의 bit가 0인 답을 원합니다.
const Difficulty = 12
type ProofOfWork struct {
Block *Block
Target *big.Int // 문제의 답 (Difficulty로 부터 얻어낸다.)
// big.Int에 관한 내용 참고 https://golang.org/pkg/math/big/
}
// Block을 받아서 ProofOfWork struct를 반환한다.
// Difficulty로 Target을 만든다.
func NewProof(b *Block) *ProofOfWork {
target := big.NewInt(1)
// 1을 왼쪽으로 256-Difficulty 만큼 이동시킨다.
// PoW에서 target보다 작은 수가 나오는 것을 정답으로 할 것이다.
// ** 작다는 뜻을 잘 생각해보자 왼쪽에 0이 Difficulty만큼 나오는 것과 같은 뜻이다.
target.Lsh(target, uint(256-Difficulty))
pow := &ProofOfWork{b, target}
return pow
}
// Block의 데이터, 이전 해시, nonce, Difficulty 값을 모두 합쳐서 데이터를 만든다.
// 이 데이터를 sha256한 값이 정답이라면 이 데이터가 적힌 블록이 추가된다.
func (pow *ProofOfWork) InitData(nonce int) []byte {
data := bytes.Join(
[][]byte{
pow.Block.PrevHash,
pow.Block.Data,
ToHex(int64(nonce)),
ToHex(int64(Difficulty)),
},
[]byte{}, // seperator
)
return data
}
// PoW를 계산하여 nonce와 정답 hash값을 반환하는 함수
func (pow *ProofOfWork) Run() (int, []byte) {
var intHash big.Int
var hash [32]byte
nonce := 0
// 사실상 무한루프
for nonce < math.MaxInt64 {
// nonce를 포함하여 계산된 데이터를 가져온다.
data := pow.InitData(nonce)
// 데이터의 해시값.
hash = sha256.Sum256(data)
fmt.Printf("\r%x", hash)
// 해시값으로 big.Int만듬
intHash.SetBytes(hash[:])
if intHash.Cmp(pow.Target) == -1 {
// Target보다 intHash가 작다는 뜻, 즉 정답.
break
} else {
// 다음 논스를 시도하자
nonce++
}
}
fmt.Println()
// nonce와 결과 hash값을 반환
return nonce, hash[:]
}
// 정답이 맞는지 검사하는 과정이다.
// Run에 비해 얼마나 쉬운지 알 수 있다. (단방향성)
func (pow *ProofOfWork) Validate() bool {
var intHash big.Int
// 블록에 포함된 Nonce를 통해 데이터 재현
data := pow.InitData(pow.Block.Nonce)
hash := sha256.Sum256(data)
intHash.SetBytes(hash[:])
return intHash.Cmp(pow.Target) == -1
}
// int64를 받아서 바이트로 변환하는 유틸리티 함수
func ToHex(num int64) []byte {
buff := new(bytes.Buffer)
err := binary.Write(buff, binary.BigEndian, num)
if err != nil {
log.Panic(err)
}
return buff.Bytes()
}blockchain/block.go
블록을 만드는 방식을 바꿔야합니다. 블록에 Nonce가 추가되었고, deriveBlock이 아닌 작업 증명 방식으로 Block을 생성하도록 코드를 수정합니다.
main.go
main을 수정해서 PoW가 잘 동작하는지 테스트합니다. 미리 만들어진 블록을 받아와서 Validate를 진행해봅니다.
메인을 실행시키면 아래와 같은 결과물이 나옵니다. //로 시작하는 부분은 추가로 주석 설명한 부분입니다.
Difficulty를 18로 바꿔서 돌려보자.
블록 추가는 어려운데, 검증은 쉬운것을 바로 체감할 수 있다.
코드는 https://github.com/siisee11/golang-blockchain 의 step2 브랜치에 있습니다 .
Last update: 04/26/2021
Last updated
Was this helpful?