15 KiB
18.1: Acessando o Bitcoind com Go
ℹ️ 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 Go e o btcd rpcclient. É importante observar que ele tem algumas peculiaridades e limitações.
Configurando o Go
Para nos preparamos para o uso do Go em nossa máquina UNIX, primeiro precisamos instalar o curl, caso ainda não o tenhamos feito:
$ sudo apt install curl
Então, vamos ler a página de downloads do Go para obtermos o link para o download mais recente e posteriormente fazer o download usando curl. Para uma configuração Debian, vamos querer usar a versão linux-amd64:
$ curl -O https://dl.google.com/go/go1.15.1.linux-amd64.tar.gz
Assim que terminarmos o download, comparamos o hash com o hash na página de downloads do Go:
$ sha256sum go1.15.1.linux-amd64.tar.gz
70ac0dbf60a8ee9236f337ed0daa7a4c3b98f6186d4497826f68e97c0c0413f6 go1.15.1.linux-amd64.tar.gz
Os hashes devem corresponder. Em caso afirmativo, extraímos o tarball e instalamos o Go em nosso sistema:
$ tar xfv go1.15.1.linux-amd64.tar.gz
$ sudo chown -R root:root ./go
$ sudo mv go /usr/local
Agora precisamos criar um caminho no Go para especificar nosso ambiente. Abra o arquivo ~ / .profile com o editor de texto de sua escolha e adicione o seguinte ao final dele:
export GOPATH=$HOME/work
export PATH=$PATH:/usr/local/go/bin:$GOPATH/bin
Em seguida, atualizamos nosso perfil:
$ source ~/.profile
Por fim, vamos criar o diretório para o nosso espaço de trabalho Go:
$ mkdir $HOME/work
Configurando o btcd e o rpcclient
Usaremos o rpcclient que advém do btcd, uma implementação Bitcoin escrita em Go. Embora o rpcclient tenha sido originalmente projetado para funcionar com o full node btcd do Bitcoin, ele também funciona com o Bitcoin Core. Ele tem algumas peculiaridades que veremos mais adiante.
Podemos usar o go get para baixá-lo:
$ go get github.com/btcsuite/btcd/rpcclient
Para testar seu funcionamento, vmaos navegar até o diretório com os exemplos do Bitcoin Core:
$ cd $GOPATH/src/github.com/btcsuite/btcd/rpcclient/examples/bitcoincorehttp
Vamos modificar o arquivo main.go e inserir os detalhes associados à configuração do Bitcoin Core, que pode ser encontrado no caminho ~ /.bitcoin/bitcoin.conf:
Host: "localhost:18332",
User: "StandUp",
Pass: "6305f1b2dbb3bc5a16cd0f4aac7e1eba",
MAINNET VS TESTNET: A porta seria 8332 para uma configuração na Mainnet.
Agora podemos executar um teste:
$ go run main.go
Devemos ver a contagem de blocos impressa:
2020/09/01 11:41:24 Block count: 1830861
Criando um Projeto rpcclient
Normalmente, iríamos criar projetos no diretório ~/work/src/myproject/bitcoin:
$ mkdir -p ~/work/src/myproject/bitcoin
$ cd ~/work/src/myproject/bitcoin
Cada projeto deve ter os seguintes imports:
import (
"log"
"fmt"
"github.com/btcsuite/btcd/rpcclient"
)
Esta declaração import permite que importemos bibliotecas relevantes. Para cada exemplo aqui, precisaremos importar "log", "fmt" e "github.com/btcsuite/btcd/rpcclient". Podemos precisar importar bibliotecas adicionais em alguns exemplos.
- O
logé usado para mostrar mensagens de erro na tela. Após cada vez que o node Bitcoin for chamado, uma instruçãoifirá verificar se há algum erro. Se houver erros, ologé usado para imprimi-los; - O
fmté usado para imprimir a saída; - O
rpcclienté obviamente a biblioteca dorpcclient;
Construindo Nossa Conexão
Cada função do bitcoind no Go começa com a criação da conexão RPC, usando a função ConnConfig:
connCfg := &rpcclient.ConnConfig{
Host: "localhost:18332",
User: "StandUp",
Pass: "431451790e3eee1913115b9dd2fbf0ac",
HTTPPostMode: true,
DisableTLS: true,
}
client, err := rpcclient.New(connCfg, nil)
if err != nil {
log.Fatal(err)
}
defer client.Shutdown()
Os parâmetros da connCfg permitem que escolhamos a porta Bitcoin RPC, nome de usuário, senha e se estamos usando a testnet ou mainnet.
NOTA: Novamente, precisamos nos certificar de substituir o
Usere oPasscom aqueles encontrados no nosso~/.bitcoin/bitcon.conf.
A função rpcclient.New(connCfg, nil) configura oclient para nos conectarmos ao nosso node Bitcoin.
A linha defer client.Shutdown() é para desconectar do nosso node Bitcoin, uma vez que a função main() termina de ser executada. Após a linha defer client.Shutdown() é onde as coisas interessantes acontecem –– e será muito fácil de utilizar. Isso porque o rpcclient ajuda a transformar os comandos bitcoin-cli em funções, usando PascalCase. Por exemplo, bitcoin-cli getblockcount se transformará em client.GetBlockCount no Go.
Fazendo uma Chamada RPC
Agora, tudo o que precisamos fazer é uma chamada informativa como GetBlockCount ou GetBlockHash usando nosso client:
blockCount, err := client.GetBlockCount()
if err != nil {
log.Fatal(err)
}
blockHash, err := client.GetBlockHash(blockCount)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%d\n", blockCount)
fmt.Printf("%s\n", blockHash.String())
Fazendo uma Chamada RPC com Argumentos
As funções rpcclient também podem receber entradas, por exemplo, client.GetBlockHash(blockCount) recebe a contagem de blocos como uma entrada. O client.GetBlockHash (blockCount) de cima seria parecido com um comando bitcoin-cli:
$ bitcoin-cli getblockhash 1830868
00000000000002d53b6b9bba4d4e7dc44a79cebd1024d1bcfb9b3cc07d6cad9c
No entanto, uma peculiaridade com hashes no rpcclient é que normalmente eles irão ser mostrados em uma codificação diferente se imprimirmos normalmente com o blockHash. Para imprimi-los como uma string, precisamos usar o blockHash.String().
Executando Nosso Código
Podemos baixar o código completo do diretório src.
Podemos então, executar:
$ go run blockinfo.go
1830868
00000000000002d53b6b9bba4d4e7dc44a79cebd1024d1bcfb9b3cc07d6cad9c
O último número do bloco, junto com nosso hash, devem ser impressos.
Procurando por Fundos
Devido às limitações do btcd no rpcclient, não podemos fazer uso da função getwalletinfo. No entanto, podemos usar o RPC getbalance:
wallet, err := client.GetBalance("*")
if err != nil {
log.Fatal(err)
}
fmt.Println(wallet)
O client.GetBalance("*") requer a entrada "*" devido a uma peculiaridade do btcd. O asterisco significa que desejamos obter o saldo de todas as nossas carteiras.
Se executarmos o código src, deveremos obter uma saída semelhante a esta:
$ go run getbalance.go
0.000689 BTC
Criando um Endereço
Podemos gerar endereços em Go, mas não podemos especificar o tipo do endereço:
Isso requer o uso de uma função especial chaincfg, para especificar para qual rede os endereços estão sendo criados. Esta especificação é necessária apenas durante a geração do endereço, por isso é usada apenas neste exemplo. Também podemos incluir isso nos demais exemplos, mas não é necessário.
Certifique-se de importar o "github.com/btcsuite/btcd/chaincfg":
import (
"log"
"fmt"
"github.com/btcsuite/btcd/rpcclient"
"github.com/btcsuite/btcd/chaincfg"
)
Em seguida, vamos chamar o connCfG com o parâmetro chaincfg.TestNet3Params.Name:
connCfg := &rpcclient.ConnConfig{
Host: "localhost:18332",
User: "bitcoinrpc",
Pass: "431451790e3eee1913115b9dd2fbf0ac",
HTTPPostMode: true,
DisableTLS: true,
Params: chaincfg.TestNet3Params.Name,
}
client, err := rpcclient.New(connCfg, nil)
if err != nil {
log.Fatal(err)
}
defer client.Shutdown()
MAINNET VS TESTNET: O
Params: chaincfg.TestNet3Params.Namedeve serParams: chaincfg.MainNetParams.Name,na Mainnet.
Podemos então criar nosso endereço:
address, err := client.GetNewAddress("")
if err != nil {
log.Fatal(err)
}
fmt.Println(address)
Uma peculiaridade com o client.GetNewAddress("") é que uma string vazia precisa ser incluída para que tudo funcione perfeitamente.
Executando o código fonte teremos os seguintes resultados:
$ go run getaddress.go
tb1qutkcj34pw0aq7n9wgp3ktmz780szlycwddfmza
Decodificando um Endereço
Criar um endereço exigia um trabalho extra, em especificar a blockchain correta. Usar um endereço também exigirá, porque teremos que decodificá-lo antes de usá-lo.
Isso significa que teremos que importar as bibliotecas "github.com/btcsuite/btcutil" e "github.com/btcsuite/btcd/chaincfg".
- O
btcutilpermite que um endereço Bitcoin seja decodificado de uma forma que orpcclientpossa entender. Isso é necessário ao trabalhar com endereços norpcclient; - O
chaincfgé (novamente) usado para configurar nossa chain como a chain Testnet. Isso é necessário para a decodificação de endereços, pois os endereços usados na Mainnet e na Testnet são diferentes.
import (
"log"
"fmt"
"github.com/btcsuite/btcd/rpcclient"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcd/chaincfg"
)
A variável defaultNet agora é usada para especificar se nosso node Bitcoin está na Testnet ou na Mainnet. Essa informação (e o objeto btcutil) é então usada para decodificar o endereço.
MAINNET VS TESTNET:
&chaincfg.TestNet3Paramsdeve ser& chaincfg.MainNetParamsna Mainnet.
defaultNet := &chaincfg.TestNet3Params
addr, err := btcutil.DecodeAddress("mpGpCMX6SuUimDZKiVViuhd7EGyVxkNnha", defaultNet)
if err != nil {
log.Fatal(err)
}
NOTA: Precisamos alterar o endereço (
mpGpCMX6SuUimDZKiVViuhd7EGyVxkNnha) para um que seja da nossa carteira. Podemos usar obitcoin-cli listunspentpara encontrar alguns endereços com fundos para teste. Se quisermos ser realmente sofisticados, podemos modificar o código Go para obter um argumento e, em seguida, escrever um script que executa olistunspent, para depois salvarmos a informação em uma variável e executar o código Go nela.
Só depois disso usamos o RPC getreceivedbyaddress no nosso endereço decodificado:
wallet, err := client.GetReceivedByAddress(addr)
if err != nil {
log.Fatal(err)
}
fmt.Println(wallet)
Ao executar o código, devemos obter uma saída semelhante a esta:
$ go run getamountreceived.go
0.0085 BTC
Enviando uma Transação
Agora temos todas as peças do quebra-cabeça no lugar para enviar uma transação. Vamos querer:
- Importar as bibliotecas corretas, incluindo a
chaincfgpara especificar uma rede e obtcutilpara decodificar um endereço; - Escolher um endereço para enviar;
- Decodificar esse endereço;
- Executar o
sendtoaddresspara enviar fundos da maneira fácil.
package main
import (
"log"
"fmt"
"github.com/btcsuite/btcd/rpcclient"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcd/chaincfg"
)
func main() {
connCfg := &rpcclient.ConnConfig{
Host: "localhost:18332",
User: "StandUp",
Pass: "431451790e3eee1913115b9dd2fbf0ac",
HTTPPostMode: true,
DisableTLS: true,
}
client, err := rpcclient.New(connCfg, nil)
if err != nil {
log.Fatal(err)
}
defer client.Shutdown()
defaultNet := &chaincfg.TestNet3Params
addr, err := btcutil.DecodeAddress("n2eMqTT929pb1RDNuqEnxdaLau1rxy3efi", defaultNet)
if err != nil {
log.Fatal(err)
}
sent, err := client.SendToAddress(addr, btcutil.Amount(1e4))
if err != nil {
log.Fatal(err)
}
fmt.Println(sent)
}
Quando executamos o código, o txid da transação nos será retornado:
$ go run sendtransaction.go
9aa4cd6559e0d69059eae142c35bfe78b71a8084e1fcc2c74e2a9675e9e7489d
Consultando uma Transação
Para consultar uma transação, como a que acabamos de enviar, precisaremos fazer mais uma vez algumas conversões, desta vez do txid. O "github.com/btcsuite/btcd/chaincfg/chainhash" é importado para permitir que os hashes sejam armazenados no código Go. O chainhash.NewHashFromStr("hash") converte um hash em uma string para um formato que funciona com o rpcclient.
package main
import (
"log"
"fmt"
"github.com/btcsuite/btcd/rpcclient"
"github.com/btcsuite/btcd/chaincfg/chainhash"
)
func main() {
connCfg := &rpcclient.ConnConfig{
Host: "localhost:18332",
User: "StandUp",
Pass: "431451790e3eee1913115b9dd2fbf0ac",
HTTPPostMode: true,
DisableTLS: true,
}
client, err := rpcclient.New(connCfg, nil)
if err != nil {
log.Fatal(err)
}
defer client.Shutdown()
chash, err := chainhash.NewHashFromStr("1661ce322c128e053b8ea8fcc22d17df680d2052983980e2281d692b9b4ab7df")
if err != nil {
log.Fatal(err)
}
transactions, err := client.GetTransaction(chash)
if err != nil {
log.Fatal(err)
}
fmt.Println(transactions)
}
NOTA: Novamente, vamos querer trocar o txid por um que realmente será reconhecido pelo nosso sistema.
Ao executar o código, ele imprimirá os detalhes associados a uma transação, como seu valor e quantas vezes foi confirmada:
$ go run lookuptransaction.go
{
"amount": 0.00100000,
"confirmations": 4817,
"blockhash": "000000006628870b0a8a66abea9cf0d4e815c491f079e3fa9e658a87b5dc863a",
"blockindex": 117,
"blocktime": 1591857418,
"txid": "1661ce322c128e053b8ea8fcc22d17df680d2052983980e2281d692b9b4ab7df",
"walletconflicts": [
],
"time": 1591857343,
"timereceived": 1591857343,
"bip125-replaceable": "no",
"details": [
{
"address": "mpGpCMX6SuUimDZKiVViuhd7EGyVxkNnha",
"category": "receive",
"amount": 0.00100000,
"label": "",
"vout": 0
}
],
"hex": "02000000000101e9e8c3bd057d54e73baadc60c166860163b0e7aa60cab33a03e89fb44321f8d5010000001716001435c2aa3fc09ea53c3e23925c5b2e93b9119b2568feffffff02a0860100000000001976a914600c8c6a4abb0a502ea4de01681fe4fa1ca7800688ac65ec1c000000000017a91425b920efb2fde1a0277d3df11d0fd7249e17cf8587024730440220403a863d312946aae3f3ef0a57206197bc67f71536fb5f4b9ca71a7e226b6dc50220329646cf786cfef79d60de3ef54f702ab1073694022f0618731902d926918c3e012103e6feac9d7a8ad1ac6b36fb4c91c1c9f7fff1e7f63f0340e5253a0e4478b7b13f41fd1a00"
}
Resumo: Acessando o Bitcoind com Go
Embora o btcd e o rpcclient tenham alguns limites, ainda podemos executar os principais comandos RPC no Go. A documentação para o rpcclient está disponível no Godoc. Se a documentação não tiver o que procuramos, podemos consultar também o repositório btcd. Geralmente é bem documentado e fácil de ler. Com base nesses exemplos, devemos ser capazes de incorporar o Bitcoin em um projeto Go e fazer coisas como enviar e receber moedas.
O Que Vem Depois?
Vamos aprender mais sobre "Conversando com o Bitcoind com Outras Linguagens" na seção §18.2: Acessando Bitcoin com Java.