🔖

Web3.0ブロックチェーン開発の学びメモ - 6日目

2024/01/07に公開

概要

こちらの記事の 6 日目です!
今回は実際に Go 言語でブロックチェーンで仮想通貨をやりとりする流れを実装してみたいと思います!
参考にしたのはシリコンバレーエンジニア酒井潤さんの Udemy 教材です!
現役シリコンバレーエンジニアが教える Go で始めるスクラッチからのブロックチェーン開発入門

ただ、コードを丸写ししても頭に入らないので、最終的にはフレームワークや設計を自分自身でして、コード書くところまで行きたいなと願ってます。(これは願望です。)

とても分かりやすく、丁寧に説明されているので講座なので、興味があればぜひ購入してみてください。

ブロックチェーンの実装

まず、実際のブロックチェーンを全て再現するのは不可能であるため、主要な概念を抑えた形での実装になっております。
そのため、以前に記載してきた概念と細かい点で若干違ってくるところもあるかもしれませんが、元となる考えは同じになってますので、ご了承ください。

ブロックの概要おさらい

まず、ブロックとその概要図のおさらいからしていこうと思います。
ブロックイメージ

上記の図のとおり、トランザクションはトランザクションプールという入れ物の中に未検収のものが全て格納されている状態でした。
そして、マイナーがそれを複数個集めて検収し、ブロックを形成していくという流れでしたね。
図で言うと、ブロックの中の transactions にトランザクションを複数持っており、nonce がマイナーがプルーフオブワークナンスで見つけ出した答えの数値となります。

各ブロックの内容

  • prev hash:以前のブロックを元に作成されたハッシュ値
  • timestamp:ブロックの作成日時
  • nonce:プルーフオブワークで導き出した答え(ナンス)
  • transactions:複数個のトランザクション

各データの構造

まずは各データの構造を見ていきたいと思います。

type Transaction struct {
	senderBlockchainAddress    string
	recipientBlockchainAddress string
	value                      float32
}

type Block struct {
	previousHash [32]byte
	nonce        int
	timestamp    int64
	transactions []*Transaction
}

type Blockchain struct {
	transactionPool   []*Transaction
	chain             []*Block
	blockchainAddress string
}
  • Transaction
    • 送信元アドレス
    • 受信アドレス
    • 送信金額
  • Block
    • 直前のブロックを元に作成されたハッシュ
    • プルーフオブワークで得た nonce
    • 作成時間
    • トランザクションデータ(スライス)
  • Blockchain
    • トランザクションプール(未検収分のトランザクションデータ)
    • ブロックチェーンデータ:全てのブロックを繋いだデータ
    • blockchainAddress:マイナーのアドレス

となっております。blockchainAddress はマイニング後にマイナーが報酬を受け取るために持っているマイナー(ノード、端末)のアドレスです。

ブロックのハッシュを求めるメソッド

では、まずブロックのハッシュを求めるメソッドを見ていきます。

// *********************
// そのブロックのハッシュを求める。
// (Blockのstructにsha256をかける)
// *********************
func (b *Block) Hash() [32]byte {
	m, _ := json.Marshal(b)
	return sha256.Sum256(m)
}

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

流れとしては、

  • LastBlock でブロックチェーンに格納されている直前のブロックを取得。
  • Hash メソッドでそのブロックに対して sha256 をかけてハッシュ化する。
    といった感じです。

ブロックを作成するメソッド

ブロックを作成するメソッドは以下のようになります。

func (bc *Blockchain) CreateBlock(nonce int, previousHash [32]byte) *Block {
	b := NewBlock(nonce, previousHash, bc.transactionPool)
	bc.chain = append(bc.chain, b)        //ブロックが作成されたらブロックチェーンに追加する。
	bc.transactionPool = []*Transaction{} //ブロックを作成したらトランザクションプールを空にする。実際はマイナーがブロックに取り込んだ分だけトランザクションを消していくが、今回はプールに溜まっている全てのトランザクションをブロックに取り込むようにする。
	return b
}

コメントにも書いてますが、ブロックチェーンにブロックが追加されたらトランザクションプールを空にしております。これはデモのため、トランザクション数がそこまでないので、ブロックを作成するごとに全てのトランザクションをブロックに取り込んでいるためこのようになってます。実際はプールにかなりの数 t、トランザクションが溜まっていると思うので、マイナーはそこから決められた数のトランザクションを取り込み、取り込んだデータ分、プールから削除すると言う形になっております。

プルーフオブワークでナンスを求める

Nonceイメージ
このハッシュ化した結果の先頭数桁が 0 になるまで challenge の数字を増やしていくという作業がプルーフオブワークです。
この桁数をいくらにするかによって、難易度は変わってきます。この計算にかかる時間がおよそ 10 分になるように難易度が調整されていくと言うイメージですね!

コードで見ると以下のようになります。

const MINING_DIFFICULTY = 3

func (bc *Blockchain) ValidProof(nonce int, previousHash [32]byte, transactions []*Transaction, difficulty int) bool {
	zeros := strings.Repeat("0", difficulty)
	guessBlock := Block{
		timestamp:    0,
		nonce:        nonce,
		previousHash: previousHash,
		transactions: transactions,
	}
	guessHashStr := fmt.Sprintf("%x", guessBlock.Hash())
	return guessHashStr[:difficulty] == zeros
}
func (bc *Blockchain) ProofOfWork() int {
	transactions := bc.CopyTransactionPool()
	previousHash := bc.LastBlock().Hash()
	nonce := 0
	for !bc.ValidProof(nonce, previousHash, transactions, MINING_DIFFICULTY) {
		nonce += 1
	}
	return nonce
}

MINGIN_DIFFICULTY はプルーフオブワークの難易度になります。
ValidProof でハッシュ化した先頭数桁が 0 になっているか判定しております。上記では先頭 3 桁が 0 になるかチェックしています。
こちらの関数で true が返ってきた時の nonce が答えになります。
ProofOfWork では、ValidProof を用いて、nonce が求められるまで+1 していきます。

マイニングのコード

まずはコードを見ていきましょう。

func (bc *Blockchain) AddTransaction(sender string, recipient string, value float32) {
	t := NewTransaction(sender, recipient, value)
	bc.transactionPool = append(bc.transactionPool, t)
}

const (
	MINING_SENDER = "MINING ADDRESS"
	MINING_REWARD = 1.0
)

func (bc *Blockchain) Mining() bool {
	bc.AddTransaction(MINING_SENDER, bc.blockchainAddress, MINING_REWARD)
	nonce := bc.ProofOfWork()
	previousHash := bc.LastBlock().Hash()
	bc.CreateBlock(nonce, previousHash)
	log.Println("action=mining, status=success")
	return true
}

以前のブロックチェーン概要でも見ていきましたが、マイニングが完了し、ブロックチェーンに取り込まれるとマイナーに報酬が入ります。
そのため、Mining メソッドの先頭でマイナーへの報酬をトランザクションに含めてからマイニングを実行していきます。
CreateBlock メソッド内でブロックを作成、ブロックチェーンへ取り込みと言う流れで処理を行っているため、マイナーへの報酬も含めてトランザクションが取り込まれていきます。

まとめ

コードで見るとだいぶブロックチェーンの概要がつかめてきました!
言葉で説明されるよりコード読んだ方が理解が進むのはエンジニアとして成長できていると言うことでしょうか 😅
今日はここまでとします!

GitHubで編集を提案

Discussion