(Hepatizonの文脈投稿)

4 件のメッセージ BitcoinTalk Hepatizon, ギャビン・アンドレセン, サトシ・ナカモト 2010年7月24日 — 2010年7月24日
Hepatizon 2010年7月24日 原文 · 個別ページ

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

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

main.hには以下がある:

Code: (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;

}

そして

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

コードの重要な部分は: Code:fileout << FLATDATA(pchMessageStart) << nSize; … fileout << *this;pchMessageStartは4バイトのマジックバイトで、FLATDATAで書き込まれる。

CBlock自体は<< *thisで書き込まれ、main.hのIMPLEMENT_SERIALIZEで行われる: Code: 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日 原文 · 個別ページ

説明ありがとう。

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

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