パート2:ネットワーキング-200行未満のGoコードでブロックチェーンを記述する

画像

このシリーズの最初の部分を読みましたか? そうでない場合は、見てください。 心配しないで、お待ちください...


ようこそ


最初の投稿「Goでの200行未満のコードでのブロックチェーンの記述」からのフィードバックに驚きました。 初心者のブロックチェーン開発者向けの小さなレッスンで意図されていたことが、新しい生活を始めました。 ネットワークを追加する投稿を行うリクエストが殺到しました。


始める前に、 Telegramチャットに参加できます! これは私たちに質問し、フィードバックを与え、新しいレッスンを求めるのに最適な場所です。 コードに関するヘルプが必要な場合は、これが最適な場所です。


前回の投稿では、新しいブロックをハッシュして検証する独自のブロックチェーンを作成する方法を示しました。 しかし、これはすべて1つのメモで実行されました。 別のノードをメインアプリケーションに接続して新しいブロックを作成し、他のすべてのノードでブロックチェーン全体を更新するにはどうすればよいですか?


作業プロセス


画像

ステップ1



ステップ2



ステップ3



レッスンの後、自分でやってみてください。それぞれの新しいターミナルは「最初の」ターミナルとしても機能しますが、異なるTCPポートを持ち、それぞれがネットワークの正しい操作のための接続を持っています。


あなたにできること



できないこと


前の投稿と同様に、このレッスンの目的は、ノードからコアネットワークを取得することです。これにより、ブロックチェーンを自分でさらに学習できます。 最初の端末に書き込む別のネットワークからコンピューターを追加することはできませんが、これはクラウドでバイナリを実行することで実現できます。 さらに、各ノードのブロックのチェーンがモデル化されます。 心配しないで、すぐにすべてを説明します。


コーディングを始めましょう!


場所は、以前の投稿のレビューになります。 ブロック生成、ハッシュ、チェックなど、多くの機能を残しましょう。 HTTP機能は使用しませんが、コンソールで結果を表示し、TCPを使用してネットワークで作業します。


TCPとHTTPの違いは何ですか?


詳細については説明しませんが、知っておく必要があるのは、TCPがデータを送信する基礎となるプロトコルであるということだけです。 HTTPは、インターネットとブラウザ間のこのデータ転送を使用するために、TCPスタックの上に構築されます。 Webサイトを閲覧するときは、HTTPプロトコルを使用しています。 このレッスンでは、ブラウザで何も表示する必要がないため、TCPを使用します。 Goには、必要なすべてのTCP接続機能を提供する優れたネットワークパッケージがあります。


インストール、インポート、および概要


いくつかの実装は、 最初の部分ですでに検討されています。 ブロックチェーンを生成して検証するには、前の記事の関数を使用します。


設置


メインディレクトリに.envファイルを作成し、 .envの行を追加します。


 ADDR=9000 

使用するTCPポート番号(この場合は9000)をADDRと呼ばれる環境変数に保存します。


まだ行っていない場合は、次のパッケージをインストールします。


 go get github.com/davecgh/go-spew/spew 

コンソールにブロックチェーンの美しいプリントを


 go get github.com/joho/godotenv 

.envファイルから変数を読み取ります。
空のmain.goファイルを作成します。 コードをそこに配置します。


輸入品


必要なパッケージ宣言とインポート:


 package main import ( "bufio" "crypto/sha256" "encoding/hex" "encoding/json" "io" "log" "net" "os" "strconv" "time" "github.com/davecgh/go-spew/spew" "github.com/joho/godotenv" ) 

復習
次のコードフラグメントは、 最初の部分で詳しく説明されています。


Block構造を作成してブロックスライスを宣言しましょう。これがブロックチェーンになります。


 type Block struct { Index int Timestamp string BPM int Hash string PrevHash string } var Blockchain []Block 

また、新しいブロックを作成するときに必要になるハッシュ関数も宣言します。


 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) } 

ブロックを作成する機能:


 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 } 

新しいブロックが正しいことを確認できます。このため、 PrevHashフィールドが前のブロックのHashフィールドを参照していることを確認します。


 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 } 

これで、最も長いチェーンを正しいチェーンとすることが保証されます。


 func replaceChain(newBlocks []Block) { if len(newBlocks) > len(Blockchain) { Blockchain = newBlocks } } 

いいね! ブロックのチェーンを操作するための基本的な機能を取得しました。 これで、ネットワーク接続の作成に進むことができます。


ネットワーク接続


新しいブロックを転送し、チェーンに統合し、ネットワークの新しいチェーンをブロードキャストできるネットワークを作成しましょう。


main関数から始めましょうが、その前に、グローバル変数bcServer宣言しましょう。これは、着信ブロックを受け入れるチャネルです。


 var bcServer chan []Block 

注:チャネルはGoで最も人気のあるツールの1つであり、美しい読み取り/書き込みデータ実装を提供し、競合状態を防ぐために最もよく使用されます。 複数のGo-routineが同じチャネルで有能に(並列処理と混同しないように)記述している場合、それらは強力なツールになります。 通常、Javaおよびその他のCライクな言語では、データにアクセスするにはミューテックスをロックおよびロック解除する必要があります。 Goにはチャネルもありますが、Goにはミューテックスがあります。 詳細については、 こちらをご覧ください


ここで、メイン関数を宣言し、ルートディレクトリにある.envファイルから環境変数をロードしましょう。 また、main関数でbcServerインスタンスを実行します。


 func main() { err := godotenv.Load() if err != nil { log.Fatal(err) } bcServer = make(chan []Block) // create genesis block t := time.Now() genesisBlock := Block{0, t.String(), 0, "", ""} spew.Dump(genesisBlock) Blockchain = append(Blockchain, genesisBlock) } 

次に、TCPサーバーを作成する必要があります。 TCPサーバーはHTTPサーバーに似ていますが、ブラウザーで動作するにはTCPプロトコルだけでは不十分です。 すべてのデータはコンソールを介して表示されます。 TCPポートへのいくつかの接続を処理します。 これをメイン関数に追加します。


 server, err := net.Listen("tcp", ":"+os.Getenv("ADDR")) if err != nil { log.Fatal(err) } defer server.Close() 

このコードは、ポート9000でTCPサーバーを起動します。接続が不要になったときに接続が閉じるように、 defer server.Close()を実行defer server.Close()ことが重要です。 遅延の詳細については、 こちらをご覧ください


ここで、接続を確立する要求を受け取るたびに新しい接続を作成する必要があり、それを処理する必要があります。 別のコードを追加します。


 for { conn, err := server.Accept() if err != nil { log.Fatal(err) } go handleConn(conn) } 

新しい接続を受け入れる無限ループを作成します。 競争力のある処理のために、Goのルーチンgo handleConn(conn)のハンドラーで各接続を開始するため、ループを停止しません。 したがって、複数の接続を競争的に同時に聞くことができます。


注意深い読者は、 handleConnハンドラーhandleConn宣言されていないことに気付くでしょう。 これまでにメイン関数main作成しました。 完全に次のようになります。


 func main() { err := godotenv.Load() if err != nil { log.Fatal(err) } bcServer = make(chan []Block) // create genesis block t := time.Now() genesisBlock := Block{0, t.String(), 0, "", ""} spew.Dump(genesisBlock) Blockchain = append(Blockchain, genesisBlock) // start TCP and serve TCP server server, err := net.Listen("tcp", ":"+os.Getenv("ADDR")) if err != nil { log.Fatal(err) } defer server.Close() for { conn, err := server.Accept() if err != nil { log.Fatal(err) } go handleConn(conn) } } 

ここで、 handleConn関数を作成しましょう。 引数は1つだけで、これはnet.Connインターフェイスです。 私たちの意見では、Goのインターフェイスは印象的であり、すべてのCライクな言語と区別されます。 競争力とGoルーチンは言語を宣伝しますが、インターフェイスと、インターフェイスを明示的に実装できないという事実が最も強力な言語機能です。 Goでインターフェースをまだ使用していない場合は、できるだけ早くそれらを確認してください。 インターフェイスは、Go開発者になるための次のステップです。


ハンドラ関数deferに遅延接続遅延クロージャを配置して、完了時に閉じることを忘れないようにします。


 func handleConn(conn net.Conn) { defer conn.Close() } 

次に、クライアントが新しいブロックをチェーンに追加できるようにする必要があります。 データには、 最初の部分と同様に、心拍数を使用します。 心拍数を1分間測定し、この数値を覚えておいてください。 これはBPMパラメーター(ビート/分)になります


上記を実装するには、次のものが必要です。



上記の機能を実装するコード:


 io.WriteString(conn, "Enter a new BPM:") scanner := bufio.NewScanner(conn) go func() { for scanner.Scan() { bpm, err := strconv.Atoi(scanner.Text()) if err != nil { log.Printf("%v not a number: %v", scanner.Text(), err) continue } newBlock, err := generateBlock(Blockchain[len(Blockchain)-1], bpm) if err != nil { log.Println(err) continue } if isBlockValid(newBlock, Blockchain[len(Blockchain)-1]) { newBlockchain := append(Blockchain, newBlock) replaceChain(newBlockchain) } bcServer <- Blockchain io.WriteString(conn, "\nEnter a new BPM:") } }() 

新しいスキャナーを作成します。 for scanner.Scan()は、Goルーチンで他の接続とは別に競合的に機能するループです。 BMP値の文字列をすばやく変換します(常にinteger型になるので、チェックしてください)。 標準のブロック生成を実行し、ブロックの有効性を確認し、新しいブロックをチェーンに追加します。


構文bcServer <- Blockchainは、以前に作成したチャネルに新しいチェーンをドロップすることを意味します。 次に、クライアントが新しいBPM値を入力して次のブロックを作成することをお勧めします。


放送チャンネル
TCPサーバー上のすべての接続に対して、新しいブロックチェーンを送信する必要があります。 1台のコンピューターでプログラムを作成するため、データがすべてのクライアントに送信される方法をシミュレートする必要があります。 handleConn関数handleConnhandleConnを追加する必要があります。



すべてを正しい順序で実行するコードを次に示します。


 go func() { for { time.Sleep(30 * time.Second) output, err := json.Marshal(Blockchain) if err != nil { log.Fatal(err) } io.WriteString(conn, string(output)) } }() for _ = range bcServer { spew.Dump(Blockchain) } 

いいね! handleConn関数の準備handleConnできました。 実際、プログラム全体の準備が整っており、200行のコードでコンパクトに保ちました。 それは悪くないですか?


コード全体はこちらから入手できます


面白いもの


コードを使用してディレクトリに移動し、実行してプログラムを実行しましょうgo run main.go


画像

予想どおり、ベースユニットが表示されます。 同時に、ポート9000でTCPサーバーを起動し、複数の接続を受け入れることができます。


新しいターミナルウィンドウを開き、 nc localhost 9000を使用してTCPサーバーに接続します。 端末で異なる色を使用するため、これらが異なるクライアントであることは明らかです。 複数のクライアントを起動するには、異なるターミナルセッションでこれを数回行います。


画像
画像
画像

次に、いずれかのクライアントでBPMを入力します。 新しいブロックが最初のターミナルに追加されたことがわかります! ネットワークは機能しています!


画像
画像

30秒待っています。 他のクライアントのいずれかに移動すると、これらのクライアントがBPMに入らなかった場合でも、新しいブロックチェーンがすべてのクライアントに渡されたことがわかります。


画像

次のステップ


おめでとうございます! 前回のレッスンから独自のブロックチェーンを作成しただけでなく、ネットワークも追加しました。 次に進むにはいくつかの方向があります。



PS翻訳の作者は、翻訳の指摘された誤りと不正確さに感謝します。



Source: https://habr.com/ru/post/J348624/


All Articles