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çãoif
irá 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
User
e oPass
com 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.Name
deve 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
btcutil
permite que um endereço Bitcoin seja decodificado de uma forma que orpcclient
possa 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.TestNet3Params
deve ser& chaincfg.MainNetParams
na 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 listunspent
para 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
chaincfg
para especificar uma rede e obtcutil
para decodificar um endereço; - Escolher um endereço para enviar;
- Decodificar esse endereço;
- Executar o
sendtoaddress
para 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 o Bitcoin com Java.