Golang - Go로 구현하는 블록체인 : 작업 증명 - 2

Lumi·2022년 2월 21일
0

Golang

목록 보기
32/38
post-thumbnail

🔥 개요

참고 링크 : https://jeiwan.net

블록체인에 대해 공부를 해보고 알고 있는 사람이라면 합의 알고리즘에 대해서 들어보았을 것 입니다.

합의 알고리즘은 일종의 법칙입니다.

미국법, 대한민국법 이와 같이 나라같이 정해진 법이 있고

플랫폼마다 원하는 형태로 이러한 형식을 따르고 있습니다.

대표적으로는 PoW, PoS이런 합의 알고리즘이 있으며 저희는 이번에 PoW에 대해서 다루어 보겠습니다.

🔨 기본 구조 및 셋팅

원래 PoW라고 하면 변화하는 값이 필요하지만 저희는 그런 부분까지는 고려하지 않았기 떄문에 단순히 상수값을 통해서 작성을 해보았습니다.

  • const targetBits = 24

블록의 난이도를 의미할 변수 값 입니다.

이후 작업증명을 담당할 구조체를 선언해 줍니다.

type ProofOfWork struct {
	block  *Block
	target *big.Int
	//작업 증명을 담당할 구조체를 선언
}

여기에서 target일종의 난이도를 의미하며 해시값을 큰 정수로 변환함으로써 해당 값이 target이라는 값보다 작은지를 검증하면서 알고리즘이 작동하게 될 것입니다.

  • 이 부분에 대해서는 PoW가 어떤방식으로 검증을 하는지에 대해서 추가적으로 알아보면 좋을것 같습니다.

이후 해당 해시값을 계산해 주는 함수가 필요 합니다.

func NewProofOwWork(b *Block) *ProofOfWork {
	target := big.NewInt(1)
	target.Lsh(target, uint(256-targetBits))
	pow := &ProofOfWork{b, target}
	return pow
}
  • 안써본 패키지가 많아서 천천히 알아 보았습니다.

일단 기본적으로 target을 big.NewInt()를 통해서 초기화 해줍니다.

  • 그냥 숫자로 초기화 해주는 것과 동일합니다.
  • target = 1의 값을 가지게 됩니다.

그후 시프트 연산을 시전합니다.

시프트 연산은 상당히 복잡한 연산입니다.

만약 1 << 2 와 같은 시프트 연산을 실행한다면

0001 이라는 비트값이 --> 0100이 되는 것과 같습니다.

즉 1이라는 값이 4가 되는 효과를 나타냅니다.
- 대략 2의n배의 값을 가지게 됩니다.

코드에 보이는 대로 시프트 연산을 실시하면 아마 굉장히 큰 숫자가 나오겠죠

  • 2의24승을 곱해주는 것과 동일합니다.

이렇게 나온 값을 저희는 맞춰야하는 값으로 인식을 하고 앞으로 이러한 값보다 작은 값이 들어오게 되면 트랜잭션이 성공적으로 검증이 되는 것을 의미하게 될 것입니다.

🔨 해시계산 함수

이 부분에 대해서는 굉장히 복잡해서 이해를 하는데에 많은 시간이 걸렸습니다.

또한 int값을 hex값으로 변화하고 해당 변환값이 bytes이어야 하는 부분에서 많은 고민을 했던것 같습니다.

  • 실제로 참고했던 링크에서는 이러한 부분에 대해서 깊게 다루지는 않으셔서;; 개인적으로 공부를 해보았습니다.
func IntToHex(num int64) []byte {
	buff := new(bytes.Buffer)
	binary.Write(buff, binary.BigEndian, num)

	return buff.Bytes()
}

func (pow *ProofOfWork) prepareData(nonce int) []byte {
	data := bytes.Join([][]byte{
		pow.block.PrevBlockHash,
		pow.block.Data,
		IntToHex(pow.block.Timestamp),
		IntToHex(int64(targetBits)),
		IntToHex(int64(nonce)),
	},
		[]byte{})
	return data
}
  • 추가된 코드는 이와 같고 하나하나 다루어 보겠습니다.

일단 기본적으로 IntToHex에 대해서 알아야 합니다.

  • 숫자값을 받고 hex처리한 bytes값을 리턴합니다.

golang의 pkg사이트를 참고해보면 new(bytes.Buffer)이라는 함수는 bytes를 가지고 있는 슬라이스를 만들어 주는 함수 입니다.

이후 Write를 통해서 bytes슬라이스를 채워줍니다.

  • Write함수를 살펴보면 이런 설명을 하고 있습니다.
Write writes the binary representation of data into w. Data must be a fixed-size value or a slice of fixed-size values, or a pointer to such data. Boolean values encode as one byte: 1 for true, and 0 for false. Bytes written to w are encoded using the specified byte order and read from successive fields of the data. When writing structs, zero values are written for fields with blank (_) field names

네 저는 영어를 잘 모르지만 간단하게 알아듣게 설명을 하자면

W에 값을 입력합니다. Data는 반드시 고정값또는 고정된 사이즈를 가진 슬라이여야 합니다.
-여기에서 Data부분은 맨끝 인자를 말합니다.

에러를 한개 받을수 있고 W에 에 기록된 bytes는 특병한 방법으로 인코딩 되며 성공적인 데이터 필드에서 읽어집니다??
  • 이정도로 이해를 하였습니다..
  • 이 부분에 대해서는 youtube를 통해서 다루어 볼 것 같습니다.

그후 prepareData라는 함수를 다루어 보아야 합니다.

이 함수는 단순히 블록의 값들을 활용하여 병합을 하는 단순한 역할을 수행하고 있습니다.

🔨 핵심 코드

핵심 역할을 담당하는 부분이 될 함수 입니다.

func (pow *ProofOfWork) Run() (int, []byte) {
	var hashInt big.Int
	var hash [32]byte
	max_number := ^uint(0)
	nonce := 0

	fmt.Printf("블록 마이닝 시작!  %s\n", pow.block.Data)

	for uint(nonce) < max_number {
		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("마이닝 성공")

	return nonce, hash[:]
}
  • 이 부분에 대해서 전반적으로 모두 이해를 하지는 못하였지만 천천히 다루어 보겠습니다.

일단 기본적인 변수들을 선언해 줍니다.

  • ^uint(0)을 활용하는 이유는 비트반전을 통해서 양의값중 가장 큰 값을 도출해 내기 위함입니다.

이후 0부터 데이터를 확인해가면서 for문을 작동 시킵니다.

이떄 data는 prepareData라는 함수에 의해서 []byte값을 가지게 됩니다.

  • 이 byte값은 일종의 데이터를 담고 있는 슬라이스로 생각을 하면 됩니다.

그후 Sum256을 통해서 체크섬을 만들어 냅니다.

  • 이 부분은 잘 이해를 하지 못하였습니다;;

그후 수정된 해시값을 큰 정수로 변환을 하게 된뒤에 비교를 하게 됩니다.

🔨 NewBlock함수 수정

이제 함수에서 nonce값을 활용을 하기 떄문에 구조체와 함수를 수정해 줍니다.

type Block struct {
	Timestamp     int64
	Data          []byte
	PrevBlockHash []byte
	Hash          []byte
	Nonce 			int
}

그후 함수는 이와 같이 수정 합니다.

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

	pow := NewProofOwWork(block)
	nonce, hash := pow.Run()

	block.Hash = hash[:]
	block.Nonce = nonce
	return block
}
profile
[기술 블로그가 아닌 하루하루 기록용 블로그]

0개의 댓글