ブロックの読み書きと FLATDATA

5 件のメッセージ BitcoinTalk Hepatizon, ギャビン・アンドレセン, サトシ・ナカモト, ichi 2010年7月24日 — 2010年7月24日
Hepatizon 2010年7月24日 01:27 UTC 原文 ·

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

コードの重要な部分は:

fileout << FLATDATA(pchMessageStart) << nSize;
...
fileout << *this;pchMessageStartは4バイトのマジックバイトで、FLATDATAで書き込まれる。

CBlock 自体は<< *this で書き込まれ、main.h の IMPLEMENT_SERIALIZE で行われる:

    IMPLEMENT_SERIALIZE
    (
        READWRITE(this->nVersion);
        nVersion = this->nVersion;
        READWRITE(hashPrevBlock);
        READWRITE(hashMerkleRoot);
        READWRITE(nTime);
        READWRITE(nBits);
        READWRITE(nNonce);

        // ConnectBlock depends on vtx being last so it can calculate offset                                             
        if (!(nType & (SER_GETHASH|SER_BLOCKHEADERONLY)))
            READWRITE(vtx);
        else if (fRead)
            const_cast<CBlock*>(this)->vtx.clear();
    )

READWRITE マクロは適切な処理を行い、メンバーをマシン非依存の方法で読み書きする。

トランザクションとブロックをダンプできる簡略化された Python コードは http://github.com/gavinandresen/bitcointools を参照。

Hepatizon 2010年7月24日 02:53 UTC 原文 ·

説明ありがとう。

初歩的なコードの間違いばかりしてしまう……

FLATDATA は固定フィールド長の配列をシリアライズするための回避策だった。配列を直接シリアライズする方法を理解させる、よりクリーンな方法があったが、MSVC6 ではそれができず、当時は MSVC6 との互換性を維持したかったのだ。Boost の中で MSVC6 がサポートしていないものを使用しているため、もう MSVC6 はサポートしていない。0.2.0 の後にサポートを失った。いつか、FLATDATA で包むことなく固定長配列をシリアライズする方法を知っているクリーンな方法に切り替えるかもしれない。

ichi 2010年7月24日 22:59 UTC 原文 ·

ブロックデータをテキストファイルにダンプする件で、丁寧に手ほどきしてくれるとすごく助かる。

私はプログラマじゃないし、gavinandresen の Python は私にはほとんど読み解けない。

データ(SQL)オタクで、Bitcoin のトランザクション履歴を分析したい。スワーム構成などに注目している。特に攻撃の検出方法に興味がある。

みんな、どんな助けでもありがたい。