私はここ4か月間、「政府および産業界におけるブロックチェーンベースのデータ保護および管理ツールの開発」というプロジェクトに取り組んでいます。
ここで、このプロジェクトをどのように始めたかをお話しし、プログラム コードを詳しく説明したいと思います。

これは一連の記事の最初の記事です。ここではサーバーとプロトコルについて説明します。実際、読者はこれらのブロックチェーン要素の独自のバージョンを書くこともできます。
— ブロックチェーンのデータ構造とトランザクション、およびデータベースとのやり取りを実装するパッケージについて説明します。
昨年のデジタルブレイクスルーハッカソンでは、分散型台帳技術を使って産業やデジタル経済に役立つシステムを作るというアイデアが投げかけられ、イノベーション支援基金から開発助成金も交付された(この助成金については、これからスタートアップを始める人向けに別途記事を書こうかな)、そして今、順番が回ってきた。
開発はGo言語で行われ、ブロックが保存されるデータベースはLevelDBです。
主な部分はプロトコル、サーバー(TCP と WebSocket を実行)です。前者はブロックチェーンを同期するためのもので、後者はクライアントに接続し、トランザクションや JavaScript からのコマンドなどを送信するためのものです。
前述のように、このブロックチェーンは主に、サプライヤーと顧客の間、または両者の間での製品の交換を自動化し、保護するために必要です。彼らはお互いを信頼することに急いでいません。しかし、課題は、計算機を内蔵した「小切手帳」を作成するだけでなく、製品ライフサイクルの作業で発生する日常的なタスクのほとんどを自動化するシステムを作成することです。これを担うバイトコードは、ブロックチェーンでは慣例となっているように、トランザクションの入力と出力に保存されます (トランザクション自体はブロックになっており、LevelDB のブロックは GOB 形式で事前にコード化されています)。まず、プロトコルとサーバー (ノードとも呼ばれます) について説明します。
プロトコルの動作は複雑ではなく、その主な目的は、特別なコマンドラインに応じて、通常はブロックまたはトランザクションであるいくつかのデータをロードするモードに切り替えることです。また、ノードが接続先とその状態を認識するために、インベントリの交換にも必要です (同期セッション用に接続されたノードは、IP が既知であり、状態データがメモリに保存されているため、「ネイバー」とも呼ばれます)。
フォルダ(ディレクトリとも呼ばれる) Linux)はGo言語ではパッケージと呼ばれ、このディレクトリ内のGoコードを含むすべてのファイルの先頭に package [folder_name_where_this_file_is] と記述します。そうしないと、コンパイラにパッケージを渡すことができません。まあ、Go言語を知っている人にとっては秘密でも何でもありません。これらのパッケージは次のとおりです。
- ネットワーク相互作用(サーバー、クライアント、プロトコル)
- 保存および送信されるデータの構造(ブロック、トランザクション)
- データベース(ブロックチェーン)
- コンセンサス
- スタック仮想マシン (xvm)
- 補助(暗号、タイプ)は今のところこれですべてです。
これはトレーニング バージョンであり、プロセス間通信といくつかの実験的なコンポーネントが欠けていますが、構造は開発中のものに対応しています。コメント欄に何かご提案がありましたら、今後の開発に喜んで考慮させていただきます。サーバーとパッケージについての説明 .
まずはサーバーを見てみましょう。
サーバー サブルーチンは、プロトコル パッケージのデータ構造を使用して、TCP プロトコル上で実行されるデータ サーバーの機能を実装します。
サブルーチンは次のパッケージを使用します。 , , 。パッケージ自体 tcp_server.go データ構造を含む サーブ.
type Serve struct {
Port string
BufSize int
ST *types.Settings
}次のパラメータを取ることができます。
- データが交換されるネットワークポート
- JSON形式のサーバー構成ファイル
- デバッグモードで実行するためのフラグ(プライベートブロックチェーン)
進捗:
- JSONファイルから構成を読み取る
- デバッグ モード フラグがチェックされます。設定されている場合、ネットワーク同期スケジューラは起動されず、ブロックチェーンはロードされません。
- 構成データ構造の初期化とサーバーの起動
サーバー
- プロトコルに従って TCP サーバーの起動とネットワークのやり取りを実行します。
- ポート番号、バッファ サイズ、および構造体へのポインターで構成される Serve データ構造体が含まれています。 タイプ.設定
- Runメソッドは、ネットワークインタラクションを開始します(指定されたポートで着信接続をリッスンし、新しい接続が受信されると、その処理は新しいスレッドのプライベートハンドルメソッドに転送されます)。
- В ハンドル 接続からのデータはバッファに読み込まれ、文字列表現に変換されて渡されます。 プロトコルの選択
- プロトコルの選択 戻る 結果 またはエラーが発生します。 結果 その後、 プロトコル解釈を返す 侵入者 — 型のオブジェクト データの解釈、または選択結果の処理中にエラーが発生する
- その後スイッチが実行される intrpr.コマンド[0] 次のいずれかがチェックされます。 結果、入力、エラー そしてセクションがあります デフォルト
- セクションで 結果 スイッチは値によって位置付けられます intrpr.コマンド[1] 値をチェックする バッファ長 и バージョン (それぞれの場合に対応する関数が呼び出されます)
機能 バージョンの取得 и バッファ長 ファイル内にある srvlib.go サーバー パッケージ。
GetVersion(conn net.Conn, version string)コンソールに出力し、パラメータで渡されたバージョンをクライアントに送信するだけです。
conn.Write([]byte("result:" + version)).
機能
BufferLength(conn net.Conn, intrpr *protocol.InterpreteData)次のようにブロック、トランザクション、またはその他の指定されたデータのロードを実行します。
- プロトコルで指定され、受け入れる必要のあるデータのタイプをコンソールに出力します。
fmt.Println("DataType:", intrpr.Commands[2]) - 値を読み取る intrpr.Body 数値変数に バッファ長
- バッファを作成する ニューバッファ 指定サイズ:
make([]byte, buf_len) - OK 応答を送信します:
conn.Write([]byte("result:ok")) - 読み取りストリームから完全なバッファ フィルを実行します。
io.ReadFull(conn, newbuf).
- バッファの内容をコンソールに出力します。
fmt.Println(string(newbuf))読み取られたバイト数
fmt.Println("Bytes length:", n) - OK 応答を送信します:
conn.Write([]byte("result:ok"))
サーバー パッケージのメソッドは、パッケージの関数を使用して受信したデータを処理するように構成されています。 .
プロトコル
プロトコルは、ネットワーク交換においてデータを表す手段として機能します。
Choice(str 文字列) (文字列、エラー) サーバーが受信したデータに対して一次処理を実行し、データの文字列表現を入力として受け取り、 通訳:
- 入力文字列は、次のようにしてヘッドとボディに分割されます。 ReqParseN2(文字列)
- head は要素に分割され、ReqParseHead(head) を使用してコマンド スライスに配置されます。
- В スイッチ(コマンド[0]) 受信したコマンドを選択(コマンド、キー、アドレス またはセクションがトリガーされる デフォルト)
- cmd 2のコマンドがチェックされる switch(commands[1]) — 長さ и getversion.
- 長さ データ型をチェックする コマンド[2] そしてそれを保存し データ・タイプ
- 確認する ボディ 文字列値を含む
len(body) < 1 - 応答文字列を返します:
"result:bufferlength:" + datatype + "/" + body - getversion 文字列を返す
return "result:version/auto"
通訳
InterpreteData 構造が含まれており、返されたデータの二次処理を実行します。 選択 弦と物体の形成 データの解釈.
type InterpreteData struct {
Head string
Commands []string
Body string
IsErr bool
ErrCode int
ErrMessage string
}機能
Interprete(str string) (*InterpreteData, error)文字列を受け取る 結果 オブジェクトへの参照を作成し返す データの解釈.
進捗:
- 同様に 選択 頭部と胴体は以下を使用して抽出されます ReqParseN2(文字列)
- ヘッドは要素に分解されます ReqParseHead(ヘッド)
- オブジェクトが初期化される データの解釈 そしてそれへのポインタが返されます:
res := &InterpreteData{
Head: head,
Commands: commands,
Body: body,
}
return res, nilこのオブジェクトは サーバー.go メインパッケージ。
クライアント
クライアントパッケージには関数が含まれています TCPコネクト и TCP応答データ.
機能
TCPConnect(s *types.Settings, data []byte, payload []byte)このように動作します:
- 渡された設定オブジェクトで指定された接続に接続が行われます。
net.Dial("tcp", s.Host + ":" + s.Port) - データ パラメータで渡されたデータは次のように送信されます。
conn.Write(data) - 答えは読み上げられています
resp, n, _ := TCPResponseData(conn, s.BufSize)コンソールに印刷されています
fmt.Println(string(resp[:n])) - 譲渡された場合 ペイロード そしてそれを渡す
conn.Write(payload)また、サーバーの応答を読み取り、コンソールに出力します。
機能
TCPResponseData(conn net.Conn, bufsiz int) ([]byte, int, error)指定されたサイズのバッファを作成し、サーバの応答をそこに読み込み、このバッファと読み取られたバイト数、およびエラー オブジェクトを返します。
サブルーチンクライアント
ノード サーバーにコマンドを送信したり、簡単な統計情報やテストを取得したりするために使用します。
次のパラメータを受け入れることができます: JSON 形式の構成ファイル、文字列としてサーバーに転送されるデータ、ペイロードに転送されるファイルへのパス、ノード スケジューラ エミュレーション フラグ、数値として転送されるデータのタイプ。
- 構成の取得
st := types.ParseConfig(*config) - emuフラグが渡されると、 シェデューラー
- ファイルへのパスにfフラグが指定されている場合は、そのデータを FDB コンテンツはサーバーに送信されます
client.TCPConnect(st, []byte(CMD_BUFFER_LENGTH + ":" + strconv.Itoa(*t) + "/" + strconv.Itoa(fdblen)), fdb) - ファイルが指定されていない場合は、フラグのデータがそのまま送信されます。 -d:
client.TCPConnect(st, []byte(*data), nil)
これはすべて、プロトコルの構造を示す簡略化された表現です。開発中に必要な機能がその構造に追加されます。
第 3 部ではブロックとトランザクションのデータ構造について説明し、第 4 部では JavaScript から接続するための WebSocket サーバーについて説明し、第 XNUMX 部では同期スケジューラについて検討し、その後、入力と出力からバイトコードを処理するスタック マシン、暗号化、および出力のプールについて説明します。
出所: habr.com
