DEV Community

Cover image for Day 2: Bitcoin Explorer - The "I Should Have Just Used Bitcoin-CLI" Edition
Samuel Thuku
Samuel Thuku

Posted on

Day 2: Bitcoin Explorer - The "I Should Have Just Used Bitcoin-CLI" Edition

Previous day's adventure


Morning: Docker Debacles

So there I was, bright-eyed and bushy-tailed, ready to build a Bitcoin Explorer. The PDF said "just use Bitcoin Core." Simple. Elegant. Boring.

Where's the fun in that?

Me, apparently: "You know what would make this 100x harder? Docker."

My inner voice, screaming: "NOBODY SAYS THAT."

But I couldn't help myself. I love creating problems where none exist. It's my superpower. And my curse.

Let me walk you through the hour I spent trying to get a Bitcoin node running in Docker, as documented by my increasingly unhinged command history:

2015  E: Unable to locate package doker.io
2016  E: Couldn't find any package by glob 'doker.io'
Enter fullscreen mode Exit fullscreen mode

Note to self: "docker" has a 'c' in it. Remember this for next time. (I won't.)

After finally installing Docker correctly (spelling matters, who knew?), I pulled the image:

2019  sudo docker pull lncm/bitcoind:v27.0
Enter fullscreen mode Exit fullscreen mode

Then came the first attempt at running it:

2020  docker run -d --name bitcoin-node -p 18443:18443 lncm/bitcoind:v27.0 bitcoind -regtest -rpcbind=0.0.0.0 -rpcallowip=0.0.0.0/0
Enter fullscreen mode Exit fullscreen mode

Permission denied. Of course.

2021  sudo docker run -d --name bitcoin-node -p 18443:18443 lncm/bitcoind:v27.0 bitcoind -regtest -rpcbind=0.0.0.0 -rpcallowip=0.0.0.0/0
Enter fullscreen mode Exit fullscreen mode

Container runs! Then immediately dies. Great.

2022  sudo docker ps
# (empty. of course.)
Enter fullscreen mode Exit fullscreen mode

This pattern repeated. A lot. Like, a lot a lot.

2023  sudo docker run... (dies)
2024  sudo docker start bitcoin-node (fails)
2025  sudo docker rm -f bitcoin-node (rage delete)
2026  sudo docker run... (dies again)
Enter fullscreen mode Exit fullscreen mode

I went through this loop so many times that I think I achieved some kind of meditative state. The five stages of Docker grief:

  1. Denial: "It'll work this time, I just need one more flag"
  2. Anger: "WHY WON'T YOU STAY RUNNING"
  3. Bargaining: "Please... I'll use any image... just work..."
  4. Depression: stares at terminal for 3 minutes
  5. Acceptance: "Fine, I'll try a different image"
2029  sudo docker run -d --name bitcoin-node -p 18443:18443 lncm/bitcoind:v27.0 bitcoind -regtest -rpcbind=0.0.0.0 -rpcallowip=0.0.0.0/0 -rpcuser=user -rpcpassword=pass
2030  sudo docker logs bitcoin-node
# Error: No such file or directory - something about config
Enter fullscreen mode Exit fullscreen mode

At some point I switched to the ruimarinho/bitcoin-core image, then the official bitcoin/bitcoin image. Same problems, different flavors.

The actual issue? The command arguments were in the wrong place. The bitcoind part needed to be after the image name, not before. And the flags needed to be passed correctly. And I needed to accept that I'd wasted 45 minutes on something that should have taken 2.

2064  sudo docker run -d --name bitcoin-node -p 18443:18443 bitcoin/bitcoin:27.0 -regtest=1 -rpcbind=0.0.0.0 -rpcallowip=0.0.0.0/0 -rpcuser=user -rpcpassword=pass -server=1
2065  sudo docker ps
# FINALLY. A running container.
Enter fullscreen mode Exit fullscreen mode

Victory dance performed. Neighbors concerned.


The Cookie Crisis

But wait! The adventure wasn't over. Now I needed to talk to this container.

2071  docker exec bitcoin-node bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass createwallet "dev_wallet"
# works!
2074  sudo docker exec bitcoin-node bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass getblockchaininfo
# returns something!
Enter fullscreen mode Exit fullscreen mode

But then... I got lazy. Or forgetful. Or both. (Spoiler: both.)

Instead of using the -rpcuser and -rpcpassword flags every time, I thought I'd set up a bitcoin.conf file. Classic mistake - assuming configuration works on the first try.

2085  nano ~/.bashrc
# added some aliases. felt powerful.
2091  nano ~/.bitcoin/bitcoin.conf
# added rpcuser=user, rpcpassword=pass, regtest=1
2092  bitcoin-cli getblockchaininfo
# Error: Could not locate RPC credentials. No authentication cookie found.
Enter fullscreen mode Exit fullscreen mode

Turns out, the Docker container doesn't magically know about my local bitcoin.conf. It has its own filesystem. Its own config. Its own existential crisis about why it was created.

Moral of the story: Just use the flags. Or mount a volume. Or, you know, don't use Docker at all like the PDF suggested. BUT WHERE'S THE FUN IN THAT?


Finally, Some Actual Coding

After the Docker disaster, I needed to write actual Python. Remember the PDF? The challenges?

I opened explorer_starter.py and started implementing. The RPC helper was already there (bless the bootcamp authors), but it had a small problem:

resp = requests.post(url, data=data, auth=("user", "pass"))
# Missing .json() and return statement
Enter fullscreen mode Exit fullscreen mode

Fixed that. Then came the challenges.

Challenge 1: Blockchain Info (Easy mode)

def show_blockchaininfo():
    info = rpc("getblockchaininfo")
    print(f"Blocks: {info['difficulty']}")  # Wait, difficulty? Not blocks?
Enter fullscreen mode Exit fullscreen mode

Um. That's not what the PDF asked for. Nice try, past me. Fixed it to actually show chain, blocks, AND difficulty.

Challenge 2: Wallet Balance

def show_wallet_balance(wallet_name):
    try:
        info = rpc("getbalance", wallet=wallet_name)
        if info is not None:  # Fixed: "is not None" not "is not None"
            print(f"Balance for {wallet_name} is: {info}")
Enter fullscreen mode Exit fullscreen mode

This worked! Mostly. When the wallet was loaded. Which it wasn't, half the time.

Challenge 3: List Transactions (The Typo Trap)

This one got me good. Look closely:

if tx['category'] in ('recieve', 'generate', 'immature'):  # RECIEVE?!
Enter fullscreen mode Exit fullscreen mode

I wrote 'recieve' instead of 'receive'. My editor didn't catch it. The Python interpreter didn't catch it (it's a valid string, just the wrong one). But Bitcoin Core definitely caught it - because transactions have category 'receive', not 'recieve'.

So my "IN" transactions were always empty. Everything showed as "OUT". Including incoming money.

That's not ideal for a wallet display.

"Wait, why does Alice have negative balance? She just mined 101 blocks!"

Found it. Fixed it. Facepalmed appropriately.

Challenge 4: Decode Transaction

This was actually fun. Looking at raw transactions, seeing the inputs and outputs, understanding the structure. The coinbase transaction (mining reward) has no inputs - just a special coinbase field.

def decode_transaction(txid):
    tx = rpc("getrawtransaction", [txid, True])
    print(f"Size: {tx['size']} bytes")

    print("\nInputs")
    for vin in tx['vin']:
        if 'coinbase' in vin:
            print("COINBASE (mining reward) - money printer goes brrr")
        else:
            print(f"From: {vin['txid'][:20]}...")
Enter fullscreen mode Exit fullscreen mode

It's like being a blockchain detective. Except instead of solving crimes, you're just... looking at numbers. Glorious numbers.

Challenge 5: Block Details

def show_block(blockhash=None):
    if blockhash is None:
        blockhash = rpc("getbestblockhash")  # Get the tip if none provided
    block = rpc("getblock", [blockhash, 1])

    print(f"==== Block #{block['height']} =====")
    print(f"Hash: {block['hash'][:20]}...")
    print(f"Time: {block['time']}")
    print(f"Transactions: {block['nTx']}")
Enter fullscreen mode Exit fullscreen mode

This worked beautifully. I felt like a real blockchain explorer. Not a fake one. A legitimate one.


Bonus Feature: UTXO Explorer

The PDF had a homework suggestion: "Show UTXO set for a wallet." I decided to implement it because I'm an overachiever (and also because the transactions weren't showing up correctly and I wanted to debug).

def show_wallet_utxos(wallet_name):
    utxos = rpc("listunspent", [0, 9999999], wallet=wallet_name)

    print(f"\n--- UTXOs for {wallet_name} ---")
    total = 0
    for utxo in utxos:
        amount = utxo['amount']
        total += amount
        print(f"TxID: {utxo['txid'][:10]}... | Amount: {amount} BTC")

    print(f"Total Balance: {total} BTC")
Enter fullscreen mode Exit fullscreen mode

UTXOs (Unspent Transaction Outputs) are Bitcoin's version of "here's exactly where your money is." It's like having a wallet full of gift cards instead of a single bank balance. Each UTXO is a specific output from a specific transaction that hasn't been spent yet.

Bitcoin doesn't have "balances" - it has UTXOs. Your wallet just sums them up to show you a number. Mind blown? Mine was.


The RPC Helper Evolution

Throughout the day, my rpc() function got some upgrades:

def rpc(method, params=None, wallet=None):
    url = "http://127.0.0.1:18443/"
    if wallet:
        url = f"{url}wallet/{wallet}"

    data = json.dumps({
        "jsonrpc": "1.0", 
        "id": "explorer",
        "method": method, 
        "params": params or []
    })

    try:
        resp = requests.post(url, data=data, auth=("user", "pass")).json()
        if resp.get("error"):
            print(f"RPC Error: {resp['error']}")
            return None
        return resp["result"]
    except Exception as e:
        print(f"Connection error: {e}")
        return None
Enter fullscreen mode Exit fullscreen mode

Error handling! JSON parsing! Proper returns! It's almost production-ready. (Please don't put this in production.)


The Final Working Explorer

After all the debugging, the typo fixes, the Docker nightmares, and the existential crisis about why I chose this path, I had a working Bitcoin Explorer that could:

  1. Show blockchain info (chain type, block height, difficulty)
  2. Check wallet balances (Alice had 101 BTC, Bob had... nothing. Poor Bob.)
  3. List recent transactions (with correct IN/OUT directions!)
  4. Decode any transaction (peer into the matrix)
  5. Display block details (hash, height, time, tx count)
  6. Show UTXOs (because I'm fancy)

What I Learned Today (The Real TL;DR)

Docker is powerful but hates me personally. The container approach is great for isolation, but debugging flags and paths will make you question your life choices. If the PDF says "just run Bitcoin Core locally," maybe... just do that?

JSON-RPC is actually pretty straightforward. Send JSON, get JSON. No magic. Just data. Beautiful, predictable data.

Bitcoin transactions are just directed graphs. Inputs point to previous outputs. Outputs point to... nothing, until they become inputs. It's all connected. It's beautiful. It's also why UTXOs exist.

Typos will betray you every time. 'recieve' vs 'receive' cost me 20 minutes. Spell check your strings, people.

Reading blockchain data is empowering. Being able to query the chain, see balances, decode transactions - it feels like you're looking at the raw fabric of digital scarcity. (Okay, that's dramatic. But it is cool.)


Tonight's Homework (That I'll Definitely Do)

  • [x] Complete all 5 explorer challenges (DONE)
  • [ ] Try the multisig bonus exercise (maybe tomorrow)
  • [x] Add UTXO feature (already did, overachiever style)
  • [ ] Calculate total fees in a block (sounds fun)
  • [ ] Search transactions by address (for the next release)

A Note on the Multisig Section

The PDF mentioned 2-of-2 multisig as preparation for Lightning. The idea: Alice AND Bob must both sign to spend. Neither can steal alone. It's the foundation of trustless payment channels.

I glanced at the 03_multisig.sh exercise. Then I glanced at the clock. Then I glanced at my coffee cup (empty). Then I decided multisig is a "tomorrow problem."

Future me is going to be so annoyed at past me.


Final Stats

Metric Value
Docker containers created 12
Docker containers killed 11
Typo-induced debugging sessions 4
Times I said "why isn't this working" 47
Times the answer was "because Docker" 32
Lines of Python written ~150
UTXOs discovered Too many
Sanity remaining Minimal
Fun had Actually, quite a lot

Tomorrow: Lightning Network! Where channels open, payments route, and I inevitably break everything again.

Same bat-time, same bat-channel. (But hopefully fewer Docker commands.)


P.S. If you're doing this bootcamp and your explorer isn't working, check your wallet names. I spent 10 minutes querying "alise" before realizing I can't type. bitcoin-cli listwallets is your friend. Use it.

P.P.S. - The cookie file is at ~/.bitcoin/regtest/.cookie if you're running locally. Save yourself the pain I endured.

Top comments (0)