ビットコイン ソースコード研究ノート (パート 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ドルに迫る

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

推薦する

Web3はどの段階に達しましたか?

インターネットの発展の歴史を振り返ると、Web1段階で伝統的な広告産業のデジタル化が完了し、Web2...

Googleの金融商品とサービスに関する新しいポリシーが発効し、再び暗号通貨の広告が行われている

Googleは、更新された金融商品およびサービスに関するポリシーが8月3日に全面的に発効した後、再び...

プライベートな概念実証、許可型台帳、ブロックチェーン特許

Baozou Timesのコメント:新興のブロックチェーン分野では概念実証は非常に一般的です。新しい...

Antminer T15 レビュー

Bitmain初の7nmチップを搭載したAntminer T15は2018年11月に発売され、その...

ビットコイン投資家の個人情報が漏洩する。ベルギー税務当局は脱税者を追跡している

ベルギーの税務当局は暗号通貨への課税を真剣に受け止め始めている。ブリュッセル・タイムズによると、ベル...

2017 年に注目すべきブロックチェーン ヘルスケア企業トップ 3

クレイジーな解説: 今日、ブロックチェーン技術を基盤とする医療企業がゲームのルールを再定義しています...

X12コイン、総額1800万、CryptoNightアルゴリズム、CPUグラフィックカードのMoneroマイニングをサポート!

ブロックチェーンの開始 – 2017 年半ば (2017 年 5 月 8 日) X12 は安全で、プ...

イーサリアムのセカンドレイヤー拡張ソリューションであるRollupとPlasmaの原理と現状について簡単に説明します。

金融はブロックチェーンが最も簡単に実装できるシナリオです。過去 1 年間、イーサリアム エコシステム...

フォークの余波?ビットコインの分裂は法的混乱を引き起こす可能性がある

クレイジーな解説: ビットコインのフォーク傾向が強まる中、両陣営は、どちらかが極端な行動をとった場合...

ビットコインを取引した大統領は金儲けした

エルサルバドル大統領は金を稼いだ2024年2月29日、エルサルバドルのナジブ・ブケレ大統領は、ビット...

経済混乱がビットコインに大きな打撃を与える中、マクロ経済要因が物語の中心となっている

暗号資産市場は不安定になりつつあり、マクロ要因が再び市場の流れを支配しています。ビットコインは底を打...

シンガポールトークン2049についての私の見解

本日をもって、シンガポールのWeb3ワークの旅も終了しました。実際、実践者としては昨日終わりました。...

データパイ:複数の通貨が90%急落。 DeFiは終わりを迎えようとしているのか、それとも新たな出発点なのか?

1 DEFIは下落を止めたが、買えるか? DeFiは最近急激に下落しており、多くは最高値から90%...