Demo build. This is a software product for sale — not an investment, token sale, or financial service. Coins shown run on testnet and have no monetary value. Buyers are responsible for their own licensing, compliance and any mainnet launch.

Demo pool testnet only no real mining revenue
BTC testnet XMR testnet ETC testnet RVN testnet KAS testnet GCC testnet
GCC updates 11 min read

Testnet retrospective: 4 weeks, 3 forks, one good idea.

What broke, what held, and the surprising consensus tweak that came out of week three.

GCC is a fork of Litecoin v0.21.5.5 — Scrypt proof-of-work, 60-second target blocks, gcc1… bech32 addresses, its own genesis block. It runs on testnet only; the coins it produces have no monetary value. This post is the unglamorous builder’s log of bringing that chain up over about four weeks: the two consensus mistakes that cost me whole evenings, the cache problem that made a perfectly healthy chain look dead, and the one structural decision that turned a re-brand from a day of find-and-replace into a single edit.

I’m writing it the way I wish someone had written it for me before I started. If you ever license the stack and stand up a chain of your own, you’ll hit at least two of these three. Forewarned.

01 · Why fork Litecoin at all

The honest answer is that a Scrypt Litecoin fork is the path of least surprise. The codebase is mature, the build is well-trodden, and Scrypt is memory-hard enough to be interesting on commodity hardware without needing the ASIC ecosystem Bitcoin’s SHA-256 assumes. For a demonstrable, re-skinnable stack — which is what GCC is — “boring and well-understood” beats “novel and fragile” every single time.

A fork is not a copy. You change the genesis block, the network magic bytes, the address prefixes, the default ports, the checkpoints, and the chain parameters. Everything else you inherit — including, as I learned, every assumption the original developers baked in about which soft-forks were already active by the time anyone ran the code. That inheritance is exactly where the first two evenings went.

02 · Week one — the empty-block trap

The chain came up. Nodes connected, the miner sidecar ticked over once a minute, blocks appeared in the explorer with sensible heights. By every surface signal it was working. Then I sent the first transaction and it never confirmed. Sent another. Also stuck. The mempool filled; the blocks kept coming; not one transaction made it into a block.

The blocks were empty. Height was climbing, but every block carried only its coinbase.

The cause is a subtle one and it’s worth understanding precisely. When you start a fresh fork, the soft-forks that long-running networks activated years ago — P2SH (BIP16), the BIP34/65/66 family, CSV, and SegWit — are not automatically on. They activate by deployment schedule, and on a brand-new chain that schedule hasn’t run. Here’s the trap: the block assembler in a modern Litecoin/Bitcoin node will happily build a block template, but if SegWit isn’t active it refuses to include SegWit-style transactions — and a modern wallet produces SegWit transactions by default. So the node mines, the wallet broadcasts, and the two are speaking past each other. Empty blocks forever.

The fix is to activate the soft-forks from genesis — set their deployment to always-active in the chain parameters rather than waiting for a signalling window that, on a private testnet with one miner, will never meaningfully happen. You can confirm the state with the deployment info on the chain:

bash
check which soft-forks the node considers active
gcc-cli getdeploymentinfo | grep -A2 '"segwit"'
"active": false <- the empty-block cause
after setting deployments always-active in chainparams + re-genesis
"active": true

03 · Week two — the assert that crash-loops the node

Turning SegWit on fixed the empty blocks. It also, within the hour, introduced a far louder failure: the node started crash-looping on startup. It would come up, begin validating, and die. Restart, validate, die. The logs ended on a VerifyScript assertion — an internal consistency check failing hard rather than returning an error.

This one took me longer than it should have, because the symptom (a crash on script verification) feels unrelated to the change I’d made (enabling SegWit). It isn’t. SegWit’s witness programs assume P2SH semantics underneath them; the script verifier reaches a state it considers impossible if SegWit is active but P2SH is not, and an “impossible” state in consensus code is an assert, not a graceful error. The node isn’t being fragile — it’s correctly refusing to run in a configuration that should never exist on a real network.

Once the full set was active from genesis, the chain behaved: transactions confirmed inside 60 seconds, the explorer showed populated blocks, and the node stopped dying on boot. Two evenings, two consensus lessons, both of which read as one-line parameter changes in hindsight and absolutely did not feel like one-liners at the time.

04 · Week three — the dead chain that wasn’t

By week three the chain was solid, so I did what you do with a chain still in development: I reset it. New genesis, fresh parameters, clean slate. The node came up healthy, mined happily, reported a sensible tip. And the explorer showed a dead chain — frozen at an old height, serving blocks that no longer existed, refusing to advance.

Nothing was wrong with the chain. Everything was wrong with what sat in front of it.

When you re-genesis, the node’s own datadir gets wiped — but the electrs index and the explorer’s cache do not, unless you wipe them yourself. They were faithfully serving the previous chain: an index built against block hashes that the new genesis had invalidated. The node was on chain B; the explorer was narrating chain A from memory. To anyone looking at the public surface, the chain was dead.

The drill I settled on — stop the stack, drop the index and cache volumes, bring it back up to rebuild from the new genesis:

bash
after a re-genesis: wipe index + cache, not just the node
docker compose down
docker volume rm gcc_electrs_db gcc_mempool_cache
docker compose up -d
electrs: reindexing from genesis · tip now tracks node ✓

It’s an obvious failure once you’ve seen it, and an absolutely baffling one the first time. The lesson generalises beyond chains: whenever you reset a source of truth, hunt down every cache that trusted the old one.

05 · The one good idea — a single source of truth

Here’s the part that actually mattered for the product. GCC isn’t an end in itself; it’s the reference chain for a stack that gets re-branded — a buyer takes it and runs their own coin under their own name, ticker, ports and address prefix. The first time I tried to spin up a second chain (the test coins that now run alongside GCC), I did it the naive way: copy the GCC config, find-and-replace the name, the ticker, the magic bytes, the ports, the prefixes, the genesis parameters. It worked, eventually, after I’d missed one prefix in one file and spent half an hour on a chain that wouldn’t accept its own addresses.

That was the moment the design clicked. Every coin-distinguishing value belongs in one file — a coin-params source of truth — and everything else reads from it. Ticker, coin name, network magic, address prefixes, default ports, faucet parameters: one place, one edit. Re-branding stops being a scavenger hunt across a dozen files and becomes a single diff plus a rebuild.

bash
coin-params.env — the entire re-brand surface, one file
COIN_NAME=GetCryptoCoin
COIN_TICKER=GCC
ADDRESS_PREFIX=gcc1
P2P_PORT=9433 RPC_PORT=9332
BLOCK_TARGET_SECONDS=60
change these, rebuild — a whole new coin, no file hunt

The three test coins that now run beside GCC each came up from exactly that pattern: one params file each, no manual edits scattered through the tree, no missed prefixes. The proof that the idea is right is that the second, third and fourth chains were boring to create. Boring is the goal.

06 · What four weeks actually taught me

Two of the three big lessons were consensus traps that masquerade as something they’re not — empty blocks that look like a fee problem, a crash-loop that looks unrelated to the change that caused it. Both came from the same root: a fresh fork doesn’t inherit the activation history of the chain it forked from, only the code. Activate the full soft-fork set from genesis, together, and both problems vanish before they appear.

The third lesson — wipe every cache when you reset the source — is the kind of thing nobody writes down because it feels too obvious to mention, right up until it eats your evening.

And the good idea is the one that pays back every week since: put the re-brand surface in one file. None of this is exciting. All of it is the difference between a stack you can hand to someone else and a pile of config only you can operate. Everything here runs on testnet; the coins are worth nothing and are meant to be. The point was never the coin — it was proving the stack is solid enough to re-skin and run. After three forks and four weeks, it is.

S

AUTHOR

Steven

UK-based solo developer. Builds the white-label crypto stack — website, wallet, blockchain and mining pool — that buyers re-brand and launch. Writes here about the engineering and self-custody discipline behind it.

13Articles
12Years