gRPCとgolangでブロックチェーンを作ってみる
長らく更新していなかったので雪で東京が4年ぶりの積雪に見舞われている中で久々の更新をしてみました。
作成した背景
近頃ビットコインや仮想通貨で盛り上がりを見せている日本にてブロックチェーン技術が注目を集めています。 ブロックチェーン技術は2009年にスタートし、今まで8年以上一度も落ちることなくサービスを稼働させていました。マウントゴックスの事件ではセキュリティでなく内部の人為的な行為が原因であることがわかり、注目を集めていました。 そんなセキュリティの高いブロックチェーン技術を作ってみたいと思ったことが今回の背景です。
環境
作成するための要件
- gRPCの設定をする
- goとgRPCでサーバーを立てる
- ブロックの生成
- ブロックのハッシュを次のブロックに含むデータ構造を実現する
- ブロックチェーンのリストを表示する
gRPCのセッティングとgoでのサーバー接続の流れ
gRPCは、Googleによって開発されたRPCフレームワークで、HTTP/2を使用した通信層(ProtocolBuffersでシリアライズ)とProtocolBuffers(標準)としたテンプレートコードの生成がセットで提供されています。
最初にgRPCのドキュメントに沿ってパッケージを取得し
.proto ファイルをコンパイルするコンパイラを用意します。
https://grpc.io/docs/quickstart/go.html
$ go get google.golang.org/grpc $ go get -u github.com/golang/protobuf/protoc-gen-go
以下のように.proto ファイルを生成し、ルールを作成する
syntax = "proto3";
package proto;
// The blockchain service definition
service Blockchain {
// Sends a AddBlockRequest Return AddBlockResponse
rpc AddBlock(AddBlockRequest) returns (AddBlockResponse) {}
rpc GetBlockChain(GetBlockchainRequest) returns (GetBlockchainResponse) {}
}
// The request AddBlockRequest containing the data
message AddBlockRequest {
string data = 1;
}
// The request AddBlockResponse containing the hash
message AddBlockResponse {
string hash = 1;
}
message GetBlockchainRequest {}
message Block {
string hash = 1;
string prevBlockHash = 2;
string data = 3;
}
message GetBlockchainResponse {
repeated Block blocks = 1;
}
上記生成した.proto をコンパイルして、サーバーとクライアントのひな形コード /proto/blockchain.pb.go が作成される
$ protoc --go_out=plugins=grpc:. proto/blockchain.proto
gRPCの設定が終わったので/server/main.goを作成しサーバーを立てていく
package main
import (
pt "../proto"
"./blockchain"
"golang.org/x/net/context"
"google.golang.org/grpc"
"log"
"net"
)
type Server struct {
}
func main() {
listener, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatalf("unable to listen on 8080 port: %v", err)
}
srv := grpc.NewServer()
pt.RegisterBlockchainServer(srv, &Server{})
srv.Serve(listener)
}
// TODO: 後ほどブロックを作成するために実装
func (s *Server) AddBlock(ctx context.Context, in *pt.AddBlockRequest) (*pt.AddBlockResponse, error) {
return new(pt.AddBlockResponse), nil
}
// TODO: 後ほどブロックを取得するために実装
func (s *Server) GetBlockChain(ctx context.Context, in *pt.GetBlockchainRequest) (*pt.GetBlockchainResponse, error) {
return new(pt.GetBlockchainResponse), nil
}
以下コマンドでサーバーが無事立ち上がっていることがわかります。
$ go run server/main.go
ブロックチェーンを作っていく流れ
ブロックチェーンを作る流れとしては以下のように組み立てていきます。 1. ブロックチェーン生成のための基盤を作る 2. ブロックを作る 3. ブロックチェーンのリストを表示する
まずはserver/blockchain/blockchain.go を作成する。
package blockchain
import (
"crypto/sha256"
"encoding/hex"
)
// ブロックの構造体
type Block struct {
Hash string
PrevBlockHash string
Data string
}
// ブロックチェーンの構造体
type Blockchain struct {
Blocks []*Block
}
// ブロックの構造体のHashにセットする
func (b *Block) setHash() {
hash := sha256.Sum256([]byte(b.PrevBlockHash + b.Data))
b.Hash = hex.EncodeToString(hash[:])
}
// 新しいブロックを作る
func NewBlock(data string, prevBlockHash string) *Block {
block := &Block{
Data: data,
PrevBlockHash: prevBlockHash,
}
block.setHash()
return block
}
// ブロックチェーンにブロックを追加する
func (bc *Blockchain) AddBlock(data string) *Block {
prevBlock := bc.Blocks[len(bc.Blocks)-1]
newBlock := NewBlock(data, prevBlock.Hash)
bc.Blocks = append(bc.Blocks, newBlock)
return newBlock
}
// ブロックチェーンを作る
func NewBlockchain() *Blockchain {
return &Blockchain{[]*Block{NewGenesisBlock()}}
}
func NewGenesisBlock() *Block {
return NewBlock("Genesis Block", "")
}
/server/main.go のひな形のTODOの箇所を実装する
// AddBlockRequestを送るとAddBlockResponseのレスポンスを返す。(ブロックチェーンにブロックを追加し、追加したブロックを返却)
func (s *Server) AddBlock(ctx context.Context, in *pt.AddBlockRequest) (*pt.AddBlockResponse, error) {
block := s.Blockchain.AddBlock(in.Data)
return &pt.AddBlockResponse{
Hash: block.Hash,
}, nil
return new(pt.AddBlockResponse), nil
}
// GetBlockchainRequestを送りGetBlockchainResponseのレスポンスを返す。(ブロックチェーンをリストで取得する)
func (s *Server) GetBlockChain(ctx context.Context, in *pt.GetBlockchainRequest) (*pt.GetBlockchainResponse, error) {
resp := new(pt.GetBlockchainResponse)
for _, b := range s.Blockchain.Blocks {
resp.Blocks = append(resp.Blocks, &pt.Block{
PrevBlockHash: b.PrevBlockHash,
Hash: b.Hash,
Data: b.Data,
})
}
return resp, nil
}
client側からブロックを追加できるようにする
/client/main.go を作成し以下コードを作成する
package main
import (
"flag"
"google.golang.org/grpc"
"log"
pt "../proto"
"golang.org/x/net/context"
"time"
)
var client pt.BlockchainClient
func main() {
addFlag := flag.Bool("add", false, "add new Block")
listFlag := flag.Bool("list", false, "get the blockchain")
flag.Parse()
conn, err := grpc.Dial("localhost:8080", grpc.WithInsecure())
if err != nil {
log.Fatalf("cannot dial server: %v", err)
}
client = pt.NewBlockchainClient(conn)
if *addFlag {
addBlock()
}
if *listFlag {
getBlockchain()
}
}
// ブロックを追加する
func addBlock() {
block, err := client.AddBlock(context.Background(), &pt.AddBlockRequest{
Data: time.Now().String(),
})
if err != nil {
log.Fatalf("unable to add block: %v", err)
}
log.Printf("new block hash: %s\n", block.Hash)
}
// ブロックチェーンを取得する
func getBlockchain() {
bc, err := client.GetBlockChain(context.Background(), &pt.GetBlockchainRequest{})
if err != nil {
log.Fatalf("unable get blockchain: %v", err)
}
log.Println("blocks:")
for _, b := range bc.Blocks {
log.Printf("hash: %s, prev block hash: %s, data: %s", b.Hash, b.PrevBlockHash, b.Data)
}
}
実行してみる
二つのプロセスを使って実行する。
1, server/main.goをbuildしサーバーを立てる
$ go build server/main.go $ ./server
2, client/main.goを実行する。
オプションにaddをつけてブロックを追加し、オプションにlistをつけてブロックチェーンが作成されているかみてみよう
$ go run client/main.go --add 2018/01/20 22:05:32 new block hash: 5db0c1b5f14afc0085a3ac07221058dc100d4c5978c939842abef81eb65dae7e $ go run client/main.go --list 2018/01/20 22:05:37 blocks: 2018/01/20 22:05:37 hash: 89eb0ac031a63d2421cd05a2fbe41f3ea35f5c3712ca839cbf6b85c4ee07b7a3, prev block hash: , data: Genesis Block 2018/01/20 22:05:37 hash: 5db0c1b5f14afc0085a3ac07221058dc100d4c5978c939842abef81eb65dae7e, prev block hash: 89eb0ac031a63d2421cd05a2fbe41f3ea35f5c3712ca839cbf6b85c4ee07b7a3, data: 2018-01-20 22:05:32.281834137 +0900 JST
無事ブロックが作成され新規で作成されたブロックに以前に作成したハッシュの情報を残していることが確認できました。
まとめ
簡易的なブロックチェーンを作ってみました。 gRPCを使うのも初めてで探り探り利用してきましたがなんとか作ることができました。 gRPC余裕があれば拡張をしてブロックチェーンを深く作ってみたいです。