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)