8
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Goでブロックチェーンを実装してみる

Posted at

この記事のPythonで書かれてたものをGoに置き換えただけ

ブロックチェーンを作ることで学ぶ 〜ブロックチェーンがどのように動いているのか学ぶ最速の方法は作ってみることだ〜 - Qiita

唯一違う点はgo funcで鯖を二つ同時に立ててる

ギフハブはこちら

変数名とかは大体同じ。

3時間くらいでサクっと作ったからおそらく変なところあります。。
間違っていたらプルリクかコメントお願いします!

PS.リファクタリングしてコメント消して本家にプルリク投げてみようかな...

main.go
package main

import (
	"time"
	"encoding/json"
	"crypto/sha256"
	"encoding/hex"
	"strconv"
	"github.com/google/uuid"
	"strings"
	"github.com/labstack/echo"
	"net/http"
)

type Blockchain struct {
	Chain              []Block
	CurrentTransaction Transaction
	Nodes []string
}

type Transaction struct {
	Sender string
	Recipient string
	Amount int
}

type Block struct {
	Index        int         `json:"index"`
	Timestamp    int64       `json:"timestamp"`
	Transactions Transaction `json:"transactions"`
	Proof        int         `json:"proof"`
	PreviousHash string      `json:"previous_hash"`
}

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

var blockchain Blockchain


func (Blockchain) Init(){
	//# ジェネシスブロックを作る
	blockchain = Blockchain{}
	blockchain.NewBlock("1",100)
}

/*
	 ブロックチェーンに新しいブロックを作る
	 :param proof: <int> プルーフ・オブ・ワークアルゴリズムから得られるプルーフ
	 :param previous_hash: (オプション) <str> 前のブロックのハッシュ
	 :return: <dict> 新しいブロック
*/
func (Blockchain) NewBlock(previousHash string,proof int) Block {
	pg := ""
	if previousHash != ""{
		pg = previousHash
	}else{
		pg = blockchain.Hash(blockchain.Chain[len(blockchain.Chain) - 1])
	}
	block := Block{
		Index:        len(blockchain.Chain) + 1,
		Timestamp:    time.Now().Unix(),
		Transactions: blockchain.CurrentTransaction,
		Proof:        proof,
		PreviousHash: pg, // or blockchain.Hash(blockchain.Chain[-1])
	}
	blockchain.CurrentTransaction = Transaction{}
	blockchain.Chain = append(blockchain.Chain,block)
	return block
}

/*
	次に採掘されるブロックに加える新しいトランザクションを作る
	:param sender: <str> 送信者のアドレス
	:param recipient: <str> 受信者のアドレス
	:param amount: <int> 量
	:return: <int> このトランザクションを含むブロックのアドレス
*/
func (Blockchain) NewTransaction(sender string,recipient string,amount int) int{
	// 新しいトランザクションをリストに加える
	blockchain.CurrentTransaction = Transaction{Sender:sender,Recipient:recipient,Amount:amount}
	return blockchain.LastBlock().Index + 1
}

/*
	ブロックの SHA-256 ハッシュを作る
	:param block: <dict> ブロック
	:return: <str>
*/
func (Blockchain) Hash(block Block)string{
	// ブロックをハッシュ化する
	blockJson,err := json.Marshal(block)
	if err != nil{
		panic(err)
	}
	converted := sha256.Sum256([]byte(blockJson))
	return hex.EncodeToString(converted[:])
}

func (Blockchain) LastBlock() Block {
	// チェーンの最後のブロックをリターンする
	return blockchain.Chain[len(blockchain.Chain) - 1]
}

/*
		シンプルなプルーフ・オブ・ワークのアルゴリズム:
		- hash(pp') の最初の4つが0となるような p' を探す
		- p は1つ前のブロックのプルーフ、 p' は新しいブロックのプルーフ
		:param last_proof: <int>
		:return: <int>
*/
func (Blockchain) ProofOfWork(lastProof int)int{
	proof := 0
	for blockchain.ValidProof(lastProof,proof) == false{
		proof += 1
	}

	return proof
}

/*
        プルーフが正しいかを確認する: hash(last_proof, proof)の最初の4つが0となっているか?
        :param last_proof: <int> 前のプルーフ
        :param proof: <int> 現在のプルーフ
        :return: <bool> 正しければ true 、そうでなれけば false
*/
func (Blockchain) ValidProof(lastProof int,proof int)bool{
	guess := []byte(strconv.Itoa(lastProof) + strconv.Itoa(proof))
	sha256s := sha256.Sum256(guess)
	guessHash := hex.EncodeToString(sha256s[:])
	return guessHash[:4] == "0000"
}

/*
        ブロックチェーンが正しいかを確認する
        :param chain: <list> ブロックチェーン
        :return: <bool> True であれば正しく、 False であればそうではない
*/
func (Blockchain) ValidChain(chain []Block)bool{
	lastBlock := chain[0]
	currentIndex := 1

	for currentIndex < len(chain){
		block := chain[currentIndex]
		//# ブロックのハッシュが正しいかを確認
		if block.PreviousHash != blockchain.Hash(lastBlock){
			return false
		}
		//# プルーフ・オブ・ワークが正しいかを確認
		if !blockchain.ValidProof(lastBlock.Proof,block.Proof){
			return false
		}
		lastBlock = block
		currentIndex += 1
	}
	return true
}
/*
	これがコンセンサスアルゴリズムだ。ネットワーク上の最も長いチェーンで自らのチェーンを
	置き換えることでコンフリクトを解消する。
	:return: <bool> 自らのチェーンが置き換えられると True 、そうでなれけば False
*/
func (Blockchain) ResolveConflicts()bool{
	neighbours := blockchain.Nodes
	var newChain []Block

	//# 自らのチェーンより長いチェーンを探す必要がある
	maxLength := len(blockchain.Chain)

	for _,node := range neighbours{
		response,err := http.Get(node + "/chain")
		if err != nil{
			panic(err)
		}
		if response.StatusCode != 200{
			panic(err)
		}
		var fullChain FullChain
		if err := json.NewDecoder(response.Body).Decode(&fullChain); err != nil{
			panic(err)
			continue
		}
		length := fullChain.Length
		chain := fullChain.Chain

		if length > maxLength && blockchain.ValidChain(chain){
			maxLength =  length
			newChain = chain
		}
	}
	if len(newChain) != 0{
		blockchain.Chain = newChain
		return true
	}
	return false
}

/*
	ノードリストに新しいノードを加える
	:param address: <str> ノードのアドレス 例: 'http://192.168.0.5:5000'
	:return: None
*/
func (Blockchain) RegisterNode(address string){
	blockchain.Nodes = append(blockchain.Nodes,address)

	fix := make(map[string]bool)
	one := []string{}
	for _,a := range blockchain.Nodes{
		if !fix[a]{
			fix[a] = true
			one = append(one, a)
		}
	}
	blockchain.Nodes = one
}

var NodeIdentifire string

func main() {
	// echo server
	e := echo.New()

	//# このノードのグローバルにユニークなアドレスを作る
	NodeIdentifire = strings.Replace(uuid.New().String(),"-","",-1)

	blockchain.Init()

	e.GET("/mine", Mine)
	e.POST("/transactions/new", NewTransactionPost)
	e.GET("/chain", FullChainGET)

	e.POST("/nodes/register",RegisterNode)
	e.GET("/nodes/resolve",Consensus)

	go func(echoEcho *echo.Echo) {
		copyEcho := echoEcho
		copyEcho.Start(":5001")
	}(e)
	e.Start(":5000")
}

type Post2 struct {
	Nodes []string `json:"nodes"`
}

type Response2 struct {
	Message string `json:"message"`
	TotalNode []string `json:"total_node"`
}

func RegisterNode(e echo.Context)error{
	nodes := new(Post2)
	if err := e.Bind(nodes); err != nil{
		return e.JSON(http.StatusBadRequest,"Status Bad Request.")
	}

	for _,node := range nodes.Nodes{
		blockchain.RegisterNode(node)
	}

	var response2 Response2
	response2.Message = "新しいノードが追加されました"
	response2.TotalNode = blockchain.Nodes

	return e.JSON(http.StatusCreated,response2)
}

func Consensus(e echo.Context)error{
	replaced := blockchain.ResolveConflicts()
	if replaced{
		type Response struct {
			Message string `json:"message"`
			NewChain []Block `json:"new_chain"`
		}
		var response Response
		response.Message = "チェーンが置き換えられました"
		response.NewChain = blockchain.Chain
		return e.JSON(http.StatusOK,response)
	}else{
		type Response struct {
			Message string `json:"message"`
			Chain []Block `json:"chain"`
		}
		var response Response
		response.Message = "チェーンが確認されました"
		response.Chain = blockchain.Chain
		return e.JSON(http.StatusOK,response)
	}

}

type Post struct {
	Sender string
	Recipient string
	Amount int
}
//# メソッドはPOSTで/transactions/newエンドポイントを作る。メソッドはPOSTなのでデータを送信する
func NewTransactionPost(e echo.Context)error{
	post := new(Post)
	if err := e.Bind(post); err != nil {
		return e.JSON(http.StatusBadRequest,"Status Bad Request.")
	}

	// '新しいトランザクションを追加します'
	index := blockchain.NewTransaction(post.Sender,post.Recipient,post.Amount)
	return e.JSON(http.StatusCreated,"トランザクションはブロック" + strconv.Itoa(index) + "に追加されました")
}

//# メソッドはGETで/mineエンドポイントを作る
func Mine(e echo.Context)error{
	// '新しいブロックを採掘します'
	lastBlock := blockchain.LastBlock()
	lastProof := lastBlock.Proof
	proof := blockchain.ProofOfWork(lastProof)

	blockchain.NewTransaction("0",NodeIdentifire,1)

	block := blockchain.NewBlock("",proof)

	response := struct {
		Message string `json:"message"`
		Index int `json:"index"`
		Transactions Transaction `json:"transactions"`
		Proof int `json:"proof"`
		PreviousHash string `json:"previous_hash"`
	}{Message:"新しいブロックを採掘しました。",
	Index:block.Index,
	Transactions:block.Transactions,
	Proof:block.Proof,
	PreviousHash:block.PreviousHash}

	return e.JSON(http.StatusCreated,response)
}

//# メソッドはGETで、フルのブロックチェーンをリターンする/chainエンドポイントを作る
func FullChainGET(e echo.Context)error{
	var response FullChain
	response.Chain = blockchain.Chain
	response.Length = len(blockchain.Chain)

	return e.JSON(http.StatusOK,response)
}
8
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?