DS & Algorithm/열혈 자료구조 책 study

BlockChain개발을 한다면 어떤 자료구조를 사용할까

JaeHyunShin 2020. 1. 20. 03:55

알고리즘 수업에서 blchain을 위한 전용 알고리즘을 배우지는 않는다

기존것을 활용하지 않고 새 알고리즘이 나올 여지가 있다면 논문을 위한 blue

 

블록

“블록체인”의 “블록”부터 이야기해 보겠습니다. 블록체인에서 중요한 정보를 저장하는 것이 블록입니다. 예를 들어, 비트코인 블록에 저장된 유효한 정보는 비트코인 거래 (Transaction)이며, 거래 정보는 모든 암호화폐의 본질입니다. 이 외에도, 블록에는 버전, 현재 타임 스탬프 및 이전 블록의 해시와 같은 몇몇 기술 정보를 포함하고 있습니다.
이글에서 우리는 비트코인 기술 규범에 묘사된 것과 같은 블록체인을 구현하지는 않지만, 몇 가지 중요한 정보를 포함하고 있는 간소화된 블록체인을 구현할 것입니다.

간소화된 블록체인은 이런 모습일 것이다:

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

  • Timestamp 현재 타임스탬프. 즉, 블록이 만들어 지는 시간.
  • Data는 블록 저장의 실질적이고 효율적인 정보이다.
  • PrevBlockHash는 이전 블록의 해시를 저장한다.
  • Hash는 현재 블록의 해시이다.

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

그럼 우리는 해시를 어떻게 계산해야 할까? 해시를 어떻게 계산하느냐가 블록 체인에서 매우 중요한 부분이다. 바로 이 특성 때문에 블록체인이 안전하다고 볼 수 있다. 하나의 해시를 계산하는 것이 계산 상으로 매우 어려운 작업이므로, 성능좋은 PC에서도 적지 않은 시간이 걸립니다. (이것이 바로 왜 사람들이 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()
}
}