🌊
GolangでブロックチェーンのPoWの簡易的な実装をしてみた
概要
Golangとブロックチェーンを最近勉強し始めて、練習と興味がてら、PoWの簡易的な実装を行いました。
複数ノードによるコンセンサスまで考慮すると大変そうだったので単一のノードでのことだけ考えて実装してみました。また今回エラー処理やテストは書いていません。
※ Golangもブロックチェーンも初心者レベルなので、間違いを含んでいる可能性がそれなりあります
structの解説
だいたい画像のようなものを実装しました。
Block
- Blockの中に取引の記録Transactionsが保存されている
- Blockは自身のハッシュ値を持つ
- Blockは前のブロックのハッシュ値を持ちこれがチェーンとなっている
BlockChain
- 内部にBlockの配列を持つ
- 未処理の取引記録pendingTransactionsを持つ
- マイニングを実行するとpendingTransactionsがブロックに記録され、さらに条件を満たすハッシュ値(先頭が00)になるnonceを見つける
Block
内部に取引記録を保存することができます。
またmining()
メソッドを実行することで、適切なハッシュ値を計算します。
block_chain/bchain/block.go
package bchain
import (
"crypto/sha256"
"fmt"
"time"
)
type (
block struct {
timestamp time.Time
transactions []*transaction
previousHash [32]byte
hash [32]byte
nonce int
}
)
func NewBlock(timestamp time.Time, transactions []*transaction, previousHash [32]byte) *block {
b := block{
timestamp: timestamp,
transactions: transactions,
previousHash: previousHash,
nonce: 0,
}
b.hash = b.calcHash()
return &b
}
func (b block) String() string {
return fmt.Sprintf("%s", b.transactions)
}
// private
// nonceの値をインクリメントしていき、条件に合致するハッシュ値が見つかるまで計算を続ける
func (b *block) mining() {
isContinue := true
for i := 0; isContinue; i++ {
b.nonce++
b.hash = b.calcHash()
strSha256 := fmt.Sprintf("%x", b.hash)
isContinue = string([]rune(strSha256)[:2]) != "00"
}
fmt.Println("Succeeded to mining")
}
func (b block) calcHash() [32]byte {
s := fmt.Sprintf(
"%s:%s:%s:%s",
b.previousHash,
b.timestamp,
b.transactions,
string(rune(b.nonce)),
)
byteSha256 := sha256.Sum256([]byte(s))
return byteSha256
}
Transaction
取引記録用のstructなので特別なことはしていません。
block_chain/bchain/transaction.go
package bchain
import "fmt"
type (
address string
money float64
transaction struct {
sender address
recipient address
amount money
}
)
func NewTransaction(sender string, recipient string, amount float64) *transaction {
t := transaction{
sender: address(sender),
recipient: address(recipient),
amount: money(amount),
}
return &t
}
func (tx transaction) String() string {
return fmt.Sprintf(
"{ %s sent %v => %s }",
tx.sender,
tx.amount,
tx.recipient,
)
}
BlockChain
block_chain/bchain/block_chain.go
package bchain
import (
"crypto/sha256"
"fmt"
"time"
)
type BlockChain struct {
blocks []*block
pendingTransactions []*transaction
miningReward money
}
// チェーンの中の最初のブロック(genesis block)を精製して、structを返す
func NewBlockChain() *BlockChain {
genesisBlock := NewBlock(
time.Now(),
make([]*transaction, 0),
sha256.Sum256([]byte(fmt.Sprintf("0"))),
)
return &BlockChain{
miningReward: money(12.5),
blocks: []*block{genesisBlock},
}
}
func (bc BlockChain) String() string {
return fmt.Sprintf("blocks: %s", bc.blocks)
}
func (bc *BlockChain) AddTransaction(tx *transaction) {
bc.pendingTransactions = append(bc.pendingTransactions, tx)
}
// チェーンに記録されている未処理のトランザクションに対してマイニングを行い確定する
func (bc *BlockChain) Mine(rewardAddress address) {
// ブロックの生成とマイニングを行いチェーンに追加する
block := NewBlock(
time.Now(),
bc.pendingTransactions,
bc.lastBlock().hash,
)
block.mining()
bc.blocks = append(bc.blocks, block)
// pendingTransactionの初期化
bc.pendingTransactions = make([]*transaction, 0)
// マイニングの報酬を記録。次のマイニングで確定される
bc.pendingTransactions = append(
bc.pendingTransactions,
NewTransaction(
"",
string(rewardAddress),
float64(bc.miningReward),
),
)
}
// ブロックチェーンが全体で整合性を持っているか計算
func (bc BlockChain) IsValid() bool {
for i := len(bc.blocks) - 1; i >= 0; i-- {
b := bc.blocks[i]
// ハッシュ値が書き換えられていないか
if b.hash != b.calcHash() {
return false
}
// 前のハッシュ値が一致するか
if i != 0 && b.previousHash != bc.blocks[i-1].hash {
return false
}
}
return true
}
// 取引記録を全て参照し、保持金額を計算する
func (bc BlockChain) GetBalanceOfAddress(a string) money {
var balance money
for _, block := range bc.blocks {
for _, transaction := range block.transactions {
if transaction.sender == address(a) {
balance -= transaction.amount
}
if transaction.recipient == address(a) {
balance += transaction.amount
}
}
}
return balance
}
// private
func (bc BlockChain) lastBlock() block {
return *bc.blocks[len(bc.blocks)-1]
}
mainでの挙動
block_chain/main.go
package main
import (
"fmt"
"goTestProject/block_chain/bchain"
)
func main() {
// ブロックチェーンの生成
bc := bchain.NewBlockChain()
// 取引の記録
bc.AddTransaction(
bchain.NewTransaction(
"Ken",
"Taro",
2,
),
)
bc.AddTransaction(
bchain.NewTransaction(
"Taro",
"Ken",
10,
),
)
// 上で作成した記録に対してマイニングを行い確定する
bc.Mine("Ken")
// マイニングの報酬が支払われているので、もう一度マイニングを行い確定する
bc.Mine("Ken")
fmt.Printf("%+v\n", bc)
fmt.Printf("block Chain valid: %v\n", bc.IsValid())
fmt.Printf("Ken balance: %f\n", bc.GetBalanceOfAddress("Ken"))
fmt.Printf("Taro balance: %f\n", bc.GetBalanceOfAddress("Taro"))
}
実行結果
残高がマイナスになることを考慮していないので、残高がマイナスになっていますが、マイニングとマイニングの報酬が支払われていることを確認できました。
Succeeded to mining
Succeeded to mining
blocks: [[] [{ Ken sent 2 => Taro } { Taro sent 10 => Ken }] [{ sent 12.5 => Ken }]]
block Chain valid: true
Ken balance: 20.500000
Taro balance: -8.000000
Discussion