[ ブロックチェーン ]Go言語でBlockchainを作ってみる

この記事は約18分で読めます。

今回は基本的な ブロックチェーン (Blockchain)をGo言語で作って行きます!!

最新技術で難しそうに見えますが、実は基本的なブロックチェーンの仕組みは簡単なのです!

ブロックチェーンをpythonで作る

この記事を参考にGolangで作成しました。

それでは作成していきましょう!

ブロックチェーンとは

[ 仮想通貨 ]仮想通貨の未来~ビットコイン~
今回はビットコインをはじめとする仮想通貨について、まとめました。法定通貨との違いであったり、仮想通貨に用いられているブロックチェーンについても、仮想通貨から見た、メリットデメリットをまとめています。

 

ここでも説明していますが、分散型の台帳で、

あのビットコインに使われている技術です!

実装機能

  • Proof Of Work
  • コイン量計算
  • 他のノードとつなぐ
  • 他のノードとチェーンを比較する
  • マイニング
  • トランザクション追加

実践

ディレクトリ構成

.
├── Makefile
├── README.md
├── blockchain.go
├── consensus.go
├── controller.go
├── go.mod
├── go.sum
├── main.go
├── middleware.go
└── util.go

Moduleモードを使用しているため、相対パスが利用できません。そのため、今回は階層化していません。

blockchain.go

package main

import (
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"errors"
	"net/http"
	"time"
)

type FullChain struct {
	Chain  []Block `json:"chain"`
	Length int     ` json:"length"`
}

type Blockchain struct {
	Chain              []Block 
	CurrentTransactions []Transaction
	Nodes              []string
}
type Block struct {
	Index        int
	Timestamp    int64
	Transactions  []Transaction
	Proof        int
	PreviousHash string
}

type Transaction struct {
	Sender    string
	Recipient string
	Amount    int
}

var WORKLEVEL = 4

func Init()*Blockchain{
	bc := new(Blockchain)
	bc.NewBlock(100, "1")
	return bc
}

func (bc *Blockchain) NewBlock(proof int, previousHash ...string) Block {
	var pg string
	if len(previousHash) != 0 {
		pg = previousHash[0]
	} else {
		pg = bc.Hash(bc.Chain[len(bc.Chain)-1])
	}
	block := Block{
		Index:        (len(bc.Chain) + 1),
		Timestamp:    time.Now().Unix(),
		Transactions:  bc.CurrentTransactions,
		Proof:        proof,
		PreviousHash: pg,
	}
	bc.CurrentTransactions = []Transaction{}
	bc.Chain = append(bc.Chain, block)
	return block
}

func (bc Blockchain) LastBlock() Block {
	return bc.Chain[len(bc.Chain)-1]
}

func (bc *Blockchain) NewTransaction(sender, recipient string, amount int) int {
	if (bc.GetAmount(sender)-amount)<0 && sender != "0"/*minus amount  &&!genesis*/{
		return 0
	}
	bc.CurrentTransactions = append(bc.CurrentTransactions,Transaction{
		Sender:    sender,
		Recipient: recipient,
		Amount:    amount,
	})
	return bc.LastBlock().Index + 1
}

func (bc *Blockchain) Hash(block Block) string {
	blockString, err := json.Marshal(block)
	if err != nil {
		panic(err)
	}
	hashData := sha256.Sum256([]byte(blockString))
	return hex.EncodeToString(hashData[:])
}

func (bc *Blockchain) RegisterNode(address string) error {
	res, err := http.Get(address + "/chain")
	if err != nil || res.StatusCode != http.StatusOK {
		return err
	}
	for _, v := range bc.Nodes {
		if v == address {
			return errors.New("すでに登録されています")
		}
	}
	bc.Nodes = append(bc.Nodes, address)
	return nil
}

ここでは、ブロックチェーンの基本構造を定義しています。

consensus.go

ここでは、コンセンサスアルゴリズムの処理をまとめています。
基本的なブロックチェーン(ビットコインと一緒)では、POWが用いられています。計算量でブロックを決定していきます。
その合意をとる処理をまとめています。

package main

import (
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"log"
	"net/http"
	"strconv"
	"strings"
)

func ValidProof(lastProof, proof int, level int) bool {
	guess := []byte(strconv.Itoa(lastProof) + strconv.Itoa(proof))
	hashData := sha256.Sum256(guess)
	guessHash := hex.EncodeToString(hashData[:])
	return guessHash[:level] == strings.Repeat("0", level)
}

func (bc *Blockchain) ValidChain(chain []Block) bool {
	lastBlock := chain[0]
	currentIndex := 1
	for currentIndex < len(chain) { block := chain[currentIndex] if block.PreviousHash != bc.Hash(lastBlock) { return false } if !ValidProof(lastBlock.Proof, block.Proof, WORKLEVEL) { return false } lastBlock = block currentIndex++ } return true } func (bc Blockchain) ProofOfWork(lastProof, level int) int { proof := 0 for !ValidProof(lastProof, proof, level) { proof++ } return proof } func (bc *Blockchain) ResolveConflicts() bool { neighbours := bc.Nodes newChain := []Block{} maxLength := len(bc.Chain) for _, node := range neighbours { res, err := http.Get(node + "/chain") if err != nil { log.Println(err.Error()) continue } if res.StatusCode != http.StatusOK { log.Println(err.Error()) continue } var fullChain FullChain if err := json.NewDecoder(res.Body).Decode(&fullChain); err != nil { log.Println(err.Error()) continue } log.Println(fullChain.Length, ":", maxLength, ":", bc.ValidChain(fullChain.Chain)) if fullChain.Length > maxLength && bc.ValidChain(fullChain.Chain) {
			maxLength = fullChain.Length
			newChain = fullChain.Chain
		}
	}
	if len(newChain) != 0 {
		bc.Chain = newChain
		return true
	}
	return false
}

 

controller.go

ここでは、ブロックチェーンに対して、http通信でやり取りする処理をcontrollerでまとめています。この機能により、ブラウザでもブロックチェーンがやり取りできます。
以前、ginを用いて、webを作りましたが、
[Go] Go言語入門~Webアプリを作ろう~

今回はechoというフレームワークを用いています。

 

package main

import (
	"net/http"
	"strconv"

	"github.com/labstack/echo"
)

func MineController(c echo.Context) error {
	bc := GetBlockchain(c)
	lastBlock := bc.LastBlock()
	lastProof := lastBlock.Proof
	proof := bc.ProofOfWork(lastProof, WORKLEVEL)
	nodeIdentifire := GetIdent(c)
	bc.NewTransaction("0", *nodeIdentifire, 1)
	block := bc.NewBlock(proof)
	return c.JSON(http.StatusOK, block)
}

func NewTransactionController(c echo.Context) error {
	type params struct {
		Sender    string `json:"sender"`
		Recipient string `json:"recipient"`
		Amount    int    `json:"amount"`
	}
	p := new(params)
	bc := GetBlockchain(c)
	if err := c.Bind(&p); err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{
			"message": "bind error",
		})
	}
	index := bc.NewTransaction(p.Sender, p.Recipient, p.Amount)
	if index == 0{
		return c.JSON(http.StatusBadRequest, map[string]string{
			"message": "cannot added Transaction to Block",
		})
	}
	return c.JSON(http.StatusCreated, map[string]string{
		"message": "added Transaction to Block" + strconv.Itoa(index),
	})
}

func ChainController(c echo.Context) error {
	bc := GetBlockchain(c)
	var f FullChain
	f.Chain = bc.Chain
	f.Length = len(bc.Chain)
	return c.JSON(http.StatusOK, f)
}

func RegisterNodeController(c echo.Context) error {
	type params struct {
		Nodes []string `json:"nodes"`
	}
	p := new(params)
	bc := GetBlockchain(c)
	if err := c.Bind(&p); err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{
			"message": "bind error",
		})
	}
	for _, node := range p.Nodes {
		if err := bc.RegisterNode(node);err != nil{
			return c.JSON(http.StatusBadRequest, map[string]string{
				"message": err.Error(),
			})
		}
	}
	return c.JSON(http.StatusCreated, map[string]interface{}{
		"message": "register nodes",
		"nodes":   bc.Nodes,
	})
}

func ConsensusController(c echo.Context) error {
	type response struct {
		Message string             `json:"message"`
		Chain   []Block `json:"chain"`
	}
	bc := GetBlockchain(c)
	res := new(response)
	if bc.ResolveConflicts() {
		res.Message = "updated chain"
	} else {
		res.Message = "comfirmed chain"
	}
	res.Chain = bc.Chain
	return c.JSON(http.StatusOK, res)
}

func GetNodes(c echo.Context)error{
	bc := GetBlockchain(c)
	return c.JSON(http.StatusOK, map[string]interface{}{
		"nodes":   bc.Nodes,
	})
}

func GetAmout(c echo.Context)error{
	bc := GetBlockchain(c)
	nodeIdent := c.QueryParam("node")
	if nodeIdent ==""{
		return c.JSON(http.StatusNotFound, map[string]interface{}{
			"message":   "nodeパラメータを選択してください",
		})
	}
	return c.JSON(http.StatusOK,map[string]interface{}{
		"node":nodeIdent,
		"amount":bc.GetAmount(nodeIdent),
	})
}

middleware.go

ここでは、ブロックチェーンにアクセスする時に通す、ミドルウェアを定義しています。
ブロックチェーンをecho.contextに持たせることで、コントローラーのなかでブロックチェーンオブジェクトを利用することができます。

package main

import (
	"fmt"
	"github.com/labstack/echo"
)

const (
	BLOCKCHAIN = "BLOCKCHAIN"
	IDENTIFIRE = "IDENT"

)

func InsertBlockchainMiddleware(bc *Blockchain) echo.MiddlewareFunc {
	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return echo.HandlerFunc(func(c echo.Context) error {
			c.Set(BLOCKCHAIN, bc)
			return next(c)
		})
	}
}

func InsertIdentMiddleware(nodeIdent string) echo.MiddlewareFunc {
	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return echo.HandlerFunc(func(c echo.Context) error {
			c.Set(IDENTIFIRE, &nodeIdent)
			return next(c)
		})
	}
}

func GetBlockchain(c echo.Context) *Blockchain {
	fmt.Println(c.Get(BLOCKCHAIN).(*Blockchain))
	return c.Get(BLOCKCHAIN).(*Blockchain)
}

func GetIdent(c echo.Context) *string {
	return c.Get(IDENTIFIRE).(*string)
}

main.go

ここでは、webAPIライクに実装していて、http通信できるようにルーティングであったり、ブロックチェーンのgenesisブロックを作っています。

package main

import (
	"flag"
	"log"
	"strings"

	"github.com/google/uuid"
	"github.com/labstack/echo"
	mw "github.com/labstack/echo/middleware"
)

var port = flag.String("p", "5000", "port option")

var bc *Blockchain
var nodeIdentifire string

func init() {
	flag.Parse()
	bc = Init()
}

func main() {
	e := echo.New()
	nodeIdentifire = strings.Replace(uuid.New().String(), "-", "", -1)

	e.Use(mw.Logger())
	e.Use(InsertBlockchainMiddleware(bc))
	e.Use(InsertIdentMiddleware(nodeIdentifire))
	e.GET("/mine", MineController)
	e.POST("/transactions/new", NewTransactionController)
	e.GET("/chain", ChainController)

	e.GET("/amount",GetAmout)

	e.GET("/nodes",GetNodes)
	e.POST("/nodes/register", RegisterNodeController)
	e.GET("/nodes/resolve", ConsensusController)

	log.Println("nodeIdent:", nodeIdentifire)
	e.Start(":" + *port)
}

util.go

ここでは、雑処理をまとめています。

package main

func Contains(s []interface{}, e interface{}) bool {
	for _, v := range s {
		if e == v {
			return true
		}
	}
	return false
}



func (bc *Blockchain)GetAmount(nodeIdent string)int{
	amount := 0
	for _,v := range bc.Chain{
		for _,t := range v.Transactions{
			if t.Sender == nodeIdent{
				amount -= t.Amount
			}
			if t.Recipient == nodeIdent{
				amount += t.Amount
			}
		}
	}
	return amount
}

実行

go run *.go -p 3000

*.goを用いているのは、mainパッケージを全て記述しなければいけないのを省略しています。

curlやPOSTMANを用いて、httpリクエストしてみてください!

複数ノードを建てた場合は、/nodes/registerに

{"nodes": ["http://localhost:5001"]}

で登録したいノードを指定して、postしてください。連携ができれば、二つのノードで更新しあいます。

プロジェクト配布

blockchain-in-go.zip

これをダウンロードしてください!

moduleモードで作ってます。バージョンはGo v1.11です!

まとめ

今回は、基本的なブロックチェーンを作成してみました。

P2Pでやり取りするのを見るのは面白いので、是非みてください!

近々、P2P型のチャットツールを公開しようと思います!

それでは!

タイトルとURLをコピーしました