btc: BDK で Rust を扱ってみよう
2025/03/04
はじめに
Output Descriptors をちょっと触ったので、次は miniscript とかどうかななどと考えていた。
が、そこで私は思い出した。
そういえば Rust の勉強をしている途中だったな、と。
Rust のチュートリアルに飽きたので BDK のサンプルを動かしてみていたのだ。
それが libwally-core で descriptor wallet っぽいのを触るために BDK のサンプルと同じコードを作ってみようとしてしまい、
果ては Esplora の API を呼び出すところまでやってしまったというわけだ。
無駄になったとは思わないが、脱線しすぎだ。
Quick Start Example
BDK の Quick Start Example はこちらだ。
リストでデータを取得できる関数があったので追加してみた。
拡張公開鍵なのであまりやれることがない。
秘密鍵を作るところからやるように改造するか。
Full Wallet Example
と思ったら、次の課題がそういうことをやるようなサンプルだった。
このページにはコードが部分的にしか載っていない。 全部のコードは GitHub に置いてある。
main.rs の中身をコピーして、bdk_wallet と bdk_esplora を追加してビルドしたのだがエラーになった。
features ?
use bdk_wallet::rusqlite::Connection;
でエラーになった。
error[E0432]: unresolved import
bdk_wallet::rusqlite
bdk_wallet のバージョンが、GitHub では “1.0.0” だが私は “1.1.0” を使っていたのでその影響か?と思ったのだが、
Cargo.toml を見るとバージョンだけでなく features = ["keys-bip39", "rusqlite"]
とも書かれていた。
そういうものがあるとして、bdk_wallet のページを見てもそういうのが指定できるかどうかが載っていない。
Cargo.tml か。
- default
- std
- compiler
- all-keys
- keys-bip39
- rusqlite
- file_store
- test-utils
何も指定しなかった場合、--no-default-features
などを指定しないなら default
が採用される。
bdk_wallet の default
は std
で、それには keys-bip39
と resqlite
は入っていない。
なので明示的に書かないとダメ、というのはわかった。
わかったが、それをどうやって知るのだろうか。
もう少し先のチュートリアル には載っているのだが、ドキュメントに載っていないとわからんと思うのだ。
ドキュメントに載せるやり方があるそうだ。
“Optional features” というセクションが追加されるだけでなく、それぞれの横に名前が出てきてマウスカーソルを当てるとホバーする。
“and” や “or” は features の名前ではなくて複数あるときのようだ。若干フォントが違うのでわかるのかな?
bdk_wallet でも lib.rs なんかに書いてあるんだけどね?
よくわからんです。
bdk_esplora の方は “blocking” を書いていないのだがビルドが成功している。
Cargo.toml からすると、defautl の "blocking-https"
が ["blocking", "esplora-client/blocking-https"]
なので自然に含んでいる形だ。
ならば OKだ。
anyhow ?
Result<(), anyhow::Error>
でエラーになった。
error[E0433]: failed to resolve: use of undeclared crate or module
anyhow
Cargo.toml には anyhow = "1"
があるが、確かに私は書いていない。
anyhow はエラー関係のライブラリだそうだ。
"1"
とだけあったので有効にするフラグかと思ってしまった。
どこで使っているかというと fn main() -> Result<(), anyhow::Error>
だ。
main
の戻り値なんて i32
でいいんじゃないの?
と思ったが、main関数の戻りの型にanyhow::Resultを使うというやり方が書いてあるくらいには普通なようだ。
main
関数の取り得るパターンは 3つあるようだ。
まず、何も返さないパターン。
fn main() {}
次は !
を返すパターン・・・?
!
は Never type というもの。
_Noreturn
みたいなものかな?
最後まで到達することはなく、その関数から戻ることはない。
fn main() -> ! {
std::process::exit(0);
}
そして最後のこれが今回のパターンなのか。
Termination はトレートで ExitCode は構造体。
fn main() -> impl std::process::Termination {
std::process::ExitCode::SUCCESS
}
exit も fn main() -> Result<(), MyError>
を例にしているし、学習が進むまではそういうものだと思っておけば良いのかな。
Result<(), anyhow::Error>
の Result
は標準のもので pub enum Result<T, E>
だった。
()
は “unit” と呼ばれるプリミティブ型。
“there is no other meaningful value” とあるので !
とは違う。void
のイメージか?
その場合の終わり方は ;
にすると良いそうだ。
impl<T: Termination, E: Debug>
だから、戻り値という形での値はないけれどもデバッグ情報として E
を返すことができるという意味だろうか。
Linux ではプロセスの戻り値ではあるけれども関数の戻り値ではないということを表しているのだろうか。
まあ、このサンプルコードは return
がなくて最後に Ok(())
があるだけなので、深く考えても確認できないように思う。
追記(2025/03/05)
そう思っていたのだが、この部分を別の関数にして戻り値なしにし Ok(())
を削除するとコンパイルエラーになった。
?
で終わっている行がそうなっていて、どうも Result
か Option
を返すらしい。
演算子表の一番下にある「エラー移譲」だろうか?
ChatGPT氏に、Rust で return
を省略することができる利点を聞いたときに「途中での return
がわかりやすい」というのがあって、
そのときはなるほどと思ったのだが・・・。
extended private key の descriptor をつくろう
さて、コンパイルできるようになったので動かすとすぐ実行エラーになる。
descriptor がフォーマットに合っていないというか、自分で作らないといけないからだ。
コメントアウトされた行を使ってよいのかもしれないが、安心してテストするなら自分で作りたい。
自作するのは面倒だったので、Sparrow Wallet を testnet で起動し、P2TR で新しくウォレットを作った後、Export で “Output Descriptor” を選択してファイルに書き出した。
3つ出力されていて、Receive and Change、Receive、Change だ。
tr(
で始まっているのでそのまま貼り付けると Full Wallet Example で読み取ってくれた。
Munitynet は鍵については testnet と同じなのであまり考えなくて良い。
full-wallet.sqlite3
というファイルが作られた。
コードの中身を見ていなかったが、出力に 5166 sats 足りないようなことが書かれていたので 5,000 sats の送金だろう。
Mutinynet からお金をもらおう
お金じゃないんだけどね。
Googleアカウントでログインするのだけど、GitHub アカウントと連携になる。
Lightning Network などでの受け取りもできるが、今回は Bitcoin アドレスを使う。
アドレスはコマンドを実行したときに出力されているものを使った。
Esplora というか見た目は mempool.space なのだが名前は何でもよかろう。
マイニング周期が短いので、そこまで待たずに確認できるだろう。
100,000 sats
・・・元が signet なので ssats
かもしれないが、とにかくそれだけ送金されていた。
cargo run
すると残高も見えるようになった。
そして assert した!
thread 'main' panicked at src/main.rs:104:5:
assertion failed: finalized
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
104行目 である。
assert!()
は true
であることをチェックするマクロなので wallet.sign()
から false
が返ってきているということになる。
なんだ、署名の失敗って・・・。
あ、tprv
じゃなくて tpub
になってる!
Export したのに。。。
Sparrow Wallet でも Electrum Wallet でも Export では Extended Public Key しかできないようだった(コマンドライン操作でできたのかも?)。
幸い、いくつか先のチュートリアルがニモニックから Extended Private Key などにして出力するようになっていたので、 そこから tprv を取り出すことができた。
その場合はちゃんと署名が成功してトランザクションを展開できた。
おわりに
意外と大変だったが、Rust の勉強にはならなかった気がする。