15 KiB
18.5: Acessando o Bitcoind com Rust
ℹ️ NOTA: Esta seção foi adicionada recentemente ao curso e é um rascunho inicial que ainda pode estar aguardando revisão.
Esta seção explica como interagir com o bitcoind
usando a linguagem de programação Rust e o bitcoincore-rpc
crate.
Configurando o Rust
Precisaremos instalar o Rust e o Cargo.
Eles podem ser instalados via curl
. Basta usar a instalação "padrão":
$ curl https://sh.rustup.rs -sSf | sh
Se tudo correr bem, veremos:
Rust is installed now. Great!
Em seguida, precisaremos fazer o logout e o login novamente ou então adicionar o diretório binário do Cargo ao nosso caminho manualmente:
$ source $HOME/.cargo/env
Configurando o bitcoincore-rpc
Para a maioria das linguagens de programação, precisamos instalar uma biblioteca Bitcoin RPC antes de criar nosso primeiro projeto, mas aqui iremos fazê-lo como parte da criação do nosso projeto.
Criando um Projeto bitcoincore-rpc
Podemos criar um novo projeto usando cargo new btc_test
:
$ cargo new btc_test
Created binary (application) `btc_test` package
Isso criará um diretório btc_test
que contém um exemplo de código-fonte "hello world" que está no src/main.rs
e um arquivo Cargo.toml
.
Vamos compilar e executar nosso código com cargo run
:
$ cd btc_test
$ cargo run
Compiling btc_test v0.1.0 (/home/standup/btc_test)
Finished dev [unoptimized + debuginfo] target(s) in 0.14s
Running `target/debug/btc_test`
Hello, world!
ℹ️ NOTA: se encontrarmos o erro
linker‘ cc ’not found
, teremos que instalar um Compilador C. Se estivermos no Linux, podemos instalar com as ferramentas de desenvolvimento.
Para acessar o crate (biblioteca) bitcoincore-rpc
, devemos adicioná-lo ao nosso arquivo Cargo.toml
na seção dependencies
:
[dependencies]
bitcoincore-rpc = "0.11.0"
Quando usarmos o comando cargo run
novamente, ele irá instalar os crates e suas (numerosas) dependências.
$ cargo run
Updating crates.io index
...
Compiling bitcoin v0.23.0
Compiling bitcoincore-rpc-json v0.11.0
Compiling bitcoincore-rpc v0.11.0
Compiling btc_test v0.1.0 (/home/standup/btc_test)
Finished dev [unoptimized + debuginfo] target(s) in 23.56s
Running `target/debug/btc_test`
Hello, world!
Quando estivermos usando o bitcoin-rpc
, normalmente precisaremos incluir o seguinte:
use bitcoincore_rpc::{Auth, Client, RpcApi};
Construindo Nossa Conexão
Para criarmos um RPC client
do Bitcoin, vamos modificar o src/main.rs
:
use bitcoincore_rpc::{Auth, Client, RpcApi};
fn main() {
let rpc = Client::new(
"http://localhost:18332".to_string(),
Auth::UserPass("StandUp".to_string(), "password".to_string()),
)
.unwrap();
}
Como de costume, vamos nos certificar de inserir nosso nome de usuário e senha corretos no ~/.bitcoin/bitcoin.conf
. Aqui, eles serão usados como argumentos para Auth :: UserPass
.
🔗 TESTNET vs MAINNET: E, como de costume, use a porta 8332 para a Mainnet.
Quando terminarmos, também devemos fechar nossa conexão:
let _ = rpc.stop().unwrap();
O cargo run
deve compilar e executar o exemplo com sucesso com um aviso warning: unused variable: rpc
Fazendo uma Chamada RPC
As chamadas RPC são feitas usando o rpc Client
que criamos:
let mining_info = rpc.get_mining_info().unwrap();
println!("{:#?}", mining_info);
Geralmente, as palavras na chamada RPC são separadas por _
s. Uma lista completa está disponível na documentação do crate.
Fazendo uma Chamada RPC com Argumentos
O envio de uma chamada RPC com argumentos usando Rust requer apenas o conhecimento de como a função é apresentada. Por exemplo, a função get_block
é definida da seguinte forma na documentação:
fn get_block(&self, hash: &BlockHash) -> Result<Block>
Só precisamos permitir que ele pegue emprestado um blockhash, que pode ser recuperado (por exemplo) usando o comando get_best_block_hash
.
Aqui está o código completo para recuperar um hash de bloco, transformando-o em um bloco e imprimi-lo.
let hash = rpc.get_best_block_hash().unwrap();
let block = rpc.get_block(&hash).unwrap();
println!("{:?}", block);
NOTA: Outra chamada possível que consideramos para esta seção é a
get_address_info
, mas, infelizmente, no momento de escrita, a funçãobitcoincore-rpc
não funciona com versões recentes do Bitcoin Core devido ao crate não abordar as mais recentes mudanças de API no Bitcoin Core. Esperamos que isso seja resolvido no próximo lançamento do crate, mas, enquanto isso programador, cuidado.
Executando Nosso Código
Podemos acessar o código src e executá-lo. Infelizmente, a informação do "Block" sairá um pouco feia porque este exemplo não inclui uma biblioteca para embelezá-la.
$ cargo run
Compiling btc_test v0.1.0 (/home/standup/btc_test)
Finished dev [unoptimized + debuginfo] target(s) in 1.61s
Running `target/debug/btc_test`
GetMiningInfoResult {
blocks: 1832335,
current_block_weight: None,
current_block_tx: None,
difficulty: 4194304.0,
network_hash_ps: 77436285865245.1,
pooled_tx: 4,
chain: "test",
warnings: "Warning: unknown new rules activated (versionbit 28)",
}
Block { header: BlockHeader { version: 541065216, prev_blockhash: 000000000000027715981d5a3047daf6819ea3b8390b73832587594a2074cbf5, merkle_root: 4b2e2c2754b6ed9cf5c857a66ed4c8642b6f6b33b42a4859423e4c3dca462d0c, time: 1599602277, bits: 436469756, nonce: 218614401 }, txdata: [Transaction { version: 1, lock_time: 0, input: [TxIn { previous_output: OutPoint { txid: 0000000000000000000000000000000000000000000000000000000000000000, vout: 4294967295 }, script_sig: Script(OP_PUSHBYTES_3 8ff51b OP_PUSHBYTES_22 315448617368263538434f494e1d00010320a48db852 OP_PUSHBYTES_32 <push past end>), sequence: 4294967295, witness: [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]] }], output: [TxOut { value: 19721777, script_pubkey: Script(OP_HASH160 OP_PUSHBYTES_20 011beb6fb8499e075a57027fb0a58384f2d3f784 OP_EQUAL) }, TxOut { value: 0, script_pubkey: Script(OP_RETURN OP_PUSHBYTES_36 aa21a9ed63363f3620ab5e38b8860a50c84050e5ec31af3636bbd73f01ba9f14103100ee) }] }, Transaction { version: 2, lock_time: 1832282, input: [TxIn { previous_output: OutPoint { txid: cbf880f73d421baf0aa4f0d28e63ba00e5bc6bd934b91eb0641354ce5ca42f7e, vout: 0 }, script_sig: Script(OP_PUSHBYTES_22 00146b8dbd32e5deb90d22934e1513bae6e70156cd50), sequence: 4294967294, witness: [[48, 68, 2, 32, 13, 89, 205, 30, 67, 24, 196, 83, 65, 224, 44, 138, 98, 58, 81, 135, 132, 209, 23, 166, 23, 44, 3, 228, 95, 102, 166, 214, 62, 38, 155, 147, 2, 32, 119, 2, 34, 246, 148, 255, 166, 10, 90, 52, 242, 32, 74, 241, 123, 148, 89, 199, 197, 3, 152, 134, 242, 215, 109, 61, 241, 241, 13, 70, 86, 207, 1], [2, 192, 145, 170, 206, 55, 4, 36, 138, 145, 217, 50, 19, 73, 130, 136, 245, 131, 184, 142, 239, 75, 13, 67, 17, 177, 57, 86, 151, 139, 89, 35, 109]] }], output: [TxOut { value: 1667908, script_pubkey: Script(OP_HASH160 OP_PUSHBYTES_20 908ca2b8b49ccf53efa2226afa85f6cc58dfd7e7 OP_EQUAL) }, TxOut { value: 9093, script_pubkey: Script(OP_DUP OP_HASH160 OP_PUSHBYTES_20 42ee67664ce16edefc68ad0e4c5b7ce2fc2ccc18 OP_EQUALVERIFY OP_CHECKSIG) }] }, ...] }
Procurando por Fundos
Podemos procurar fundos sem argumentos opcionais usando a função get_balance
:
let balance = rpc.get_balance(None, None).unwrap();
println!("Balance: {:?} BTC", balance.as_btc());
Conforme mostrado, a função as_btc()
ajuda a gerar o saldo em um formato legível:
Balance: 3433.71692741 BTC
Criando um Endereço
A criação de um endereço demonstra como fazer uma chamada RPC com vários argumentos opcionais especificados (por exemplo, um rótulo e um tipo de endereço).
// Gerar um novo endereço
let myaddress = rpc
.get_new_address(Option::Some("BlockchainCommons"), Option::Some(json::AddressType::Bech32))
.unwrap();
println!("address: {:?}", myaddress);
Isso também exigirá que tragamos a definição json
para o escopo:
use bitcoincore_rpc::{json, Auth, Client, RpcApi};
Enviando uma Transação
Agora temos tudo de que precisamos para criar uma transação, o que será feito em cinco partes:
- Listar UTXOs;
- Preencher variáveis;
- Criar transação bruta;
- Assinar transação;
- Enviar transação.
1. Listando os UTXOs
Para iniciar a criação de uma transação, primeiro encontramos um UTXO para usar. O seguinte pega o primeiro UTXO com pelo menos 0,01 BTC:
let unspent = rpc
.list_unspent(
None,
None,
None,
None,
Option::Some(json::ListUnspentQueryOptions {
minimum_amount: Option::Some(Amount::from_btc(0.01).unwrap()),
maximum_amount: None,
maximum_count: None,
minimum_sum_amount: None,
}),
)
.unwrap();
let selected_tx = &unspent[0];
println!("selected unspent transaction: {:#?}", selected_tx);
Isso nos exigirá trazer mais estruturas para o escopo:
use bitcoincore_rpc::bitcoin::{Address, Amount};
Podemos observar que estamos passando cinco variáveis ao comando list_unspent
. Os primeiros quatro (minconf
, maxconf
, address
e include_unsafe
) não são usados aqui. O quinto é o query_options
, que não usamos antes, mas temos algumas opções de filtragem poderosas, incluindo a capacidade de olhar apenas para os UTXOs com um certo valor, mínimo ou máximo.
2. Preenchendo as Variáveis
Para começar a preencher as variáveis que precisaremos para criar uma nova transação, criamos a entrada do txid
e do vout
ao UTXO que selecionamos:
let selected_utxos = json::CreateRawTransactionInput {
txid: selected_tx.txid,
vout: selected_tx.vout,
sequence: None,
};
Em seguida, podemos calcular a quantia que vamos gastar subtraindo uma taxa de mineração dos fundos no UTXO:
// enviar todo o bitcoin no UTXO exceto um valor pequeno que será pago aos mineradores
let unspent_amount = selected_tx.amount;
let amount = unspent_amount - Amount::from_btc(0.00001).unwrap();
Por fim, podemos criar um mapa do hash do endereço e da quantidade para formar a saída:
let mut output = HashMap::new();
output.insert(
myaddress.to_string(),
amount,
);
Outra característica é necessária para a variável de saída: o HashMap
. Ele nos permite armazenar
valores por chave, que iremos precisar para representar a informação {address: amount}
.
use std::collections::HashMap;
3. Criando a Transação Bruta
Agora estamos prontos para criar uma transação bruta:
let unsigned_tx = rpc
.create_raw_transaction(&[selected_utxos], &output, None, None)
.unwrap();
4. Assinando a Transação
A assinatura da transação pode ser feita com um simples uso do sign_raw_transaction_with_wallet
:
let signed_tx = rpc
.sign_raw_transaction_with_wallet(&unsigned_tx, None, None)
.unwrap();
println!("signed tx {:?}", signed_tx.transaction().unwrap());
5. Enviando a Transação
Finalmente, podemos transmitir a transação:
let txid_sent = rpc
.send_raw_transaction(&signed_tx.transaction().unwrap())
.unwrap();
println!("{:?}", txid_sent);
Executando Nosso Código
Agora podemos executar o código completo do src.
$ cargo run
Compiling btc_test v0.1.0 (/home/standup/btc_test)
warning: unused variable: `unspent_amount`
--> src/main.rs:86:9
|
86 | let unspent_amount = selected_tx.amount;
| ^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unspent_amount`
|
= note: `#[warn(unused_variables)]` on by default
warning: 1 warning emitted
Finished dev [unoptimized + debuginfo] target(s) in 2.11s
Running `target/debug/btc_test`
Balance: 0.01031434 BTC
address: tb1qx5jz36xgt9q2rkh4daee8ewfj0g5z05v8qsua2
selected unspent transaction: ListUnspentResultEntry {
txid: 84207ffec658ae29ad1fdd330d8a13613303c3cf281ce628fadeb7636ffb535e,
vout: 1,
address: Some(
tb1qrcf8c29966tvqxhwrtd2se3rj6jeqtll3r46a4,
),
label: None,
redeem_script: None,
witness_script: None,
script_pub_key: Script(OP_0 OP_PUSHBYTES_20 1e127c28a5d696c01aee1adaa8662396a5902fff),
amount: Amount(1029734 satoshi),
confirmations: 1246,
spendable: true,
solvable: true,
descriptor: Some(
"wpkh([ce0c7e14/0\'/1\'/26\']02c581259ba7e6aef6d7ea23adb08f7c7f10c4c678f2e097a4074639e7685d4805)#j3pctfhf",
),
safe: true,
}
unsigned tx Transaction {
version: 2,
lock_time: 0,
input: [
TxIn {
previous_output: OutPoint {
txid: 84207ffec658ae29ad1fdd330d8a13613303c3cf281ce628fadeb7636ffb535e,
vout: 1,
},
script_sig: Script(),
sequence: 4294967295,
witness: [],
},
],
output: [
TxOut {
value: 1028734,
script_pubkey: Script(OP_0 OP_PUSHBYTES_20 352428e8c85940a1daf56f7393e5c993d1413e8c),
},
],
}
signed tx Transaction { version: 2, lock_time: 0, input: [TxIn { previous_output: OutPoint { txid: 84207ffec658ae29ad1fdd330d8a13613303c3cf281ce628fadeb7636ffb535e, vout: 1 }, script_sig: Script(), sequence: 4294967295, witness: [[48, 68, 2, 32, 98, 230, 199, 113, 156, 242, 158, 42, 148, 229, 239, 44, 9, 226, 127, 219, 72, 51, 26, 135, 44, 212, 179, 200, 213, 63, 56, 167, 0, 55, 236, 235, 2, 32, 41, 43, 30, 109, 60, 162, 124, 67, 20, 126, 4, 107, 124, 95, 9, 200, 132, 246, 147, 235, 176, 55, 59, 45, 190, 18, 211, 201, 143, 62, 163, 36, 1], [2, 197, 129, 37, 155, 167, 230, 174, 246, 215, 234, 35, 173, 176, 143, 124, 127, 16, 196, 198, 120, 242, 224, 151, 164, 7, 70, 57, 231, 104, 93, 72, 5]] }], output: [TxOut { value: 1028734, script_pubkey: Script(OP_0 OP_PUSHBYTES_20 352428e8c85940a1daf56f7393e5c993d1413e8c) }] }
b0eda3517e6fac69e58ae315d7fe7a1981e3a858996cc1e3135618cac9b79d1a
Resumo: Acessando o Bitcoind com Rust
O bitcoincore-rpc
é um crate simples e robusto que nos permitirá interagir com o Bitcoin RPC usando Rust. No entanto, no momento em que este livro foi escrito, ele ficou para trás no Bitcoin Core, o que pode causar alguns problemas de uso.
O Que Vem Depois?
Vamos aprender mais sobre "Conversando com o Bitcoind com Outras Linguagens" na seção §18.6: Acessando o Bitcoind com Swift.