Bitcoin на практике: regtest, RPC и rust-bitcoin
Три урока мы разбирали Bitcoin по чертежам. Пора собрать всё руками: поднять собственный узел, где ты сам майнишь блоки одной командой, поговорить с ним по RPC и построить транзакцию на Rust. Это тот же путь, что мы прошли со своим самодельным узлом, только теперь узел настоящий.
Идея
Гонять эксперименты в основной сети Bitcoin невозможно: настоящие деньги, блок раз в десять минут. Поэтому у Bitcoin Core есть тренировочные режимы, и главный из них это regtest: приватная сеть из одного узла, где блоки создаются мгновенно по твоей команде.
Ступени тренировки:
- regtest: твоя личная сеть, ты майнишь сам, монеты игрушечные. Для разработки и тестов.
- testnet и signet: публичные тестовые сети с бесплатными монетами. signet новее и стабильнее, блоки в нём подписывает известный набор ключей, поэтому нет войн за хешрейт.
- mainnet: настоящая сеть с настоящими деньгами.
Код везде один, меняется только сеть в конфиге. Отладил в regtest, запустил в mainnet.
bitcoin-cli: узел одной командой
Узел это демон bitcoind, а bitcoin-cli шлёт ему команды. Шаг 0, поставить Bitcoin Core:
brew install bitcoin # macOS
sudo apt install bitcoind # Debian/Ubuntu
# или вообще без установки, в докере:
docker run -d --name bitcoind bitcoin/bitcoin bitcoind -regtest
Дальше один раз настроим доступ по RPC. Логин и пароль запишем в конфиг: их подхватят и bitcoind, и bitcoin-cli, и наш будущий Rust-клиент, так все трое говорят на одном языке. Поднять regtest и намайнить себе монеты это несколько строк:
# один раз: RPC-логин и пароль в конфиг
mkdir -p ~/.bitcoin
printf 'rpcuser=user\nrpcpassword=pass\n' >> ~/.bitcoin/bitcoin.conf
# запускаем узел в режиме regtest
# -rest пригодится ниже, а -fallbackfee нужен, потому что на свежем
# regtest нет истории комиссий и узлу не из чего оценить fee
bitcoind -regtest -daemon -rest -fallbackfee=0.0001
# создаём кошелёк и адрес
bitcoin-cli -regtest createwallet "test"
addr=$(bitcoin-cli -regtest getnewaddress)
# майним 101 блок на свой адрес (мгновенно, без перебора PoW)
# 101 а не 1, потому что награда за блок созревает только через 100 блоков
bitcoin-cli -regtest generatetoaddress 101 "$addr"
# смотрим баланс: 50 BTC с первого блока стали тратимыми
bitcoin-cli -regtest getbalance
Обрати внимание на 101 блок: субсидия coinbase из урока 20 становится тратимой только через 100 блоков сверху. Это защита от трат монет с блока, который может устареть при реорге (урок 22).
Отправь и разбери транзакцию
Теперь сквозной сценарий: отправить монеты и разобрать получившуюся транзакцию по полям из урока 20.
# новый адрес-получатель и платёж на него
# (без -fallbackfee при запуске узла sendtoaddress тут бы упал:
# на пустом regtest узлу не из чего оценить комиссию)
dest=$(bitcoin-cli -regtest getnewaddress)
txid=$(bitcoin-cli -regtest sendtoaddress "$dest" 0.3)
# подтверждаем платёж: майним один блок поверх
bitcoin-cli -regtest generatetoaddress 1 "$addr" > /dev/null
# полный разбор транзакции (true значит развёрнутый JSON, а не сырые байты)
bitcoin-cli -regtest getrawtransaction "$txid" true
Выдача, сокращённая до главного:
{
"txid": "9f3c1a...",
"size": 222,
"vsize": 141,
"weight": 561,
"vin": [
{
"txid": "4b1e77...",
"vout": 0,
"scriptSig": { "asm": "", "hex": "" },
"txinwitness": ["30440220...01", "02a1b2..."]
}
],
"vout": [
{
"value": 0.30000000,
"n": 0,
"scriptPubKey": {
"asm": "0 e8df018c7e326cc253faac7e46cdc51e68542c42",
"hex": "0014e8df018c7e326cc253faac7e46cdc51e68542c42",
"address": "bcrt1qar0srrr...",
"type": "witness_v0_keyhash"
}
},
{
"value": 49.69998590,
"n": 1,
"scriptPubKey": { "address": "bcrt1q9h5k...", "type": "witness_v0_keyhash" }
}
]
}
Вся теория блока про Bitcoin тут как на ладони:
- Вход один. Кошелёк потратил целиком coinbase-выход на 50 BTC (помнишь, UTXO тратится только целиком).
scriptSigпустой, а подпись и ключ уехали вtxinwitness: это SegWit из урока 21 в живом виде. - Выхода два. 0.3 BTC получателю и почти 49.7 BTC сдачи себе.
type: witness_v0_keyhashэто P2WPKH, замок «докажи владение ключом с этим хешем». - Поля fee нет. Комиссия это входы минус выходы: 50 минус 0.3 минус 49.69998590 даёт 0.0000141 BTC. Ровно та арифметика из урока 20.
vsizeменьшеsize. Witness тарифицируется по льготному весу, отсюда и разница: комиссию считают по виртуальному размеру.
RPC: узел это сервер
За bitcoin-cli стоит JSON-RPC: узел это сервер, а CLI лишь тонкий клиент. Тот же интерфейс доступен любой программе. Есть и REST-интерфейс для чтения (блоки, транзакции по HTTP GET), но пишущие операции идут через JSON-RPC с аутентификацией.
REST мы уже включили флагом -rest при запуске узла, так что его можно пощупать обычным curl, безо всякой аутентификации:
curl -s http://127.0.0.1:18443/rest/chaininfo.json
# {"chain":"regtest","blocks":102,"headers":102,"bestblockhash":"3f9a...",...}
Из Rust в узел ходят через крейт bitcoincore-rpc:
use bitcoincore_rpc::{Auth, Client, RpcApi};
fn main() -> anyhow::Result<()> {
// подключаемся к локальному regtest-узлу;
// логин и пароль те же, что мы записали в bitcoin.conf
let rpc = Client::new(
"http://127.0.0.1:18443",
Auth::UserPass("user".into(), "pass".into()),
)?;
let info = rpc.get_blockchain_info()?;
println!("сеть: {}, высота: {}", info.chain, info.blocks);
// майним блоки прямо из кода, как наш самодельный узел майнил сам
let address = rpc.get_new_address(None, None)?.assume_checked();
let hashes = rpc.generate_to_address(10, &address)?;
println!("намайнено блоков: {}", hashes.len());
Ok(())
}
Это ровно то, что делал наш узел-CLI из урока 19, только теперь на другом конце настоящий Bitcoin Core.
премиум-урок
Продолжение в премиуме: 1 200 ₽ в месяц или 10 000 ₽ в год. Вместе с ним серверные проверки задач и школьные токены AI-коуча.
Оплата пока не подключена: премиум и курсы открываются промокодом.