ビットコイン ソースコード研究ノート (パート 2)

ビットコイン ソースコード研究ノート (パート 2)

第2章

この章では、前章でのトランザクション作成に続いて、Bitcoin クライアントがデータをシリアル化するプロセスについて説明します。

Bitcoin クライアントのすべてのシリアル化関数は、seriliaze.h に実装されています。その中でも、CDataStream クラスはデータシリアル化の中核構造です。

データストリーム

CDataStream には、シリアル化されたデータを格納するための文字クラス コンテナーがあります。コンテナ タイプとデータ処理用のストリーム インターフェイスを組み合わせます。この機能を実現するために、6 つのメンバー関数を使用します。

クラス CDataStream
{
保護されています:
    typedef vector<char, secure_allocator<char> > vector_type;
    ベクトル型vch;
    符号なし整数nReadPos;
    短い状態;
    短い exceptmask;
公共:
    int nType;
    int バージョン;
    //.......
}
  • vch はシリアル化されたデータを格納します。これは、カスタム メモリ アロケータを備えた文字コンテナー タイプです。このメモリ アロケータは、メモリの割り当て/割り当て解除が必要なときにコンテナ実装によって呼び出されます。メモリ アロケータは、メモリをオペレーティング システムに解放する前にメモリ内のデータをクリアし、ローカル マシン上の他のプロセスがデータにアクセスするのを防ぎ、データ ストレージのセキュリティを確保します。このメモリ アロケータの実装についてはここでは説明しません。serialize.h で確認できます。

  • nReadPos は、データを読み取る vch の開始位置です。

  • 状態はエラーインジケーターです。この変数は、シリアル化/デシリアル化中に発生する可能性のあるエラーを示すために使用されます。

  • exceptmask はエラーマスクです。 ios::badbit に初期化されます | ios::failbit。状態と同様に、エラーの種類を示すために使用されます。

  • nType の値は、SER_NETWORK、SER_DISK、SER_GETHASH、SER_SKIPSIG、SER_BLOCKHEADERONLY のいずれかであり、その機能は、CDataStream に特定のシリアル化操作を実行するように通知することです。これら 5 つのシンボルは列挙型 enum で定義されています。各シンボルは int 型 (4 バイト) であり、その値は 2 の累乗です。

列挙型
{
    // 主なアクション
    SER_NETWORK = (1 << 0)、
    SER_DISK = (1 << 1)、
    SER_GETHASH = (1 << 2)、
    // 修飾子
    SER_SKIPSIG = (1 << 16)、
    SER_BLOCKHEADERONLY = (1 << 17)、
};
  • nVersion はバージョン番号です。

CDataStream::read() および CDataStream::write()

メンバー関数 CDataStream::read() および CDataStream::write() は、CDataStream オブジェクトのシリアル化/逆シリアル化を実行するために使用される低レベル関数です。

 CDataStream& 読み取り(char* pch, int nSize)
    {
        // バッファの先頭から読み取る
        assert(nSize >= 0);
        符号なし整数 nReadPosNext = nReadPos + nSize;
        (nReadPosNext >= vch.size()) の場合
        {
            (nReadPosNext > vch.size()) の場合
            {
                setstate(ios::failbit, "CDataStream::read() : データの終わり");
                memset(pch, 0, nSize);
                nSize = vch.size() - nReadPos;
            }
            memcpy(pch, &vch[nReadPos], nSize);
            読み取り位置 = 0;
            vch.clear();
            (*これ) を返します。
        }
        memcpy(pch, &vch[nReadPos], nSize);
        読み取り位置 = 読み取り位置次へ;
        (*これ) を返します。
    }
 CDataStream& write(const char* pch, int nSize)
    {
        // バッファの末尾に書き込む
        assert(nSize >= 0);
        vch.insert(vch.end(), pch, pch + nSize);
        (*これ) を返します。
    }

CDataStream::read() は、CDataStream から nSize 文字を char* pch が指すメモリ空間にコピーします。実装方法は次のとおりです。

  • vch から読み取るデータの終了位置を計算します。unsigned int nReadPosNext = nReadPos + nSize。

  • 終了位置が vch のサイズより大きい場合、現在読み取るデータが不足しています。この場合、関数 setState() を呼び出してすべてのゼロを pch にコピーすることにより、状態は ios::failbit に設定されます。

  • それ以外の場合は、memcpy(pch, &vch[nReadPos], nSize) を呼び出して、vch の位置 nReadPos から始まる nSize 文字を、pch が指す事前割り当てメモリにコピーします。次に、nReadPos から次の開始位置 nReadPosNext (行 22) に移動します。

この実装は、1) ストリームからデータの一部が読み取られた後は、再度読み取ることができないことを示しています。 2) nReadPos は最初の有効なデータの読み取り位置です。

CDataStream::write() は非常にシンプルです。 pch が指す nSize 文字を vch の末尾に追加します。

マクロ READDATA() および WRITEDATA()

CDataStream::read() 関数と CDataStream::write() 関数は、プリミティブ型 (int、bool、unsigned long など) をシリアル化/逆シリアル化するために使用されます。これらのデータ型をシリアル化するには、これらの型のポインターを char* に変換します。これらの型のサイズがわかっているので、CDataStream から読み取ったり、文字バッファに書き込んだりすることができます。これらの関数を参照するために使用される 2 つのマクロがヘルパーとして定義されています。

 #define WRITEDATA(s, obj) s.write((char*)&(obj), sizeof(obj))
#define READDATA(s, obj) s.read((char*)&(obj), sizeof(obj))

これらのマクロの使用方法の例を次に示します。次の関数は、unsigned long 型をシリアル化します。

テンプレート<typename Stream> inline void Serialize(Stream& s, unsigned long a, int, int=0) { WRITEDATA(s, a); }

WRITEDATA(s, a) を独自の定義に置き換えます。拡張された関数は次のとおりです。

テンプレート<typename Stream> inline void Serialize(Stream& s, unsigned long a, int, int=0) { s.write((char*)&(a), sizeof(a)); }

この関数は、unsigned long パラメータ a を受け取り、そのメモリ アドレスを取得し、ポインタを char* に変換して、関数 s.write() を呼び出します。

CDataStream の演算子 << と >>

CDataStream は、シリアル化と逆シリアル化のために演算子 << と >> をオーバーロードします。

    テンプレート<typename T>
    CDataStream& 演算子<<(const T& obj)
    {
        // このストリームにシリアル化します
        ::Serialize(*this, obj, nType, nVersion);
        (*これ) を返します。
    }
    テンプレート<typename T>
    CDataStream& 演算子>>(T& オブジェクト)
    {
        // このストリームからアンシリアル化します
        ::Unserialize(*this, obj, nType, nVersion);
        (*これ) を返します。
    }

ヘッダー ファイル serialize.h には、14 個のプリミティブ型 (char、short、int、long、long long の符号付きおよび符号なしバージョン、および char、float、double、bool) に対するこれら 2 つのグローバル関数の 14 個のオーバーロードと、6 個の複合型 (string、vector、pair、map、set、CScript) に対する 6 個のオーバーロードが含まれています。したがって、これらの型の場合、次のコードを使用するだけでデータをシリアル化/逆シリアル化できます。

 CDataStream ss(SER_GETHASH);
ss<<obj1<<obj2; //シリアル化 ss>>obj3>>obj4; //デシリアライズ

2 番目の引数 obj に一致する実装タイプがない場合は、次の汎用 T グローバル関数が呼び出されます。

テンプレート<typename Stream, typename T>
インライン void Serialize(Stream& os, const T& a, long nType, int nVersion=VERSION)
{
    a.Serialize(os, (int)nType, nVersion);
}

このジェネリック バージョンでは、シグネチャ T::Serialize(Stream, int, int) を持つメンバー関数を実装するために、型 T を使用する必要があります。これは a.Serialize() を介して呼び出されます。

型をシリアル化する方法

前回の紹介では、ジェネリック型 T はシリアル化のために次の 3 つのメンバー関数を実装する必要があります。

    符号なし int GetSerializeSize(int nType=0, int nVersion=VERSION) const;
    void Serialize(Stream& s, int nType=0, int nVersion=VERSION) const;
    void アンシリアル化(Stream& s, int nType=0, int nVersion=VERSION);

これら 3 つの関数は、ジェネリック型 T を持つ対応するグローバル関数によって呼び出されます。これらのグローバル関数は、CDataStream のオーバーロードされた演算子 << および >> によって呼び出されます。

マクロ IMPLEMENT_SERIALIZE(statements) は、任意の型に対してこれら 3 つの関数の実装を定義するために使用されます。

 #define IMPLEMENT_SERIALIZE(ステートメント) \
    符号なし int GetSerializeSize(int nType=0, int nVersion=VERSION) const \
    {\
        CSerActionGetSerializeSize ser_action; \
        定数ブール fGetSize = true; \
        定数ブールfWrite = false; \
        定数ブール fRead = false; \
        符号なし整数nSerSize = 0; \
        ser_streamプレースホルダーs; \
        s.nType = nType; \
        s.nVersion = nVersion; \
        {ステートメント} \
        nSerSize を返します。 \
    } \
    テンプレート<typename Stream> \
    void Serialize(Stream& s, int nType=0, int nVersion=VERSION) const \
    {\
        CSerActionser_action をシリアル化します。 \
        定数ブール fGetSize = false; \
        定数ブールfWrite = true; \
        定数ブール fRead = false; \
        符号なし整数nSerSize = 0; \
        {ステートメント} \
    } \
    テンプレート<typename Stream> \
    void アンシリアル化(Stream& s, int nType=0, int nVersion=VERSION) \
    {\
        CSerActionser_action をアンシリアル化します。 \
        定数ブール fGetSize = false; \
        定数ブールfWrite = false; \
        定数ブール fRead = true; \
        符号なし整数nSerSize = 0; \
        {ステートメント} \
    }

次の例は、このマクロの使用方法を示しています。

 #include <iostream>
#include "serialize.h"
名前空間 std を使用します。
クラスAClass{
公共:
    AClass(int xin) : x(xin){};
    整数x;
    IMPLEMENT_SERIALIZE(READWRITE(this->x);)
}
int main() {
    CDataStream astream2;
    クラスaObj(200); // x が 200 の AClass 型オブジェクト cout<<"aObj="<<aObj.x>>endl;
    asream2<<aObj;
    Aクラスa2(1) // x が 1 である別のオブジェクト astream2>>a2
    cout<<"a2="<<a2.x<<endl;
    0を返します。
}

このプログラムは、AClass オブジェクトをシリアル化/デシリアル化します。次の結果が画面に出力されます。

 200 のオブジェクト
a2=200

AClass の次の 3 つのシリアル化/逆シリアル化メンバー関数は、1 行のコードで実装できます。

IMPLEMENT_SERIALIZE(READWRITE(this->x);)

マクロREADWRITE()の定義は次のとおりです。

 #define READWRITE(obj) (nSerSize += ::SerReadWrite(s, (obj), nType, nVersion, ser_action))

このマクロの展開は、マクロ IMPLEMENT_SERIALIZE(ステートメント) の 3 つの関数すべてに配置されます。したがって、一度に次の 3 つのことを行う必要があります。1) シリアル化されたデータのサイズを返す、2) データをストリームにシリアル化 (書き込む) する。 3) ストリームからデータを逆シリアル化 (読み取り) します。マクロ IMPLEMENT_SERIALIZE(ステートメント) のこれらの 3 つの関数の定義を参照してください。

マクロ READWRITE(obj) がどのように機能するかを理解するには、まず、nSerSize、s、nType、nVersion、および ser_action が完全な形式でどこから来るのかを理解する必要があります。これらはすべて、マクロ IMPLEMENT_SERIALIZE(ステートメント) の 3 つの関数本体から取得されます。

  • nSerSize は unsigned int であり、3 つの関数すべてで 0 に初期化されます。

  • ser_action は、3 つの関数すべてで宣言されるオブジェクトですが、3 つの異なる型です。これは、CSerActionGetSerializeSize、CSerActionSerialize、および CSerActionUnserialize の 3 つの関数です。

  • s は最初の関数で ser_streamplaceholder 型として定義されます。これは他の 2 つの関数に渡される最初のパラメーターであり、パラメーター タイプは Stream です。

  • nType と nVersion は、3 つの関数すべてにおいて入力パラメータです。

したがって、マクロ READWRITE() がマクロ IMPLEMENT_SERIALIZE() に展開されると、そのシンボルはすべてマクロ IMPLEMENT_SERIALIZE() の本体にすでに存在しているため、評価されます。 READWRITE(obj) の拡張は、グローバル関数::SerReadWrite(s, (obj), nType, nVersion, ser_action) を呼び出します。この関数の 3 つのバージョンをすべて次に示します。

テンプレート<typename Stream, typename T>
インライン unsigned int SerReadWrite(Stream& s, const T& obj, int nType, int nVersion, CSerActionGetSerializeSize ser_action)
{
    戻り値::GetSerializeSize(obj, nType, nVersion);
}
テンプレート<typename Stream, typename T>
インライン unsigned int SerReadWrite(Stream& s, const T& obj, int nType, int nVersion, CSerActionSerialize ser_action)
{
    ::Serialize(s, obj, nType, nVersion);
    0を返します。
}
テンプレート<typename Stream, typename T>
インライン unsigned int SerReadWrite(Stream& s, T& obj, int nType, int nVersion, CSerActionUnserialize ser_action)
{
    ::Unserialize(s, obj, nType, nVersion);
    0を返します。
}

ご覧のとおり、関数 ::SerReadWrite() は 3 つのバージョンにオーバーロードされています。最後のパラメータに応じて、それぞれグローバル関数 ::GetSerialize()、::Serialize()、::Unserialize() を呼び出します。これら 3 つの機能は前の章で紹介しました。

::SerReadWrite() の 3 つの異なるバージョンの最後のパラメータを調べると、それらはすべて void 型であることがわかります。これら 3 つのタイプの唯一の目的は、マクロ IMPLEMENT_SERIALIZE() によって定義されるすべての関数によって使用される ::SerReadWrite() の 3 つのバージョンを区別することです。

<<:  日本がビットコイン価格を新たな高値に導き、史上最高値の1,277ドルに迫る

>>:  テンセントはブロックチェーンのホワイトペーパーを発表し、エンタープライズレベルのブロックチェーンインフラプラットフォームの構築を目指す

推薦する

ビッグブルーIBM: サプライチェーン + モノのインターネット + ブロックチェーン = 未来

多くの人が暗号通貨ビットコインについて聞いたことがあるが、その基盤となる技術であるブロックチェーンを...

世界最大の資産運用会社ブラックロック:ビットコインは世界市場資産に進化する可能性がある

世界最大の資産運用会社ブラックロックのトップは最近、ビットコインが世界的な市場資産に進化する可能性が...

マイニングマシン企業が香港証券取引所に上場できないのはなぜでしょうか?

火曜日、有名なデジタル暗号通貨マイニングマシンメーカーであるビットメインは、社内文書の中で、香港証券...

ビットコインは米国の陰謀か?

マスク氏が登壇、ウォール街は買い漁り!ビットコインの急騰は米国の陰謀か? 2021年3月13日、ビッ...

CMEグループは12月11日にビットコイン先物契約の正式な開始を確定し、BTCは8,200ドルを突破して新たな高値を記録した。

ビットコイン投資家にとって、今年はクリスマスが早く来そうだ。先月、世界最大のデリバティブ市場であるC...

機関投資家の関心によりイーサリアム価格は4,300ドルまで上昇する可能性

主要な暗号資産の 1 つであるイーサリアムのブロックチェーン技術は、市場の新たな熱狂の中心となってい...

コインゾーントレンド: 今週のビッグデータに基づくビットコインの価格動向 (2017-03-06)

多くの勢力がキャンプを攻撃し、拠点を占領しています。ショートサイドのテストに注目1. 市場動向<...

考えること | EIP-1559 とイーサリアムへの道

(革靴、ゴッホ)来年のイーサリアムにとって、その歴史において非常に重要なキーポイントがいくつかありま...

イーサリアムのステーブルコイン発行額が過去最高の840億ドルを記録

最新データによると、イーサリアム上のステーブルコインの総発行額は840億米ドルに達し、過去最高を記録...

時代の最前線に立ち、富のチャンスを掴む——Lieyun Finance BEYOND2021ブロックチェーン技術と応用サミットが開幕

「輪を破り、成長する」ブロックチェーン技術とアプリケーションサミットが誕生しました。 4月15日から...

李嘉誠、ビットコイン決済スタートアップBitPayに投資

明らかに、投資家はマイニング以外に、不安定なビットコイン取引から利益を得る別の方法を見つけようとして...

5月第3週の最新マイニングマシン市場データ<2021.5.15-2021.5.21>

2021.5.15~2021.5.21の5月第3週の人気マイニングマシンの最新市場参考価格は図の通...