[블록체인 개발 공부] 블록체인 개발하기 PART 1

in #blockchain7 years ago (edited)

이 글은 아래 블로그(Coral Health)의 내용을 번역한 것입니다.

https://medium.com/@mycoralhealth/code-your-own-blockchain-in-less-than-200-lines-of-go-e296282bcffc

먼저 블록체인을 공부하기 위해서는 다음과 같은 과정이 필요합니다.

  • 나만의 블록체인 만들어 보기
  • 해싱 워크가 어떻게 블록체인의 무결성을 유지시키는지에 대한 이해
  • 어떻게 새로운 블락들이 생기는지 알기
  • 여러 노드에서 동시에 블록이 생성될때 이를 어떻게 해결하는지에 대한 이해
  • 웹브라우저에서 블록체인 확인
  • 새로운 블록 작성

블록체인 시작

Go lang을 사용하여 블록체인을 만들어 봅시다.

환경 설정

먼저 Go lang을 설치하고 configure를 설정 해야하는데 다음 패키지들을 설치해야합니다.

Spew 설치
go get github.com/davecgh/go-spew/spew

Spew는 콘솔에서 Go data구조와 각각의 부분을 완벽한 포맷으로 볼수 있게 해주는 패키지 입니다. 디버깅을 할때 매우 유용합니다.

Gorilla/mux설치
go get github.com/gorilla/mux
Gorilla/mux 는 웹 핸들러를 작성하기 위해 많이 사용하는 패키지입니다.

Gotdotenv
go get github.com/joho/godotenv
Gotdotenv는 .env파일(root 디렉토리에서 http port같이 하드코드를 할 수 있는 파일)로 부터 읽을수 있게 해주는 패키지 입니다.

소스가 있는 root 디렉토리에 .env 파일을 생성하고 다음과 같이 http 포트 작성하면 됩니다.

ADDR=8080

main.go 파일을 생성하시고 코딩을 시작해봅시다.

Imports

먼저 개발에 필요한 라이브러리를 import 해야 합니다. 다음과 같이 선언하면 됩니다.

package main

import (
    "crypto/sha256"
    "encoding/hex"
    "encoding/json"
    "io"
    "log"
    "net/http"
    "os"
    "time"

    "github.com/davecgh/go-spew/spew"
    "github.com/gorilla/mux"
    "github.com/joho/godotenv"
)

Data model

블록체인을 구성하기위한 구조체를 정의합니다.

type Block struct {
    Index     int
    Timestamp string
    BPM       int
    Hash      string
    PrevHash  string
}

각각의 블록에는 데이터들이 존재합니다.

Index는 블록체인에서 데이터 레코드의 위치를 나타냅니다.
Timestamp 는 데이터가 작성될때의 시간이 작성되며 자동으로 결정됩니다.
BPM 또는 beats per minute, 이것은 pulse rate을 의미합니다.
Hash 는 SHA256를 이용하며 이 데이타 레코드의 식별을 하는데 사용됩니다.
PrevHash 이전 데이터 레코드의 Hash를 의미합니다.

var Blockchain []Block


블록과 블록체인에서 해시가 어떻게 사용되는가?

해시는 블록을 식별하고 올바른 순서를 유지하기 위해 사용합니다.
각 블록의 PreHash가 이전 블록 해시와 동일 한지를 보장함으로써 체인의 구성된 블록들이 순서가 맞는지 알 수 있습니다.

해싱과 새로운 블록 생성
왜 해싱이 필요한가? 그 이유는 2가지가 있습니다.

첫번째 이유는 공간을 절약하기 위해서 입니다.
해시는 데이터 블록에 있는 모든 데이터로 부터 얻습니다.
우리의 간단히 테스트용을 위해 개발했기 때문에 단지 몇가지 데이터 포인트를 가집니다.
그러나 우리가 백개 천개 백만개의 이전 블록들로 부터 데이터를 가진다면 문제가 될 수 있습니다.
따라서 이전 블록에 있는 데이터들을 계속해서 반복해서 복사하는 것보다 단일 SHA256 문자열 또는 해시값들을 해시하는 것이 더 효율적입니다.

두번째 이유는 블록체인의 무결성 보존입니다.

위 그림처럼 각 블록에 PrevHash를 저장함으로써 블록체인에서 블록들이 올바른 순서로 있다는 것을 보장 할 수 있습니다. 만약에 악의적인 사용자가 친입하여 데이터를 수정하려는 시도하면 블록의 해시가 빠르게 변할것입니다. 그러면 체인은 “break”하게 되고 이전 해시 값과 일치 하지 않기 때문에 친입에 의해 변조된 체인이 라는 것을 알게 될것입니다.

블록에 대한 SHA256 해시를 생성하는 함수를 작성해 봅시다

func calculateHash(block Block) string {
    record := string(block.Index) + block.Timestamp + string(block.BPM) + block.PrevHash
    h := sha256.New()
    h.Write([]byte(record))
    hashed := h.Sum(nil)
    return hex.EncodeToString(hashed)
}

calcuateHash 함수는 블록의 Index, Timestamp, BPM, PrevHash를 연결하여 하나의 레코드로 만듭니다. 그리고 레코드를 SHA256로 해시화 한후 문자열 형태의 해시 값으로 반환 합니다.

아래generateBlock 함수는 새로운 블록을 생성하는 함수입니다.

func generateBlock(oldBlock Block, BPM int) (Block, error) {

    var newBlock Block

    t := time.Now()

    newBlock.Index = oldBlock.Index + 1
    newBlock.Timestamp = t.String()
    newBlock.BPM = BPM
    newBlock.PrevHash = oldBlock.Hash
    newBlock.Hash = calculateHash(newBlock)

    return newBlock, nil
}

이 함수를 실행하기 위해서는 이전 블록에 대한 정보가 필요합니다. 이전 PrevHash가 있어야 블록의 해시를 계산할 수 있기 때문입니다. 그리고 인자 값으로 BPM을 받아야하는데 나중에 다른 함수에서 다룰 예정이니 일단 패스하도록 하겠습니다.

Timestamp에는 현재시간을 얻을 수 있는 Now()함수를 사용합니다. PrevHash에는 이전 블록의 해시값이 들어가며 Index는 연속적으로 증가 시킵니다.

블록 유효성 검사

이제 블록들에 손상이 있는지를 확인하기 위해서 어떤 기능이 필요한지 알아봅시다.

Index를 확인함으로써 연속적으로 증가했는지를 확인 합니다 .
그리고 Prevhash가 이전 블록의 Hash와 같은지를 환인 합니다.
마지막으로 우리는 현재블록의 해쉬를 확인합니다. calculateHash를 다시 호출함으로써
현재 블록에 이상이 없는지 확인합니다.

이를 토대로 IsBlockvalid함수를 작성해봅시다.
이 함수는 bool 형태로 값을 합니다. 즉 , True와 False로 값은 반환하게 됩니다.
만약 이상이 없다면 True로 반환 할 것입니다.

func isBlockValid(newBlock, oldBlock Block) bool {
    if oldBlock.Index+1 != newBlock.Index {
        return false
    }

    if oldBlock.Hash != newBlock.PrevHash {
        return false
    }

    if calculateHash(newBlock) != newBlock.Hash {
        return false
    }

    return true
}

만약에 블록체인 시스템에서 두개의 노드에서 블록이 추가가 되어 블록들이 우리 시스템에 오게 되면 어떻게 해야할까?

답은 간단합니다. 가장 크기가 큰 체인을 선택하면 됩니다.

두개의 노드에서 체인의 길이가 다르기 때문에 자연스럽게 더 긴 체인에 최근생성된 블록이 추가 됩니다.
현재 가진 체인 보다 새롭게 생성된 체인쪽에 길이가 더 큰게 맞다면 현재 블록에 덮어 씌우면 해결이 됩니다.

1_H1fCp0NLun0Kn0wIy0dyEA.png

그럼 이제 replaceChain함수를 작성해 봅시다.

func replaceChain(newBlocks []Block) {
    if len(newBlocks) > len(Blockchain) {
        Blockchain = newBlocks
    }
}

이제 우리가 블록체인에서 기본적으로 필요한 함수들을 작성해 봤습니다.

지금부터는 쉽게 블록체인을 보고 생성할 수 있도록 웹 브라우저를 이용할 것입니다.

Web Server

브라우저를 통해 보기 위해서는 웹서버가 필요합니다.
이미 앞선 설치한 Gorilla/mux 패키지를 이용하여 run함수를 작성하고 웹서버를 올려봅시다.

func run() error {
    mux := makeMuxRouter()
    httpAddr := os.Getenv("ADDR")
    log.Println("Listening on ", os.Getenv("ADDR"))
    s := &http.Server{
        Addr:           ":" + httpAddr,
        Handler:        mux,
        ReadTimeout:    10 * time.Second,
        WriteTimeout:   10 * time.Second,
        MaxHeaderBytes: 1 << 20,
    }

    if err := s.ListenAndServe(); err != nil {
        return err
    }

    return nil
}

.env 파일에 포트를 이미 8080으로 설정해두었습니다.
실행하게되면 콘솔로그에 서버가 올라오고 실행되는 것을 확인 할수 있을 것입니다.

모든 handler를 정의 하기 위해 makeMuxRouter 함수를 작성해보십니다.

func makeMuxRouter() http.Handler {
    muxRouter := mux.NewRouter()
    muxRouter.HandleFunc("/", handleGetBlockchain).Methods("GET")
    muxRouter.HandleFunc("/", handleWriteBlock).Methods("POST")
    return muxRouter
}

브라우저에서 블록체인을 보고 블록을 생성하기 위해서 2개의 route가 필요합니다.
GET request를 우리가 올린 서버에 보내면 브라우저를 통해 우리가 개발한 블록체인을 보일 것입니다.
POST request를 보낸다면 블록을 생성할 수 있습니다.

  • GET handler 함수를 작성해 봅시다.
func handleGetBlockchain(w http.ResponseWriter, r *http.Request) {
    bytes, err := json.MarshalIndent(Blockchain, "", "  ")
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    io.WriteString(w, string(bytes))
}

이제 브라우저에서 localhost:8080로 접속하면 확인할 수 있을 것입니다.

POST request는 BPM을 담은 구조체가 필요합니다.
다음과 같이 코드를 작성합시다.

type Message struct {
    BPM int
}

POST request를 이용하여 새로운 블록을 생성하는 Handler 코드를 다음과 같이 작성합니다.

func handleWriteBlock(w http.ResponseWriter, r *http.Request) {
    var m Message

    decoder := json.NewDecoder(r.Body)
    if err := decoder.Decode(&m); err != nil {
        respondWithJSON(w, r, http.StatusBadRequest, r.Body)
        return
    }
    defer r.Body.Close()

    newBlock, err := generateBlock(Blockchain[len(Blockchain)-1], m.BPM)
    if err != nil {
        respondWithJSON(w, r, http.StatusInternalServerError, m)
        return
    }
    if isBlockValid(newBlock, Blockchain[len(Blockchain)-1]) {
        newBlockchain := append(Blockchain, newBlock)
        replaceChain(newBlockchain)
        spew.Dump(Blockchain)
    }

    respondWithJSON(w, r, http.StatusCreated, newBlock)

}

위코드에서 spew.Dump는 콘솔에 찍어 주기 위한 함수 입니다. 이를 통해 디버깅을 쉽게 할수 있습니다.

그리고 POST request를 보내기 위해서는 Postman을 설치하여 보내거나 curl을 이용하여 보낼수 있습니다.

아래 그림과 같이 POSTMAN으로 보내시면 됩니다.

스크린샷 2018-03-28 오전 12.55.38.png

POST 메시지가 잘 보내졌는지 확인 하기 위해서 repondWithJSON 함수를 이용합니다.

함수는 다음과 같이 작성해야 실행 가능 합니다

func respondWithJSON(w http.ResponseWriter, r *http.Request, code int, payload interface{}) {
    response, err := json.MarshalIndent(payload, "", "  ")
    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        w.Write([]byte("HTTP 500: Internal Server Error"))
        return
    }
    w.WriteHeader(code)
    w.Write(response)
}

이제 마지막으로 main 함수를 작성합시다.

func main() {
    err := godotenv.Load()
    if err != nil {
        log.Fatal(err)
    }

    go func() {
        t := time.Now()
        genesisBlock := Block{0, t.String(), 0, "", ""}
        spew.Dump(genesisBlock)
        Blockchain = append(Blockchain, genesisBlock)
    }()
    log.Fatal(run())

}

godotenv.Load()함수는 .env에 있는 설정을 불러오는 함수 입니다. 여기에 저희는 서버 포트를 작성했었는데
그것을 가져오기 위한 함수 입니다.

genesisBlock은 main함수에서 가장 중요한 부분입니다.
블록체인 시스템을 실행하면 Initialize가 필요합니다. 그렇지 않으면 새로운 블록을 생성 하는데 이전 블록이 존재 하지않아 이전 해시와 비교할 수 없습니다.

go run main.go를 실행하시면 서버가 실행될 것입니다.

이제 아래와 같이 브라우저에서 localhost:8080으로 접속하면 블록이 생성되는것을 볼 수 있습니다.

1_4HRKAkMy1smgB9xpGLj6RA.png

다음은 네트워킹에 대한 내용입니다.

감사합니다.

Sort:  

Congratulations @evasioner! You have completed some achievement on Steemit and have been rewarded with new badge(s) :

You published your First Post

Click on any badge to view your own Board of Honor on SteemitBoard.
For more information about SteemitBoard, click here

If you no longer want to receive notifications, reply to this comment with the word STOP

Upvote this notification to help all Steemit users. Learn why here!