21 KiB
18.4: Accesso a Bitcoind con Python
ℹ️ NOTA: Questa sezione è stata aggiunta di recente al corso ed è una bozza iniziale che potrebbe essere ancora in attesa di revisione. Lettore avvisato.
Questa sezione spiega come interagire con bitcoind utilizzando il linguaggio di programmazione Python e il Python-BitcoinRPC.
Configurare Python
Se hai già installato Bitcoin Core, dovresti avere anche Python 3 disponibile. Puoi verificarlo eseguendo:
$ python3 --version
Se restituisce un numero di versione (ad es., 3.7.3 o 3.8.3) allora hai python3 installato.
Tuttavia, se in qualche modo non hai Python installato, dovrai compilarlo da sorgente come segue. Consulta la variante "Compilare Python da Sorgente" prima di continuare.
Configurare BitcoinRPC
Sia che tu abbia utilizzato un Python esistente o che lo abbia compilato da sorgente, ora sei pronto per installare la libreria python-bitcoinrpc:
$ pip3 install python-bitcoinrpc
Se non hai pip installato, dovrai eseguire il seguente comando:
$ sudo apt install python3-pip
(Poi ripeti le istruzioni pip3 install python-bitcoinrpc.)
Creare un Progetto BitcoinRPC
Generalmente dovrai includere le dichiarazioni appropriate da bitcoinrpc nei tuoi progetti Bitcoin in Python. Quanto segue ti darà accesso ai comandi basati su RPC:
from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException
Potresti anche trovare utili i seguenti:
from pprint import pprint
import logging
pprint stamperà in modo leggibile la risposta json da bitcoind.
logging stamperà la chiamata che fai a bitcoind e la risposta di bitcoind, il che è utile quando fai molte chiamate insieme. Se non vuoi l'output eccessivo nel terminale, commenta semplicemente il blocco logging.
Costruire la Tua Connessione
Ora sei pronto per iniziare a interagire con bitcoind stabilendo una connessione. Crea un file chiamato btcrpc.py e digita quanto segue:
logging.basicConfig()
logging.getLogger("BitcoinRPC").setLevel(logging.DEBUG)
# rpc_user and rpc_password are set in the bitcoin.conf file
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)
Gli argomenti nell'URL sono <rpc_username>:<rpc_password>@<host_IP_address>:<port>. Come al solito, l'user e il pass si trovano nel tuo ~/.bitcoin/bitcoin.conf, mentre l'host è il tuo localhost e la porta è 18332 per testnet. L'argomento timeout è specificato poiché i socket vanno in timeout sotto carico pesante su mainnet. Se ottieni una risposta socket.timeout: timed out, sii paziente e aumenta il timeout.
🔗 MAINNET VS TESTNET: La porta sarebbe 8332 per una configurazione mainnet.
Effettuare una Chiamata RPC
Se rpc_client è stato inizializzato con successo, sarai in grado di inviare comandi RPC al tuo nodo bitcoin.
Per utilizzare un metodo RPC da python-bitcoinrpc, utilizzerai l'oggetto rpc_client che hai creato, il quale fornisce la maggior parte delle funzionalità accessibili tramite bitcoin-cli, utilizzando gli stessi nomi dei metodi.
Ad esempio, quanto segue recupererà il blockcount del tuo nodo:
block_count = rpc_client.getblockcount()
print("---------------------------------------------------------------")
print("Block Count:", block_count)
print("---------------------------------------------------------------\n")
Dovresti vedere il seguente output con logging abilitato:
DEBUG:BitcoinRPC:-3-> getblockcount []
DEBUG:BitcoinRPC:<-3- 1773020
---------------------------------------------------------------
Block Count: 1773020
---------------------------------------------------------------
Effettuare una Chiamata RPC con Argomenti
Puoi utilizzare quel blockcount come argomento per recuperare l'hash del blocco di un blocco e anche per recuperare i dettagli di quel blocco.
Questo viene fatto inviando comandi all'oggetto rpc_client con un argomento:
blockhash = rpc_client.getblockhash(block_count)
block = rpc_client.getblock(blockhash)
Il getblockhash restituirà un singolo valore, mentre il getblock restituirà un array associativo di informazioni sul blocco, che include un array sotto block['tx'] che fornisce dettagli su ogni transazione all'interno del blocco:
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")
Eseguire il Codice
Puoi recuperare il codice sorgente ed eseguirlo con 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
---------------------------------------------------------------
Cercare Fondi
Puoi recuperare in modo simile le informazioni del tuo wallet con l'RPC getwalletinfo:
wallet_info = rpc_client.getwalletinfo()
print("---------------------------------------------------------------")
print("Wallet Info:")
print("-----------")
pprint(wallet_info)
print("---------------------------------------------------------------\n")
Dovresti avere un output simile al seguente con logging disabilitato:
---------------------------------------------------------------
#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}
---------------------------------------------------------------
Altri comandi informativi come getblockchaininfo, getnetworkinfo, getpeerinfo e getblockchaininfo funzioneranno in modo simile.
Altri comandi possono darti informazioni specifiche su elementi selezionati all'interno del tuo wallet.
Recuperare un Array
L'RPC listtransactions ti consente di visualizzare le ultime 10 transazioni sul tuo sistema (o un insieme arbitrario di transazioni utilizzando gli argomenti count e skip). Mostra come un comando RPC possa restituire un array facile da manipolare:
tx_list = rpc_client.listtransactions()
pprint(tx_list)
Esplorare un UTXO
Puoi utilizzare in modo simile listunspent per ottenere un array di UTXO sul tuo sistema:
print("Exploring UTXOs")
## List UTXOs
utxos = rpc_client.listunspent()
print("Utxos: ")
print("-----")
pprint(utxos)
print("------------------------------------------\n")
Per manipolare un array come quello restituito da listtransactions o listunspent, devi semplicemente prendere l'elemento appropriato dall'array appropriato:
## Select a UTXO - first one selected here
utxo_txid = utxos[0]['txid']
Per listunspent, ottieni un txid. Puoi recuperare informazioni al riguardo con gettransaction, quindi decodificarle con 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")
Questo codice è disponibile su 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}
---------------------------------------------------------------
Creare un Indirizzo
Creare un nuovo indirizzo con Python 3 richiede solo l'uso di un RPC come getnewaddress o getrawchangeaddress.
new_address = rpc_client.getnewaddress("Learning-Bitcoin-from-the-Command-Line")
new_change_address = rpc_client.getrawchangeaddress()
In questo esempio, dai il comando getnewaddress con un argomento: l'etichetta Learning-Bitcoin-from-the-Command-Line.
Inviare una Transazione
Creare una transazione in Python 3 richiede la combinazione di alcuni degli esempi precedenti (creare indirizzi e recuperare UTXO) con alcuni nuovi comandi RPC per creare, firmare e inviare una transazione, proprio come hai fatto in precedenza dalla riga di comando.
Ci sono cinque passaggi:
- Creare due indirizzi, uno che fungerà da destinatario e l'altro per il resto.
- Selezionare un UTXO e impostare i dettagli della transazione.
- Creare una transazione grezza.
- Firmare la transazione grezza con la chiave privata dell'UTXO.
- Trasmettere la transazione sulla testnet bitcoin.
1. Selezionare UTXO e Impostare i Dettagli della Transazione
Nel seguente frammento di codice, selezioni prima l'UTXO che vuoi spendere. Poi ottieni il suo indirizzo, l'ID della transazione e l'indice del vettore dell'output.
utxos = rpc_client.listunspent()
selected_utxo = utxos[0] # again, selecting the first utxo here
utxo_address = selected_utxo['address']
utxo_txid = selected_utxo['txid']
utxo_vout = selected_utxo['vout']
utxo_amt = float(selected_utxo['amount'])
Successivamente, recuperi anche l'indirizzo del destinatario a cui vuoi inviare i bitcoin, calcoli la quantità di bitcoin che vuoi inviare e calcoli la commissione per i miner e l'importo del resto. Qui, l'importo è diviso arbitrariamente in due e una commissione per i miner è impostata arbitrariamente.
recipient_address = new_address
recipient_amt = utxo_amt / 2 # sending half coins to recipient
miner_fee = 0.00000300 # choose appropriate fee based on your tx size
change_address = new_change_address
change_amt = float('%.8f'%((utxo_amt - recipient_amt) - miner_fee))
⚠️ ATTENZIONE: Ovviamente un programma reale farebbe scelte più sofisticate su quale UTXO utilizzare, cosa fare con i fondi e quale commissione per i miner pagare.
2. Creare Transazione Grezza
Ora hai tutte le informazioni per inviare una transazione, ma prima di poterlo fare, devi creare una transazione.
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)
Ricorda che il formato del comando createrawtransaction è:
$ bitcoin-cli createrawtransaction '[{"txid": <utxo_txid>, "vout": <vector_id>}]' '{"<address>": <amount>}'
Il txids_vouts è quindi un elenco e addresses_amts è un dizionario python, per corrispondere al formato di createrawtransaction.
Se vuoi vedere di più sui dettagli della transazione che hai creato, puoi usare decoderawtransaction, sia in Python 3 che con bitcoin-cli.
3. Firmare la Transazione Grezza
Firmare una transazione è spesso la parte più complicata dell'invio di una transazione programmaticamente. Qui recuperi una chiave privata da un indirizzo con dumpprivkey e la inserisci in un array:
address_priv_key = [] # list of priv keys of each utxo
address_priv_key.append(rpc_client.dumpprivkey(utxo_address))
Puoi quindi utilizzare quell'array (che dovrebbe contenere le chiavi private di ogni UTXO che viene speso) per firmare il tuo unsigned_tx_hex:
signed_tx = rpc_client.signrawtransactionwithkey(unsigned_tx_hex, address_priv_key)
Questo restituisce un oggetto JSON con l'hex della transazione firmata e se è stata firmata completamente o meno.
4. Trasmettere la Transazione
Infine, sei pronto per trasmettere la transazione firmata sulla rete bitcoin:
send_tx = rpc_client.sendrawtransaction(signed_tx['hex'])
Eseguire il Codice
Il codice di esempio è pieno di istruzioni print per dimostrare tutti i dati disponibili in ogni punto:
$ 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
Sommario: Accesso a Bitcoind con Python
Accedere a Bitcoind con Python è molto facile utilizzando la libreria python-bitcoinrpc. La prima cosa da fare è sempre stabilire una connessione con la tua istanza bitcoind, poi puoi chiamare tutte le API bitcoin come descritto nella documentazione di bitcoin-core. Questo rende facile creare programmi piccoli o grandi per gestire il tuo nodo, controllare i saldi o creare applicazioni interessanti sopra, accedendo alla piena potenza di bitcoin-cli.
Cosa c'è Dopo?
Scopri di più su "Parlare con Bitcoin in Altri Linguaggi" in 18.5: Accesso a Bitcoind con Rust.
Variante: Compilare Python da Sorgente
Se hai bisogno di installare Python 3 da sorgente, segui queste istruzioni, poi continua con "Creare un Progetto BitcoinRPC".
1. Installare le Dipendenze
$ 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. Scaricare e Estrarre Python
$ wget https://www.python.org/ftp/python/3.8.3/Python-3.8.3.tgz
$ tar -xzf Python-3.8.3.tgz
3. Compilare il Sorgente Python e Controllare l'Installazione:
$ cd Python-3.8.3
$ sudo ./configure --enable-optimizations
$ sudo make -j 8 # enter the number of cores of your system you want to use to speed up the build process.
$ sudo make altinstall
$ python3.8 --version
Dopo aver ottenuto l'output della versione, rimuovi il file sorgente:
$ rm Python-3.8.3.tgz