GO언어 - 블록체인 구현해보기

블록체인

GO언어를 처음 접하였을때 제일 관심있었던게 블록체인이다. 블록체인 알고리즘 자체가 신기하였고 나도 이 기회에 공부해보고 싶었던 것이 컸다.
GO언어를 이용하여 최근에 블록체인을 구현한다고 하는데 아무래도 제일 큰 이유 중 하나가 코드가 간단하며 실행속도가 매우 빨라서 그런 것 같다.
이 기회에 블록체인에 대해서 참고자료를 통해 간단하게 구현해보려고 한다.

[참고 자료]

블록

“블록체인”의 “블록”부터 본다면 블록체인에서 중요한 정보를 저장하는 것이 블록이라고 할 수 있다.
예를 들어, 비트코인 블록에 저장된 유효한 정보는 비트코인 거래 (Transaction)이며, 거래 정보는 모든 암호화폐의 본질입니다.
이 외에도, 블록에는 버전, 현재 타임 스탬프 및 이전 블록의 해시와 같은 몇몇 기술 정보를 포함하고 있습니다.
엄청 복잡한 내용들을 축약하고 몇 가지 중요한 정보를 포함하고 있는 간소화된 블록체인은 아래와 같다.

type Block struct {
    Timestamp     int64
    Data          []byte
    PrevBlockHash []byte
    Hash          []byte
}
  • Timestamp : 현재 타임스탬프 (블록이 만들어 지는 시간)
  • Data : 블록 저장의 실질적이고 효율적인 정보
  • PrevBlockHash : 이전 블록의 해시를 저장해주는 것
  • Hash : 현재 블록의 해시

해시

비트코인 기술 규범에서 Timestamp, PrevBlockHash, Hash는 블록 헤드(block header)이며 블록 헤드는 하나의 별도 데이터 구조이다.
또 거래의 Data는 또 다른 별개의 데이터 구조이다. 간편함을 위해 이 두 개를 같이 섞어놨다고 할 수 있다.

이때 우리는 해시를 계산해야 되는데 해시를 어떻게 계산하느냐가 블록 체인에서 매우 중요한 부분이다. 바로 이 특성 때문에 블록체인이 안전하다고 볼 수 있다.
하나의 해시를 계산하는 것이 계산 상으로 매우 어려운 작업이기 때문에 사람들이 GPU를 사서 비트코인을 캐는 이유라고 볼 수 있다.
이는 의도적인 아키텍처 설계로, 새로운 블록을 추가하는 것을 매우 어렵게 하여, 블록이 일단 가입되면 다시 수정하기 어렵다는 것을 보증할수 있다. 앞으로 이 매커니즘을 깊게 공부해보도록 하겠다.

해시의 계산

현재, 우리는 블록 필드(Timestamp, Data 및 PrevBlockHash)를 가져와서 서로 연결한 다음, 결과에 SHA-256의 해시를 계산합니다. SetHash 메소드를 만들어보겠다.

해시 계산을 위한 메소드

func (b *Block) SetHash() {
    timestamp := []byte(strconv.FormatInt(b.Timestamp, 10))
    headers := bytes.Join([][]byte{b.PrevBlockHash, b.Data, timestamp}, []byte{})
    hash := sha256.Sum256(headers)

    b.Hash = hash[:]
}

그리고 Golang의 관례에 따라 하나의 블록 생성을 단순화하는 함수를 구현한다.

블록 생성 함수

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


블록체인의 구현

이제 하나의 블록체인을 구현해 보겠다. 본질적으로, 블록체인은 단지 특정 구조를 가진 데이터베이스일 뿐이고, 순서가 지정되고 연결된 리스트이다.
즉, 블록이 삽입된 순서대로 저장되고 각 블록이 이전 블록에 연결되는 형태이고 이런 구조는 체인의 최신 블록을 신속하게 가져올 수 있고 해시를 통해 한 블록을 효율적으로 검색할 수 있게 해준다.

Golang 에서는, 하나의 배열(array)과 맵(map)으로 구조를 구현한다고 한다.
array는 정렬된 해시를 저장하고, map은 hash ->block (Golang에서 map은 순서가 지정되지 않음)을 저장한다.
하지만, 지금 구현해볼 블록체인 프로토타입의 경우 배열을 사용하는데 그 이유는 해시를 통해 블록을 얻을 필요가 없기 때문이다.

블록 제작

type Blockchain struct {
    blocks []*Block
}

첫 번째 블록 체인 형태를 만들어보았다. 형태가 매우 간단하게 생겨먹었다.
그리고 블록을 추가하도록 해주는 함수까지 구현해주겠다.

블록 추가 함수

func (bc *Blockchain) AddBlock(data string) {
    prevBlock := bc.blocks[len(bc.blocks)-1]
    newBlock := NewBlock(data, prevBlock.Hash)
    bc.blocks = append(bc.blocks, newBlock)
}

새로운 블록을 추가하기 위해서 우리는 기존 블록이 필요하지만 현재 우리의 체인에는 블록이 없다는 문제를 발견하였다.
어떠한 블록체인이라도 적어도 하나의 블록은 있어야 하며, 이러한 블록, 즉 체인의 첫 번째 블록을 흔히 제네시스 블록 (genesis block)이라고 부른다.

제네시스 블록

func NewGenesisBlock() *Block {
    return NewBlock("Genesis Block", []byte{})
}

우리는 하나의 함수를 구현하여 제네시스 블록이 있는 블록체인을 만들 수 있습니다

func NewBlockchain() *Blockchain {
    return &Blockchain{[]*Block{NewGenesisBlock()}}
}

기본적인 세팅을 완료하였다. 이제 메인함수를 통해 블록체인의 동작상태를 확인해보도록 하겠다.

func main() {
    bc := NewBlockchain()

    bc.AddBlock("Send 1 BTC to Ivan")
    bc.AddBlock("Send 2 more BTC to Ivan")

    for _, block := range bc.blocks {
        fmt.Printf("Prev. hash: %x\n", block.PrevBlockHash)
        fmt.Printf("Data: %s\n", block.Data)
        fmt.Printf("Hash: %x\n", block.Hash)
        fmt.Println()
    }
}


실행 결과

Prev. hash: 
Data: Genesis Block
Hash: 9b1be1ea023c7071c897448e043f48b9fcf6e04bebdd3f260cbca9c915d3b89a

Prev. hash: 9b1be1ea023c7071c897448e043f48b9fcf6e04bebdd3f260cbca9c915d3b89a
Data: Send 1 BTC to Ivan
Hash: 5421545761c1bac0b71bae9c5af3265446474552b2b6b145ad3f408acaa0ba77

Prev. hash: 5421545761c1bac0b71bae9c5af3265446474552b2b6b145ad3f408acaa0ba77
Data: Send 2 more BTC to Ivan
Hash: 5e6bae0e842de66a7034f60e74fdab305ef2e6ecd8c6b007d243f31a5f5cc0e4

Categories:

Updated:

Comments