Learning-Bitcoin-from-the-C.../pt/18_4_Accessing_Bitcoind_with_Python.md
2021-09-29 08:42:17 -03:00

21 KiB
Raw Blame History

18.4: Acessando o Bitcoind com Python

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 Python e o Python-BitcoinRPC.

Configurando o Python

Se já temos o Bitcoin Core instalado, provavelmente temos o Python 3 disponível. Podemos verificar isso executando:

$ python3 --version

Se ele retornar um número de versão (por exemplo, 3.7.3 ou 3.8.3), então temos o python3 instalado.

No entanto, se não tivermos o Python instalado, precisaremos compilá-lo a partir do código-fonte. Devemos consultar como fazer isso em "Construindo Python da Fonte" antes de continuarmos.

Configurando o BitcoinRPC

Quer tenhamos usado um Python existente ou compilado a partir da fonte, agora estamos prontos para instalar a biblioteca python-bitcoinrpc:

$ pip3 install python-bitcoinrpc

Se não instalamos o pip, precisaremos executar o seguinte:

$ sudo apt install python3-pip

Em seguida, vamos repetir o comando pip3 install python-bitcoinrpc.

Criando um Projeto BitcoinRPC

Geralmente, precisaremos incluir declarações apropriadas do bitcoinrpc em nosso projetos Bitcoin usando o Python. O seguinte fornecerá acesso aos comandos baseados em RPC:

from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException

Também podemos achar o seguinte comando útil:

from pprint import pprint
import logging

O pprint irá imprimir a resposta json do bitcoind.

O logging irá imprimir a chamada que fizemos para o respose do bitcoind e o próprio bitcoind, o que é útil quando fazemos um monte de chamadas juntas. Se não quisermos um retorno muito grande no terminal, podemos comentar o bloco logging.

Construindo a Nossa Conexão

Agora estamos prontos para começar a interagir com o bitcoind estabelecendo uma conexão. Vamos criar um arquivo chamado btcrpc.py e digitar o seguinte:

logging.basicConfig()
logging.getLogger("BitcoinRPC").setLevel(logging.DEBUG)
# rpc_user e rpc_password estão configurados no arquivo bitcoin.conf
rpc_user = "StandUp"
rpc_pass = "6305f1b2dbb3bc5a16cd0f4aac7e1eba"
rpc_host = "127.0.0.1"
rpc_client = AuthServiceProxy(f"http://{rpc_user}:{rpc_pass}@{rpc_host}:18332", timeout=120)

Os argumentos na URL são <rpc_username>:<rpc_password>@<host_IP_address>:<port>. Como de costume, o user e o pass são encontrados no arquivo ~/.bitcoin/bitcoin.conf, enquanto o host é o nosso localhost, e a porta é 18332 para Testnet. O argumento timeout é especificado desde o tempo limite dos sockets sob grande demanda na rede principal. Se obtivermos a resposta socket.timeout: timed out, precisamos ser pacientes e aumentar o timeout.

🔗 MAINNET VS TESTNET: A porta seria 8332 para uma configuração na Mainnet.

Fazendo uma Chamada RPC

Se o rpc_client for inicializado com sucesso, seremos capazes de enviar comandos RPC para o nosso node do Bitcoin.

Para usar um método RPC do python-bitcoinrpc, usaremos o objeto rpc_client que criamos, que fornece a maior parte da funcionalidade que pode ser acessada através do bitcoin-cli, usando os mesmos nomes do método.

Por exemplo, o comando a seguir irá recuperar a contagem de blocos do nosso node:

block_count = rpc_client.getblockcount()
print("---------------------------------------------------------------")
print("Block Count:", block_count)
print("---------------------------------------------------------------\n")

Devemos obter a seguinte resposta com o logging habilitado:

DEBUG:BitcoinRPC:-3-> getblockcount []
DEBUG:BitcoinRPC:<-3- 1773020
---------------------------------------------------------------
Block Count: 1773020
---------------------------------------------------------------

Fazendo uma Chamada RPC com Argumentos

Podemos usar esse blockcount como um argumento para recuperar o blockhash de um bloco e também para recuperar os detalhes do bloco.

Isso é feito enviando nossos comandos do objeto rpc_client com um argumento:

blockhash = rpc_client.getblockhash(block_count)
block = rpc_client.getblock(blockhash)

O getblockhash retornará um único valor, enquanto o getblock retornará um array associativo de informações sobre o bloco, que inclui um array em block['tx'] fornecendo detalhes sobre cada transação dentro do bloco:

nTx = block['nTx']
if nTx > 10:
    it_txs = 10
    list_tx_heading = "First 10 transactions: "
else:
    it_txs = nTx
    list_tx_heading = f"All the {it_txs} transactions: "
print("---------------------------------------------------------------")
print("BLOCK: ", block_count)
print("-------------")
print("Block Hash...: ", blockhash)
print("Merkle Root..: ", block['merkleroot'])
print("Block Size...: ", block['size'])
print("Block Weight.: ", block['weight'])
print("Nonce........: ", block['nonce'])
print("Difficulty...: ", block['difficulty'])
print("Number of Tx.: ", nTx)
print(list_tx_heading)
print("---------------------")
i = 0
while i < it_txs:
    print(i, ":", block['tx'][i])
    i += 1
print("---------------------------------------------------------------\n")

Executando nosso código

Podemos usar o código que está no src/ e executá-lo com python3:

$ python3 getinfo.py
---------------------------------------------------------------
Block Count: 1831106
---------------------------------------------------------------
---------------------------------------------------------------
BLOCK:  1831106
-------------
Block Hash...:  00000000000003b2ea7c2cdfffd86156ad1f5606ab58e128940a2534d1348b04
Merkle Root..:  056a547fe59208167eef86fa694263728fb684119254b340c1f86bdd423a8082
Block Size...:  52079
Block Weight.:  128594
Nonce........:  1775583700
Difficulty...:  4194304
Number of Tx.:  155
First 10 transactions: 
---------------------
0 : d228d55112e3aa26265b0118cfdc98345c229d20fe074b9afb87107c03ce11b5
1 : 92822e8e34fafb472b87c99ea3f3e16440452b3f361ed86c6fa62175173fb750
2 : fa7c67600c14d4aa350a9674688f1429577954f4a6c5e4639d06c8964824f647
3 : 3a91d1527e308e5603dafde7ab17824f441a73a779d2571d073466dc9e8451b2
4 : 30fd0e5527b1522e7b26a4818b9edac80fe47c0c39fc34705478a49e684708d0
5 : 24c5372b38c78cbaf5b0b305925502a491bc0c1b5758f50c0bd335abb6ae85f5
6 : be70e125a5793efc5e32051fecba0668df971bdf371138c8261201c2a46b2d38
7 : 41ebf52c847a59ba0aeb4425c74e89a01e91defa86a82785ff53ed4668054561
8 : dc8211b4ce122f87692e7c203672e3eb1ffc44c0a307eafcc560323fcc5fae78
9 : 59e2d8e11cad287eacf3207e64a373f65059286b803ef0981510193ae29cbc8c
---------------------------------------------------------------

Pesquisando Fundos

Da mesma forma, podemos recuperar as informações da nossa carteira com o RPC getwalletinfo:

wallet_info = rpc_client.getwalletinfo()
print("---------------------------------------------------------------")
print("Wallet Info:")
print("-----------")
pprint(wallet_info)
print("---------------------------------------------------------------\n")

Devemos obter um retorno semelhante ao que tivemos abaixo com o logging desabilitado:

---------------------------------------------------------------
Wallet Info:
-----------
{'avoid_reuse': False,
 'balance': Decimal('0.07160443'),
 'hdseedid': '6dko666b1cc0d69b7eb0539l89eba7b6390kdj02',
 'immature_balance': Decimal('0E-8'),
 'keypoololdest': 1542245729,
 'keypoolsize': 999,
 'keypoolsize_hd_internal': 1000,
 'paytxfee': Decimal('0E-8'),
 'private_keys_enabled': True,
 'scanning': False,
 'txcount': 9,
 'unconfirmed_balance': Decimal('0E-8'),
 'walletname': '',
 'walletversion': 169900}
---------------------------------------------------------------

Outros comandos informativos como getblockchaininfo, getnetworkinfo, getpeerinfo e getblockchaininfo funcionarão de forma semelhante.

Outros comandos podem fornecer informações específicas sobre elementos selecionados na sua carteira.

Recuperando um Array

O RPC listtransactions permite que observemos as 10 transações mais recentes do nosso sistema (ou algum conjunto arbitrário de transações usando os argumentos count e skip). Ele mostra como um comando RPC pode retornar uma matriz simples de ser manipulada:

tx_list = rpc_client.listtransactions()
pprint(tx_list)

Explorando um UTXO

Da mesma forma, podemos usar o listunspent para obter uma matriz de UTXOs:

print("Exploring UTXOs")
## List UTXOs
utxos = rpc_client.listunspent()
print("Utxos: ")
print("-----")
pprint(utxos)
print("------------------------------------------\n")

Para manipular uma matriz como a retornada de listtransactions ou listunpsent, você apenas pega o item apropriado do elemento apropriado da matriz:

## Select a UTXO - first one selected here
utxo_txid = utxos[0]['txid']

Para o listunspent, obtemos um txid. Podemos recuperar as informações sobre ele com o comando gettransaction e, em seguida, decodificá-lo com um decoderawtransaction:

utxo_hex = rpc_client.gettransaction(utxo_txid)['hex']

utxo_tx_details = rpc_client.decoderawtransaction(utxo_hex)

print("Details of Utxo with txid:", utxo_txid)
print("---------------------------------------------------------------")
print("UTXO Details:")
print("------------")
pprint(utxo_tx_details)
print("---------------------------------------------------------------\n")

Este código está disponível no arquivo walletinfo.py.

$ python3 walletinfo.py 
---------------------------------------------------------------
Wallet Info:
-----------
{'avoid_reuse': False,
 'balance': Decimal('0.01031734'),
 'hdseedid': 'da5a1b058deb9e51ecffef1b0ddc069a5dfb2c5f',
 'immature_balance': Decimal('0E-8'),
 'keypoololdest': 1596567843,
 'keypoolsize': 1000,
 'keypoolsize_hd_internal': 999,
 'paytxfee': Decimal('0E-8'),
 'private_keys_enabled': True,
 'scanning': False,
 'txcount': 6,
 'unconfirmed_balance': Decimal('0E-8'),
 'walletname': '',
 'walletversion': 169900}
---------------------------------------------------------------

Utxos: 
-----
[{'address': 'mv9cjEnS2o1EygBMdrz99LzhM8KeEMoXDg',
  'amount': Decimal('0.00001000'),
  'confirmations': 1180,
  'desc': "pkh([ce0c7e14/0'/0'/25']02d0541b9211aecd25913f7fdecfc1b469215fa326d52067b1b3f7efbd12316472)#n06pq9q5",
  'label': '-addresstype',
  'safe': True,
  'scriptPubKey': '76a914a080d1a10f5e7a02d0a291f118982ed19e8cfcd788ac',
  'solvable': True,
  'spendable': True,
  'txid': '84207ffec658ae29ad1fdd330d8a13613303c3cf281ce628fadeb7636ffb535e',
  'vout': 0},
 {'address': 'tb1qrcf8c29966tvqxhwrtd2se3rj6jeqtll3r46a4',
  'amount': Decimal('0.01029734'),
  'confirmations': 1180,
  'desc': "wpkh([ce0c7e14/0'/1'/26']02c581259ba7e6aef6d7ea23adb08f7c7f10c4c678f2e097a4074639e7685d4805)#j3pctfhf",
  'safe': True,
  'scriptPubKey': '00141e127c28a5d696c01aee1adaa8662396a5902fff',
  'solvable': True,
  'spendable': True,
  'txid': '84207ffec658ae29ad1fdd330d8a13613303c3cf281ce628fadeb7636ffb535e',
  'vout': 1},
 {'address': 'mzDxbtYY3LBBBJ6HhaBAtnHv6c51BRBTLE',
  'amount': Decimal('0.00001000'),
  'confirmations': 1181,
  'desc': "pkh([ce0c7e14/0'/0'/23']0377bdd176f985b4af2f6bdbb22c2925b6007b6c07ba171f75e65990c002615e98)#3y6ef6vu",
  'label': '-addresstype',
  'safe': True,
  'scriptPubKey': '76a914cd339342b06042bb986a45e73d56db46acc1e01488ac',
  'solvable': True,
  'spendable': True,
  'txid': '1679bee019c61608340b79810377be2798efd4d2ec3ace0f00a1967af70666b9',
  'vout': 1}]
------------------------------------------

Details of Utxo with txid: 84207ffec658ae29ad1fdd330d8a13613303c3cf281ce628fadeb7636ffb535e
---------------------------------------------------------------
UTXO Details:
------------
{'hash': '0c6c27f58f122329bbc53a91f290b35ce23bd2708706b21a04cdc387dc8e2fd9',
 'locktime': 1831103,
 'size': 225,
 'txid': '84207ffec658ae29ad1fdd330d8a13613303c3cf281ce628fadeb7636ffb535e',
 'version': 2,
 'vin': [{'scriptSig': {'asm': '', 'hex': ''},
          'sequence': 4294967294,
          'txid': '1679bee019c61608340b79810377be2798efd4d2ec3ace0f00a1967af70666b9',
          'txinwitness': ['3044022014b3e2359fb46d8cbc4cd30fa991b455edfa4b419a4c64a53fcdfc79e3ca89db022010cefc3268bc252d55f1982c426328b709b47d02332def9e2efb3b12de2cf0d301',
                          '0351b470e87b44e8e9607acf09b8d4543c51c93c17dc741176319e60202091f2be'],
          'vout': 0}],
 'vout': [{'n': 0,
           'scriptPubKey': {'addresses': ['mv9cjEnS2o1EygBMdrz99LzhM8KeEMoXDg'],
                            'asm': 'OP_DUP OP_HASH160 '
                                   'a080d1a10f5e7a02d0a291f118982ed19e8cfcd7 '
                                   'OP_EQUALVERIFY OP_CHECKSIG',
                            'hex': '76a914a080d1a10f5e7a02d0a291f118982ed19e8cfcd788ac',
                            'reqSigs': 1,
                            'type': 'pubkeyhash'},
           'value': Decimal('0.00001000')},
          {'n': 1,
           'scriptPubKey': {'addresses': ['tb1qrcf8c29966tvqxhwrtd2se3rj6jeqtll3r46a4'],
                            'asm': '0 1e127c28a5d696c01aee1adaa8662396a5902fff',
                            'hex': '00141e127c28a5d696c01aee1adaa8662396a5902fff',
                            'reqSigs': 1,
                            'type': 'witness_v0_keyhash'},
           'value': Decimal('0.01029734')}],
 'vsize': 144,
 'weight': 573}
---------------------------------------------------------------

Crindo um Endereço

Criar um novo endereço com Python 3 requer apenas o uso de um RPC como getnewaddress ou getrawchangeaddress.

new_address = rpc_client.getnewaddress("Learning-Bitcoin-from-the-Command-Line")
new_change_address = rpc_client.getrawchangeaddress()

Neste exemplo, usamos o comando getnewaddress junto com um argumento: o rótulo Learning-Bitcoin-from-the-Command-Line.

Enviando uma Transação

A criação de uma transação no Python 3 requer a combinação de alguns dos exemplos anteriores, de criação de endereços e de recuperação de UTXOs, com alguns comandos novos do RPC para criar, assinar e enviar uma transação, da mesma forma que fizemos anteriormente na linha de comando.

Existem cinco etapas:

  1. Criar dois endereços, um que funcionará como destinatário e outro para o troco;
  2. Selecionar um UTXO e definir os detalhes da transação;
  3. Criar uma transação bruta;
  4. Assinar a transação bruta com a chave privada do UTXO;
  5. Transmitir a transação na Testnet do bitcoin.

1. Selecionando o UTXO e Definindo os Detalhes da Transação

No trecho de código a seguir, primeiro selecionaremos o UTXO que gostaríamos de gastar. Em seguida, iremos obter o endereço, id da transação e o índice vetorial da saída.

utxos = rpc_client.listunspent()
selected_utxo = utxos[0]  # novamente, selecionando o primeiro utxo aqui
utxo_address = selected_utxo['address']
utxo_txid = selected_utxo['txid']
utxo_vout = selected_utxo['vout']
utxo_amt = float(selected_utxo['amount'])

Em seguida, também recuperamos o endereço do destinatário para o qual desejamos enviar os bitcoins, calculamos a quantidade de bitcoins que desejamos enviar e calculamos a taxa de minerador e o valor do troco. Aqui, o valor é dividido arbitrariamente em dois e uma taxa de minerador é definida arbitrariamente.

recipient_address = new_address
recipient_amt = utxo_amt / 2  # enviando metade das moedas para o recebedor
miner_fee = 0.00000300        # escolha uma taxa apropriada baseada no tamanho da sua transação
change_address = new_change_address
change_amt = float('%.8f'%((utxo_amt - recipient_amt) - miner_fee))

⚠️ AVISO: Obviamente, um programa real faria escolhas mais sofisticadas sobre qual UTXO usar, o que fazer com os fundos e qual taxa de mineração pagar.

2. Criando uma Transação Bruta

Agora temos todas as informações para enviar uma transação, mas antes de enviá-la, devemos criá-la.

txids_vouts = [{"txid": utxo_txid, "vout": utxo_vout}]
addresses_amts = {f"{recipient_address}": recipient_amt, f"{change_address}": change_amt}
unsigned_tx_hex = rpc_client.createrawtransaction(txids_vouts, addresses_amts)

Lembre-se de que o formato do comando createrawtransaction é:

$ bitcoin-cli createrawtransaction '[{"txid": <utxo_txid>, "vout": <vector_id>}]' '{"<address>": <amount>}'

O txids_vouts é, portanto, uma lista, e o address_amts é um dicionário python, para combinar com o formato de createrawtransaction.

Se quisermos ver mais sobre os detalhes da transação que criamos, podemos usar decoderawtransaction, tanto no Python 3 quanto com bitcoin-cli.

3. Assinar Transação Bruta

Assinar uma transação geralmente é a parte mais complicada do envio de uma transação, quando olhamos a parte de programação. Aqui recuperamos uma chave privada de um endereço com dumpprivkey e a colocamos em uma matriz:

address_priv_key = []  # list of priv keys of each utxo
address_priv_key.append(rpc_client.dumpprivkey(utxo_address))

Depois, usamos esse array, que deve conter as chaves privadas de cada UTXO que está sendo gasto, para assinar nosso unsigned_tx_hex:

signed_tx = rpc_client.signrawtransactionwithkey(unsigned_tx_hex, address_priv_key)

Isso retornará um objeto JSON com o hex da transação assinada e se foi assinado completamente ou não:

4. Transmitindo a Transação

Finalmente, estamos prontos para transmitir a transação assinada na rede Bitcoin:

send_tx = rpc_client.sendrawtransaction(signed_tx['hex'])

Executando Nosso Código

Este código está cheio de instruções com print para demonstrar todos os dados disponíveis a cada momento:

$ python3 sendtx.py 
Creating a Transaction
---------------------------------------------------------------
Transaction Details:
-------------------
UTXO Address.......:  mv9cjEnS2o1EygBMdrz99LzhM8KeEMoXDg
UTXO Txid..........:  84207ffec658ae29ad1fdd330d8a13613303c3cf281ce628fadeb7636ffb535e
Vector ID of Output:  0
UTXO Amount........:  1e-05
Tx Amount..........:  5e-06
Recipient Address..:  tb1qca0elxxqzw5xc0s3yq5qhapzzj90ka0zartu6y
Change Address.....:  tb1qrveukqrvqm9h6fua99xvcxgnvdx507dg8e8hrt
Miner Fee..........:  3e-06
Change Amount......:  2e-06
---------------------------------------------------------------

---------------------------------------------------------------
Unsigned Transaction Hex:  02000000015e53fb6f63b7defa28e61c28cfc3033361138a0d33dd1fad29ae58c6fe7f20840000000000ffffffff02f401000000000000160014c75f9f98c013a86c3e1120280bf422148afb75e2c8000000000000001600141b33cb006c06cb7d279d294ccc1913634d47f9a800000000
---------------------------------------------------------------

---------------------------------------------------------------
Signed Transaction: 
----------------------
{'complete': True,
 'hex': '02000000015e53fb6f63b7defa28e61c28cfc3033361138a0d33dd1fad29ae58c6fe7f2084000000006a47304402205da9b2234ea057c9ef3b7794958db6c650c72dedff1a90d2915147a5f6413f2802203756552aba0dd8ebd71b0f28341becc01b28d8b28af063d7c8ce89f9c69167f8012102d0541b9211aecd25913f7fdecfc1b469215fa326d52067b1b3f7efbd12316472ffffffff02f401000000000000160014c75f9f98c013a86c3e1120280bf422148afb75e2c8000000000000001600141b33cb006c06cb7d279d294ccc1913634d47f9a800000000'}
---------------------------------------------------------------

---------------------------------------------------------------
TXID of sent transaction:  187f8baa222f9f37841d966b6bad59b8131cfacca861cbe9bfc8656bd16a44cc

Resumo: Acessando o Bitcoind com Python

Acessar Bitcoind com Python é muito fácil usando a biblioteca python-bitcoinrpc. A primeira coisa a se fazer é estabelecer uma conexão com nossa instância bitcoind, então podemos fazer todas as chamadas da API do Bitcoin conforme descrito nos documentos do Bitcoin Core. Isso torna mais fácil criar programas pequenos ou grandes para gerenciar nosso próprio node, verificando saldos ou criando aplicações interessantes, conforme acessamos todo o poder do bitcoin-cli.

O Que Vem Depois?

Vamos aprender mais sobre "Conversando com o Bitcoind com Outras Linguagens" na seção §18.5: Acessando o Bitcoind com Rust.

Variante: Construindo Python da Fonte

Se precisarmos instalar o Python 3 a partir do código-fonte, precisaremos seguir estas instruções, e então continuar com "Criando um projeto BitcoinRPC".

1. Instalando as Dependências

$ sudo apt-get install build-essential checkinstall
$ sudo apt-get install libreadline-gplv2-dev libncursesw5-dev libssl-dev libsqlite3-dev tk-dev libgdbm-dev libc6-dev libbz2-dev libffi-dev zlib1g-dev

2. Baixando e Extraindo o Python

$ wget https://www.python.org/ftp/python/3.8.3/Python-3.8.3.tgz
$ tar -xzf Python-3.8.3.tgz

3. Compilando o Código-Fonte Python e Verificando a Instalação:

$ cd Python-3.8.3
$ sudo ./configure --enable-optimizations
$ sudo make -j 8  # coloque o número de núcleos que você quer usar do seu sistema para acelerar o processo
$ sudo make altinstall
$ python3.8 --version

Depois de obter o retorno da versão, podemos remover o arquivo de instalação:

$ rm Python-3.8.3.tgz