TIL 29 - Golang으로 블록체인 만들기(RESTful하게 채굴하기) 10

프동프동·2023년 2월 10일
0

TIL

목록 보기
29/46
post-thumbnail

구현

네트워크 : difficulty의 값만큼 0의 개수를 증가시켜 난이도를 어렵게 만든다

채굴자 : nonce 값을 변경해가며 0의 개수가 맞는 것을 찾아낸다.

  • 소스 코드
    • main.go
      package main
      
      import (
      	"crypto/sha256"
      	"fmt"
      	"strings"
      )
      
      func main() {
      	difficulty := 2
      	// target := "0" * 2
      	// 첫번째 인자값을 두번째 인자값 만큼 연결해서 출력해준다.
      	target := strings.Repeat("0", difficulty)
      	nonce := 1
      	for {
      		// 16진수 string으로 변환
      		hash := fmt.Sprintf("%x", sha256.Sum256([]byte("hello"+fmt.Sprint(nonce))))
      		fmt.Printf("Hash:%s\nTarget:%s\nNonce:%d\n\n", hash, target, nonce)
      		if strings.HasPrefix(hash, target) {
      			return
      		} else {
      			nonce++
      		}
      	}
      
      }
  • 실행 결과
    • difficulty가 2일 때
      Hash:001b92541ed0a22b0cb89018b561d895503206c0082c0ecf2d0b7e5182191eed
      Target:00
      Nonce:227
      • 227번만에 찾았다.
    • difficulty가 3일 때
      Hash:0006bc9ad4253c42e32b546dc17e5ea3fedaecdabef371b09906cea9387e8695
      Target:000
      Nonce:10284
      • 10284번 걸렸다.
    • difficulty가 4일 때
      Hash:0000e49eab06aa7a6b3aef7708991b91a7e01451fd67f520b832b89b18f4e7de
      Target:0000
      Nonce:60067
      • 60067번
  • 난이도가 조금만 올라가도 엄청나게 연산이 많이 필요한 것을 느낄 수 있다.
  • 실제 비트코인은 좀 더 복잡하다

상기 코드를 추가하여 기존 코드에 채굴기능 만들기

  • 블록 구조체 자체를 해쉬함수를 통해 해시하기
  • 소스 코드
    • blockchain/block.go
      func (b *Block) mine() {
      	target := strings.Repeat("0", b.Difficulty)
      	for {
      		blockAsString := fmt.Sprint(b)
      		// 블록을 string으로 바꾼 후 해쉬로 변환시킨다음 16진수 string으로 다시 변환한다.
      		hash := fmt.Sprintf("%x", sha256.Sum256([]byte(blockAsString)))
      		fmt.Printf("Block as String:%s\nHash:%s\nTarget:%s\nNonce:%d\n\n\n", blockAsString, hash, target, b.Nonce)
      		if strings.HasPrefix(hash, target) {
      			b.Hash = hash
      			break
      		} else {
      			b.Nonce++
      		}
      
      	}
      }
  • 실행 결과
    • Method : GET

    • URL : http://localhost:4000/blocks

      HTTP/1.1 200 OK
      Date: Sat, 31 Dec 2022 02:47:41 GMT
      Content-Length: 138
      Content-Type: text/plain; charset=utf-8
      Connection: close
      
      [
        {
          "data": "Genesis Block",
          "hash": "0056f988ce765e06b2fccc508947a1771b1822f889c3594bc174aa6032fc688c",
          "height": 1,
          "defficulty": 2,
          "nonce": 76
        }
      ]

Defficulty 자동으로 수정되게 변경하기

  • 비트코인에서 착안
    • 1개의 블록 생성에 10분
    • 2016개의 블록을 생성하는데 2주
      • 2주보다 적게 걸렷으면 Difficulty 늘리기
      • 2주보다 많이 걸렸으면 Difficulty 줄이기

8분~ 12분 사이 5개 블록이 생성되는 것을 기준으로 코드 작성

  • 조건
    • 초기 난이도는 2
      • 해시 값이 맨앞자리 부터 00으로 시작하면 검증완료
    • 5개 블록 생성 시 8분~12분 사이에 생성된 블록은 난이도 유지
    • 8분 미만 시 5개 블록이 생성되면
      • 난이도 높이기
    • 12분 초과 시 5개 블록이 생성되면
      • 난이도 낮추기
  • Block에 Timestamp 추가하기
    • 블록의 생성에 얼마나 시간이 걸렸는지 확인하기 위함
  • REST API로 확인하기 위해 내용 추가
  • 소스 코드
    • chain.go
      
      const (
      	defaultDifficulty  int = 2
      	difficultyInterval int = 5
      	blockInterval      int = 2
      	allowedRange       int = 2
      )
      
      type blockchain struct {
      	// 최근에 등록된 Hash
      	NewestHash string `json:"newestHash"`
      	// 블록의 수
      	Height            int `json:"height"`
      	// 현재 난이도
      	CurrentDifficulty int `json:"currentdifficulty"`
      }
      
      func (b *blockchain) AddBlock(data string) {
      	block := createBlock(data, b.NewestHash, b.Height+1)
      	b.NewestHash = block.Hash
      	b.Height = block.Height
      	b.CurrentDifficulty = block.Difficulty
      	b.persist()
      }
      
      func (b *blockchain) recalculateDifficulty() int {
      	allBlocks := b.Blocks()
      	// 블록 슬라이스에 가장 최근 블록이 앞에 들어간다.
      	newestBlock := allBlocks[0]
      	// 가장 최근에 난이도가 재설정된 블록은 allBlock[5-1]이다.
      	lastRecalculatedBlock := allBlocks[difficultyInterval-1]
      	// 두 블록 사이에 걸린시간 : 최근 생성된 블록의 시간 - 블록의 난이도가 재설정된 후 생성된 블록의 시간
      	// 타임스탬프가 Unix Time이기에 초단위로 변경해줘야한다.
      	actualTime := (newestBlock.Timestamp / 60) - (lastRecalculatedBlock.Timestamp / 60)
      	// 예상 시간 : 5 * 2 = 10분 기준으로 난이도를 설정한다.
      	expectedTime := difficultyInterval * blockInterval
      
      	// 10분을 기준으로 앞뒤로 2분씩 범위안에만 들어오면 난이도를 유지한다.
      	// 예상 시간 보다 빠르게 블록이 생성되면 난이도를 1만큼 증가시킨다.
      	if actualTime <= (expectedTime - allowedRange) {
      		return b.CurrentDifficulty + 1
      	} else if actualTime >= (expectedTime + allowedRange) {
      		// 예상 시간 보다 느리게 블록이 생성되면 난이도를 1만큼 감소시킨다.
      		return b.CurrentDifficulty - 1
      	}
      	return b.CurrentDifficulty
      }
      
      func (b *blockchain) difficulty() int {
      	// 제네시스 블록체인은 Difficulty가 2다.
      	if b.Height == 0 {
      		return defaultDifficulty
      	} else if b.Height%difficultyInterval == 0 {
      		// 비트코인은 2016개, 우리의 블록체인은 5개 블록마다 체크하여 난이도를 조정한다
      		return b.recalculateDifficulty()
      	} else {
      		// 난이도가 변경된 후 블록이 5개가 추가되지 않았으면 현재 난이도를 그대로 유지한다.
      		return b.CurrentDifficulty
      	}
    • block.go
      type Block struct {
      	Data       string `json:"data"`
      	Hash       string `json:"hash"`
      	PrevHash   string `json:"prevhash,omitempty"`
      	Height     int    `json:"height"`
      	Difficulty int    `json:"defficulty"`
      	Nonce      int    `json:"nonce"`
      	Timestamp  int    `json:"timestamp"`
      }
      
      func (b *Block) mine() {
      	target := strings.Repeat("0", b.Difficulty)
      	for {
      		// time.Now().Unix() : int64를 반환한다. 1970년 1월 1일 UTC로부터 흐른 시간을 초단위로
      		b.Timestamp = int(time.Now().Unix())
      		hash := utils.Hash(b)
      
      	if strings.HasPrefix(hash, target) {
      		b.Hash = hash
      		break
      	} else {
      		b.Nonce++
      	}
      }
      
      func createBlock(data string, prevHash string, height int) *Block {
      	block := &Block{
      		Data:       data,
      		Hash:       "",
      		PrevHash:   prevHash,
      		Height:     height,
      		Difficulty: Blockchain().difficulty(),
      		Nonce:      0,
      	}
      	block.mine()
      	block.persist()
      	return block
      }
      
  • 실행 결과
    • Method : GET

    • URL : http://localhost:4000/blocks

    • 기능 : 현재 생성된 블록 모두 가져오기

    • 결과

      HTTP/1.1 200 OK
      Content-Type: application/json
      Date: Sat, 31 Dec 2022 04:28:23 GMT
      Content-Length: 162
      Connection: close
      
      [
        {
          "data": "Genesis Block",
          "hash": "007b0d340726556946f5121d17bb7bb2f6b892e037a6e4cd783336aa0ff497fe",
          "height": 1,
          "defficulty": 2,
          "nonce": 234,
          "timestamp": 1672460696
        }
      ]
    • Method : GET

    • URL : http://localhost:4000/status

    • 기능 : 현재 체인의 상태 가져오기

    • 결과

      HTTP/1.1 200 OK
      Content-Type: application/json
      Date: Sat, 31 Dec 2022 04:27:48 GMT
      Content-Length: 115
      Connection: close
      
      {
        "newestHash": "007b0d340726556946f5121d17bb7bb2f6b892e037a6e4cd783336aa0ff497fe",
        "height": 1,
        "currentdifficulty": 2
      }
    • Method : POST

    • URL : http://localhost:4000/blocks

    • 기능 : JSON형태의 값을 받아 블록 하나 생성하기

      • 전달 된 값
        {
          "message" : "Blockchain Test"
        }
    • 결과

      ```go
      HTTP/1.1 201 Created
      Content-Type: application/json
      Date: Sat, 31 Dec 2022 04:29:35 GMT
      Content-Length: 0
      Connection: close
      ```

      시나리오 진행

      총 5개의 블록을 생성했을 때 8분 미만으로 5개의 블록이 생성되면 난이도가 1 올라간다.

    • 초기 상태

      {
        "newestHash": "00005e8d76daa1d457e5cde329901a1c1bd352cb00e4d626f3571c6431d2dd36",
        "height": 1,
        "currentdifficulty": 2
      }
    • 블록이 6번째에 난이도가 변한것을 알 수 있다.

      {
        "newestHash": "000e4683d1ca7256e62118d1cffde70b5957f9813d32e6f7f0b4873f5df1d81c",
        "height": 6,
        "currentdifficulty": 3
      }
    • 난이도가 재설정된 블록 부터 시작해서 8분 미만으로 5개의 블록이 추가로 생성되었기에 난이도가 또 올라간 것을 확인할 수 있다.

      {
        "newestHash": "0000be41b16c285e84f5b5deec30317127f1612e4c66e8d5016e3ce484a100ea",
        "height": 11,
        "currentdifficulty": 4
      }
profile
좋은 개발자가 되고싶은

0개의 댓글