Blockchain

Go로 만드는 블록체인 part 2 - Proof of Work

hou27 2022. 1. 10. 22:35

이번엔 작업증명(Proof of Work)을 추가해보겠다. 현 상황에서는 블록을 추가할 때 아무런 과정없이 그냥 추가할 수 있지만, 실제 블록체인에서는 합의 알고리즘을 통해 블록을 생성한다. 합의 알고리즘은 암호 화폐 네트워크의 무결성과 보안을 유지하기 위해 중요하다. 합의 알고리즘은 분산화 각각의 노드들이 서로 누가 진짜인지 합의할 수 있게 한다.

Consensus algorithm

 

합의 알고리즘 - 해시넷

합의 알고리즘(consensus algorithm)이란 다수의 참여자들이 통일된 의사결정을 하기 위해 사용하는 알고리즘을 말한다. 합의 모델, 합의 방식, 합의 메커니즘 또는 합의 프로토콜이라고도 한다. 블록

wiki.hash.kr

작업 증명은 새로운 블록을 블록체인에 추가하는 ‘작업’을 완료했음을 ‘증명’하는 것이라고 이해하면 된다. 새로운 블록을 블록체인에 추가하려면, 그 새로운 블록의 블록 해쉬를 계산해내야하고, 그 블록 해쉬를 계산해내려면 그 블록의 블록 헤더 정보 중의 하나인 nonce값을 계산을 통해 구해야 한다.

 

Nonce : 최초 0에서 시작하여 조건을 만족하는 해쉬값을 찾아낼때까지의 1씩 증가하는 계산 횟수

nonce 값을 입력값 중의 하나로 하여 계산되는 블록 해쉬값이 특정 숫자보다 작아지게 하는 과정이 곧 채굴인 것이다.

 

여기서 특정 숫자가 바로 블록 해시를 생성할 때 사용하는 SHA256 알고리즘의 결과값인 256 bit에서

target bit 값만큼을 shift 연산자를 통해 왼쪽으로 비트를 밀어주어 획득하는 값이다.

 

코드와 함께 살펴보겠다.

 

우선 새로운 nonce가 block 구조체에 추가되었음을 알린다.

type Block struct {
	TimeStamp	int32 `validate:"required"`
	Hash		[]byte `validate:"required"`
	PrevHash	[]byte `validate:"required"`
	Data		[]byte `validate:"required"`
	Nonce		int `validate:"min=0"`
}

그리고 이런 구조를 선언하고 설계할 때는 bitcoin의 구조를 참고했다.(TMI)

https://www.linkedin.com/pulse/blockchain-data-structure-ronald-chan

 

Blockchain Data Structure

Good day all,                This is a write up on Blockchain data structures. We delve into the different components of a block, and then how blocks are set up to interact with each other.

www.linkedin.com

Type ProofOfWork

type ProofOfWork struct {
	block  *Block
	target *big.Int
}

Block과 Target 값을 가진 구조체를 선언해주었다.

Function NewProofOfWork

const targetBits = 6

테스트 단계에 있기 때문에 채굴 난이도는 6정도로 설정해주었다.

// Build a new ProofOfWork and return
func NewProofOfWork(b *Block) *ProofOfWork {
	target := big.NewInt(1)
	target.Lsh(target, uint(256-targetBits)) // target = 1 << 256-targetBits
	pow := &ProofOfWork{b, target}

	return pow
}

target = 1 << 256-targetBits 를 통해 타켓 값을 얻어내고, ProofOfWork 구조체에 담아준다.

function prepareData

Nonce 라는 것은 단순 Counter 이며 이는 블록 헤더와 결합되어 target 보다 더 작은 값을 찾기위해 데이터를 준비시키기 위한 메서드라고 볼 수 있다. 기존의 블록 헤더에 있던 값들과 난이도를 포함하여 nonce 를 결합한 데이터를 준비한다.

 

위에서 언급했듯이 shift연산을 통해 얻어낸 타겟 값보다 작은 값이 나올 때까지 nonce값을 계속 1씩 증가시키며 계산하는데, 그 과정에서 해시를 계산할 데이터를 준비해주는 함수이다.

func (pow *ProofOfWork) prepareData(nonce int) []byte {
	data := bytes.Join(
		[][]byte{
			[]byte(pow.block.PrevHash),
			[]byte(pow.block.Data),
			IntToHex(int64(pow.block.TimeStamp)),
			IntToHex(int64(targetBits)),
			IntToHex(int64(nonce)),
		},
		[]byte{},
	)
	return data
}

Function Run

반복문을 통해 계산을 진행하며 target과 해시된 data를 비교하여 채굴을 진행하는 함수이다.

여기서 반복문은 무한 루프이다. 타겟보다 계산된 해시값이 더 작을 때 작업을 종료한다.

// Mining
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++
		}
	}
	return nonce, hash[:]
}

Function Validate

특정 블록이 작업증명을 거친 것인지 확인하기 위한 함수이다.

// Validate hash
func (pow *ProofOfWork) Validate() bool {
	var hashInt big.Int

	hash := sha256.Sum256(
		pow.prepareData(pow.block.Nonce),
	)
	hashInt.SetBytes(hash[:])

	isValid := hashInt.Cmp(pow.target) == -1
	return isValid
}

블록들을 보여줄 때 해당 블록을 증명해줄 것이다.

// Show Blockchains
func (bc Blockchain) ShowBlocks() {
	for _, block := range GetBlockchain().blocks {
		pow := NewProofOfWork(block)
		fmt.Println("TimeStamp:", block.TimeStamp)
		fmt.Printf("Data: %s\n", block.Data)
		fmt.Printf("Hash: %x\n", block.Hash)
		fmt.Printf("Prev Hash: %x\n", block.PrevHash)
		fmt.Printf("Nonce: %d\n", block.Nonce)
		fmt.Printf("is Validated: %s\n", strconv.FormatBool(pow.Validate()))
	}
}
fmt.Printf("is Validated: %s\n", strconv.FormatBool(pow.Validate())) // Here^^

 

이제 새로 추가한 코드들을 기존 코드에 적용하도록 하겠다.

 

새로운 블록을 생성할 때 이제는 바로 해시를 계산하는게 아니라

새로운 ProofOfWork를 생성한 후 채굴을 진행한다.

 

그래서 newblock.calculateHash() 라인을 pow := NewProofOfWork(block)로 바꿔주었다.

func NewBlock(data string, prevBlockHash []byte) *Block {
	block := &Block{prevBlockHash, []byte{}, time.Now().Unix(), []byte(data), 0}
	pow := NewProofOfWork(block)
	block.Nonce, block.Hash = pow.Run()

	return block
}

 

package main

import "strconv"

func main() {
	chain := GetBlockchain()
	for i := 1; i < 10; i++ {
		chain.AddBlock(strconv.Itoa(i))
	}
	chain.ShowBlocks()
}

 

 

블록 하나하나가 추가될 때마다 Added를 출력하며,

마지막으로 블록 전체를 출력해주도록 하였다.

 

실행 시 모습 :

0166be9d913999feea4fc94b818202d6d2b12484c201a6f223b311fcc6b385fd Added
0215c646e6a3faac0f954e8bf88263816aefefdbeeb9363f6c7b8b388b2303a6 Added
00b2aac6a4f0130af48d941ef153cf5c4c64d7a17f9b71ba17d2f1995d55cc33 Added
02913ec4213f7329574ce2a8cc25b0a0e92b00a40e35403cc48a68ffe21075a8 Added
026d60bcf8b6ff1652e2c065c593162e109070b91fba291d0e3b5e3261e281fd Added
00f04419689bcefb43fe386381f81dcf86e5c72a91c43444e015f22863ad1e39 Added
005c4a22a662cc653f6d60bd19eeb6f243705298e76bc485fa22d91c7edb2283 Added
020868cc9190e17d1b9a9e05f27ba4358d5a6c4a0491446ba45628ccfbd3239b Added
01cebbd78196c674e64a50854b1ec9be7da8c0804e47d43721fee51589e5a81d Added
03a312dd6b769a858733a0f3131b02084e90c30d03b338f1ea529734e3703245 Added
TimeStamp: 1642923947
Data: Genesis Block
Hash: 0166be9d913999feea4fc94b818202d6d2b12484c201a6f223b311fcc6b385fd
Prev Hash: 
Nonce: 47
is Validated: true
TimeStamp: 1642923947
Data: 1
Hash: 0215c646e6a3faac0f954e8bf88263816aefefdbeeb9363f6c7b8b388b2303a6
Prev Hash: 0166be9d913999feea4fc94b818202d6d2b12484c201a6f223b311fcc6b385fd
Nonce: 36
is Validated: true
TimeStamp: 1642923947
Data: 2
Hash: 00b2aac6a4f0130af48d941ef153cf5c4c64d7a17f9b71ba17d2f1995d55cc33
Prev Hash: 0215c646e6a3faac0f954e8bf88263816aefefdbeeb9363f6c7b8b388b2303a6
Nonce: 47
is Validated: true
TimeStamp: 1642923947
Data: 3
Hash: 02913ec4213f7329574ce2a8cc25b0a0e92b00a40e35403cc48a68ffe21075a8
Prev Hash: 00b2aac6a4f0130af48d941ef153cf5c4c64d7a17f9b71ba17d2f1995d55cc33
Nonce: 21
is Validated: true
TimeStamp: 1642923947
Data: 4
Hash: 026d60bcf8b6ff1652e2c065c593162e109070b91fba291d0e3b5e3261e281fd
Prev Hash: 02913ec4213f7329574ce2a8cc25b0a0e92b00a40e35403cc48a68ffe21075a8
Nonce: 0
is Validated: true
TimeStamp: 1642923947
Data: 5
Hash: 00f04419689bcefb43fe386381f81dcf86e5c72a91c43444e015f22863ad1e39
Prev Hash: 026d60bcf8b6ff1652e2c065c593162e109070b91fba291d0e3b5e3261e281fd
Nonce: 137
is Validated: true
TimeStamp: 1642923947
Data: 6
Hash: 005c4a22a662cc653f6d60bd19eeb6f243705298e76bc485fa22d91c7edb2283
Prev Hash: 00f04419689bcefb43fe386381f81dcf86e5c72a91c43444e015f22863ad1e39
Nonce: 188
is Validated: true
TimeStamp: 1642923947
Data: 7
Hash: 020868cc9190e17d1b9a9e05f27ba4358d5a6c4a0491446ba45628ccfbd3239b
Prev Hash: 005c4a22a662cc653f6d60bd19eeb6f243705298e76bc485fa22d91c7edb2283
Nonce: 320
is Validated: true
TimeStamp: 1642923947
Data: 8
Hash: 01cebbd78196c674e64a50854b1ec9be7da8c0804e47d43721fee51589e5a81d
Prev Hash: 020868cc9190e17d1b9a9e05f27ba4358d5a6c4a0491446ba45628ccfbd3239b
Nonce: 113
is Validated: true
TimeStamp: 1642923947
Data: 9
Hash: 03a312dd6b769a858733a0f3131b02084e90c30d03b338f1ea529734e3703245
Prev Hash: 01cebbd78196c674e64a50854b1ec9be7da8c0804e47d43721fee51589e5a81d
Nonce: 53
is Validated: true

https://github.com/hou27/blockchain_go/tree/part2

 

GitHub - hou27/blockchain_go: Making a Cryptocurrency with GO.

Making a Cryptocurrency with GO. . Contribute to hou27/blockchain_go development by creating an account on GitHub.

github.com

 


참고자료

 

blockchain structure

block header