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'
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
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
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
Container runs! Then immediately dies. Great.
2022 sudo docker ps
# (empty. of course.)
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)
I went through this loop so many times that I think I achieved some kind of meditative state. The five stages of Docker grief:
- Denial: "It'll work this time, I just need one more flag"
- Anger: "WHY WON'T YOU STAY RUNNING"
- Bargaining: "Please... I'll use any image... just work..."
- Depression: stares at terminal for 3 minutes
- 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
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.
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!
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.
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
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?
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}")
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?!
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]}...")
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']}")
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")
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
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:
- Show blockchain info (chain type, block height, difficulty)
- Check wallet balances (Alice had 101 BTC, Bob had... nothing. Poor Bob.)
- List recent transactions (with correct IN/OUT directions!)
- Decode any transaction (peer into the matrix)
- Display block details (hash, height, time, tx count)
- 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)