Learning-Bitcoin-from-the-C.../pt/18_1_Accessing_Bitcoind_with_Go.md
2021-08-24 09:55:33 -03:00

15 KiB
Raw Blame History

17.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 mais utilizado por nós e vamos escolher e adicionar 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ção if irá verificar se há algum erro. Se houver erros, o log é usado para imprimi-los;
  • O fmt é usado para imprimir a saída;
  • O rpcclient é obviamente a biblioteca do rpcclient;

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 o Pass com aquele encontrado no nosso ~/.bitcoin/bitcon.conf.

A função rpcclient.New(connCfg, nil) configura oclient para nos conectarmos ao nosso node de 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 é necessário é 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](src / 17_1_blockinfo.go).

Podemos então, executar:

$ go run blockinfo.go 
1830868
00000000000002d53b6b9bba4d4e7dc44a79cebd1024d1bcfb9b3cc07d6cad9c

O último número do bloco junto com nosso hash deve ser impresso.

Pesquisando os fundos

Devido às limitações do btcd no rpcclient, não pode fazer o 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](src / 17_1_getbalance.go), 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.

Be sure to import "github.com/btcsuite/btcd/chaincfg": Vamos nos certificar 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 ser Params: 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 the source teremos os seguintes resultados:

$ go run getaddress.go 
tb1qutkcj34pw0aq7n9wgp3ktmz780szlycwddfmza

Decodificando um endereço

Criando um endereço exigia um trabalho extra, ao especificar a blockchain correta. Usar um endereço também funcionará, 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 o rpcclient possa entender. Isso é necessário ao trabalhar com endereços no rpcclient;
  • O chaincfg é (novamente) usado para configurar nossa cadeia como a cadeia 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 do Bitcoin que está na Testnet ou na Mainnet. Essa informação (e o objeto btcutil) é então usado 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 o bitcoin-cli listunspent para encontrar alguns endereços com fundos para o teste. Se quisermos ser realmente sofisticados, podemos modificar o código Go para obter um argumento e, em seguida, escrever um script que executa o listunspent, 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:

  1. Importar as bibliotecas corretas, incluindo a chaincfg para especificar uma rede e o btcutil para decodificar um endereço;
  2. Escolher um endereço para enviar;
  3. Decodificar esse endereço;
  4. Executar o sendtoaddress para enviar fundos da maneira mais 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

Pesquisando 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 nosso valor e quantas vezes foi confirmado:

$ 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 §17.2: Acessando Bitcoin com Java.