Mercurial > public > bitcaviar
changeset 1:f45c3c4f9932
add blockchain rpcs
author | Dennis Concepcion Martin <dennisconcepcionmartin@gmail.com> |
---|---|
date | Sat, 09 Oct 2021 12:45:03 +0200 |
parents | 32ce869b7f82 |
children | db53bb130244 |
files | README.md src/pybitcoin/blockchain.py src/pybitcoin/config.py src/pybitcoin/helpers.py tests/test_blockchain.py tests/test_helpers.py |
diffstat | 6 files changed, 643 insertions(+), 5 deletions(-) [+] |
line wrap: on
line diff
--- a/README.md Thu Oct 07 10:21:21 2021 +0200 +++ b/README.md Sat Oct 09 12:45:03 2021 +0200 @@ -1,2 +1,2 @@ # pybitcoin -A simple Python wrapper for Bitcoin JSON-RPC API \ No newline at end of file +A simple Python wrapper for Bitcoin JSON-RPC API. \ No newline at end of file
--- a/src/pybitcoin/blockchain.py Thu Oct 07 10:21:21 2021 +0200 +++ b/src/pybitcoin/blockchain.py Sat Oct 09 12:45:03 2021 +0200 @@ -1,3 +1,446 @@ -"""Blockchain RPCs""" +""" +Blockchain RPCs +More info: https://developer.bitcoin.org/reference/rpc/ +""" + +import json +from src.pybitcoin.helpers import run + + +def get_best_block_hash(bitcoin): + """ + Get the hash of the best (tip) block in the most-work fully-validated chain + More info: https://developer.bitcoin.org/reference/rpc/getbestblockhash.html + :param bitcoin: src.pybitcoin.config.Bitcoin, required + :return: string + """ + + command = [bitcoin.cli_dir, bitcoin.data_dir, 'getbestblockhash'] + best_block_hash = run(command) + best_block_hash = best_block_hash.rstrip() + + return best_block_hash + + +def get_block(bitcoin, blockhash, verbosity=1): + """ + Get block data + More info: https://developer.bitcoin.org/reference/rpc/getblock.html + :param blockhash: string, required + :param verbosity: int, optional, default=1 + :param bitcoin: src.pybitcoin.config.Bitcoin, required + :return: if verbosity=0 returns string, else returns dict + """ + + command = [bitcoin.cli_dir, bitcoin.data_dir, 'getblock', blockhash, str(verbosity)] + block = run(command) + + if verbosity == 1 or verbosity == 2: + block = json.loads(block) + + return block + + +def get_blockchain_info(bitcoin): + """ + Get an object containing various state info regarding blockchain processing + More info: https://developer.bitcoin.org/reference/rpc/getblockchaininfo.html + :param bitcoin: src.pybitcoin.config.Bitcoin, required + :return: dict + """ + + command = [bitcoin.cli_dir, bitcoin.data_dir, 'getblockchaininfo'] + blockchain_info = run(command) + blockchain_info = json.loads(blockchain_info) + + return blockchain_info + + +def get_block_count(bitcoin): + """ + Get the height of the most-work fully-validated chain + More info: https://developer.bitcoin.org/reference/rpc/getblockcount.html + :param bitcoin: src.pybitcoin.config.Bitcoin, required + :return: int + """ + + command = [bitcoin.cli_dir, bitcoin.data_dir, 'getblockcount'] + block_count = run(command) + block_count = int(block_count) + + return block_count + + +def get_block_filter(bitcoin, block_hash, filter_type='basic'): + """ + Get a BIP 157 content filter for a particular block. + To enable the compact block filter, you need to start bitcoind with the -blockfilterindex=basic + (or simply -blockfilterindex) command line option, or put that option in your bitcoin.conf file + More info: https://developer.bitcoin.org/reference/rpc/getblockfilter.html + :param bitcoin: src.pybitcoin.config.Bitcoin, required + :param block_hash: string, required + :param filter_type: string, optional, default=basic + :return: dict + """ + + command = [bitcoin.cli_dir, bitcoin.data_dir, 'getblockfilter', block_hash, filter_type] + block_filter = run(command) + block_filter = json.loads(block_filter) + + return block_filter + + +def get_block_hash(bitcoin, height): + """ + Get hash of block in best-block-chain at height provided + More info: https://developer.bitcoin.org/reference/rpc/getblockhash.html + :param bitcoin: src.pybitcoin.config.Bitcoin, required + :param height: int, required + :return: string + """ + + command = [bitcoin.cli_dir, bitcoin.data_dir, 'getblockhash', str(height)] + block_hash = run(command) + block_hash = block_hash.rstrip() + + return block_hash + + +def get_block_header(bitcoin, block_hash, verbose=True): + """ + Get block header information + More info: https://developer.bitcoin.org/reference/rpc/getblockheader.html + :param bitcoin: src.pybitcoin.config.Bitcoin, required + :param block_hash: string, required + :param verbose: boolean, optional, default=True + :return: if verbose=false returns string, else returns dict + """ + + command = [bitcoin.cli_dir, bitcoin.data_dir, 'getblockheader', block_hash, str(verbose).lower()] + block_header = run(command) + + if verbose: + block_header = json.loads(block_header) + + return block_header + + +def get_block_stats(bitcoin, hash_or_height, stats='all'): + """ + Compute per block statistics for a given window. All amounts are in satoshis. + It won’t work for some heights with pruning. + More info: https://developer.bitcoin.org/reference/rpc/getblockstats.html + :param bitcoin: src.pybitcoin.config.Bitcoin, required + :param hash_or_height: string, required + :param stats: list of strings, optional, default=all values + :return: dict + """ + + if len(hash_or_height) == 64: # It's a hash + hash_or_height = json.dumps(hash_or_height) + + if stats == 'all': + command = [bitcoin.cli_dir, bitcoin.data_dir, 'getblockstats', hash_or_height] + else: + stats = json.dumps(stats) + command = [bitcoin.cli_dir, bitcoin.data_dir, 'getblockstats', hash_or_height, stats] + + block_stats = run(command) + block_stats = json.loads(block_stats) + + return block_stats + + +def get_chain_tips(bitcoin): + """ + Get information about all known tips in the block tree, including the main chain as well as orphaned branches + More info: https://developer.bitcoin.org/reference/rpc/getchaintips.html + :param bitcoin: src.pybitcoin.config.Bitcoin, required + :return: list of dicts + """ + + command = [bitcoin.cli_dir, bitcoin.data_dir, 'getchaintips'] + chain_tips = run(command) + chain_tips = json.loads(chain_tips) + + return chain_tips + + +# noinspection PyIncorrectDocstring +def get_chain_tx_stats(bitcoin, nblocks=None): + # noinspection PyUnresolvedReferences + """ + Get statistics about the total number and rate of transactions in the chain + More info: https://developer.bitcoin.org/reference/rpc/getchaintxstats.html + :param bitcoin: src.pybitcoin.config.Bitcoin, required + :param nblocks: int, optional, default=one month + :param blockhash: currently not supported + :return: dict + """ + + if nblocks: + command = [bitcoin.cli_dir, bitcoin.data_dir, 'getchaintxstats', str(nblocks)] + else: + command = [bitcoin.cli_dir, bitcoin.data_dir, 'getchaintxstats'] + + chain_tx_stats = run(command) + chain_tx_stats = json.loads(chain_tx_stats) + + return chain_tx_stats + + +def get_difficulty(bitcoin): + """ + Get the proof-of-work difficulty as a multiple of the minimum difficulty + More info: https://developer.bitcoin.org/reference/rpc/getdifficulty.html + :param bitcoin: src.pybitcoin.config.Bitcoin, required + :return: string + """ + + command = [bitcoin.cli_dir, bitcoin.data_dir, 'getdifficulty'] + difficulty = run(command) + + return difficulty + + +def get_mempool_ancestors(bitcoin, txid, verbose=False): + """ + Get all in-mempool ancestors if txid is in the mempool + More info: https://developer.bitcoin.org/reference/rpc/getmempoolancestors.html + :param bitcoin: src.pybitcoin.config.Bitcoin, required + :param txid: string, required + :param verbose: boolean, optional, default=False + :return: if verbose=False returns list of dicts, else dict + """ + + command = [bitcoin.cli_dir, bitcoin.data_dir, 'getmempoolancestors', txid, str(verbose).lower()] + mempool_ancestors = run(command) + mempool_ancestors = json.loads(mempool_ancestors) + + return mempool_ancestors + -import subprocess +def get_mempool_descendants(bitcoin, txid, verbose=False): + """ + Get all in-mempool descendants if txid is in the mempool + More info: https://developer.bitcoin.org/reference/rpc/getmempooldescendants.html + :param bitcoin: src.pybitcoin.config.Bitcoin, required + :param txid: string, required + :param verbose: boolean, optional, default=False + :return: if verbose=False returns list, else dict + """ + + command = [bitcoin.cli_dir, bitcoin.data_dir, 'getmempooldescendants', txid, str(verbose).lower()] + mempool_descendants = run(command) + mempool_descendants = json.loads(mempool_descendants) + + return mempool_descendants + + +def get_mempool_entry(bitcoin, txid): + """ + Get mempool data for given transaction + The transaction id must be in mempool + More info: https://developer.bitcoin.org/reference/rpc/getmempoolentry.html + :param bitcoin: src.pybitcoin.config.Bitcoin, required + :param txid: string, required + :return: dict + """ + + command = [bitcoin.cli_dir, bitcoin.data_dir, 'getmempoolentry', txid] + mempool_entry = run(command) + mempool_entry = json.loads(mempool_entry) + + return mempool_entry + + +def get_mempool_info(bitcoin): + """ + Get details on the active state of the TX memory pool + :param bitcoin: + :return: dict + """ + + command = [bitcoin.cli_dir, bitcoin.data_dir, 'getmempoolinfo'] + mempool_info = run(command) + mempool_info = json.loads(mempool_info) + + return mempool_info + + +def get_raw_mempool(bitcoin, verbose=False, mempool_sequence=False): + """ + Get all transaction ids in memory pool + More info: https://developer.bitcoin.org/reference/rpc/getrawmempool.html + :param bitcoin: src.pybitcoin.config.Bitcoin, required + :param verbose: boolean, optional, default=False + :param mempool_sequence: boolean, optional, default=False + :return: if verbose=False returns list, else dict + """ + + command = [bitcoin.cli_dir, bitcoin.data_dir, 'getrawmempool', str(verbose).lower(), str(mempool_sequence).lower()] + raw_mempool = run(command) + raw_mempool = json.loads(raw_mempool) + + return raw_mempool + + +def get_tx_out(bitcoin, txid, n, include_mempool=True): + """ + Get details about an unspent transaction output + More info: https://developer.bitcoin.org/reference/rpc/gettxout.html + :param bitcoin: src.pybitcoin.config.Bitcoin, required + :param txid: string, required + :param n: int, required + :param include_mempool: boolean, optional, default=true + :return: dict + """ + + command = [bitcoin.cli_dir, bitcoin.data_dir, 'gettxout', txid, str(n), str(include_mempool).lower()] + tx_out = run(command) + + if tx_out: + tx_out = json.loads(tx_out) + else: + tx_out = {'message': 'no unspent transaction'} + + return tx_out + + +def get_tx_out_proof(bitcoin, txids, blockhash=None): + """ + Get a hex-encoded proof that “txid” was included in a block + NOTE: By default this function only works sometimes. This is when there is an unspent output in the utxo for + this transaction. To make it always work, you need to maintain a transaction index, using the -txindex command + line option or specify the block in which the transaction is included manually (by blockhash) + More info: https://developer.bitcoin.org/reference/rpc/gettxoutproof.html + :param bitcoin: src.pybitcoin.config.Bitcoin, required + :param txids: list, required + :param blockhash: string, optional + :return: string + """ + + txids = json.dumps(txids) # Convert list to json array + + if blockhash: + command = [bitcoin.cli_dir, bitcoin.data_dir, 'gettxoutproof', txids, blockhash] + else: + command = [bitcoin.cli_dir, bitcoin.data_dir, 'gettxoutproof', txids] + + tx_out_proof = run(command) + tx_out_proof = tx_out_proof.rstrip() + + return tx_out_proof + + +def get_tx_out_set_info(bitcoin, hash_type=None): + """ + Get statistics about the unspent transaction output set + Note this call may take some time + More info: https://developer.bitcoin.org/reference/rpc/gettxoutsetinfo.html + :param bitcoin: src.pybitcoin.config.Bitcoin, required + :param hash_type: string, optional, default=hash_serialized_2 + :return: dict + """ + + if hash_type: + command = [bitcoin.cli_dir, bitcoin.data_dir, 'gettxoutsetinfo', hash_type] + else: + command = [bitcoin.cli_dir, bitcoin.data_dir, 'gettxoutsetinfo'] + + tx_out_set_info = run(command) + tx_out_set_info = json.loads(tx_out_set_info) + + return tx_out_set_info + + +def get_precious_block(bitcoin, blockhash): + """ + Treats a block as if it were received before others with the same work. + A later precious block call can override the effect of an earlier one. + The effects of precious block are not retained across restarts. + More info: https://developer.bitcoin.org/reference/rpc/preciousblock.html + :param bitcoin: src.pybitcoin.config.Bitcoin, required + :param blockhash: string, required + :return: None + """ + + command = [bitcoin.cli_dir, bitcoin.data_dir, 'preciousblock', blockhash] + precious_block = run(command) + + return precious_block + + +def prune_blockchain(bitcoin, height): + """ + Get prune blockchain height + You have to set your node into prune mode to make this call work. + More info: https://developer.bitcoin.org/reference/rpc/pruneblockchain.html + :param bitcoin: src.pybitcoin.config.Bitcoin, required + :param height: int, required + :return: string + """ + + command = [bitcoin.cli_dir, bitcoin.data_dir, 'pruneblockchain', str(height)] + prune_blockchain_height = run(command) + prune_blockchain_height = int(prune_blockchain_height) + + return prune_blockchain_height + + +def save_mempool(bitcoin): + """ + Dumps the mempool to disk. It will fail until the previous dump is fully loaded + More info: https://developer.bitcoin.org/reference/rpc/savemempool.html + :param bitcoin: src.pybitcoin.config.Bitcoin, required + :return: None + """ + + command = [bitcoin.cli_dir, bitcoin.data_dir, 'savemempool'] + run(command) + + return None + + +""" +Currently not supported scantxoutset: https://developer.bitcoin.org/reference/rpc/scantxoutset.html +""" + + +def verify_chain(bitcoin, checklevel=3, nblocks=6): + """ + Verifies blockchain database + More info: https://developer.bitcoin.org/reference/rpc/verifychain.html + :param bitcoin: src.pybitcoin.config.Bitcoin, required + :param checklevel: int, optional, default=3, range=0-4 + :param nblocks: int, optional, default=6, 0=all + :return: boolean + """ + + command = [bitcoin.cli_dir, bitcoin.data_dir, 'verifychain', str(checklevel), str(nblocks)] + verification = run(command).rstrip() + + if verification == 'true': + verification = True + elif verification == 'false': + verification = False + + return verification + + +def verify_tx_out_proof(bitcoin, proof): + """ + Get the txid(s) which the proof commits to + Verifies that a proof points to a transaction in a block, returning the transaction it commits to and throwing + an RPC error if the block is not in our best chain + More info: https://developer.bitcoin.org/reference/rpc/verifytxoutproof.html + :param bitcoin: src.pybitcoin.config.Bitcoin, required + :param proof: string, required + :return: list + """ + + command = [bitcoin.cli_dir, bitcoin.data_dir, 'verifytxoutproof', proof] + txids = run(command) + txids = json.loads(txids) + + return txids
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pybitcoin/config.py Sat Oct 09 12:45:03 2021 +0200 @@ -0,0 +1,11 @@ +class Bitcoin: + """ + Store the directory of bitcoin-cli and where the blockchain data is. + """ + def __init__(self, cli_dir, data_dir): + """ + :param cli_dir: string, required + :param data_dir: string, required + """ + self.cli_dir = cli_dir + self.data_dir = '-datadir=' + data_dir
--- a/src/pybitcoin/helpers.py Thu Oct 07 10:21:21 2021 +0200 +++ b/src/pybitcoin/helpers.py Sat Oct 09 12:45:03 2021 +0200 @@ -6,13 +6,13 @@ def run(command): """ Execute shell command - :param command: string, required + :param command: list, required :return: string """ output = subprocess.run(command, capture_output=True, text=True) if output.returncode != 0: # An error occurred - return 'error: ' + output.stderr + raise ValueError(output.stderr) else: return output.stdout
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test_blockchain.py Sat Oct 09 12:45:03 2021 +0200 @@ -0,0 +1,173 @@ +from unittest import TestCase +from src.pybitcoin.blockchain import * +from src.pybitcoin import config + + +class TestBlockchain(TestCase): + bitcoin = config.Bitcoin(cli_dir='bitcoin-cli', data_dir='/Users/dennis/Bitcoin') + + def test_get_best_block_hash(self): + best_block_hash = get_best_block_hash(bitcoin=self.bitcoin) + self.assertIsInstance(best_block_hash, str) + + def test_get_block(self): + block_hash = '00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09' + block = get_block(bitcoin=self.bitcoin, blockhash=block_hash, verbosity=0) + self.assertIsInstance(block, str) + + block = get_block(bitcoin=self.bitcoin, blockhash=block_hash) + self.assertIsInstance(block, dict) + + block = get_block(bitcoin=self.bitcoin, blockhash=block_hash, verbosity=2) + self.assertIsInstance(block, dict) + + def test_get_blockchain_info(self): + blockchain_info = get_blockchain_info(bitcoin=self.bitcoin) + self.assertIsInstance(blockchain_info, dict) + + def test_get_block_count(self): + block_count = get_block_count(bitcoin=self.bitcoin) + self.assertIsInstance(block_count, int) + + def test_get_block_filter(self): + block_hash = '00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09' + block_filter = get_block_filter(bitcoin=self.bitcoin, block_hash=block_hash) + self.assertIsInstance(block_filter, dict) + + def test_get_block_hash(self): + block_hash = get_block_hash(bitcoin=self.bitcoin, height='1000') + self.assertIsInstance(block_hash, str) + + def test_get_block_header(self): + block_hash = '00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09' + block_header = get_block_header(bitcoin=self.bitcoin, block_hash=block_hash) + self.assertIsInstance(block_header, dict) + + block_header = get_block_header(bitcoin=self.bitcoin, block_hash=block_hash, verbose=False) + self.assertIsInstance(block_header, str) + + def test_get_block_stats(self): + block_hash = '00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09' + block_height = '1000' + stats = ['avgfeerate'] + + block_stats = get_block_stats(bitcoin=self.bitcoin, hash_or_height=block_hash) + self.assertIsInstance(block_stats, dict) + + block_stats = get_block_stats(bitcoin=self.bitcoin, hash_or_height=block_height) + self.assertIsInstance(block_stats, dict) + + block_stats = get_block_stats(bitcoin=self.bitcoin, hash_or_height=block_hash, stats=stats) + self.assertIsInstance(block_stats, dict) + + block_stats = get_block_stats(bitcoin=self.bitcoin, hash_or_height=block_height, stats=stats) + self.assertIsInstance(block_stats, dict) + + def test_get_chain_tips(self): + chain_tips = get_chain_tips(bitcoin=self.bitcoin) + self.assertIsInstance(chain_tips, list) + self.assertIsInstance(chain_tips[0], dict) + + def test_get_chain_tx_stats(self): + chain_tx_stats = get_chain_tx_stats(bitcoin=self.bitcoin) + self.assertIsInstance(chain_tx_stats, dict) + + chain_tx_stats = get_chain_tx_stats(bitcoin=self.bitcoin, nblocks=10) + self.assertIsInstance(chain_tx_stats, dict) + + def test_get_difficulty(self): + difficulty = get_difficulty(bitcoin=self.bitcoin) + self.assertIsInstance(difficulty, str) + + def test_get_mempool_ancestors(self): + raw_mempool = get_raw_mempool(bitcoin=self.bitcoin) + txid = raw_mempool[50] + + mempool_ancestors = get_mempool_ancestors(bitcoin=self.bitcoin, txid=txid) + self.assertIsInstance(mempool_ancestors, list) + + mempool_ancestors = get_mempool_ancestors(bitcoin=self.bitcoin, txid=txid, verbose=True) + self.assertIsInstance(mempool_ancestors, dict) + + def test_get_mempool_descendants(self): + raw_mempool = get_raw_mempool(bitcoin=self.bitcoin) + txid = raw_mempool[50] + + mempool_descendants = get_mempool_descendants(bitcoin=self.bitcoin, txid=txid) + self.assertIsInstance(mempool_descendants, list) + + mempool_descendants = get_mempool_descendants(bitcoin=self.bitcoin, txid=txid, verbose=True) + self.assertIsInstance(mempool_descendants, dict) + + def test_get_mempool_entry(self): + raw_mempool = get_raw_mempool(bitcoin=self.bitcoin) + txid = raw_mempool[50] + + mempool_entry = get_mempool_entry(bitcoin=self.bitcoin, txid=txid) + self.assertIsInstance(mempool_entry, dict) + + def test_get_mempool_info(self): + mempool_info = get_mempool_info(bitcoin=self.bitcoin) + self.assertIsInstance(mempool_info, dict) + + def test_get_raw_mempool(self): + raw_mempool = get_raw_mempool(bitcoin=self.bitcoin) + self.assertIsInstance(raw_mempool, list) + + raw_mempool = get_raw_mempool(bitcoin=self.bitcoin, verbose=True) + self.assertIsInstance(raw_mempool, dict) + + raw_mempool = get_raw_mempool(bitcoin=self.bitcoin, mempool_sequence=True) + self.assertIsInstance(raw_mempool, dict) + + def test_get_tx_out(self): + raw_mempool = get_raw_mempool(bitcoin=self.bitcoin) + txid = raw_mempool[50] + + tx_out = get_tx_out(bitcoin=self.bitcoin, txid=txid, n=2) + self.assertIsInstance(tx_out, dict) + + def test_get_tx_out_proof(self): + # noinspection DuplicatedCode + block_count = get_block_count(bitcoin=self.bitcoin) + block_hash = get_block_hash(bitcoin=self.bitcoin, height=block_count) + block = get_block(bitcoin=self.bitcoin, blockhash=block_hash) + txid = block['tx'][0] + + tx_out_proof = get_tx_out_proof(bitcoin=self.bitcoin, txids=[txid]) + self.assertIsInstance(tx_out_proof, str) + + tx_out_proof = get_tx_out_proof(bitcoin=self.bitcoin, txids=[txid], blockhash=block_hash) + self.assertIsInstance(tx_out_proof, str) + + def test_get_tx_out_set_info(self): + tx_out_set_info = get_tx_out_set_info(bitcoin=self.bitcoin) + self.assertIsInstance(tx_out_set_info, dict) + + def test_get_precious_block(self): + block_count = get_block_count(bitcoin=self.bitcoin) + block_hash = get_block_hash(bitcoin=self.bitcoin, height=block_count) + precious_block = get_precious_block(bitcoin=self.bitcoin, blockhash=block_hash) + print(precious_block) + + def test_prune_blockchain(self): + # To test it node must be in prune mode (delete old blocks) + pass + + def test_save_mempool(self): + save_mempool(bitcoin=self.bitcoin) + + def test_verify_chain(self): + verification = verify_chain(bitcoin=self.bitcoin) + self.assertIsInstance(verification, bool) + + def test_verify_tx_out_proof(self): + # noinspection DuplicatedCode + block_count = get_block_count(bitcoin=self.bitcoin) + block_hash = get_block_hash(bitcoin=self.bitcoin, height=block_count) + block = get_block(bitcoin=self.bitcoin, blockhash=block_hash) + txid = block['tx'][0] + tx_out_proof = get_tx_out_proof(bitcoin=self.bitcoin, txids=[txid]) + + txids = verify_tx_out_proof(bitcoin=self.bitcoin, proof=tx_out_proof) + self.assertIsInstance(txids, list)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test_helpers.py Sat Oct 09 12:45:03 2021 +0200 @@ -0,0 +1,11 @@ +from unittest import TestCase +from src.pybitcoin import helpers +from src.pybitcoin import config + + +class TestHelpers(TestCase): + bitcoin = config.Bitcoin(cli_dir='bitcoin-cli', data_dir='/Users/dennis/Bitcoin') + + def test_run(self): + output = helpers.run(command=[self.bitcoin.cli_dir, self.bitcoin.data_dir, 'getblockhash', '1']) + self.assertIsInstance(output, str)