btcdで独自ネットワークを構築する
概要
BitcoinのGo言語実装としてbtcdがある。Bitcoinの様々な研究開発を行う際に、実際のmainnetとは異なり価値の持たない通貨を扱う別のネットワークであるtestnetがよく用いられる。しかし、testnet上では、さまざまな試験が世界中で行われており、TXを投入しても承認に時間がかかるなど、様々な試験をするには適さない面がある。本項では、btcdを拡張し、testnet相当の別のネットワークを構築する手順を記す。
環境
- btcd v0.20.1-beta (commit id: f3ec13030e4e828869954472cbc51ac36bee5c1d)
- 以下のコマンドで
$GOPATH
へダウンロードし、$GOPATH/src/github.com/btcsuite/btcd
以下のソースを改修していくgo get -d github.com/btcsuite/btcd
- 以下のコマンドで
- btcwallet (commit id:704cd189ac2386b54ab64e17f67f1c7c5ef5c7ac)
- btcd上で送金などの操作を行うために利用
- btcd同様に以下のコマンドでダウンロードし、改修を行う
go get -d github.com/btcsuite/btcwallet
Btcdの改修
Btcdで書き換えるべきファイル
chaincfg/genesis.go
: Genesis Blockの設定パラメータchaincfg/params.go
: ネットワーク全体に関わるパラメータconfig.go
: 上記chaincfg/params.go
を読み込むプログラムparams.go
:config.go
からchaincfg/params.go
を読み込むためのインターフェースwire/protocol.go
: ノード通信時のメッセージプレフィックスなどの設定
パラメータ読み込み部の改修
btcdでは、btcd.go
内でloadConfig()
を呼ぶことで、ノードの設定を読み込んでいる。loadConfig()
の実態はconfig.go
の中にある。まずは、type config struct
の中のコマンドラインパラメータの中に、mynet
のオプションを追加する。
type config struct { ... TestNet3 bool `long:"testnet" description:"Use the test network"` + MyNet bool `long:"mynet" description:"Use the my network"` RegressionTest bool `long:"regtest" description:"Use the regression test network"` SimNet bool `long:"simnet" description:"Use the simulation test network"` ...
次にloadConfig()
内のネットワークパラメータ読み込み部に追記する。
func loadConfig() (*config, []string, error) { ... if cfg.TestNet3 { numNets++ activeNetParams = &testNet3Params } if cfg.RegressionTest { numNets++ activeNetParams = ®ressionNetParams } + if cfg.MyNet { + numNets++ + activeNetParams = &myNetParams + } if cfg.SimNet { numNets++ // Also disable dns seeding on the simulation test network. activeNetParams = &simNetParams cfg.DisableDNSSeed = true } ...
ここで呼ばれる*NetParams
は、params.go
内で定義されるため、追記する。ここでRPCのためのポートを任意に設定する。
... // MyNetParams var MyNetParams = params{ Params: &chaincfg.MyNetParams, rpcPort: "11454", } ...
通信時のprefix設定
ノードは通信時に、ネットワークを識別するためのprefixをつける。それらは、wire/protocol.go
に定義されている。このprefixはどのように定義されるべきかは筆者は把握していない。ここでは任意に設定した。
// BitcoinNet represents which bitcoin network a message belongs to. type BitcoinNet uint32 // Constants used to indicate the message bitcoin network. They can also be // used to seek to the next message when a stream's state is unknown, but // this package does not provide that functionality since it's generally a // better idea to simply disconnect clients that are misbehaving over TCP. const ( // MainNet represents the main bitcoin network. MainNet BitcoinNet = 0xd9b4bef9 // TestNet represents the regression test network. TestNet BitcoinNet = 0xdab5bffa // TestNet3 represents the test network (version 3). TestNet3 BitcoinNet = 0x0709110b // MyNet represents the my network. MyNet BitcoinNet = 0x79616a75 // SimNet represents the simulation test network. SimNet BitcoinNet = 0x12141c16 )
設定パラメータの作成
上記で読み込むよう設定したchaincfg.MyNetParams
はchaincfg/params.go
の中で定義される。今回はtestnetとregtestの設定をベースに以下の設定を追記した。ここではここで記される内容の詳細には立ち入らないが、ここのパラメータを色々いじることで様々なネットワークを作ることができる。
... // MyNet var MyNetParams = Params{ Name: "mynet", Net: wire.MyNet, DefaultPort: "11451", DNSSeeds: nil, // Chain parameters GenesisBlock: &MyNetGenesisBlock, GenesisHash: &MyNetGenesisHash, PowLimit: regressionPowLimit, PowLimitBits: 0x207fffff, // same as regtest BIP0034Height: 0, // always BIP0065Height: 0, // always BIP0066Height: 0, // always CoinbaseMaturity: 100, SubsidyReductionInterval: 210000, TargetTimespan: time.Hour * 24 * 14, // 14 days TargetTimePerBlock: time.Minute * 10, // 10 minutes RetargetAdjustmentFactor: 4, // 25% less, 400% more ReduceMinDifficulty: true, MinDiffReductionTime: time.Minute * 20, // TargetTimePerBlock * 2 GenerateSupported: true, // Checkpoints ordered from oldest to newest. Checkpoints: nil, // Consensus rule change deployments. // // The miner confirmation window is defined as: // target proof of work timespan / target proof of work spacing RuleChangeActivationThreshold: 1512, // 75% of MinerConfirmationWindow MinerConfirmationWindow: 2016, Deployments: [DefinedDeployments]ConsensusDeployment{ DeploymentTestDummy: { BitNumber: 28, StartTime: 0, // always ExpireTime: 0, // always }, DeploymentCSV: { BitNumber: 0, StartTime: 0, // always ExpireTime: 0, // always }, DeploymentSegwit: { BitNumber: 1, StartTime: 0, // always ExpireTime: 0, // always }, }, // Mempool parameters RelayNonStdTxs: true, // Human-readable part for Bech32 encoded segwit addresses, as defined in // BIP 173. Bech32HRPSegwit: "tb", // always tb for test net // Address encoding magics PubKeyHashAddrID: 0x00, // starts with 1 ScriptHashAddrID: 0xc4, // starts with 2 WitnessPubKeyHashAddrID: 0x03, // starts with QW WitnessScriptHashAddrID: 0x28, // starts with T7n PrivateKeyID: 0xef, // starts with 9 (uncompressed) or c (compressed) // BIP32 hierarchical deterministic extended key magics HDPrivateKeyID: [4]byte{0x04, 0x35, 0x83, 0x94}, // starts with tprv HDPublicKeyID: [4]byte{0x04, 0x35, 0x87, 0xcf}, // starts with tpub // BIP44 coin type used in the hierarchical deterministic path for // address generation. HDCoinType: 1, } ...
Genesis Blockのパラメータ設定
chaincfg.MyNetParams
の中からGenesis Blockのデータと、そのハッシュをchaincfg/genesis.go
から読み込んでいる。そこでGenesisのハッシュ値などを設定する必要がある。そのため、Genesis Blockの生成は既存のスクリプトを用いて行った。しかしこのスクリプトはPython2.7でしか動作しない。
https://github.com/lhartikk/GenesisH0
以下のスクリプトで生成する。今回は、簡易的に実装するためにregtestと同様の値(ごく簡単なdifficultyでの生成)を行っている。
$ python genesis.py -n 00000000 -b 545259519 04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73 algorithm: SHA256 merkle hash: 4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b pszTimestamp: The Times 03/Jan/2009 Chancellor on brink of second bailout for banks pubkey: 04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f time: 1581663396 bits: 0x207fffff Searching for genesis hash.. genesis hash found! nonce: 1 genesis hash: 718790f769dc86320b52d4c176a94e775947e922dd019e99b91cc2f9ddbc48b8
上記スクリプトで生成した値をそれぞれのフィールドへ適用させる。ここでmyNetGenesisHash
の値はリトルエンディアンになっていることに注意する。
... var myNetGenesisHash = chainhash.Hash([chainhash.HashSize]byte{ // Make go vet happy. 0xb8, 0x48, 0xbc, 0xdd, 0xf9, 0xc2, 0x1c, 0xb9, 0x99, 0x9e, 0x01, 0xdd, 0x22, 0xe9, 0x47, 0x59, 0x77, 0x4e, 0xa9, 0x76, 0xc1, 0xd4, 0x52, 0x0b, 0x32, 0x86, 0xdc, 0x69, 0xf7, 0x90, 0x87, 0x71, }) // myNetMerkleRoot is the hash of the first transaction in the genesis // block for the my network. It is the same as the merkle root // for the main network. var myNetGenesisMerkleRoot = genesisMerkleRoot // myNetGenesisBlock defines the genesis block of the block chain which // serves as the public transaction ledger for the my network. var myNetGenesisBlock = wire.MsgBlock{ Header: wire.BlockHeader{ Version: 1, PrevBlock: chainhash.Hash{}, // 0000000000000000000000000000000000000000000000000000000000000000 MerkleRoot: myNetGenesisMerkleRoot, // 4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b Timestamp: time.Unix(1581663396, 0), Bits: 0x207fffff, // 545259519 [7fffff0000000000000000000000000000000000000000000000000000000000] Nonce: 1, }, Transactions: []*wire.MsgTx{&genesisCoinbaseTx}, } ...
ビルドと起動確認
以下のコマンドでビルドする。$GOPATH/bin
にバイナリが吐き出される。
GO111MODULE=on go install -v . ./cmd/...
以下のコマンドでbtcdが起動する。設定したGenesisで正常に2回起動されれば成功。というのも、初回の起動の時にgenesisで設定したものがブロックとしてストレージに書き込まれ、2回目の起動の時にのみGenesisHashとの照合が行われるため。もし2回目が起動しなければ、Genesis設定周りのパラメータを再確認する。
$GOPATH/bin/btcd --mynet
btcctlを用いたインタラクション
btcdは同梱されているbtctlを用いてインタラクションを行う。btcctl自体にmynet
オプションを追加しても良いが、ここではconfigファイルを用いた設定方法を記す。
まずは、btcdとbtcctlの設定ファイルを以下のように記述し、それぞれbtcd.conf
とbtcctl.conf
として保存する。
[Application Options] rpcuser=myuser rpcpass=hogehoge
btcdを以下のコマンドで起動する。
$GOPATH/bin/btcd --mynet -C $PATH_TO_btcd.conf
別のコンソールで、以下のコマンドでインタラクションできることを確認する。ここではparams.go
で設定したRPC Portを指定するのを忘れてはならない。
$GOPATH/bin/btcctl -C $PATH_TO_btcctl.conf -s localhost:11454 getblockchaininfo
マイニングの実施と確認
今回設定しているchaincfg/params.go
の中で、btcdで作ったネットワーク上でregtestと同じようにgenreate(ブロックの生成)が簡単にできるようになっている。そこでブロックの生成を実施し、送金を試してみる。
btcwalletと改修後のビルド注意点
btcdからは送金などにまつわるいわゆるwalletの機能は除外されている。walletの機能はbtcwalletという実装に含まれている。このbtcwalletは、RPCでbtcdと通信し、walletの機能を提供する。したがって、go実装のものを使ってbitcoinの操作を行うためには、btcctlをインターフェースとして、nodeへの操作時はbtcdへ、walletの操作時はbtcwalletへRPCでコマンドを送信し操作する事になる。
ここでは、btcwalletを以下のコマンドでダウンロードし、mynet
オプションを利用できるように改修していく。
go get -d github.com/btcsuite/btcwallet
改修したbtcwalletをビルドする際は以下のコマンド。この時、公式のドキュメントではGO111MODULE=on
の環境変数を設定するが、btcdの中のchaincfg
をモジュールとして読み込んでいる。そのため、改修したローカルのbtcdを参照するようにGO111MODULE=off
としなければならないことに注意する。
GO111MODULE=off go install -v . ./cmd/...
btcwalletの改修
基本的には、コマンドラインのmynet
オプションを追加し、それが指定された時は、指定したchaincfg
のパラメータを読みにいく、という基本的な流れは変わらない。以下にdiffを記す。
+++ b/config.go @@ -53,6 +53,7 @@ type config struct { CreateTemp bool `long:"createtemp" description:"Create a temporary simulation wallet (pass=password) in the data directory indicated; must call with --datadir"` AppDataDir *cfgutil.ExplicitString `short:"A" long:"appdata" description:"Application data directory for wallet config, databases and logs"` TestNet3 bool `long:"testnet" description:"Use the test Bitcoin network (version 3) (default mainnet)"` + MyNet bool `long:"mynet" description:"Use the my Bitcoin network (default mainnet)"` SimNet bool `long:"simnet" description:"Use the simulation test network (default mainnet)"` NoInitialLoad bool `long:"noinitialload" description:"Defer wallet creation/opening on startup and enable loading wallets over RPC"` DebugLevel string `short:"d" long:"debuglevel" description:"Logging level {trace, debug, info, warn, error, critical}"` @@ -365,6 +366,10 @@ func loadConfig() (*config, []string, error) { activeNet = &netparams.SimNetParams numNets++ } + if cfg.MyNet { + activeNet = &netparams.MyNetParams + numNets++ + } if numNets > 1 { str := "%s: The testnet and simnet params can't be used " + "together -- choose one" diff --git a/netparams/params.go b/netparams/params.go index c15047e..aeb5a57 100644 --- a/netparams/params.go +++ b/netparams/params.go @@ -37,3 +37,10 @@ var SimNetParams = Params{ RPCClientPort: "18556", RPCServerPort: "18554", } + +// Mynet +var MyNetParams = Params{ + Params: &chaincfg.MyNetParams, + RPCClientPort: "11454", + RPCServerPort: "11455", +}
BtcdとBtcwalletのセットアップ
まずはbtcwalletでwalletを作成する。
$GOPATH/bin/btcwallet --mynet --create
次に、btcdを起動するが、デフォルトではwallet<->btcdとwallet<->btcctlの通信はTLSを用いる。今回は、簡易的に試験するために、それらを外すオプションをつける。
$GOPATH/bin/btcd --mynet -C $PATH_TO_btcd.conf --notls
次に、btcwalletを起動し、btcdへ接続する。このとき,-u
オプション(ユーザ名)と-P
オプション(パスワード)は、btcd.confで設定したものを入力する。btcd同様にTLSを無効化するオプションを入れている。
$GOPATH/bin/btcwallet -u myuser -P hogehoge --mynet --noclienttls --noservertls
btcctlから、btcd、btcwalletにそれぞれgetblockchaininfo
コマンドを送信し、それぞれ同じ出力が得られれば改修は成功。
# to btcd $GOPATH/bin/btcctl -C $PATH_TO_btcctl.conf -s localhost:11454 --notls getblockchaininfo # to btcwallet $GOPATH/bin/btcctl -C $PATH_TO_btcctl.conf -s localhost:11455 --notls getblockchaininfo
coinbaseアカウントの設定
btcwalletではcreate時にdefault
アカウントが作成されている。以下のコマンドでアカウントのアドレスが確認できる。
$GOPATH/bin/btcctl -C $PATH_TO_btcctl.conf -s localhost:11455 getaccountaddress default
出力されたアドレスを、btcd.conf
へcoinbaseアカウントとして設定し、再起動する。
[Application Options] rpcuser=myuser rpcpass=hogehoge + miningaddr=$DUMPED_DEFAULT_ACCOUNT
generateコマンドの実行と残高確認
bitcoinでは、マイニング報酬は100ブロック以降でないと送金できない。そのため、101ブロックマイニングをgenerate
コマンドで実行する。
$GOPATH/bin/btcctl -C $PATH_TO_btcctl.conf -s localhost:11454 generate 101
btcwallet側のログに、同期したメッセージが表示されれば以下のコマンドで残高が増えているのが確認できる。同期が起こらなければ、btcwalletを再起動すると起動時にbtcdとブロックの同期が行われる。
$GOPATH/bin/btcctl -C $PATH_TO_btcctl.conf -s localhost:11455 getbalance default
dockerで複数ノードを起動
適当に書いた以下のDockerfileで本実装を複数ノードで動かしてみる。
FROM golang:1.13.4-buster LABEL maintainer="Rysouke Abe <chike@sfc.wide.ad.jp>" RUN apt update RUN apt install -y vim RUN go get -d github.com/btcsuite/btcd RUN rm -r /go/src/github.com/btcsuite/btcd RUN git clone https://github.com/chike0905/btcd.git /go/src/github.com/btcsuite/btcd WORKDIR /go/src/github.com/btcsuite/btcd RUN git checkout mynet RUN go install -v . ./cmd/... WORKDIR /go RUN go get -d github.com/btcsuite/btcwallet RUN rm -r /go/src/github.com/btcsuite/btcwallet RUN git clone https://github.com/chike0905/btcwallet.git /go/src/github.com/btcsuite/btcwallet WORKDIR /go/src/github.com/btcsuite/btcwallet RUN git checkout mynet RUN GO111MODULE=off go install -v . ./cmd/... WORKDIR /root/.btcd ADD elements/btcd.conf /root/.btcd/btcd.conf WORKDIR /root/.btcwallet ADD elements/btcctl.conf /root/.btcwallet/btcctl.conf WORKDIR /root CMD ["bash"]
btcdmynet:latest
としてビルドする。
docker build -t btcdmynet:latest .
HVのコンテナ同士でのみ通信できるブリッジネットワークを作成し、dockerコンテナをそれぞれそれらに接続する。
docker network create --driver=bridge --internal internalnet
dockerコンテナをそれぞれ起動。
docker run -it --rm --net internalnet --name btcd1 btcdmynet:latest
docker run -it --rm --net internalnet --name btcd2 btcdmynet:latest
上記手順でそれぞれのコンテナ上でbtcdを起動させてから、btc1
からbtcctlのaddnode
コマンドでpeerを追加する
btcctl -C .btcwallet/btcctl.conf -s localhost:11454 --notls addnode $ADDR_BTC2 add
btc1
上で上記手順を用いてgenerate
コマンドを走らせると、btc2
上に同期されていることがわかる。
まとめ
本稿では、testnet相当の別のネットワークを柔軟に設定するために、btcd/btcwalletを改修して独自のネットワークを構築する手順を記した。本項では、chaincfg/params.go
の中のパラメータの詳細には立ち入らなかったが、それらをいじることで様々なパラメータを持つネットワークを構築することが可能である。PoW周りのパラメータなど、様々な値を操作することが可能であり、様々な実験が実施可能であることが期待できる。
筆者の感想にはなるが、btcdはbitcoindに比べて後発なのもあり、そのアーキテクチャなどが洗練されつつあるイメージがある。本稿で述べたような手法を用いながら、様々な検証を行い、さらなる洗練が期待される。