이 내용은 CoralHeath 블로그의 내용을 번역 한 것 입니다.
https://medium.com/@mycoralhealth/part-2-networking-code-your-own-blockchain-in-less-than-200-lines-of-go-17fe1dad46e1
지난 포스팅에서는 기본적인 블록체인을 코딩했습니다.
하지만 하나의 노드에서만 동작 해보았을 뿐입니다.
어떻게 다른 노드들과 연결하여 새로운 블록을 생성할 수 있을까요? 그리고 모든 노드의 블록을 업데이트 할 수 있을까요?
이번 포스팅을 통해 알아 봅시다.
WorkFlow
- 터미널1 에서 제네시스 블록과 새로운 노드와 연결할 수 있는 서버를 생성합니다.
Step 1
- 터미널2은 TCP 통신을 통해 첫번째 터미널과 연결합니다.
- 터미널2는 터미널1에 블록을 생성합니다.
Step 2
- 터미널 1은 블록을 검사합니다.
- 터미널 1은 새로운 블록이 추가된 블록체인을 다른 노드에 전송합니다.
Step 3
- 모든 터미널에 블록체인이 동기화 됩니다.
아래 내용은 이번 포스팅에서 해야 할 내용 들입니다.
- 제네시스 블록을 제공하는 첫번째 터미널 생성합니다.
- 추가 터미널을 생성하고 첫번째 터미널에 블록을 생성합니다.
- 첫번째 터미널은 여러개의 터미널에 블록체인을 업데이트 해줍니다.
이 튜토리얼의 목표는 노드에서 생성된 블록체인이 어디로 이동하는지 결정할 수 있는 기본적인 네트워크를 만드는 것입니다.
우리는 아직 다른 노드에서 첫번째 노드에 블록을 생성하는 방법을 아직 모릅니다. 이 포스팅에서 데이터를 클라우드에 올리는 것처럼 구현하여 쉽게 달성할 수 있게 될 것입니다. 또한 새로운 블록이 생성되어 블록체인에 업데이트 됬을때 이 새로운 블록체인을 다른 노드에 알리는 것을 시뮬레이션하게 될 것입니다.
이제 코딩을 시작해 봅시다.
지난 포스팅에서 코딩 했던 내용 중 블록생성, 해싱, 그리고 유효성을 체크하는 함수를 똑같이 사용할 예정입니다.
그리고 이번에는 TCP통신을 사용할 것이고 콘솔을 통해 확인할 것이기 때문에 HTTP는 사용하지 않을 것입니다.
TCP와 HTTP의 차이점은 무엇인가?
너무 자세히 알지 못하더라고 TCP가 데이터를 전송하는 프로토콜인지는 알아야합니다.
HTTP는 브라우저와 web에서 데이터를 전송할 때 TCP위에서 작동합니다.
이번 튜토리얼을 위해서 TCP를 이용할 것입니다. GO lang에는 우리가 필요로 하는 TCP 관련 함수들을 제공하는 net 패키지가 있습니다.
환경 설정, imports 그리고 복습
지난번에 포스팅했던 part1의 코드 일부를 활용할 예정입니다. 지난 포스팅을 못보신 분들은 여기서 확인 가능합니다.
Setup
루트 디렉토리에 .env 파일을 생성하시고 다음과 같이 작성하세요.
ADDR=9000
개발을 위해서는 spew, godotenv 패키지가 필요합니다.
아래 명령어로 설치하세요(Go lang이 설치 되어있어야만 실행가능합니다).
go get github.com/davecgh/go-spew/spew
go get github.com/joho/godotenv
그리고 main.go 파일을 생성하세요.
Imports
package main
import (
"bufio"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"io"
"log"
"net"
"os"
"strconv"
"time"
"github.com/davecgh/go-spew/spew"
"github.com/joho/godotenv"
)
Review
Block 구조체를 생성하고 Blockchain 배열을 선언합시다.
// Block represents each 'item' in the blockchain
type Block struct {
Index int
Timestamp string
BPM int
Hash string
PrevHash string
}
// Blockchain is a series of validated Blocks
var Blockchain []Block
블록이 생성할 때 필요한 해시 계산 함수를 작성합시다.
// SHA256 hashing
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)
블록생성 함수 입니다.
// create a new block using previous block's hash
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와 이전 블록의 Hash를 체크함으로써 유효성을 체크한다는 것을 명심하세요.
// make sure block is valid by checking index, and comparing the hash of the previous block
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
}
동시에 블록이 생성되는 경우 가장 긴 체인을 선택하여 사용하는 함수입니다.
// make sure the chain we're checking is longer than the current blockchain
func replaceChain(newBlocks []Block) {
if len(newBlocks) > len(Blockchain) {
Blockchain = newBlocks
}
}
지난 Part1 포스팅에서 사용한 함수들 중 필요한 함수를 가져왔습니다.
이제 networking 하는 방법을 알아 봅시다.
Networking
새로운 블록들이 생성되고 다른 노드에 알려주는 네트워크를 셋팅해 봅시다.
main함수를 통해 시작해 봅시다.
시작전에 bcServer(blochain server를 줄임말) 라는 전역변수를 우리가 선언했던 구조체들 아래에 선언합시다.
// bcServer handles incoming concurrent Blocks
var bcServer chan []Block
이제 main 함수를 작성 해봅시다.
func main() {
err := godotenv.Load()
if err != nil {
log.Fatal(err)
}
bcServer = make(chan []Block)
// create genesis block
t := time.Now()
genesisBlock := Block{0, t.String(), 0, "", ""}
spew.Dump(genesisBlock)
Blockchain = append(Blockchain, genesisBlock)
}
먼저 root 디렉토리에 있는 .env 파일에서 환경 변수들을 불러 오는 코드를 작성합니다.
이미 설정했던 .env의 ADDR을 내용을 불러와 9000번 포트를 사용할 수 있습니다.
TCP server 코드를 작성해 봅시다.
모든 데이터 전송은 터미널 콘솔을 통해 진행됩니다.
이 포트를 통해 여러 개의 connection을 다룰 것입니다.
아래 코드를 main 함수 마지막 아래에 추가 하세요.
// start TCP and serve TCP server
server, err := net.Listen("tcp", ":"+os.Getenv("ADDR"))
if err != nil {
log.Fatal(err)
}
defer server.Close()
위 코드에서 defer server.Close()함수는 connection이 더 이상 필요 없을 때 종료하기 위해서 사용됩니다.
defer를 더 알아보고 싶으면 여기를 클릭하세요.
이제 우리는 매번 connection 요청이 올때 마다 새로운 connection을 생성해야 합니다.
for {
conn, err := server.Accept()
if err != nil {
log.Fatal(err)
}
go handleConn(conn)
}
새로운 connection들은 받을 수 있도록 무한 루프를 생성 합시다.
동시에 각 connection들을 분리해서 다룰 수 있도록 해야 하는데 이는 Go루틴 go handleConn(conn)을 통해 해결합니다.
func main() {
err := godotenv.Load()
if err != nil {
log.Fatal(err)
}
bcServer = make(chan []Block)
// create genesis block
t := time.Now()
genesisBlock := Block{0, t.String(), 0, "", ""}
spew.Dump(genesisBlock)
Blockchain = append(Blockchain, genesisBlock)
// start TCP and serve TCP server
server, err := net.Listen("tcp", "localhost:"+os.Getenv("ADDR"))
if err != nil {
log.Fatal(err)
}
defer server.Close()
for {
conn, err := server.Accept()
if err != nil {
log.Fatal(err)
}
go handleConn(conn)
}
}
이제 Main함수 작성이 끝났습니다.
그런데 아직 handleConn 함수를 작성하지 않아 실행이 되지 않습니다.
이제 handleConn 함수를 작성해 봅시다.
func handleConn(conn net.Conn) {
defer conn.Close()
}
이 함수는 한 개의 인자(net.Conn)가 필요합니다.
defer conn.Close()를 이용하여 각 connection을 종료할 수 있습니다.
이제 우리는 사용자가 새로운 블록을 블록체인에 추가하는 것을 해야 합니다.
지난 포스팅에서 사용했던 pulse rate BPM을 사용할 예정입니다.
위 내용을 달성하기 위해서는 다음과 같은 것들이 필요합니다.
BPM
을 입력한다.- Stdin scan을 통해 사용자가 입력한 값을 받는다.
- 이전에 생성한 gnerateBlock, isBlockValid 그리고 replaceChanin 함수들을 이용하여 데이터를 가진 블록을 생성한다.
- 우리가 다른 노드에 알려주기 위해 만든 네트워크에 블록체인을 넣는다.
- 사용자가 새로운 BPM을 입력할 수 있도록 한다.
위 내용을 충족할 수 있는 코드가 바로 아래 있습니다. 이 코드는 defer conn.Close()
아래에 작성하면 됩니다.
io.WriteString(conn, "Enter a new BPM:")
scanner := bufio.NewScanner(conn)
// take in BPM from stdin and add it to blockchain after conducting necessary validation
go func() {
for scanner.Scan() {
bpm, err := strconv.Atoi(scanner.Text())
if err != nil {
log.Printf("%v not a number: %v", scanner.Text(), err)
continue
}
newBlock, err := generateBlock(Blockchain[len(Blockchain)-1], bpm)
if err != nil {
log.Println(err)
continue
}
if isBlockValid(newBlock, Blockchain[len(Blockchain)-1]) {
newBlockchain := append(Blockchain, newBlock)
replaceChain(newBlockchain)
}
bcServer <- Blockchain
io.WriteString(conn, "\nEnter a new BPM:")
}
}()
이제 위 코드에 대해서 하나하나 알아 봅시다.
- 새로운 스캐너를 생성합니다.
for scanner.Scan()
반복문은 Go 루틴안에 작성합니다. 그러면 다른 connection들과 분리하여 수행합니다.- 사용자가 입력한
bpm
을 integer 형으로 변환 해줍니다. - 블록을 생성하고 체크한 다음 새로운 블록은 가진 블록체인을 생성합니다.
bcServer <- Blockchain
이 문장은 새로운 블록체인을 채널에 넣는 것을 의미합니다.
그리고나서 사용자의 새로운 BPM을 새로운 블록에 입력할 수 있도록 합니다.
Broadcasting
블록체인 안에 변화가 생기면 이 새로운 블록체인은 TCP를 통해 연결된 다른 노드에 알려줘야 합니다.
우리는 이 것을 하나의 컴퓨터에서 하기 때문에 어떻게 모든 사용자들이 변화 된 내용을 받는지 시뮬레이션하게 될 것입니다.
- 새로운 블록체인을 JSON(json marshall)형태로 넣습니다.
- 새로운 블록체인을 연결된 모든 콘솔에 출력 합니다.
- 수용할수 있는 양보다 초과해서 블록체인 데이터를 받을 수 없기 때문에 타이머 셋팅 합니다. 현재 라이브 되고있는 블록체인 네트워크에 새로운 블록체인이 매 X 분마다 다른 노드에 전송하는것을 볼 수 있을 것입니다. 이것을 우리는 30초로 셋팅합니다.
- main 블록체인을 첫번째 터미널에서 출력하면 다른 노드들에 의해 계속 해서 새로운 블록이 생성되고 합쳐지는 것을 볼 수 있습니다.
코드는 아래 있습니다.
// simulate receiving broadcast
go func() {
for {
time.Sleep(30 * time.Second)
output, err := json.Marshal(Blockchain)
if err != nil {
log.Fatal(err)
}
io.WriteString(conn, string(output))
}
}()
for _ = range bcServer {
spew.Dump(Blockchain)
}
완성된 코드는 Github https://github.com/evasioner/blockchain에 올려 놓았습니다.
실행 테스트
go run main.go
으로 실행합니다.
서버가 올라온 것을 확인하면 새롭게 터미널을 실행하여
netcat을 이용하여 접속해봅시다.
nc localhost 9000
으로 실행하면 BPM을 입력하라고 할것 입니다.
그리고 입력하시고 싶은 숫자를 입력하세요
입력하게 되면 터미널 1에서 블록이 추가된것을 확인 할수 있습니다.
그리고 아래 그림과 같이 매 30초마다 블록이 업데이트 되었다는 것이 클라이언트에 오게 됩니다.
Next Step
다음 포스팅에서는 마이닝 알고리즘에 대해서 알아 볼것 입니다.
현재 많은 플랫폼에서 사용하고 있는 POW(proof of work) 작업증명 방식과 POS(proof of stake) 지분증명 방식 등을 직접 코딩하고 테스트 할 예정입니다.
감사합니다.
흥미롭네요. 시간날때 자세히 보기 위해 리스팀합니다~
이런 좋은자료 너무 감사합니다!
아직 프론트단 공부중인데, 나중에 정주행해야겠네요
감사합니다. 블록체인 말고도 다른 개발글도 올릴예정이니 많이 놀러와주세요
이더리움도 해주세요
DApp 개발도 포스팅 올릴께요 ㅋㅋ
Congratulations @evasioner! You have completed some achievement on Steemit and have been rewarded with new badge(s) :
You made your First Comment
Award for the number of upvotes received
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
Do not miss the last announcement from @steemitboard!