보다 더 생생한 기록

[GO] [코드리뷰] 블록체인 구축 코드 리뷰 (PoW) - (2) 본문

Go

[GO] [코드리뷰] 블록체인 구축 코드 리뷰 (PoW) - (2)

viviviviviid 2023. 9. 2. 15:31

Overview

  • 강의 진도 : 9강 Mining (41%)

 

Added Contents

Proof of Work 합의 알고리즘을 추가하였다. 채굴자는 최근 생성된 블록에서 논스만 조절하고 해시화한다.
이제 생성된 해시가 체인이 제공하는 문제의 목표기준에 부합하면, 해당 블록의 마이닝이 종료되고 블록이 체인에 추가된다.

 

난이도와 블록간 시간차를 계산하기 위해 여러 내용들이 추가되었다.

const (
	defaultDifficulty  int = 2
	difficultyInterval int = 5 // 5 블록마다 걸린 시간을 측정할것임
	blockInterval      int = 2 // 2분마다 한개 생성하는것을 목표로 잡음
	allowedRange       int = 2 // expectedTime과의 Gap차이 허용 구간
)

type blockchain struct {
	NewestHash        string 
	Height            int   
	CurrentDifficulty int   
}

이를 이용하여 매 5블록(프로젝트에서 임의로 정한 기준)이 생성될때까지 걸린 사이시간을 구하고,
이게 우리가 목표로 잡은 10분(2분차이허용: allowedRange 상수) 이하라면 난이도를 높인다.
만약 10분(2분차이허용) 이상이라면 난이도를 낮춰 목표시간에 근접하도록 조절해주는 함수를 구현하였다.

func (b *blockchain) recalculateDifficulty() int {
	allBlocks := b.Blocks()
	newestBlock := allBlocks[0]                                                         // chain.go에서 Blocks를 보면, 우리는 최근 해시부터 찾아들어갔다는 것을 확인할 수 있다. 즉 0번 인덱스를 조회해야 최근 블록내용이 나온다.
	lastRecalculatedBlock := allBlocks[difficultyInterval-1]                            // 가장 최근 업데이트된 블록
	actualTime := (newestBlock.Timestamp / 60) - (lastRecalculatedBlock.Timestamp / 60) // unix라서 60을 나눠줌으로 분단위
	expectedTime := difficultyInterval * blockInterval                                  // 우린 블록당 2분으로 예상을 했고, 5블록마다 측정한다면 이 둘의 곱은 10분이어야함.

	if actualTime < (expectedTime - allowedRange) {
		return b.CurrentDifficulty + 1
	} else if actualTime > (expectedTime + allowedRange) {
		return b.CurrentDifficulty - 1
	}
	return b.CurrentDifficulty
}

func (b *blockchain) difficulty() int {
	if b.Height == 1 {
		return defaultDifficulty
	} else if b.Height%difficultyInterval == 0 { // 우리는 매 5블록마다 걸린 시간 측정
		// 비트코인은 2016 블록마다 측정 -> 2주간 측정했을때 24 * 14 (2주시간) == 2016 / 60 (한시간마다 1블록이라고 치면)
		// 즉 2주보다 더 걸렸으면 난이도를 낮추고, 덜 걸렸으면 난이도를 높임.

		return b.recalculateDifficulty()
	} else { // 첫번째 블록이 아니면서, 난이도 조절이 필요 없을때
		return b.CurrentDifficulty
	}

}

비트코인은 2016 블록마다 측정하는데, 이는 2주간 블록을 10분마다 하나씩 해결하면 딱 들어맞는 블록개수이다.
(숫자가 정말 이쁘지 않은가)


Mining Process

1.  가장 최근 생성된 블록의 pointer값을 가져온다.

type Block struct {
	Data       string 
	Hash       string 
	PrevHash   string 
	Height     int    
	Difficulty int    
	Nonce      int    
	Timestamp  int    
}

2. 그 포인터 값을 해시화한다.

func Hash(i interface{}) string {
	s := fmt.Sprintf("%v", i) // v: default formmater
	hash := sha256.Sum256([]byte(s))
	return fmt.Sprintf("%x", hash)
}

3. 체인은 해시값의 맨 앞 문자들이 0으로 시작하길 원한다. (difficulty의 개수에 따라 0이 몇개 있어야할지 정해짐)

difficulty = 4 라면

체인이 원하는 해시값은 "0000dkjwje21ejlk32saj1241adksadjl" 이런 값이다

즉 해시의 맨 첫 4자리가 0으로 구성되어야 한다는 것이다.

 

func (b *Block) mine() { 
	target := strings.Repeat("0", b.Difficulty)
	for {
		b.Timestamp = int(time.Now().Unix())
		hash := utils.Hash(b)
		fmt.Printf("\n\n\nTarget: %s\nHash: %s\nNonce: %d\n\n\n", target, hash, b.Nonce)
		if strings.HasPrefix(hash, target) {
			b.Hash = hash
			break
		} else {
			b.Nonce++
		}
	}
}

HasPrefix(a, b)는 a의 앞부분에 b가 있는지 확인하는 strings 패키지.

 

4. 아니라면 1번에서 가져온 Block의 nonce 값을 1 올려서 다시 해싱한다. (즉 채굴자는 nonce의 값만 조절이 가능하다)

5. 그 뒤 새로 생성된 해시를 3번과정을 거친다 의 반복

 

아직 채굴자에 대한 보상 체계는 구축되지 않았다.