265 lines
9.7 KiB
Markdown
265 lines
9.7 KiB
Markdown
---
|
|
title: Architecture
|
|
description: Package structure, dependency graph, CGo boundary, and core data structures.
|
|
---
|
|
|
|
# Architecture
|
|
|
|
## Dependency Graph
|
|
|
|
```
|
|
+------------+
|
|
| consensus |
|
|
+------+-----+
|
|
|
|
|
+------------+------------+
|
|
| |
|
|
+-----+-----+ +-----+-----+
|
|
| chain | | difficulty |
|
|
+-----+------+ +-----------+
|
|
|
|
|
+--------+---------+
|
|
| | |
|
|
+----+--+ +---+---+ +---+---+
|
|
| p2p | | rpc | | wallet|
|
|
+----+--+ +---+---+ +---+---+
|
|
| | |
|
|
+--------+---------+
|
|
|
|
|
+----+----+
|
|
| wire |
|
|
+----+----+
|
|
|
|
|
+-------+-------+
|
|
| |
|
|
+---+---+ +-----+-----+
|
|
| types | | config |
|
|
+---+---+ +-----------+
|
|
|
|
|
+---+---+
|
|
| crypto | <-- CGo boundary
|
|
+--------+
|
|
```
|
|
|
|
### Key Relationships
|
|
|
|
- **config** and **types** are leaf packages with no internal dependencies (stdlib only).
|
|
- **wire** depends on **types** for struct definitions and **config** for version-specific serialisation rules.
|
|
- **crypto** wraps the C++ library via CGo; it is used by **wire** (for hashing), **chain** (for signature verification), and **wallet** (for key derivation and tx signing).
|
|
- **p2p**, **rpc**, and **wallet** are higher-level packages that depend on wire-level serialisation.
|
|
- **chain** is the central coordinator: it validates blocks using **consensus** rules, adjusts **difficulty**, and stores state.
|
|
- **consensus** is standalone -- no dependency on **chain** or any storage layer. All functions are pure: they take types, config, and height, returning errors.
|
|
|
|
## Package Details
|
|
|
|
### config/
|
|
|
|
Every consensus-critical constant, derived from the C++ `currency_config.h.in` and `default.cmake`. The `ChainConfig` struct aggregates all parameters:
|
|
|
|
```go
|
|
// Pre-populated globals for each network.
|
|
var Mainnet = config.ChainConfig{
|
|
Name: "Lethean",
|
|
Abbreviation: "LTHN",
|
|
IsTestnet: false,
|
|
Coin: 1_000_000_000_000, // 10^12 atomic units
|
|
BlockReward: 1_000_000_000_000, // 1 LTHN per block
|
|
P2PPort: 36942,
|
|
RPCPort: 36941,
|
|
// ... all other parameters
|
|
}
|
|
```
|
|
|
|
The hardfork schedule is defined separately with lookup functions:
|
|
|
|
```go
|
|
version := config.VersionAtHeight(config.MainnetForks, height)
|
|
active := config.IsHardForkActive(config.MainnetForks, config.HF4Zarcanum, height)
|
|
```
|
|
|
|
### types/
|
|
|
|
Fixed-size byte arrays matching the CryptoNote specification:
|
|
|
|
```go
|
|
type Hash [32]byte // Keccak-256 hash
|
|
type PublicKey [32]byte // Ed25519 public key
|
|
type SecretKey [32]byte // Ed25519 secret key
|
|
type KeyImage [32]byte // Double-spend detection
|
|
type Signature [64]byte // Cryptographic signature
|
|
```
|
|
|
|
All types provide `String()` (hex encoding), `IsZero()`, and `FromHex()` methods.
|
|
|
|
### Block Structure
|
|
|
|
```go
|
|
type BlockHeader struct {
|
|
MajorVersion uint8 // Consensus rules version (0, 1, 2, 3)
|
|
Nonce uint64 // PoW nonce (8 bytes LE on wire)
|
|
PrevID Hash // Previous block hash (32 bytes)
|
|
MinorVersion uint64 // Soft-fork signalling (varint on wire)
|
|
Timestamp uint64 // Unix epoch seconds (varint on wire)
|
|
Flags uint8 // Bit 0: PoS flag (0=PoW, 1=PoS)
|
|
}
|
|
|
|
type Block struct {
|
|
BlockHeader
|
|
MinerTx Transaction // Coinbase transaction
|
|
TxHashes []Hash // Hashes of included transactions
|
|
}
|
|
```
|
|
|
|
The wire serialisation order differs from the struct field order. Canonical format: `major_version`, `nonce`, `prev_id`, `minor_version`, `timestamp`, `flags`.
|
|
|
|
### Transaction Structure
|
|
|
|
```go
|
|
type Transaction struct {
|
|
Version uint64 // 0=genesis, 1=pre-HF4, 2=post-HF4, 3=post-HF5
|
|
Vin []TxInput // Inputs (variant type)
|
|
Vout []TxOutput // Outputs (variant type)
|
|
Extra []byte // Raw wire bytes for bit-identical round-tripping
|
|
HardforkID uint8 // v3+ only
|
|
Signatures [][]Signature // v0/v1 ring signatures
|
|
SignaturesRaw []byte // v2+ raw signature bytes (CLSAG, etc.)
|
|
Attachment []byte // Service attachments
|
|
Proofs []byte // v2+ proofs (BP+, balance, surjection)
|
|
}
|
|
```
|
|
|
|
Wire format differs between versions:
|
|
- **v0/v1:** `version, vin, vout, extra, [signatures, attachment]`
|
|
- **v2+:** `version, vin, extra, vout, [hardfork_id], [attachment, signatures, proofs]`
|
|
|
|
### Input Types
|
|
|
|
| Type | Tag | Description |
|
|
|------|-----|-------------|
|
|
| `TxInputGenesis` | `0x00` | Coinbase input (block height only) |
|
|
| `TxInputToKey` | `0x01` | Standard spend with ring signature |
|
|
| `TxInputZC` | `0x25` | Zarcanum confidential input (no amount field) |
|
|
|
|
### Output Types
|
|
|
|
| Type | Tag | Description |
|
|
|------|-----|-------------|
|
|
| `TxOutputBare` | `0x24` | Transparent output (visible amount) |
|
|
| `TxOutputZarcanum` | `0x26` | Confidential output (Pedersen commitment) |
|
|
|
|
A `TxOutputZarcanum` contains:
|
|
|
|
```go
|
|
type TxOutputZarcanum struct {
|
|
StealthAddress PublicKey // One-time stealth address
|
|
ConcealingPoint PublicKey // Group element Q (premultiplied by 1/8)
|
|
AmountCommitment PublicKey // Pedersen commitment (premultiplied by 1/8)
|
|
BlindedAssetID PublicKey // Asset type blinding (premultiplied by 1/8)
|
|
EncryptedAmount uint64 // XOR-encrypted amount
|
|
MixAttr uint8 // Mixing attribute
|
|
}
|
|
```
|
|
|
|
### Address Encoding
|
|
|
|
Four address types via distinct base58 prefixes:
|
|
|
|
| Type | Prefix | Starts with | Auditable | Integrated |
|
|
|------|--------|-------------|-----------|------------|
|
|
| Standard | `0x1eaf7` | `iTHN` | No | No |
|
|
| Integrated | `0xdeaf7` | `iTHn` | No | Yes |
|
|
| Auditable | `0x3ceff7` | `iThN` | Yes | No |
|
|
| Auditable integrated | `0x8b077` | `iThn` | Yes | Yes |
|
|
|
|
Encoding format:
|
|
```
|
|
base58(varint(prefix) || spend_pubkey(32) || view_pubkey(32) || flags(1) || keccak256_checksum(4))
|
|
```
|
|
|
|
CryptoNote base58 splits input into 8-byte blocks, each encoded independently into 11 characters. Uses legacy Keccak-256 (pre-NIST), not SHA3-256.
|
|
|
|
## wire/
|
|
|
|
Consensus-critical binary serialisation. Key primitives:
|
|
|
|
- **Varint:** 7-bit LEB128 with MSB continuation (same as protobuf). Max 10 bytes per uint64.
|
|
- **Block hash:** `Keccak256(varint(len) || block_hashing_blob)` -- the length prefix comes from the C++ `binary_archive` serialisation of a `blobdata` type.
|
|
- **Tree hash:** CryptoNote Merkle tree over transaction hashes (direct port of `crypto/tree-hash.c`).
|
|
|
|
Extra, attachment, and proof fields are stored as opaque raw wire bytes. This enables bit-identical round-tripping without implementing all 20+ extra variant types.
|
|
|
|
## consensus/
|
|
|
|
Three-layer validation, all hardfork-aware:
|
|
|
|
**Layer 1 -- Structural (no crypto):**
|
|
Transaction size, input/output counts, key image uniqueness, extra parsing, version checks.
|
|
|
|
**Layer 2 -- Economic:**
|
|
Block reward (fixed 1 LTHN with size penalty using 128-bit arithmetic), fee extraction, balance checks. Pre-HF4 fees go to miner; post-HF4 fees are burned.
|
|
|
|
```go
|
|
func BaseReward(height uint64) uint64 {
|
|
if height == 0 {
|
|
return config.Premine // Genesis block: 10M LTHN
|
|
}
|
|
return config.BlockReward // All other blocks: 1 LTHN
|
|
}
|
|
```
|
|
|
|
**Layer 3 -- Cryptographic (CGo):**
|
|
PoW hash verification (RandomX, key `"LetheanRandomXv1"`), NLSAG ring signatures (pre-HF4), CLSAG signatures (post-HF4), Bulletproofs+ range proofs.
|
|
|
|
## chain/
|
|
|
|
Persistent blockchain storage using go-store (pure-Go SQLite). Five storage groups:
|
|
|
|
| Group | Key | Value |
|
|
|-------|-----|-------|
|
|
| `blocks` | Height (zero-padded) | JSON metadata + hex blob |
|
|
| `block_index` | Block hash | Height |
|
|
| `transactions` | Tx hash | JSON metadata + hex blob |
|
|
| `spent_keys` | Key image | Block height |
|
|
| `outputs:{amount}` | Index | Global output entry |
|
|
|
|
Supports two sync modes:
|
|
- **RPC sync:** Poll-based fetching from a JSON-RPC daemon.
|
|
- **P2P sync:** Levin protocol REQUEST_CHAIN / REQUEST_GET_OBJECTS loop.
|
|
|
|
Both share a common `processBlockBlobs()` validation path.
|
|
|
|
## wallet/
|
|
|
|
Interface-driven design with four core abstractions:
|
|
|
|
| Interface | Purpose | v1 Implementation |
|
|
|-----------|---------|-------------------|
|
|
| `Scanner` | Detect owned outputs via ECDH | `V1Scanner` |
|
|
| `Signer` | Produce ring signatures | `NLSAGSigner` |
|
|
| `Builder` | Construct signed transactions | `V1Builder` |
|
|
| `RingSelector` | Pick decoy outputs | `RPCRingSelector` |
|
|
|
|
Account key derivation: `viewSecret = sc_reduce32(Keccak256(spendSecret))`, matching the C++ `account_base::generate()` pattern. 25-word CryptoNote mnemonic encoding using the Electrum 1626-word dictionary.
|
|
|
|
## mining/
|
|
|
|
Solo PoW miner flow:
|
|
|
|
1. `GetBlockTemplate(walletAddr)` -- fetch template from daemon
|
|
2. `HeaderMiningHash(block)` -- Keccak-256 of `BlockHashingBlob` with nonce=0
|
|
3. Nonce loop: `RandomXHash("LetheanRandomXv1", headerHash || nonce_LE)`
|
|
4. `CheckDifficulty(powHash, difficulty)` -- solution found?
|
|
5. `SubmitBlock(hexBlob)` -- submit the solved block
|
|
|
|
## CGo Bridge Design
|
|
|
|
The crypto package follows **ADR-001: Go Shell + C++ Crypto Library**:
|
|
|
|
- `crypto/upstream/` -- 37 vendored C++ files from Zano commit `fa1608cf`
|
|
- `crypto/compat/` -- Compatibility stubs replacing epee/Boost dependencies
|
|
- `crypto/bridge.h` -- Stable C API (29 functions). Only `uint8_t*` pointers cross the boundary.
|
|
- `crypto/randomx/` -- Vendored RandomX source (26 files including x86_64 JIT)
|
|
|
|
Build: `cmake -S crypto -B crypto/build && cmake --build crypto/build --parallel`
|
|
|
|
All curve points on chain are stored premultiplied by the cofactor inverse (1/8). The `PointMul8`/`PointDiv8` helpers convert between representations. CLSAG generate takes full points; CLSAG verify takes premultiplied values.
|