<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Samuel Thuku</title>
    <description>The latest articles on DEV Community by Samuel Thuku (@samthuku).</description>
    <link>https://dev.to/samthuku</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3718513%2Fcdd3751d-640e-4630-ad24-fb7469711ea3.png</url>
      <title>DEV Community: Samuel Thuku</title>
      <link>https://dev.to/samthuku</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/samthuku"/>
    <language>en</language>
    <item>
      <title>Day 3: CLI or Polar? Learnt Lightning Both the Hard and the Easy Way</title>
      <dc:creator>Samuel Thuku</dc:creator>
      <pubDate>Thu, 11 Jun 2026 01:54:06 +0000</pubDate>
      <link>https://dev.to/samthuku/day-3-cli-or-polar-learnt-lightning-both-the-hard-and-the-easy-way-4mcg</link>
      <guid>https://dev.to/samthuku/day-3-cli-or-polar-learnt-lightning-both-the-hard-and-the-easy-way-4mcg</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.to/samthuku/day-2-bitcoin-explorer-the-i-should-have-just-used-bitcoin-cli-edition-2ig1"&gt;previous day's adventure&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Some people learn to swim by reading a book. Others get pushed into the deep end. Today was both. Somewhere in the middle, a lifeguard(Polar) showed up.&lt;/p&gt;

&lt;p&gt;Today the goal was simple on paper. Learn the Lightning Network, understand how it works and continue with the setup. The CLI came first. No shortcuts. No GUIs. Just a terminal and a lot of commands.&lt;/p&gt;

&lt;p&gt;The LND binaries for version 0.20.1-beta were downloaded and extracted. After that, &lt;code&gt;lnd&lt;/code&gt; and &lt;code&gt;lncli&lt;/code&gt; were moved into &lt;code&gt;/usr/local/bin&lt;/code&gt; so the system could actually find them.&lt;/p&gt;

&lt;p&gt;Then came the first real friction point.&lt;/p&gt;

&lt;p&gt;LND was executed, but it immediately threw errors.&lt;/p&gt;

&lt;p&gt;At first, it looked like a simple config issue. It was not.&lt;/p&gt;

&lt;p&gt;LND does not just run, it needs to be explicitly told what environment it is living in&lt;/p&gt;

&lt;p&gt;So it had to be run with the correct flags&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nt"&gt;--bitcoin&lt;/span&gt;.active
&lt;span class="nt"&gt;--bitcoin&lt;/span&gt;.regtest
&lt;span class="nt"&gt;--bitcoin&lt;/span&gt;.node&lt;span class="o"&gt;=&lt;/span&gt;bitcoind
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
`&lt;/p&gt;

&lt;p&gt;Without those, LND defaults to &lt;code&gt;btcd&lt;/code&gt; which was not even running. That mismatch alone broke everything.&lt;/p&gt;

&lt;p&gt;Once it was pointed to &lt;code&gt;bitcoind&lt;/code&gt;, things finally started behaving.&lt;/p&gt;

&lt;p&gt;That was the first real lesson LND is strict. If the environment is not described properly it refuses to guess.&lt;/p&gt;

&lt;p&gt;Next came Bitcoin Core configuration&lt;/p&gt;

&lt;p&gt;That meant editing &lt;code&gt;bitcoin.conf&lt;/code&gt; and adding ZMQ&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;bash id="wtthwk"&lt;br&gt;
zmqpubrawblock=tcp://127.0.0.1:28332&lt;br&gt;
zmqpubrawtx=tcp://127.0.0.1:28333&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The file was saved. Everything looked right. Nothing worked.&lt;/p&gt;

&lt;p&gt;LND kept failing silently until the real issue surfaced. &lt;code&gt;bitcoind&lt;/code&gt; was still running the old config.&lt;/p&gt;

&lt;p&gt;Restarting it fixed everything instantly.&lt;/p&gt;

&lt;p&gt;Three terminal windows were opened Alice, Bob and a general workspace&lt;/p&gt;

&lt;p&gt;Wallets were created&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;bash&lt;br&gt;
alice create&lt;br&gt;
bob create&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Passwords were set. Seed phrases were skipped since this was regtest.&lt;/p&gt;

&lt;p&gt;At first both nodes showed &lt;code&gt;synced_to_chain=false&lt;/code&gt; Still catching up on 101 blocks from yesterday.&lt;/p&gt;

&lt;p&gt;Then suddenly &lt;code&gt;true&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That moment when everything just clicks into place.&lt;/p&gt;

&lt;p&gt;Alice was funded next&lt;/p&gt;

&lt;p&gt;A new address was generated&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;bash&lt;br&gt;
alice newaddress p2wkh&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;1 BTC was sent from the regtest wallet then blocks were mined&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;bash&lt;br&gt;
bitcoin-cli generatetoaddress 6&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;100,000,000 sats appeared&lt;/p&gt;

&lt;p&gt;Still one of the most satisfying moments in the stack&lt;/p&gt;

&lt;p&gt;Lightning setup came next&lt;/p&gt;

&lt;p&gt;Bob’s pubkey was grabbed and nodes were connected&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;bash&lt;br&gt;
alice connect &amp;lt;bob_pubkey&amp;gt;@localhost:9736&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then a channel was opened&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;bash&lt;br&gt;
alice openchannel --node_key=&amp;lt;bob_pubkey&amp;gt; --local_amt=1000000&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;A few blocks later the channel became active&lt;/p&gt;

&lt;p&gt;Alice had 1M sats Bob had 0&lt;/p&gt;

&lt;p&gt;For now&lt;/p&gt;

&lt;p&gt;First payment&lt;/p&gt;

&lt;p&gt;An invoice was created&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;bash&lt;br&gt;
bob addinvoice --amt=50000 --memo="Lightning Coffee"&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Payment went through using &lt;code&gt;payinvoice&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And it worked&lt;/p&gt;

&lt;p&gt;Nothing moved in the channel. Only the state changed.&lt;/p&gt;

&lt;p&gt;That is the system in one line.&lt;/p&gt;

&lt;p&gt;Alice Bob Charlie&lt;/p&gt;

&lt;p&gt;A simple chain formed next&lt;/p&gt;

&lt;p&gt;Alice had no direct channel to Charlie but payment still flowed&lt;/p&gt;

&lt;p&gt;Bob became the router&lt;/p&gt;

&lt;p&gt;A tiny fee was earned for forwarding it&lt;/p&gt;

&lt;p&gt;That is when Lightning stopped feeling like nodes and started feeling like a network&lt;/p&gt;

&lt;p&gt;Polar came next&lt;/p&gt;

&lt;p&gt;After surviving the CLI setup, polar felt like the easy mode reward&lt;/p&gt;

&lt;p&gt;It was not&lt;/p&gt;

&lt;p&gt;Polar depends on Docker containers. Without Docker running properly nothing starts&lt;/p&gt;

&lt;p&gt;So Docker had to be installed and healthy first&lt;/p&gt;

&lt;p&gt;Only then did Polar work&lt;/p&gt;

&lt;p&gt;Two LND nodes were dragged onto a canvas and started&lt;/p&gt;

&lt;p&gt;Everything came up instantly&lt;/p&gt;

&lt;p&gt;No config files, no flags, no terminal juggling&lt;/p&gt;

&lt;p&gt;But under the hood it was still Docker running LND exactly like before&lt;/p&gt;

&lt;p&gt;Polar does not remove complexity. It wraps it&lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem Lightning Solves
&lt;/h3&gt;

&lt;p&gt;Bitcoin on chain does 2-7 transactions per second.Visa does tens of thousands. Blocks take on average 10 minutes to be mined and added to the chain&lt;/p&gt;

&lt;p&gt;That makes real time payments impossible&lt;/p&gt;

&lt;p&gt;Lightning fixes this by moving transactions off chain&lt;/p&gt;

&lt;p&gt;One on chain transaction opens a channel After that payments are instant and nearly free&lt;/p&gt;

&lt;p&gt;Think bar tab You do not swipe for every drink You settle at the end&lt;/p&gt;

&lt;h3&gt;
  
  
  How a Channel Actually Works
&lt;/h3&gt;

&lt;p&gt;A Lightning channel is a 2 of 2 multisig wallet&lt;/p&gt;

&lt;p&gt;Both participants control it Neither can move funds alone&lt;/p&gt;

&lt;p&gt;Every payment updates shared state Old states are invalidated using revocation keys If someone cheats they lose everything&lt;/p&gt;

&lt;p&gt;That is the enforcement layer&lt;/p&gt;

&lt;p&gt;So when sats moved between Alice and Bob nothing changed on chain Only state moved&lt;/p&gt;

&lt;h3&gt;
  
  
  Invoices and the Preimage
&lt;/h3&gt;

&lt;p&gt;Invoices are hash locks&lt;/p&gt;

&lt;p&gt;A secret preimage is generated and only its hash is shared&lt;/p&gt;

&lt;p&gt;Payment locks funds to that hash&lt;/p&gt;

&lt;p&gt;Reveal the preimage and funds are claimed&lt;/p&gt;

&lt;p&gt;It was verified manually by hashing and matching the invoice hash&lt;/p&gt;

&lt;p&gt;It matched exactly&lt;/p&gt;

&lt;p&gt;That is the moment cryptography stops being abstract and starts being real&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi Hop Routing
&lt;/h3&gt;

&lt;p&gt;Alice Bob Charlie&lt;/p&gt;

&lt;p&gt;Alice does not know Charlie Charlie does not know Alice Bob just forwards payments&lt;/p&gt;

&lt;p&gt;Bob earns a small fee&lt;/p&gt;

&lt;p&gt;Each node only sees one hop That is onion routing&lt;/p&gt;

&lt;p&gt;Forwarding history showed the fee instantly&lt;/p&gt;

&lt;p&gt;Small but real&lt;/p&gt;

&lt;p&gt;That is the incentive system working&lt;/p&gt;

&lt;h3&gt;
  
  
  Channel Close Options
&lt;/h3&gt;

&lt;p&gt;Cooperative close is clean both parties agree funds settle on chain&lt;/p&gt;

&lt;p&gt;Force close is fallback one party disappears and state is broadcast then locked by timelock&lt;/p&gt;

&lt;p&gt;Both were tested Cooperative was instant Force close felt like waiting for the chain to settle a dispute&lt;/p&gt;

&lt;p&gt;Both are necessary&lt;/p&gt;

&lt;h3&gt;
  
  
  Why This Matters
&lt;/h3&gt;

&lt;p&gt;Lightning is already live&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Strike enables global Bitcoin payments&lt;/li&gt;
&lt;li&gt;Bitnob enables cross border remittances&lt;/li&gt;
&lt;li&gt;Cash App supports Lightning at scale&lt;/li&gt;
&lt;li&gt;Pick n Pay accepts Bitcoin in stores&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What used to feel experimental is already infrastructure&lt;/p&gt;

&lt;h3&gt;
  
  
  Day 3 Summary
&lt;/h3&gt;

&lt;p&gt;CLI forced everything to be understood flags btcd vs bitcoind ZMQ wallets channels failures&lt;/p&gt;

&lt;p&gt;Polar showed what happens when all of that runs inside Docker with a UI on top&lt;/p&gt;

&lt;p&gt;Both mattered&lt;/p&gt;

&lt;p&gt;Lightning channels are multisig with evolving state Payments are hash locked Routing is trustless Timelocks enforce fairness&lt;/p&gt;

&lt;p&gt;Somewhere between broken configs and running containers money moved without banks without intermediaries and without permission&lt;/p&gt;

&lt;p&gt;Just math doing its job&lt;br&gt;
&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

</description>
      <category>polar</category>
      <category>blockchain</category>
      <category>bitcoin</category>
      <category>lnd</category>
    </item>
    <item>
      <title>Day 2: Bitcoin Explorer - The "I Should Have Just Used Bitcoin-CLI" Edition</title>
      <dc:creator>Samuel Thuku</dc:creator>
      <pubDate>Tue, 09 Jun 2026 21:57:01 +0000</pubDate>
      <link>https://dev.to/samthuku/day-2-bitcoin-explorer-the-i-should-have-just-used-bitcoin-cli-edition-2ig1</link>
      <guid>https://dev.to/samthuku/day-2-bitcoin-explorer-the-i-should-have-just-used-bitcoin-cli-edition-2ig1</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.to/samthuku/from-blocks-to-blinks-my-bitcoin-lightning-network-bootcamp-adventureday-1-5fj3"&gt;Previous day's adventure&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Morning: Docker Debacles
&lt;/h2&gt;

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

&lt;p&gt;Where's the fun in that?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Me, apparently:&lt;/strong&gt; "You know what would make this 100x harder? Docker."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My inner voice, screaming:&lt;/strong&gt; "NOBODY SAYS THAT."&lt;/p&gt;

&lt;p&gt;But I couldn't help myself. I love creating problems where none exist. It's my superpower. And my curse.&lt;/p&gt;

&lt;p&gt;Let me walk you through the &lt;em&gt;hour&lt;/em&gt; I spent trying to get a Bitcoin node running in Docker, as documented by my increasingly unhinged command history:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;2015  E: Unable to locate package doker.io
2016  E: Couldn&lt;span class="s1"&gt;'t find any package by glob '&lt;/span&gt;doker.io&lt;span class="s1"&gt;'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note to self: "docker" has a 'c' in it. Remember this for next time. (I won't.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;After finally installing Docker correctly (spelling matters, who knew?), I pulled the image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;2019  &lt;span class="nb"&gt;sudo &lt;/span&gt;docker pull lncm/bitcoind:v27.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then came the &lt;em&gt;first&lt;/em&gt; attempt at running it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;2020  docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; bitcoin-node &lt;span class="nt"&gt;-p&lt;/span&gt; 18443:18443 lncm/bitcoind:v27.0 bitcoind &lt;span class="nt"&gt;-regtest&lt;/span&gt; &lt;span class="nt"&gt;-rpcbind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.0.0.0 &lt;span class="nt"&gt;-rpcallowip&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.0.0.0/0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Permission denied. Of course.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;2021  &lt;span class="nb"&gt;sudo &lt;/span&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; bitcoin-node &lt;span class="nt"&gt;-p&lt;/span&gt; 18443:18443 lncm/bitcoind:v27.0 bitcoind &lt;span class="nt"&gt;-regtest&lt;/span&gt; &lt;span class="nt"&gt;-rpcbind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.0.0.0 &lt;span class="nt"&gt;-rpcallowip&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.0.0.0/0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Container runs! Then immediately dies. Great.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;2022  &lt;span class="nb"&gt;sudo &lt;/span&gt;docker ps
&lt;span class="c"&gt;# (empty. of course.)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern repeated. A lot. Like, &lt;em&gt;a lot a lot&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;2023  &lt;span class="nb"&gt;sudo &lt;/span&gt;docker run... &lt;span class="o"&gt;(&lt;/span&gt;dies&lt;span class="o"&gt;)&lt;/span&gt;
2024  &lt;span class="nb"&gt;sudo &lt;/span&gt;docker start bitcoin-node &lt;span class="o"&gt;(&lt;/span&gt;fails&lt;span class="o"&gt;)&lt;/span&gt;
2025  &lt;span class="nb"&gt;sudo &lt;/span&gt;docker &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; bitcoin-node &lt;span class="o"&gt;(&lt;/span&gt;rage delete&lt;span class="o"&gt;)&lt;/span&gt;
2026  &lt;span class="nb"&gt;sudo &lt;/span&gt;docker run... &lt;span class="o"&gt;(&lt;/span&gt;dies again&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I went through this loop so many times that I think I achieved some kind of meditative state. The five stages of Docker grief:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Denial:&lt;/strong&gt; "It'll work this time, I just need one more flag"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anger:&lt;/strong&gt; "WHY WON'T YOU STAY RUNNING"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bargaining:&lt;/strong&gt; "Please... I'll use any image... just work..."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Depression:&lt;/strong&gt; &lt;em&gt;stares at terminal for 3 minutes&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Acceptance:&lt;/strong&gt; "Fine, I'll try a different image"
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;2029  &lt;span class="nb"&gt;sudo &lt;/span&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; bitcoin-node &lt;span class="nt"&gt;-p&lt;/span&gt; 18443:18443 lncm/bitcoind:v27.0 bitcoind &lt;span class="nt"&gt;-regtest&lt;/span&gt; &lt;span class="nt"&gt;-rpcbind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.0.0.0 &lt;span class="nt"&gt;-rpcallowip&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.0.0.0/0 &lt;span class="nt"&gt;-rpcuser&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;user &lt;span class="nt"&gt;-rpcpassword&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;pass
2030  &lt;span class="nb"&gt;sudo &lt;/span&gt;docker logs bitcoin-node
&lt;span class="c"&gt;# Error: No such file or directory - something about config&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At some point I switched to the &lt;code&gt;ruimarinho/bitcoin-core&lt;/code&gt; image, then the official &lt;code&gt;bitcoin/bitcoin&lt;/code&gt; image. Same problems, different flavors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The actual issue?&lt;/strong&gt; The command arguments were in the wrong place. The &lt;code&gt;bitcoind&lt;/code&gt; part needed to be &lt;em&gt;after&lt;/em&gt; 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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;2064  &lt;span class="nb"&gt;sudo &lt;/span&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; bitcoin-node &lt;span class="nt"&gt;-p&lt;/span&gt; 18443:18443 bitcoin/bitcoin:27.0 &lt;span class="nt"&gt;-regtest&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nt"&gt;-rpcbind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.0.0.0 &lt;span class="nt"&gt;-rpcallowip&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.0.0.0/0 &lt;span class="nt"&gt;-rpcuser&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;user &lt;span class="nt"&gt;-rpcpassword&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;pass &lt;span class="nt"&gt;-server&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
2065  &lt;span class="nb"&gt;sudo &lt;/span&gt;docker ps
&lt;span class="c"&gt;# FINALLY. A running container.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Victory dance performed. Neighbors concerned.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Cookie Crisis
&lt;/h2&gt;

&lt;p&gt;But wait! The adventure wasn't over. Now I needed to talk to this container.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;2071  docker &lt;span class="nb"&gt;exec &lt;/span&gt;bitcoin-node bitcoin-cli &lt;span class="nt"&gt;-regtest&lt;/span&gt; &lt;span class="nt"&gt;-rpcuser&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;user &lt;span class="nt"&gt;-rpcpassword&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;pass createwallet &lt;span class="s2"&gt;"dev_wallet"&lt;/span&gt;
&lt;span class="c"&gt;# works!&lt;/span&gt;
2074  &lt;span class="nb"&gt;sudo &lt;/span&gt;docker &lt;span class="nb"&gt;exec &lt;/span&gt;bitcoin-node bitcoin-cli &lt;span class="nt"&gt;-regtest&lt;/span&gt; &lt;span class="nt"&gt;-rpcuser&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;user &lt;span class="nt"&gt;-rpcpassword&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;pass getblockchaininfo
&lt;span class="c"&gt;# returns something!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But then... I got lazy. Or forgetful. Or both. (Spoiler: both.)&lt;/p&gt;

&lt;p&gt;Instead of using the &lt;code&gt;-rpcuser&lt;/code&gt; and &lt;code&gt;-rpcpassword&lt;/code&gt; flags every time, I thought I'd set up a &lt;code&gt;bitcoin.conf&lt;/code&gt; file. Classic mistake - assuming configuration works on the first try.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;2085  nano ~/.bashrc
&lt;span class="c"&gt;# added some aliases. felt powerful.&lt;/span&gt;
2091  nano ~/.bitcoin/bitcoin.conf
&lt;span class="c"&gt;# added rpcuser=user, rpcpassword=pass, regtest=1&lt;/span&gt;
2092  bitcoin-cli getblockchaininfo
&lt;span class="c"&gt;# Error: Could not locate RPC credentials. No authentication cookie found.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Turns out, the Docker container doesn't magically know about my local &lt;code&gt;bitcoin.conf&lt;/code&gt;. It has its &lt;em&gt;own&lt;/em&gt; filesystem. Its &lt;em&gt;own&lt;/em&gt; config. Its &lt;em&gt;own&lt;/em&gt; existential crisis about why it was created.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Moral of the story:&lt;/strong&gt; 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?&lt;/p&gt;




&lt;h2&gt;
  
  
  Finally, Some Actual Coding
&lt;/h2&gt;

&lt;p&gt;After the Docker disaster, I needed to write actual Python. Remember the PDF? The challenges?&lt;/p&gt;

&lt;p&gt;I opened &lt;code&gt;explorer_starter.py&lt;/code&gt; and started implementing. The RPC helper was already there (bless the bootcamp authors), but it had a small problem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pass&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;# Missing .json() and return statement
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fixed that. Then came the challenges.&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenge 1: Blockchain Info (Easy mode)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show_blockchaininfo&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;info&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rpc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;getblockchaininfo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Blocks: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;difficulty&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Wait, difficulty? Not blocks?
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h3&gt;
  
  
  Challenge 2: Wallet Balance
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show_wallet_balance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wallet_name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;info&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rpc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;getbalance&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;wallet_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# Fixed: "is not None" not "is not None"
&lt;/span&gt;            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Balance for &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;wallet_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; is: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This worked! Mostly. When the wallet was loaded. Which it wasn't, half the time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenge 3: List Transactions (The Typo Trap)
&lt;/h3&gt;

&lt;p&gt;This one got me good. Look closely:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;category&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;recieve&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;generate&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;immature&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  &lt;span class="c1"&gt;# RECIEVE?!
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I wrote &lt;code&gt;'recieve'&lt;/code&gt; instead of &lt;code&gt;'receive'&lt;/code&gt;. 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 &lt;code&gt;'receive'&lt;/code&gt;, not &lt;code&gt;'recieve'&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So my "IN" transactions were always empty. Everything showed as "OUT". Including incoming money.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That's not ideal for a wallet display.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Wait, why does Alice have negative balance? She just mined 101 blocks!"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Found it. Fixed it. Facepalmed appropriately.&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenge 4: Decode Transaction
&lt;/h3&gt;

&lt;p&gt;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 &lt;code&gt;coinbase&lt;/code&gt; field.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;decode_transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;txid&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rpc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;getrawtransaction&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;txid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Size: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;size&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Inputs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;vin&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;vin&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;coinbase&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;vin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;COINBASE (mining reward) - money printer goes brrr&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;From: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;vin&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;txid&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's like being a blockchain detective. Except instead of solving crimes, you're just... looking at numbers. Glorious numbers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenge 5: Block Details
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show_block&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blockhash&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;blockhash&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;blockhash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rpc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;getbestblockhash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Get the tip if none provided
&lt;/span&gt;    &lt;span class="n"&gt;block&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rpc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;getblock&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;blockhash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;==== Block #&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;height&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; =====&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hash: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;hash&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Time: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;time&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Transactions: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;nTx&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This worked beautifully. I felt like a real blockchain explorer. Not a fake one. A &lt;em&gt;legitimate&lt;/em&gt; one.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bonus Feature: UTXO Explorer
&lt;/h2&gt;

&lt;p&gt;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).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show_wallet_utxos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wallet_name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;utxos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rpc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;listunspent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9999999&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;wallet_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;--- UTXOs for &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;wallet_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; ---&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;utxo&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;utxos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;utxo&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TxID: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;utxo&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;txid&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;... | Amount: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; BTC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Total Balance: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; BTC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

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




&lt;h2&gt;
  
  
  The RPC Helper Evolution
&lt;/h2&gt;

&lt;p&gt;Throughout the day, my &lt;code&gt;rpc()&lt;/code&gt; function got some upgrades:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rpc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://127.0.0.1:18443/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;wallet/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;jsonrpc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;explorer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;method&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;params&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pass&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RPC Error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;result&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Connection error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Error handling! JSON parsing! Proper returns! It's almost production-ready. (Please don't put this in production.)&lt;/p&gt;




&lt;h2&gt;
  
  
  The Final Working Explorer
&lt;/h2&gt;

&lt;p&gt;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:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Show blockchain info&lt;/strong&gt; (chain type, block height, difficulty)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check wallet balances&lt;/strong&gt; (Alice had 101 BTC, Bob had... nothing. Poor Bob.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;List recent transactions&lt;/strong&gt; (with correct IN/OUT directions!)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decode any transaction&lt;/strong&gt; (peer into the matrix)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Display block details&lt;/strong&gt; (hash, height, time, tx count)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Show UTXOs&lt;/strong&gt; (because I'm fancy)&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  What I Learned Today (The Real TL;DR)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Docker is powerful but hates me personally.&lt;/strong&gt; 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?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JSON-RPC is actually pretty straightforward.&lt;/strong&gt; Send JSON, get JSON. No magic. Just data. Beautiful, predictable data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bitcoin transactions are just directed graphs.&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Typos will betray you every time.&lt;/strong&gt; &lt;code&gt;'recieve'&lt;/code&gt; vs &lt;code&gt;'receive'&lt;/code&gt; cost me 20 minutes. Spell check your strings, people.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reading blockchain data is empowering.&lt;/strong&gt; 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.)&lt;/p&gt;




&lt;h2&gt;
  
  
  Tonight's Homework (That I'll Definitely Do)
&lt;/h2&gt;

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




&lt;h2&gt;
  
  
  A Note on the Multisig Section
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;I glanced at the &lt;code&gt;03_multisig.sh&lt;/code&gt; exercise. Then I glanced at the clock. Then I glanced at my coffee cup (empty). Then I decided multisig is a "tomorrow problem."&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Future me is going to be so annoyed at past me.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Stats
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Docker containers created&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker containers killed&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typo-induced debugging sessions&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Times I said "why isn't this working"&lt;/td&gt;
&lt;td&gt;47&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Times the answer was "because Docker"&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lines of Python written&lt;/td&gt;
&lt;td&gt;~150&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UTXOs discovered&lt;/td&gt;
&lt;td&gt;Too many&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sanity remaining&lt;/td&gt;
&lt;td&gt;Minimal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fun had&lt;/td&gt;
&lt;td&gt;Actually, quite a lot&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;&lt;strong&gt;Tomorrow:&lt;/strong&gt; Lightning Network! Where channels open, payments route, and I inevitably break everything again.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Same bat-time, same bat-channel. (But hopefully fewer Docker commands.)&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;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. &lt;code&gt;bitcoin-cli listwallets&lt;/code&gt; is your friend. Use it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;P.P.S. - The cookie file is at &lt;code&gt;~/.bitcoin/regtest/.cookie&lt;/code&gt; if you're running locally. Save yourself the pain I endured.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>bitcoin</category>
      <category>cli</category>
      <category>devjournal</category>
      <category>docker</category>
    </item>
    <item>
      <title>From Blocks to Blinks: My Bitcoin Lightning Network Bootcamp Adventure(Day 1)</title>
      <dc:creator>Samuel Thuku</dc:creator>
      <pubDate>Mon, 08 Jun 2026 16:17:23 +0000</pubDate>
      <link>https://dev.to/samthuku/from-blocks-to-blinks-my-bitcoin-lightning-network-bootcamp-adventureday-1-5fj3</link>
      <guid>https://dev.to/samthuku/from-blocks-to-blinks-my-bitcoin-lightning-network-bootcamp-adventureday-1-5fj3</guid>
      <description>&lt;p&gt;I woke up thinking I was just going to attend a normal Bitcoin bootcamp. By the end of the day I had fought with a build system, wrestled a missing CMake installation, mined coins on a private chain, and casually sent transactions between wallets named Alice and Bob like it was nothing.&lt;/p&gt;

&lt;p&gt;It was the kind of day that feels longer than it actually is not because it was boring, but because every hour had a different kind of problem to solve.&lt;/p&gt;




&lt;h2&gt;
  
  
  The first battle: a machine with no sudo
&lt;/h2&gt;

&lt;p&gt;The day started with what looked simple on paper: install Bitcoin Core and run it in regtest mode. In reality, I was working on a managed machine without sudo access. That one detail changed everything.&lt;/p&gt;

&lt;p&gt;Normally you’d just:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt-get &lt;span class="nb"&gt;install &lt;/span&gt;bitcoin-core
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But that wasn’t an option here. So the entire setup became a kind of “build everything yourself” exercise.&lt;/p&gt;

&lt;p&gt;I cloned the Bitcoin repository and immediately ran into the first wall: dependencies and build tools. CMake, Boost, libevent all the usual suspects. The system version of CMake either wasn’t suitable or wasn’t consistently available, so I had to improvise.&lt;/p&gt;

&lt;p&gt;That turned into a long detour.&lt;/p&gt;




&lt;h2&gt;
  
  
  The CMake scavenger hunt
&lt;/h2&gt;

&lt;p&gt;What followed was basically a mini treasure hunt across installation methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tried system packages (limited by permissions)&lt;/li&gt;
&lt;li&gt;Searched for existing CMake binaries across &lt;code&gt;/usr&lt;/code&gt;, &lt;code&gt;/opt&lt;/code&gt;, and &lt;code&gt;$HOME&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Installed CMake manually using Kitware’s release script&lt;/li&gt;
&lt;li&gt;Then reinstalled it again after path issues&lt;/li&gt;
&lt;li&gt;Fixed PATH in &lt;code&gt;.zshrc&lt;/code&gt; multiple times&lt;/li&gt;
&lt;li&gt;Verified with &lt;code&gt;cmake --version&lt;/code&gt; more times than I care to admit&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At one point I even had multiple competing installations of CMake and had to figure out which one the shell was actually using.&lt;/p&gt;

&lt;p&gt;There was a moment where &lt;code&gt;cmake&lt;/code&gt; existed, but not where Bitcoin Core expected it. Another moment where it existed in &lt;code&gt;$HOME/cmake&lt;/code&gt;, but the PATH pointed somewhere else entirely.&lt;/p&gt;

&lt;p&gt;That part of the day felt less like development and more like debugging my own environment.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bitcoin Core build struggle
&lt;/h2&gt;

&lt;p&gt;Once CMake finally behaved, I tried:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cmake &lt;span class="nt"&gt;-B&lt;/span&gt; build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It failed. Not because Bitcoin Core was broken, but because dependencies were still missing or not properly aligned.&lt;/p&gt;

&lt;p&gt;That led to the next strategy: building via the &lt;code&gt;depends&lt;/code&gt; system.&lt;/p&gt;

&lt;p&gt;So I:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Entered &lt;code&gt;depends/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Ran &lt;code&gt;make -j4&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Attempted to compile dependencies manually&lt;/li&gt;
&lt;li&gt;Rebuilt everything with flags to avoid GUI components (&lt;code&gt;NO_QT=1&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Cleaned build artifacts with &lt;code&gt;git clean -fdx&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Restarted configuration multiple times&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At some point the build directory had been created, deleted, and recreated so many times it stopped feeling like a project folder and started feeling like a scratchpad.&lt;/p&gt;

&lt;p&gt;Eventually, I got to a working configuration using the toolchain file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cmake &lt;span class="nt"&gt;-B&lt;/span&gt; build &lt;span class="nt"&gt;--toolchain&lt;/span&gt; depends/x86_64-pc-linux-gnu/toolchain.cmake
cmake &lt;span class="nt"&gt;--build&lt;/span&gt; build &lt;span class="nt"&gt;-j4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That was the turning point. From there, Bitcoin Core actually compiled.&lt;/p&gt;




&lt;h2&gt;
  
  
  First successful run: regtest mode
&lt;/h2&gt;

&lt;p&gt;Once &lt;code&gt;bitcoind&lt;/code&gt; finally started, everything shifted from setup pain to actual exploration.&lt;/p&gt;

&lt;p&gt;I configured:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;~/.bitcoin/bitcoin.conf&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;regtest mode (so I wasn’t touching real Bitcoin)&lt;/li&gt;
&lt;li&gt;RPC access for CLI interaction&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then I started playing with the node.&lt;/p&gt;

&lt;p&gt;The first real confirmation that everything worked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bitcoin-cli &lt;span class="nt"&gt;-regtest&lt;/span&gt; getblockchaininfo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Seeing a valid response from a freshly built Bitcoin node felt like crossing a threshold. At that point, all the earlier frustration made sense.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wallets, mining, and “fake” Bitcoin that feels real
&lt;/h2&gt;

&lt;p&gt;We created wallets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Alice&lt;/li&gt;
&lt;li&gt;Bob&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then generated addresses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bitcoin-cli &lt;span class="nt"&gt;-regtest&lt;/span&gt; &lt;span class="nt"&gt;-rpcwallet&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;alice getnewaddress
bitcoin-cli &lt;span class="nt"&gt;-regtest&lt;/span&gt; &lt;span class="nt"&gt;-rpcwallet&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;bob getnewaddress
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then came mining:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bitcoin-cli &lt;span class="nt"&gt;-regtest&lt;/span&gt; generatetoaddress 101 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ALICE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reason for 101 blocks was explained: coinbase maturity rules. Only after 100 confirmations do mined rewards become spendable.&lt;/p&gt;

&lt;p&gt;That moment when Alice suddenly had spendable Bitcoin made the abstract idea of mining click properly. It wasn’t just theory anymore. It was observable state changes in a ledger I could control.&lt;/p&gt;




&lt;h2&gt;
  
  
  Sending transactions between Alice and Bob
&lt;/h2&gt;

&lt;p&gt;The next step was moving coins around:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bitcoin-cli &lt;span class="nt"&gt;-regtest&lt;/span&gt; &lt;span class="nt"&gt;-rpcwallet&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;alice sendtoaddress &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BOB&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Watching balances update between wallets felt surprisingly interactive. It wasn’t “simulation” in a toy sense it was a real node executing real consensus rules, just in a private sandbox.&lt;/p&gt;

&lt;p&gt;We checked:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;mempool state&lt;/li&gt;
&lt;li&gt;transaction IDs&lt;/li&gt;
&lt;li&gt;wallet balances&lt;/li&gt;
&lt;li&gt;block confirmations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At one point I tried querying transactions with placeholder TXIDs, broke the command formatting a few times, and had to carefully inspect outputs to understand what actually got recorded.&lt;/p&gt;

&lt;p&gt;That part taught something subtle: Bitcoin CLI is extremely literal. It doesn’t guess intent. It just executes exactly what you pass.&lt;/p&gt;




&lt;h2&gt;
  
  
  Flash quizzes and satoshis
&lt;/h2&gt;

&lt;p&gt;Interspersed with the technical work were flash quizzes. They were fast-paced, and surprisingly fun. Answering correctly sometimes earned satoshis, which added a small but motivating game layer to the learning.&lt;/p&gt;

&lt;p&gt;It changed the tone of the day. Instead of just debugging and reading outputs, there was a competitive rhythm to it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mining, decentralisation, and what actually matters
&lt;/h2&gt;

&lt;p&gt;One of the bigger conceptual takeaways was mining and decentralisation.&lt;/p&gt;

&lt;p&gt;Seeing mining in regtest mode made it clear that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mining is not “creating money from nowhere”&lt;/li&gt;
&lt;li&gt;It’s validating state transitions&lt;/li&gt;
&lt;li&gt;It enforces agreement on history through work&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And decentralisation stopped being a slogan. It became a system where no single command or machine dictates the ledger even though in my setup, I was temporarily “all miners at once.”&lt;/p&gt;

&lt;p&gt;That contrast actually helped understanding rather than hurting it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The quantum computing concern
&lt;/h2&gt;

&lt;p&gt;I had a lingering worry going into the session: if quantum computers become powerful enough, could they break Bitcoin’s cryptography and render it worthless?&lt;/p&gt;

&lt;p&gt;We discussed it, and the key point that stuck was this:&lt;/p&gt;

&lt;p&gt;Bitcoin’s security relies heavily on elliptic curve cryptography (ECC), specifically secp256k1. While quantum computers theoretically pose a threat via Shor’s algorithm, the practical barrier is still enormous. We are not at a point where private keys can be derived from public keys at scale.&lt;/p&gt;

&lt;p&gt;Also, Bitcoin isn’t static. If that ever became a real threat, the protocol could migrate to quantum-resistant signature schemes through upgrades.&lt;/p&gt;

&lt;p&gt;So the fear didn’t disappear, but it became more grounded. Less “Bitcoin will suddenly die”, more “cryptography evolves with threats”.&lt;/p&gt;




&lt;h2&gt;
  
  
  Ending the day
&lt;/h2&gt;

&lt;p&gt;By the end of the session, I had:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Built Bitcoin Core from source under constraints&lt;/li&gt;
&lt;li&gt;Fixed multiple environment/tooling issues without sudo&lt;/li&gt;
&lt;li&gt;Run a regtest node&lt;/li&gt;
&lt;li&gt;Created wallets&lt;/li&gt;
&lt;li&gt;Mined blocks&lt;/li&gt;
&lt;li&gt;Sent transactions&lt;/li&gt;
&lt;li&gt;Explored the blockchain via CLI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But more importantly, I had gone from treating Bitcoin as a peer-to-peer trading tool I occasionally used, to understanding it as a system with rules I could directly interact with.&lt;/p&gt;

&lt;p&gt;And then there’s Lightning Network which is what tomorrow is about. Today was the foundation. Tomorrow is where things start moving fast.&lt;/p&gt;

&lt;p&gt;For a first day, it wasn’t smooth. But it was the kind of messy that actually teaches you something.&lt;/p&gt;

</description>
      <category>bitcoin</category>
      <category>blockchain</category>
      <category>lighteningnetwork</category>
      <category>decentralized</category>
    </item>
    <item>
      <title>Why Does Google Keep Changing My Language? (And How to Fix It)</title>
      <dc:creator>Samuel Thuku</dc:creator>
      <pubDate>Sun, 17 May 2026 09:33:19 +0000</pubDate>
      <link>https://dev.to/samthuku/why-does-google-keep-changing-my-language-and-how-to-fix-it-2ifp</link>
      <guid>https://dev.to/samthuku/why-does-google-keep-changing-my-language-and-how-to-fix-it-2ifp</guid>
      <description>&lt;p&gt;Have you ever searched for something on your browser and suddenly Google decides you now speak a completely different language just because of where you are? 😂&lt;/p&gt;

&lt;p&gt;Maybe you searched for a programming tutorial and got results in French.&lt;br&gt;
Maybe you traveled somewhere and your browser started showing local language websites you never asked for.&lt;br&gt;
Or maybe Google saw your IP address and thought:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Ah yes, this person definitely wants everything translated.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Meanwhile you are just trying to search in peace. 😭&lt;/p&gt;

&lt;p&gt;The good news is that there’s a permanent fix.&lt;/p&gt;

&lt;p&gt;Instead of letting Google guess your language and region based on your location, you can customize the Google search engine URL so your browser always forces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;English results&lt;/li&gt;
&lt;li&gt;English interface&lt;/li&gt;
&lt;li&gt;US-based search ranking&lt;/li&gt;
&lt;li&gt;Minimal personalization&lt;/li&gt;
&lt;li&gt;No annoying country redirects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the best part?&lt;/p&gt;

&lt;p&gt;It does NOT slow down your internet or increase latency.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Permanent Fix
&lt;/h2&gt;

&lt;p&gt;We are going to create a custom Google search engine URL.&lt;/p&gt;

&lt;p&gt;Use this URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://www.google.com/ncr/search?q=%s&amp;amp;hl=en&amp;amp;gl=us&amp;amp;lr=lang_en&amp;amp;pws=0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What each part means:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hl=en
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Forces Google interface language to English.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gl=us
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Makes Google prioritize US-style search results.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lr=lang_en
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prioritizes English-language pages only.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pws=0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reduces personalized search results.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ncr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Means “No Country Redirect.”&lt;br&gt;
Stops Google from redirecting you to country specific domains like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;google.fr&lt;/li&gt;
&lt;li&gt;google.de&lt;/li&gt;
&lt;li&gt;google.co.ke&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Step-by-Step Setup Guide
&lt;/h2&gt;
&lt;h2&gt;
  
  
  Chrome
&lt;/h2&gt;

&lt;p&gt;Open:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chrome://settings/searchEngines
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Click:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Add
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then enter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Search Engine:
Google English
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Shortcut:
g
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;URL:
https://www.google.com/ncr/search?q=%s&amp;amp;hl=en&amp;amp;gl=us&amp;amp;lr=lang_en&amp;amp;pws=0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now click:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Save
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then click the 3 dots beside the new search engine and choose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Make Default
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Done ✅&lt;/p&gt;




&lt;h2&gt;
  
  
  Microsoft Edge
&lt;/h2&gt;

&lt;p&gt;Open:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;edge://settings/searchEngines
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Click:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Add
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Search Engine:
Google English
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Shortcut:
g
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;URL:
https://www.google.com/ncr/search?q=%s&amp;amp;hl=en&amp;amp;gl=us&amp;amp;lr=lang_en&amp;amp;pws=0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save it and set it as default.&lt;/p&gt;

&lt;p&gt;Done ✅&lt;/p&gt;




&lt;h2&gt;
  
  
  Firefox
&lt;/h2&gt;

&lt;p&gt;Open:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;about:preferences#search
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Firefox handles this a little differently.&lt;/p&gt;

&lt;p&gt;Create a new bookmark with this URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://www.google.com/ncr/search?q=%s&amp;amp;hl=en&amp;amp;gl=us&amp;amp;lr=lang_en&amp;amp;pws=0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then assign a keyword like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;g
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now whenever you type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;g your search here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Firefox will use your custom Google setup.&lt;/p&gt;

&lt;p&gt;Done ✅&lt;/p&gt;




&lt;h2&gt;
  
  
  Bonus Tip: Disable Browser Location Access
&lt;/h2&gt;

&lt;p&gt;Even after changing the search URL, some browsers still share your location.&lt;/p&gt;

&lt;p&gt;You can disable that too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chrome / Edge
&lt;/h2&gt;

&lt;p&gt;Open:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chrome://settings/content/location
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;edge://settings/content/location
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then choose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Don't allow sites to see your location
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Done ✅&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Result
&lt;/h2&gt;

&lt;p&gt;After this setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Google stops changing languages randomly&lt;/li&gt;
&lt;li&gt;Search results stay in English&lt;/li&gt;
&lt;li&gt;Country redirects disappear&lt;/li&gt;
&lt;li&gt;Results become more consistent&lt;/li&gt;
&lt;li&gt;Your browser behaves the same no matter where you travel&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No VPN required.&lt;br&gt;
No extensions required.&lt;br&gt;
No slowdown.&lt;/p&gt;

&lt;p&gt;Just clean, predictable Google searches.&lt;/p&gt;




&lt;h2&gt;
  
  
  About the Author
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Written by Samuel Thuku, a software engineer who enjoys solving annoying everyday tech problems, automating things that should have worked properly in the first place, and making the internet a little less chaotic one fix at a time. 😄&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>google</category>
      <category>productivity</category>
      <category>tutorial</category>
      <category>web</category>
    </item>
    <item>
      <title>Battling the Ghost in the Server: A Tale of Rsync, Tildes, and Misleading Banners.</title>
      <dc:creator>Samuel Thuku</dc:creator>
      <pubDate>Fri, 15 May 2026 07:29:11 +0000</pubDate>
      <link>https://dev.to/samthuku/battling-the-ghost-in-the-server-a-tale-of-rsync-tildes-and-misleading-banners-p5a</link>
      <guid>https://dev.to/samthuku/battling-the-ghost-in-the-server-a-tale-of-rsync-tildes-and-misleading-banners-p5a</guid>
      <description>&lt;p&gt;By 4:47 PM, all I wanted was one final win before shutting down my laptop: copy a few files from a remote shared server to my local machine. A task so simple it barely deserved a second thought.&lt;/p&gt;

&lt;p&gt;Or so I believed.&lt;/p&gt;

&lt;p&gt;What started as a routine &lt;code&gt;rsync&lt;/code&gt; command quickly spiraled into a full-blown terminal drama featuring misleading error messages, hostile authentication policies, disappearing sessions, and a server banner that outright lied to my face. Every fix uncovered a new problem, every command felt like a puzzle, and the humble file transfer turned into an unexpected battle against a highly secured shared machine.&lt;/p&gt;

&lt;p&gt;This is the story of how a “five-minute task” became an afternoon-long debugging thriller — and the exact commands, mistakes, and lessons that finally led to victory.&lt;/p&gt;

&lt;p&gt;Today at the office, this "five-minute task" turned into an epic tech thriller. Here is the story of how a routine file transfer turned into a battle of wits against a shared machine, the commands that saved the day, and the valuable lessons learned along the way.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: The First Attempt (The False Start)
&lt;/h2&gt;

&lt;p&gt;Armed with confidence, I opened my terminal. I knew the target IP address (&lt;code&gt;192.168.1.6&lt;/code&gt;) and I knew my destination (the current directory, represented by a trusty dot &lt;code&gt;.&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;I confidently typed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rsync &lt;span class="nt"&gt;-pvarh&lt;/span&gt; 192.168.1.6 &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Climax:
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rsync: [sender] link_stat "/home/username/192.168.1.6" failed: No such file or directory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Lesson:
&lt;/h2&gt;

&lt;p&gt;Terminal networks are literal. Because I forgot the username and the colon (&lt;code&gt;:&lt;/code&gt;), &lt;code&gt;rsync&lt;/code&gt; thought I was looking for a local folder named &lt;code&gt;192.168.1.6&lt;/code&gt; inside my own home directory. Oops.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Fighting the Authentication Loop
&lt;/h2&gt;

&lt;p&gt;Alright, easy fix. I needed to tell &lt;code&gt;rsync&lt;/code&gt; to use SSH to talk to the remote server. I added my username and the IP address.&lt;/p&gt;

&lt;p&gt;But then, a new boss appeared: the corporate password policy.&lt;/p&gt;

&lt;p&gt;Every time I made a slight typo in my password, the server completely dropped my connection instantly. No second chances. No "Please try again." Just a cold, hard termination.&lt;/p&gt;

&lt;p&gt;To bypass this and force the terminal to let me try typing my password properly without dropping the session, I brought out the SSH options flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rsync &lt;span class="nt"&gt;-pvarh&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"ssh -o PubkeyAuthentication=no"&lt;/span&gt; username@192.168.1.6:~ &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By telling &lt;code&gt;rsync&lt;/code&gt; to ignore my local public keys, it forced a direct, clean password prompt.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: The Gaslighting Server Banner
&lt;/h2&gt;

&lt;p&gt;I ran the command. The password prompt appeared. I carefully typed my password and hit Enter.&lt;/p&gt;

&lt;p&gt;Suddenly, my screen flashed this aggressive warning:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;════════════════════════════════════════
User does not exist or password incorrect
════════════════════════════════════════
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I panicked. Did I get locked out? Is my account deleted?&lt;/p&gt;

&lt;p&gt;But wait... right underneath that scary banner, the terminal printed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rsync: [sender] link_stat "/home/username/~" failed: No such file or directory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Plot Twist:
&lt;/h2&gt;

&lt;p&gt;The password was correct! The server had logged me in successfully, but a custom corporate security banner was programmed to print that terrifying "incorrect password" message anyway just to confuse unauthorized guests (and exhausted sysadmins).&lt;/p&gt;

&lt;p&gt;However, a new problem arose: &lt;code&gt;rsync&lt;/code&gt; was looking for a literal folder named &lt;code&gt;~&lt;/code&gt; instead of recognizing it as my home directory. On highly secure shared machines, the shell often blocks shortcut symbols like the tilde (&lt;code&gt;~&lt;/code&gt;) from expanding.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Sweet, Sweet Victory
&lt;/h2&gt;

&lt;p&gt;With the final puzzle piece in place, I realized I had to stop using shortcuts and give the server exact, absolute paths. No more tildes.&lt;/p&gt;

&lt;p&gt;I swapped &lt;code&gt;~&lt;/code&gt; for the full, explicit path &lt;code&gt;/home/username/&lt;/code&gt; and ran the final command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rsync &lt;span class="nt"&gt;-pvarh&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"ssh -o PubkeyAuthentication=no"&lt;/span&gt; username@192.168.1.6:/home/username/ &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Result:
&lt;/h2&gt;

&lt;p&gt;The incremental file list loaded perfectly, the progress bars flew across the screen, and the files safely landed on my local machine. Success!&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Takeaways for Your Next Server Battle
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Colons Matter
&lt;/h2&gt;

&lt;p&gt;Always use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;user@IP:/path
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;for remote targets.&lt;/p&gt;

&lt;p&gt;Without the colon, you are just talking to your own computer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Beware of Banners
&lt;/h2&gt;

&lt;p&gt;Don't trust every error message you read on a shared corporate server; look at the actual terminal output underneath the scary boxes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Be Explicit
&lt;/h2&gt;

&lt;p&gt;Shortcuts like &lt;code&gt;~&lt;/code&gt; are great for local terminal work, but when automating or crossing servers, spell out the full path (like &lt;code&gt;/home/username/&lt;/code&gt;) to avoid confusion.&lt;/p&gt;

</description>
      <category>devjournal</category>
      <category>devops</category>
      <category>linux</category>
      <category>security</category>
    </item>
    <item>
      <title>Stop Solving Solved Problems: Escaping the Cycle of Duplicated Code</title>
      <dc:creator>Samuel Thuku</dc:creator>
      <pubDate>Wed, 11 Mar 2026 07:55:54 +0000</pubDate>
      <link>https://dev.to/samthuku/stop-solving-solved-problems-escaping-the-cycle-of-duplicated-code-3bfa</link>
      <guid>https://dev.to/samthuku/stop-solving-solved-problems-escaping-the-cycle-of-duplicated-code-3bfa</guid>
      <description>&lt;p&gt;It’s a scenario that plays out in countless offices and home offices every day: a software developer stares at a tricky problem, cracks their knuckles, and begins architecting a solution in their mind. They envision the modules, the APIs, and the elegant lines of code that will slay the dragon. Days, or even weeks, later they emerge with a working solution, proud of their creation, only to discover (or have a colleague point out) that an open-source library or internal tool already does the exact same thing.&lt;/p&gt;

&lt;p&gt;Sometimes the existing tool is even better than what the developer built.&lt;/p&gt;

&lt;p&gt;This phenomenon is the source of a well-worn joke in the industry: “Every time a developer encounters a problem, they start building a solution, completely missing that it already exists.” While it’s funny, the reality behind it is a costly and frustrating cycle of duplicated effort. It’s a multifaceted issue rooted in psychology, corporate culture, and sometimes a lack of training. It’s often summarized by the pejorative term “Not Invented Here” (NIH) syndrome.&lt;/p&gt;

&lt;p&gt;The Case for “Proudly Found Elsewhere”&lt;/p&gt;

&lt;p&gt;The cost of this duplication is staggering. It’s not just wasted development hours. It’s the compounded cost of maintaining multiple, slightly different implementations of the same thing. Every bug fix has to be applied in multiple places. Every new developer has to learn multiple systems. Over time, the software ecosystem becomes bloated and brittle.&lt;/p&gt;

&lt;p&gt;This is why a cultural shift toward “Proudly Found Elsewhere” is so important. It’s a mindset that prioritizes solving the business problem over the personal satisfaction of writing every line of code. It recognizes that standing on the shoulders of giants, whether they are open-source maintainers or a team in the next building, leads to faster delivery, higher quality, and more sustainable software.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Build a “Proudly Found Elsewhere” Culture
&lt;/h2&gt;

&lt;p&gt;Moving past NIH syndrome requires a conscious effort from both individuals and organizations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For organizations: Build a marketplace, not a black hole.&lt;/strong&gt;&lt;br&gt;
If you want code to be reused, you have to make it easy to find and easy to use. This means investing in internal component marketplaces where teams can publish their libraries with clear documentation, usage examples, and versioning. It also means fostering a culture of collaboration where teams are encouraged to talk to each other about their technical challenges and share solutions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For organizations: Incentivize reuse.&lt;/strong&gt;&lt;br&gt;
Change the metrics. Reward engineers who contribute to internal libraries and those who choose to reuse them. If a project is delivered faster and with fewer bugs because it leveraged existing components, that should be celebrated as a win, not dismissed as “not doing real work.”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For developers: Ask “Why not?” before “How?”&lt;/strong&gt;&lt;br&gt;
Before writing a single line of code for a common problem like logging, authentication, or data validation, make it a habit to search for existing solutions. Ask your teammates. Check the company wiki. Look for open-source libraries. Assume the solution already exists until you can prove that it doesn’t.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For developers: Contribute, don’t fork.&lt;/strong&gt;&lt;br&gt;
If you find an existing library that covers 80% of what you need, don’t immediately decide to build the remaining 20% from scratch. Consider contributing the missing feature back to the original project. Another option is extending it with a plugin. Improving an existing tool is almost always better than creating a new one that will have to be maintained forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Nuance: When Reinvention Is the Right Choice
&lt;/h2&gt;

&lt;p&gt;It’s important to acknowledge that reinventing the wheel isn’t always a bad thing. Sometimes it leads to innovation. As developer Lea Verou has argued, using a massive, bloated library for a tiny fraction of its features can introduce significant performance overhead and long-term maintenance costs. If you only need 5% of a library’s functionality, writing a small, purpose-built function might actually be the smarter choice.&lt;/p&gt;

&lt;p&gt;For example, parsing a few lines of simple Markdown for bold text and links doesn’t necessarily require a 1,700-line library. A small custom function might do the job more efficiently and with far less complexity.&lt;/p&gt;

&lt;p&gt;The key is intentionality. The goal is to stop the accidental or arrogant reinvention of wheels, the kind that happens because a developer didn’t look, didn’t ask, or didn’t care. By shifting our focus from the pride of creation to the satisfaction of solving real problems efficiently, we can escape this costly cycle.&lt;/p&gt;

&lt;p&gt;We should celebrate finding the perfect existing solution just as much as we celebrate building a new one from scratch.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>softwareengineering</category>
      <category>opensource</category>
      <category>discuss</category>
    </item>
    <item>
      <title>My Brain on Concurrency: Goroutines, Mutexes, and a Coworking Space Analogy</title>
      <dc:creator>Samuel Thuku</dc:creator>
      <pubDate>Thu, 05 Mar 2026 00:09:14 +0000</pubDate>
      <link>https://dev.to/samthuku/my-brain-on-concurrency-goroutines-mutexes-and-a-coworking-space-analogy-4hf4</link>
      <guid>https://dev.to/samthuku/my-brain-on-concurrency-goroutines-mutexes-and-a-coworking-space-analogy-4hf4</guid>
      <description>&lt;p&gt;For the past week, I've been deep in the weeds of Go. I've built a few REST APIs and CLI tools, so I understand the syntax fairly well. But there is a pillar of Go that I kept treating like a black box: Concurrency.&lt;/p&gt;

&lt;p&gt;I understood the value of it, making programs fast by doing multiple things at once but I didn't understand the mechanics.I mapped it to something I deal with every day, a busy coworking space.&lt;/p&gt;

&lt;p&gt;Here is what I learned about Goroutines, Mutexes, and the often overlooked RWMutex, reframed through the lens of freelancers fighting over resources.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The workers: Understanding Goroutines
&lt;/h2&gt;

&lt;p&gt;Imagine a single-threaded program as a co-working space with just one person working there. They have the whole place to themselves. They can spread out, take calls, and use the whiteboard without interruption. It's peaceful, but if they get stuck waiting for a client to email them back, the entire office goes idle.&lt;/p&gt;

&lt;p&gt;Goroutines are like inviting more freelancers into that space.&lt;/p&gt;

&lt;p&gt;Now you have multiple people working on their own projects independently. One is designing a logo, another is writing a blog post, a third is on a sales call. They are all making progress simultaneously. The beauty of Go is that it doesn't necessarily need a separate physical room (OS thread) for each freelancer. The Go runtime acts like a hyper-efficient community manager, shuffling the freelancers onto available desks as needed, allowing thousands of them to coexist without renting a skyscraper.&lt;/p&gt;

&lt;p&gt;Where you actually use this: Think about a web server. In languages like Python or Ruby, each incoming user request typically takes up an entire OS thread, which is heavy and limits how many users you can handle. In Go, each request is handed off to a lightweight Goroutine. This is why Go is so popular for building high-throughput APIs. When thousands of people hit your app at the same time, you're not crashing—you're just activating more freelancers.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The Problem: The Resource Contention
&lt;/h2&gt;

&lt;p&gt;But hiring more freelancers creates a new problem. What happens when multiple freelancers need to use the same shared resource?&lt;/p&gt;

&lt;p&gt;In my coworking space analogy, imagine there is only one premium conference room with a video setup. If two freelancers have back-to-back client calls booked at the exact same time, we have a conflict.&lt;/p&gt;

&lt;p&gt;I wrote a small program to simulate this. I imagined a booking system for that conference room. Two Goroutines (freelancers, Alice and Bob) check the calendar simultaneously. They both see that the 10 AM slot is free. They both book it. Suddenly, we have double-booking chaos. Two clients show up to the same meeting link, and the freelancers look unprofessional.&lt;/p&gt;

&lt;p&gt;This is a Race Condition. Both processes were able to read the calendar at the same time, assumed the resource was available, and acted on that stale information.&lt;/p&gt;

&lt;p&gt;Where you actually see this: This exact scenario happens in e-commerce during flash sales. Imagine 100 people trying to buy the last pair of sneakers. Without protection, your inventory system checks stock for all 100 at once, sees "1 available," and approves every purchase. You've now oversold inventory and have 99 angry customers. The race condition isn't just a coding error, it's a financial disaster.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The Receptionist: Introducing Mutexes
&lt;/h2&gt;

&lt;p&gt;This is where I learned about Mutexes (Mutual Exclusion Locks). A Mutex is like a receptionist at the front desk.&lt;/p&gt;

&lt;p&gt;The rules are simple:&lt;br&gt;
  &lt;strong&gt;The Lock:&lt;/strong&gt; When a freelancer wants to check or modify the conference room calendar, they must go to the receptionist and ask for the physical booking logbook. The receptionist hands it over and tells everyone else to wait.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Work:&lt;/strong&gt; The freelancer can now safely look at the calendar, see the free slot, and write their name down. They know no one else can sneakily modify it while they are writing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Unlock:&lt;/strong&gt; When they are done, they hand the logbook back to the receptionist, who can then give it to the next freelancer waiting in line.&lt;/p&gt;

&lt;p&gt;I refactored my program to introduce a receptionist (the Mutex). By ensuring that only one freelancer could hold the logbook at a time, the chaos stopped.&lt;/p&gt;

&lt;p&gt;The output finally made sense. Alice grabbed the logbook, checked the calendar, booked her slot, and handed it back. Only then did Bob receive the logbook. When Bob looked at the calendar, the 10 AM slot was clearly marked as taken, so he booked the 11 AM slot instead. No double-booking, no chaos.&lt;/p&gt;

&lt;p&gt;Where you actually use this: Any time you have a shared resource that needs to stay consistent. This could be a bank account ledger (ensuring two withdrawals don't overdraft you), a connection pool to a database (ensuring two Goroutines don't grab the same connection), or a simple in-memory cache (Go maps will actually crash if one Goroutine tries to read while another writes). Mutexes are the gatekeepers for your critical data.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The Optimization: The RWMutex (Reader/Writer Mutex)
&lt;/h2&gt;

&lt;p&gt;But then I ran into a performance problem. My receptionist (the standard Mutex) was doing their job too well.&lt;/p&gt;

&lt;p&gt;In my co-working space, we added a bulletin board next to the reception desk. It lists all the upcoming community events. This board is read by dozens of freelancers every hour, but it is only updated once a week by the community manager.&lt;/p&gt;

&lt;p&gt;Under my current receptionist rules, if someone wants to read the bulletin board, they have to take the logbook and lock everyone else out. This means if one person is slowly reading the events, twenty other people can't even glance at it. Worse, if someone is reading it, the community manager can't post the weekly update. Everything is blocked for everyone, even for simple look-ups. My program was running slowly because read operations were queuing up behind each other for no reason.&lt;/p&gt;

&lt;p&gt;This is where I discovered the RWMutex (Read-Write Mutex).&lt;/p&gt;

&lt;p&gt;The RWMutex is a smarter receptionist with different rules for readers and writers:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multiple Readers:&lt;/strong&gt; If a freelancer just wants to look at the bulletin board (a read operation), the receptionist lets them look simultaneously. Ten people can read the board at once without blocking each other.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exclusive Writer:&lt;/strong&gt; However, if the community manager needs to update the board (a write operation), the receptionist becomes strict. They wait for all current readers to finish, then lock the board exclusively. No one can read while the manager is updating, ensuring no one sees a half-finished, inconsistent version of the board.&lt;/p&gt;

&lt;p&gt;When I refactored my code to use an RWMutex, I saw a huge performance boost. For data that was read frequently but written to rarely, the RWMutex allowed hundreds of Goroutines to access it simultaneously. The system only slowed down during the occasional write, which is exactly what I wanted.&lt;/p&gt;

&lt;p&gt;Where you actually use this: Think about a configuration service for a large application. Thousands of microservices might be reading the configuration values every second to know which database to connect to. However, a developer updates the configuration maybe once a day. Using a standard Mutex here would be massive overkill—readers would be blocked waiting for other readers. An RWMutex allows all those read operations to happen in parallel, keeping the system fast while still protecting the occasional write.&lt;br&gt;
Key Takeaways from My Journey&lt;/p&gt;

&lt;p&gt;If you are just starting with Go concurrency, here are the mental notes I wish I had:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Goroutines are like freelancers:&lt;/strong&gt; They allow your program to handle thousands of tasks at once, which is why Go excels at web servers and data pipelines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Race conditions are like double-booking:&lt;/strong&gt; They lead to corrupted data, oversold inventory, and inaccurate balances.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mutexes are the receptionist:&lt;/strong&gt; They ensure that only one task can modify a critical resource at a time, keeping your data safe.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RWMutex is a smarter receptionist:&lt;/strong&gt; It lets multiple readers share the resource for better performance, perfect for configuration data or any read-heavy workload.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use the Race Detector:&lt;/strong&gt; Go's built-in race detector acts like a security camera. It showed me exactly where my double-booking was happening, saving me hours of debugging.&lt;/p&gt;

&lt;p&gt;Concurrency is powerful, but it forces you to think about your program not as a linear script, but as a living system of independent actors competing for shared resources. It's a paradigm shift, but getting these concepts to click was incredibly satisfying.&lt;/p&gt;

&lt;p&gt;_&lt;br&gt;
Thuku Samuel is a software engineer passionate about clean code and Go programming._&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>go</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building a Calculator in Go: A Masterclass in Software Engineering Best Practices</title>
      <dc:creator>Samuel Thuku</dc:creator>
      <pubDate>Mon, 23 Feb 2026 12:06:05 +0000</pubDate>
      <link>https://dev.to/samthuku/building-a-calculator-in-go-a-masterclass-in-software-engineering-best-practices-4fo2</link>
      <guid>https://dev.to/samthuku/building-a-calculator-in-go-a-masterclass-in-software-engineering-best-practices-4fo2</guid>
      <description>&lt;p&gt;When I set out to build a calculator in Go, I thought it would be a weekend project. After all, how complex could a calculator be?&lt;/p&gt;

&lt;p&gt;I could not have been more wrong.&lt;/p&gt;

&lt;p&gt;What started as a simple calculator evolved into a complete software engineering bootcamp. From requirements to architecture, from testing to security, this tiny project taught me more about professional development than many large-scale systems I have worked on.&lt;br&gt;
The Humble Calculator: More Complex Than You Think&lt;/p&gt;

&lt;p&gt;Before writing code, I asked myself: what does a professional calculator actually need?&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Basic arithmetic and advanced functions

A responsive graphical interface

A command-line interface for power users

Calculation history

Cross-platform compatibility

Comprehensive tests

A secure API layer serving both interfaces
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;This became my requirements document, forcing me to think about edge cases and constraints before coding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson 1:&lt;/strong&gt; Even small projects benefit from written requirements.&lt;/p&gt;
&lt;h2&gt;
  
  
  Architecture: Layering Like an Onion
&lt;/h2&gt;

&lt;p&gt;I chose a clean layered architecture:&lt;br&gt;
text&lt;/p&gt;

&lt;p&gt;calculator/&lt;br&gt;
├── cmd/&lt;br&gt;
│   ├── cli/              # CLI entry point&lt;br&gt;
│   └── gui/              # GUI entry point&lt;br&gt;
├── docs/&lt;br&gt;
├── internal/&lt;br&gt;
│   ├── core/              # Core logic&lt;br&gt;
│   ├── parser/            # Expression evaluation&lt;br&gt;
│   ├── service/           # Tokenization&lt;br&gt;
│   ├── ui/                # Fyne interface&lt;br&gt;
│   └── api/               # API layer&lt;br&gt;
├── go.mod&lt;br&gt;
├── go.sum&lt;br&gt;
└── Makefile&lt;/p&gt;

&lt;p&gt;Each layer has a single responsibility. The core knows nothing about the interfaces. The parser evaluates expressions independently. The API layer orchestrates everything securely for both the GUI and CLI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson 2:&lt;/strong&gt; Clean architecture benefits projects of any size.&lt;/p&gt;
&lt;h2&gt;
  
  
  The API Layer: Building a Secure Foundation
&lt;/h2&gt;

&lt;p&gt;The most important decision was creating an API layer between the interfaces and business logic. This single abstraction transformed my application.&lt;/p&gt;

&lt;p&gt;Both interfaces share the exact same underlying logic. But simplicity does not mean sacrificing security. A production-ready API needs multiple layers of protection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Input validation comes first.&lt;/strong&gt; Every expression is checked for empty values, length limits prevent denial of service attacks, and a whitelist ensures only valid characters are accepted. No unexpected symbols, no control characters, nothing that could be used for injection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Structural validation follows.&lt;/strong&gt; The API checks for consecutive operators and ensures parentheses are properly balanced. Only well-formed expressions reach the calculation engine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Panic recovery wraps everything.&lt;/strong&gt; Despite best efforts, bugs happen. The API catches any panics, logs them, and returns friendly errors instead of crashing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Audit logging tracks every request.&lt;/strong&gt; Timestamps and expressions are logged for debugging, performance monitoring, and security forensics. This proved invaluable when users reported unexpected behavior.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Standardized responses ensure consistency.&lt;/strong&gt; Every calculation returns the same structure with result, success indicator, and execution time. Both the GUI and CLI parse the same response format.&lt;/p&gt;

&lt;p&gt;All this complexity remains invisible to both interfaces. The API stays simple while the implementation handles the hard parts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson 3:&lt;/strong&gt; Security is built in from day one, not added later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson 4:&lt;/strong&gt; Good APIs abstract complexity behind simple interfaces.&lt;br&gt;
The Beauty of Shared Logic&lt;/p&gt;

&lt;p&gt;With the API layer in place, both interfaces enjoy several benefits automatically.&lt;/p&gt;

&lt;p&gt;The GUI gets a responsive experience with proper error handling. The CLI gets the same reliability for scripting and automation. When I fixed a bug in the parser, both interfaces benefited immediately. When I added security validation, both interfaces became more secure without any changes to their code.&lt;/p&gt;

&lt;p&gt;This is the power of the API layer. It becomes the single source of truth for calculation logic, and every interface simply plugs into it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson 5:&lt;/strong&gt; Build once, use everywhere.&lt;br&gt;
The History Feature: Ring Buffers&lt;/p&gt;

&lt;p&gt;Displaying the last 3 operations required a fixed-size history. Go's container/ring provided the perfect solution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;go

var operationHistory = ring.New(3)

func addToHistory(expression string, result float64) {
    opString := fmt.Sprintf("%s = %v", expression, result)
    operationHistory.Value = opString
    operationHistory = operationHistory.Next()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When users press equals, operations are added to the ring, automatically overwriting the oldest entry. The GUI displays this history visually, while the CLI could easily add a history command.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson 6:&lt;/strong&gt; Know your standard library.&lt;br&gt;
The UI Challenge: Custom Button Colors&lt;/p&gt;

&lt;p&gt;Fyne is a delightful UI toolkit, but customizing button colors required creativity. The solution stacked colored rectangles behind transparent buttons:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;go

btn := func(label string, textColor color.Color, bgColor color.Color, tapped func()) fyne.CanvasObject {
    txt := canvas.NewText(label, textColor)
    txt.TextStyle = fyne.TextStyle{Bold: true}

    bg := canvas.NewRectangle(bgColor)
    button := widget.NewButton("", tapped)
    button.Importance = widget.LowImportance

    content := container.NewStack(bg, button, txt)
    return withHeight(content, 75)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Number buttons became blue, operators orange, and clear red.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson 7:&lt;/strong&gt; Understanding composition lets you break free from default constraints.&lt;br&gt;
Testing Through the API&lt;/p&gt;

&lt;p&gt;Testing through the public API exercises the entire flow in one go:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;go

func TestAddition(t *testing.T) {
    result, err := api.Calculate("2+2")
    if err != nil || result != 4 {
        t.Errorf("Expected 4, got %v", result)
    }
}

func TestDivisionByZero(t *testing.T) {
    _, err := api.Calculate("5/0")
    if err == nil {
        t.Errorf("Expected error for division by zero")
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When I refactored internal code, tests caught regressions immediately. Because the API serves both interfaces, testing it once covers both the GUI and CLI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson 8:&lt;/strong&gt; Test through your public API.&lt;/p&gt;

&lt;p&gt;Error Handling: Graceful Failure&lt;/p&gt;

&lt;p&gt;A calculator that crashes on invalid input is useless. Every edge case was anticipated:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Division by zero returns an error

Invalid decimals are handled

Consecutive operators are validated

Empty calculations default to zero
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The calculator state remains consistent even after errors, whether accessed through the GUI or CLI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson 9:&lt;/strong&gt; Users will find creative ways to break your software. Handle it gracefully.&lt;br&gt;
The Development Lifecycle&lt;/p&gt;

&lt;p&gt;This project followed the full software lifecycle:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Requirements gathering

Architecture design

API-first implementation

Building both interfaces against the same API

Testing and refactoring

Documentation

Release
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Each phase taught something valuable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson 10:&lt;/strong&gt; Building software is about understanding problems, not just writing code.&lt;br&gt;
The Big Picture&lt;/p&gt;

&lt;p&gt;The API layer transformed my calculator from a single application into a platform serving both a graphical interface and a command-line interface. It enables consistent behavior across interfaces, comprehensive testing, and easy addition of future clients. Each layer does one thing well, and they all work together seamlessly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson 11:&lt;/strong&gt; Think platform, not application.&lt;br&gt;
Key Takeaways&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Start with requirements to prevent scope creep

Architect for change with clean separation of concerns

Build an API layer first even with multiple interfaces

Design for multiple interfaces so business logic remains interface-agnostic

Build once, use everywhere by sharing logic through the API

Know your standard library for perfect data structures

Understand UI composition to break free from defaults

Test through your public API for fearless refactoring

Handle errors gracefully because users break things

Document decisions for your future self

Embrace the full lifecycle as each phase teaches something

Keep APIs simple while implementation handles complexity

Build security in from day one with validation, recovery, and audit trails

Think platform, not application for maximum re-usability
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;A calculator seems simple, but building one properly touches on every aspect of software engineering. It is the perfect project for learning industry best practices without overwhelming complexity.&lt;/p&gt;

&lt;p&gt;The API layer was the unexpected hero. It forced me to think about my software as a platform serving multiple interfaces, made security foundational, simplified testing, and kept both the GUI and CLI beautifully ignorant of underlying complexity.&lt;/p&gt;

&lt;p&gt;If you want to level up your skills, build something simple the right way. Start with requirements, design the API first, then build your interfaces against it. You will be surprised how much you learn.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Happy coding!&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Thuku Samuel is a software engineer passionate about clean code and Go programming.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>discuss</category>
      <category>architecture</category>
      <category>go</category>
    </item>
  </channel>
</rss>
