go-blockchain/docs/history.md
Claude d8eb6c5478
Some checks failed
Security Scan / security (push) Successful in 9s
Test / Test (push) Failing after 30s
docs: archive completed plans, expand history for TUI and difficulty
Move all 21 plan files to docs/plans/completed/ — every phase (0-9) is
implemented. Expand history.md with full writeups for the Difficulty
Computation, V2+ Zarcanum Consensus, and TUI Dashboard sections that
were previously just bullet points.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 13:47:35 +00:00

36 KiB

Project History

Origin

go-blockchain implements the Lethean blockchain protocol in pure Go, following ADR-001 (Go Shell + C++ Crypto Library). The chain lineage is CryptoNote (2014) to IntenseCoin (2017) to Lethean to a Zano rebase. All consensus parameters are derived from the canonical C++ source files currency_config.h.in and default.cmake.

The package was created as part of the broader effort to rewrite the Lethean node tooling in Go, keeping protocol logic in Go while deferring only the mathematically complex cryptographic primitives (ring signatures, bulletproofs, Zarcanum proofs) to a cleaned C++ library via CGo in later phases.


Phase 0 -- Scaffold

Commit: 4c0b7f2 -- feat: Phase 0 scaffold -- config, types, wire, difficulty

Phase 0 established the foundational four packages with complete test suites and full coverage of the consensus-critical configuration surface.

Packages implemented

  • config/ -- All chain constants from currency_config.h.in and default.cmake: tokenomics (Coin, BlockReward, fees, premine), address prefixes (standard, integrated, auditable, auditable integrated), network ports (mainnet 36940-36942, testnet 46940-46942), difficulty parameters (window 720, lag 15, cut 60, targets 120s), block and transaction limits, version constants, PoS parameters, P2P constants, network identity, currency identity, and alias rules. ChainConfig struct with pre-populated Mainnet and Testnet globals. Hardfork schedule (HF0-HF6) with VersionAtHeight() and IsHardForkActive() lookup functions.

  • types/ -- Fixed-size cryptographic types: Hash (32 bytes), PublicKey (32 bytes), SecretKey (32 bytes), KeyImage (32 bytes), Signature (64 bytes). Hex encode/decode for Hash and PublicKey. CryptoNote base58 address encoding with Keccak-256 checksums. Address struct with Encode()/DecodeAddress() round-trip. BlockHeader, Block, Transaction structs. Input types (TxInputGenesis, TxInputToKey) and output types (TxOutputBare, TxOutputZarcanum) with wire type tags. TxInput and TxOutput interfaces.

  • wire/ -- CryptoNote varint encoding (7-bit LEB128 with MSB continuation). EncodeVarint() and DecodeVarint() with sentinel errors for overflow and empty input. Maximum 10 bytes per uint64.

  • difficulty/ -- LWMA difficulty adjustment algorithm. NextDifficulty() examines a window of timestamps and cumulative difficulties to compute the next target. Handles insufficient data (returns StarterDifficulty), zero time spans, and negative difficulty deltas.

Tests added

75 test cases across 5 test files, all passing with the race detector:

  • config/config_test.go -- 7 test functions validating every constant group against C++ source values: tokenomics, address prefixes, ports (mainnet and testnet), difficulty parameters, network identity, ChainConfig struct fields, transaction limits, and transaction version constants.

  • config/hardfork_test.go -- 7 test functions covering VersionAtHeight() on both mainnet and testnet fork schedules, IsHardForkActive() for boundary conditions, unknown version queries, empty fork lists, single-element fork lists, and full fork schedule validation for both networks.

  • types/address_test.go -- 8 test functions covering encode/decode round-trips for all four address types, deterministic encoding, auditable flag detection, integrated prefix detection, invalid input rejection (empty, invalid base58 characters, too short), checksum corruption detection, base58 round-trip, and base58 edge cases (empty encode/decode).

  • difficulty/difficulty_test.go -- 7 test functions covering stable difficulty with constant intervals, empty input, single entry, fast blocks (difficulty increases), slow blocks (difficulty decreases), zero time span handling, and algorithm constants.

  • wire/varint_test.go -- 5 test functions covering encoding known values, decoding known values, round-trip across all bit boundaries (0 through MaxUint64), empty input errors, and overflow detection.

Coverage

Package Coverage
config 100.0%
difficulty 81.0%
types 73.4%
wire 95.2%

go test -race ./... passed clean. go vet ./... produced no warnings.


Phase 1 -- Wire Serialisation

Phase 1 added consensus-critical binary serialisation for blocks and transactions, verified to be bit-identical to the C++ daemon output. The definitive proof is the genesis block hash test: serialising the testnet genesis block and computing its Keccak-256 hash produces the exact value returned by the C++ daemon (cb9d5455ccb79451931003672c405f5e2ac51bff54021aa30bc4499b1ffc4963).

Type corrections from Phase 0

Phase 0 types had several mismatches with the C++ wire format, corrected here:

  • BlockHeader.MinorVersion changed from uint8 to uint64 (varint on wire)
  • BlockHeader.Flags added (uint8, 1 byte fixed)
  • Transaction.Version changed from uint8 to uint64 (varint on wire)
  • Transaction.UnlockTime removed (lives in extra variants, not top-level)
  • All variant tags corrected to match SET_VARIANT_TAGS from currency_basic.h: InputTypeGenesis=0, InputTypeToKey=1, OutputTypeBare=36, OutputTypeZarcanum=38
  • TxOutToKey struct added (public key + mix_attr, 33 bytes packed)
  • TxOutRef variant type added (global index or ref_by_id)
  • Transaction.Signatures, Transaction.Attachment, Transaction.Proofs fields added

Files added

File Purpose
wire/encoder.go Sticky-error streaming encoder
wire/decoder.go Sticky-error streaming decoder
wire/block.go Block/BlockHeader encode/decode
wire/transaction.go Transaction encode/decode (v0/v1 + v2+ stubs)
wire/treehash.go Keccak-256 + CryptoNote Merkle tree hash
wire/hash.go BlockHash, TransactionPrefixHash, TransactionHash
wire/encoder_test.go Encoder round-trip tests
wire/decoder_test.go Decoder round-trip tests
wire/block_test.go Block header + full block round-trip tests
wire/transaction_test.go Coinbase, ToKey, signatures, variant tag tests
wire/treehash_test.go Tree hash for 0-8 hashes
wire/hash_test.go Genesis block hash verification

Key findings

  • Block hash length prefix: The C++ get_object_hash(blobdata) serialises the string through binary_archive before hashing, prepending varint(length). The actual block hash input is varint(len) || block_hashing_blob, not just the blob itself.

  • Genesis data sources: The _genesis_tn.cpp.gen uint64 array is the canonical genesis transaction data, not the .genesis_tn.txt hex dump (which was stale from a different wallet generation).

  • Extra as raw bytes: Transaction extra, attachment, and proofs are stored as opaque raw wire bytes with tag-level boundary detection. This enables bit-identical round-tripping without implementing all 20+ extra variant types.

Coverage

Package Coverage
config 100.0%
difficulty 81.0%
types 73.4%
wire 76.8%

Wire coverage is reduced by v2+ code paths (0% -- Phase 2 scope). Excluding v2+ stubs, the v0/v1 serialisation code exceeds 85% coverage.

Phase 2 -- Crypto Bridge

Phase 2 created the crypto/ package with a CGo bridge to the vendored C++ CryptoNote crypto library. The upstream code (37 files from Zano commit fa1608cf) is built as a static library via CMake, with a thin C API (bridge.h) separating Go from C++ types.

Files added

File Purpose
crypto/upstream/ 37 vendored C++ files (unmodified copies)
crypto/compat/ 11 compatibility stubs replacing epee/Boost
crypto/build/ CMake build output (libcryptonote.a, ~680KB)
crypto/CMakeLists.txt C11/C++17 static library build
crypto/bridge.h Stable C API contract (29 functions)
crypto/bridge.cpp C→C++ wrappers with memcpy marshalling
crypto/doc.go Package documentation + build instructions
crypto/crypto.go CGo flags + FastHash binding
crypto/keygen.go Key generation, derivation, DerivePublicKey/SecretKey
crypto/keyimage.go Key image generation and validation
crypto/signature.go Standard + NLSAG ring signatures
crypto/clsag.go CLSAG (GG/GGX/GGXXG) + cofactor helpers
crypto/proof.go BPP, BPPE, BGE, Zarcanum verification wrappers
crypto/PROVENANCE.md Upstream origin mapping + update workflow
crypto/crypto_test.go 30 tests (all passing)

Key findings

  • CMake build required 5 iterations to resolve all include paths. The upstream files use relative includes (../currency_core/, crypto/crypto-ops.h) that assume the full Zano source tree. Solved with symlinks, additional include paths, and compat stubs.

  • eth_signature.cpp excluded from build -- requires Bitcoin's secp256k1 library which is not needed for CryptoNote consensus crypto.

  • cn_fast_hash name collision between bridge.h and hash-ops.h. Resolved by renaming the bridge wrapper to bridge_fast_hash.

  • Zero key is valid on Ed25519 -- it represents the identity element. Tests use 0xFF...FF for invalid key checks instead.

  • 1/8 premultiplication is critical for CLSAG correctness. On-chain commitments are stored as (1/8)*P. Generate takes full points; verify takes premultiplied values. PointMul8/PointDiv8 helpers convert between forms.

  • Proof verification stubs return "not implemented" -- the serialisation format for BPPE/BGE/Zarcanum proofs requires matching the exact on-chain binary layout, which needs real chain data via RPC (Phase 4).

Tests added

22 test cases in crypto/crypto_test.go:

  • Hashing (2): Known Keccak-256 vector (empty input), non-zero hash
  • Key ops (3): GenerateKeys round-trip, CheckKey negative, uniqueness
  • Key derivation (2): ECDH commutativity, output scanning round-trip (send → receive → derive ephemeral secret → verify public key match)
  • Key images (3): Generation, determinism, invalid input rejection
  • Standard sigs (3): Round-trip, wrong key, wrong message
  • Ring sigs NLSAG (2): 4-member ring round-trip, wrong message
  • CLSAG GG (2): Generate+verify round-trip with cofactor handling, wrong message
  • CLSAG size (2): GGX and GGXXG signature size calculations
  • Proof stubs (3): Skipped -- pending Phase 4 chain data

All passing with -race and go vet.

Phase 3 -- P2P Levin Protocol

Phase 3 implemented the CryptoNote Levin binary protocol for peer-to-peer communication across two repositories. The go-p2p package gained a node/levin/ sub-package with the wire format (header, portable storage, framed TCP connection). The go-blockchain package gained a p2p/ package with command handlers for handshake, timed sync, ping, and block/transaction relay.

Files added (go-p2p)

File Purpose
node/levin/header.go 33-byte Levin header encode/decode
node/levin/header_test.go Header tests (9 tests)
node/levin/varint.go Portable storage varint (2-bit size mark)
node/levin/varint_test.go Varint tests (14 tests)
node/levin/storage.go Portable storage section encode/decode
node/levin/storage_test.go Storage tests (14 tests)
node/levin/connection.go Framed TCP connection
node/levin/connection_test.go Connection tests (7 tests)

Files added/modified (go-blockchain)

File Purpose
config/config.go Added NetworkID constants and ClientVersion
config/config_test.go NetworkID validation test
p2p/commands.go Command ID re-exports
p2p/sync.go CoreSyncData type
p2p/sync_test.go CoreSyncData roundtrip test
p2p/ping.go Ping encode/decode
p2p/ping_test.go Ping tests (2 tests)
p2p/handshake.go Handshake command + NodeData + peerlist decoding
p2p/handshake_test.go Handshake tests (4 tests)
p2p/timedsync.go Timed sync request/response
p2p/relay.go Block/tx relay + chain request/response
p2p/relay_test.go Relay tests (6 tests)
p2p/integration_test.go C++ testnet integration test

Key findings

  • Portable storage varint differs from CryptoNote varint. CryptoNote uses 7-bit LEB128 (implemented in wire/varint.go). Portable storage uses a 2-bit size mark in the low bits of the first byte (1/2/4/8 byte encoding). Both implementations exist in separate packages.

  • POD-as-blob serialisation. Hashes, network IDs, and other fixed-size types are encoded as STRING values containing raw bytes, not as typed fields. The peerlist is a single STRING blob containing packed 24-byte entries (ip:4 + port:4 + id:8 + last_seen:8).

  • Network ID bytes from net_node.inl. Mainnet: byte 10 = 0x00 (not testnet), byte 15 = 0x54 (84 = formation version). Testnet: byte 10 = 0x01, byte 15 = 0x64 (100). These are validated during handshake.

  • P2P port is 46942, not 46941. The testnet ports are: 46940 (stratum), 46941 (RPC), 46942 (P2P).

  • No Transport interface extraction. The existing go-p2p WebSocket transport is tightly coupled to WebSocket semantics. The Levin code lives in node/levin/ as a standalone sub-package rather than sharing an interface.

Tests added

44 go-p2p tests + 13 go-blockchain p2p tests + 1 integration test = 58 total. All passing with -race and go vet.

Phase 4 -- RPC Client

Phase 4 implemented a typed JSON-RPC 2.0 client for querying the Lethean C++ daemon. The rpc/ package provides Go methods for 10 core daemon endpoints covering chain info, block headers, block details, transactions, and mining.

Files added

File Purpose
rpc/client.go Client struct, JSON-RPC 2.0 call() + legacy legacyCall()
rpc/client_test.go Client transport tests (7 tests)
rpc/types.go BlockHeader, DaemonInfo, BlockDetails, TxInfo types
rpc/info.go GetInfo, GetHeight (legacy), GetBlockCount
rpc/info_test.go Info endpoint tests (3 tests)
rpc/blocks.go GetLastBlockHeader, GetBlockHeaderByHeight/ByHash, GetBlocksDetails
rpc/blocks_test.go Block endpoint tests (5 tests)
rpc/transactions.go GetTxDetails, GetTransactions (legacy)
rpc/transactions_test.go Transaction endpoint tests (4 tests)
rpc/mining.go SubmitBlock
rpc/mining_test.go Mining endpoint tests (2 tests)
rpc/integration_test.go Build-tagged integration test against C++ testnet

Tests added

21 mock tests + 1 integration test = 22 total.

Mock tests use httptest.Server with canned JSON responses to verify all 10 endpoints and their error paths. The integration test (//go:build integration) connects to the C++ testnet daemon on localhost:46941 and verifies the genesis block hash matches the Phase 1 result (cb9d5455...).

All passing with -race and go vet.

Key findings

  • Legacy vs JSON-RPC. Two endpoints (GetHeight, GetTransactions) use plain JSON POST to dedicated URI paths rather than JSON-RPC 2.0. The C++ daemon registers these with MAP_URI_AUTO_JON2 (not MAP_JON_RPC), so they are not accessible via /json_rpc. The client provides legacyCall() for these paths alongside call() for standard JSON-RPC.

  • SubmitBlock array params. The submitblock method takes a JSON array ["hexblob"] as its params, not a named object. This is one of the few JSON-RPC 2.0 endpoints in the daemon that uses positional parameters.

  • Status field convention. All daemon responses include a "status": "OK" field outside the JSON-RPC result envelope. The client checks this after successful JSON-RPC decode and returns an error for non-OK status values.

Phase 5 -- Chain Storage and Sync Client

Commit range: 8cb5cb4..23d337e

Added chain/ package implementing blockchain storage and RPC sync client. Uses go-store (pure-Go SQLite) as the persistence backend with five storage groups mapping to the C++ daemon's core containers.

Files added:

  • chain/meta.go -- BlockMeta, TxMeta, outputEntry types
  • chain/chain.go -- Chain struct, New(), Height(), TopBlock()
  • chain/store.go -- Block and transaction storage/retrieval
  • chain/index.go -- Key image and output index operations
  • chain/validate.go -- Header validation (linkage, height, size)
  • chain/sync.go -- RPC sync loop with block processing and indexing
  • chain/chain_test.go -- Storage round-trip tests (6 tests)
  • chain/validate_test.go -- Validation tests (5 tests)
  • chain/sync_test.go -- Sync loop tests with mock RPC (2 tests)
  • chain/integration_test.go -- C++ testnet integration test

Storage schema:

  • blocks -- by height (zero-padded), JSON meta + hex blob
  • block_index -- hash to height
  • transactions -- tx hash to meta + blob
  • spent_keys -- key image to height
  • outputs:{amount} -- per-amount global output index

Dependencies added: forge.lthn.ai/core/go-store (local replace).

Phase 6 -- Wallet Core

Commit range: 5b677d1..11b50d0

Added wallet/ package implementing full send+receive wallet functionality with interface-driven design for v1/v2+ extensibility.

Files added

File Purpose
wallet/wordlist.go 1626-word Electrum mnemonic dictionary
wallet/mnemonic.go 25-word seed phrase encode/decode with CRC32 checksum
wallet/mnemonic_test.go Mnemonic tests (9 tests)
wallet/extra.go TX extra parser for tags 22/14/11
wallet/extra_test.go Extra parsing tests (10 tests)
wallet/account.go Account key management with Argon2id+AES-256-GCM encryption
wallet/account_test.go Account tests (6 tests)
wallet/transfer.go Transfer type and go-store persistence
wallet/transfer_test.go Transfer tests (15 tests)
wallet/scanner.go Scanner interface + V1Scanner (ECDH output detection)
wallet/scanner_test.go Scanner tests (7 tests)
wallet/signer.go Signer interface + NLSAGSigner (CGo ring signatures)
wallet/signer_test.go Signer tests (4 tests)
wallet/ring.go RingSelector interface + RPCRingSelector
wallet/ring_test.go Ring selection tests (3 tests)
wallet/builder.go Builder interface + V1Builder (TX construction)
wallet/builder_test.go Builder tests (3 tests)
wallet/wallet.go Wallet orchestrator (sync, balance, send)
wallet/wallet_test.go Wallet orchestrator tests (2 tests)
wallet/integration_test.go C++ testnet integration test
rpc/wallet.go GetRandomOutputs + SendRawTransaction RPC endpoints
rpc/wallet_test.go RPC wallet endpoint tests (4 tests)

Files modified

File Change
types/types.go Added PublicKey.IsZero() method
crypto/bridge.h Added cn_sc_reduce32() declaration
crypto/bridge.cpp Added cn_sc_reduce32() implementation
crypto/crypto.go Added ScReduce32() Go wrapper

Key findings

  • View key derivation requires sc_reduce32. The raw Keccak-256 of the spend secret key must be reduced modulo the Ed25519 group order before it is a valid scalar. Added cn_sc_reduce32 to the CGo bridge.

  • Interface-driven design. Four core interfaces (Scanner, Signer, Builder, RingSelector) decouple v1 implementations from the orchestrator. Future v2+ (Zarcanum/CLSAG) implementations slot in by implementing the same interfaces.

  • go-store GetAll. Transfer listing uses store.GetAll(group) which returns all key-value pairs in a group, rather than iterating with individual Gets.

  • CryptoNote mnemonic encoding. The 1626-word Electrum dictionary encodes 4 bytes into 3 words using modular arithmetic: val = w1 + n*((n-w1+w2)%n) + n*n*((n-w2+w3)%n) where n=1626. The 25th word is a CRC32 checksum.

Tests added

63 unit tests + 1 integration test = 64 total across wallet/ and rpc/wallet. All passing with -race and go vet.

Phase 7 -- Consensus Rules

Commit range: fa1c127..112da0e (12 commits)

Added standalone consensus/ package implementing three-layer validation (structural, economic, cryptographic) for blocks and transactions. Vendored RandomX PoW hash function into the CGo crypto bridge. Integrated consensus validation into the chain sync pipeline.

Files added

File Purpose
consensus/doc.go Package documentation
consensus/errors.go 21 sentinel error variables
consensus/errors_test.go Error uniqueness tests
consensus/reward.go Block reward with 128-bit size penalty
consensus/reward_test.go Reward calculation tests
consensus/fee.go Fee extraction with overflow detection
consensus/fee_test.go Fee tests
consensus/tx.go Transaction semantic validation (8 checks)
consensus/tx_test.go TX validation tests
consensus/block.go Block validation (timestamp, miner tx, reward)
consensus/block_test.go Block validation tests
consensus/pow.go PoW difficulty check (256-bit comparison)
consensus/pow_test.go PoW tests
consensus/verify.go Signature verification scaffold
consensus/verify_test.go Signature verification tests
consensus/integration_test.go Build-tagged integration test
crypto/randomx/ Vendored RandomX source (26 files)
crypto/pow.go CGo RandomX hash wrapper
crypto/pow_test.go RandomX hash test

Files modified

File Change
crypto/CMakeLists.txt Added RandomX static library build
crypto/bridge.h Added bridge_randomx_hash() declaration
crypto/bridge.cpp Added bridge_randomx_hash() implementation
crypto/crypto.go Added RandomX CGo flags
config/config.go Added MaxTransactionBlobSize constant
chain/sync.go Added SyncOptions, consensus validation calls
chain/sync_test.go Updated for new Sync() signature

Key findings

  • RandomX integration. Vendored the full RandomX source (26 files including x86_64 JIT compiler) into crypto/randomx/. Built as a separate static library (librandomx.a) linked into the CGo bridge. Cache key is "LetheanRandomXv1", input is header_hash(32B) || nonce(8B LE).

  • 128-bit arithmetic for size penalty. The block reward penalty formula uses math/bits.Mul64 and bits.Div64 for 128-bit intermediate products, matching the C++ 128-bit unsigned arithmetic exactly.

  • Standalone package. consensus/ has zero dependencies on chain/ or any storage layer. All functions are pure: take types + config + height, return errors. The RingOutputsFn callback type decouples signature verification from chain storage.

  • Hardfork-aware validation. Every validation function accepts the fork schedule as a parameter. Pre-HF4 and post-HF4 code paths are implemented throughout (input types, fee treatment, output requirements).

Tests added

37 unit tests + 1 integration test = 38 total in consensus/. Coverage: 82.1% of statements.

All passing with -race and go vet.

Phase 8 -- Mining

Solo PoW miner in mining/ package. Fetches block templates from the C++ daemon, computes header mining hash (nonce=0 Keccak-256), grinds nonces with RandomX, submits solutions. Single-threaded loop with poll-based template refresh.

Commits: 8735e53..f9ff8ad on feat/phase8-mining

Known limitations:

  • Single-threaded (CGo RandomX bridge uses static global cache/VM).
  • Solo mining only (no stratum protocol).

V2+ Transaction Serialisation

Completed and verified v2+ (Zarcanum/HF4+) transaction serialisation. The code is tested against a real post-HF4 coinbase transaction from testnet block 101 (tx hash 543bc3c29e9f4c5d1fc566be03fb4da1f2ce2d70d4312fdcc3e4eed7ca3b61e0, 1323 bytes). Round-trip encoding produces byte-identical output and the prefix hash matches the known tx hash.

Bugs fixed

  • V2 suffix field order — was attachment + proofs, corrected to attachment + signatures + proofs (matching C++ serialisation order).
  • TransactionHash — was hashing the full transaction blob; corrected to delegate to TransactionPrefixHash for all versions (matching C++ get_transaction_hash).
  • tagSignedParts (17) — was reading 4 fixed bytes (uint32 LE); corrected to read two varints (n_outs + n_extras), matching C++ VARINT_FIELD.

New features

  • TxInputZC — Zarcanum confidential input (tag 0x25). Wire format: key_offsets + k_image + etc_details (no amount field).
  • SignaturesRaw — new Transaction field for v2+ raw signature bytes.
  • tagZarcanumTxDataV1 (39) — extra variant handler (varint fee).
  • Proof tag handlers (46-48)zc_asset_surjection_proof (vector of BGE_proof_s), zc_outs_range_proof (BPP + aggregation proof), zc_balance_proof (double Schnorr, 96 bytes).
  • Signature tag handlers (42-45)NLSAG_sig, ZC_sig, void_sig, zarcanum_sig with full crypto blob readers for CLSAG GGX/GGXXG, BPP/BPPE.

Files

File Action
types/transaction.go Added TxInputZC, SignaturesRaw field
wire/transaction.go Fixed suffix, added InputTypeZC, 10 tag handlers
wire/transaction_v2_test.go New -- v2 round-trip, hash, fields, prefix tests
wire/hash.go Fixed TransactionHash to delegate to prefix hash

Block Sync (Phase 3 addendum)

Commit range: ca3d8e0..a74ac2e (17 commits)

Wired real NLSAG ring signature verification into the consensus path and implemented full P2P block synchronisation via the Levin protocol. The chain can now sync to tip from either a JSON-RPC daemon or a raw P2P peer, with all blocks validated through the same shared code path.

Highlights

  • NLSAG ring signature verification wired into consensus path
  • Full testnet chain synced via RPC with all blocks validated
  • Ring signature verification passes on all spending transactions
  • P2P block sync via REQUEST_CHAIN / REQUEST_GET_OBJECTS protocol
  • Sparse chain history builder for P2P sync requests
  • Shared processBlockBlobs() for RPC and P2P paths
  • Context cancellation and progress logging for long syncs
  • TxInputZC key image tracking for v2+ transactions
  • Block blob reconstruction from daemon JSON response

Files added

File Purpose
chain/p2p.go P2PConnection interface + LevinP2PConn adapter
chain/p2p_sync.go P2PSync() state machine (REQUEST_CHAIN / REQUEST_GET_OBJECTS loop)
chain/sparse.go SparseChainHistory() exponentially-spaced block hash list
chain/integration_p2p_test.go P2P sync integration test against C++ testnet
p2p/getobjects.go RequestGetObjects (2003) and ResponseGetObjects (2004) types
p2p/getobjects_test.go GetObjects round-trip tests
p2p/integration_chain_test.go Chain request + block fetch integration test

Files modified

File Change
chain/sync.go Extracted processBlockBlobs(), added resolveBlockBlobs(), context support, progress logging
chain/index.go TxInputZC key image tracking
consensus/verify.go verifyV1Signatures() now performs real NLSAG ring signature verification via CGo
p2p/relay.go BlockCompleteEntry, BlockContextInfo types for chain entry responses

Key findings

  • Shared validation path. processBlockBlobs() decodes, validates, and indexes blocks identically for both RPC and P2P sync. This ensures consensus rules are applied regardless of the transport used to fetch blocks.

  • Sparse chain history. SparseChainHistory() builds an exponentially-spaced list of block hashes (every 1, 2, 4, 8, ... blocks back from tip), matching the C++ get_short_chain_history() algorithm. This minimises the data exchanged during the REQUEST_CHAIN handshake.

  • P2PSync state machine. The sync loop issues REQUEST_CHAIN to get a list of chain entry hashes, then fetches blocks in batches via REQUEST_GET_OBJECTS, repeating until the peer has no more blocks.

  • Overlap block skipping. The first block in a REQUEST_GET_OBJECTS response overlaps with the last known block (to confirm chain continuity). This duplicate is detected and skipped during processing.

  • Network ID and serialisation fixes. The C++ daemon is strict about portable storage field ordering and network ID bytes. Several round-trips were needed to match the exact byte layout expected by the C++ peer.


Known Limitations

V2+ spending transactions untested. The v2 coinbase serialisation is verified, but TxInputZC and signature tags 42-45 remain untested with real spending transaction data (no spending txs on testnet yet).

BPP range proof verification tested with real data. The cn_bpp_verify bridge function (Bulletproofs++, 1 delta, bpp_crypto_trait_ZC_out) is verified against a real testnet coinbase transaction from block 101 (post-HF4). The cn_bppe_verify function (Bulletproofs++ Enhanced, 2 deltas, bpp_crypto_trait_Zarcanum) is wired but untested with real data -- it is used for Zarcanum PoS range proofs, not regular transaction output proofs. BGE (one-out-of-many) proofs are wired but untested (coinbase transactions have empty BGE proof vectors). CLSAG GGX/GGXXG verify functions are similarly wired but untested without real ring data. Zarcanum proof verification remains stubbed -- the bridge API needs extending to pass kernel_hash, ring, last_pow_block_id, stake_ki, and pos_difficulty.

CGo toolchain required. The crypto/ package requires CMake, GCC/Clang, OpenSSL, and Boost headers to build libcryptonote.a. Pure Go packages (config/, types/, wire/, difficulty/) remain buildable without a C toolchain.

Base58 uses math/big. The CryptoNote base58 implementation converts each 8-byte block via big.Int arithmetic. This is correct but not optimised for high-throughput scenarios. A future phase may replace this with a lookup-table approach if profiling identifies it as a bottleneck.

Difficulty coverage at 81%. The window-capping branch in NextDifficulty() that limits the window to BlocksCount (735) entries is not fully exercised by the current test suite. Adding test vectors with 1,000+ block entries would cover this path.

Types coverage at 73.4%. Unexported base58 helper functions have branches that are exercised indirectly through the public API but are not fully reached by the current test vectors. Additional edge-case address strings would improve coverage.

Future forks are placeholders. HF3 through HF6 are defined with activation height 999,999,999 on mainnet. These heights will be updated when each fork is scheduled for activation on the live network.

Difficulty Computation

Added local LWMA difficulty computation to the P2P sync pipeline. Previously, blocks fetched via P2P had difficulty=0 hardcoded, causing consensus.CheckDifficulty() to skip validation and cumulative difficulty tracking to remain zero.

Changes

File Change
config/config.go Added BlockTarget = 120 constant (seconds)
chain/difficulty.go Chain.NextDifficulty(height) — reads up to 735 blocks of history, feeds difficulty.NextDifficulty()
chain/p2p_sync.go Replaced difficulty=0 with c.NextDifficulty(blockHeight)
difficulty/difficulty.go Fixed LWMA algorithm to be hardfork-aware (target parameter)

Key findings

  • Lookback window. The LWMA algorithm examines up to BlocksCount (735) previous blocks for timestamps and cumulative difficulties. At chain heights below 735, the full available history is used.

  • Genesis case. Height 0 returns difficulty 1 (no history to compute from).

  • Validated against C++ daemon. P2P-computed difficulties match RPC-provided values for every block on testnet.


V2+ Zarcanum Consensus

Commits: 2026-02-22

Extended the consensus engine to handle v2+ (Zarcanum/HF4+) signature and proof verification. This covers the post-HF4 coinbase transactions with Bulletproofs++ range proofs.

Changes

File Change
consensus/verify.go Added verifyV2Signatures() with CLSAG and proof dispatch
consensus/integration_test.go V2 coinbase verification against testnet block 101

Key findings

  • BPP verified with real data. cn_bpp_verify (Bulletproofs++, 1 delta, bpp_crypto_trait_ZC_out) passes against a real testnet coinbase from block 101 (post-HF4).

  • BPPE, BGE, Zarcanum remain wired but untested. These require spending transaction data (not just coinbase) which does not yet exist on testnet.


TUI Dashboard (Phase 9)

Commits: 2026-02-22..2026-02-23

Added a terminal dashboard for the Lethean Go node, rendered through the core/cli Frame (bubbletea + lipgloss) layout system. Runs as a standalone binary in cmd/chain/.

Architecture

Model library (tui/) with a thin cmd/chain/main.go wiring layer (~175 lines). The binary starts P2P sync in a background goroutine and runs the Frame TUI in the main goroutine.

Layout: "HCF" — header (StatusModel), content (ExplorerModel), footer (KeyHintsModel).

┌─────────────────────────────────────────────────┐
│ Header: StatusModel                             │
│ ⛓ height 6,312 │ sync 100% │ diff 1.2M │ peers │
├─────────────────────────────────────────────────┤
│ Content: ExplorerModel                          │
│ Block list / block detail / tx detail views     │
├─────────────────────────────────────────────────┤
│ Footer: KeyHintsModel                           │
│ ↑/↓ select │ enter view │ esc back │ q quit    │
└─────────────────────────────────────────────────┘

Files added

File Purpose
tui/messages.go Custom bubbletea message types (NodeStatusMsg)
tui/node.go Node wrapper — polls chain state every 2s via bubbletea commands
tui/node_test.go Node wrapper tests
tui/status_model.go StatusModel — chain sync header (height, difficulty, peers)
tui/status_model_test.go Status model tests
tui/explorer_model.go ExplorerModel — block list, block detail, tx detail views
tui/explorer_model_test.go Explorer model navigation and view tests
tui/keyhints_model.go KeyHintsModel — context-sensitive footer key hints
tui/keyhints_model_test.go Key hints tests
cmd/chain/main.go Binary wiring: P2P sync goroutine + Frame TUI

Data flow

  1. cmd/chain/main.go opens SQLite store, creates chain.Chain, starts P2P sync loop in a goroutine (Levin handshake + block fetch, retry on failure).
  2. Node wrapper reads chain height, tip hash, difficulty, and timestamp via bubbletea Tick commands every 2 seconds.
  3. StatusModel renders the header from NodeStatusMsg snapshots.
  4. ExplorerModel queries chain.Chain directly for block lists and detail views (read-only SQLite).
  5. KeyHintsModel renders per-view navigation hints.

Navigation

View Key Action
Block list ↑/↓ Move cursor
Block list PgUp/PgDn Scroll by page
Block list Home Jump to chain tip
Block list Enter Open block detail
Block detail ↑/↓ Select transaction
Block detail Enter Open tx detail
Block detail Esc Back to block list
Tx detail Esc Back to block detail

Dependencies added

  • forge.lthn.ai/core/cli — Frame, FrameModel interface
  • github.com/charmbracelet/bubbletea (transitive via core/cli)
  • github.com/charmbracelet/lipgloss (transitive via core/cli)