ブロックの読み書きと FLATDATA

人物: Hepatizon

Bitcoin がブロックデータを保存する方法を理解しようとしている——とりわけブロックチェーン/トランザクション履歴の統計を実行し、Bitcoin が実際にどの程度匿名なのかを確認したい。そこで Bitcoin がブロックデータをファイルに読み書きする方法をソースで確認した。

(補足:これは 0.3.2 のものだ)

main.h には以下がある:

 (CBlock::WriteToDisk)bool WriteToDisk(bool fWriteTransactions, unsigned int& nFileRet, unsigned int& nBlockPosRet)
{
    // Open history file to append
    CAutoFile fileout = AppendBlockFile(nFileRet);
    if (!fileout)
        return error("CBlock::WriteToDisk() : AppendBlockFile failed");
    if (!fWriteTransactions)
        fileout.nType |= SER_BLOCKHEADERONLY;

    // Write index header
    unsigned int nSize = fileout.GetSerializeSize(*this);
    fileout << FLATDATA(pchMessageStart) << nSize;

    // Write block
    nBlockPosRet = ftell(fileout);
    if (nBlockPosRet == -1)
        return error("CBlock::WriteToDisk() : ftell failed");
    fileout << *this;

    // Flush stdio buffers and commit to disk before returning
    fflush(fileout);
#ifdef __WXMSW__
    _commit(_fileno(fileout));
#else
    fsync(fileno(fileout));
#endif

    return true;
}

そして

 (CBlock::ReadFromDisk)bool ReadFromDisk(unsigned int nFile, unsigned int nBlockPos, bool fReadTransactions=true)
{
    SetNull();

    // Open history file to read
    CAutoFile filein = OpenBlockFile(nFile, nBlockPos, "rb");
    if (!filein)
        return error("CBlock::ReadFromDisk() : OpenBlockFile failed");
    if (!fReadTransactions)
        filein.nType |= SER_BLOCKHEADERONLY;

    // Read block
    filein >> *this;

    // Check the header
    if (CBigNum().SetCompact(nBits) > bnProofOfWorkLimit)
        return error("CBlock::ReadFromDisk() : nBits errors in block header");
    if (GetHash() > CBigNum().SetCompact(nBits).getuint256())
        return error("CBlock::ReadFromDisk() : GetHash() errors in block header");

    return true;
}

FLATDATA は serialize.h で以下のように定義されている:

 (FLATDATA)#define FLATDATA(obj)   REF(CFlatData((char*)&(obj), (char*)&(obj) + sizeof(obj)))

さて——読み間違いなら訂正してほしいが——私の理解では、FLATDATA の呼び出しは CBlock オブジェクトの生バイトを文字の配列として解釈する。CBlock::WriteToDisk メソッドは定数 4 バイトのメッセージヘッダー(0xf9, 0xbe, 0xb4, 0xd9)、CBlock オブジェクトのバイトサイズ、そして CBlock の FLATDATA——つまり CBlock オブジェクトの生バイトそのもの——をディスクに書き込む。したがってヘッダーの後、ファイルに書き込まれるデータはメモリー上の CBlock オブジェクトとバイト単位で同一となる。また、正しく読んでいれば、CBlock::ReadFromFile はそれらのバイトをメモリー上の CBlock オブジェクト用に確保された領域に直接コピーしてブロックを再生成する。これは正しいだろうか?

関連する質問——C++クラスのインスタンスが内部的にどう表現されるかは標準では保証されていないという印象がある。異なるコンパイラーや異なる最適化フラグでプログラムをコンパイルすると、メンバー変数がメモリーに格納される順序が変わる可能性があり、一部のデバッグモードコンパイラーはメモリー検査を容易にするためにメンバー変数間に数バイトを追加することさえある。これについて確信は持てない、ただ聞きかじっただけで真剣に疑問を持ったことはない。