GO언어 - 블록체인 구현해보기
블록체인
GO언어를 처음 접하였을때 제일 관심있었던게 블록체인이다. 블록체인 알고리즘 자체가 신기하였고 나도 이 기회에 공부해보고 싶었던 것이 컸다.
GO언어를 이용하여 최근에 블록체인을 구현한다고 하는데 아무래도 제일 큰 이유 중 하나가 코드가 간단하며 실행속도가 매우 빨라서 그런 것 같다.
이 기회에 블록체인에 대해서 참고자료를 통해 간단하게 구현해보려고 한다.
[참고 자료]
- Github 자료 : https://github.com/mingrammer/blockchain-tutorial
- Medium : https://medium.com/@kimjunyong/go-%EC%96%B8%EC%96%B4%EB%A5%BC-%ED%86%B5%ED%95%B4-%EB%B8%94%EB%A1%9D%EC%B2%B4%EC%9D%B8-%EA%B0%9C%EB%B0%9C%ED%95%98%EA%B8%B0-1%EB%B6%80-%EA%B8%B0%EB%B3%B8-%ED%94%84%EB%A1%9C%ED%86%A0-%ED%83%80%EC%9E%85-942e9fe1382a
블록
“블록체인”의 “블록”부터 본다면 블록체인에서 중요한 정보를 저장하는 것이 블록이라고 할 수 있다.
예를 들어, 비트코인 블록에 저장된 유효한 정보는 비트코인 거래 (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
Comments