こんにちは、Habr! 記事「 200行未満のGoで独自のブロックチェーンをコーディングしてください! 」の翻訳を紹介します 。

このレッスンは、Javascriptでのブロックチェーンの簡単な記述に関するよく適応された投稿です。 Goに移植し、ブラウザでの会話の表示などの追加機能を追加しました。
レッスンの例は、心拍数データに基づいています。 私たちは医療会社です。 楽しみのために、トレーニングコース中に心拍数 (1分あたりの拍数)を計算し、この数を考慮することができます。
世界中のほとんどすべての開発者がブロックチェーンについて聞いたことがありますが、ほとんどの場合、これがどのように機能するのかわかりません。 多くはビットコイン、 スマートコントラクトについて聞いただけです。 この投稿は、200行未満のコードでGoで独自のブロックチェーンを作成できるようにすることで、ブロックチェーンに関する噂を払拭しようとする試みです! このレッスンの最後に、ブロックチェーンにデータを実行してローカルで書き込み、ブラウザで表示できます。
自分でブロックチェーンを作成するよりも、ブロックチェーンについて学ぶ良い方法はありますか?
あなたにできること
- 独自のブロックチェーンを作成する
 - ブロックチェーンの整合性を維持する上でハッシュがどのように機能するかを理解
 - 新しいブロックの追加方法を見る
 - 複数のノードがブロックを生成するときの衝突の解決方法をご覧ください
 - ブラウザーでブロックチェーンのビューを作成します
 - 新しいブロックを追加
 - 基本的なブロックチェーンの知識を得る
 
できないこと
この投稿をシンプルにするために、仕事の 証明とステークの 証明のより高度な概念は考慮しません。 ネットワークの相互作用がシミュレートされるため、ブロックチェーンを表示し、追加されたブロックを表示できます。 ネットワーキングは将来の投稿のために予約されます。
さあ始めましょう!
設置
Goでコードを作成するため、既に開発した経験があることを前提としています。 インストール後、次のパッケージも使用します。
go get github.com/davecgh/go-spew/spew
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      Spewを使用すると、構造とスライスをコンソールに美しく出力できます。
 go get github.com/gorilla/mux
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      Gorilla / muxは、リクエストハンドラを記述するための一般的なパッケージです。
 go get github.com/joho/godotenv
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
        Gotdotenv
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    使用すると、ディレクトリのルートにある.env
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    ファイルから読み取ることができるため、コードにhttpポートなどのパラメーターを設定する必要がありません。 
 ディレクトリのルートに.env
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    ファイルを作成して、HTTP要求をリッスンするポートを決定します。 ファイルに行を追加するだけです: 
ADDR=8080
  main.go
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    ファイルを作成します。 実装全体がこのファイルに含まれ、200行未満のコードが含まれます。 
輸入品
パッケージのインポートとパッケージ宣言:
 package main import ( "crypto/sha256" "encoding/hex" "encoding/json" "io" "log" "net/http" "os" "time" "github.com/davecgh/go-spew/spew" "github.com/gorilla/mux" "github.com/joho/godotenv" )
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      データモデル
ブロックチェーンである各ブロックの構造を定義しましょう。 以下に、これらすべてのフィールドが必要な理由を説明します。
 type Block struct { Index int Timestamp string BPM int Hash string PrevHash string }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      各ブロックには、ブロックチェーンに記録され、各心拍数測定のイベントを表すデータが含まれています。
-   
Index
-ブロックチェーン内のデータレコードのインデックス -   
Timestamp
-データが書き込まれているときのタイムスタンプ -   
BPM
毎分ビート。 これはあなたの心拍数です -   
Hash
-現在のレコードを識別するSHA256識別子 -   
PrevHash
チェーン内の前のレコードを識別するSHA256識別子 
ブロックチェーンを宣言しましょう。ブロックチェーンは構造のほんの一部です。
 var Blockchain []Block
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
       では、ブロックとブロックチェーンでハッシュはどのように使用されますか? ハッシュを使用して、ブロックを正しい順序で識別して保存します。 各ブロックのPrevHash
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    フィールドは前のブロックのHash
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    フィールドを参照している(つまり、等しい)ため、正しいブロック順序がわかります。 
      新しいブロックのハッシュ化と作成
なぜハッシュする必要があるのですか? 次の2つの主な理由でハッシュを取得します。
- スペースを節約します。 ハッシュは、ブロック内のすべてのデータから作成されます。 私たちの場合、データブロックは数個しかありませんが、数百、数千、または数百万の以前のレコードからのデータがあると想像してください。 このデータをSHA256の1行でハッシュし、ハッシュをハッシュする方が、以前のブロックからすべてのデータを何度もコピーするよりもはるかに効率的です。
 - チェーンの整合性を維持します。 上の図で行ったように、以前のハッシュを保存することにより、ブロックチェーン内のブロックが正しい順序であることを確認できます。 攻撃者がFに参加してデータを操作(たとえば、生命保険の価格を修正するために心拍数を変更)したい場合、ハッシュは変化し始め、誰もがチェーンが「壊れている」ことを知り、誰もがこのチェーンを信頼できないことを知っています。
 
  Block
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    データを受け取り、それらのSHA256ハッシュを作成する関数を作成しましょう。 
 func calculateHash(block Block) string { record := string(block.Index) + block.Timestamp + string(block.BPM) + block.PrevHash h := sha256.New() h.Write([]byte(record)) hashed := h.Sum(nil) return hex.EncodeToString(hashed) }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
        calculateHash
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    関数は、 Block
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    構造のIndex
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     、 Timestamp
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     、 BPM
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     、 PrevHash
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    を1行に結合しIndex
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     。これは関数の引数であり、SHA256ハッシュの文字列表現としてすべてを返します。 これで、新しいgenerateBlock
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    関数を使用して、必要なすべての要素を含む新しいブロックを生成できます。 これを行うには、ハッシュとインデックスを取得できるように前のブロックを転送し、新しいBPM
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    心拍数値を送信する必要があります。 
 func generateBlock(oldBlock Block, BPM int) (Block, error) { var newBlock Block t := time.Now() newBlock.Index = oldBlock.Index + 1 newBlock.Timestamp = t.String() newBlock.BPM = BPM newBlock.PrevHash = oldBlock.Hash newBlock.Hash = calculateHash(newBlock) return newBlock, nil }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
       現在の時刻はtime.Now()
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    によってブロックに自動的に書き込まれることに注意してください。 また、 calculateHash
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    関数が呼び出されたことにも注意してください。 前のブロックのハッシュ値がPrevHash
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    フィールドにコピーされます。  Index
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    は、前のブロックの値から1だけ増加します。 
ブロックチェック
 ここで、前のブロックの有効性をチェックする関数を作成する必要があります。 これを行うには、 Index
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    をチェックして、期待どおりに成長することを確認します。 また、 PrevHash
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    前のブロックのHash
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     PrevHash
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    一致することを確認します。 最後に、現在のブロックのハッシュを再計算して、正しいことを確認します。 これらすべてのアクションを実行し、ブール値を返すisBlockValid
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    関数を書きましょう。 すべてのチェックが正しくパスした場合、関数はtrue
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    を返しtrue
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     。 
 func isBlockValid(newBlock, oldBlock Block) bool { if oldBlock.Index+1 != newBlock.Index { return false } if oldBlock.Hash != newBlock.PrevHash { return false } if calculateHash(newBlock) != newBlock.Hash { return false } return true }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      ブロックチェーンエコシステムの2つのノードがチェーンにブロックを追加し、両方がそれらを取得したときに問題が発生した場合はどうなりますか。 どのソースを適切なソースとして選択しますか? 最も長いチェーンを選択します。 これは古典的なブロックチェーンの問題です。
したがって、受け入れる新しいチェーンが現在のチェーンよりも長いことを確認しましょう。 もしそうなら、新しいブロックを持つ新しいチェーンでチェーンを書き換えることができます。
      チェーンスライスの長さを比較するだけです。
 func replaceChain(newBlocks []Block) { if len(newBlocks) > len(Blockchain) { Blockchain = newBlocks } }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      成功したら、背中を軽く叩くことができます! ブロックチェーンの機能フレームワークについて説明しました。
今、私たちはブロックチェーンを表示して、理想的にはブラウザで書き込み、友人に自慢できる便利な方法が必要です!
Webサーバー
Webサーバーがどのように機能するかを既に理解しており、Goの使用経験があることを前提としています。
 以前にダウンロードしたGorrila/mux
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    パッケージを使用します。  run
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    関数を作成してサーバーを起動し、後で呼び出します。 
 func run() error { mux := makeMuxRouter() httpAddr := os.Getenv("ADDR") log.Println("Listening on ", os.Getenv("ADDR")) s := &http.Server{ Addr: ":" + httpAddr, Handler: mux, ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, MaxHeaderBytes: 1 << 20, } if err := s.ListenAndServe(); err != nil { return err } return nil }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
       ポートは、以前に作成した.env
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    ファイルから構成されていることに注意してください。  log.Printlnメソッドを呼び出して、サーバーの起動に関する情報をコンソールに表示します。 サーバーをセットアップしてListenAndServe
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    を呼び出しListenAndServe
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     。  Goの一般的なプラクティス。 
 次に、ハンドラーを定義するmakeMuxRouter
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    関数を作成する必要があります。 ブラウザでブロックチェーンを表示および記録するには、2つの単純なルートで十分です。  localhost
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    にGET
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    リクエストを送信した場合、チェーンを確認します。  POST
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    リクエストを送信すると、データを書き込むことができます。 
 func makeMuxRouter() http.Handler { muxRouter := mux.NewRouter() muxRouter.HandleFunc("/", handleGetBlockchain).Methods("GET") muxRouter.HandleFunc("/", handleWriteBlock).Methods("POST") return muxRouter }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
        GET
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    リクエストハンドラー: 
 func handleGetBlockchain(w http.ResponseWriter, r *http.Request) { bytes, err := json.MarshalIndent(Blockchain, "", " ") if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } io.WriteString(w, string(bytes)) }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
        JSON形式でブロックチェーンを説明します。これは、 localhost:8080
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    任意のブラウザーで表示できます。  .env
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    ファイルでポートを指定できます。 
  POST
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    リクエストはもう少し複雑で、新しいMessage
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    メッセージ構造が必要です。 
 type Message struct { BPM int }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      ブロックチェーン書き込みハンドラーのコード。
 func handleWriteBlock(w http.ResponseWriter, r *http.Request) { var m Message decoder := json.NewDecoder(r.Body) if err := decoder.Decode(&m); err != nil { respondWithJSON(w, r, http.StatusBadRequest, r.Body) return } defer r.Body.Close() newBlock, err := generateBlock(Blockchain[len(Blockchain)-1], m.BPM) if err != nil { respondWithJSON(w, r, http.StatusInternalServerError, m) return } if isBlockValid(newBlock, Blockchain[len(Blockchain)-1]) { newBlockchain := append(Blockchain, newBlock) replaceChain(newBlockchain) spew.Dump(Blockchain) } respondWithJSON(w, r, http.StatusCreated, newBlock) }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
       別のメッセージ構造を使用した理由は、 POST
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    要求の本文がJSON
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    あり、それを使用して新しいブロックを書き込むためです。 これにより、次のフォームのPOST
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    リクエストを送信でき、ハンドラーが残りのブロックを埋めてくれます。 
 {"BPM":50}
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
        50
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    は心拍数の例です。 心拍数の値を使用できます。 
 リクエストボディをvar m Message
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    構造にデコードした後、新しいブロックを作成し、前のサイドと新しいパルス値を前にgenerateBlock
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    したgenerateBlock
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    関数に渡します。  isBlockValid
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    関数を使用して、新しいブロックが正しいことを確認するために簡単なチェックを行いましょう。 
注:
-   
spew.Dump
は、構造をコンソールにうまく表示する便利な機能です。 デバッグに役立ちます。 -   クエリをテストするには、Postmanを使用します。  端末から離れられない場合も
curl
は同様に機能します。 
  POST
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    リクエストが成功または失敗したときに通知を受け取りたい。 小さなラッパーを使用して結果を取得します。  Goではエラーが無視されることはありません。 
 func respondWithJSON(w http.ResponseWriter, r *http.Request, code int, payload interface{}) { response, err := json.MarshalIndent(payload, "", " ") if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("HTTP 500: Internal Server Error")) return } w.WriteHeader(code) w.Write(response) }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      ほぼ完了!
 すべての開発を1つの関数main
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    結合しましょう。 
 func main() { err := godotenv.Load() if err != nil { log.Fatal(err) } go func() { t := time.Now() genesisBlock := Block{0, t.String(), 0, "", ""} spew.Dump(genesisBlock) Blockchain = append(Blockchain, genesisBlock) }() log.Fatal(run()) }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      ここで何が起こっていますか?
-   
godotenv.Load()
使用すると、.env
ファイルから変数を読み取ることができます - GENESISBLOCKは、メインのメイン機能の最も重要な部分です。 最初のブロックを初期化する必要があります、なぜなら 前のブロックはまだ存在しません。
 
すべて準備完了です!
  githubですべてのコードを取得できます 
      
        
        
        
      
     コードを確認しましょう。 
      
        
        
        
      
     ターミナルでgo run main.go
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    アプリケーションを起動します 
      
        
        
        
      
     ターミナルで、Webサーバーが動作していることを確認し、初期化された最初のブロックの出力を取得します。 
      localhost:8080にアクセスします。 予想どおり、最初のブロックが表示されます。
       次に、 POST
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    リクエストを送信してブロックを追加しましょう。  Postmanを使用して、異なるBPM
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    値を持ついくつかの新しいブロックを追加します。 
curlコマンド(翻訳者から):
 curl -X POST http://localhost:8080/ -H 'content-type: application/json' -d '{"BPM":50}'
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      
       ブラウザでページを更新します。 これで、チェーン内の新しいブロックを確認できます。  PrevHash
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    新しいブロックには古いブロックのHash
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    に対応するPrevHash
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    が含まれています! 
      将来的には
おめでとうございます! 正しいハッシュとブロック検証を使用してブロックチェーンを作成しました。 これで、プルーフオブワーク、プルーフオブステーク、スマートコントラクト、Dapps、サイドチェーンなど、より複雑なブロックチェーンの問題を学習できます。
このレッスンでは、Proof of Workを使用して追加される新しいブロックなどのトピックは扱いません。 これは別のレッスンになりますが、Proof of Workメカニズムがない多くのブロックチェーンがあります。 現在、Webサーバーでブロックチェーンデータを記録および表示することにより、すべてがモデル化されています。 このチュートリアルにはP2Pコンポーネントはありません。
Proof of Workメカニズムを追加してネットワークで作業する場合は、 Telegramチャットで報告するか、 Twitterでフォローしてください! これらは私達に連絡する最良の方法です。 レッスンに対する新しいフィードバックと新しい提案をお待ちしています。 私たちはあなたから聞いてうれしいです!
Coral Healthの詳細と医学研究でブロックチェーンを使用する方法については、当社のWebサイトをご覧ください。
PS翻訳の作者は、翻訳の指摘された誤りと不正確さに感謝します。