はじめに
今回は実際にブロック到達の通知を貰ってからブロック詳細情報を取ってくるところを書きたいと思います。
処理の大まかな流れ(おさらい)
前編でも書きましたが、処理の流れはこの様になっています。
- 繋ぎに行くサーバの情報を取得する
- サーバと接続して最初のご挨拶
- 相手が生きているかどうか、定期的にチェック
- ブロックが到達したという通知を待つ
- 「到達した」という情報が来たら、さらに詳しいブロック情報をリクエストしゲット!
前回はStep3までカバーしたので、今回はStep4と5の解説になります。連続した流れなので一つの章で説明しちゃいます。
ブロック情報の取得
新しいブロックやトランザクションの情報がフルノードサーバに届いた場合、その情報は繋がっているピア(接続しているホスト)に通知されます。それが Inv
メッセージになります。Inv
メッセージはボディにInventory
構造体の配列を持ちます。そしてInventory
構造体は以下のように定義されています。
pub struct Inventory {
pub inv_type: InvType,
pub hash: sha256d::Hash
}
pub enum InvType {
Error,
Transaction,
Block,
WitnessBlock,
WitnessTransaction
}
これを受け取って処理する部分はこのようになります。
pub fn main() {
....
loop {
let message: Result<RawNetworkMessage, _> = reader.read_next();
let payload = message.unwrap().payload;
match payload {
...
NetworkMessage::Inv(dat) => {
for inv in dat {
if inv.inv_type == InvType::Block {
self.on_inv_block(inv);
}
}
}
}
}
}
Inventory
構造体には、何に関する情報なのか(inv_type
)と、ハッシュ値( hash
)のみが含まれます。配列で渡されるそのデータを一つずつ確認して、タイプが Block
だったらon_inv_block()
というメソッドを呼びます。
fn on_inv_block(&mut self, inv: Inventory) {
println!("{:?}", inv);
self.send_getdata(inv);
}
fn send_getdata(&mut self, inv: Inventory) {
Self::send_message(&mut self.stream, NetworkMessage::GetData([inv].to_vec()));
}
on_inv_block()
の中でsend_getdata()
を呼んでいて、そこで受け取ったInventory
型のデータをそのまま渡します。そして、ピアに対してGetData
メッセージを送り、ブロックの詳細情報をリクエストします。
GetData
に対する返答がBlcok
メッセージで返されます。ややこしいのですが、Block
メッセージのボディがBlock
という名前の構造体で、中身は以下のようになっています。
pub struct BlockHeader {
pub version: u32,
pub prev_blockhash: sha256d::Hash,
pub merkle_root: sha256d::Hash,
pub time: u32,
pub bits: u32,
pub nonce: u32,
}
pub struct Block {
pub header: BlockHeader,
pub txdata: Vec<Transaction>
}
header
にはそのブロックのメタデータ(そのブロックと親ブロックのハッシュ、生成日時、Nonceなど)が含まれていて、そのブロックに含まれるトランザクションの配列がtxdata
として続きます。
これを受け取るコードが以下になります。
fn store_block(&self, block: block::Block) {
println!("Block: block_hash: {}, prev_hash: {}, timestamp: {:?}",
block.bitcoin_hash(),
block.header.prev_blockhash,
Utc.timestamp(block.header.time.try_into().unwrap(), 0),
);
}
pub fn main() {
....
loop {
let message: Result<RawNetworkMessage, _> = reader.read_next();
let payload = message.unwrap().payload;
match payload {
...
NetworkMessage::Block(block)=> {
self.store_block(block);
}
}
}
}
本来であれば、受け取ったブロックをDBに格納して解析などに利用するところですが、Bitcoinのプロトコルを理解する上では本質的ではない部分なので割愛し、ここでは受け取った情報をそのまま標準出力に書き出しています。
まとめ
分量的にはだいぶコンパクトになりましたが、Bitcoinのプロトコルを用いてブロックの情報を入手する方法を解説してみました。Inv
というメッセージにより新たに生成されたブロックの情報が伝播され、それを元に詳細な情報を入手する方法がわかったかと思います。
なお、今回利用した Bitcoinというクレートですが、一つ残念な部分があります。前編でも書いたようにBitcoinのプロトコルを話す最初にVersion
メッセージを送るのですが、このクレートはバージョンが70001
に固定になっています。Bitcoindのバージョンでいうと、2013年のv0.8.0相当。それ以降に追加されたメッセージタイプなどは残念ながらサポートされていません。ということで、このクレートをそのまま商用に使うのはちょっと厳しそうです。
一方で、オープンソースなので自分で変更を加えることは可能です。実際、Githubリポジトリのプルリクエストを見ると、例えばこれとかはBIP152のCompact Blockの対応を入れようとしてやり取りが続いています。そんな活動に足を踏み入れてOSSに貢献しながら、商用利用の道を探るというのも有りかも知れません。